diff --git a/packages/variable-editor/src/components/variables/dialog/AddDialog.tsx b/packages/variable-editor/src/components/variables/dialog/AddDialog.tsx index 730d0d3f..4daf36aa 100644 --- a/packages/variable-editor/src/components/variables/dialog/AddDialog.tsx +++ b/packages/variable-editor/src/components/variables/dialog/AddDialog.tsx @@ -11,16 +11,18 @@ import { DialogTrigger, Flex, Input, + Message, selectRow } from '@axonivy/ui-components'; import { IvyIcons } from '@axonivy/ui-icons'; -import { EMPTY_KNOWN_VARIABLES } from '@axonivy/variable-editor-protocol'; +import { EMPTY_KNOWN_VARIABLES, type KnownVariables } from '@axonivy/variable-editor-protocol'; import { type Table } from '@tanstack/react-table'; -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useAppContext } from '../../../context/AppContext'; import { useMeta } from '../../../context/useMeta'; import { keyOfFirstSelectedNonLeafRow, keysOfAllNonLeafRows, newNodeName, subRowNamesOfRow, toRowId } from '../../../utils/tree/tree'; import { addNode } from '../../../utils/tree/tree-data'; +import type { AddNodeReturnType } from '../../../utils/tree/types'; import { validateName, validateNamespace } from '../data/validation-utils'; import { createVariable, type Variable } from '../data/variable'; import './AddDialog.css'; @@ -37,9 +39,13 @@ export const AddVariableDialog = ({ table }: AddVariableDialogProps) => { const [namespace, setNamespace] = useState(''); const nameValidationMessage = useMemo(() => validateName(name, subRowNamesOfRow(namespace, table)), [name, namespace, table]); - const namespaceValidationMessage = useMemo(() => validateNamespace(namespace, variables), [namespace, variables]); + const [knownVariable, setKnownVariable] = useState(); + useEffect(() => setKnownVariable(undefined), [name, namespace]); + + const knownVariables = useMeta('meta/knownVariables', context, EMPTY_KNOWN_VARIABLES).data; + const initializeVariableDialog = () => { setName(newNodeName(table, 'NewVariable')); setNamespace(keyOfFirstSelectedNonLeafRow(table)); @@ -47,24 +53,42 @@ export const AddVariableDialog = ({ table }: AddVariableDialogProps) => { const namespaceOptions = () => keysOfAllNonLeafRows(table).map(key => ({ value: key })); - const knownVariables = useMeta('meta/knownVariables', context, EMPTY_KNOWN_VARIABLES).data; + const updateSelection = (addNodeReturnValue: AddNodeReturnType) => { + selectRow(table, toRowId(addNodeReturnValue.newNodePath)); + setSelectedVariable(addNodeReturnValue.newNodePath); + }; - const addVariable = () => { - const namespaceKey = namespace ? namespace.split('.') : []; - const overwrittenVariable = findKnownVariable(knownVariables, ...namespaceKey, name); + const addKnown = (knownVariable: KnownVariables) => { setVariables(old => { - let addNodeReturnValue; - if (overwrittenVariable) { - addNodeReturnValue = addKnownVariable(old, overwrittenVariable); - } else { - addNodeReturnValue = addNode(name, namespace, old, createVariable); - } - selectRow(table, toRowId(addNodeReturnValue.newNodePath)); - setSelectedVariable(addNodeReturnValue.newNodePath); + const addNodeReturnValue = addKnownVariable(old, knownVariable); + updateSelection(addNodeReturnValue); return addNodeReturnValue.newData; }); }; + const addVar = () => { + setVariables(old => { + const addNodeReturnValue = addNode(name, namespace, old, createVariable); + updateSelection(addNodeReturnValue); + return addNodeReturnValue.newData; + }); + }; + + const addVariable = (event: React.MouseEvent) => { + if (knownVariable) { + addKnown(knownVariable); + return; + } + const namespaceKey = namespace ? namespace.split('.') : []; + const foundKnownVariable = findKnownVariable(knownVariables, ...namespaceKey, name); + if (foundKnownVariable) { + setKnownVariable(foundKnownVariable); + event.preventDefault(); + return; + } + addVar(); + }; + const allInputsValid = () => !nameValidationMessage && !namespaceValidationMessage; return ( @@ -106,18 +130,24 @@ export const AddVariableDialog = ({ table }: AddVariableDialogProps) => { - - - + + {knownVariable && ( + + )} + + + + diff --git a/playwright/tests/integration/mock/editor.spec.ts b/playwright/tests/integration/mock/editor.spec.ts index 70d6ecf8..7a8ac7c6 100644 --- a/playwright/tests/integration/mock/editor.spec.ts +++ b/playwright/tests/integration/mock/editor.spec.ts @@ -1,6 +1,6 @@ -import { test } from '@playwright/test'; -import { VariableEditor } from '../../pageobjects/VariableEditor'; +import { expect, test } from '@playwright/test'; import type { Table } from '../../pageobjects/Table'; +import { VariableEditor } from '../../pageobjects/VariableEditor'; let editor: VariableEditor; let tree: Table; @@ -89,7 +89,7 @@ test('add', async () => { test('addVariableDialogDefaultValues', async () => { await tree.row(5).click(); await tree.row(5).expectValues(['useUserPassFlow', '']); - await editor.add.open(); + await editor.add.open.click(); await editor.add.expectValues('NewVariable', 'microsoft-connector.useUserPassFlow', 'microsoft-connector', 'microsoft-connector.useUserPassFlow'); }); @@ -97,48 +97,48 @@ test.describe('addVariableDialogValidation', async () => { test.describe('name', async () => { test('onNameChange', async () => { const add = editor.add; - await add.open(); + await add.open.click(); await add.nameMessage.expectToBeHidden(); - await add.expectCreateEnabled(); + await expect(add.create.locator).toBeEnabled(); await add.name.fill(''); await add.nameMessage.expectErrorMessage('Name cannot be empty.'); - await add.expectCreateDisabled(); + await expect(add.create.locator).toBeDisabled(); await add.name.fill('microsoft-connector'); await add.nameMessage.expectErrorMessage('Name is already present in this Namespace.'); - await add.expectCreateDisabled(); + await expect(add.create.locator).toBeDisabled(); }); test('onNamespaceChange', async () => { const add = editor.add; - await add.open(); + await add.open.click(); await add.name.fill('appId'); await add.nameMessage.expectToBeHidden(); - await add.expectCreateEnabled(); + await expect(add.create.locator).toBeEnabled(); await add.namespace.choose('microsoft-connector'); await add.nameMessage.expectErrorMessage('Name is already present in this Namespace.'); - await add.expectCreateDisabled(); + await expect(add.create.locator).toBeDisabled(); }); test('onNamespaceInput', async () => { const add = editor.add; - await add.open(); + await add.open.click(); await add.name.fill('appId'); await add.nameMessage.expectToBeHidden(); - await add.expectCreateEnabled(); + await expect(add.create.locator).toBeEnabled(); await add.namespace.fill('microsoft-connector'); await add.nameMessage.expectErrorMessage('Name is already present in this Namespace.'); - await add.expectCreateDisabled(); + await expect(add.create.locator).toBeDisabled(); }); }); test('namespace', async () => { const add = editor.add; - await add.open(); + await add.open.click(); await add.namespaceMessage.expectInfoMessage("Folder structure of variable (e.g. 'Connector.Key')"); - await add.expectCreateEnabled(); + await expect(add.create.locator).toBeEnabled(); await add.namespace.fill('microsoft-connector.appId.New.Namespace'); await add.namespaceMessage.expectErrorMessage("Namespace 'microsoft-connector.appId' is not a folder, you cannot add a child to it."); - await add.expectCreateDisabled(); + await expect(add.create.locator).toBeDisabled(); }); }); diff --git a/playwright/tests/integration/mock/import.spec.ts b/playwright/tests/integration/mock/import.spec.ts index ba40a6ee..61cb9580 100644 --- a/playwright/tests/integration/mock/import.spec.ts +++ b/playwright/tests/integration/mock/import.spec.ts @@ -89,7 +89,18 @@ describe('disabledMetadataOfOverwrittenVariable', async () => { }); test('import variable when manually adding a known variable', async () => { - await editor.addVariable('Comprehend', 'Amazon'); + await editor.add.open.click(); + await editor.add.name.fill('Comprehend'); + await editor.add.namespace.fill('Amazon'); + + await expect(editor.add.footerMessage).toBeHidden(); + + await editor.add.create.click(); + + await expect(editor.add.footerMessage).toBeVisible(); + + await editor.add.create.click(); + await editor.details.expectFolderValues('Comprehend', 'Amazon comprehend connector settings'); await editor.tree.expectRowCount(15); await editor.tree.row(11).click(); diff --git a/playwright/tests/pageobjects/AddVariableDialog.ts b/playwright/tests/pageobjects/AddVariableDialog.ts index 4fae423b..dff02874 100644 --- a/playwright/tests/pageobjects/AddVariableDialog.ts +++ b/playwright/tests/pageobjects/AddVariableDialog.ts @@ -5,41 +5,27 @@ import { FieldMessage } from './FieldMessage'; import { TextArea } from './TextArea'; export class AddVariableDialog { - private readonly add: Button; + readonly open: Button; readonly name: TextArea; readonly nameMessage: FieldMessage; readonly namespace: Combobox; readonly namespaceMessage: FieldMessage; - private readonly create: Button; + readonly footerMessage: Locator; + readonly create: Button; constructor(page: Page, parent: Locator) { - this.add = new Button(parent, { name: 'Add variable' }); + this.open = new Button(parent, { name: 'Add variable' }); this.name = new TextArea(parent); this.nameMessage = new FieldMessage(parent, { label: 'Name' }); this.namespace = new Combobox(page, parent); this.namespaceMessage = new FieldMessage(parent, { label: 'Namespace' }); + this.footerMessage = parent.locator('.ui-dialog-footer').locator('.ui-message'); this.create = new Button(parent, { name: 'Create variable' }); } - async open() { - await this.add.click(); - } - async expectValues(name: string, namespaceValue: string, ...namespaceOptions: Array) { await this.name.expectValue(name); await this.namespace.expectValue(namespaceValue); await this.namespace.expectOptions(...namespaceOptions); } - - async createVariable() { - await this.create.click(); - } - - async expectCreateEnabled() { - await this.create.expectEnabled(); - } - - async expectCreateDisabled() { - await this.create.expectDisabled(); - } } diff --git a/playwright/tests/pageobjects/Button.ts b/playwright/tests/pageobjects/Button.ts index 9697efe0..24bd77fc 100644 --- a/playwright/tests/pageobjects/Button.ts +++ b/playwright/tests/pageobjects/Button.ts @@ -1,7 +1,7 @@ import { expect, type Locator } from '@playwright/test'; export class Button { - private readonly locator: Locator; + readonly locator: Locator; constructor(parentLocator: Locator, options?: { name?: string; nth?: number }) { if (options?.name) { diff --git a/playwright/tests/pageobjects/VariableEditor.ts b/playwright/tests/pageobjects/VariableEditor.ts index 4cff0e44..cfee4066 100644 --- a/playwright/tests/pageobjects/VariableEditor.ts +++ b/playwright/tests/pageobjects/VariableEditor.ts @@ -2,10 +2,10 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { AddVariableDialog } from './AddVariableDialog'; import { Button } from './Button'; import { Details } from './Details'; +import { OverwriteDialog } from './OverwriteDialog'; import { Settings } from './Settings'; import { Table } from './Table'; import { TextArea } from './TextArea'; -import { OverwriteDialog } from './OverwriteDialog'; export class VariableEditor { readonly page: Page; @@ -69,13 +69,13 @@ export class VariableEditor { } async addVariable(name?: string, namespace?: string) { - await this.add.open(); + await this.add.open.click(); if (name) { await this.add.name.fill(name); } if (namespace) { await this.add.namespace.fill(namespace); } - await this.add.createVariable(); + await this.add.create.click(); } }