Skip to content

Commit

Permalink
Added support for template schema (#5812)
Browse files Browse the repository at this point in the history
- load template schema in frontend UI
- display template schema in schema visualizer
- updated types
  • Loading branch information
bilalabbad authored Feb 23, 2025
1 parent 058e0c4 commit b0a2fec
Show file tree
Hide file tree
Showing 19 changed files with 462 additions and 181 deletions.
16 changes: 2 additions & 14 deletions frontend/app/src/entities/nodes/hooks/useObjectItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,12 @@ import {
getObjectRelationships,
} from "@/entities/nodes/object-items/getSchemaObjectColumns";
import { getPermission } from "@/entities/permission/utils";
import {
genericSchemasAtom,
nodeSchemasAtom,
profileSchemasAtom,
} from "@/entities/schema/stores/schema.atom";
import { getSchema } from "@/entities/schema/domain/get-schema";
import { ModelSchema } from "@/entities/schema/types";
import { getTokens } from "@/entities/user-profile/api/getTokens";
import useQuery from "@/shared/api/graphql/useQuery";
import { Filter } from "@/shared/hooks/useFilters";
import { gql } from "@apollo/client";
import { useAtomValue } from "jotai";

const getQuery = (schema?: ModelSchema, filters?: Array<Filter>) => {
if (!schema) return "query {ok}";
Expand All @@ -24,15 +19,8 @@ const getQuery = (schema?: ModelSchema, filters?: Array<Filter>) => {
return getTokens;
}

const nodes = useAtomValue(nodeSchemasAtom);
const generics = useAtomValue(genericSchemasAtom);
const profiles = useAtomValue(profileSchemasAtom);

const kindFilter = filters?.find((filter) => filter.name === "kind__value");

const kindFilterSchema = [...nodes, ...generics, ...profiles].find(
({ kind }) => kind === kindFilter?.value
);
const { schema: kindFilterSchema } = getSchema(kindFilter?.value);

// All the filter values are being sent out as strings inside quotes.
// This will not work if the type of filter value is not string.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import {
relationshipsForTabs,
} from "@/config/constants";
import { ATTRIBUTE_KINDS_FOR_LIST_VIEW } from "@/entities/schema/constants";
import { profileSchemasAtom } from "@/entities/schema/stores/schema.atom";
import { AttributeKind, GenericSchema, ModelSchema, NodeSchema } from "@/entities/schema/types";
import { isGenericSchema } from "@/entities/schema/utils/is-generic-schema";
import { store } from "@/shared/stores";
import { sortByOrderWeight } from "@/shared/utils/common";
import * as R from "ramda";

Expand Down Expand Up @@ -142,84 +140,3 @@ export const getObjectTabs = (tabs: any[], data: any) => {
count: data[tab.name]?.count,
}));
};

// Include current value in the options to make it available in the select component
export const getRelationshipOptions = (row: any, field: any, schemas: any[], generics: any[]) => {
const value = row && (row[field.name]?.node ?? row[field.name]);

if (value?.edges) {
return value.edges.map((edge: any) => ({
name: edge.node.display_label,
id: edge.node.id,
}));
}

const generic = generics.find((generic: any) => generic.kind === field.peer);

if (generic) {
const options = (generic.used_by || []).map((name: string) => {
const profiles = store.get(profileSchemasAtom);

const relatedSchema = [...schemas, ...profiles].find((s: any) => s.kind === name);

if (relatedSchema) {
return {
id: name,
name: relatedSchema.name,
};
}
});

return options;
}

if (!value) {
return [];
}

const option = {
name: value.display_label,
id: value.id,
};

// Initial option for relationships to make the current value available
return [option];
};

type tgetOptionsFromRelationship = {
options: any[];
schemas?: any;
generic?: any;
peerField?: string;
};

export const getOptionsFromRelationship = ({
options,
schemas,
generic,
peerField,
}: tgetOptionsFromRelationship) => {
if (!generic) {
return options.map((option: any) => ({
name: peerField ? (option[peerField]?.value ?? option[peerField]) : option.display_label,
id: option.id,
kind: option.__typename,
}));
}

if (generic) {
return (generic.used_by || []).map((name: string) => {
const relatedSchema = schemas.find((s: any) => s.kind === name);

if (relatedSchema) {
return {
name: relatedSchema.name,
id: name,
kind: relatedSchema.kind,
};
}
});
}

return [];
};
11 changes: 3 additions & 8 deletions frontend/app/src/entities/nodes/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { components } from "@/shared/api/rest/types.generated";

// https://docs.infrahub.app/reference/schema/relationship/#kind
export type RelationshipKind =
| "Generic"
| "Attribute"
| "Component"
| "Parent"
| "Group"
| "Hierarchy"
| "Profile";
export type RelationshipKind = components["schemas"]["RelationshipKind"];

export type NodeCore = {
id: string;
Expand Down
17 changes: 4 additions & 13 deletions frontend/app/src/entities/nodes/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,8 @@ import {
IP_PREFIX_GENERIC,
} from "@/entities/ipam/constants";
import { RESOURCE_GENERIC_KIND } from "@/entities/resource-manager/constants";
import {
genericSchemasAtom,
nodeSchemasAtom,
profileSchemasAtom,
} from "@/entities/schema/stores/schema.atom";
import { isGenericSchema } from "@/entities/schema/utils/is-generic-schema";
import { store } from "@/shared/stores";
import { constructPath, overrideQueryParams } from "../../shared/api/rest/fetch";
import { getSchema } from "@/entities/schema/domain/get-schema";
import { constructPath, overrideQueryParams } from "@/shared/api/rest/fetch";

const regex = /^Related/; // starts with Related

Expand All @@ -39,13 +33,10 @@ export const getObjectDetailsUrl2 = (
]);
}

const nodes = store.get(nodeSchemasAtom);
const generics = store.get(genericSchemasAtom);
const profiles = store.get(profileSchemasAtom);
const schema = [...nodes, ...generics, ...profiles].find(({ kind }) => kind === objectKind);
const { schema, isGeneric } = getSchema(objectKind);
if (!schema) return "#";

if (!isGenericSchema(schema)) {
if (!isGeneric) {
const inheritFrom = schema.inherit_from;

if (inheritFrom?.includes(IP_PREFIX_GENERIC)) {
Expand Down
22 changes: 20 additions & 2 deletions frontend/app/src/entities/schema/domain/get-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,29 @@ import {
genericSchemasAtom,
nodeSchemasAtom,
profileSchemasAtom,
templateSchemasAtom,
} from "@/entities/schema/stores/schema.atom";
import { store } from "@/shared/stores";
import { beforeEach, describe, expect, it } from "vitest";
import {
generateGenericSchema,
generateNodeSchema,
generateProfileSchema,
generateTemplateSchema,
} from "../../../../tests/fake/schema";
import { getSchema } from "./get-schema";

describe("getSchema", () => {
const nodeSchema = generateNodeSchema({ kind: "Node" });
const genericSchema = generateGenericSchema({ kind: "Generic" });
const profileSchema = generateProfileSchema({ kind: "Profile" });
const templateSchema = generateTemplateSchema({ kind: "Template" });

beforeEach(() => {
store.set(nodeSchemasAtom, [nodeSchema]);

store.set(genericSchemasAtom, [genericSchema]);

store.set(profileSchemasAtom, [profileSchema]);
store.set(templateSchemasAtom, [templateSchema]);
});

it("should return null schema when no kind is provided", () => {
Expand All @@ -32,6 +34,7 @@ describe("getSchema", () => {
isGeneric: false,
isNode: false,
isProfile: false,
isTemplate: false,
});
});

Expand All @@ -42,6 +45,7 @@ describe("getSchema", () => {
isGeneric: false,
isNode: true,
isProfile: false,
isTemplate: false,
});
});

Expand All @@ -52,6 +56,7 @@ describe("getSchema", () => {
isGeneric: true,
isNode: false,
isProfile: false,
isTemplate: false,
});
});

Expand All @@ -62,6 +67,18 @@ describe("getSchema", () => {
isGeneric: false,
isNode: false,
isProfile: true,
isTemplate: false,
});
});

it("should return template schema when kind matches a template", () => {
const result = getSchema("Template");
expect(result).toEqual({
schema: templateSchema,
isGeneric: false,
isNode: false,
isProfile: false,
isTemplate: true,
});
});

Expand All @@ -72,6 +89,7 @@ describe("getSchema", () => {
isGeneric: false,
isNode: false,
isProfile: false,
isTemplate: false,
});
});
});
2 changes: 2 additions & 0 deletions frontend/app/src/entities/schema/domain/get-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
genericSchemasAtom,
nodeSchemasAtom,
profileSchemasAtom,
templateSchemasAtom,
} from "@/entities/schema/stores/schema.atom";
import { SchemaResult, resolveSchema } from "@/entities/schema/utils/resolve-schema";
import { store } from "@/shared/stores";
Expand All @@ -11,5 +12,6 @@ export const getSchema = (kind?: string | null): SchemaResult => {
nodeSchemas: store.get(nodeSchemasAtom),
genericSchemas: store.get(genericSchemasAtom),
profileSchemas: store.get(profileSchemasAtom),
templateSchemas: store.get(templateSchemasAtom),
});
};
9 changes: 8 additions & 1 deletion frontend/app/src/entities/schema/stores/schema.atom.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { GenericSchema, Namespace, NodeSchema, ProfileSchema } from "@/entities/schema/types";
import {
GenericSchema,
Namespace,
NodeSchema,
ProfileSchema,
TemplateSchema,
} from "@/entities/schema/types";
import { atom } from "jotai";

export const nodeSchemasAtom = atom<NodeSchema[]>([]);
export const genericSchemasAtom = atom<GenericSchema[]>([]);
export const profileSchemasAtom = atom<ProfileSchema[]>([]);
export const templateSchemasAtom = atom<TemplateSchema[]>([]);
export const namespacesAtom = atom<Namespace[]>([]);

// Current schema hash for tracking changes
Expand Down
3 changes: 2 additions & 1 deletion frontend/app/src/entities/schema/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { components } from "@/shared/api/rest/types.generated";
export type NodeSchema = components["schemas"]["APINodeSchema"];
export type GenericSchema = components["schemas"]["APIGenericSchema"];
export type ProfileSchema = components["schemas"]["APIProfileSchema"];
export type TemplateSchema = components["schemas"]["APITemplateSchema"];

export type ModelSchema = GenericSchema | NodeSchema | ProfileSchema;
export type ModelSchema = GenericSchema | NodeSchema | ProfileSchema | TemplateSchema;

export type RelationshipSchema = components["schemas"]["RelationshipSchema"];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@ import {
namespacesAtom,
nodeSchemasAtom,
profileSchemasAtom,
templateSchemasAtom,
} from "@/entities/schema/stores/schema.atom";
import { schemaKindLabelState } from "@/entities/schema/stores/schemaKindLabel.atom";
import { schemaKindNameState } from "@/entities/schema/stores/schemaKindName.atom";
import { GenericSchema, Namespace, NodeSchema, ProfileSchema } from "@/entities/schema/types";
import {
GenericSchema,
Namespace,
NodeSchema,
ProfileSchema,
TemplateSchema,
} from "@/entities/schema/types";
import { tokenSchema } from "@/entities/user-profile/ui/token-schema";
import { Branch } from "@/shared/api/graphql/generated/graphql";
import { fetchUrl } from "@/shared/api/rest/fetch";
Expand Down Expand Up @@ -41,6 +48,7 @@ export const withSchemaContext = (AppComponent: any) => (props: any) => {
const setGenerics = useSetAtom(genericSchemasAtom);
const setNamespaces = useSetAtom(namespacesAtom);
const setProfiles = useSetAtom(profileSchemasAtom);
const setTemplates = useSetAtom(templateSchemasAtom);
const setState = useSetAtom(stateAtom);
const branches = useAtomValue(branchesState);
const [branchInQueryString] = useQueryParam(QSP.BRANCH, StringParam);
Expand All @@ -54,15 +62,17 @@ export const withSchemaContext = (AppComponent: any) => (props: any) => {
main: string;
nodes: NodeSchema[];
generics: GenericSchema[];
namespaces: Namespace[];
profiles: ProfileSchema[];
templates: TemplateSchema[];
namespaces: Namespace[];
} = await fetchUrl(CONFIG.SCHEMA_URL(branch?.name));

const hash = schemaData.main;
const schema = sortByName([...schemaData.nodes, tokenSchema]);
const generics = sortByName(schemaData.generics || []);
const namespaces = sortByName(schemaData.namespaces || []);
const profiles = sortByName(schemaData.profiles || []);
const templates = sortByName(schemaData.templates || []);
const namespaces = sortByName(schemaData.namespaces || []);

schema.forEach((s) => {
s.attributes = sortByOrderWeight(s.attributes || []);
Expand All @@ -73,12 +83,14 @@ export const withSchemaContext = (AppComponent: any) => (props: any) => {
...schema.map((s) => s.kind),
...generics.map((s) => s.kind),
...profiles.map((s) => s.kind),
...templates.map((s) => s.kind),
];

const schemaNames = [
...schema.map((s) => s.label),
...generics.map((s) => s.label),
...profiles.map((s) => s.label),
...templates.map((s) => s.label),
];
const schemaKindNameTuples = R.zip(schemaKinds, schemaNames);
const schemaKindNameMap = {
Expand All @@ -99,6 +111,7 @@ export const withSchemaContext = (AppComponent: any) => (props: any) => {
setSchemaKindLabelState(schemaKindLabelMap);
setNamespaces(namespaces);
setProfiles(profiles);
setTemplates(templates);
setState({ isReady: true });
} catch (error) {
toast(
Expand Down
3 changes: 3 additions & 0 deletions frontend/app/src/entities/schema/ui/hooks/useSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
genericSchemasAtom,
nodeSchemasAtom,
profileSchemasAtom,
templateSchemasAtom,
} from "@/entities/schema/stores/schema.atom";
import { SchemaResult, resolveSchema } from "@/entities/schema/utils/resolve-schema";
import { useAtomValue } from "jotai/index";
Expand All @@ -10,10 +11,12 @@ export const useSchema = (kind: string | null | undefined): SchemaResult => {
const nodeSchemas = useAtomValue(nodeSchemasAtom);
const profileSchemas = useAtomValue(profileSchemasAtom);
const genericSchemas = useAtomValue(genericSchemasAtom);
const templateSchemas = useAtomValue(templateSchemasAtom);

return resolveSchema(kind, {
nodeSchemas,
genericSchemas,
profileSchemas,
templateSchemas,
});
};
Loading

0 comments on commit b0a2fec

Please sign in to comment.