refactor: sort json before exporting and add tags to flows (#6849)
* 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 <cristhian.lousa@gmail.com> Co-authored-by: italojohnny <italojohnnydosanjos@gmail.com>
This commit is contained in:
parent
1cc49962d5
commit
e5f654a752
5 changed files with 66 additions and 24 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ const ExportModal = forwardRef(
|
|||
currentFlow?.description ?? "",
|
||||
);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
size="smaller-h-full"
|
||||
|
|
@ -48,6 +47,7 @@ const ExportModal = forwardRef(
|
|||
last_tested_version: version,
|
||||
endpoint_name: currentFlow!.endpoint_name,
|
||||
is_component: false,
|
||||
tags: currentFlow!.tags,
|
||||
},
|
||||
name!,
|
||||
description,
|
||||
|
|
@ -65,6 +65,7 @@ const ExportModal = forwardRef(
|
|||
last_tested_version: version,
|
||||
endpoint_name: currentFlow!.endpoint_name,
|
||||
is_component: false,
|
||||
tags: currentFlow!.tags,
|
||||
}),
|
||||
name!,
|
||||
description,
|
||||
|
|
|
|||
|
|
@ -87,13 +87,11 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
|
|||
} group justify-between rounded-lg border border-border p-4 hover:border-placeholder-foreground hover:shadow-sm`}
|
||||
data-testid="list-card"
|
||||
>
|
||||
{/* left side */}
|
||||
<div
|
||||
className={`flex min-w-0 ${
|
||||
isComponent ? "cursor-default" : "cursor-pointer"
|
||||
} items-center gap-4`}
|
||||
>
|
||||
{/* Icon */}
|
||||
<div
|
||||
className={cn(
|
||||
`item-center flex justify-center rounded-lg p-3`,
|
||||
|
|
@ -124,7 +122,6 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* right side */}
|
||||
<div className="ml-5 flex items-center gap-2">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
|
|||
|
|
@ -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<T>(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<T>(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 ?? [],
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue