From e5f654a7526786ccabaf901b416eeed86d047ed6 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 5 Mar 2025 09:06:26 -0300 Subject: [PATCH] refactor: sort json before exporting and add tags to flows (#6849) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Enhance flow download with recursive JSON sorting - Add `sortJsonStructure` function to recursively sort object keys and array elements - Improve `downloadFlow` function to create deterministic JSON output - Add error handling for flow download process - Ensure consistent and predictable JSON file generation * feat: Include flow tags in export modal payload Add `tags` to the export modal payload to ensure comprehensive flow metadata is preserved during export * 📝 (model.py): Add tags field to FlowRead and FlowHeader models for better organization and categorization of flows ✨ (use-post-add-flow.ts): Add support for adding tags to a new flow in the frontend ✨ (exportModal/index.tsx): Include tags when exporting a flow in the export modal ♻️ (index.tsx): Refactor ListComponent to remove unnecessary comments and improve code readability 🔧 (reactflowUtils.ts): Update createNewFlow function to include tags field when creating a new flow * style: add blank line --------- Co-authored-by: cristhianzl Co-authored-by: italojohnny --- .../services/database/models/flow/model.py | 2 + .../API/queries/flows/use-post-add-flow.ts | 2 + src/frontend/src/modals/exportModal/index.tsx | 3 +- .../pages/MainPage/components/list/index.tsx | 3 - src/frontend/src/utils/reactflowUtils.ts | 80 ++++++++++++++----- 5 files changed, 66 insertions(+), 24 deletions(-) diff --git a/src/backend/base/langflow/services/database/models/flow/model.py b/src/backend/base/langflow/services/database/models/flow/model.py index 7b154381c..ae20c17e7 100644 --- a/src/backend/base/langflow/services/database/models/flow/model.py +++ b/src/backend/base/langflow/services/database/models/flow/model.py @@ -200,6 +200,7 @@ class FlowRead(FlowBase): id: UUID user_id: UUID | None = Field() folder_id: UUID | None = Field() + tags: list[str] | None = Field(None, description="The tags of the flow") class FlowHeader(BaseModel): @@ -215,6 +216,7 @@ class FlowHeader(BaseModel): endpoint_name: str | None = Field(None, description="The name of the endpoint associated with this flow") description: str | None = Field(None, description="A description of the flow") data: dict | None = Field(None, description="The data of the component, if is_component is True") + tags: list[str] | None = Field(None, description="The tags of the flow") @field_validator("data", mode="before") @classmethod diff --git a/src/frontend/src/controllers/API/queries/flows/use-post-add-flow.ts b/src/frontend/src/controllers/API/queries/flows/use-post-add-flow.ts index 687c23ba4..cb29adc64 100644 --- a/src/frontend/src/controllers/API/queries/flows/use-post-add-flow.ts +++ b/src/frontend/src/controllers/API/queries/flows/use-post-add-flow.ts @@ -15,6 +15,7 @@ interface IPostAddFlow { endpoint_name: string | undefined; icon: string | undefined; gradient: string | undefined; + tags: string[] | undefined; } export const usePostAddFlow: useMutationFunctionType< @@ -34,6 +35,7 @@ export const usePostAddFlow: useMutationFunctionType< icon: payload.icon || null, gradient: payload.gradient || null, endpoint_name: payload.endpoint_name || null, + tags: payload.tags || null, }); return response.data; }; diff --git a/src/frontend/src/modals/exportModal/index.tsx b/src/frontend/src/modals/exportModal/index.tsx index fda72a402..a2d71ff5b 100644 --- a/src/frontend/src/modals/exportModal/index.tsx +++ b/src/frontend/src/modals/exportModal/index.tsx @@ -31,7 +31,6 @@ const ExportModal = forwardRef( currentFlow?.description ?? "", ); const [open, setOpen] = useState(false); - return ( { } group justify-between rounded-lg border border-border p-4 hover:border-placeholder-foreground hover:shadow-sm`} data-testid="list-card" > - {/* left side */}
- {/* Icon */}
{
- {/* right side */}
diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index cf045e1f5..e10c5b639 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -1707,34 +1707,73 @@ export function extractFieldsFromComponenents(data: APIObjectType) { }); return fields; } +/** + * Recursively sorts all object keys and arrays in a JSON structure + * @param obj - The object to sort keys and arrays for + * @returns A new object with sorted keys and arrays + */ +function sortJsonStructure(obj: T): T { + // Handle null case + if (obj === null) { + return obj; + } + // Handle arrays - sort array elements if they are objects + if (Array.isArray(obj)) { + return obj.map((item) => sortJsonStructure(item)) as unknown as T; + } + + // Only process actual objects + if (typeof obj !== "object") { + return obj; + } + + // Create a new object with sorted keys + return Object.keys(obj) + .sort() + .reduce((result, key) => { + // Recursively sort nested objects and arrays + result[key] = sortJsonStructure(obj[key]); + return result; + }, {} as any); +} + +/** + * Downloads the flow as a JSON file with sorted keys and arrays + * @param flow - The flow to download + * @param flowName - The name to use for the flow + * @param flowDescription - Optional description for the flow + */ export function downloadFlow( flow: FlowType, flowName: string, flowDescription?: string, ) { - let clonedFlow = cloneDeep(flow); - removeFileNameFromComponents(clonedFlow); - // create a data URI with the current flow data - const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( - JSON.stringify( - { - ...clonedFlow, - name: flowName, - description: flowDescription, - }, - null, - 2, - ), - )}`; + try { + const clonedFlow = cloneDeep(flow); - // create a link element and set its properties - const link = document.createElement("a"); - link.href = jsonString; - link.download = `${flowName && flowName != "" ? flowName : flow.name}.json`; + removeFileNameFromComponents(clonedFlow); - // simulate a click on the link element to trigger the download - link.click(); + const flowData = { + ...clonedFlow, + name: flowName, + description: flowDescription, + }; + + console.log(flowData); + + const sortedData = sortJsonStructure(flowData); + const sortedJsonString = JSON.stringify(sortedData, null, 2); + + const dataUri = `data:text/json;chatset=utf-8,${encodeURIComponent(sortedJsonString)}`; + const downloadLink = document.createElement("a"); + downloadLink.href = dataUri; + downloadLink.download = `${flowName || flow.name}.json`; + + downloadLink.click(); + } catch (error) { + console.error("Error downloading flow:", error); + } } export function getRandomElement(array: T[]): T { @@ -1760,6 +1799,7 @@ export const createNewFlow = ( is_component: flow?.is_component ?? false, folder_id: folderId, endpoint_name: flow?.endpoint_name ?? undefined, + tags: flow?.tags ?? [], }; };