feat: change download flows to download as .zip file (#2870)
* 📝 (flows.py): Add endpoint to download multiple flows as a zip file 📝 (constants.ts): Add FLOWS constant for API endpoint 📝 (index.ts): Add use-post-download-multiple-flows query function 📝 (use-post-download-multiple-flows.ts): Implement function to download multiple flows as a zip file 📝 (index.tsx): Import usePostDownloadMultipleFlows in componentsComponent 📝 (index.tsx): Implement handleExport function to download multiple flows as a zip file 📝 (index.tsx): Update handleExport function to handle downloading and saving the zip file 📝 (headerComponent.tsx): Add shouldSelectAll state and setShouldSelectAll function to manage select all functionality * formatting file * 📝 (flows.py): Remove download_file endpoint and refactor download_multiple_file to support downloading multiple flows as a zip file 📝 (index.tsx): Refactor export functionality to handle exporting multiple flows as a zip file or a single flow as a JSON file 🔧 (use-post-download-multiple-flows.ts): Update API endpoint for downloading flows 🔧 (index.tsx): Remove unused imports and functions related to exporting flows 🔧 (reactflowUtils.ts): Remove unused downloadFlows function and related imports * [autofix.ci] apply automated fixes * 📝 (flows.py): remove unnecessary whitespace and improve code readability by aligning function parameters in download_multiple_file function --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
2db87e89bf
commit
61265cc327
10 changed files with 170 additions and 129 deletions
|
|
@ -13,6 +13,8 @@ export const URLs = {
|
|||
AUTOLOGIN: "auto_login",
|
||||
REFRESH: "refresh",
|
||||
BUILD: `build`,
|
||||
CUSTOM_COMPONENT: `custom_component`,
|
||||
FLOWS: `flows`,
|
||||
FOLDERS: `folders`,
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -197,19 +197,6 @@ export async function readFlowsFromDatabase() {
|
|||
}
|
||||
}
|
||||
|
||||
export async function downloadFlowsFromDatabase() {
|
||||
try {
|
||||
const response = await api.get(`${BASE_URL_API}flows/download/`);
|
||||
if (response && response?.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response?.status}`);
|
||||
}
|
||||
return response?.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function uploadFlowsToDatabase(flows: FormData) {
|
||||
try {
|
||||
const response = await api.post(`${BASE_URL_API}flows/upload/`, flows);
|
||||
|
|
|
|||
1
src/frontend/src/controllers/API/queries/flows/index.ts
Normal file
1
src/frontend/src/controllers/API/queries/flows/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./use-post-download-multiple-flows";
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { useMutationFunctionType } from "@/types/api";
|
||||
import { UseMutationResult } from "@tanstack/react-query";
|
||||
import { api } from "../../api";
|
||||
import { getURL } from "../../helpers/constants";
|
||||
import { UseRequestProcessor } from "../../services/request-processor";
|
||||
|
||||
interface IPostDownloadMultipleFlows {
|
||||
flow_ids: string[];
|
||||
}
|
||||
|
||||
export const usePostDownloadMultipleFlows: useMutationFunctionType<
|
||||
undefined,
|
||||
IPostDownloadMultipleFlows
|
||||
> = (options?) => {
|
||||
const { mutate } = UseRequestProcessor();
|
||||
|
||||
const postDownloadMultipleFlowsFn = async (
|
||||
payload: IPostDownloadMultipleFlows,
|
||||
): Promise<any> => {
|
||||
const response = await api.post<any>(
|
||||
`${getURL("FLOWS")}/download/`,
|
||||
payload.flow_ids,
|
||||
{ responseType: "blob" },
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
const mutation: UseMutationResult<
|
||||
IPostDownloadMultipleFlows,
|
||||
any,
|
||||
IPostDownloadMultipleFlows
|
||||
> = mutate(
|
||||
["usePostDownloadMultipleFlows"],
|
||||
async (payload: IPostDownloadMultipleFlows) => {
|
||||
const res = await postDownloadMultipleFlowsFn(payload);
|
||||
return res;
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
return mutation;
|
||||
};
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
import { useCallback } from "react";
|
||||
import { FlowType } from "../../../../../types/flow";
|
||||
|
||||
const useExportFlows = (
|
||||
selectedFlowsComponentsCards: string[],
|
||||
allFlows: Array<FlowType>,
|
||||
downloadFlow: (flow: any, name: string, description: string) => void,
|
||||
removeApiKeys: (flow: any) => any,
|
||||
version: string,
|
||||
setSuccessData: (data: { title: string }) => void,
|
||||
setSelectedFlowsComponentsCards: (
|
||||
selectedFlowsComponentsCards: string[],
|
||||
) => void,
|
||||
handleSelectAll: (select: boolean) => void,
|
||||
cardTypes: string,
|
||||
) => {
|
||||
const handleExport = useCallback(() => {
|
||||
selectedFlowsComponentsCards.forEach((selectedFlowId) => {
|
||||
const selectedFlow = allFlows.find((flow) => flow.id === selectedFlowId);
|
||||
if (selectedFlow) {
|
||||
downloadFlow(
|
||||
removeApiKeys({
|
||||
id: selectedFlow.id,
|
||||
data: selectedFlow.data,
|
||||
description: selectedFlow.description,
|
||||
name: selectedFlow.name,
|
||||
last_tested_version: version,
|
||||
endpoint_name: selectedFlow.endpoint_name,
|
||||
is_component: false,
|
||||
}),
|
||||
selectedFlow.name,
|
||||
selectedFlow.description,
|
||||
);
|
||||
}
|
||||
});
|
||||
setSuccessData({ title: `${cardTypes} exported successfully` });
|
||||
setSelectedFlowsComponentsCards([]);
|
||||
handleSelectAll(false);
|
||||
}, [
|
||||
selectedFlowsComponentsCards,
|
||||
allFlows,
|
||||
downloadFlow,
|
||||
removeApiKeys,
|
||||
version,
|
||||
setSuccessData,
|
||||
setSelectedFlowsComponentsCards,
|
||||
handleSelectAll,
|
||||
cardTypes,
|
||||
]);
|
||||
|
||||
return { handleExport };
|
||||
};
|
||||
|
||||
export default useExportFlows;
|
||||
|
|
@ -1,18 +1,15 @@
|
|||
import { usePostDownloadMultipleFlows } from "@/controllers/API/queries/flows";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { FormProvider, useForm, useWatch } from "react-hook-form";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import CardsWrapComponent from "../../../../components/cardsWrapComponent";
|
||||
import IconComponent from "../../../../components/genericIconComponent";
|
||||
import PaginatorComponent from "../../../../components/paginatorComponent";
|
||||
import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent";
|
||||
import { Button } from "../../../../components/ui/button";
|
||||
import DeleteConfirmationModal from "../../../../modals/deleteConfirmationModal";
|
||||
import useAlertStore from "../../../../stores/alertStore";
|
||||
import { useDarkStore } from "../../../../stores/darkStore";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { useFolderStore } from "../../../../stores/foldersStore";
|
||||
import { FlowType } from "../../../../types/flow";
|
||||
import { downloadFlow, removeApiKeys } from "../../../../utils/reactflowUtils";
|
||||
import useFileDrop from "../../hooks/use-on-file-drop";
|
||||
import { getNameByType } from "../../utils/get-name-by-type";
|
||||
import { sortFlows } from "../../utils/sort-flows";
|
||||
|
|
@ -23,7 +20,6 @@ import useDeleteMultipleFlows from "./hooks/use-delete-multiple";
|
|||
import useDescriptionModal from "./hooks/use-description-modal";
|
||||
import useFilteredFlows from "./hooks/use-filtered-flows";
|
||||
import useDuplicateFlows from "./hooks/use-handle-duplicate";
|
||||
import useExportFlows from "./hooks/use-handle-export";
|
||||
import useSelectAll from "./hooks/use-handle-select-all";
|
||||
import useSelectOptionsChange from "./hooks/use-select-options-change";
|
||||
import useSelectedFlows from "./hooks/use-selected-flows";
|
||||
|
|
@ -78,6 +74,8 @@ export default function ComponentsComponent({
|
|||
const isLoadingFolders = useFolderStore((state) => state.isLoadingFolders);
|
||||
const setSelectedFolder = useFolderStore((state) => state.setSelectedFolder);
|
||||
|
||||
const [shouldSelectAll, setShouldSelectAll] = useState(true);
|
||||
|
||||
const cardTypes = useMemo(() => {
|
||||
if (window.location.pathname.includes("components")) {
|
||||
return "Components";
|
||||
|
|
@ -128,19 +126,59 @@ export default function ComponentsComponent({
|
|||
cardTypes,
|
||||
);
|
||||
|
||||
const version = useDarkStore((state) => state.version);
|
||||
const { mutate: mutateDownloadMultipleFlows } =
|
||||
usePostDownloadMultipleFlows();
|
||||
|
||||
const { handleExport } = useExportFlows(
|
||||
selectedFlowsComponentsCards,
|
||||
allFlows,
|
||||
downloadFlow,
|
||||
removeApiKeys,
|
||||
version,
|
||||
setSuccessData,
|
||||
setSelectedFlowsComponentsCards,
|
||||
handleSelectAll,
|
||||
cardTypes,
|
||||
);
|
||||
const handleExport = () => {
|
||||
mutateDownloadMultipleFlows(
|
||||
{
|
||||
flow_ids: selectedFlowsComponentsCards,
|
||||
},
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
const selectedFlow = allFlows.find(
|
||||
(flow) => flow.id === selectedFlowsComponentsCards[0],
|
||||
);
|
||||
|
||||
const blobType =
|
||||
selectedFlowsComponentsCards.length > 1
|
||||
? "application/zip"
|
||||
: "application/json";
|
||||
|
||||
const fileNameSuffix =
|
||||
selectedFlowsComponentsCards.length > 1
|
||||
? "_langflow_flows.zip"
|
||||
: `${selectedFlow!.name}.json`;
|
||||
|
||||
const blob = new Blob([data], { type: blobType });
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
|
||||
let current_time = new Date().toISOString().replace(/[:.]/g, "");
|
||||
|
||||
current_time = current_time
|
||||
.replace(/-/g, "")
|
||||
.replace(/T/g, "")
|
||||
.replace(/Z/g, "");
|
||||
|
||||
link.download =
|
||||
selectedFlowsComponentsCards.length > 1
|
||||
? `${current_time}${fileNameSuffix}`
|
||||
: `${fileNameSuffix}`;
|
||||
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
setSuccessData({ title: `${cardTypes} exported successfully` });
|
||||
setSelectedFlowsComponentsCards([]);
|
||||
handleSelectAll(false);
|
||||
setShouldSelectAll(true);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const { handleSelectOptionsChange } = useSelectOptionsChange(
|
||||
selectedFlowsComponentsCards,
|
||||
|
|
@ -184,6 +222,8 @@ export default function ComponentsComponent({
|
|||
<div className="flex w-full gap-4 pb-5">
|
||||
{allFlows?.length > 0 && (
|
||||
<HeaderComponent
|
||||
shouldSelectAll={shouldSelectAll}
|
||||
setShouldSelectAll={setShouldSelectAll}
|
||||
handleDelete={() => handleSelectOptionsChange("delete")}
|
||||
handleSelectAll={handleSelectAll}
|
||||
handleDuplicate={() => handleSelectOptionsChange("duplicate")}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ type HeaderComponentProps = {
|
|||
handleDuplicate: () => void;
|
||||
handleExport: () => void;
|
||||
disableFunctions: boolean;
|
||||
setShouldSelectAll: (select) => void;
|
||||
shouldSelectAll: boolean;
|
||||
};
|
||||
|
||||
const HeaderComponent = ({
|
||||
|
|
@ -19,9 +21,9 @@ const HeaderComponent = ({
|
|||
handleDuplicate,
|
||||
handleExport,
|
||||
disableFunctions,
|
||||
setShouldSelectAll,
|
||||
shouldSelectAll,
|
||||
}: HeaderComponentProps) => {
|
||||
const [shouldSelectAll, setShouldSelectAll] = useState(true);
|
||||
|
||||
const handleClick = () => {
|
||||
handleSelectAll(shouldSelectAll);
|
||||
setShouldSelectAll((prevState) => !prevState);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import {
|
|||
SUCCESS_BUILD,
|
||||
specialCharsRegex,
|
||||
} from "../constants/constants";
|
||||
import { downloadFlowsFromDatabase } from "../controllers/API";
|
||||
import { DESCRIPTIONS } from "../flow_constants";
|
||||
import {
|
||||
APIClassType,
|
||||
|
|
@ -1423,22 +1422,6 @@ export function downloadFlow(
|
|||
link.click();
|
||||
}
|
||||
|
||||
export function downloadFlows() {
|
||||
downloadFlowsFromDatabase().then((flows) => {
|
||||
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
|
||||
JSON.stringify(flows),
|
||||
)}`;
|
||||
|
||||
// create a link element and set its properties
|
||||
const link = document.createElement("a");
|
||||
link.href = jsonString;
|
||||
link.download = `flows.json`;
|
||||
|
||||
// simulate a click on the link element to trigger the download
|
||||
link.click();
|
||||
});
|
||||
}
|
||||
|
||||
export function getRandomElement<T>(array: T[]): T {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue