fix: add Export modal instead of Download in start page, change Download button size (#8095)

* Fix download button size on main page

* Added export modal into main page card dropdown

* added open and setopen as optional

* Add download component directly if its component

* Added success message to exported flow

* Added data test id

* Fixed export tests

* Added downloaded message and data test id

* Fixed bulk actions test
This commit is contained in:
Lucas Oliveira 2025-05-19 16:55:32 -03:00 committed by GitHub
commit 15ccc886f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 69 additions and 26 deletions

View file

@ -1,5 +1,6 @@
import { track } from "@/customization/utils/analytics";
import useFlowStore from "@/stores/flowStore";
import { FlowType } from "@/types/flow";
import { ReactNode, forwardRef, useEffect, useState } from "react";
import IconComponent from "../../components/common/genericIconComponent";
import EditFlowSettings from "../../components/core/editFlowSettingsComponent";
@ -16,11 +17,21 @@ import { downloadFlow, removeApiKeys } from "../../utils/reactflowUtils";
import BaseModal from "../baseModal";
const ExportModal = forwardRef(
(props: { children: ReactNode }, ref): JSX.Element => {
(
props: {
children?: ReactNode;
open?: boolean;
setOpen?: (open: boolean) => void;
flowData?: FlowType;
},
ref,
): JSX.Element => {
const version = useDarkStore((state) => state.version);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setNoticeData = useAlertStore((state) => state.setNoticeData);
const [checked, setChecked] = useState(false);
const currentFlow = useFlowStore((state) => state.currentFlow);
const currentFlowOnPage = useFlowStore((state) => state.currentFlow);
const currentFlow = props.flowData ?? currentFlowOnPage;
const isBuilding = useFlowStore((state) => state.isBuilding);
useEffect(() => {
setName(currentFlow?.name ?? "");
@ -30,7 +41,13 @@ const ExportModal = forwardRef(
const [description, setDescription] = useState(
currentFlow?.description ?? "",
);
const [open, setOpen] = useState(false);
const [customOpen, customSetOpen] = useState(false);
const [open, setOpen] =
props.open !== undefined && props.setOpen !== undefined
? [props.open, props.setOpen]
: [customOpen, customSetOpen];
return (
<BaseModal
size="smaller-h-full"
@ -69,12 +86,16 @@ const ExportModal = forwardRef(
}),
name!,
description,
);
).then(() => {
setSuccessData({
title: "Flow exported successfully",
});
});
setOpen(false);
track("Flow Exported", { flowId: currentFlow!.id });
}}
>
<BaseModal.Trigger asChild>{props.children}</BaseModal.Trigger>
<BaseModal.Trigger asChild>{props.children ?? <></>}</BaseModal.Trigger>
<BaseModal.Header description={EXPORT_DIALOG_SUBTITLE}>
<span className="pr-2">Export</span>
<IconComponent
@ -107,7 +128,13 @@ const ExportModal = forwardRef(
</span>
</BaseModal.Content>
<BaseModal.Footer submit={{ label: "Export", loading: isBuilding }} />
<BaseModal.Footer
submit={{
label: "Export",
loading: isBuilding,
dataTestId: "modal-export-button",
}}
/>
</BaseModal>
);
},

View file

@ -2,25 +2,24 @@ import ForwardedIconComponent from "@/components/common/genericIconComponent";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import useAlertStore from "@/stores/alertStore";
import { FlowType } from "@/types/flow";
import { downloadFlow } from "@/utils/reactflowUtils";
import useDuplicateFlow from "../../hooks/use-handle-duplicate";
import useSelectOptionsChange from "../../hooks/use-select-options-change";
type DropdownComponentProps = {
flowData: FlowType;
setOpenDelete: (open: boolean) => void;
handlePlaygroundClick?: () => void;
handleExport: () => void;
handleEdit: () => void;
};
const DropdownComponent = ({
flowData,
setOpenDelete,
handleExport,
handleEdit,
}: DropdownComponentProps) => {
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const { handleDuplicate } = useDuplicateFlow({ flow: flowData });
const duplicateFlow = () => {
@ -31,16 +30,12 @@ const DropdownComponent = ({
);
};
const handleExport = () => {
downloadFlow(flowData, flowData.name, flowData.description);
setSuccessData({ title: `${flowData.name} exported successfully` });
};
const { handleSelectOptionsChange } = useSelectOptionsChange(
[flowData.id],
setErrorData,
setOpenDelete,
duplicateFlow,
handleExport,
duplicateFlow,
handleEdit,
);
@ -74,7 +69,7 @@ const DropdownComponent = ({
aria-hidden="true"
className="mr-2 h-4 w-4"
/>
Download
Export
</DropdownMenuItem>
<DropdownMenuItem
onClick={(e) => {

View file

@ -82,6 +82,7 @@ const HeaderComponent = ({
const handleDownload = () => {
downloadFlows({ ids: selectedFlows });
setSuccessData({ title: "Flows downloaded successfully" });
};
const handleDelete = () => {
@ -190,13 +191,15 @@ const HeaderComponent = ({
<div className="flex items-center">
<div
className={cn(
"-mr-4 flex w-0 items-center gap-2 overflow-hidden opacity-0 transition-all duration-300",
"-mr-3 flex w-0 items-center gap-2 overflow-hidden opacity-0 transition-all duration-300",
selectedFlows.length > 0 && "w-36 opacity-100",
)}
>
<Button
variant="outline"
size="iconMd"
className="h-8 w-8"
data-testid="download-bulk-btn"
onClick={handleDownload}
loading={isDownloading}
>

View file

@ -11,10 +11,11 @@ import {
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
import DeleteConfirmationModal from "@/modals/deleteConfirmationModal";
import ExportModal from "@/modals/exportModal";
import FlowSettingsModal from "@/modals/flowSettingsModal";
import useAlertStore from "@/stores/alertStore";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { FlowType } from "@/types/flow";
import { downloadFlow } from "@/utils/reactflowUtils";
import { swatchColors } from "@/utils/styleUtils";
import { cn, getNumberFromString } from "@/utils/utils";
import { useEffect, useState } from "react";
@ -42,6 +43,7 @@ const ListComponent = ({
const setErrorData = useAlertStore((state) => state.setErrorData);
const { folderId } = useParams();
const [openSettings, setOpenSettings] = useState(false);
const [openExportModal, setOpenExportModal] = useState(false);
const isComponent = flowData.is_component ?? false;
const { getIcon } = useGetTemplateStyle(flowData);
@ -86,6 +88,15 @@ const ListComponent = ({
: getNumberFromString(flowData.gradient ?? flowData.id)) %
swatchColors.length;
const handleExport = () => {
if (flowData.is_component) {
downloadFlow(flowData, flowData.name, flowData.description);
setSuccessData({ title: `${flowData.name} exported successfully` });
} else {
setOpenExportModal(true);
}
};
const [icon, setIcon] = useState<string>("");
useEffect(() => {
@ -193,18 +204,15 @@ const ListComponent = ({
<DropdownComponent
flowData={flowData}
setOpenDelete={setOpenDelete}
handleExport={handleExport}
handleEdit={() => {
setOpenSettings(true);
}}
handlePlaygroundClick={() => {
// handlePlaygroundClick();
}}
/>
</DropdownMenuContent>
</DropdownMenu>
</div>
</Card>
{openDelete && (
<DeleteConfirmationModal
open={openDelete}
@ -214,6 +222,11 @@ const ListComponent = ({
note={!flowData.is_component ? "and its message history" : ""}
/>
)}
<ExportModal
open={openExportModal}
setOpen={setOpenExportModal}
flowData={flowData}
/>
<FlowSettingsModal
open={openSettings}
setOpen={setOpenSettings}

View file

@ -4,8 +4,8 @@ const useSelectOptionsChange = (
selectedFlowsComponentsCards: string[] | undefined,
setErrorData: (data: { title: string; list: string[] }) => void,
setOpenDelete: (value: boolean) => void,
handleDuplicate: () => void,
handleExport: () => void,
handleDuplicate: () => void,
handleEdit: () => void,
) => {
const handleSelectOptionsChange = useCallback(
@ -33,8 +33,8 @@ const useSelectOptionsChange = (
setErrorData,
setOpenDelete,
handleDuplicate,
handleExport,
handleEdit,
handleExport,
],
);

View file

@ -35,6 +35,9 @@ test(
await page.getByTestId("icon-ChevronLeft").last().click();
await page.getByTestId("home-dropdown-menu").nth(0).click();
await page.getByTestId("btn-download-json").last().click();
await page.getByText("Export").first().isVisible();
await page.getByTestId("modal-export-button").isVisible();
await page.getByTestId("modal-export-button").click();
await expect(page.getByText(/.*exported successfully/)).toBeVisible({
timeout: 10000,
});
@ -42,6 +45,9 @@ test(
await page.getByText("Flows", { exact: true }).click();
await page.getByTestId("home-dropdown-menu").nth(0).click();
await page.getByTestId("btn-download-json").last().click();
await page.getByText("Export").first().isVisible();
await page.getByTestId("modal-export-button").isVisible();
await page.getByTestId("modal-export-button").click();
await expect(page.getByText(/.*exported successfully/).last()).toBeVisible({
timeout: 10000,
});

View file

@ -58,9 +58,8 @@ test(
await expect(secondCheckbox).toBeChecked();
await expect(thirdCheckbox).toBeChecked();
// Test bulk download
await page.getByTestId("home-dropdown-menu").first().click();
await page.getByTestId("btn-download-json").last().click();
await expect(page.getByText(/.*exported successfully/)).toBeVisible({
await page.getByTestId("download-bulk-btn").last().click();
await expect(page.getByText(/.*downloaded successfully/)).toBeVisible({
timeout: 10000,
});