diff --git a/client/src/pages/automation/project/components/project-header/components/DeployButton.tsx b/client/src/pages/automation/project/components/project-header/components/DeployButton.tsx index 0c2f382f592..ed843785092 100644 --- a/client/src/pages/automation/project/components/project-header/components/DeployButton.tsx +++ b/client/src/pages/automation/project/components/project-header/components/DeployButton.tsx @@ -34,8 +34,14 @@ const DeployButton = ({project}: {project: Project}) => { return ( - - - + {(lookupDependsOnValues && (isLoading || isClusterElementNodeOptionsLoading)) || + isClusterElementOptionsLoading ? ( + + Loading... + + ) : null} + + {((lookupDependsOnValues && + !(isLoading || isClusterElementNodeOptionsLoading || isClusterElementOptionsLoading)) || + !lookupDependsOnValues) && ( + <> + {currentOption ? ( + + {currentOption?.icon && ( + + )} + + {currentOption?.label} + + ) : ( + !(isRefetching || isClusterElementNodeOptionsRefetching) && ( + {memoizedPlaceholder} + ) + )} + + )} - + + + + )} + + event.preventDefault()} + side="bottom" + > - + {!optionsLoadedDynamically && ( + + )} - No item found. + {!optionsLoadedDynamically && No item found.} - { - setOpen(false); - - if (onValueChange) { - onValueChange(''); - } - }} - value="" - > - Select... - - {value === '' && } - + {optionsLoadedDynamically && (isLoading || isRefetching) && ( +
+ + + Loading options... +
+ )} + + {optionsLoadedDynamically && + !isLoading && + !isRefetching && + !(options as Array)?.length && ( +
+ Start typing to filter... +
+ )} {(options as Array)?.map((option) => { const labelAndDescription = [ @@ -580,6 +654,25 @@ const PropertyComboBox = ({ ); })} + + {!optionsLoadedDynamically && ( + { + setOpen(false); + + if (onValueChange) { + onValueChange(''); + } + }} + value="" + > + Select... + + {value === '' && } + + )}
diff --git a/client/src/pages/platform/workflow-editor/components/properties/hooks/useProperty.ts b/client/src/pages/platform/workflow-editor/components/properties/hooks/useProperty.ts index ec34d96c879..de9e106addb 100644 --- a/client/src/pages/platform/workflow-editor/components/properties/hooks/useProperty.ts +++ b/client/src/pages/platform/workflow-editor/components/properties/hooks/useProperty.ts @@ -152,6 +152,7 @@ type UsePropertyReturnType = { name: string | undefined; options?: PropertyAllType['options']; optionsDataSource?: OptionsDataSource; + optionsLoadedDynamically?: boolean; placeholder: string; propertiesDataSource?: PropertiesDataSource; /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -297,6 +298,7 @@ export const useProperty = ({ numberPrecision, options, optionsDataSource, + optionsLoadedDynamically, placeholder = '', properties, propertiesDataSource, @@ -317,6 +319,10 @@ export const useProperty = ({ let {displayCondition} = property; + if (optionsLoadedDynamically) { + console.log(property); + } + const { deleteClusterElementParameterMutation, deleteWorkflowNodeParameterMutation, @@ -1815,6 +1821,7 @@ export const useProperty = ({ name, options, optionsDataSource, + optionsLoadedDynamically, placeholder, propertiesDataSource, propertyParameterValue, diff --git a/client/src/shared/types.ts b/client/src/shared/types.ts index 4beb0b81cac..269c85b68b4 100644 --- a/client/src/shared/types.ts +++ b/client/src/shared/types.ts @@ -368,6 +368,7 @@ export type PropertyAllType = Omit & { controlType?: ControlType; custom?: boolean; expressionEnabled?: boolean; + optionsLoadedDynamically?: boolean; properties?: Array; }; diff --git a/client/test/playwright/pages/workflowPage.ts b/client/test/playwright/pages/workflowPage.ts index f9ac2687838..70ccb9f772d 100644 --- a/client/test/playwright/pages/workflowPage.ts +++ b/client/test/playwright/pages/workflowPage.ts @@ -106,6 +106,10 @@ export class WorkflowPage { return this.arrayPropertyItemAt(index).getByRole('textbox'); } + arrayPropertyItemSpinbuttonAt(index: number): Locator { + return this.arrayPropertyItemAt(index).getByRole('spinbutton'); + } + async addArrayItemsToReachRowCount({ itemType = 'STRING', targetRowCount, diff --git a/client/test/playwright/tests/properties/arrayProperty.spec.ts b/client/test/playwright/tests/properties/arrayProperty.spec.ts index 9a3830864bf..05553630ffa 100644 --- a/client/test/playwright/tests/properties/arrayProperty.spec.ts +++ b/client/test/playwright/tests/properties/arrayProperty.spec.ts @@ -402,7 +402,7 @@ test.describe('ArrayProperty - Array property type (ArrayProperty.tsx)', () => { }); await test.step('Set first INTEGER row to a value and leave the second INTEGER row empty', async () => { - const firstIntegerInput = workflowPage.arrayPropertyItemTextboxAt(1); + const firstIntegerInput = workflowPage.arrayPropertyItemSpinbuttonAt(1); await replaceMentionsInputValue({ input: firstIntegerInput, @@ -410,7 +410,7 @@ test.describe('ArrayProperty - Array property type (ArrayProperty.tsx)', () => { value: valueOnDeletedIntegerRow, }); - const secondIntegerInput = workflowPage.arrayPropertyItemTextboxAt(2); + const secondIntegerInput = workflowPage.arrayPropertyItemSpinbuttonAt(2); await replaceMentionsInputValue({ input: secondIntegerInput, @@ -434,7 +434,7 @@ test.describe('ArrayProperty - Array property type (ArrayProperty.tsx)', () => { await expect(arrayItems).toHaveCount(2); - const remainingIntegerRowAfterDeleted = workflowPage.arrayPropertyItemTextboxAt(1); + const remainingIntegerRowAfterDeleted = workflowPage.arrayPropertyItemSpinbuttonAt(1); await expect(remainingIntegerRowAfterDeleted).toHaveValue(''); }); diff --git a/client/test/playwright/tests/properties/objectProperty.spec.ts b/client/test/playwright/tests/properties/objectProperty.spec.ts index c1381616b1b..4075877ec42 100644 --- a/client/test/playwright/tests/properties/objectProperty.spec.ts +++ b/client/test/playwright/tests/properties/objectProperty.spec.ts @@ -78,7 +78,7 @@ test.describe('ObjectProperty - Object property type (ObjectProperty.tsx)', () = const integerProperty = workflowPage.firstTaskComponentConfigurationPanel.getByLabel('Integer property'); - const integerInput = integerProperty.getByRole('textbox'); + const integerInput = integerProperty.getByRole('spinbutton'); await expect(integerInput).toHaveValue(expectedValues.Integer); }); diff --git a/client/test/playwright/tests/properties/propertyPersistence.spec.ts b/client/test/playwright/tests/properties/propertyPersistence.spec.ts index a5cc5fe5f86..0ed611b629c 100644 --- a/client/test/playwright/tests/properties/propertyPersistence.spec.ts +++ b/client/test/playwright/tests/properties/propertyPersistence.spec.ts @@ -5,6 +5,7 @@ import {WorkflowPage} from '../../pages/workflowPage'; import sampleWorkflow from '../../sampleWorkflow.json'; import {clickAndExpectToBeVisible} from '../../utils/clickAndExpectToBeVisible'; import {type TestProjectI, type TestWorkflowI} from '../../utils/projectUtils'; +import {fillPropertyInput} from '../../utils/propertyValidationUtils'; import {getWorkflowDefinition, openPropertiesTab, reopenConfigurationPanel} from '../../utils/workflowUtils'; export const test = mergeTests(loginTest(), projectTest, importWorkflowTest); @@ -69,7 +70,7 @@ test.describe('Reading from Workflow Definition', () => { await test.step('integer property', async () => { const integerProperty = configurationPanel.getByLabel('Integer property'); - const integerInput = integerProperty.getByRole('textbox'); + const integerInput = integerProperty.getByRole('spinbutton'); await expect(integerInput).toHaveValue(expectedValues.Integer); }); @@ -216,6 +217,10 @@ test.describe('Saving to Workflow Definition', () => { expect(expectedPropertyType).toBe('object'); expect(propertyInputValue).toBe(expectedPropertyValue[0].toString()); + } else if (expectedPropertyName === 'Integer') { + const integerInput = property.getByRole('spinbutton'); + + await expect(integerInput).toHaveValue(expectedPropertyValue!.toString()); } else { const propertyInputValue = await propertyInput.inputValue(); @@ -244,7 +249,7 @@ test.describe('Saving to Workflow Definition', () => { test('should save basic INTEGER property', async () => { const integerProperty = configurationPanel.getByLabel('Integer property'); - const integerInput = integerProperty.getByRole('textbox'); + const integerInput = integerProperty.getByRole('spinbutton'); const testValue = 123; @@ -259,7 +264,7 @@ test.describe('Saving to Workflow Definition', () => { test('should save negative value to the INTEGER property', async () => { const integerProperty = configurationPanel.getByLabel('Integer property'); - const integerInput = integerProperty.getByRole('textbox'); + const integerInput = integerProperty.getByRole('spinbutton'); const testValue = -123; diff --git a/client/test/playwright/tests/properties/propertyValidation.spec.ts b/client/test/playwright/tests/properties/propertyValidation.spec.ts index 6bfdb9b1c03..88abd8334ca 100644 --- a/client/test/playwright/tests/properties/propertyValidation.spec.ts +++ b/client/test/playwright/tests/properties/propertyValidation.spec.ts @@ -27,7 +27,7 @@ test.describe('Property validation - string', () => { test.describe('String Regular Expression', () => { test('should show validation error when value does not match regex (only letters allowed)', async () => { await test.step('Enter invalid value', async () => { - await fillPropertyInput(configurationPanel, 'stringRegEx property', '123'); + await fillPropertyInput({configurationPanel, propertyLabel: 'stringRegEx property', value: '123'}); }); await test.step('Assert validation error', async () => { @@ -41,7 +41,7 @@ test.describe('Property validation - string', () => { test('should clear regex validation error when value matches (letters only)', async () => { await test.step('Enter invalid value', async () => { - await fillPropertyInput(configurationPanel, 'stringRegEx property', '12'); + await fillPropertyInput({configurationPanel, propertyLabel: 'stringRegEx property', value: '12'}); }); await test.step('Assert validation error', async () => { @@ -53,7 +53,7 @@ test.describe('Property validation - string', () => { }); await test.step('Enter valid value', async () => { - await fillPropertyInput(configurationPanel, 'stringRegEx property', 'ab'); + await fillPropertyInput({configurationPanel, propertyLabel: 'stringRegEx property', value: 'ab'}); }); await test.step('Assert no validation error', async () => { @@ -63,7 +63,7 @@ test.describe('Property validation - string', () => { test('should not show validation error when value matches regex', async () => { await test.step('Enter valid value', async () => { - await fillPropertyInput(configurationPanel, 'stringRegEx property', 'abc'); + await fillPropertyInput({configurationPanel, propertyLabel: 'stringRegEx property', value: 'abc'}); }); await test.step('Assert no validation error', async () => { @@ -75,7 +75,7 @@ test.describe('Property validation - string', () => { test.describe('String Min Length', () => { test('should show validation error when string length is below minLength', async () => { await test.step('Enter value below minLength', async () => { - await fillPropertyInput(configurationPanel, 'stringMinLength property', 'a'); + await fillPropertyInput({configurationPanel, propertyLabel: 'stringMinLength property', value: 'a'}); }); await test.step('Assert validation error', async () => { @@ -89,7 +89,11 @@ test.describe('Property validation - string', () => { test('should not show validation error when string length meets minLength', async () => { await test.step('Enter value meeting minLength', async () => { - await fillPropertyInput(configurationPanel, 'stringMinLength property', 'abcde'); + await fillPropertyInput({ + configurationPanel, + propertyLabel: 'stringMinLength property', + value: 'abcde', + }); }); await test.step('Assert no validation error', async () => { @@ -101,7 +105,11 @@ test.describe('Property validation - string', () => { test.describe('String Max Length', () => { test('should show validation error when string length exceeds maxLength', async () => { await test.step('Enter value exceeding maxLength', async () => { - await fillPropertyInput(configurationPanel, 'stringMaxLength property', 'abcdef'); + await fillPropertyInput({ + configurationPanel, + propertyLabel: 'stringMaxLength property', + value: 'abcdef', + }); }); await test.step('Assert validation error', async () => { @@ -115,7 +123,7 @@ test.describe('Property validation - string', () => { test('should not show validation error when string length is within maxLength', async () => { await test.step('Enter value within maxLength', async () => { - await fillPropertyInput(configurationPanel, 'stringMaxLength property', 'abc'); + await fillPropertyInput({configurationPanel, propertyLabel: 'stringMaxLength property', value: 'abc'}); }); await test.step('Assert no validation error', async () => { @@ -140,7 +148,12 @@ test.describe('Property validation - numeric', () => { test.describe('Integer Max Value', () => { test('should show validation error when value exceeds maxValue', async () => { await test.step('Enter value exceeding maxValue', async () => { - await fillPropertyInput(configurationPanel, 'integerMaxValue property', '11'); + await fillPropertyInput({ + configurationPanel, + inputRole: 'spinbutton', + propertyLabel: 'integerMaxValue property', + value: '11', + }); }); await test.step('Assert validation error', async () => { @@ -154,7 +167,12 @@ test.describe('Property validation - numeric', () => { test('should not show validation error when value is within maxValue', async () => { await test.step('Enter value within maxValue', async () => { - await fillPropertyInput(configurationPanel, 'integerMaxValue property', '10'); + await fillPropertyInput({ + configurationPanel, + inputRole: 'spinbutton', + propertyLabel: 'integerMaxValue property', + value: '10', + }); }); await test.step('Assert no validation error', async () => { @@ -166,7 +184,12 @@ test.describe('Property validation - numeric', () => { test.describe('Integer Min Value', () => { test('should show validation error when value is below minValue', async () => { await test.step('Enter value below minValue', async () => { - await fillPropertyInput(configurationPanel, 'integerMinValue property', '9'); + await fillPropertyInput({ + configurationPanel, + inputRole: 'spinbutton', + propertyLabel: 'integerMinValue property', + value: '9', + }); }); await test.step('Assert validation error', async () => { @@ -180,7 +203,12 @@ test.describe('Property validation - numeric', () => { test('should not show validation error when value meets minValue', async () => { await test.step('Enter value meeting minValue', async () => { - await fillPropertyInput(configurationPanel, 'integerMinValue property', '10'); + await fillPropertyInput({ + configurationPanel, + inputRole: 'spinbutton', + propertyLabel: 'integerMinValue property', + value: '10', + }); }); await test.step('Assert no validation error', async () => { @@ -192,7 +220,7 @@ test.describe('Property validation - numeric', () => { test.describe('Number Max Value', () => { test('should show validation error when value exceeds maxValue', async () => { await test.step('Enter value exceeding maxValue', async () => { - await fillPropertyInput(configurationPanel, 'numberMaxValue property', '6'); + await fillPropertyInput({configurationPanel, propertyLabel: 'numberMaxValue property', value: '6'}); }); await test.step('Assert validation error', async () => { @@ -206,7 +234,7 @@ test.describe('Property validation - numeric', () => { test('should not show validation error when value is within maxValue', async () => { await test.step('Enter value within maxValue', async () => { - await fillPropertyInput(configurationPanel, 'numberMaxValue property', '5'); + await fillPropertyInput({configurationPanel, propertyLabel: 'numberMaxValue property', value: '5'}); }); await test.step('Assert no validation error', async () => { @@ -218,7 +246,7 @@ test.describe('Property validation - numeric', () => { test.describe('Number Min Value', () => { test('should show validation error when value is below minValue', async () => { await test.step('Enter value below minValue', async () => { - await fillPropertyInput(configurationPanel, 'numberMinValue property', '4'); + await fillPropertyInput({configurationPanel, propertyLabel: 'numberMinValue property', value: '4'}); }); await test.step('Assert validation error', async () => { @@ -232,7 +260,7 @@ test.describe('Property validation - numeric', () => { test('should not show validation error when value meets minValue', async () => { await test.step('Enter value meeting minValue', async () => { - await fillPropertyInput(configurationPanel, 'numberMinValue property', '5'); + await fillPropertyInput({configurationPanel, propertyLabel: 'numberMinValue property', value: '5'}); }); await test.step('Assert no validation error', async () => { @@ -244,7 +272,11 @@ test.describe('Property validation - numeric', () => { test.describe('Number max decimal places', () => { test('should show validation error when decimal places exceed maxNumberPrecision', async () => { await test.step('Enter value with too many decimal places', async () => { - await fillPropertyInput(configurationPanel, 'numberMaxNumPrecision property', '1.123'); + await fillPropertyInput({ + configurationPanel, + propertyLabel: 'numberMaxNumPrecision property', + value: '1.123', + }); }); await test.step('Assert validation error', async () => { @@ -258,7 +290,11 @@ test.describe('Property validation - numeric', () => { test('should not show validation error when decimal places are within maxNumberPrecision', async () => { await test.step('Enter value within max decimal places', async () => { - await fillPropertyInput(configurationPanel, 'numberMaxNumPrecision property', '1.12'); + await fillPropertyInput({ + configurationPanel, + propertyLabel: 'numberMaxNumPrecision property', + value: '1.12', + }); }); await test.step('Assert no validation error', async () => { @@ -270,7 +306,11 @@ test.describe('Property validation - numeric', () => { test.describe('Number min decimal places', () => { test('should show validation error when decimal places are below minNumberPrecision', async () => { await test.step('Enter value with too few decimal places', async () => { - await fillPropertyInput(configurationPanel, 'numberMinNumPrecision property', '1.1'); + await fillPropertyInput({ + configurationPanel, + propertyLabel: 'numberMinNumPrecision property', + value: '1.1', + }); }); await test.step('Assert validation error', async () => { @@ -284,7 +324,11 @@ test.describe('Property validation - numeric', () => { test('should not show validation error when decimal places meet minNumberPrecision', async () => { await test.step('Enter value meeting min decimal places', async () => { - await fillPropertyInput(configurationPanel, 'numberMinNumPrecision property', '1.12'); + await fillPropertyInput({ + configurationPanel, + propertyLabel: 'numberMinNumPrecision property', + value: '1.12', + }); }); await test.step('Assert no validation error', async () => { @@ -296,7 +340,11 @@ test.describe('Property validation - numeric', () => { test.describe('Number precision', () => { test('should show validation error when decimal places exceed numberPrecision', async () => { await test.step('Enter value with too many decimal places', async () => { - await fillPropertyInput(configurationPanel, 'numberPrecision property', '1.1234'); + await fillPropertyInput({ + configurationPanel, + propertyLabel: 'numberPrecision property', + value: '1.1234', + }); }); await test.step('Assert validation error', async () => { @@ -310,7 +358,11 @@ test.describe('Property validation - numeric', () => { test('should not show validation error when decimal places are within numberPrecision', async () => { await test.step('Enter value within number precision', async () => { - await fillPropertyInput(configurationPanel, 'numberPrecision property', '1.123'); + await fillPropertyInput({ + configurationPanel, + propertyLabel: 'numberPrecision property', + value: '1.123', + }); }); await test.step('Assert no validation error', async () => { diff --git a/client/test/playwright/utils/propertyValidationUtils.ts b/client/test/playwright/utils/propertyValidationUtils.ts index 8cab06dc021..7340373a1d8 100644 --- a/client/test/playwright/utils/propertyValidationUtils.ts +++ b/client/test/playwright/utils/propertyValidationUtils.ts @@ -1,15 +1,25 @@ -import {expect, type Locator, type Page} from '@playwright/test'; +import {type Locator, type Page, expect} from '@playwright/test'; import {WorkflowPage} from '../pages/workflowPage'; import {clickAndExpectToBeVisible} from './clickAndExpectToBeVisible'; -export async function fillPropertyInput( - configurationPanel: Locator, - propertyLabel: string, - value: string -): Promise { +interface FillPropertyInputProps { + configurationPanel: Locator; + inputRole?: 'textbox' | 'spinbutton'; + propertyLabel: string; + value: string; +} + +export async function fillPropertyInput({ + configurationPanel, + inputRole = 'textbox', + propertyLabel, + value, +}: FillPropertyInputProps): Promise { const property = configurationPanel.getByLabel(propertyLabel); - const input = property.getByRole('textbox'); + + const input = property.getByRole(inputRole); + await input.clear(); await input.fill(value); } @@ -20,7 +30,9 @@ export async function assertPropertyValidation( expectedError?: string ): Promise { const property = configurationPanel.getByLabel(propertyLabel); + const alert = property.locator('[role="alert"]'); + if (expectedError !== undefined) { await expect(alert).toBeVisible(); await expect(alert).toHaveText(expectedError);