diff --git a/packages/core/forms/src/generator/FormGenerator.vue b/packages/core/forms/src/generator/FormGenerator.vue index 3fcc3996ad..24f88dae62 100644 --- a/packages/core/forms/src/generator/FormGenerator.vue +++ b/packages/core/forms/src/generator/FormGenerator.vue @@ -30,12 +30,31 @@ :key="`group-${i}`" > + + + + + + + + + + + + + + diff --git a/packages/entities/entities-plugins/src/components/PluginForm.cy.ts b/packages/entities/entities-plugins/src/components/PluginForm.cy.ts index 18395c2d15..c86f5042e9 100644 --- a/packages/entities/entities-plugins/src/components/PluginForm.cy.ts +++ b/packages/entities/entities-plugins/src/components/PluginForm.cy.ts @@ -3,15 +3,16 @@ import type { Router } from 'vue-router' import { createMemoryHistory, createRouter } from 'vue-router' import { type KongManagerPluginFormConfig, type KonnectPluginFormConfig } from '../types' import { - schema, credentialSchema, plugin1, aclCredential1, - schema2, scopedService, scopedConsumer, customPluginSchema, } from '../../fixtures/mockData' +import schemaAiProxy from '../../fixtures/schemas/ai-proxy' +import schemaCors from '../../fixtures/schemas/cors' +import schemaMocking from '../../fixtures/schemas/mocking' import PluginForm from './PluginForm.vue' import { VueFormGenerator } from '../../src' @@ -52,7 +53,7 @@ describe('', () => { }, { statusCode: 200, - body: params?.mockData ?? schema, + body: params?.mockData ?? schemaCors, }, ).as(params?.alias ?? 'getPluginSchema') } @@ -221,8 +222,8 @@ describe('', () => { cy.get('#config-private_network').should('be.visible') }) - it('should show common, required, and advanced fields when groupFields is true', () => { - interceptKMSchema({ mockData: schema2 }) + it('should show general, hoisted, and advanced fields when groupFields is true', () => { + interceptKMSchema({ mockData: schemaMocking }) cy.mount(PluginForm, { global: { components: { VueFormGenerator } }, @@ -246,8 +247,13 @@ describe('', () => { cy.getTestId('form-back').should('be.enabled') cy.getTestId('form-cancel').should('be.visible') - // scope fields + // pinned fields (but they should not be under a KCollapse) + cy.get('#enabled').should('exist') + .parent('.k-collapse').should('not.exist') + + // scope fields (this is also pinned, but they should not be under a KCollapse) cy.get('.field-selectionGroup').should('be.visible') + .parent('.k-collapse').should('not.exist') cy.get('.Global-check').should('be.visible') cy.get('.Scoped-check').should('be.visible') cy.get('.field-selectionGroup').find('.field-AutoSuggest').should('not.be.visible') @@ -257,45 +263,92 @@ describe('', () => { cy.get('#route-id').should('be.visible') cy.getTestId('k-collapse-title') - .contains('Common Fields') - .parents('.k-collapse') - .first() - .as('commonFields') - - cy.getTestId('k-collapse-title') - .contains('Required Fields') + .contains('Plugin Configuration') .parents('.k-collapse') .first() - .as('requiredFields') + .as('pluginFields') - cy.getTestId('k-collapse-title') - .contains('Advanced Fields') - .parents('.k-collapse') + cy.get('.k-collapse.nested-collapse [data-testid="k-collapse-trigger-label"]') + .contains('Advanced Parameters') + .parents('.k-collapse.nested-collapse') .first() .as('advancedFields') - // common fields - cy.get('@commonFields').find('#enabled').should('exist') - cy.get('@commonFields').find('#instance_name').should('exist') - cy.get('@commonFields').find('#tags').should('exist') - cy.get('@commonFields').find('.plugin-protocols-select').should('be.visible') - - // required fields - cy.get('@requiredFields').find('#config-required_non_checkbox_field').should('be.visible') - + // non-advanced plugin fields (but they should not be under the nested KCollapse) + // field rule alerts + cy.get('@pluginFields').find('.plugin-field-rule-alerts').contains('At least one of').should('be.visible') + .parent('.k-collapse.nested-collapse').should('not.exist') + // protocol selector + cy.get('@pluginFields').find('.plugin-protocols-select').should('be.visible') + .parent('.k-collapse.nested-collapse').should('not.exist') + // other required fields + cy.get('@pluginFields').find('#config-required_non_checkbox_field').should('be.visible') + .parent('.k-collapse.nested-collapse').should('not.exist') + cy.get('@pluginFields').find('#config-api_specification').should('be.visible') + .parent('.k-collapse.nested-collapse').should('not.exist') + cy.get('@pluginFields').find('#config-api_specification_filename').should('be.visible') + .parent('.k-collapse.nested-collapse').should('not.exist') + cy.get('@pluginFields').find('#config-include_base_path').should('be.visible') + .parent('.k-collapse.nested-collapse').should('not.exist') + cy.get('@pluginFields').find('#config-random_status_code').should('be.visible') + .parent('.k-collapse.nested-collapse').should('not.exist') + + // advanced plugin fields (they should be under the nested KCollapse) + // instance name and tags + cy.get('@advancedFields').find('#instance_name').should('exist').parent('.k-collapse').should('not.exist') + cy.get('@advancedFields').find('#tags').should('exist').parent('.k-collapse').should('not.exist') // advanced fields should be hidden by default cy.get('@advancedFields').findTestId('k-collapse-hidden-content').should('be.hidden') - // reveal them cy.get('@advancedFields').findTestId('k-collapse-trigger-content').click() cy.get('@advancedFields').findTestId('k-collapse-hidden-content').should('be.visible') - // advanced fields - cy.get('@advancedFields').find('#config-api_specification').should('be.visible') - cy.get('@advancedFields').find('#config-api_specification_filename').should('be.visible') - cy.get('@advancedFields').find('#config-include_base_path').should('be.visible') cy.get('@advancedFields').find('#config-included_status_codes').should('be.visible') - cy.get('@advancedFields').find('#config-random_status_code').should('be.visible') + cy.get('@advancedFields').find('#config-max_delay_time').should('be.visible') + cy.get('@advancedFields').find('#config-min_delay_time').should('be.visible') + }) + + it('should use legacy form when groupFields is true but useLegacyForm in the plugin metadata is true', () => { + interceptKMSchema({ mockData: schemaAiProxy }) + + cy.mount(PluginForm, { + global: { components: { VueFormGenerator } }, + props: { + config: { + ...baseConfigKM, + groupFields: true, + }, + pluginType: 'ai-proxy', + }, + router, + }) + + cy.wait('@getPluginSchema') + cy.get('.kong-ui-entities-plugin-form-container').should('be.visible') + + // button state + cy.getTestId('form-submit').should('be.visible') + cy.getTestId('form-submit').should('be.enabled') + cy.getTestId('form-back').should('be.visible') + cy.getTestId('form-back').should('be.enabled') + cy.getTestId('form-cancel').should('be.visible') + + // scope fields + cy.get('.field-selectionGroup').should('be.visible') + cy.get('.Global-check').should('be.visible') + cy.get('.Scoped-check').should('be.visible') + cy.get('.field-selectionGroup').find('.field-AutoSuggest').should('not.be.visible') + cy.get('.Scoped-check input').click() + cy.get('.field-selectionGroup').find('.field-AutoSuggest').should('be.visible') + cy.get('#service-id').should('not.exist') // ai-proxy only supports route scoping + cy.get('#route-id').should('be.visible') + + // legacy form should not contain any KCollapse elements + cy.get('.k-collapse').should('not.exist') + + // some of the fields + cy.get('#config-model-name').should('be.visible') + cy.get('#config-model-provider').should('be.visible') }) it('should show correct form components for custom plugin with arrays of objects', () => { @@ -703,7 +756,7 @@ describe('', () => { }) it('should handle error state - validation error', () => { - interceptKMSchema({ mockData: schema2 }) + interceptKMSchema({ mockData: schemaMocking }) cy.intercept( { method: 'POST', @@ -796,7 +849,7 @@ describe('', () => { }, { statusCode: 200, - body: params?.mockData ?? schema, + body: params?.mockData ?? schemaCors, }, ).as(params?.alias ?? 'getPluginSchema') } @@ -966,8 +1019,8 @@ describe('', () => { cy.get('#config-private_network').should('be.visible') }) - it('should show common, required, and advanced fields when groupFields is true', () => { - interceptKonnectSchema({ mockData: schema2 }) + it('should show general, hoisted, and advanced fields when groupFields is true', () => { + interceptKonnectSchema({ mockData: schemaMocking }) cy.mount(PluginForm, { global: { components: { VueFormGenerator } }, @@ -992,8 +1045,13 @@ describe('', () => { cy.getTestId('form-back').should('be.enabled') cy.getTestId('form-cancel').should('be.visible') - // scope fields + // pinned fields (but they should not be under a KCollapse) + cy.get('#enabled').should('exist') + .parent('.k-collapse').should('not.exist') + + // scope fields (this is also pinned, but they should not be under a KCollapse) cy.get('.field-selectionGroup').should('be.visible') + .parent('.k-collapse').should('not.exist') cy.get('.Global-check').should('be.visible') cy.get('.Scoped-check').should('be.visible') cy.get('.field-selectionGroup').find('.field-AutoSuggest').should('not.be.visible') @@ -1003,45 +1061,93 @@ describe('', () => { cy.get('#route-id').should('be.visible') cy.getTestId('k-collapse-title') - .contains('Common Fields') - .parents('.k-collapse') - .first() - .as('commonFields') - - cy.getTestId('k-collapse-title') - .contains('Required Fields') + .contains('Plugin Configuration') .parents('.k-collapse') .first() - .as('requiredFields') + .as('pluginFields') - cy.getTestId('k-collapse-title') - .contains('Advanced Fields') - .parents('.k-collapse') + cy.get('.k-collapse.nested-collapse [data-testid="k-collapse-trigger-label"]') + .contains('Advanced Parameters') + .parents('.k-collapse.nested-collapse') .first() .as('advancedFields') - // common fields - cy.get('@commonFields').find('#enabled').should('exist') - cy.get('@commonFields').find('#instance_name').should('exist') - cy.get('@commonFields').find('#tags').should('exist') - cy.get('@commonFields').find('.plugin-protocols-select').should('be.visible') - - // required fields - cy.get('@requiredFields').find('#config-required_non_checkbox_field').should('be.visible') - + // non-advanced plugin fields (but they should not be under the nested KCollapse) + // field rule alerts + cy.get('@pluginFields').find('.plugin-field-rule-alerts').contains('At least one of').should('be.visible') + .parent('.k-collapse.nested-collapse').should('not.exist') + // protocol selector + cy.get('@pluginFields').find('.plugin-protocols-select').should('be.visible') + .parent('.k-collapse.nested-collapse').should('not.exist') + // other required fields + cy.get('@pluginFields').find('#config-required_non_checkbox_field').should('be.visible') + .parent('.k-collapse.nested-collapse').should('not.exist') + cy.get('@pluginFields').find('#config-api_specification').should('be.visible') + .parent('.k-collapse.nested-collapse').should('not.exist') + cy.get('@pluginFields').find('#config-api_specification_filename').should('be.visible') + .parent('.k-collapse.nested-collapse').should('not.exist') + cy.get('@pluginFields').find('#config-include_base_path').should('be.visible') + .parent('.k-collapse.nested-collapse').should('not.exist') + cy.get('@pluginFields').find('#config-random_status_code').should('be.visible') + .parent('.k-collapse.nested-collapse').should('not.exist') + + // advanced plugin fields (they should be under the nested KCollapse) + // instance name and tags + cy.get('@advancedFields').find('#instance_name').should('exist').parent('.k-collapse').should('not.exist') + cy.get('@advancedFields').find('#tags').should('exist').parent('.k-collapse').should('not.exist') // advanced fields should be hidden by default cy.get('@advancedFields').findTestId('k-collapse-hidden-content').should('be.hidden') - // reveal them cy.get('@advancedFields').findTestId('k-collapse-trigger-content').click() cy.get('@advancedFields').findTestId('k-collapse-hidden-content').should('be.visible') - // advanced fields - cy.get('@advancedFields').find('#config-api_specification').should('be.visible') - cy.get('@advancedFields').find('#config-api_specification_filename').should('be.visible') - cy.get('@advancedFields').find('#config-include_base_path').should('be.visible') cy.get('@advancedFields').find('#config-included_status_codes').should('be.visible') - cy.get('@advancedFields').find('#config-random_status_code').should('be.visible') + cy.get('@advancedFields').find('#config-max_delay_time').should('be.visible') + cy.get('@advancedFields').find('#config-min_delay_time').should('be.visible') + }) + + it('should use legacy form when groupFields is true but useLegacyForm in the plugin metadata is true', () => { + interceptKonnectSchema({ mockData: schemaAiProxy }) + + cy.mount(PluginForm, { + global: { components: { VueFormGenerator } }, + props: { + config: { + ...baseConfigKonnect, + groupFields: true, + }, + pluginType: 'ai-proxy', + useCustomNamesForPlugin: true, + }, + router, + }) + + cy.wait('@getPluginSchema') + cy.get('.kong-ui-entities-plugin-form-container').should('be.visible') + + // button state + cy.getTestId('form-submit').should('be.visible') + cy.getTestId('form-submit').should('be.enabled') + cy.getTestId('form-back').should('be.visible') + cy.getTestId('form-back').should('be.enabled') + cy.getTestId('form-cancel').should('be.visible') + + // scope fields + cy.get('.field-selectionGroup').should('be.visible') + cy.get('.Global-check').should('be.visible') + cy.get('.Scoped-check').should('be.visible') + cy.get('.field-selectionGroup').find('.field-AutoSuggest').should('not.be.visible') + cy.get('.Scoped-check input').click() + cy.get('.field-selectionGroup').find('.field-AutoSuggest').should('be.visible') + cy.get('#service-id').should('not.exist') // ai-proxy only supports route scoping + cy.get('#route-id').should('be.visible') + + // legacy form should not contain any KCollapse elements + cy.get('.k-collapse').should('not.exist') + + // some of the fields + cy.get('#config-model-name').should('be.visible') + cy.get('#config-model-provider').should('be.visible') }) it('should show correct form components for custom plugin with arrays of objects', () => { @@ -1473,7 +1579,7 @@ describe('', () => { }) it('should handle error state - validation error', () => { - interceptKonnectSchema({ mockData: schema2 }) + interceptKonnectSchema({ mockData: schemaMocking }) cy.intercept( { method: 'POST', diff --git a/packages/entities/entities-plugins/src/components/PluginForm.vue b/packages/entities/entities-plugins/src/components/PluginForm.vue index f665fd5bca..857492d42d 100644 --- a/packages/entities/entities-plugins/src/components/PluginForm.vue +++ b/packages/entities/entities-plugins/src/components/PluginForm.vue @@ -27,6 +27,7 @@ :fetch-url="fetchUrl" :form-fields="getRequestBody" :is-readonly="form.isReadonly" + no-validate @cancel="handleClickCancel" @fetch:error="(err: any) => $emit('error', err)" @fetch:success="initForm" @@ -131,37 +132,38 @@