feature: add edit option to global variables table (#3712)

* first attempt to edit variables on the data table

* refactor: Rename AddNewVariableButton to GlobalVariableModal and update its usage

The component AddNewVariableButton has been renamed to GlobalVariableModal to better reflect its purpose. The component is now used in multiple places, including the GlobalVariablesPage and InputGlobalComponent. This change improves code clarity and consistency.

* fix: fix apply to fields on table edit option

* refactor: Trim field names before checking for availability in GlobalVariableModal and GlobalVariablesPage

Trim field names before checking for availability in GlobalVariableModal and GlobalVariablesPage to ensure consistent comparison and avoid any potential issues with leading or trailing spaces.

* Refactor GlobalVariablesPage to remove unused cellRenderer in the "value" field

* [autofix.ci] apply automated fixes

* Add validation for 'value' field in VariableRead model and import CREDENTIAL_TYPE

- Introduced a field validator for the 'value' field in the VariableRead model to handle cases where the variable type is CREDENTIAL_TYPE.
- Added necessary import for CREDENTIAL_TYPE.
- Removed an unnecessary blank line in the variable API.

* Add constants for credential and generic types in variable service

* Refactor import statements in `kubernetes.py` to improve module organization

* Refactor imports in test_service.py for better organization

* refactor: Update import statements in variable.py for better organization

* Refactor import and reorder fields in VariableRead model

- Changed import of `CREDENTIAL_TYPE` from `service` to `constants` module.
- Reordered fields in `VariableRead` model to place `type` before `value`.

* ✨ (userSettings.spec.ts): Add additional randomName variables for testing purposes
📝 (userSettings.spec.ts): Update test to interact with global variables and improve readability and maintainability of the code

* test: fix test_create_variable

---------
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
Co-authored-by: italojohnny <italojohnnydosanjos@gmail.com>
This commit is contained in:
anovazzi1 2024-09-09 10:48:04 -03:00 • committed by GitHub
commit d3b8f6cd7e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 186 additions and 68 deletions

View file

@ -9,7 +9,8 @@ from langflow.services.database.models.user.model import User
from langflow.services.database.models.variable import VariableCreate, VariableRead, VariableUpdate from langflow.services.database.models.variable import VariableCreate, VariableRead, VariableUpdate
from langflow.services.deps import get_session, get_settings_service, get_variable_service from langflow.services.deps import get_session, get_settings_service, get_variable_service
from langflow.services.variable.base import VariableService from langflow.services.variable.base import VariableService
from langflow.services.variable.service import GENERIC_TYPE, DatabaseVariableService from langflow.services.variable.constants import GENERIC_TYPE
from langflow.services.variable.service import DatabaseVariableService
router = APIRouter(prefix="/variables", tags=["Variables"]) router = APIRouter(prefix="/variables", tags=["Variables"])
@ -61,7 +62,6 @@ def read_variables(
"""Read all variables.""" """Read all variables."""
try: try:
return variable_service.get_all(user_id=current_user.id, session=session) return variable_service.get_all(user_id=current_user.id, session=session)
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) from e raise HTTPException(status_code=500, detail=str(e)) from e

View file

@ -2,8 +2,11 @@ from datetime import datetime, timezone
from typing import TYPE_CHECKING, List, Optional from typing import TYPE_CHECKING, List, Optional
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from pydantic import ValidationInfo, field_validator
from sqlmodel import JSON, Column, DateTime, Field, Relationship, SQLModel, func from sqlmodel import JSON, Column, DateTime, Field, Relationship, SQLModel, func
from langflow.services.variable.constants import CREDENTIAL_TYPE
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.services.database.models.user.model import User from langflow.services.database.models.user.model import User
@ -52,8 +55,16 @@ class VariableRead(SQLModel):
id: UUID id: UUID
name: Optional[str] = Field(None, description="Name of the variable") name: Optional[str] = Field(None, description="Name of the variable")
type: Optional[str] = Field(None, description="Type of the variable") type: Optional[str] = Field(None, description="Type of the variable")
value: Optional[str] = Field(None, description="Encrypted value of the variable")
default_fields: Optional[List[str]] = Field(None, description="Default fields for the variable") default_fields: Optional[List[str]] = Field(None, description="Default fields for the variable")
@field_validator("value")
@classmethod
def validate_value(cls, value: str, info: ValidationInfo):
if info.data.get("type") == CREDENTIAL_TYPE:
return None
return value
class VariableUpdate(SQLModel): class VariableUpdate(SQLModel):
id: UUID # Include the ID for updating id: UUID # Include the ID for updating

View file

@ -0,0 +1,2 @@
CREDENTIAL_TYPE = "Credential"
GENERIC_TYPE = "Generic"

View file

@ -10,8 +10,8 @@ from langflow.services.base import Service
from langflow.services.database.models.variable.model import Variable, VariableCreate from langflow.services.database.models.variable.model import Variable, VariableCreate
from langflow.services.settings.service import SettingsService from langflow.services.settings.service import SettingsService
from langflow.services.variable.base import VariableService from langflow.services.variable.base import VariableService
from langflow.services.variable.constants import CREDENTIAL_TYPE, GENERIC_TYPE
from langflow.services.variable.kubernetes_secrets import KubernetesSecretManager, encode_user_id from langflow.services.variable.kubernetes_secrets import KubernetesSecretManager, encode_user_id
from langflow.services.variable.service import CREDENTIAL_TYPE, GENERIC_TYPE
class KubernetesSecretService(VariableService, Service): class KubernetesSecretService(VariableService, Service):

View file

@ -12,13 +12,11 @@ from langflow.services.base import Service
from langflow.services.database.models.variable.model import Variable, VariableCreate, VariableUpdate from langflow.services.database.models.variable.model import Variable, VariableCreate, VariableUpdate
from langflow.services.deps import get_session from langflow.services.deps import get_session
from langflow.services.variable.base import VariableService from langflow.services.variable.base import VariableService
from langflow.services.variable.constants import CREDENTIAL_TYPE, GENERIC_TYPE
if TYPE_CHECKING: if TYPE_CHECKING:
from langflow.services.settings.service import SettingsService from langflow.services.settings.service import SettingsService
CREDENTIAL_TYPE = "Credential"
GENERIC_TYPE = "Generic"
class DatabaseVariableService(VariableService, Service): class DatabaseVariableService(VariableService, Service):
def __init__(self, settings_service: "SettingsService"): def __init__(self, settings_service: "SettingsService"):

View file

@ -24,7 +24,7 @@ def test_create_variable(client, body, active_user, logged_in_headers):
assert body["type"] == result["type"] assert body["type"] == result["type"]
assert body["default_fields"] == result["default_fields"] assert body["default_fields"] == result["default_fields"]
assert "id" in result.keys() assert "id" in result.keys()
assert "value" not in result.keys() assert body["value"] != result["value"]
def test_create_variable__variable_name_alread_exists(client, body, active_user, logged_in_headers): def test_create_variable__variable_name_alread_exists(client, body, active_user, logged_in_headers):

View file

@ -1,11 +1,14 @@
from langflow.services.database.models.variable.model import VariableUpdate from datetime import datetime
import pytest
from unittest.mock import patch from unittest.mock import patch
from uuid import uuid4 from uuid import uuid4
from datetime import datetime
from sqlmodel import SQLModel, Session, create_engine import pytest
from sqlmodel import Session, SQLModel, create_engine
from langflow.services.database.models.variable.model import VariableUpdate
from langflow.services.deps import get_settings_service from langflow.services.deps import get_settings_service
from langflow.services.variable.service import GENERIC_TYPE, CREDENTIAL_TYPE, DatabaseVariableService from langflow.services.variable.constants import CREDENTIAL_TYPE, GENERIC_TYPE
from langflow.services.variable.service import DatabaseVariableService
@pytest.fixture @pytest.fixture

View file

@ -1,8 +1,10 @@
import { import {
useGetGlobalVariables, useGetGlobalVariables,
usePatchGlobalVariables,
usePostGlobalVariables, usePostGlobalVariables,
} from "@/controllers/API/queries/variables"; } from "@/controllers/API/queries/variables";
import getUnavailableFields from "@/stores/globalVariablesStore/utils/get-unavailable-fields"; import getUnavailableFields from "@/stores/globalVariablesStore/utils/get-unavailable-fields";
import { GlobalVariable } from "@/types/global_variables";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import BaseModal from "../../modals/baseModal"; import BaseModal from "../../modals/baseModal";
import useAlertStore from "../../stores/alertStore"; import useAlertStore from "../../stores/alertStore";
@ -17,21 +19,33 @@ import sortByName from "./utils/sort-by-name";
//TODO IMPLEMENT FORM LOGIC //TODO IMPLEMENT FORM LOGIC
export default function AddNewVariableButton({ export default function GlobalVariableModal({
children, children,
asChild, asChild,
initialData,
open: myOpen,
setOpen: mySetOpen,
}: { }: {
children: JSX.Element; children?: JSX.Element;
asChild?: boolean; asChild?: boolean;
initialData?: GlobalVariable;
open?: boolean;
setOpen?: (a: boolean | ((o?: boolean) => boolean)) => void;
}): JSX.Element { }): JSX.Element {
const [key, setKey] = useState(""); const [key, setKey] = useState(initialData?.name ?? "");
const [value, setValue] = useState(""); const [value, setValue] = useState(initialData?.value ?? "");
const [type, setType] = useState("Generic"); const [type, setType] = useState(initialData?.type ?? "Generic");
const [fields, setFields] = useState<string[]>([]); const [fields, setFields] = useState<string[]>(
const [open, setOpen] = useState(false); initialData?.default_fields ?? [],
);
const [open, setOpen] =
mySetOpen !== undefined && myOpen !== undefined
? [myOpen, mySetOpen]
: useState(false);
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const componentFields = useTypesStore((state) => state.ComponentFields); const componentFields = useTypesStore((state) => state.ComponentFields);
const { mutate: mutateAddGlobalVariable } = usePostGlobalVariables(); const { mutate: mutateAddGlobalVariable } = usePostGlobalVariables();
const { mutate: updateVariable } = usePatchGlobalVariables();
const { data: globalVariables } = useGetGlobalVariables(); const { data: globalVariables } = useGetGlobalVariables();
const [availableFields, setAvailableFields] = useState<string[]>([]); const [availableFields, setAvailableFields] = useState<string[]>([]);
@ -39,12 +53,13 @@ export default function AddNewVariableButton({
if (globalVariables && componentFields.size > 0) { if (globalVariables && componentFields.size > 0) {
const unavailableFields = getUnavailableFields(globalVariables); const unavailableFields = getUnavailableFields(globalVariables);
const fields = Array.from(componentFields).filter( const fields = Array.from(componentFields).filter(
(field) => !unavailableFields.hasOwnProperty(field), (field) => !unavailableFields.hasOwnProperty(field.trim()),
);
setAvailableFields(
sortByName(fields.concat(initialData?.default_fields ?? [])),
); );
setAvailableFields(sortByName(fields));
} }
}, [globalVariables, componentFields]); }, [globalVariables, componentFields, initialData]);
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
@ -71,35 +86,52 @@ export default function AddNewVariableButton({
setOpen(false); setOpen(false);
setSuccessData({ setSuccessData({
title: `Variable ${name} created successfully`, title: `Variable ${name} ${initialData ? "updated" : "created"} successfully`,
}); });
}, },
onError: (error) => { onError: (error) => {
let responseError = error as ResponseErrorDetailAPI; let responseError = error as ResponseErrorDetailAPI;
setErrorData({ setErrorData({
title: "Error creating variable", title: `Error ${initialData ? "updating" : "creating"} variable`,
list: [ list: [
responseError?.response?.data?.detail ?? responseError?.response?.data?.detail ??
"An unexpected error occurred while adding a new variable. Please try again.", `An unexpected error occurred while ${initialData ? "updating a new" : "creating"} variable. Please try again.`,
], ],
}); });
}, },
}); });
} }
function submitForm() {
if (!initialData) {
handleSaveVariable();
} else {
updateVariable({
id: initialData.id,
name: key,
value: value,
default_fields: fields,
});
setOpen(false);
}
}
return ( return (
<BaseModal <BaseModal
open={open} open={open}
setOpen={setOpen} setOpen={setOpen}
size="x-small" size="x-small"
onSubmit={handleSaveVariable} onSubmit={submitForm}
> >
<BaseModal.Header <BaseModal.Header
description={ description={
"This variable will be encrypted and will be available for you to use in any of your projects." "This variable will be encrypted and will be available for you to use in any of your projects."
} }
> >
<span className="pr-2"> Create Variable </span> <span className="pr-2">
{" "}
{initialData ? "Update" : "Create"} Variable{" "}
</span>
<ForwardedIconComponent <ForwardedIconComponent
name="Globe" name="Globe"
className="h-6 w-6 pl-1 text-primary" className="h-6 w-6 pl-1 text-primary"
@ -119,6 +151,7 @@ export default function AddNewVariableButton({
></Input> ></Input>
<Label>Type (optional)</Label> <Label>Type (optional)</Label>
<InputComponent <InputComponent
disabled={initialData?.type !== undefined}
setSelectedOption={(e) => { setSelectedOption={(e) => {
setType(e); setType(e);
}} }}
@ -161,7 +194,10 @@ export default function AddNewVariableButton({
</div> </div>
</BaseModal.Content> </BaseModal.Content>
<BaseModal.Footer <BaseModal.Footer
submit={{ label: "Save Variable", dataTestId: "save-variable-btn" }} submit={{
label: `${initialData ? "Update" : "Save"} Variable`,
dataTestId: "save-variable-btn",
}}
/> />
</BaseModal> </BaseModal>
); );

View file

@ -7,7 +7,7 @@ import DeleteConfirmationModal from "../../modals/deleteConfirmationModal";
import useAlertStore from "../../stores/alertStore"; import useAlertStore from "../../stores/alertStore";
import { InputGlobalComponentType } from "../../types/components"; import { InputGlobalComponentType } from "../../types/components";
import { cn } from "../../utils/utils"; import { cn } from "../../utils/utils";
import AddNewVariableButton from "../addNewVariableButtonComponent/addNewVariableButton"; import GlobalVariableModal from "../GlobalVariableModal/GlobalVariableModal";
import ForwardedIconComponent from "../genericIconComponent"; import ForwardedIconComponent from "../genericIconComponent";
import InputComponent from "../inputComponent"; import InputComponent from "../inputComponent";
import { CommandItem } from "../ui/command"; import { CommandItem } from "../ui/command";
@ -72,7 +72,7 @@ export default function InputGlobalComponent({
optionsPlaceholder={"Global Variables"} optionsPlaceholder={"Global Variables"}
optionsIcon="Globe" optionsIcon="Globe"
optionsButton={ optionsButton={
<AddNewVariableButton> <GlobalVariableModal>
<CommandItem value="doNotFilter-addNewVariable"> <CommandItem value="doNotFilter-addNewVariable">
<ForwardedIconComponent <ForwardedIconComponent
name="Plus" name="Plus"
@ -81,7 +81,7 @@ export default function InputGlobalComponent({
/> />
<span>Add New Variable</span> <span>Add New Variable</span>
</CommandItem> </CommandItem>
</AddNewVariableButton> </GlobalVariableModal>
} }
optionButton={(option) => ( optionButton={(option) => (
<DeleteConfirmationModal <DeleteConfirmationModal

View file

@ -15,6 +15,7 @@ export default function TableAutoCellRender({
setValue, setValue,
colDef, colDef,
formatter, formatter,
api,
}: CustomCellRender) { }: CustomCellRender) {
function getCellType() { function getCellType() {
let format: string = formatter ? formatter : typeof value; let format: string = formatter ? formatter : typeof value;
@ -63,7 +64,10 @@ export default function TableAutoCellRender({
} else { } else {
return ( return (
<StringReader <StringReader
editable={!!colDef?.onCellValueChanged} editable={
!!colDef?.onCellValueChanged ||
!!api.getGridOption("onCellValueChanged")
}
setValue={setValue!} setValue={setValue!}
string={value} string={value}
/> />

View file

@ -1,13 +1,15 @@
import { useMutationFunctionType } from "@/types/api"; import { useMutationFunctionType } from "@/types/api";
import { GlobalVariable } from "@/types/global_variables";
import { UseMutationResult } from "@tanstack/react-query"; import { UseMutationResult } from "@tanstack/react-query";
import { api } from "../../api"; import { api } from "../../api";
import { getURL } from "../../helpers/constants"; import { getURL } from "../../helpers/constants";
import { UseRequestProcessor } from "../../services/request-processor"; import { UseRequestProcessor } from "../../services/request-processor";
interface PatchGlobalVariablesParams { interface PatchGlobalVariablesParams {
name: string; name?: string;
value: string; value?: string;
id: string; id: string;
default_fields?: string[];
} }
export const usePatchGlobalVariables: useMutationFunctionType< export const usePatchGlobalVariables: useMutationFunctionType<
@ -16,15 +18,13 @@ export const usePatchGlobalVariables: useMutationFunctionType<
> = (options?) => { > = (options?) => {
const { mutate, queryClient } = UseRequestProcessor(); const { mutate, queryClient } = UseRequestProcessor();
async function patchGlobalVariables({ async function patchGlobalVariables(
name, GlobalVariable: PatchGlobalVariablesParams,
value, ): Promise<any> {
id, const res = await api.patch(
}: PatchGlobalVariablesParams): Promise<any> { `${getURL("VARIABLES")}/${GlobalVariable.id}`,
const res = await api.patch(`${getURL("VARIABLES")}/${id}`, { GlobalVariable,
name, );
value,
});
return res.data; return res.data;
} }

View file

@ -1,13 +1,22 @@
import IconComponent from "../../../../components/genericIconComponent"; import IconComponent from "../../../../components/genericIconComponent";
import { Button } from "../../../../components/ui/button"; import { Button } from "../../../../components/ui/button";
import TableAutoCellRender from "@/components/tableComponent/components/tableAutoCellRender";
import { import {
useDeleteGlobalVariables, useDeleteGlobalVariables,
useGetGlobalVariables, useGetGlobalVariables,
} from "@/controllers/API/queries/variables"; } from "@/controllers/API/queries/variables";
import { ColDef, ColGroupDef, SelectionChangedEvent } from "ag-grid-community"; import { useTypesStore } from "@/stores/typesStore";
import { useState } from "react"; import { GlobalVariable } from "@/types/global_variables";
import AddNewVariableButton from "../../../../components/addNewVariableButtonComponent/addNewVariableButton"; import {
ColDef,
ColGroupDef,
RowClickedEvent,
RowDoubleClickedEvent,
SelectionChangedEvent,
} from "ag-grid-community";
import { useEffect, useRef, useState } from "react";
import GlobalVariableModal from "../../../../components/GlobalVariableModal/GlobalVariableModal";
import Dropdown from "../../../../components/dropdownComponent"; import Dropdown from "../../../../components/dropdownComponent";
import ForwardedIconComponent from "../../../../components/genericIconComponent"; import ForwardedIconComponent from "../../../../components/genericIconComponent";
import TableComponent from "../../../../components/tableComponent"; import TableComponent from "../../../../components/tableComponent";
@ -16,6 +25,9 @@ import useAlertStore from "../../../../stores/alertStore";
export default function GlobalVariablesPage() { export default function GlobalVariablesPage() {
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const [openModal, setOpenModal] = useState(false);
const initialData = useRef<GlobalVariable | undefined>(undefined);
const getTypes = useTypesStore((state) => state.getTypes);
const BadgeRenderer = (props) => { const BadgeRenderer = (props) => {
return props.value !== "" ? ( return props.value !== "" ? (
<div> <div>
@ -28,6 +40,11 @@ export default function GlobalVariablesPage() {
); );
}; };
useEffect(() => {
//get the components to build the Aplly To Fields dropdown
getTypes(true);
}, []);
const DropdownEditor = ({ options, value, onValueChange }) => { const DropdownEditor = ({ options, value, onValueChange }) => {
return ( return (
<Dropdown options={options} value={value} onSelect={onValueChange}> <Dropdown options={options} value={value} onSelect={onValueChange}>
@ -36,7 +53,7 @@ export default function GlobalVariablesPage() {
); );
}; };
// Column Definitions: Defines the columns to be displayed. // Column Definitions: Defines the columns to be displayed.
const [colDefs, setColDefs] = useState<(ColDef<any> | ColGroupDef<any>)[]>([ const colDefs: ColDef[] = [
{ {
headerName: "Variable Name", headerName: "Variable Name",
field: "name", field: "name",
@ -51,25 +68,20 @@ export default function GlobalVariablesPage() {
options: ["Generic", "Credential"], options: ["Generic", "Credential"],
}, },
flex: 1, flex: 1,
editable: false,
}, },
// { {
// field: "value", field: "value",
// cellEditor: "agLargeTextCellEditor", },
// flex: 2,
// editable: false,
// },
{ {
headerName: "Apply To Fields", headerName: "Apply To Fields",
field: "default_fields", field: "default_fields",
valueFormatter: (params) => { valueFormatter: (params) => {
return params.value.join(", "); return params.value?.join(", ") ?? "";
}, },
flex: 1, flex: 1,
editable: false,
resizable: false, resizable: false,
}, },
]); ];
const [selectedRows, setSelectedRows] = useState<string[]>([]); const [selectedRows, setSelectedRows] = useState<string[]>([]);
@ -93,6 +105,11 @@ export default function GlobalVariablesPage() {
}); });
} }
function updateVariables(event: RowClickedEvent<GlobalVariable>) {
initialData.current = event.data;
setOpenModal(true);
}
return ( return (
<div className="flex h-full w-full flex-col justify-between gap-6"> <div className="flex h-full w-full flex-col justify-between gap-6">
<div className="flex w-full items-start justify-between gap-6"> <div className="flex w-full items-start justify-between gap-6">
@ -109,12 +126,12 @@ export default function GlobalVariablesPage() {
</p> </p>
</div> </div>
<div className="flex flex-shrink-0 items-center gap-2"> <div className="flex flex-shrink-0 items-center gap-2">
<AddNewVariableButton asChild> <GlobalVariableModal asChild>
<Button data-testid="api-key-button-store" variant="primary"> <Button data-testid="api-key-button-store" variant="primary">
<IconComponent name="Plus" className="w-4" /> <IconComponent name="Plus" className="w-4" />
Add New Add New
</Button> </Button>
</AddNewVariableButton> </GlobalVariableModal>
</div> </div>
</div> </div>
@ -126,12 +143,21 @@ export default function GlobalVariablesPage() {
setSelectedRows(event.api.getSelectedRows().map((row) => row.name)); setSelectedRows(event.api.getSelectedRows().map((row) => row.name));
}} }}
rowSelection="multiple" rowSelection="multiple"
onRowClicked={updateVariables}
suppressRowClickSelection={true} suppressRowClickSelection={true}
pagination={true} pagination={true}
columnDefs={colDefs} columnDefs={colDefs}
rowData={globalVariables ?? []} rowData={globalVariables ?? []}
onDelete={removeVariables} onDelete={removeVariables}
/> />
{initialData.current && (
<GlobalVariableModal
key={initialData.current.id}
initialData={initialData.current}
open={openModal}
setOpen={setOpenModal}
/>
)}
</div> </div>
</div> </div>
); );

View file

@ -3,4 +3,5 @@ export type GlobalVariable = {
type: string; type: string;
default_fields: string[]; default_fields: string[];
name: string; name: string;
value?: string;
}; };

View file

@ -21,6 +21,8 @@ test("should see general profile gradient", async ({ page }) => {
test("should interact with global variables", async ({ page }) => { test("should interact with global variables", async ({ page }) => {
const randomName = Math.random().toString(36).substring(2); const randomName = Math.random().toString(36).substring(2);
const randomName2 = Math.random().toString(36).substring(2);
const randomName3 = Math.random().toString(36).substring(2);
await page.goto("/"); await page.goto("/");
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
@ -50,29 +52,64 @@ test("should interact with global variables", async ({ page }) => {
.fill("testtesttesttesttesttesttesttest"); .fill("testtesttesttesttesttesttesttest");
await page.getByTestId("popover-anchor-apply-to-fields").click(); await page.getByTestId("popover-anchor-apply-to-fields").click();
await page.waitForTimeout(3000); await page.waitForTimeout(1000);
await page.getByPlaceholder("Search options...").fill("System Message"); await page.getByPlaceholder("Search options...").fill("System");
await page.waitForSelector("text=System Message", { timeout: 30000 }); await page.waitForTimeout(500);
await page.getByText("System Message").first().click(); await page.waitForSelector("text=System", { timeout: 30000 });
await page.waitForTimeout(500);
await page.getByText("System").last().click();
await page.getByPlaceholder("Search options...").fill("openAI"); await page.getByPlaceholder("Search options...").fill("openAI");
await page.waitForSelector("text=OpenAI API Base", { timeout: 30000 }); await page.waitForSelector("text=openai", { timeout: 30000 });
await page.getByText("OpenAI API Base").first().click(); await page.waitForTimeout(500);
await page.getByPlaceholder("Search options...").fill("llama"); await page.getByText("openai").last().click();
await page.getByText("Ollama").first().click(); await page.waitForTimeout(500);
await page.getByPlaceholder("Search options...").fill("ollama");
await page.waitForSelector("text=ollama", { timeout: 30000 });
await page.getByText("ollama").first().click();
await page.keyboard.press("Escape"); await page.keyboard.press("Escape");
await page.getByText("Save Variable", { exact: true }).click(); await page.getByText("Save Variable", { exact: true }).click();
await page.getByText(randomName).last().isVisible(); await page.getByText(randomName).last().isVisible();
await page.getByText(randomName).last().click();
await page.getByText(randomName).last().click();
await page.waitForTimeout(500);
await page
.getByPlaceholder("Insert a name for the variable...")
.fill(randomName2);
await page.getByText("Update Variable", { exact: true }).last().click();
await page.getByText(randomName2).last().isVisible();
await page.getByText(randomName2).last().click();
await page.waitForTimeout(500);
await page
.getByPlaceholder("Insert a name for the variable...")
.fill(randomName3);
await page.getByText("Update Variable", { exact: true }).last().click();
await page.getByText(randomName3).last().isVisible();
const focusElementsOnBoard = async ({ page }) => { const focusElementsOnBoard = async ({ page }) => {
await page.waitForSelector( await page.waitForSelector(
'[aria-label="Press Space to toggle all rows selection (unchecked)"]', '[aria-label="Press Space to toggle all rows selection (unchecked)"]',