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 { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
import useAddFlow from "@/hooks/flows/use-add-flow"; import useAddFlow from "@/hooks/flows/use-add-flow";
@ -17,6 +17,7 @@ import {
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { UPLOAD_ERROR_ALERT } from "@/constants/alerts_constants"; import { UPLOAD_ERROR_ALERT } from "@/constants/alerts_constants";
import { SAVED_HOVER } from "@/constants/constants"; import { SAVED_HOVER } from "@/constants/constants";
import { useGetRefreshFlowsQuery } from "@/controllers/API/queries/flows/use-get-refresh-flows-query"; 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 onFlowPage = useFlowStore((state) => state.onFlowPage);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow); const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
const stopBuilding = useFlowStore((state) => state.stopBuilding); 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 { 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( useGetRefreshFlowsQuery(
{ {
@ -73,6 +92,12 @@ export const MenuBar = ({}: {}): JSX.Element => {
const changesNotSaved = const changesNotSaved =
customStringify(currentFlow) !== customStringify(currentSavedFlow); customStringify(currentFlow) !== customStringify(currentSavedFlow);
useEffect(() => {
if (measureRef.current) {
setInputWidth(measureRef.current.offsetWidth);
}
}, [flowName]);
function handleAddFlow() { function handleAddFlow() {
try { try {
addFlow().then((id) => { addFlow().then((id) => {
@ -113,6 +138,80 @@ export const MenuBar = ({}: {}): JSX.Element => {
const changes = useShortcutsStore((state) => state.changesSave); const changes = useShortcutsStore((state) => state.changesSave);
useHotkeys(changes, handleSave, { preventDefault: true }); 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 ? ( return currentFlow && onFlowPage ? (
<div className="flex items-center justify-center gap-2 truncate"> <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"> <div className="header-menu-bar hidden w-20 max-w-fit grow justify-end truncate md:flex">
@ -138,25 +237,60 @@ export const MenuBar = ({}: {}): JSX.Element => {
</div> </div>
<div className="overflow-hidden truncate text-sm sm:whitespace-normal"> <div className="overflow-hidden truncate text-sm sm:whitespace-normal">
<DropdownMenu> <div className="header-menu-bar-display-2 truncate">
<DropdownMenuTrigger asChild>
<div className="header-menu-bar-display-2 group truncate">
<div <div
className="header-menu-flow-name-2 truncate" className="header-menu-flow-name-2 truncate"
data-testid="flow-configuration-button" 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 <div
className="truncate font-semibold group-hover:text-primary dark:text-[white]" className="truncate font-semibold text-primary"
data-testid="flow_name" data-testid="flow_name"
onClick={() => {
setEditingName(true);
setFlowName(currentFlow.name);
}}
> >
{currentFlow.name} {currentFlow.name}
</div> </div>
)}
</div> </div>
<DropdownMenu>
<DropdownMenuTrigger
className="group"
data-testid="flow_menu_trigger"
>
<IconComponent <IconComponent
name="ChevronDown" 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> </DropdownMenuTrigger>
<DropdownMenuContent className="w-44 bg-white dark:bg-background"> <DropdownMenuContent className="w-44 bg-white dark:bg-background">
<DropdownMenuLabel>Options</DropdownMenuLabel> <DropdownMenuLabel>Options</DropdownMenuLabel>
@ -176,11 +310,17 @@ export const MenuBar = ({}: {}): JSX.Element => {
}} }}
className="cursor-pointer" className="cursor-pointer"
> >
<IconComponent name="Settings2" className="header-menu-options" /> <IconComponent
Flow Settings name="SquarePen"
className="header-menu-options"
/>
Edit Details
</DropdownMenuItem> </DropdownMenuItem>
{!autoSaving && ( {!autoSaving && (
<DropdownMenuItem onClick={handleSave} className="cursor-pointer"> <DropdownMenuItem
onClick={handleSave}
className="cursor-pointer"
>
<ToolbarSelectItem <ToolbarSelectItem
value="Save" value="Save"
icon="Save" icon="Save"
@ -280,6 +420,8 @@ export const MenuBar = ({}: {}): JSX.Element => {
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div>
<FlowSettingsModal <FlowSettingsModal
open={openSettings} open={openSettings}
setOpen={setOpenSettings} setOpen={setOpenSettings}

View file

@ -80,6 +80,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
onDoubleClickCapture={(event) => { onDoubleClickCapture={(event) => {
handleFocus(event); handleFocus(event);
}} }}
data-testid="input-flow-name"
/> />
) : ( ) : (
<span className="font-normal text-muted-foreground word-break-break-word"> <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 * @constant
*/ */
export const SETTINGS_DIALOG_SUBTITLE = 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) * The base text for subtitle of Flow Logs (Menubar)

View file

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

View file

@ -10,11 +10,13 @@ type DropdownComponentProps = {
flowData: FlowType; flowData: FlowType;
setOpenDelete: (open: boolean) => void; setOpenDelete: (open: boolean) => void;
handlePlaygroundClick?: () => void; handlePlaygroundClick?: () => void;
handleEdit: () => void;
}; };
const DropdownComponent = ({ const DropdownComponent = ({
flowData, flowData,
setOpenDelete, setOpenDelete,
handleEdit,
}: DropdownComponentProps) => { }: DropdownComponentProps) => {
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
@ -29,17 +31,32 @@ const DropdownComponent = ({
downloadFlow(flowData, flowData.name, flowData.description); downloadFlow(flowData, flowData.name, flowData.description);
setSuccessData({ title: `${flowData.name} exported successfully` }); setSuccessData({ title: `${flowData.name} exported successfully` });
}; };
const { handleSelectOptionsChange } = useSelectOptionsChange( const { handleSelectOptionsChange } = useSelectOptionsChange(
[flowData.id], [flowData.id],
setErrorData, setErrorData,
setOpenDelete, setOpenDelete,
handleDuplicate, handleDuplicate,
handleExport, handleExport,
handleEdit,
); );
return ( 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 <DropdownMenuItem
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();

View file

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

View file

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

View file

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

View file

@ -128,8 +128,8 @@ test(
await page.getByTestId("fit_view").click(); await page.getByTestId("fit_view").click();
await page.getByTestId("zoom_out").click(); await page.getByTestId("zoom_out").click();
await page.getByTestId("flow-configuration-button").click(); await page.getByTestId("flow_menu_trigger").click();
await page.getByText("Flow Settings", { exact: true }).last().click(); await page.getByText("Edit Details", { exact: true }).last().click();
await page.getByPlaceholder("Flow Name").fill(randomFlowName); await page.getByPlaceholder("Flow Name").fill(randomFlowName);
@ -201,8 +201,8 @@ test(
await page.getByTestId("fit_view").click(); await page.getByTestId("fit_view").click();
await page.getByTestId("zoom_out").click(); await page.getByTestId("zoom_out").click();
await page.getByTestId("flow-configuration-button").click(); await page.getByTestId("flow_menu_trigger").click();
await page.getByText("Flow Settings", { exact: true }).last().click(); await page.getByText("Edit Details", { exact: true }).last().click();
await page.getByPlaceholder("Flow Name").fill(secondRandomFlowName); 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.getByRole("heading", { name: "Basic Prompting" }).click();
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
const flowName = await page.getByTestId("flow_name").innerText(); const flowName = await page.getByTestId("flow_name").innerText();
await page.getByTestId("flow_name").click(); await page.getByTestId("flow_menu_trigger").click();
await page.getByText("Flow Settings").click(); await page.getByText("Edit Details").click();
const flowDescription = await page const flowDescription = await page
.getByPlaceholder("Flow description") .getByPlaceholder("Flow description")
.inputValue(); .inputValue();

View file

@ -25,8 +25,8 @@ test(
await page.getByTestId("fit_view").click(); await page.getByTestId("fit_view").click();
await page.getByTestId("flow-configuration-button").click(); await page.getByTestId("flow_menu_trigger").click();
await page.getByText("Flow Settings").click(); await page.getByText("Edit Details").click();
await page.getByPlaceholder("Flow name").fill(randomName); await page.getByPlaceholder("Flow name").fill(randomName);
await page.getByText("Save").last().click(); await page.getByText("Save").last().click();
await page.getByTestId("icon-ChevronLeft").last().click(); await page.getByTestId("icon-ChevronLeft").last().click();
@ -72,8 +72,8 @@ test(
await page.getByTestId(`card-${randomName}`).first().click(); await page.getByTestId(`card-${randomName}`).first().click();
await page.getByTestId("flow-configuration-button").click(); await page.getByTestId("flow_menu_trigger").click();
await page.getByText("Flow Settings").click(); await page.getByText("Edit Details").click();
await page.getByPlaceholder("Flow name").fill(secondRandomName); await page.getByPlaceholder("Flow name").fill(secondRandomName);
await page.getByText("Save").last().click(); await page.getByText("Save").last().click();
await page.getByTestId("icon-ChevronLeft").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, timeout: 3000,
}); });
await page.getByTestId("flow_name").click(); await page.getByTestId("flow_menu_trigger").click();
await page.getByText("Flow Settings").first().click(); await page.getByText("Edit Details").first().click();
await page await page
.getByPlaceholder("Flow name") .getByPlaceholder("Flow name")
.fill( .fill(
@ -39,8 +39,8 @@ test(
await page.getByText("Changes saved successfully").isVisible(); await page.getByText("Changes saved successfully").isVisible();
await page.getByTestId("flow_name").click(); await page.getByTestId("flow_menu_trigger").click();
await page.getByText("Flow Settings").first().click(); await page.getByText("Edit Details").first().click();
const flowName = await page.getByPlaceholder("Flow name").inputValue(); const flowName = await page.getByPlaceholder("Flow name").inputValue();
const flowDescription = await page const flowDescription = await page