diff --git a/packages/ui-tests/cypress/e2e/designer/basicNodeActions/stepDisable.cy.ts b/packages/ui-tests/cypress/e2e/designer/basicNodeActions/stepDisable.cy.ts index 63104c08c..4745de6bb 100644 --- a/packages/ui-tests/cypress/e2e/designer/basicNodeActions/stepDisable.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/basicNodeActions/stepDisable.cy.ts @@ -9,6 +9,7 @@ describe('Tests for Design page', () => { cy.selectDisableNode('setHeader'); cy.openStepConfigurationTab('setHeader'); + cy.selectFormTab('All'); cy.checkConfigCheckboxObject('disabled', true); cy.openSourceCode(); diff --git a/packages/ui-tests/cypress/e2e/designer/branchingFlows/branchingStepActions.cy.ts b/packages/ui-tests/cypress/e2e/designer/branchingFlows/branchingStepActions.cy.ts index d2dd2dc42..e22a0939c 100644 --- a/packages/ui-tests/cypress/e2e/designer/branchingFlows/branchingStepActions.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/branchingFlows/branchingStepActions.cy.ts @@ -8,6 +8,7 @@ describe('User completes normal actions on steps in a branch', () => { cy.openDesignPage(); cy.openStepConfigurationTab('marshal'); + cy.selectFormTab('All'); cy.interactWithConfigInputObject('allowJmsType'); cy.interactWithConfigInputObject('collectionType', 'collection Type'); diff --git a/packages/ui-tests/cypress/e2e/designer/rootContainerConfig/rootContainersConf.cy.ts b/packages/ui-tests/cypress/e2e/designer/rootContainerConfig/rootContainersConf.cy.ts index 8bae9dfa0..8607206d9 100644 --- a/packages/ui-tests/cypress/e2e/designer/rootContainerConfig/rootContainersConf.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/rootContainerConfig/rootContainersConf.cy.ts @@ -39,6 +39,7 @@ describe('Test for camel route root containers configuration', () => { cy.openStepConfigurationTab('camel-route'); + cy.selectFormTab('All'); cy.interactWithConfigInputObject('description', 'test.description'); cy.interactWithConfigInputObject('group', 'test.group'); cy.interactWithConfigInputObject('inputType.description', 'test.inputType.description'); @@ -91,6 +92,7 @@ describe('Test for camel route root containers configuration', () => { cy.openStepConfigurationTab('eip-action'); + cy.selectFormTab('All'); cy.interactWithConfigInputObject('name', 'test.name'); cy.interactWithConfigInputObject('title', 'test.title'); cy.interactWithConfigInputObject('description', 'test.description'); @@ -130,6 +132,7 @@ describe('Test for camel route root containers configuration', () => { cy.openStepConfigurationTab('pipe'); + cy.selectFormTab('All'); cy.get(`input[name="name"]`).clear(); cy.get(`input[name="name"]`).type('testName'); diff --git a/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/modelineConf.cy.ts b/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/modelineConf.cy.ts index 063f0ea21..e410cd7a7 100644 --- a/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/modelineConf.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/modelineConf.cy.ts @@ -9,6 +9,7 @@ describe('Tests for modeline', () => { cy.removeNodeByName('https'); cy.openStepConfigurationTab('kamelet:log-sink'); + cy.selectFormTab('All'); cy.get(`input[name="parameters.showProperties"]`).check(); cy.openSourceCode(); diff --git a/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/propertiesFilter.cy.ts b/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/propertiesFilter.cy.ts index 76152681f..80aa5dc7b 100644 --- a/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/propertiesFilter.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/propertiesFilter.cy.ts @@ -7,7 +7,7 @@ describe('Tests for side panel step filtering', () => { cy.uploadFixture('flows/camelRoute/basic.yaml'); cy.openDesignPage(); cy.openStepConfigurationTab('setHeader'); - + cy.selectFormTab('All'); // expand wrapped section cy.contains('button', 'Processor advanced properties').click(); @@ -32,6 +32,7 @@ describe('Tests for side panel step filtering', () => { cy.uploadFixture('flows/camelRoute/basic.yaml'); cy.openDesignPage(); cy.openStepConfigurationTab('setHeader'); + cy.selectFormTab('All'); // expand wrapped section cy.contains('button', 'Processor advanced properties').click(); @@ -57,6 +58,7 @@ describe('Tests for side panel step filtering', () => { cy.uploadFixture('flows/camelRoute/basic.yaml'); cy.openDesignPage(); cy.openStepConfigurationTab('log'); + cy.selectFormTab('All'); // check all fields are present cy.get(`input[name="id"]`).should('exist'); @@ -75,18 +77,19 @@ describe('Tests for side panel step filtering', () => { cy.uploadFixture('flows/camelRoute/basic.yaml'); cy.openDesignPage(); cy.openStepConfigurationTab('log'); + cy.selectFormTab('All'); cy.interactWithConfigInputObject('variableSend', 'testVariableSend'); cy.interactWithConfigInputObject('variableReceive', 'testVariableReceive'); - cy.get('button[id="User Modified"]').click(); + cy.selectFormTab('Modified'); cy.get(`input[name="variableSend"]`).should('exist'); cy.get(`input[name="variableReceive"]`).should('exist'); cy.get(`textarea[name="description"]`).should('not.exist'); cy.get(`input[name="id"]`).should('not.exist'); - cy.get('button[id="All Fields"]').click(); + cy.selectFormTab('All'); cy.get(`input[name="variableSend"]`).should('exist'); cy.get(`input[name="variableReceive"]`).should('exist'); diff --git a/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/routeBeanConf.cy.ts b/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/routeBeanConf.cy.ts index 36304f3e9..3e8d91072 100644 --- a/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/routeBeanConf.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/routeBeanConf.cy.ts @@ -13,6 +13,7 @@ describe('Test for node bean reference and configuration support', () => { cy.openDesignPage(); cy.openStepConfigurationTab('sql'); + cy.selectFormTab('All'); cy.configureNewBeanReference('parameters.dataSource'); cy.get(`input[name="name"]`).clear().type('test'); cy.get(`input[name="type"]`).clear().type('org.acme'); @@ -55,6 +56,7 @@ describe('Test for node bean reference and configuration support', () => { cy.openDesignPage(); cy.openStepConfigurationTab('sql'); + cy.selectFormTab('All'); cy.configureBeanReference('parameters.dataSource', data.dataSource); cy.openSourceCode(); @@ -68,12 +70,14 @@ describe('Test for node bean reference and configuration support', () => { cy.openDesignPage(); cy.openStepConfigurationTab('sql'); + cy.selectFormTab('All'); cy.configureBeanReference('parameters.dataSource', data.dataSource); cy.openSourceCode(); cy.checkCodeSpanLine('dataSource: "' + data.dataSource + '"'); cy.openDesignPage(); cy.openStepConfigurationTab('sql'); + cy.selectFormTab('All'); cy.deselectNodeBean('parameters.dataSource'); cy.openSourceCode(); cy.checkCodeSpanLine('dataSource: "' + data.dataSource + '"', 0); @@ -85,6 +89,7 @@ describe('Test for node bean reference and configuration support', () => { cy.uploadFixture('flows/camelRoute/sqlBeans.yaml'); cy.openDesignPage(); cy.openStepConfigurationTab('sql'); + cy.selectFormTab('All'); cy.configureBeanReference('parameters.dataSource', 'postgreSqlSource'); cy.openBeans(); @@ -93,6 +98,7 @@ describe('Test for node bean reference and configuration support', () => { cy.get('[data-testid="metadata-row-1"]').should('not.exist'); cy.openDesignPage(); cy.openStepConfigurationTab('sql'); + cy.selectFormTab('All'); cy.get(`div[data-fieldname="parameters.dataSource"] input[value="#postgreSqlSource"]`).should('not.exist'); }); }); diff --git a/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/stepConfiguration.cy.ts b/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/stepConfiguration.cy.ts index 9fa7799b8..24feb6cbb 100644 --- a/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/stepConfiguration.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/stepConfiguration.cy.ts @@ -8,6 +8,7 @@ describe('Tests for Design page', () => { cy.openDesignPage(); // Configure timer - source step cy.openStepConfigurationTab('timer-source'); + cy.selectFormTab('All'); cy.interactWithConfigInputObject('period', '3000'); cy.interactWithConfigInputObject('message', 'test message'); cy.get(`input[name="message"]`).clear(); @@ -15,6 +16,7 @@ describe('Tests for Design page', () => { // Configure kafka-sink step cy.openStepConfigurationTab('kafka-sink'); + cy.selectFormTab('All'); cy.interactWithConfigInputObject('topic', 'topicname'); cy.interactWithConfigInputObject('bootstrapServers', 'bootstrap'); cy.interactWithConfigInputObject('securityProtocol', 'security'); diff --git a/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/uriRouteConfig.cy.ts b/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/uriRouteConfig.cy.ts index 27d6c55ae..970d81d3e 100644 --- a/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/uriRouteConfig.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/sidepanelConfig/uriRouteConfig.cy.ts @@ -17,6 +17,7 @@ describe('Test URI node config', () => { cy.checkNodeExist('file', 1); cy.openStepConfigurationTab('timer'); + cy.selectFormTab('All'); cy.checkConfigInputObject('parameters.period', '1000'); cy.checkConfigInputObject('parameters.delay', '2000'); cy.checkConfigInputObject('parameters.repeatCount', '10'); @@ -49,6 +50,7 @@ describe('Test URI node config', () => { // CHECK the aws2-s3 properties cy.openStepConfigurationTab('aws2-s3'); + cy.selectFormTab('All'); cy.checkConfigCheckboxObject('parameters.autoCreateBucket', true); cy.checkConfigInputObject('parameters.bucketNameOrArn', 'testBucket'); }); @@ -69,6 +71,7 @@ describe('Test URI node config', () => { // CHECK the aws2-s3 properties cy.openStepConfigurationTab('aws2-s3'); + cy.selectFormTab('All'); cy.checkConfigCheckboxObject('parameters.autoCreateBucket', true); cy.checkConfigInputObject('parameters.bucketNameOrArn', 'testBucket'); }); diff --git a/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/errorHandler.cy.ts b/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/errorHandler.cy.ts index 6bb8a943c..4e4645bf8 100644 --- a/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/errorHandler.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/errorHandler.cy.ts @@ -10,6 +10,7 @@ describe('Test for errorHandler configuration container', () => { .find('.pf-topology__node__label') .find('.pf-topology__node__label__background') .click(); + cy.selectFormTab('All'); cy.get('#-oneof-toggle').click(); cy.get('ul.pf-v5-c-menu__list > li:first') @@ -33,6 +34,7 @@ describe('Test for errorHandler configuration container', () => { .find('.pf-topology__node__label') .find('.pf-topology__node__label__background') .click(); + cy.selectFormTab('All'); cy.get('#-oneof-toggle').click(); cy.get('[data-testid="-oneof-select-dropdownlist-Default Error Handler"]').click(); diff --git a/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/interceptSendToEndpoint.cy.ts b/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/interceptSendToEndpoint.cy.ts index 76a2ce6c5..d379b3a06 100644 --- a/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/interceptSendToEndpoint.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/interceptSendToEndpoint.cy.ts @@ -10,6 +10,7 @@ describe('Test for interceptSendToEndpoint configuration container', () => { .find('.pf-topology__node__label') .find('.pf-topology__node__label__background') .click(); + cy.selectFormTab('All'); cy.interactWithConfigInputObject('description', 'testDescription'); cy.interactWithConfigInputObject('skipSendToOriginalEndpoint', 'testSkipSendToOriginalEndpoint'); diff --git a/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/onCompletion.cy.ts b/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/onCompletion.cy.ts index 82f294de8..a21123d24 100644 --- a/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/onCompletion.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/onCompletion.cy.ts @@ -10,6 +10,7 @@ describe('Test for onCompletion configuration container', () => { .find('.pf-topology__node__label') .find('.pf-topology__node__label__background') .click(); + cy.selectFormTab('All'); cy.interactWithConfigInputObject('description', 'testDescription'); cy.interactWithConfigInputObject('onCompleteOnly'); diff --git a/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/onException.cy.ts b/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/onException.cy.ts index cb544bf2c..38eee19c7 100644 --- a/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/onException.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/onException.cy.ts @@ -33,6 +33,7 @@ describe('Test for root on exception container', () => { .find('.pf-topology__node__label') .find('.pf-topology__node__label__background') .click(); + cy.selectFormTab('All'); cy.selectInTypeaheadField('redeliveryPolicy.retriesExhaustedLogLevel', 'INFO'); cy.selectInTypeaheadField('redeliveryPolicy.retryAttemptedLogLevel', 'INFO'); diff --git a/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/restConfiguration.cy.ts b/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/restConfiguration.cy.ts index 44a0ae50b..68082cd95 100644 --- a/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/restConfiguration.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/restConfiguration.cy.ts @@ -10,6 +10,7 @@ describe('Test for root on rest configuration container', () => { .find('.pf-topology__node__label') .find('.pf-topology__node__label__background') .click(); + cy.selectFormTab('All'); cy.selectInTypeaheadField('component', 'coap'); cy.selectInTypeaheadField('apiComponent', 'openapi'); diff --git a/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/routeConfiguration.cy.ts b/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/routeConfiguration.cy.ts index 6c8f9776c..0da2e913f 100644 --- a/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/routeConfiguration.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/specialCamelRoutes/routeConfiguration.cy.ts @@ -10,6 +10,7 @@ describe('Test for root route configuration container', () => { .find('.pf-topology__node__label') .find('.pf-topology__node__label__background') .click(); + cy.selectFormTab('All'); cy.interactWithConfigInputObject('description', 'testDescription'); cy.interactWithConfigInputObject('errorHandler.id', 'testErrorHandlerId'); diff --git a/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/dataFormatStepConfig.cy.ts b/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/dataFormatStepConfig.cy.ts index f20d00154..eb44c8bc7 100644 --- a/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/dataFormatStepConfig.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/dataFormatStepConfig.cy.ts @@ -9,6 +9,7 @@ describe('Tests for sidebar dataformat configuration', () => { // Configure marshal dataformat cy.openStepConfigurationTab('marshal'); + cy.selectFormTab('All'); cy.selectDataformat('Base64'); cy.interactWithDataformatInputObject('lineLength', '128'); cy.interactWithDataformatInputObject('id', 'simpleDataformatId'); @@ -29,6 +30,7 @@ describe('Tests for sidebar dataformat configuration', () => { // Configure marshal dataformat cy.openStepConfigurationTab('marshal'); + cy.selectFormTab('All'); cy.selectDataformat('Avro'); cy.configureDropdownValue('library', 'avroJackson'); cy.interactWithDataformatInputObject('unmarshalType', 'com.fasterxml.jackson.databind.JsonNode'); diff --git a/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/expressionStepConfig.cy.ts b/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/expressionStepConfig.cy.ts index c08c7f163..596dc95db 100644 --- a/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/expressionStepConfig.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/expressionStepConfig.cy.ts @@ -8,6 +8,7 @@ describe('Tests for sidebar expression configuration', () => { cy.openDesignPage(); // Configure setHeader expression cy.openStepConfigurationTab('setHeader'); + cy.selectFormTab('All'); cy.selectExpression('Simple'); cy.interactWithExpressionInputObject('expression', `{{}{{}header.baz}}`); cy.interactWithExpressionInputObject('id', 'simpleExpressionId'); @@ -25,6 +26,7 @@ describe('Tests for sidebar expression configuration', () => { cy.openDesignPage(); cy.openStepConfigurationTab('setHeader'); + cy.selectFormTab('All'); cy.selectExpression('JQ'); cy.interactWithConfigInputObject('expression', '.id'); cy.addExpressionResultType('java.lang.String'); @@ -36,6 +38,7 @@ describe('Tests for sidebar expression configuration', () => { cy.checkNodeExist('setHeader', 2); cy.openStepConfigurationTab('setHeader', 1); + cy.selectFormTab('All'); cy.selectExpression('JQ'); cy.interactWithConfigInputObject('expression', '.name'); cy.addExpressionResultType('java.lang.String'); @@ -66,6 +69,7 @@ describe('Tests for sidebar expression configuration', () => { cy.openDesignPage(); // Configure setHeader expression cy.openStepConfigurationTab('setHeader'); + cy.selectFormTab('All'); cy.selectExpression('Simple'); cy.interactWithExpressionInputObject('expression', `{{}{{}header.baz}}`); cy.get('textarea[name="expression"]').should('have.value', '{{header.baz}}'); @@ -83,6 +87,7 @@ describe('Tests for sidebar expression configuration', () => { cy.openDesignPage(); // Configure setBody expression cy.openStepConfigurationTab('setBody'); + cy.selectFormTab('All'); cy.selectExpression('Simple'); cy.interactWithExpressionInputObject('expression', `{{}{{}body.baz}}`); cy.interactWithExpressionInputObject('id', 'simpleExpressionId'); diff --git a/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/loadBalancerConfig.cy.ts b/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/loadBalancerConfig.cy.ts index 8bc66de2a..e9f680125 100644 --- a/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/loadBalancerConfig.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/loadBalancerConfig.cy.ts @@ -10,6 +10,7 @@ describe('Tests for sidebar loadBalancer step configuration', () => { cy.selectAppendNode('marshal'); cy.chooseFromCatalog('processor', 'loadBalance'); cy.openStepConfigurationTab('loadBalance'); + cy.selectFormTab('All'); cy.get('[data-testid="loadbalancer-config-card"] button').click(); cy.get('[data-testid="loadbalancer-dropdownitem-roundRobinLoadBalancer"] button').click(); diff --git a/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/setHeadersStepConfig.cy.ts b/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/setHeadersStepConfig.cy.ts index 159e5e6a7..da6f3a6ed 100644 --- a/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/setHeadersStepConfig.cy.ts +++ b/packages/ui-tests/cypress/e2e/designer/specialStepConfiguration/setHeadersStepConfig.cy.ts @@ -10,6 +10,7 @@ describe('Tests for sidebar setHeaders step configuration', () => { cy.selectAppendNode('marshal'); cy.chooseFromCatalog('processor', 'setHeaders'); cy.openStepConfigurationTab('setHeaders'); + cy.selectFormTab('All'); cy.get('[data-testid="list-add-field"]').click(); diff --git a/packages/ui-tests/cypress/support/cypress.d.ts b/packages/ui-tests/cypress/support/cypress.d.ts index 9a9727741..af3366e22 100644 --- a/packages/ui-tests/cypress/support/cypress.d.ts +++ b/packages/ui-tests/cypress/support/cypress.d.ts @@ -81,6 +81,7 @@ declare global { addProperty(propertyName: string): Chainable>; addSingleKVProperty(propertyName: string, key: string, value: string): Chainable>; filterFields(filter: string): Chainable>; + selectFormTab(tab: string): Chainable>; // metadata expandWrappedSection(sectionName: string): Chainable>; closeWrappedSection(sectionName: string): Chainable>; diff --git a/packages/ui-tests/cypress/support/next-commands/nodeConfiguration.ts b/packages/ui-tests/cypress/support/next-commands/nodeConfiguration.ts index 9068f64e8..10387e5e6 100644 --- a/packages/ui-tests/cypress/support/next-commands/nodeConfiguration.ts +++ b/packages/ui-tests/cypress/support/next-commands/nodeConfiguration.ts @@ -139,3 +139,9 @@ Cypress.Commands.add('filterFields', (filter: string) => { cy.get('input.pf-v5-c-text-input-group__text-input').type(filter); }); }); + +Cypress.Commands.add('selectFormTab', (value: string) => { + cy.get('div.form-tabs').within(() => { + cy.get(`[id$="${value}"]`).click(); + }); +}); diff --git a/packages/ui/src/components/Form/CustomAutoFields.tsx b/packages/ui/src/components/Form/CustomAutoFields.tsx index 3e7e9b07a..22d124e47 100644 --- a/packages/ui/src/components/Form/CustomAutoFields.tsx +++ b/packages/ui/src/components/Form/CustomAutoFields.tsx @@ -5,9 +5,11 @@ import { KaotoSchemaDefinition } from '../../models'; import { Card, CardBody } from '@patternfly/react-core'; import { getFieldGroups } from '../../utils'; import { CatalogKind } from '../../models'; -import { FilteredFieldContext } from '../../providers'; +import { CanvasFormTabsContext, FilteredFieldContext } from '../../providers'; import './CustomAutoFields.scss'; import { CustomExpandableSection } from './customField/CustomExpandableSection'; +import { NoFieldFound } from './NoFieldFound'; +import { FormTabsModes } from '../Visualization/Canvas'; export type AutoFieldsProps = { autoField?: ComponentType<{ name: string }>; @@ -26,6 +28,7 @@ export function CustomAutoFields({ const { schema } = useForm(); const rootField = schema.getField(''); const { filteredFieldText, isGroupExpanded } = useContext(FilteredFieldContext); + const { selectedTab } = useContext(CanvasFormTabsContext); /** Special handling for oneOf schemas */ if (Array.isArray((rootField as KaotoSchemaDefinition['schema']).oneOf)) { @@ -42,6 +45,17 @@ export function CustomAutoFields({ }, {}); const propertiesArray = getFieldGroups(actualFieldsSchema); + if ( + selectedTab !== FormTabsModes.ALL_FIELDS && + propertiesArray.common.length === 0 && + Object.keys(propertiesArray.groups).length === 0 + ) { + const comment = (rootField as KaotoSchemaDefinition['schema'])['$comment'] ?? ''; + if (!comment.includes('expression') && !comment.includes('dataformat') && !comment.includes('loadbalance')) { + return ; + } + } + return createElement( element, props, diff --git a/packages/ui/src/components/Form/NoFieldFound.tsx b/packages/ui/src/components/Form/NoFieldFound.tsx new file mode 100644 index 000000000..af41b1716 --- /dev/null +++ b/packages/ui/src/components/Form/NoFieldFound.tsx @@ -0,0 +1,21 @@ +import { Alert, Card, CardBody, Button } from '@patternfly/react-core'; +import { FunctionComponent, useContext } from 'react'; +import { CanvasFormTabsContext } from '../../providers'; +import { FormTabsModes } from '../Visualization/Canvas'; + +export const NoFieldFound: FunctionComponent = () => { + const { selectedTab, onTabChange } = useContext(CanvasFormTabsContext); + return ( + + + + No field found matching this criteria. Please switch to the{' '} + {' '} + tab. + + + + ); +}; diff --git a/packages/ui/src/components/Form/dataFormat/DataFormatEditor.test.tsx b/packages/ui/src/components/Form/dataFormat/DataFormatEditor.test.tsx index 36857a1eb..69cf48418 100644 --- a/packages/ui/src/components/Form/dataFormat/DataFormatEditor.test.tsx +++ b/packages/ui/src/components/Form/dataFormat/DataFormatEditor.test.tsx @@ -94,6 +94,51 @@ describe('DataFormatEditor', () => { expect(inputIdModifiedTabElement).toHaveLength(1); }); + it('should render with only the Required fields', () => { + const visualComponentSchema: VisualComponentSchema = { + title: 'My Node', + schema: { + type: 'object', + properties: { + name: { + type: 'string', + }, + }, + } as unknown as KaotoSchemaDefinition['schema'], + definition: { + name: 'my node', + beanio: {}, + }, + }; + + mockNode = { + id: '1', + type: 'node', + data: { + vizNode: { + getComponentSchema: () => visualComponentSchema, + updateModel: (_value: unknown) => {}, + } as IVisualizationNode, + }, + }; + render(); + const buttons = screen.queryAllByRole('button', { name: 'Typeahead menu toggle' }); + expect(buttons).toHaveLength(1); + + const inputElement = screen.getAllByRole('combobox')[0]; + expect(inputElement).toHaveValue('BeanIO'); + + const inputMappingElement = screen + .queryAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('label') === 'Mapping'); + expect(inputMappingElement).toHaveLength(1); + + const inputStreamNameElement = screen + .queryAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('label') === 'Stream Name'); + expect(inputStreamNameElement).toHaveLength(1); + }); + it('should render', async () => { render(); const buttons = screen.getAllByRole('button', { name: 'Typeahead menu toggle' }); diff --git a/packages/ui/src/components/Form/dataFormat/DataFormatEditor.tsx b/packages/ui/src/components/Form/dataFormat/DataFormatEditor.tsx index b6840bd61..87e1e56eb 100644 --- a/packages/ui/src/components/Form/dataFormat/DataFormatEditor.tsx +++ b/packages/ui/src/components/Form/dataFormat/DataFormatEditor.tsx @@ -12,7 +12,12 @@ import { CanvasNode } from '../../Visualization/Canvas/canvas.models'; import './DataFormatEditor.scss'; import { DataFormatService } from './dataformat.service'; import { TypeaheadEditor } from '../customField/TypeaheadEditor'; -import { getSerializedModel, getUserUpdatedPropertiesSchema, isDefined } from '../../../utils'; +import { + getRequiredPropertiesSchema, + getSerializedModel, + getUserUpdatedPropertiesSchema, + isDefined, +} from '../../../utils'; import { FormTabsModes } from '../../Visualization/Canvas/canvasformtabs.modes'; interface DataFormatEditorProps { @@ -61,11 +66,16 @@ export const DataFormatEditor: FunctionComponent = (props }, [dataFormat]); const processedSchema = useMemo(() => { - if (props.formMode === FormTabsModes.ALL_FIELDS) return dataFormatSchema; - return { - ...dataFormatSchema, - properties: getUserUpdatedPropertiesSchema(dataFormatSchema?.properties ?? {}, dataFormatModel ?? {}), - }; + if (props.formMode === FormTabsModes.REQUIRED_FIELDS) { + return getRequiredPropertiesSchema(dataFormatSchema ?? {}); + } else if (props.formMode === FormTabsModes.ALL_FIELDS) { + return dataFormatSchema; + } else if (props.formMode === FormTabsModes.USER_MODIFIED) { + return { + ...dataFormatSchema, + properties: getUserUpdatedPropertiesSchema(dataFormatSchema?.properties ?? {}, dataFormatModel ?? {}), + }; + } }, [props.formMode, dataFormat]); const handleOnChange = useCallback( @@ -89,7 +99,7 @@ export const DataFormatEditor: FunctionComponent = (props ); const showEditor = useMemo(() => { - if (props.formMode === FormTabsModes.ALL_FIELDS) return true; + if (props.formMode === FormTabsModes.ALL_FIELDS || props.formMode === FormTabsModes.REQUIRED_FIELDS) return true; return props.formMode === FormTabsModes.USER_MODIFIED && isDefined(selectedDataFormatOption); }, [props.formMode]); diff --git a/packages/ui/src/components/Form/loadBalancer/LoadBalancerEditor.test.tsx b/packages/ui/src/components/Form/loadBalancer/LoadBalancerEditor.test.tsx index 23e15523f..5a8805106 100644 --- a/packages/ui/src/components/Form/loadBalancer/LoadBalancerEditor.test.tsx +++ b/packages/ui/src/components/Form/loadBalancer/LoadBalancerEditor.test.tsx @@ -95,6 +95,46 @@ describe('LoadBalancerEditor', () => { expect(inputIdModifiedTabElement).toHaveLength(1); }); + it('should render with only the Required fields', () => { + const visualComponentSchema: VisualComponentSchema = { + title: 'My Node', + schema: { + type: 'object', + properties: { + name: { + type: 'string', + }, + }, + } as unknown as KaotoSchemaDefinition['schema'], + definition: { + name: 'my node', + weightedLoadBalancer: {}, + }, + }; + + mockNode = { + id: '1', + type: 'node', + data: { + vizNode: { + getComponentSchema: () => visualComponentSchema, + updateModel: (_value: unknown) => {}, + } as IVisualizationNode, + }, + }; + render(); + const buttons = screen.queryAllByRole('button', { name: 'Typeahead menu toggle' }); + expect(buttons).toHaveLength(1); + + const inputElement = screen.getAllByRole('combobox')[0]; + expect(inputElement).toHaveValue('Weighted Load Balancer'); + + const inputDistributionRatioElement = screen + .queryAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('label') === 'Distribution Ratio'); + expect(inputDistributionRatioElement).toHaveLength(1); + }); + it('should render', async () => { render(); const buttons = screen.getAllByRole('button', { name: 'Typeahead menu toggle' }); diff --git a/packages/ui/src/components/Form/loadBalancer/LoadBalancerEditor.tsx b/packages/ui/src/components/Form/loadBalancer/LoadBalancerEditor.tsx index 5d37322e6..0b2c11dd6 100644 --- a/packages/ui/src/components/Form/loadBalancer/LoadBalancerEditor.tsx +++ b/packages/ui/src/components/Form/loadBalancer/LoadBalancerEditor.tsx @@ -12,7 +12,12 @@ import { CanvasNode } from '../../Visualization/Canvas/canvas.models'; import { LoadBalancerService } from './loadbalancer.service'; import './LoadBalancerEditor.scss'; import { TypeaheadEditor } from '../customField/TypeaheadEditor'; -import { getSerializedModel, getUserUpdatedPropertiesSchema, isDefined } from '../../../utils'; +import { + getRequiredPropertiesSchema, + getSerializedModel, + getUserUpdatedPropertiesSchema, + isDefined, +} from '../../../utils'; import { FormTabsModes } from '../../Visualization/Canvas/canvasformtabs.modes'; interface LoadBalancerEditorProps { @@ -62,11 +67,16 @@ export const LoadBalancerEditor: FunctionComponent = (p }, [loadBalancer]); const processedSchema = useMemo(() => { - if (props.formMode === FormTabsModes.ALL_FIELDS) return loadBalancerSchema; - return { - ...loadBalancerSchema, - properties: getUserUpdatedPropertiesSchema(loadBalancerSchema?.properties ?? {}, loadBalancerModel ?? {}), - }; + if (props.formMode === FormTabsModes.REQUIRED_FIELDS) { + return getRequiredPropertiesSchema(loadBalancerSchema ?? {}); + } else if (props.formMode === FormTabsModes.ALL_FIELDS) { + return loadBalancerSchema; + } else if (props.formMode === FormTabsModes.USER_MODIFIED) { + return { + ...loadBalancerSchema, + properties: getUserUpdatedPropertiesSchema(loadBalancerSchema?.properties ?? {}, loadBalancerModel ?? {}), + }; + } }, [props.formMode, loadBalancer]); const handleOnChange = useCallback( @@ -90,7 +100,7 @@ export const LoadBalancerEditor: FunctionComponent = (p ); const showEditor = useMemo(() => { - if (props.formMode === FormTabsModes.ALL_FIELDS) return true; + if (props.formMode === FormTabsModes.ALL_FIELDS || props.formMode === FormTabsModes.REQUIRED_FIELDS) return true; return props.formMode === FormTabsModes.USER_MODIFIED && isDefined(selectedLoadBalancerOption); }, [props.formMode]); diff --git a/packages/ui/src/components/Form/stepExpression/StepExpressionEditor.test.tsx b/packages/ui/src/components/Form/stepExpression/StepExpressionEditor.test.tsx index 20dfe0735..62cb85a40 100644 --- a/packages/ui/src/components/Form/stepExpression/StepExpressionEditor.test.tsx +++ b/packages/ui/src/components/Form/stepExpression/StepExpressionEditor.test.tsx @@ -45,13 +45,57 @@ describe('StepExpressionEditor', () => { }; }); - it('should not render', () => { + it('should not render under Modified tab', () => { render(); - const launcherButton = screen.queryAllByRole('button', { name: 'Configure Expression' }); - expect(launcherButton).toHaveLength(0); + const dropdown = screen.queryAllByRole('button', { name: 'Typeahead menu toggle' }); + expect(dropdown).toHaveLength(0); }); - it('should render', async () => { + it('should render with only the user updated fields', () => { + const visualComponentSchema: VisualComponentSchema = { + title: 'My Node', + schema: { + type: 'object', + properties: { + name: { + type: 'string', + }, + }, + } as unknown as KaotoSchemaDefinition['schema'], + definition: { + name: 'my node', + expression: { + jsonpath: { + id: 'test', + }, + }, + }, + }; + + mockNode = { + id: '1', + type: 'node', + data: { + vizNode: { + getComponentSchema: () => visualComponentSchema, + updateModel: (_value: unknown) => {}, + } as IVisualizationNode, + }, + }; + render(); + const buttons = screen.queryAllByRole('button', { name: 'Typeahead menu toggle' }); + expect(buttons).toHaveLength(1); + + const inputElement = screen.getAllByRole('combobox')[0]; + expect(inputElement).toHaveValue('JSONPath'); + + const inputIdModifiedTabElement = screen + .queryAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('label') === 'Id'); + expect(inputIdModifiedTabElement).toHaveLength(1); + }); + + it('should render under all Tab', async () => { render(); const dropdown = screen .getAllByTestId('typeahead-select-input') @@ -64,4 +108,12 @@ describe('StepExpressionEditor', () => { const form = screen.getByTestId('metadata-editor-form-expression'); expect(form.innerHTML).toContain('Suppress Exceptions'); }); + + it('should render under Required Tab', async () => { + render(); + const dropdown = screen + .getAllByTestId('typeahead-select-input') + .filter((input) => input.innerHTML.includes(SchemaService.DROPDOWN_PLACEHOLDER)); + expect(dropdown).toHaveLength(1); + }); }); diff --git a/packages/ui/src/components/Form/stepExpression/StepExpressionEditor.tsx b/packages/ui/src/components/Form/stepExpression/StepExpressionEditor.tsx index 8a28abef7..4bb5ae5f4 100644 --- a/packages/ui/src/components/Form/stepExpression/StepExpressionEditor.tsx +++ b/packages/ui/src/components/Form/stepExpression/StepExpressionEditor.tsx @@ -8,11 +8,16 @@ import { } from '@patternfly/react-core'; import { FunctionComponent, useCallback, useContext, useMemo, useState } from 'react'; import { EntitiesContext } from '../../../providers'; -import { getSerializedModel, getUserUpdatedPropertiesSchema, isDefined } from '../../../utils'; +import { + getRequiredPropertiesSchema, + getSerializedModel, + getUserUpdatedPropertiesSchema, + isDefined, +} from '../../../utils'; import { CanvasNode } from '../../Visualization/Canvas/canvas.models'; import { TypeaheadEditor } from '../customField/TypeaheadEditor'; import { ExpressionService } from '../expression/expression.service'; -import { FormTabsModes } from '../../Visualization/Canvas'; +import { FormTabsModes } from '../../Visualization/Canvas/canvasformtabs.modes'; interface StepExpressionEditorProps { selectedNode: CanvasNode; @@ -65,11 +70,16 @@ export const StepExpressionEditor: FunctionComponent }, [language]); const processedSchema = useMemo(() => { - if (props.formMode === FormTabsModes.ALL_FIELDS) return languageSchema; - return { - ...languageSchema, - properties: getUserUpdatedPropertiesSchema(languageSchema?.properties ?? {}, languageModel ?? {}), - }; + if (props.formMode === FormTabsModes.REQUIRED_FIELDS) { + return getRequiredPropertiesSchema(languageSchema ?? {}); + } else if (props.formMode === FormTabsModes.ALL_FIELDS) { + return languageSchema; + } else if (props.formMode === FormTabsModes.USER_MODIFIED) { + return { + ...languageSchema, + properties: getUserUpdatedPropertiesSchema(languageSchema?.properties ?? {}, languageModel ?? {}), + }; + } }, [props.formMode, language]); const handleOnChange = useCallback( @@ -94,7 +104,7 @@ export const StepExpressionEditor: FunctionComponent ); const showEditor = useMemo(() => { - if (props.formMode === FormTabsModes.ALL_FIELDS) return true; + if (props.formMode === FormTabsModes.ALL_FIELDS || props.formMode === FormTabsModes.REQUIRED_FIELDS) return true; return props.formMode === FormTabsModes.USER_MODIFIED && isDefined(selectedLanguageOption); }, [props.formMode]); diff --git a/packages/ui/src/components/Visualization/Canvas/CanvasForm.scss b/packages/ui/src/components/Visualization/Canvas/CanvasForm.scss index 338858a46..5c3ec9ca1 100644 --- a/packages/ui/src/components/Visualization/Canvas/CanvasForm.scss +++ b/packages/ui/src/components/Visualization/Canvas/CanvasForm.scss @@ -5,7 +5,19 @@ overflow: auto; } - .filter-fields { - margin-bottom: 15px; + .form-tabs { + display: flex; + + /* stylelint-disable-next-line selector-class-pattern */ + .pf-v5-c-toggle-group__item { + display: flex; + width: 20%; + } + + button { + flex: 1; + justify-content: center; + font-weight: bold; + } } } diff --git a/packages/ui/src/components/Visualization/Canvas/CanvasForm.test.tsx b/packages/ui/src/components/Visualization/Canvas/CanvasForm.test.tsx index 9c7bf7d28..8bb85bd44 100644 --- a/packages/ui/src/components/Visualization/Canvas/CanvasForm.test.tsx +++ b/packages/ui/src/components/Visualization/Canvas/CanvasForm.test.tsx @@ -1,5 +1,5 @@ import catalogLibrary from '@kaoto/camel-catalog/index.json'; -import { CatalogLibrary } from '@kaoto/camel-catalog/types'; +import { CatalogLibrary, RouteDefinition } from '@kaoto/camel-catalog/types'; import { act, fireEvent, render, screen } from '@testing-library/react'; import { CamelCatalogService, @@ -13,13 +13,19 @@ import { } from '../../../models'; import { IVisualizationNode, VisualComponentSchema } from '../../../models/visualization/base-visual-entity'; import { VisualFlowsApi } from '../../../models/visualization/flows/support/flows-visibility'; -import { VisibleFlowsContext, VisibleFlowsProvider } from '../../../providers'; +import { + VisibleFlowsContext, + VisibleFlowsProvider, + CanvasFormTabsContext, + CanvasFormTabsProvider, +} from '../../../providers'; import { EntitiesContext, EntitiesProvider } from '../../../providers/entities.provider'; import { camelRouteJson, kameletJson } from '../../../stubs'; import { getFirstCatalogMap } from '../../../stubs/test-load-catalog'; import { CanvasForm } from './CanvasForm'; import { CanvasNode } from './canvas.models'; import { CanvasService } from './canvas.service'; +import { FormTabsModes } from './canvasformtabs.modes'; describe('CanvasForm', () => { let camelRouteVisualEntity: CamelRouteVisualEntity; @@ -159,7 +165,14 @@ describe('CanvasForm', () => { render( - + + + , ); @@ -187,7 +200,14 @@ describe('CanvasForm', () => { render( - + + + , ); @@ -216,7 +236,14 @@ describe('CanvasForm', () => { render( - + + + , ); @@ -265,4 +292,494 @@ describe('CanvasForm', () => { expect(kameletVisualEntity.id).toEqual(newName); expect(dispatchSpy).toHaveBeenCalledWith({ type: 'renameFlow', flowId, newName }); }); + + describe('should show the User-updated field under the modified tab', () => { + beforeEach(() => { + camelRouteVisualEntity = new CamelRouteVisualEntity(camelRouteJson); + const { nodes } = CanvasService.getFlowDiagram(camelRouteVisualEntity.toVizNode()); + selectedNode = nodes[0]; // timer + }); + + it('normal text field', async () => { + render( + + + + + + + , + ); + + const allTab = screen.getByRole('button', { name: 'All' }); + const modifiedTab = screen.getByRole('button', { name: 'Modified' }); + + expect(allTab).toBeInTheDocument(); + expect(modifiedTab).toBeInTheDocument(); + + act(() => { + fireEvent.click(modifiedTab); + }); + + const inputVariableReceiveModifiedTabElement = screen + .queryAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('label') === 'Variable Receive'); + expect(inputVariableReceiveModifiedTabElement).toHaveLength(0); + + act(() => { + fireEvent.click(allTab); + }); + + await act(async () => { + const inputVariableReceiveAllTabElement = screen + .getAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('label') === 'Variable Receive'); + fireEvent.change(inputVariableReceiveAllTabElement[0], { target: { value: 'test' } }); + fireEvent.blur(inputVariableReceiveAllTabElement[0]); + }); + + act(() => { + fireEvent.click(modifiedTab); + }); + + const inputVariableReceiveModifiedTabElementNew = screen + .getAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('label') === 'Variable Receive'); + expect(inputVariableReceiveModifiedTabElementNew).toHaveLength(1); + }); + + it('expression field', async () => { + const camelRoute = { + from: { + uri: 'timer:tutorial', + steps: [ + { + setHeader: { + name: 'foo', + }, + }, + ], + }, + } as RouteDefinition; + const entity = new CamelRouteVisualEntity(camelRoute); + const rootNode: IVisualizationNode = entity.toVizNode(); + const setHeaderNode = rootNode.getChildren()![1]; + const selectedNode = { + id: '1', + type: 'node', + data: { + vizNode: setHeaderNode, + }, + }; + + render( + + + + + + + , + ); + + const allTab = screen.getByRole('button', { name: 'All' }); + const modifiedTab = screen.getByRole('button', { name: 'Modified' }); + + act(() => { + fireEvent.click(modifiedTab); + }); + + expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeNull(); + + act(() => { + fireEvent.click(allTab); + }); + + const ExpressionAllTabButton = screen.getAllByRole('button', { name: 'Typeahead menu toggle' }); + + act(() => { + fireEvent.click(ExpressionAllTabButton[0]); + }); + + const simple = screen.getByTestId('expression-dropdownitem-simple'); + act(() => { + fireEvent.click(simple.getElementsByTagName('button')[0]); + }); + + act(() => { + fireEvent.click(modifiedTab); + }); + + expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeInTheDocument(); + + const inputExpressionModifiedTabElement = screen + .queryAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('name') === 'expression'); + expect(inputExpressionModifiedTabElement).toHaveLength(0); + + act(() => { + fireEvent.click(allTab); + }); + + await act(async () => { + const expressionInput = screen + .getAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('name') === 'expression'); + fireEvent.input(expressionInput[0], { target: { value: '${header.foo}' } }); + fireEvent.blur(expressionInput[0]); + }); + + act(() => { + fireEvent.click(modifiedTab); + }); + + expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeInTheDocument(); + + const inputExpressionModifiedTabElementNew = screen + .getAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('name') === 'expression'); + expect(inputExpressionModifiedTabElementNew).toHaveLength(1); + }); + + it('dataformat field', async () => { + const camelRoute = { + from: { + uri: 'timer:tutorial', + steps: [ + { + marshal: { + id: 'ms', + }, + }, + ], + }, + } as RouteDefinition; + const entity = new CamelRouteVisualEntity(camelRoute); + const rootNode: IVisualizationNode = entity.toVizNode(); + const marshalNode = rootNode.getChildren()![1]; + const selectedNode = { + id: '1', + type: 'node', + data: { + vizNode: marshalNode, + }, + }; + + render( + + + + + + + , + ); + + const allTab = screen.getByRole('button', { name: 'All' }); + const modifiedTab = screen.getByRole('button', { name: 'Modified' }); + act(() => { + fireEvent.click(modifiedTab); + }); + + expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeNull(); + + act(() => { + fireEvent.click(allTab); + }); + + const dataformatAllTabButton = screen.getAllByRole('button', { name: 'Typeahead menu toggle' }); + await act(async () => { + fireEvent.click(dataformatAllTabButton[0]); + }); + const asn1 = screen.getByTestId('dataformat-dropdownitem-asn1'); + await act(async () => { + fireEvent.click(asn1.getElementsByTagName('button')[0]); + }); + + act(() => { + fireEvent.click(modifiedTab); + }); + + expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeInTheDocument(); + + const inputUnmarshalTypeModifiedTabElement = screen + .queryAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('label') === 'Unmarshal Type'); + expect(inputUnmarshalTypeModifiedTabElement).toHaveLength(0); + + act(() => { + fireEvent.click(allTab); + }); + + await act(async () => { + const inputUnmarshalTypeDefaultTabElement = screen + .getAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('label') === 'Unmarshal Type'); + fireEvent.change(inputUnmarshalTypeDefaultTabElement[0], { target: { value: 'test' } }); + fireEvent.blur(inputUnmarshalTypeDefaultTabElement[0]); + }); + + act(() => { + fireEvent.click(modifiedTab); + }); + + expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeInTheDocument(); + + const inputUnmarshalTypeModifiedTabElementNew = screen + .getAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('label') === 'Unmarshal Type'); + expect(inputUnmarshalTypeModifiedTabElementNew).toHaveLength(1); + }); + + it('loadbalancer field', async () => { + const camelRoute = { + from: { + uri: 'timer:tutorial', + steps: [ + { + loadBalance: { + id: 'lb', + }, + }, + ], + }, + } as RouteDefinition; + const entity = new CamelRouteVisualEntity(camelRoute); + const rootNode: IVisualizationNode = entity.toVizNode(); + const loadBalanceNode = rootNode.getChildren()![1]; + const selectedNode = { + id: '1', + type: 'node', + data: { + vizNode: loadBalanceNode, + }, + }; + + render( + + + + + + + , + ); + const allTab = screen.getByRole('button', { name: 'All' }); + const modifiedTab = screen.getByRole('button', { name: 'Modified' }); + act(() => { + fireEvent.click(modifiedTab); + }); + + expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeNull(); + + act(() => { + fireEvent.click(allTab); + }); + + const button = screen.getAllByRole('button', { name: 'Typeahead menu toggle' }); + await act(async () => { + fireEvent.click(button[0]); + }); + const weightedLoadBalancer = screen.getByTestId('loadbalancer-dropdownitem-weightedLoadBalancer'); + await act(async () => { + fireEvent.click(weightedLoadBalancer.getElementsByTagName('button')[0]); + }); + + act(() => { + fireEvent.click(modifiedTab); + }); + + expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeInTheDocument(); + + const inputDistributionRatioModifiedTabElement = screen + .queryAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('label') === 'Distribution Ratio'); + expect(inputDistributionRatioModifiedTabElement).toHaveLength(0); + + act(() => { + fireEvent.click(allTab); + }); + + await act(async () => { + const inputDistributionRatioDefaultTabElement = screen + .getAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('label') === 'Distribution Ratio'); + fireEvent.change(inputDistributionRatioDefaultTabElement[0], { target: { value: 'test' } }); + fireEvent.blur(inputDistributionRatioDefaultTabElement[0]); + }); + + act(() => { + fireEvent.click(modifiedTab); + }); + + expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeInTheDocument(); + + const inputDistributionRatioModifiedTabElementNew = screen + .getAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('label') === 'Distribution Ratio'); + expect(inputDistributionRatioModifiedTabElementNew).toHaveLength(1); + }); + }); + + describe('should show the Required field under the required tab', () => { + beforeEach(() => { + camelRouteVisualEntity = new CamelRouteVisualEntity(camelRouteJson); + const { nodes } = CanvasService.getFlowDiagram(camelRouteVisualEntity.toVizNode()); + selectedNode = nodes[0]; // timer + }); + + it('normal text field', async () => { + render( + + + + + + + , + ); + + const allTab = screen.getByRole('button', { name: 'All' }); + const requiredTab = screen.getByRole('button', { name: 'Required' }); + + expect(allTab).toBeInTheDocument(); + expect(requiredTab).toBeInTheDocument(); + + const inputTimerNameRequiredTabTabElement = screen + .queryAllByRole('textbox') + .filter((textbox) => textbox.getAttribute('label') === 'Timer Name'); + expect(inputTimerNameRequiredTabTabElement).toHaveLength(1); + }); + + it('expression field', async () => { + const camelRoute = { + from: { + uri: 'timer:tutorial', + steps: [ + { + setHeader: { + name: 'foo', + }, + }, + ], + }, + } as RouteDefinition; + const entity = new CamelRouteVisualEntity(camelRoute); + const rootNode: IVisualizationNode = entity.toVizNode(); + const setHeaderNode = rootNode.getChildren()![1]; + const selectedNode = { + id: '1', + type: 'node', + data: { + vizNode: setHeaderNode, + }, + }; + + render( + + + + + + + , + ); + + expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeInTheDocument(); + }); + + it('dataformat field', async () => { + const camelRoute = { + from: { + uri: 'timer:tutorial', + steps: [ + { + marshal: { + id: 'ms', + }, + }, + ], + }, + } as RouteDefinition; + const entity = new CamelRouteVisualEntity(camelRoute); + const rootNode: IVisualizationNode = entity.toVizNode(); + const marshalNode = rootNode.getChildren()![1]; + const selectedNode = { + id: '1', + type: 'node', + data: { + vizNode: marshalNode, + }, + }; + + render( + + + + + + + , + ); + + expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeInTheDocument(); + }); + + it('loadbalancer field', async () => { + const camelRoute = { + from: { + uri: 'timer:tutorial', + steps: [ + { + loadBalance: { + id: 'lb', + }, + }, + ], + }, + } as RouteDefinition; + const entity = new CamelRouteVisualEntity(camelRoute); + const rootNode: IVisualizationNode = entity.toVizNode(); + const loadBalanceNode = rootNode.getChildren()![1]; + const selectedNode = { + id: '1', + type: 'node', + data: { + vizNode: loadBalanceNode, + }, + }; + + render( + + + + + + + , + ); + + expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeInTheDocument(); + }); + }); }); diff --git a/packages/ui/src/components/Visualization/Canvas/CanvasForm.tsx b/packages/ui/src/components/Visualization/Canvas/CanvasForm.tsx index b674007ea..17274e98e 100644 --- a/packages/ui/src/components/Visualization/Canvas/CanvasForm.tsx +++ b/packages/ui/src/components/Visualization/Canvas/CanvasForm.tsx @@ -1,4 +1,4 @@ -import { Card, CardBody, CardHeader, SearchInput } from '@patternfly/react-core'; +import { Card, CardBody, CardHeader, SearchInput, ToggleGroup, ToggleGroupItem, Tooltip } from '@patternfly/react-core'; import { FunctionComponent, useCallback, useContext, useEffect, useMemo, useRef } from 'react'; import { VisibleFlowsContext, FilteredFieldContext } from '../../../providers'; import { ErrorBoundary } from '../../ErrorBoundary'; @@ -6,6 +6,8 @@ import './CanvasForm.scss'; import { CanvasFormHeader } from './Form/CanvasFormHeader'; import { CanvasNode } from './canvas.models'; import { CanvasFormTabs } from './CanvasFormTabs'; +import { FormTabsModes, getTabTooltip } from './canvasformtabs.modes'; +import { CanvasFormTabsContext } from '../../../providers/canvas-form-tabs.provider'; interface CanvasFormProps { selectedNode: CanvasNode; @@ -15,6 +17,7 @@ interface CanvasFormProps { export const CanvasForm: FunctionComponent = (props) => { const { visualFlowsApi } = useContext(VisibleFlowsContext)!; const { filteredFieldText, onFilterChange } = useContext(FilteredFieldContext); + const { selectedTab, onTabChange } = useContext(CanvasFormTabsContext); const flowIdRef = useRef(undefined); const visualComponentSchema = useMemo(() => { @@ -44,6 +47,25 @@ export const CanvasForm: FunctionComponent = (props) => { This node cannot be configured yet

}> + + + {Object.values(FormTabsModes).map((mode) => ( + + + + ))} + = (props) => { onChange={onFilterChange} onClear={onFilterChange} /> - diff --git a/packages/ui/src/components/Visualization/Canvas/CanvasFormTabs.test.tsx b/packages/ui/src/components/Visualization/Canvas/CanvasFormTabs.test.tsx index 9146849d2..a32e2c450 100644 --- a/packages/ui/src/components/Visualization/Canvas/CanvasFormTabs.test.tsx +++ b/packages/ui/src/components/Visualization/Canvas/CanvasFormTabs.test.tsx @@ -10,18 +10,15 @@ import { IKameletDefinition, } from '../../../models'; import { IVisualizationNode } from '../../../models/visualization/base-visual-entity'; -import { VisibleFlowsProvider } from '../../../providers'; -import { EntitiesContext, EntitiesProvider } from '../../../providers/entities.provider'; -import { camelRouteJson } from '../../../stubs'; +import { VisibleFlowsProvider, CanvasFormTabsContext } from '../../../providers'; +import { EntitiesContext } from '../../../providers/entities.provider'; import { getFirstCatalogMap } from '../../../stubs/test-load-catalog'; import { SchemaService } from '../../Form'; import { CanvasNode } from './canvas.models'; -import { CanvasService } from './canvas.service'; import { CanvasFormTabs } from './CanvasFormTabs'; +import { FormTabsModes } from './canvasformtabs.modes'; describe('CanvasFormTabs', () => { - let camelRouteVisualEntity: CamelRouteVisualEntity; - let selectedNode: CanvasNode; let componentCatalogMap: Record; let patternCatalogMap: Record; let kameletCatalogMap: Record; @@ -42,304 +39,6 @@ describe('CanvasFormTabs', () => { CamelCatalogService.setCatalogKey(CatalogKind.Entity, catalogsMap.entitiesCatalog); }); - beforeEach(() => { - camelRouteVisualEntity = new CamelRouteVisualEntity(camelRouteJson); - const { nodes } = CanvasService.getFlowDiagram(camelRouteVisualEntity.toVizNode()); - selectedNode = nodes[0]; // timer - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - describe('should show the User-updated field under the modified tab', () => { - it('normal text field', async () => { - render( - - - - - , - ); - - const defaultTab = screen.getByRole('button', { name: 'All Fields' }); - const modifiedTab = screen.getByRole('button', { name: 'User Modified' }); - - expect(defaultTab).toBeInTheDocument(); - expect(modifiedTab).toBeInTheDocument(); - - act(() => { - fireEvent.click(modifiedTab); - }); - - const inputVariableReceiveModifiedTabElement = screen - .queryAllByRole('textbox') - .filter((textbox) => textbox.getAttribute('label') === 'Variable Receive'); - expect(inputVariableReceiveModifiedTabElement).toHaveLength(0); - - act(() => { - fireEvent.click(defaultTab); - }); - - await act(async () => { - const inputVariableReceiveDefaultTabElement = screen - .getAllByRole('textbox') - .filter((textbox) => textbox.getAttribute('label') === 'Variable Receive'); - fireEvent.change(inputVariableReceiveDefaultTabElement[0], { target: { value: 'test' } }); - fireEvent.blur(inputVariableReceiveDefaultTabElement[0]); - }); - - act(() => { - fireEvent.click(modifiedTab); - }); - - const inputVariableReceiveModifiedTabElementNew = screen - .getAllByRole('textbox') - .filter((textbox) => textbox.getAttribute('label') === 'Variable Receive'); - expect(inputVariableReceiveModifiedTabElementNew).toHaveLength(1); - }); - - it('expression field', async () => { - const camelRoute = { - from: { - uri: 'timer:tutorial', - steps: [ - { - setHeader: { - name: 'foo', - }, - }, - ], - }, - } as RouteDefinition; - const entity = new CamelRouteVisualEntity(camelRoute); - const rootNode: IVisualizationNode = entity.toVizNode(); - const setHeaderNode = rootNode.getChildren()![1]; - const selectedNode = { - id: '1', - type: 'node', - data: { - vizNode: setHeaderNode, - }, - }; - - render( - - - - - , - ); - - const defaultTab = screen.getByRole('button', { name: 'All Fields' }); - const modifiedTab = screen.getByRole('button', { name: 'User Modified' }); - - act(() => { - fireEvent.click(modifiedTab); - }); - - act(() => { - fireEvent.click(defaultTab); - }); - - const button = screen - .getAllByTestId('typeahead-select-input') - .filter((input) => input.innerHTML.includes(SchemaService.DROPDOWN_PLACEHOLDER)); - act(() => { - fireEvent.click(button[0]); - }); - const simple = screen.getByTestId('expression-dropdownitem-simple'); - act(() => { - fireEvent.click(simple.getElementsByTagName('button')[0]); - }); - const expressionInput = screen - .getAllByRole('textbox') - .filter((textbox) => textbox.getAttribute('name') === 'expression'); - act(() => { - fireEvent.input(expressionInput[0], { target: { value: '${header.foo}' } }); - }); - - act(() => { - fireEvent.click(modifiedTab); - }); - }); - - it('dataformat field', async () => { - const camelRoute = { - from: { - uri: 'timer:tutorial', - steps: [ - { - marshal: { - id: 'ms', - }, - }, - ], - }, - } as RouteDefinition; - const entity = new CamelRouteVisualEntity(camelRoute); - const rootNode: IVisualizationNode = entity.toVizNode(); - const marshalNode = rootNode.getChildren()![1]; - const selectedNode = { - id: '1', - type: 'node', - data: { - vizNode: marshalNode, - }, - }; - - render( - - - - - , - ); - - const defaultTab = screen.getByRole('button', { name: 'All Fields' }); - const modifiedTab = screen.getByRole('button', { name: 'User Modified' }); - act(() => { - fireEvent.click(modifiedTab); - }); - - expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeNull(); - - act(() => { - fireEvent.click(defaultTab); - }); - - const dataformatDefaultTabButton = screen.getAllByRole('button', { name: 'Typeahead menu toggle' }); - await act(async () => { - fireEvent.click(dataformatDefaultTabButton[0]); - }); - const asn1 = screen.getByTestId('dataformat-dropdownitem-asn1'); - await act(async () => { - fireEvent.click(asn1.getElementsByTagName('button')[0]); - }); - - act(() => { - fireEvent.click(modifiedTab); - }); - - expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeInTheDocument(); - - const inputUnmarshalTypeModifiedTabElement = screen - .queryAllByRole('textbox') - .filter((textbox) => textbox.getAttribute('label') === 'Unmarshal Type'); - expect(inputUnmarshalTypeModifiedTabElement).toHaveLength(0); - - act(() => { - fireEvent.click(defaultTab); - }); - - await act(async () => { - const inputUnmarshalTypeDefaultTabElement = screen - .getAllByRole('textbox') - .filter((textbox) => textbox.getAttribute('label') === 'Unmarshal Type'); - fireEvent.change(inputUnmarshalTypeDefaultTabElement[0], { target: { value: 'test' } }); - fireEvent.blur(inputUnmarshalTypeDefaultTabElement[0]); - }); - - act(() => { - fireEvent.click(modifiedTab); - }); - - expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeInTheDocument(); - - const inputUnmarshalTypeModifiedTabElementNew = screen - .getAllByRole('textbox') - .filter((textbox) => textbox.getAttribute('label') === 'Unmarshal Type'); - expect(inputUnmarshalTypeModifiedTabElementNew).toHaveLength(1); - }); - - it('loadbalancer field', async () => { - const camelRoute = { - from: { - uri: 'timer:tutorial', - steps: [ - { - loadBalance: { - id: 'lb', - }, - }, - ], - }, - } as RouteDefinition; - const entity = new CamelRouteVisualEntity(camelRoute); - const rootNode: IVisualizationNode = entity.toVizNode(); - const loadBalanceNode = rootNode.getChildren()![1]; - const selectedNode = { - id: '1', - type: 'node', - data: { - vizNode: loadBalanceNode, - }, - }; - - render( - - - - - , - ); - const defaultTab = screen.getByRole('button', { name: 'All Fields' }); - const modifiedTab = screen.getByRole('button', { name: 'User Modified' }); - act(() => { - fireEvent.click(modifiedTab); - }); - - expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeNull(); - - act(() => { - fireEvent.click(defaultTab); - }); - - const button = screen.getAllByRole('button', { name: 'Typeahead menu toggle' }); - await act(async () => { - fireEvent.click(button[0]); - }); - const weightedLoadBalancer = screen.getByTestId('loadbalancer-dropdownitem-weightedLoadBalancer'); - await act(async () => { - fireEvent.click(weightedLoadBalancer.getElementsByTagName('button')[0]); - }); - - act(() => { - fireEvent.click(modifiedTab); - }); - - expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeInTheDocument(); - - const inputDistributionRatioModifiedTabElement = screen - .queryAllByRole('textbox') - .filter((textbox) => textbox.getAttribute('label') === 'Distribution Ratio'); - expect(inputDistributionRatioModifiedTabElement).toHaveLength(0); - - act(() => { - fireEvent.click(defaultTab); - }); - - await act(async () => { - const inputDistributionRatioDefaultTabElement = screen - .getAllByRole('textbox') - .filter((textbox) => textbox.getAttribute('label') === 'Distribution Ratio'); - fireEvent.change(inputDistributionRatioDefaultTabElement[0], { target: { value: 'test' } }); - fireEvent.blur(inputDistributionRatioDefaultTabElement[0]); - }); - - act(() => { - fireEvent.click(modifiedTab); - }); - - expect(screen.queryByRole('button', { name: 'Typeahead menu toggle' })).toBeInTheDocument(); - - const inputDistributionRatioModifiedTabElementNew = screen - .getAllByRole('textbox') - .filter((textbox) => textbox.getAttribute('label') === 'Distribution Ratio'); - expect(inputDistributionRatioModifiedTabElementNew).toHaveLength(1); - }); - }); - describe('should persists changes from both expression editor and main form', () => { beforeEach(() => { jest.spyOn(console, 'error').mockImplementation(() => {}); @@ -372,7 +71,14 @@ describe('CanvasFormTabs', () => { render( - + + + , ); @@ -432,7 +138,14 @@ describe('CanvasFormTabs', () => { render( - + + + , ); @@ -497,10 +210,18 @@ describe('CanvasFormTabs', () => { render( - + + + , ); + const button = screen.getAllByRole('button', { name: 'Typeahead menu toggle' }); await act(async () => { fireEvent.click(button[0]); @@ -547,10 +268,18 @@ describe('CanvasFormTabs', () => { render( - + + + , ); + const idInput = screen.getAllByRole('textbox').filter((textbox) => textbox.getAttribute('label') === 'Id'); await act(async () => { fireEvent.input(idInput[0], { target: { value: 'modified' } }); @@ -603,10 +332,18 @@ describe('CanvasFormTabs', () => { render( - + + + , ); + const button = screen.getAllByRole('button', { name: 'Typeahead menu toggle' }); await act(async () => { fireEvent.click(button[0]); @@ -653,10 +390,18 @@ describe('CanvasFormTabs', () => { render( - + + + , ); + const idInput = screen.getAllByRole('textbox').filter((textbox) => textbox.getAttribute('label') === 'Id'); await act(async () => { fireEvent.input(idInput[0], { target: { value: 'modified' } }); diff --git a/packages/ui/src/components/Visualization/Canvas/CanvasFormTabs.tsx b/packages/ui/src/components/Visualization/Canvas/CanvasFormTabs.tsx index d7d743b45..6e712e443 100644 --- a/packages/ui/src/components/Visualization/Canvas/CanvasFormTabs.tsx +++ b/packages/ui/src/components/Visualization/Canvas/CanvasFormTabs.tsx @@ -1,8 +1,7 @@ -import { ToggleGroup, ToggleGroupItem } from '@patternfly/react-core'; -import { FunctionComponent, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; +import { FunctionComponent, useCallback, useContext, useEffect, useMemo, useRef } from 'react'; import { EntitiesContext } from '../../../providers/entities.provider'; import { SchemaBridgeProvider } from '../../../providers/schema-bridge.provider'; -import { getUserUpdatedPropertiesSchema, isDefined, setValue } from '../../../utils'; +import { getUserUpdatedPropertiesSchema, getRequiredPropertiesSchema, isDefined, setValue } from '../../../utils'; import { CustomAutoForm, CustomAutoFormRef } from '../../Form/CustomAutoForm'; import { DataFormatEditor } from '../../Form/dataFormat/DataFormatEditor'; import { LoadBalancerEditor } from '../../Form/loadBalancer/LoadBalancerEditor'; @@ -11,6 +10,7 @@ import { UnknownNode } from '../Custom/UnknownNode'; import './CanvasFormTabs.scss'; import { CanvasNode } from './canvas.models'; import { FormTabsModes } from './canvasformtabs.modes'; +import { CanvasFormTabsContext } from '../../../providers/canvas-form-tabs.provider'; interface CanvasFormTabsProps { selectedNode: CanvasNode; @@ -18,10 +18,10 @@ interface CanvasFormTabsProps { export const CanvasFormTabs: FunctionComponent = (props) => { const entitiesContext = useContext(EntitiesContext); + const { selectedTab } = useContext(CanvasFormTabsContext); const divRef = useRef(null); const formRef = useRef(null); const omitFields = useRef(props.selectedNode.data?.vizNode?.getBaseEntity()?.getOmitFormFields() || []); - const [selectedTab, setSelectedTab] = useState(FormTabsModes.ALL_FIELDS); const visualComponentSchema = useMemo(() => { const answer = props.selectedNode.data?.vizNode?.getComponentSchema(); @@ -33,19 +33,15 @@ export const CanvasFormTabs: FunctionComponent = (props) => }, [props.selectedNode.data?.vizNode, selectedTab]); const model = visualComponentSchema?.definition; let processedSchema = visualComponentSchema?.schema; - if (selectedTab === FormTabsModes.USER_MODIFIED) { + if (selectedTab === FormTabsModes.REQUIRED_FIELDS) { + processedSchema = getRequiredPropertiesSchema(visualComponentSchema?.schema ?? {}); + } else if (selectedTab === FormTabsModes.USER_MODIFIED) { processedSchema = { ...visualComponentSchema?.schema, properties: getUserUpdatedPropertiesSchema(visualComponentSchema?.schema.properties ?? {}, model), }; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const handleItemClick = (event: any, _isSelected: boolean) => { - const id = event.currentTarget.id; - setSelectedTab(id); - }; - useEffect(() => { formRef.current?.form.reset(); }, [props.selectedNode.data?.vizNode, selectedTab]); @@ -87,17 +83,6 @@ export const CanvasFormTabs: FunctionComponent = (props) => ) : ( - - {Object.values(FormTabsModes).map((mode) => ( - - ))} - {stepFeatures.isExpressionAwareStep && ( )} diff --git a/packages/ui/src/components/Visualization/Canvas/Form/CanvasFormHeader.scss b/packages/ui/src/components/Visualization/Canvas/Form/CanvasFormHeader.scss index e37752fb4..ac02be1c1 100644 --- a/packages/ui/src/components/Visualization/Canvas/Form/CanvasFormHeader.scss +++ b/packages/ui/src/components/Visualization/Canvas/Form/CanvasFormHeader.scss @@ -2,6 +2,7 @@ display: flex; flex-flow: row nowrap; align-items: center; + margin-bottom: 15px; img[class^='form-header__icon-'] { height: 40px; diff --git a/packages/ui/src/components/Visualization/Canvas/__snapshots__/CanvasForm.test.tsx.snap b/packages/ui/src/components/Visualization/Canvas/__snapshots__/CanvasForm.test.tsx.snap index 6559b1f35..6534639ec 100644 --- a/packages/ui/src/components/Visualization/Canvas/__snapshots__/CanvasForm.test.tsx.snap +++ b/packages/ui/src/components/Visualization/Canvas/__snapshots__/CanvasForm.test.tsx.snap @@ -15,43 +15,6 @@ exports[`CanvasForm should render 1`] = `
-
-
- - - - - - -
-
@@ -100,211 +63,121 @@ exports[`CanvasForm should render 1`] = `
- - -
-
- -
-
- -
-
-
-
- - -
- -
+
+
+
- - - -
-
-
- -
-
-
+ All + +
- - -
- -
+
-
+
+
+
+ -