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}`"
>
+
+ {{ group.collapsible.description }}
+
+
+
+
+
+
+
+ collapseStates[`group-${i}-nested`] = value"
+ >
+
+
+
+
+
+
+
+
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 @@