Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/frontend/src/CustomNodes/helpers/check-code-validity.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { componentsToIgnoreUpdate } from "@/constants/constants";
import type { OutputFieldType } from "@/types/api";
import { resolveTemplateEntry } from "@/utils/component-template-utils";
import type { NodeDataType } from "../../types/flow";

// Returns true if the code is outdated (code string changed and not ignored)
Expand Down Expand Up @@ -53,12 +54,19 @@ const codeHasBreakingChange = (
export const checkCodeValidity = (
data: NodeDataType,
templates: { [key: string]: any },
templatesByModule?: { [key: string]: any },
) => {
if (!data?.node || !templates) return;
const template = templates[data.type]?.template;
const templateEntry = resolveTemplateEntry(
data.node,
templates,
templatesByModule,
data.type,
);
const template = templateEntry?.template;
const currentCode = template?.code?.value;
const thisNodesCode = data.node!.template?.code?.value;
const originalOutputs = templates[data.type]?.outputs;
const originalOutputs = templateEntry?.outputs;
const userOutputs = data.node?.outputs;
const originalTemplate = template;
const userTemplate = data.node?.template;
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/stores/__tests__/flowStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ jest.mock("../typesStore", () => ({
useTypesStore: {
getState: () => ({
templates: {},
templatesByModule: {},
types: {},
}),
},
Expand Down
16 changes: 16 additions & 0 deletions src/frontend/src/stores/__tests__/typesStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,17 @@ jest.mock("../../utils/reactflowUtils", () => ({
}),
}));

jest.mock("../../utils/component-template-utils", () => ({
buildTemplatesByModule: jest.fn(() => ({})),
}));

// Mock imports
const mockExtractSecretFieldsFromComponents =
require("../../utils/reactflowUtils").extractSecretFieldsFromComponents;
const mockTemplatesGenerator =
require("../../utils/reactflowUtils").templatesGenerator;
const mockBuildTemplatesByModule =
require("../../utils/component-template-utils").buildTemplatesByModule;
const mockTypesGenerator = require("../../utils/reactflowUtils").typesGenerator;

const mockAPIData: APIDataType = {
Expand Down Expand Up @@ -85,13 +91,15 @@ describe("useTypesStore", () => {
return new Set(Object.keys(data));
});
mockTemplatesGenerator.mockReturnValue({});
mockBuildTemplatesByModule.mockReturnValue({});
mockTypesGenerator.mockReturnValue({});

act(() => {
useTypesStore.setState({
ComponentFields: new Set(),
types: {},
templates: {},
templatesByModule: {},
data: {},
});
});
Expand All @@ -104,6 +112,7 @@ describe("useTypesStore", () => {
expect(result.current.ComponentFields).toEqual(new Set());
expect(result.current.types).toEqual({});
expect(result.current.templates).toEqual({});
expect(result.current.templatesByModule).toEqual({});
expect(result.current.data).toEqual({});
});
});
Expand Down Expand Up @@ -209,6 +218,9 @@ describe("useTypesStore", () => {
it("should set types, templates, data, and component fields", () => {
mockTypesGenerator.mockReturnValue(mockTypes);
mockTemplatesGenerator.mockReturnValue(mockTemplates);
mockBuildTemplatesByModule.mockReturnValue({
"module.text": mockTemplates.TextInput,
});
mockExtractSecretFieldsFromComponents.mockReturnValue(
new Set(["TextInput", "NumberInput"]),
);
Expand All @@ -221,12 +233,16 @@ describe("useTypesStore", () => {

expect(mockTypesGenerator).toHaveBeenCalledWith(mockAPIData);
expect(mockTemplatesGenerator).toHaveBeenCalledWith(mockAPIData);
expect(mockBuildTemplatesByModule).toHaveBeenCalledWith(mockAPIData);
expect(mockExtractSecretFieldsFromComponents).toHaveBeenCalledWith(
mockAPIData,
);

expect(result.current.types).toEqual(mockTypes);
expect(result.current.templates).toEqual(mockTemplates);
expect(result.current.templatesByModule).toEqual({
"module.text": mockTemplates.TextInput,
});
expect(result.current.data).toEqual(mockAPIData);
expect(result.current.ComponentFields).toEqual(
new Set(["TextInput", "NumberInput"]),
Expand Down
7 changes: 6 additions & 1 deletion src/frontend/src/stores/flowStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,14 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
updateComponentsToUpdate: (nodes) => {
const outdatedNodes: ComponentsToUpdateType[] = [];
const templates = useTypesStore.getState().templates;
const templatesByModule = useTypesStore.getState().templatesByModule;
nodes.forEach((node) => {
if (node.type === "genericNode") {
const codeValidity = checkCodeValidity(node.data, templates);
const codeValidity = checkCodeValidity(
node.data,
templates,
templatesByModule,
);
if (codeValidity && codeValidity.outdated)
outdatedNodes.push({
id: node.id,
Expand Down
3 changes: 3 additions & 0 deletions src/frontend/src/stores/typesStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
templatesGenerator,
typesGenerator,
} from "../utils/reactflowUtils";
import { buildTemplatesByModule } from "../utils/component-template-utils";

export const useTypesStore = create<TypesStoreType>((set, get) => ({
ComponentFields: new Set(),
Expand All @@ -17,6 +18,7 @@ export const useTypesStore = create<TypesStoreType>((set, get) => ({
},
types: {},
templates: {},
templatesByModule: {},
data: {},
setTypes: (data: APIDataType) => {
set((old) => ({
Expand All @@ -27,6 +29,7 @@ export const useTypesStore = create<TypesStoreType>((set, get) => ({
...data,
}),
templates: templatesGenerator(data),
templatesByModule: buildTemplatesByModule(data),
}));
},
setTemplates: (newState: {}) => {
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/types/zustand/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export type TypesStoreType = {
types: { [char: string]: string };
setTypes: (newState: {}) => void;
templates: { [char: string]: APIClassType };
templatesByModule: { [module: string]: APIClassType };
setTemplates: (newState: {}) => void;
data: APIDataType;
setData: (newState: {}) => void;
Expand Down
31 changes: 31 additions & 0 deletions src/frontend/src/utils/component-template-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { APIClassType, APIKindType, APIObjectType } from "@/types/api";

export function buildTemplatesByModule(
data: APIObjectType,
): Record<string, APIClassType> {
return Object.keys(data).reduce((acc, curr) => {
Object.values(data[curr]).forEach((component: APIKindType[keyof APIKindType]) => {
const moduleName = component?.metadata?.module;
if (moduleName && !acc[moduleName]) {
acc[moduleName] = component as APIClassType;
}
Comment on lines +7 to +11

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep per-module mappings from colliding

The new module map only retains the first component for each metadata.module, so any additional components in the same module will be resolved to the wrong template. The repository already ships component metadata where many components share a module (e.g. src/lfx/src/lfx/_assets/component_index.json reports far fewer modules than components), so resolveTemplateEntry will return a mismatched template for most components in those modules. That will make checkCodeValidity compare a node’s code/outputs against the wrong template, producing false “update available” banners or missing real updates for those components. Consider keying by {module, type} or storing a module→type map rather than a single component per module.

Useful? React with 👍 / 👎.

});
return acc;
}, {} as Record<string, APIClassType>);
}

export function resolveTemplateEntry(
node: { metadata?: { module?: string } } | undefined,
templatesByName: Record<string, APIClassType>,
templatesByModule?: Record<string, APIClassType>,
typeKey?: string,
): APIClassType | undefined {
const moduleName = node?.metadata?.module;
if (moduleName && templatesByModule?.[moduleName]) {
return templatesByModule[moduleName];
}
if (typeKey) {
return templatesByName[typeKey];
}
return undefined;
}
12 changes: 12 additions & 0 deletions src/frontend/src/utils/reactflowUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1838,6 +1838,18 @@ export function templatesGenerator(data: APIObjectType) {
}, {});
}

export function templatesByModuleGenerator(data: APIObjectType) {
return Object.keys(data).reduce((acc, curr) => {
Object.values(data[curr]).forEach((component) => {
const moduleName = component?.metadata?.module;
if (moduleName && !acc[moduleName]) {
acc[moduleName] = component;
}
});
return acc;
}, {} as Record<string, APIClassType>);
}

/**
* Determines if a field is a SecretStr field type
*/
Expand Down
Loading