feat: allow flow name to be edited from the main page or by clicking header (#5525)

* Added flowData to allow changing flow name without being on the flow

* Added being able to edit the flow name just by clicking the name

* Added handleEdit and edit details option on flow dropdown

* Added flow settings modal to grid and list

* Added flowData type and select option

* Update src/frontend/src/components/core/appHeaderComponent/components/FlowMenu/index.tsx

Co-authored-by: Cristhian Zanforlin Lousa <cristhian.lousa@gmail.com>

* Update src/frontend/src/components/core/appHeaderComponent/components/FlowMenu/index.tsx

Co-authored-by: Cristhian Zanforlin Lousa <cristhian.lousa@gmail.com>

* Update src/frontend/src/components/core/appHeaderComponent/components/FlowMenu/index.tsx

Co-authored-by: Cristhian Zanforlin Lousa <cristhian.lousa@gmail.com>

* Fixed useCallback import

* Implemented details boolean to not show endpoint, changed title of flow settings modal

* Changed Flow Settings to Edit Details

* Changed Flow Settings modal description

* Fixed empty name not appearing, fixed naming conditions not considered

* 📝 (editFlowSettingsComponent/index.tsx): add data-testid attribute to input element for flow name for testing purposes
 (edit-flow-name.spec.ts): create test to ensure user can edit flow name by clicking on the header or main page, with various random names generated for testing purposes

* Fixed tests that used Flow Settings

* Fixed tests that rely on flow name to open edit details

* Fixed tests

---------

Co-authored-by: Cristhian Zanforlin Lousa <cristhian.lousa@gmail.com>
Co-authored-by: anovazzi1 <otavio2204@gmail.com>
This commit is contained in:
Lucas Oliveira 2025-01-10 15:14:00 -03:00 committed by GitHub
commit 3e482dbc29
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 456 additions and 162 deletions

View file

@ -1,4 +1,4 @@
import { useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useAddFlow from "@/hooks/flows/use-add-flow";
@ -17,6 +17,7 @@ import {
DropdownMenuLabel,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { UPLOAD_ERROR_ALERT } from "@/constants/alerts_constants";
import { SAVED_HOVER } from "@/constants/constants";
import { useGetRefreshFlowsQuery } from "@/controllers/API/queries/flows/use-get-refresh-flows-query";
@ -54,8 +55,26 @@ export const MenuBar = ({}: {}): JSX.Element => {
const onFlowPage = useFlowStore((state) => state.onFlowPage);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
const stopBuilding = useFlowStore((state) => state.stopBuilding);
const [editingName, setEditingName] = useState(false);
const [flowName, setFlowName] = useState(currentFlow?.name ?? "");
const [isInvalidName, setIsInvalidName] = useState(false);
const nameInputRef = useRef<HTMLInputElement>(null);
const [inputWidth, setInputWidth] = useState<number>(0);
const measureRef = useRef<HTMLSpanElement>(null);
const { data: folders, isFetched: isFoldersFetched } = useGetFoldersQuery();
const flows = useFlowsManagerStore((state) => state.flows);
const [nameLists, setNameList] = useState<string[]>([]);
useEffect(() => {
if (flows) {
const tempNameList: string[] = [];
flows.forEach((flow) => {
tempNameList.push(flow.name);
});
setNameList(tempNameList.filter((name) => name !== currentFlow?.name));
}
}, [flows, currentFlow?.name]);
useGetRefreshFlowsQuery(
{
@ -73,6 +92,12 @@ export const MenuBar = ({}: {}): JSX.Element => {
const changesNotSaved =
customStringify(currentFlow) !== customStringify(currentSavedFlow);
useEffect(() => {
if (measureRef.current) {
setInputWidth(measureRef.current.offsetWidth);
}
}, [flowName]);
function handleAddFlow() {
try {
addFlow().then((id) => {
@ -113,6 +138,80 @@ export const MenuBar = ({}: {}): JSX.Element => {
const changes = useShortcutsStore((state) => state.changesSave);
useHotkeys(changes, handleSave, { preventDefault: true });
const handleEditName = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;
let invalid = false;
for (let i = 0; i < nameLists.length; i++) {
if (value === nameLists[i]) {
invalid = true;
break;
}
}
setIsInvalidName(invalid);
setFlowName(value);
},
[nameLists],
);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Escape") {
setEditingName(false);
setFlowName(currentFlow?.name ?? "");
setIsInvalidName(false);
}
if (e.key === "Enter") {
nameInputRef.current?.blur();
}
},
[currentFlow?.name],
);
const handleNameSubmit = useCallback(() => {
if (
flowName.trim() !== "" &&
flowName !== currentFlow?.name &&
!isInvalidName
) {
const newFlow = {
...currentFlow!,
name: flowName,
id: currentFlow!.id,
};
setCurrentFlow(newFlow);
saveFlow(newFlow)
.then(() => {
setSuccessData({ title: "Flow name updated successfully" });
})
.catch((error) => {
setErrorData({
title: "Error updating flow name",
list: [(error as Error).message],
});
setFlowName(currentFlow?.name ?? "");
});
} else if (isInvalidName) {
setErrorData({
title: "Invalid flow name",
list: ["Name already exists"],
});
setFlowName(currentFlow?.name ?? "");
} else {
setFlowName(currentFlow?.name ?? "");
}
setEditingName(false);
setIsInvalidName(false);
}, [
flowName,
currentFlow,
setCurrentFlow,
saveFlow,
setSuccessData,
setErrorData,
isInvalidName,
]);
return currentFlow && onFlowPage ? (
<div className="flex items-center justify-center gap-2 truncate">
<div className="header-menu-bar hidden w-20 max-w-fit grow justify-end truncate md:flex">
@ -138,148 +237,191 @@ export const MenuBar = ({}: {}): JSX.Element => {
</div>
<div className="overflow-hidden truncate text-sm sm:whitespace-normal">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="header-menu-bar-display-2 group truncate">
<div className="header-menu-bar-display-2 truncate">
<div
className="header-menu-flow-name-2 truncate"
data-testid="flow-configuration-button"
>
<span
ref={measureRef}
className="invisible absolute font-semibold"
style={{ whiteSpace: "pre" }}
>
{flowName}
</span>
{editingName ? (
<>
<Input
className={cn(
"h-6 px-0 font-semibold focus:border-0",
isInvalidName &&
"border-status-red focus-visible:ring-status-red",
)}
style={{ width: `${inputWidth + 1}px` }}
onChange={handleEditName}
maxLength={38}
ref={nameInputRef}
onKeyDown={handleKeyDown}
autoFocus={true}
onBlur={handleNameSubmit}
value={flowName}
id="input-flow-name"
data-testid="input-flow-name"
/>
</>
) : (
<div
className="header-menu-flow-name-2 truncate"
data-testid="flow-configuration-button"
className="truncate font-semibold text-primary"
data-testid="flow_name"
onClick={() => {
setEditingName(true);
setFlowName(currentFlow.name);
}}
>
<div
className="truncate font-semibold group-hover:text-primary dark:text-[white]"
data-testid="flow_name"
>
{currentFlow.name}
</div>
{currentFlow.name}
</div>
)}
</div>
<DropdownMenu>
<DropdownMenuTrigger
className="group"
data-testid="flow_menu_trigger"
>
<IconComponent
name="ChevronDown"
className="flex h-5 w-5 text-muted-foreground group-hover:text-primary"
className="flex h-5 w-5 text-muted-foreground hover:text-primary"
/>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-44 bg-white dark:bg-background">
<DropdownMenuLabel>Options</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => {
handleAddFlow();
}}
className="cursor-pointer"
>
<IconComponent name="Plus" className="header-menu-options" />
New
</DropdownMenuItem>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-44 bg-white dark:bg-background">
<DropdownMenuLabel>Options</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => {
handleAddFlow();
}}
className="cursor-pointer"
>
<IconComponent name="Plus" className="header-menu-options" />
New
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
setOpenSettings(true);
}}
className="cursor-pointer"
>
<IconComponent name="Settings2" className="header-menu-options" />
Flow Settings
</DropdownMenuItem>
{!autoSaving && (
<DropdownMenuItem onClick={handleSave} className="cursor-pointer">
<DropdownMenuItem
onClick={() => {
setOpenSettings(true);
}}
className="cursor-pointer"
>
<IconComponent
name="SquarePen"
className="header-menu-options"
/>
Edit Details
</DropdownMenuItem>
{!autoSaving && (
<DropdownMenuItem
onClick={handleSave}
className="cursor-pointer"
>
<ToolbarSelectItem
value="Save"
icon="Save"
dataTestId=""
shortcut={
shortcuts.find(
(s) => s.name.toLowerCase() === "changes save",
)?.shortcut!
}
/>
</DropdownMenuItem>
)}
<DropdownMenuItem
onClick={() => {
setOpenLogs(true);
}}
className="cursor-pointer"
>
<IconComponent
name="ScrollText"
className="header-menu-options"
/>
Logs
</DropdownMenuItem>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => {
uploadFlow({ position: { x: 300, y: 100 } })
.then(() => {
setSuccessData({
title: "Uploaded successfully",
});
})
.catch((error) => {
setErrorData({
title: UPLOAD_ERROR_ALERT,
list: [(error as Error).message],
});
});
}}
>
<IconComponent name="FileUp" className="header-menu-options" />
Import
</DropdownMenuItem>
<ExportModal>
<div className="header-menubar-item">
<IconComponent
name="FileDown"
className="header-menu-options"
/>
Export
</div>
</ExportModal>
<DropdownMenuItem
onClick={() => {
undo();
}}
className="cursor-pointer"
>
<ToolbarSelectItem
value="Save"
icon="Save"
value="Undo"
icon="Undo"
dataTestId=""
shortcut={
shortcuts.find(
(s) => s.name.toLowerCase() === "changes save",
)?.shortcut!
shortcuts.find((s) => s.name.toLowerCase() === "undo")
?.shortcut!
}
/>
</DropdownMenuItem>
)}
<DropdownMenuItem
onClick={() => {
setOpenLogs(true);
}}
className="cursor-pointer"
>
<IconComponent
name="ScrollText"
className="header-menu-options"
/>
Logs
</DropdownMenuItem>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => {
uploadFlow({ position: { x: 300, y: 100 } })
.then(() => {
setSuccessData({
title: "Uploaded successfully",
});
})
.catch((error) => {
setErrorData({
title: UPLOAD_ERROR_ALERT,
list: [(error as Error).message],
});
});
}}
>
<IconComponent name="FileUp" className="header-menu-options" />
Import
</DropdownMenuItem>
<ExportModal>
<div className="header-menubar-item">
<DropdownMenuItem
onClick={() => {
redo();
}}
className="cursor-pointer"
>
<ToolbarSelectItem
value="Redo"
icon="Redo"
dataTestId=""
shortcut={
shortcuts.find((s) => s.name.toLowerCase() === "redo")
?.shortcut!
}
/>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
handleReloadComponents();
}}
className="cursor-pointer"
>
<IconComponent
name="FileDown"
name="RefreshCcw"
className="header-menu-options"
/>
Export
</div>
</ExportModal>
<DropdownMenuItem
onClick={() => {
undo();
}}
className="cursor-pointer"
>
<ToolbarSelectItem
value="Undo"
icon="Undo"
dataTestId=""
shortcut={
shortcuts.find((s) => s.name.toLowerCase() === "undo")
?.shortcut!
}
/>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
redo();
}}
className="cursor-pointer"
>
<ToolbarSelectItem
value="Redo"
icon="Redo"
dataTestId=""
shortcut={
shortcuts.find((s) => s.name.toLowerCase() === "redo")
?.shortcut!
}
/>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
handleReloadComponents();
}}
className="cursor-pointer"
>
<IconComponent
name="RefreshCcw"
className="header-menu-options"
/>
Refresh All
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
Refresh All
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<FlowSettingsModal
open={openSettings}
setOpen={setOpenSettings}

View file

@ -80,6 +80,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
onDoubleClickCapture={(event) => {
handleFocus(event);
}}
data-testid="input-flow-name"
/>
) : (
<span className="font-normal text-muted-foreground word-break-break-word">

View file

@ -104,7 +104,7 @@ export const EXPORT_DIALOG_SUBTITLE = "Export flow as JSON file.";
* @constant
*/
export const SETTINGS_DIALOG_SUBTITLE =
"Customize workspace settings and preferences.";
"Customize your flow details and settings.";
/**
* The base text for subtitle of Flow Logs (Menubar)

View file

@ -15,29 +15,30 @@ import BaseModal from "../baseModal";
export default function FlowSettingsModal({
open,
setOpen,
flowData,
details,
}: FlowSettingsPropsType): JSX.Element {
const saveFlow = useSaveFlow();
const currentFlow = useFlowStore((state) => state.currentFlow);
const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const flows = useFlowsManagerStore((state) => state.flows);
const flow = flowData ?? currentFlow;
useEffect(() => {
setName(currentFlow!.name);
setDescription(currentFlow!.description);
}, [currentFlow?.name, currentFlow?.description, open]);
setName(flow!.name);
setDescription(flow!.description);
}, [flow?.name, flow?.description, open]);
const [name, setName] = useState(currentFlow!.name);
const [description, setDescription] = useState(currentFlow!.description);
const [endpoint_name, setEndpointName] = useState(
currentFlow!.endpoint_name ?? "",
);
const [name, setName] = useState(flow!.name);
const [description, setDescription] = useState(flow!.description);
const [endpoint_name, setEndpointName] = useState(flow!.endpoint_name ?? "");
const [isSaving, setIsSaving] = useState(false);
const [disableSave, setDisableSave] = useState(true);
const autoSaving = useFlowsManagerStore((state) => state.autoSaving);
function handleClick(): void {
setIsSaving(true);
if (!currentFlow) return;
const newFlow = cloneDeep(currentFlow);
if (!flow) return;
const newFlow = cloneDeep(flow);
newFlow.name = name;
newFlow.description = description;
newFlow.endpoint_name =
@ -67,22 +68,22 @@ export default function FlowSettingsModal({
flows.forEach((flow: FlowType) => {
tempNameList.push(flow.name);
});
setNameList(tempNameList.filter((name) => name !== currentFlow!.name));
setNameList(tempNameList.filter((name) => name !== flow!.name));
}
}, [flows]);
useEffect(() => {
if (
(!nameLists.includes(name) && currentFlow?.name !== name) ||
currentFlow?.description !== description ||
((currentFlow?.endpoint_name ?? "") !== endpoint_name &&
(!nameLists.includes(name) && flow?.name !== name) ||
flow?.description !== description ||
((flow?.endpoint_name ?? "") !== endpoint_name &&
isEndpointNameValid(endpoint_name ?? "", 50))
) {
setDisableSave(false);
} else {
setDisableSave(true);
}
}, [nameLists, currentFlow, description, endpoint_name, name]);
}, [nameLists, flow, description, endpoint_name, name]);
return (
<BaseModal
open={open}
@ -91,8 +92,8 @@ export default function FlowSettingsModal({
onSubmit={handleClick}
>
<BaseModal.Header description={SETTINGS_DIALOG_SUBTITLE}>
<span className="pr-2">Settings</span>
<IconComponent name="Settings2" className="mr-2 h-4 w-4" />
<span className="pr-2">Details</span>
<IconComponent name="SquarePen" className="mr-2 h-4 w-4" />
</BaseModal.Header>
<BaseModal.Content>
<EditFlowSettings
@ -102,7 +103,7 @@ export default function FlowSettingsModal({
endpointName={endpoint_name}
setName={setName}
setDescription={setDescription}
setEndpointName={setEndpointName}
setEndpointName={details ? undefined : setEndpointName}
/>
</BaseModal.Content>

View file

@ -10,11 +10,13 @@ type DropdownComponentProps = {
flowData: FlowType;
setOpenDelete: (open: boolean) => void;
handlePlaygroundClick?: () => void;
handleEdit: () => void;
};
const DropdownComponent = ({
flowData,
setOpenDelete,
handleEdit,
}: DropdownComponentProps) => {
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
@ -29,17 +31,32 @@ const DropdownComponent = ({
downloadFlow(flowData, flowData.name, flowData.description);
setSuccessData({ title: `${flowData.name} exported successfully` });
};
const { handleSelectOptionsChange } = useSelectOptionsChange(
[flowData.id],
setErrorData,
setOpenDelete,
handleDuplicate,
handleExport,
handleEdit,
);
return (
<>
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
handleSelectOptionsChange("edit");
}}
className="cursor-pointer"
data-testid="btn-edit-flow"
>
<ForwardedIconComponent
name="SquarePen"
aria-hidden="true"
className="mr-2 h-4 w-4"
/>
Edit details
</DropdownMenuItem>
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();

View file

@ -10,10 +10,10 @@ import {
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
import DeleteConfirmationModal from "@/modals/deleteConfirmationModal";
import FlowSettingsModal from "@/modals/flowSettingsModal";
import useAlertStore from "@/stores/alertStore";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { FlowType } from "@/types/flow";
import { getInputsAndOutputs } from "@/utils/storeUtils";
import { swatchColors } from "@/utils/styleUtils";
import { cn, getNumberFromString } from "@/utils/utils";
import { useState } from "react";
@ -27,6 +27,7 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
const navigate = useCustomNavigate();
const [openDelete, setOpenDelete] = useState(false);
const [openSettings, setOpenSettings] = useState(false);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const { deleteFlow } = useDeleteFlow();
@ -124,6 +125,9 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
<DropdownComponent
flowData={flowData}
setOpenDelete={setOpenDelete}
handleEdit={() => {
setOpenSettings(true);
}}
/>
</DropdownMenuContent>
</DropdownMenu>
@ -145,6 +149,12 @@ const GridComponent = ({ flowData }: { flowData: FlowType }) => {
<></>
</DeleteConfirmationModal>
)}
<FlowSettingsModal
open={openSettings}
setOpen={setOpenSettings}
flowData={flowData}
details
/>
</>
);
};

View file

@ -10,6 +10,7 @@ import {
import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useDeleteFlow from "@/hooks/flows/use-delete-flow";
import DeleteConfirmationModal from "@/modals/deleteConfirmationModal";
import FlowSettingsModal from "@/modals/flowSettingsModal";
import useAlertStore from "@/stores/alertStore";
import useFlowsManagerStore from "@/stores/flowsManagerStore";
import { FlowType } from "@/types/flow";
@ -30,6 +31,7 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
const { deleteFlow } = useDeleteFlow();
const setErrorData = useAlertStore((state) => state.setErrorData);
const { folderId } = useParams();
const [openSettings, setOpenSettings] = useState(false);
const isComponent = flowData.is_component ?? false;
const setFlowToCanvas = useFlowsManagerStore(
(state) => state.setFlowToCanvas,
@ -144,6 +146,9 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
<DropdownComponent
flowData={flowData}
setOpenDelete={setOpenDelete}
handleEdit={() => {
setOpenSettings(true);
}}
handlePlaygroundClick={() => {
// handlePlaygroundClick();
}}
@ -163,6 +168,12 @@ const ListComponent = ({ flowData }: { flowData: FlowType }) => {
<></>
</DeleteConfirmationModal>
)}
<FlowSettingsModal
open={openSettings}
setOpen={setOpenSettings}
flowData={flowData}
details
/>
</>
);
};

View file

@ -6,6 +6,7 @@ const useSelectOptionsChange = (
setOpenDelete: (value: boolean) => void,
handleDuplicate: () => void,
handleExport: () => void,
handleEdit: () => void,
) => {
const handleSelectOptionsChange = useCallback(
(action) => {
@ -23,6 +24,8 @@ const useSelectOptionsChange = (
handleDuplicate();
} else if (action === "export") {
handleExport();
} else if (action === "edit") {
handleEdit();
}
},
[
@ -31,6 +34,7 @@ const useSelectOptionsChange = (
setOpenDelete,
handleDuplicate,
handleExport,
handleEdit,
],
);

View file

@ -672,6 +672,8 @@ export type buttonBoxPropsType = {
export type FlowSettingsPropsType = {
open: boolean;
setOpen: (open: boolean) => void;
details?: boolean;
flowData?: FlowType;
};
export type groupDataType = {

View file

@ -128,8 +128,8 @@ test(
await page.getByTestId("fit_view").click();
await page.getByTestId("zoom_out").click();
await page.getByTestId("flow-configuration-button").click();
await page.getByText("Flow Settings", { exact: true }).last().click();
await page.getByTestId("flow_menu_trigger").click();
await page.getByText("Edit Details", { exact: true }).last().click();
await page.getByPlaceholder("Flow Name").fill(randomFlowName);
@ -201,8 +201,8 @@ test(
await page.getByTestId("fit_view").click();
await page.getByTestId("zoom_out").click();
await page.getByTestId("flow-configuration-button").click();
await page.getByText("Flow Settings", { exact: true }).last().click();
await page.getByTestId("flow_menu_trigger").click();
await page.getByText("Edit Details", { exact: true }).last().click();
await page.getByPlaceholder("Flow Name").fill(secondRandomFlowName);

View file

@ -116,8 +116,8 @@ test("should share component with share button", async ({ page }) => {
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.waitForTimeout(1000);
const flowName = await page.getByTestId("flow_name").innerText();
await page.getByTestId("flow_name").click();
await page.getByText("Flow Settings").click();
await page.getByTestId("flow_menu_trigger").click();
await page.getByText("Edit Details").click();
const flowDescription = await page
.getByPlaceholder("Flow description")
.inputValue();

View file

@ -25,8 +25,8 @@ test(
await page.getByTestId("fit_view").click();
await page.getByTestId("flow-configuration-button").click();
await page.getByText("Flow Settings").click();
await page.getByTestId("flow_menu_trigger").click();
await page.getByText("Edit Details").click();
await page.getByPlaceholder("Flow name").fill(randomName);
await page.getByText("Save").last().click();
await page.getByTestId("icon-ChevronLeft").last().click();
@ -72,8 +72,8 @@ test(
await page.getByTestId(`card-${randomName}`).first().click();
await page.getByTestId("flow-configuration-button").click();
await page.getByText("Flow Settings").click();
await page.getByTestId("flow_menu_trigger").click();
await page.getByText("Edit Details").click();
await page.getByPlaceholder("Flow name").fill(secondRandomName);
await page.getByText("Save").last().click();
await page.getByTestId("icon-ChevronLeft").last().click();

View file

@ -0,0 +1,106 @@
import { expect, test } from "@playwright/test";
import { readFileSync } from "fs";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
import { simulateDragAndDrop } from "../../utils/simulate-drag-and-drop";
test(
"user should be able to edit flow name by clicking on the header or on the main page",
{ tag: ["@release"] },
async ({ page }) => {
const randomName = Math.random().toString(36).substring(2, 15);
const randomName2 = Math.random().toString(36).substring(2, 15);
const randomName3 = Math.random().toString(36).substring(2, 15);
const randomName4 = Math.random().toString(36).substring(2, 15);
await awaitBootstrapTest(page);
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.getByTestId("flow_name").click();
await page.getByTestId("input-flow-name").fill(randomName);
await page.keyboard.press("Enter");
let flowName = await page.getByTestId("flow_name").textContent();
expect(flowName).toBe(randomName);
await page.getByTestId("icon-ChevronLeft").first().click();
await page.waitForSelector('[data-testid="home-dropdown-menu"]', {
timeout: 5000,
});
await page.waitForSelector(`text=${randomName}`, {
timeout: 3000,
state: "visible",
});
expect(await page.getByText(randomName).count()).toBe(1);
await page.getByText(randomName).click();
await page.getByTestId("flow_name").click();
await page.getByTestId("input-flow-name").fill(randomName2);
await page.keyboard.press("Enter");
flowName = await page.getByTestId("flow_name").textContent();
expect(flowName).toBe(randomName2);
await page.getByTestId("icon-ChevronLeft").first().click();
await page.waitForSelector('[data-testid="home-dropdown-menu"]', {
timeout: 5000,
});
await page.waitForSelector(`text=${randomName2}`, {
timeout: 3000,
state: "visible",
});
expect(await page.getByText(randomName2).count()).toBe(1);
await page.getByTestId("home-dropdown-menu").first().click();
await page.getByTestId("btn-edit-flow").click();
await page.getByTestId("input-flow-name").fill(randomName3);
await page.getByTestId("save-flow-settings").click();
await page.waitForSelector(`text=${randomName3}`, {
timeout: 3000,
state: "visible",
});
expect(await page.getByText(randomName3).count()).toBe(1);
await page.getByText(randomName3).click();
await page.getByTestId("flow_name").click();
await page.getByTestId("input-flow-name").fill(randomName4);
await page.keyboard.press("Enter");
flowName = await page.getByTestId("flow_name").textContent();
expect(flowName).toBe(randomName4);
await page.getByTestId("icon-ChevronLeft").first().click();
await page.waitForSelector('[data-testid="home-dropdown-menu"]', {
timeout: 5000,
});
await page.waitForSelector(`text=${randomName4}`, {
timeout: 3000,
state: "visible",
});
expect(await page.getByText(randomName4).count()).toBe(1);
},
);

View file

@ -15,8 +15,8 @@ test(
timeout: 3000,
});
await page.getByTestId("flow_name").click();
await page.getByText("Flow Settings").first().click();
await page.getByTestId("flow_menu_trigger").click();
await page.getByText("Edit Details").first().click();
await page
.getByPlaceholder("Flow name")
.fill(
@ -39,8 +39,8 @@ test(
await page.getByText("Changes saved successfully").isVisible();
await page.getByTestId("flow_name").click();
await page.getByText("Flow Settings").first().click();
await page.getByTestId("flow_menu_trigger").click();
await page.getByText("Edit Details").first().click();
const flowName = await page.getByPlaceholder("Flow name").inputValue();
const flowDescription = await page