From a754506d9bf77e0f51686149018a1d484542a0ad Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Fri, 20 Oct 2023 12:45:25 -0400
Subject: [PATCH 01/40] feat(plugins): form unification [khcp-9487]
---
packages/entities/entities-plugins/README.md | 4 +-
.../entities-plugins/docs/plugin-form.md | 113 ++++++++++++++++++
.../entities-plugins/src/plugins-endpoints.ts | 4 +
3 files changed, 120 insertions(+), 1 deletion(-)
create mode 100644 packages/entities/entities-plugins/docs/plugin-form.md
diff --git a/packages/entities/entities-plugins/README.md b/packages/entities/entities-plugins/README.md
index 8d9ec8b01b..8711178ee5 100644
--- a/packages/entities/entities-plugins/README.md
+++ b/packages/entities/entities-plugins/README.md
@@ -37,11 +37,13 @@ yarn add @kong-ui-public/entities-plugins
Import the component(s) in your host application as well as the package styles
```ts
-import { PluginList, PluginConfigCard } from '@kong-ui-public/entities-plugins'
+import { PluginList, PluginSelect, PluginForm, PluginConfigCard } from '@kong-ui-public/entities-plugins'
import '@kong-ui-public/entities-plugins/dist/style.css'
```
## Individual component documentation
- [``](docs/plugin-list.md)
+- [``](docs/plugin-select.md)
+- [``](docs/plugin-form.md)
- [``](docs/plugin-config-card.md)
diff --git a/packages/entities/entities-plugins/docs/plugin-form.md b/packages/entities/entities-plugins/docs/plugin-form.md
new file mode 100644
index 0000000000..dd8d621881
--- /dev/null
+++ b/packages/entities/entities-plugins/docs/plugin-form.md
@@ -0,0 +1,113 @@
+# PluginForm.vue
+
+A form component for Plugins.
+
+- [Requirements](#requirements)
+- [Usage](#usage)
+ - [Install](#install)
+ - [Props](#props)
+ - [Events](#events)
+ - [Usage example](#usage-example)
+- [TypeScript interfaces](#typescript-interfaces)
+
+## Requirements
+
+- `vue` and `vue-router` must be initialized in the host application
+- `@kong/kongponents` must be added as a dependency in the host application, globally available via the Vue Plugin installation, and the package's style imports must be added in the app entry file. [See here for instructions on installing Kongponents](https://kongponents.konghq.com/#globally-install-all-kongponents).
+- `@kong-ui-public/i18n` must be available as a `dependency` in the host application.
+- `axios` must be installed as a dependency in the host application
+
+## Usage
+
+### Install
+
+[See instructions for installing the `@kong-ui-public/entities-plugins` package.](../README.md#install)
+
+### Props
+
+#### `config`
+
+- type: `Object as PropType`
+- required: `true`
+- default: `undefined`
+- properties:
+ - `app`:
+ - type: `'konnect' | 'kongManager'`
+ - required: `true`
+ - default: `undefined`
+ - App name.
+
+ - `apiBaseUrl`:
+ - type: `string`
+ - required: `true`
+ - default: `undefined`
+ - Base URL for API requests.
+
+ - `requestHeaders`:
+ - type: `RawAxiosRequestHeaders | AxiosHeaders`
+ - required: `false`
+ - default: `undefined`
+ - Additional headers to send with all Axios requests.
+
+ - `cancelRoute`:
+ - type: `RouteLocationRaw`
+ - required: `true`
+ - default: `undefined`
+ - Route to return to when canceling creation of a plugin.
+
+ - `workspace`:
+ - type: `string`
+ - required: `true`
+ - default: `undefined`
+ - *Specific to Kong Manager*. Name of the current workspace.
+
+ - `controlPlaneId`:
+ - type: `string`
+ - required: `true`
+ - default: `undefined`
+ - *Specific to Konnect*. Name of the current control plane.
+
+ - `consumerId`:
+ - type: `string`
+ - required: `false`
+ - default: `''`
+ - Consumer to bind the plugin to on creation. This is used when creating a consumer credential.
+
+The base konnect or kongManger config.
+
+#### `pluginId`
+
+- type: `String`
+- required: `false`
+- default: `''`
+
+If showing the `Edit` type form, the ID of the plugin.
+
+### Events
+
+#### error
+
+An `@error` event is emitted when form validation fails. The event payload is the response error.
+
+#### loading
+
+A `@loading` event is emitted when loading state changes. The event payload is a boolean.
+
+#### update
+
+A `@update` event is emitted when the form is saved. The event payload is the plugin object.
+
+### Usage example
+
+Please refer to the [sandbox](../sandbox/pages/PluginListPage.vue). The form is accessible by clicking the `+ New Plugin` button or `Edit` action of an existing plugin.
+
+## TypeScript interfaces
+
+TypeScript interfaces [are available here](https://github.com/Kong/public-ui-components/blob/main/packages/entities/entities-plugins/src/types/plugin-form.ts) and can be directly imported into your host application. The following type interfaces are available for import:
+
+```ts
+import type {
+ KonnectPluginFormConfig,
+ KongManagerPluginFormConfig,
+} from '@kong-ui-public/entities-plugins'
+```
diff --git a/packages/entities/entities-plugins/src/plugins-endpoints.ts b/packages/entities/entities-plugins/src/plugins-endpoints.ts
index 432723bf26..b495a80148 100644
--- a/packages/entities/entities-plugins/src/plugins-endpoints.ts
+++ b/packages/entities/entities-plugins/src/plugins-endpoints.ts
@@ -11,12 +11,16 @@ export default {
},
form: {
konnect: {
+ create: '/api/runtime_groups/{controlPlaneId}/plugins',
edit: '/api/runtime_groups/{controlPlaneId}/plugins/{id}',
pluginSchema: '/api/runtime_groups/{controlPlaneId}/schemas/plugins/{plugin}',
+ validate: '/api/runtime_groups/{controlPlaneId}/v1/schemas/json/plugin/validate',
},
kongManager: {
+ create: '/{workspace}/plugins',
edit: '/{workspace}/plugins/{id}',
pluginSchema: '/{workspace}/schemas/plugins/{plugin}',
+ validate: '/{workspace}/schemas/plugins/validate',
},
},
item: {
From 65d781592d04e638f3fba5cdba5cc8a74f802b74 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Sun, 22 Oct 2023 21:58:31 -0400
Subject: [PATCH 02/40] fix(*): second drop
---
.../src/components/PluginCardSkeleton.vue | 62 +++
.../src/components/PluginList.vue | 3 +-
.../src/components/PluginSelect.vue | 239 +++++++++++
.../src/components/PluginSelectCard.vue | 338 +++++++++++++++
.../src/components/PluginSelectGrid.vue | 402 ++++++++++++++++++
.../entities/entities-plugins/src/index.ts | 2 +
.../entities-plugins/src/locales/en.json | 33 +-
.../entities-plugins/src/types/index.ts | 1 +
.../entities-plugins/src/types/plugin-form.ts | 35 ++
.../entities-plugins/src/types/plugin.ts | 20 +-
10 files changed, 1129 insertions(+), 6 deletions(-)
create mode 100644 packages/entities/entities-plugins/src/components/PluginCardSkeleton.vue
create mode 100644 packages/entities/entities-plugins/src/components/PluginSelect.vue
create mode 100644 packages/entities/entities-plugins/src/components/PluginSelectCard.vue
create mode 100644 packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
create mode 100644 packages/entities/entities-plugins/src/types/plugin-form.ts
diff --git a/packages/entities/entities-plugins/src/components/PluginCardSkeleton.vue b/packages/entities/entities-plugins/src/components/PluginCardSkeleton.vue
new file mode 100644
index 0000000000..1b5caff480
--- /dev/null
+++ b/packages/entities/entities-plugins/src/components/PluginCardSkeleton.vue
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/entities/entities-plugins/src/components/PluginList.vue b/packages/entities/entities-plugins/src/components/PluginList.vue
index 95328e1a30..85aa7fa53f 100644
--- a/packages/entities/entities-plugins/src/components/PluginList.vue
+++ b/packages/entities/entities-plugins/src/components/PluginList.vue
@@ -237,6 +237,7 @@ import {
useDeleteUrlBuilder,
useGatewayFeatureSupported,
} from '@kong-ui-public/entities-shared'
+import '@kong-ui-public/entities-shared/dist/style.css'
import type {
BaseTableHeaders,
@@ -259,8 +260,6 @@ import type {
import PluginIcon from './PluginIcon.vue'
-import '@kong-ui-public/entities-shared/dist/style.css'
-
const pluginMetaData = composables.usePluginMetaData()
const emit = defineEmits<{
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
new file mode 100644
index 0000000000..78b59dfa65
--- /dev/null
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+
diff --git a/packages/entities/entities-plugins/src/components/PluginSelectCard.vue b/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
new file mode 100644
index 0000000000..4c269ec76f
--- /dev/null
+++ b/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
@@ -0,0 +1,338 @@
+
+
+
+
+ {{ plugin.disabledMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('actions.edit') }}
+
+
+
+
+ {{ t('actions.delete') }}
+
+
+
+
+
+
+
+
+
+ {{ plugin.name }}
+
+
+
+ {{ plugin.description }}
+
+
+
+ {{ isCreateCustomPlugin ? t('actions.create_custom') : t('actions.enable') }}
+
+
+
+
+
+
+
+
+
diff --git a/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue b/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
new file mode 100644
index 0000000000..e5675e4222
--- /dev/null
+++ b/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
@@ -0,0 +1,402 @@
+
+
+
+
+
+
+
diff --git a/packages/entities/entities-plugins/src/index.ts b/packages/entities/entities-plugins/src/index.ts
index 55d3d14213..c7b5a5df64 100644
--- a/packages/entities/entities-plugins/src/index.ts
+++ b/packages/entities/entities-plugins/src/index.ts
@@ -1,5 +1,6 @@
import PluginIcon from './components/PluginIcon.vue'
import PluginList from './components/PluginList.vue'
+import PluginForm from './components/PluginForm.vue'
import PluginConfigCard from './components/PluginConfigCard.vue'
import composables from './composables'
@@ -8,6 +9,7 @@ const { getPluginIconURL, usePluginMetaData } = composables
export {
PluginIcon,
PluginList,
+ PluginForm,
PluginConfigCard,
getPluginIconURL,
usePluginMetaData,
diff --git a/packages/entities/entities-plugins/src/locales/en.json b/packages/entities/entities-plugins/src/locales/en.json
index 1d64dedb23..bbf00f2389 100644
--- a/packages/entities/entities-plugins/src/locales/en.json
+++ b/packages/entities/entities-plugins/src/locales/en.json
@@ -1,9 +1,11 @@
{
"actions": {
"create": "New Plugin",
+ "create_custom": "Create",
"copy_id": "Copy ID",
"copy_json": "Copy JSON",
"edit": "Edit",
+ "enable": "Enable",
"delete": "Delete",
"view": "View Details",
"configure_dynamic_ordering": "Configure Dynamic Ordering"
@@ -21,17 +23,20 @@
"errors": {
"general": "Plugins could not be retrieved",
"delete": "The plugin could not be deleted at this time.",
- "copy": "Failed to copy to clipboard"
+ "copy": "Failed to copy to clipboard",
+ "load_results": "Error loading available plugins"
},
"search": {
"placeholder": {
- "konnect": "Filter by exact instance name or ID"
+ "konnect": "Filter by exact instance name or ID",
+ "select": "Filter plugins"
},
"filter": {
"field": {
"enabled": "Enabled"
}
- }
+ },
+ "no_results": "No results found for \"{filter}\""
},
"plugins": {
"title": "Plugins",
@@ -391,6 +396,28 @@
"route": "Route ID",
"consumer": "Consumer ID",
"consumer_group": "Consumer Group ID"
+ },
+ "select": {
+ "custom_badge_text": "Data Plane Node Specific",
+ "unavailable_tooltip": "This plugin is not available",
+ "misc_plugins": "Other Plugins",
+ "tabs": {
+ "kong": {
+ "title": "Kong Plugins",
+ "description": "Kong plugins are bundled by default with Kong Gateway and are available across all control planes.",
+ "app_reg_tooltip": "This plugin is not available because App Registration is enabled for this Service."
+ },
+ "custom": {
+ "title": "Custom Plugins",
+ "description": "Custom plugins will be available in this control plane only.",
+ "empty_title": "No Custom Plugins",
+ "empty_description": "No custom plugins have been added to this Control Plane.",
+ "create": {
+ "name": "Custom Plugin",
+ "description": "Upload schema file to create custom plugin"
+ }
+ }
+ }
}
},
"glossary": {
diff --git a/packages/entities/entities-plugins/src/types/index.ts b/packages/entities/entities-plugins/src/types/index.ts
index 323770ab02..50c9c2cd36 100644
--- a/packages/entities/entities-plugins/src/types/index.ts
+++ b/packages/entities/entities-plugins/src/types/index.ts
@@ -1,3 +1,4 @@
export * from './plugin-config-card'
+export * from './plugin-form'
export * from './plugin-list'
export * from './plugin'
diff --git a/packages/entities/entities-plugins/src/types/plugin-form.ts b/packages/entities/entities-plugins/src/types/plugin-form.ts
new file mode 100644
index 0000000000..eb4beb2be4
--- /dev/null
+++ b/packages/entities/entities-plugins/src/types/plugin-form.ts
@@ -0,0 +1,35 @@
+import type { RouteLocationRaw } from 'vue-router'
+import type { KonnectBaseFormConfig, KongManagerBaseFormConfig } from '@kong-ui-public/entities-shared'
+
+export interface BasePluginFormConfig {
+ /** Route for creating a plugin */
+ createRoute: RouteLocationRaw
+ /** Consumer to bind the Plugin to on creation if Consumer Credential */
+ consumerId?: string
+}
+
+/** Konnect Plugin form config */
+export interface KonnectPluginFormConfig extends BasePluginFormConfig, KonnectBaseFormConfig {
+ /** Route for creating a custom plugin */
+ createCustomRoute: RouteLocationRaw
+ /** A function that returns the route for editing a custom plugin */
+ getCustomEditRoute: (id: string) => RouteLocationRaw
+}
+
+/** Kong Manager Plugin form config */
+export interface KongManagerPluginFormConfig extends BasePluginFormConfig, KongManagerBaseFormConfig {}
+
+export interface PluginFormFields {
+ name: string
+ tags: string
+ consumer_id: string
+}
+
+export interface PluginFormState {
+ /** Form fields */
+ fields: PluginFormFields
+ /** Form readonly state (only used when saving entity details) */
+ isReadonly: boolean
+ /** The error message to show on the form */
+ errorMessage: string
+}
diff --git a/packages/entities/entities-plugins/src/types/plugin.ts b/packages/entities/entities-plugins/src/types/plugin.ts
index c8777722a9..f834c4e7b4 100644
--- a/packages/entities/entities-plugins/src/types/plugin.ts
+++ b/packages/entities/entities-plugins/src/types/plugin.ts
@@ -8,7 +8,19 @@ export enum PluginGroup {
LOGGING = 'Logging',
DEPLOYMENT = 'Deployment',
WEBSOCKET = 'WebSocket Plugins',
- CUSTOM_PLUGINS = 'Other Plugins',
+ CUSTOM_PLUGINS = 'Custom Plugins',
+}
+
+export const PLUGIN_GROUPS_COLLAPSE_STATUS = {
+ AUTHENTICATION: true,
+ SECURITY: true,
+ TRAFFIC_CONTROL: true,
+ SERVERLESS: true,
+ ANALYTICS_AND_MONITORING: true,
+ TRANSFORMATIONS: true,
+ LOGGING: true,
+ DEPLOYMENT: true,
+ CUSTOM_PLUGINS: true,
}
export enum PluginScope {
@@ -28,3 +40,9 @@ export type PluginMetaData = {
name: string // A display name of the Plugin.
scope: PluginScope[] // The scope supported by the Plugin.
}
+
+export interface PluginType extends PluginMetaData {
+ available: boolean // whether the plugin is available or not
+ disabledMessage?: string // An optional field for plugin's disabled message.
+ id: string // the plugin schema name
+}
From 742b9ddeb780399e39b89601518477be3c0e574e Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Thu, 26 Oct 2023 14:30:26 -0400
Subject: [PATCH 03/40] fix(*): third drop
---
.../entities-plugins/docs/plugin-form.md | 6 +-
.../entities-plugins/docs/plugin-select.md | 95 +++
.../entities-plugins/sandbox/index.ts | 7 +-
.../sandbox/pages/PluginListPage.vue | 4 +-
.../sandbox/pages/PluginSelectPage.vue | 59 ++
.../DeleteCustomPluginSchemaModal.vue | 180 +++++
.../src/components/PluginCustomGrid.vue | 98 +++
.../src/components/PluginForm.cy.ts | 632 ++++++++++++++++++
.../src/components/PluginForm.vue | 233 +++++++
.../src/components/PluginSelect.vue | 105 +--
.../src/components/PluginSelectCard.vue | 59 +-
.../src/components/PluginSelectGrid.vue | 294 ++++----
.../entities-plugins/src/constants.ts | 1 +
.../entities/entities-plugins/src/index.ts | 6 +-
.../entities-plugins/src/locales/en.json | 2 +
.../entities-plugins/src/plugins-endpoints.ts | 8 +
.../entities-plugins/src/types/plugin-form.ts | 10 +-
.../entities-plugins/src/types/plugin.ts | 25 +
.../src/composables/useHelpers.ts | 40 ++
19 files changed, 1622 insertions(+), 242 deletions(-)
create mode 100644 packages/entities/entities-plugins/docs/plugin-select.md
create mode 100644 packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue
create mode 100644 packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue
create mode 100644 packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
create mode 100644 packages/entities/entities-plugins/src/components/PluginForm.cy.ts
create mode 100644 packages/entities/entities-plugins/src/components/PluginForm.vue
create mode 100644 packages/entities/entities-plugins/src/constants.ts
diff --git a/packages/entities/entities-plugins/docs/plugin-form.md b/packages/entities/entities-plugins/docs/plugin-form.md
index dd8d621881..bbcda141e0 100644
--- a/packages/entities/entities-plugins/docs/plugin-form.md
+++ b/packages/entities/entities-plugins/docs/plugin-form.md
@@ -67,11 +67,11 @@ A form component for Plugins.
- default: `undefined`
- *Specific to Konnect*. Name of the current control plane.
- - `consumerId`:
+ - `entityId`:
- type: `string`
- required: `false`
- default: `''`
- - Consumer to bind the plugin to on creation. This is used when creating a consumer credential.
+ - Id of the entity to bind the plugin to on creation.
The base konnect or kongManger config.
@@ -99,7 +99,7 @@ A `@update` event is emitted when the form is saved. The event payload is the pl
### Usage example
-Please refer to the [sandbox](../sandbox/pages/PluginListPage.vue). The form is accessible by clicking the `+ New Plugin` button or `Edit` action of an existing plugin.
+Please refer to the [sandbox](../sandbox/pages/PluginListPage.vue). The form is accessible by clicking the `Edit` action of an existing plugin or after selecting a plugin when creating a new one.
## TypeScript interfaces
diff --git a/packages/entities/entities-plugins/docs/plugin-select.md b/packages/entities/entities-plugins/docs/plugin-select.md
new file mode 100644
index 0000000000..0e15d299bf
--- /dev/null
+++ b/packages/entities/entities-plugins/docs/plugin-select.md
@@ -0,0 +1,95 @@
+# PluginSelect.vue
+
+A grid component for selecting Plugins.
+
+- [Requirements](#requirements)
+- [Usage](#usage)
+ - [Install](#install)
+ - [Props](#props)
+ - [Events](#events)
+ - [Usage example](#usage-example)
+- [TypeScript interfaces](#typescript-interfaces)
+
+## Requirements
+
+- `vue` and `vue-router` must be initialized in the host application
+- `@kong/kongponents` must be added as a dependency in the host application, globally available via the Vue Plugin installation, and the package's style imports must be added in the app entry file. [See here for instructions on installing Kongponents](https://kongponents.konghq.com/#globally-install-all-kongponents).
+- `@kong-ui-public/i18n` must be available as a `dependency` in the host application.
+- `axios` must be installed as a dependency in the host application
+
+## Usage
+
+### Install
+
+[See instructions for installing the `@kong-ui-public/entities-plugins` package.](../README.md#install)
+
+### Props
+
+#### `config`
+
+- type: `Object as PropType`
+- required: `true`
+- default: `undefined`
+- properties:
+ - `app`:
+ - type: `'konnect' | 'kongManager'`
+ - required: `true`
+ - default: `undefined`
+ - App name.
+
+ - `apiBaseUrl`:
+ - type: `string`
+ - required: `true`
+ - default: `undefined`
+ - Base URL for API requests.
+
+ - `requestHeaders`:
+ - type: `RawAxiosRequestHeaders | AxiosHeaders`
+ - required: `false`
+ - default: `undefined`
+ - Additional headers to send with all Axios requests.
+
+ - `getCreateRoute`:
+ - type: `(plugin: string) => RouteLocationRaw`
+ - required: `true`
+ - default: `undefined`
+ - A function that returns the route for creating a specific plugin type.
+
+ - `workspace`:
+ - type: `string`
+ - required: `true`
+ - default: `undefined`
+ - *Specific to Kong Manager*. Name of the current workspace.
+
+ - `controlPlaneId`:
+ - type: `string`
+ - required: `true`
+ - default: `undefined`
+ - *Specific to Konnect*. Name of the current control plane.
+
+The base konnect or kongManger config.
+
+### Events
+
+#### plugin-clicked
+
+An `@plugin-clicked` event is emitted when a plugin in the selection grid is clicked. The event payload is the plugin object.
+
+#### loading
+
+A `@loading` event is emitted when loading state changes. The event payload is a boolean.
+
+### Usage example
+
+Please refer to the [sandbox](../sandbox/pages/PluginListPage.vue). The form is accessible by clicking the `+ New Plugin` button.
+
+## TypeScript interfaces
+
+TypeScript interfaces [are available here](https://github.com/Kong/public-ui-components/blob/main/packages/entities/entities-plugins/src/types/plugin-form.ts) and can be directly imported into your host application. The following type interfaces are available for import:
+
+```ts
+import type {
+ KonnectPluginFormConfig,
+ KongManagerPluginFormConfig,
+} from '@kong-ui-public/entities-plugins'
+```
diff --git a/packages/entities/entities-plugins/sandbox/index.ts b/packages/entities/entities-plugins/sandbox/index.ts
index bc99ab7b01..cfd1e30b1a 100644
--- a/packages/entities/entities-plugins/sandbox/index.ts
+++ b/packages/entities/entities-plugins/sandbox/index.ts
@@ -16,7 +16,12 @@ const init = async () => {
component: () => import('./pages/PluginListPage.vue'),
},
{
- path: '/plugin/create',
+ path: '/plugin/select',
+ name: 'select-plugin',
+ component: () => import('./pages/PluginSelectPage.vue'),
+ },
+ {
+ path: '/plugin/create/:plugin',
name: 'create-plugin',
component: () => import('./pages/FallbackPage.vue'),
},
diff --git a/packages/entities/entities-plugins/sandbox/pages/PluginListPage.vue b/packages/entities/entities-plugins/sandbox/pages/PluginListPage.vue
index c260517b62..591263b8c7 100644
--- a/packages/entities/entities-plugins/sandbox/pages/PluginListPage.vue
+++ b/packages/entities/entities-plugins/sandbox/pages/PluginListPage.vue
@@ -56,7 +56,7 @@ const konnectConfig = ref({
apiBaseUrl: '/us/kong-api/konnect-api', // `/{geo}/kong-api/konnect-api`, with leading slash and no trailing slash
// Set the root `.env.development.local` variable to a control plane your PAT can access
controlPlaneId,
- createRoute: { name: 'create-plugin' },
+ createRoute: { name: 'select-plugin' },
getViewRoute: (plugin: EntityRow) => ({ name: 'view-plugin', params: { id: plugin.id, plugin: plugin.name } }),
getEditRoute: (plugin: EntityRow) => ({ name: 'edit-plugin', params: { id: plugin.id } }),
getScopedEntityViewRoute: (type: ViewRouteType, id: string) => ({ name: `view-${type}`, params: { id } }),
@@ -73,7 +73,7 @@ const kongManagerConfig = ref({
workspace: 'default',
apiBaseUrl: '/kong-manager', // For local dev server proxy
isExactMatch: false,
- createRoute: { name: 'create-plugin' },
+ createRoute: { name: 'select-plugin' },
getViewRoute: (plugin: EntityRow) => ({ name: 'view-plugin', params: { id: plugin.id, plugin: plugin.name } }),
getEditRoute: (plugin: EntityRow) => ({ name: 'edit-plugin', params: { id: plugin.id } }),
getScopedEntityViewRoute: (type: ViewRouteType, id: string) => ({ name: `view-${type}`, params: { id } }),
diff --git a/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue b/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue
new file mode 100644
index 0000000000..81ff226baa
--- /dev/null
+++ b/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue
@@ -0,0 +1,59 @@
+
+ Konnect API
+
+
+ Kong Manager API
+
+
+
+
diff --git a/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue b/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue
new file mode 100644
index 0000000000..2bb7e4c824
--- /dev/null
+++ b/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+ {{ plugin?.name }}
+
+
+
+
+
+
+
+
+ {{ helpText.cancel }}
+
+
+ {{ helpText.goToPlugins }}
+
+
+ {{ helpText.confirm }}
+
+
+
+
+
+
+
+
+
diff --git a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue b/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
new file mode 100644
index 0000000000..03d6ed9396
--- /dev/null
+++ b/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ plugin.disabledMessage }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ plugin.disabledMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/entities/entities-plugins/src/components/PluginForm.cy.ts b/packages/entities/entities-plugins/src/components/PluginForm.cy.ts
new file mode 100644
index 0000000000..ec13322d29
--- /dev/null
+++ b/packages/entities/entities-plugins/src/components/PluginForm.cy.ts
@@ -0,0 +1,632 @@
+// Cypress component test spec file
+import type { KongManagerPluginFormConfig, KonnectPluginFormConfig } from '../types'
+import { plugin1 } from '../../fixtures/mockData'
+import PluginForm from './PluginForm.vue'
+import { EntityBaseForm } from '@kong-ui-public/entities-shared'
+
+const cancelRoute = { name: 'plugins-list' }
+
+const baseConfigKonnect:KonnectPluginFormConfig = {
+ app: 'konnect',
+ controlPlaneId: '1234-abcd-ilove-cats',
+ apiBaseUrl: '/us/kong-api/konnect-api',
+ cancelRoute,
+}
+
+const baseConfigKM:KongManagerPluginFormConfig = {
+ app: 'kongManager',
+ workspace: 'default',
+ apiBaseUrl: '/kong-manager',
+ cancelRoute,
+}
+
+describe('', () => {
+ describe('Kong Manager', () => {
+ const interceptKM = (params?: {
+ mockData?: object
+ alias?: string
+ }) => {
+ cy.intercept(
+ {
+ method: 'GET',
+ url: `${baseConfigKM.apiBaseUrl}/${baseConfigKM.workspace}/plugins/*`,
+ },
+ {
+ statusCode: 200,
+ body: params?.mockData ?? plugin1,
+ },
+ ).as(params?.alias ?? 'getPlugin')
+ }
+
+ const interceptUpdate = (status = 200): void => {
+ cy.intercept(
+ {
+ method: 'POST',
+ url: `${baseConfigKM.apiBaseUrl}/${baseConfigKM.workspace}/schemas/plugins/validate`,
+ },
+ {
+ statusCode: status,
+ body: { },
+ },
+ ).as('validatePlugin')
+
+ cy.intercept(
+ {
+ method: 'PATCH',
+ url: `${baseConfigKM.apiBaseUrl}/${baseConfigKM.workspace}/plugins/*`,
+ },
+ {
+ statusCode: status,
+ body: { ...plugin1, tags: ['tag1', 'tag2'] },
+ },
+ ).as('updatePlugin')
+ }
+
+ it('should show create form', () => {
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKM,
+ },
+ })
+
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugins-form form').should('be.visible')
+ // button state
+ cy.getTestId('form-cancel').should('be.visible')
+ cy.getTestId('form-submit').should('be.visible')
+ cy.getTestId('form-cancel').should('be.enabled')
+ cy.getTestId('form-submit').should('be.disabled')
+ // form fields
+ cy.getTestId('plugin-form-name').should('be.visible')
+ cy.getTestId('plugin-form-tags').should('be.visible')
+ cy.getTestId('plugin-form-certificate-id').should('be.visible')
+ // certs load in select
+ cy.getTestId('plugin-form-certificate-id').click()
+ cy.get('.k-select-list .k-select-item').should('have.length', certificates.data.length)
+ })
+
+ it('should correctly handle button state - create', () => {
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKM,
+ },
+ })
+
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ // default button state
+ cy.getTestId('form-cancel').should('be.visible')
+ cy.getTestId('form-submit').should('be.visible')
+ cy.getTestId('form-cancel').should('be.enabled')
+ cy.getTestId('form-submit').should('be.disabled')
+ // enables save when required fields have values
+ cy.getTestId('plugin-form-name').type('tk-meowstersmith')
+ cy.getTestId('plugin-form-certificate-id').click()
+ cy.get(`[data-testid="k-select-item-${certificates.data[0].id}"] button`).click()
+ cy.getTestId('form-submit').should('be.enabled')
+ // disables save when required field is cleared
+ cy.getTestId('plugin-form-name').clear()
+ cy.getTestId('form-submit').should('be.disabled')
+ })
+
+ it('should allow exact match filtering of certs', () => {
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKM,
+ },
+ })
+
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ // search
+ cy.getTestId('plugin-form-certificate-id').should('be.visible')
+ cy.getTestId('plugin-form-certificate-id').type(plugin1.certificate.id)
+ // click kselect item
+ cy.getTestId(`k-select-item-${plugin1.certificate.id}`).should('be.visible')
+ cy.get(`[data-testid="k-select-item-${plugin1.certificate.id}"] button`).click()
+ cy.getTestId('plugin-form-certificate-id').should('have.value', plugin1.certificate.id)
+ })
+
+ it('should set cert selection as readonly if provided in config - on create', () => {
+
+ cy.mount(PluginForm, {
+ props: {
+ config: {
+ ...baseConfigKM,
+ certificateId: '1234-cats-beat-certs',
+ },
+ },
+ })
+
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ // search
+ cy.getTestId('plugin-form-certificate-id').should('be.visible')
+ cy.getTestId('plugin-form-certificate-id').should('have.attr', 'readonly')
+ })
+
+ it('should not set cert selection as readonly if provided in config - on edit', () => {
+
+ interceptKM()
+
+ cy.mount(PluginForm, {
+ props: {
+ config: {
+ ...baseConfigKM,
+ certificateId: '1234-cats-beat-certs',
+ },
+ pluginId: plugin1.id,
+ },
+ })
+
+ cy.wait('@getPlugin')
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ cy.getTestId('plugin-form-certificate-id').should('be.visible')
+ cy.getTestId('plugin-form-certificate-id').should('not.have.attr', 'readonly')
+ })
+
+ it('should show edit form', () => {
+
+ interceptKM()
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKM,
+ pluginId: plugin1.id,
+ },
+ })
+
+ cy.wait('@getPlugin')
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ // button state
+ cy.getTestId('form-cancel').should('be.visible')
+ cy.getTestId('form-submit').should('be.visible')
+ cy.getTestId('form-cancel').should('be.enabled')
+ cy.getTestId('form-submit').should('be.disabled')
+ // form fields
+ cy.getTestId('plugin-form-name').should('have.value', plugin1.name)
+ plugin1.tags.forEach((tag: string) => {
+ cy.getTestId('plugin-form-tags').invoke('val').then((val: string) => {
+ expect(val).to.contain(tag)
+ })
+ })
+ cy.getTestId('plugin-form-certificate-id').should('have.value', plugin1.certificate.id)
+ })
+
+ it('should correctly handle button state - edit', () => {
+
+ interceptKM()
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKM,
+ pluginId: plugin1.id,
+ },
+ })
+
+ cy.wait('@getPlugin')
+
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ // default button state
+ cy.getTestId('form-cancel').should('be.visible')
+ cy.getTestId('form-submit').should('be.visible')
+ cy.getTestId('form-cancel').should('be.enabled')
+ cy.getTestId('form-submit').should('be.disabled')
+ // enables save when form has changes
+ cy.getTestId('plugin-form-name').type('-edited')
+ cy.getTestId('form-submit').should('be.enabled')
+ // disables save when form changes are undone
+ cy.getTestId('plugin-form-name').clear()
+ cy.getTestId('plugin-form-name').type(plugin1.name)
+ cy.getTestId('form-submit').should('be.disabled')
+ })
+
+ it('should handle error state - failed to load plugin', () => {
+
+ cy.intercept(
+ {
+ method: 'GET',
+ url: `${baseConfigKM.apiBaseUrl}/${baseConfigKM.workspace}/plugins/*`,
+ },
+ {
+ statusCode: 404,
+ body: {},
+ },
+ ).as('getPlugin')
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKM,
+ pluginId: plugin1.id,
+ },
+ })
+
+ cy.wait('@getPlugin')
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ // error state is displayed
+ cy.getTestId('form-fetch-error').should('be.visible')
+ // buttons and form hidden
+ cy.getTestId('form-cancel').should('not.exist')
+ cy.getTestId('form-submit').should('not.exist')
+ cy.get('.kong-ui-entities-plugins-form form').should('not.exist')
+ })
+
+ it('should handle error state - failed to load certs', () => {
+ cy.intercept(
+ {
+ method: 'GET',
+ url: `${baseConfigKM.apiBaseUrl}/${baseConfigKM.workspace}/certificates*`,
+ },
+ {
+ statusCode: 500,
+ body: {},
+ },
+ ).as('getCertificates')
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKM,
+ },
+ })
+
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ cy.getTestId('form-error').should('be.visible')
+ })
+
+ it('should handle error state - invalid cert id', () => {
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKM,
+ },
+ })
+
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ cy.getTestId('plugin-form-certificate-id').type('xxxx')
+ cy.getTestId('no-search-results').should('be.visible')
+ cy.getTestId('invalid-certificate-message').should('exist')
+ })
+
+ it('update event should be emitted when plugin was edited', () => {
+
+ interceptKM()
+ interceptUpdate()
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKM,
+ pluginId: plugin1.id,
+ onUpdate: cy.spy().as('onUpdateSpy'),
+ },
+ }).then(({ wrapper }) => wrapper)
+ .as('vueWrapper')
+
+ cy.wait('@getPlugin')
+ cy.getTestId('plugin-form-tags').clear()
+ cy.getTestId('plugin-form-tags').type('tag1,tag2')
+
+ cy.get('@vueWrapper').then((wrapper: any) => wrapper.findComponent(EntityBaseForm)
+ .vm.$emit('submit'))
+
+ cy.wait('@validatePlugin')
+ cy.wait('@updatePlugin')
+
+ cy.get('@onUpdateSpy').should('have.been.calledOnce')
+ })
+ })
+
+ describe('Konnect', () => {
+ const interceptKonnectCerts = (params?: {
+ mockData?: object
+ alias?: string
+ }) => {
+ cy.intercept(
+ {
+ method: 'GET',
+ url: `${baseConfigKonnect.apiBaseUrl}/api/runtime_groups/${baseConfigKonnect.controlPlaneId}/certificates*`,
+ },
+ {
+ statusCode: 200,
+ body: params?.mockData ?? certificates,
+ },
+ ).as(params?.alias ?? 'getCertificates')
+ }
+
+ const interceptKonnect = (params?: {
+ mockData?: object
+ alias?: string
+ }) => {
+ cy.intercept(
+ {
+ method: 'GET',
+ url: `${baseConfigKonnect.apiBaseUrl}/api/runtime_groups/${baseConfigKonnect.controlPlaneId}/plugins/*`,
+ },
+ {
+ statusCode: 200,
+ body: params?.mockData ?? plugin1,
+ },
+ ).as(params?.alias ?? 'getPlugin')
+ }
+
+ const interceptUpdate = (status = 200): void => {
+ cy.intercept(
+ {
+ method: 'POST',
+ url: `${baseConfigKonnect.apiBaseUrl}/api/runtime_groups/${baseConfigKonnect.controlPlaneId}/v1/schemas/json/plugin/validate`,
+ },
+ {
+ statusCode: status,
+ body: { },
+ },
+ ).as('validatePlugin')
+
+ cy.intercept(
+ {
+ method: 'PUT',
+ url: `${baseConfigKonnect.apiBaseUrl}/api/runtime_groups/${baseConfigKonnect.controlPlaneId}/plugins/*`,
+ },
+ {
+ statusCode: status,
+ body: { ...plugin1, tags: ['tag1', 'tag2'] },
+ },
+ ).as('updatePlugin')
+ }
+
+ it('should show create form', () => {
+ interceptKonnectCerts()
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKonnect,
+ },
+ })
+
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugins-form form').should('be.visible')
+ // button state
+ cy.getTestId('form-cancel').should('be.visible')
+ cy.getTestId('form-submit').should('be.visible')
+ cy.getTestId('form-cancel').should('be.enabled')
+ cy.getTestId('form-submit').should('be.disabled')
+ // form fields
+ cy.getTestId('plugin-form-name').should('be.visible')
+ cy.getTestId('plugin-form-tags').should('be.visible')
+ cy.getTestId('plugin-form-certificate-id').should('be.visible')
+ // certs load in select
+ cy.getTestId('plugin-form-certificate-id').click()
+ cy.get('.k-select-list .k-select-item').should('have.length', certificates.data.length)
+ })
+
+ it('should correctly handle button state - create', () => {
+ interceptKonnectCerts()
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKonnect,
+ },
+ })
+
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ // default button state
+ cy.getTestId('form-cancel').should('be.visible')
+ cy.getTestId('form-submit').should('be.visible')
+ cy.getTestId('form-cancel').should('be.enabled')
+ cy.getTestId('form-submit').should('be.disabled')
+ // enables save when required fields have values
+ cy.getTestId('plugin-form-name').type('tk-meowstersmith')
+ cy.getTestId('plugin-form-certificate-id').click()
+ cy.get(`[data-testid="k-select-item-${certificates.data[0].id}"] button`).click()
+ cy.getTestId('form-submit').should('be.enabled')
+ // disables save when required field is cleared
+ cy.getTestId('plugin-form-name').clear()
+ cy.getTestId('form-submit').should('be.disabled')
+ })
+
+ it('should allow exact match filtering of certs', () => {
+ interceptKonnectCerts()
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKonnect,
+ },
+ })
+
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ // search
+ cy.getTestId('plugin-form-certificate-id').should('be.visible')
+ cy.getTestId('plugin-form-certificate-id').type(plugin1.certificate.id)
+ // click kselect item
+ cy.getTestId(`k-select-item-${plugin1.certificate.id}`).should('be.visible')
+ cy.get(`[data-testid="k-select-item-${plugin1.certificate.id}"] button`).click()
+ cy.getTestId('plugin-form-certificate-id').should('have.value', plugin1.certificate.id)
+ })
+
+ it('should set cert selection as readonly if provided in config - on create', () => {
+ interceptKonnectCerts()
+
+ cy.mount(PluginForm, {
+ props: {
+ config: {
+ ...baseConfigKonnect,
+ certificateId: '1234-cats-beat-certs',
+ },
+ },
+ })
+
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ cy.getTestId('plugin-form-certificate-id').should('be.visible')
+ cy.getTestId('plugin-form-certificate-id').should('have.attr', 'readonly')
+ })
+
+ it('should not set cert selection as readonly if provided in config - on edit', () => {
+ interceptKonnectCerts()
+ interceptKonnect()
+
+ cy.mount(PluginForm, {
+ props: {
+ config: {
+ ...baseConfigKonnect,
+ certificateId: '1234-cats-beat-certs',
+ },
+ pluginId: plugin1.id,
+ },
+ })
+
+ cy.wait('@getPlugin')
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ cy.getTestId('plugin-form-certificate-id').should('be.visible')
+ cy.getTestId('plugin-form-certificate-id').should('not.have.attr', 'readonly')
+ })
+
+ it('should show edit form', () => {
+ interceptKonnectCerts()
+ interceptKonnect()
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKonnect,
+ pluginId: plugin1.id,
+ },
+ })
+
+ cy.wait('@getPlugin')
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ // button state
+ cy.getTestId('form-cancel').should('be.visible')
+ cy.getTestId('form-submit').should('be.visible')
+ cy.getTestId('form-cancel').should('be.enabled')
+ cy.getTestId('form-submit').should('be.disabled')
+ // form fields
+ cy.getTestId('plugin-form-name').should('have.value', plugin1.name)
+ plugin1.tags.forEach((tag: string) => {
+ cy.getTestId('plugin-form-tags').invoke('val').then((val: string) => {
+ expect(val).to.contain(tag)
+ })
+ })
+ cy.getTestId('plugin-form-certificate-id').should('have.value', plugin1.certificate.id)
+ })
+
+ it('should correctly handle button state - edit', () => {
+ interceptKonnectCerts()
+ interceptKonnect()
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKonnect,
+ pluginId: plugin1.id,
+ },
+ })
+
+ cy.wait('@getPlugin')
+
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ // default button state
+ cy.getTestId('form-cancel').should('be.visible')
+ cy.getTestId('form-submit').should('be.visible')
+ cy.getTestId('form-cancel').should('be.enabled')
+ cy.getTestId('form-submit').should('be.disabled')
+ // enables save when form has changes
+ cy.getTestId('plugin-form-name').type('-edited')
+ cy.getTestId('form-submit').should('be.enabled')
+ // disables save when form changes are undone
+ cy.getTestId('plugin-form-name').clear()
+ cy.getTestId('plugin-form-name').type(plugin1.name)
+ cy.getTestId('form-submit').should('be.disabled')
+ })
+
+ it('should handle error state - failed to load plugin', () => {
+ interceptKonnectCerts()
+
+ cy.intercept(
+ {
+ method: 'GET',
+ url: `${baseConfigKonnect.apiBaseUrl}/api/runtime_groups/${baseConfigKonnect.controlPlaneId}/plugins/*`,
+ },
+ {
+ statusCode: 404,
+ body: {},
+ },
+ ).as('getPlugin')
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKonnect,
+ pluginId: plugin1.id,
+ },
+ })
+
+ cy.wait('@getPlugin')
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ // error state is displayed
+ cy.getTestId('form-fetch-error').should('be.visible')
+ // buttons and form hidden
+ cy.getTestId('form-cancel').should('not.exist')
+ cy.getTestId('form-submit').should('not.exist')
+ cy.get('.kong-ui-entities-plugins-form form').should('not.exist')
+ })
+
+ it('should handle error state - failed to load certs', () => {
+ cy.intercept(
+ {
+ method: 'GET',
+ url: `${baseConfigKonnect.apiBaseUrl}/api/runtime_groups/${baseConfigKonnect.controlPlaneId}/certificates*`,
+ },
+ {
+ statusCode: 500,
+ body: {},
+ },
+ ).as('getCertificates')
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKonnect,
+ },
+ })
+
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ cy.getTestId('form-error').should('be.visible')
+ })
+
+ it('should handle error state - invalid cert id', () => {
+ interceptKonnectCerts()
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKonnect,
+ },
+ })
+
+ cy.get('.kong-ui-entities-plugins-form').should('be.visible')
+ cy.getTestId('plugin-form-certificate-id').type('xxxx')
+ cy.getTestId('no-search-results').should('be.visible')
+ cy.getTestId('invalid-certificate-message').should('exist')
+ })
+
+ it('update event should be emitted when plugin was edited', () => {
+ interceptKonnectCerts()
+ interceptKonnect()
+ interceptUpdate()
+
+ cy.mount(PluginForm, {
+ props: {
+ config: baseConfigKonnect,
+ pluginId: plugin1.id,
+ onUpdate: cy.spy().as('onUpdateSpy'),
+ },
+ }).then(({ wrapper }) => wrapper)
+ .as('vueWrapper')
+
+ cy.wait('@getPlugin')
+ cy.getTestId('plugin-form-tags').clear()
+ cy.getTestId('plugin-form-tags').type('tag1,tag2')
+
+ cy.get('@vueWrapper').then((wrapper: any) => wrapper.findComponent(EntityBaseForm)
+ .vm.$emit('submit'))
+
+ cy.wait('@validatePlugin')
+ cy.wait('@updatePlugin')
+
+ cy.get('@onUpdateSpy').should('have.been.calledOnce')
+ })
+ })
+})
diff --git a/packages/entities/entities-plugins/src/components/PluginForm.vue b/packages/entities/entities-plugins/src/components/PluginForm.vue
new file mode 100644
index 0000000000..9fcf18dd77
--- /dev/null
+++ b/packages/entities/entities-plugins/src/components/PluginForm.vue
@@ -0,0 +1,233 @@
+
+
+
+
+
+
+
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index 78b59dfa65..657fb040e5 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -10,27 +10,7 @@
/>
-
-
@@ -46,8 +26,15 @@
{{ t('plugins.select.tabs.kong.description') }}
pluginsList = value"
+ :disabled-plugins="disabledPlugins"
+ :filtered-plugins="filteredPlugins"
+ :ignored-plugins="ignoredPlugins"
+ only-available-plugins
+ @loading="(val: boolean) => $emit('loading', val)"
+ @plugin-clicked="(val: PluginType) => $emit('plugin-clicked', val)"
+ @plugin-list-updated="(val: PluginCardList) => pluginsList = val"
/>
@@ -57,10 +44,10 @@
{{ t('plugins.select.tabs.custom.description') }}
-
+
+
@@ -103,8 +90,16 @@
diff --git a/packages/entities/entities-plugins/src/constants.ts b/packages/entities/entities-plugins/src/constants.ts
new file mode 100644
index 0000000000..5a86f4f285
--- /dev/null
+++ b/packages/entities/entities-plugins/src/constants.ts
@@ -0,0 +1 @@
+export const PLUGINS_PER_ROW = 3
diff --git a/packages/entities/entities-plugins/src/index.ts b/packages/entities/entities-plugins/src/index.ts
index c7b5a5df64..cb0f154817 100644
--- a/packages/entities/entities-plugins/src/index.ts
+++ b/packages/entities/entities-plugins/src/index.ts
@@ -1,6 +1,7 @@
import PluginIcon from './components/PluginIcon.vue'
import PluginList from './components/PluginList.vue'
-import PluginForm from './components/PluginForm.vue'
+import PluginSelect from './components/PluginSelect.vue'
+import PluginSelectGrid from './components/PluginSelectGrid.vue'
import PluginConfigCard from './components/PluginConfigCard.vue'
import composables from './composables'
@@ -9,7 +10,8 @@ const { getPluginIconURL, usePluginMetaData } = composables
export {
PluginIcon,
PluginList,
- PluginForm,
+ PluginSelect,
+ PluginSelectGrid,
PluginConfigCard,
getPluginIconURL,
usePluginMetaData,
diff --git a/packages/entities/entities-plugins/src/locales/en.json b/packages/entities/entities-plugins/src/locales/en.json
index bbf00f2389..f3b4d0e204 100644
--- a/packages/entities/entities-plugins/src/locales/en.json
+++ b/packages/entities/entities-plugins/src/locales/en.json
@@ -401,6 +401,8 @@
"custom_badge_text": "Data Plane Node Specific",
"unavailable_tooltip": "This plugin is not available",
"misc_plugins": "Other Plugins",
+ "view_more": "View {count} more",
+ "view_less": "View less",
"tabs": {
"kong": {
"title": "Kong Plugins",
diff --git a/packages/entities/entities-plugins/src/plugins-endpoints.ts b/packages/entities/entities-plugins/src/plugins-endpoints.ts
index b495a80148..c6fcfb1d44 100644
--- a/packages/entities/entities-plugins/src/plugins-endpoints.ts
+++ b/packages/entities/entities-plugins/src/plugins-endpoints.ts
@@ -9,6 +9,14 @@ export default {
forEntity: '/{workspace}/{entityType}/{entityId}/plugins',
},
},
+ select: {
+ konnect: {
+ availablePlugins: '/api/runtime_groups/{controlPlaneId}/v1/available-plugins',
+ },
+ kongManager: {
+ availablePlugins: '/{workspace}/kong',
+ },
+ },
form: {
konnect: {
create: '/api/runtime_groups/{controlPlaneId}/plugins',
diff --git a/packages/entities/entities-plugins/src/types/plugin-form.ts b/packages/entities/entities-plugins/src/types/plugin-form.ts
index eb4beb2be4..6d9c4ee3c4 100644
--- a/packages/entities/entities-plugins/src/types/plugin-form.ts
+++ b/packages/entities/entities-plugins/src/types/plugin-form.ts
@@ -2,10 +2,10 @@ import type { RouteLocationRaw } from 'vue-router'
import type { KonnectBaseFormConfig, KongManagerBaseFormConfig } from '@kong-ui-public/entities-shared'
export interface BasePluginFormConfig {
- /** Route for creating a plugin */
- createRoute: RouteLocationRaw
- /** Consumer to bind the Plugin to on creation if Consumer Credential */
- consumerId?: string
+ /** A function that returns the route for creating a plugin */
+ getCreateRoute: (id: string) => RouteLocationRaw
+ /** Entity to bind the Plugin to on creation */
+ entityId?: string
}
/** Konnect Plugin form config */
@@ -22,7 +22,7 @@ export interface KongManagerPluginFormConfig extends BasePluginFormConfig, KongM
export interface PluginFormFields {
name: string
tags: string
- consumer_id: string
+ entity_id: string
}
export interface PluginFormState {
diff --git a/packages/entities/entities-plugins/src/types/plugin.ts b/packages/entities/entities-plugins/src/types/plugin.ts
index f834c4e7b4..992cb36196 100644
--- a/packages/entities/entities-plugins/src/types/plugin.ts
+++ b/packages/entities/entities-plugins/src/types/plugin.ts
@@ -11,6 +11,19 @@ export enum PluginGroup {
CUSTOM_PLUGINS = 'Custom Plugins',
}
+export const PluginGroupArray = [
+ PluginGroup.AUTHENTICATION,
+ PluginGroup.SECURITY,
+ PluginGroup.TRAFFIC_CONTROL,
+ PluginGroup.SERVERLESS,
+ PluginGroup.ANALYTICS_AND_MONITORING,
+ PluginGroup.TRANSFORMATIONS,
+ PluginGroup.LOGGING,
+ PluginGroup.DEPLOYMENT,
+ PluginGroup.WEBSOCKET,
+ PluginGroup.CUSTOM_PLUGINS,
+]
+
export const PLUGIN_GROUPS_COLLAPSE_STATUS = {
AUTHENTICATION: true,
SECURITY: true,
@@ -46,3 +59,15 @@ export interface PluginType extends PluginMetaData {
disabledMessage?: string // An optional field for plugin's disabled message.
id: string // the plugin schema name
}
+
+export type DisabledPlugin = {
+ [key: string]: string // [plugin.id]: plugin.disabledMessage
+}
+
+export type PluginCardList = {
+ [key in PluginGroup]?: PluginType[]
+}
+
+export type TriggerLabels = {
+ [key in PluginGroup]?: string // [plugin.group]: label
+}
diff --git a/packages/entities/entities-shared/src/composables/useHelpers.ts b/packages/entities/entities-shared/src/composables/useHelpers.ts
index ebfd40a499..8979a00552 100644
--- a/packages/entities/entities-shared/src/composables/useHelpers.ts
+++ b/packages/entities/entities-shared/src/composables/useHelpers.ts
@@ -10,7 +10,47 @@ export default function useHelpers() {
return slotProps?.[propName] ?? undefined
}
+ /**
+ * Check if 2 objects are equal
+ * @param {Object} a first object to compare
+ * @param {Object} b second object to compare
+ * @returns {Boolean} whether or not the objects are equal
+ */
+ const objectsAreEqual = (a: Record, b: Record) => {
+ try {
+ return JSON.stringify(a) === JSON.stringify(b)
+ } catch (e) {
+ return false
+ }
+ }
+
+ /**
+ * A comparator function that given a key, compares object values with that key, and returns the results of
+ * localCompare on those values (see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare for reference)
+ * Also checks for undefined, nulls and sub-Arrays.
+ * @param {String} property the key to sort on
+ * @returns {Function} a comparator function
+ */
+ const sortAlpha = (property: string) => {
+ return (a: Record, b: Record) => {
+ let propertyA = a[property] === undefined || a[property] === null ? '' : a[property]
+ let propertyB = b[property] === undefined || b[property] === null ? '' : b[property]
+
+ if (Array.isArray(a[property])) {
+ propertyA = a[property][0]
+ }
+
+ if (Array.isArray(b[property])) {
+ propertyB = b[property][0]
+ }
+
+ return propertyA.localeCompare(propertyB)
+ }
+ }
+
return {
getPropValue,
+ objectsAreEqual,
+ sortAlpha,
}
}
From b83ae969bed45f7f57c0666c82c0745759219b7b Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Thu, 26 Oct 2023 17:11:17 -0400
Subject: [PATCH 04/40] fix(*): ts & lint errors
---
.../DeleteCustomPluginSchemaModal.vue | 18 +-
.../src/components/PluginCardSkeleton.vue | 2 +-
.../src/components/PluginForm.cy.ts | 632 ------------------
.../src/components/PluginForm.vue | 27 +-
.../src/components/PluginSelect.vue | 10 +-
.../src/components/PluginSelectGrid.vue | 166 +++--
6 files changed, 126 insertions(+), 729 deletions(-)
delete mode 100644 packages/entities/entities-plugins/src/components/PluginForm.cy.ts
diff --git a/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue b/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue
index 2bb7e4c824..e6e93b97ee 100644
--- a/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue
+++ b/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue
@@ -92,6 +92,7 @@
From af36bdd88f2662b61eeeab92643f15da9057e399 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Thu, 26 Oct 2023 17:43:00 -0400
Subject: [PATCH 05/40] fix(*): custom drop 1
---
.../DeleteCustomPluginSchemaModal.vue | 75 ++++++++++++-------
.../src/components/PluginCustomGrid.vue | 11 ++-
.../src/components/PluginForm.vue | 4 +-
.../entities-plugins/src/locales/en.json | 15 +++-
4 files changed, 71 insertions(+), 34 deletions(-)
diff --git a/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue b/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue
index e6e93b97ee..11702463fd 100644
--- a/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue
+++ b/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue
@@ -7,7 +7,8 @@
@canceled="handleCancel"
>
-
+
+
+
+
+
@@ -34,13 +37,27 @@
@submit.prevent="handleSubmit"
>
- {{ helpText.confirmModalText1 }}
+ {{ t('delete.confirmModalText1') }}
{{ plugin?.name }}?
-
{{ helpText.confirmModalText2 }}
-
+
{{ t('delete.confirmModalText2') }}
+
+
+
+
- {{ helpText.cancel }}
+ {{ t('actions.cancel') }}
- {{ helpText.goToPlugins }}
+ {{ t('actions.go_to_plugins') }}
- {{ helpText.confirm }}
+ {{ t('actions.confirm_delete') }}
@@ -108,10 +125,11 @@ const { getMessageFromError } = useErrors()
const title = computed((): string => {
return isPluginSchemaInUse.value
- ? t('configuration.plugins.list.deleteCustomPlugin.pluginSchemaInUseTitle', { name: props.plugin?.name })
- : `${t('actions.delete')} ${props.plugin?.name || 'plugin'}`
+ ? t('delete.plugin_schema_in_use_title')
+ : t('delete.title', { name: props.plugin?.name || t('delete.custom_plugin') })
})
+const isLoading = ref(false)
const errorMessage = ref('')
const customPluginNameFormValue = ref('')
const isPluginSchemaInUse = ref(false)
@@ -121,6 +139,9 @@ const handleCancel = (): void => {
}
const handleSubmit = async (): Promise
=> {
+ isLoading.value = true
+ errorMessage.value = ''
+
if (!props.plugin?.id) {
return
}
@@ -131,21 +152,25 @@ const handleSubmit = async (): Promise => {
emit('proceed')
+ // TODO:
/* notify({
appearance: 'success',
- message: i18n.t('configuration.plugins.list.deleteCustomPlugin.successMessage', { name: props.plugin?.name }),
+ message: t('delete.success_message', { name: props.plugin?.name || t('glossary.plugin') }),
}) */
- } catch (err) {
+ } catch (err: any) {
+ // TODO: refactor
+ const { response } = err
if (
- err.response?.status === 400 &&
- err.response.data?.message &&
- err.response.data.message.includes('plugin schema is currently in use')
+ response?.status === 400 &&
+ response.data?.message &&
+ response.data.message.includes('plugin schema is currently in use')
) {
isPluginSchemaInUse.value = true
-
} else {
errorMessage.value = getMessageFromError(err)
}
+ } finally {
+ isLoading.value = false
}
}
diff --git a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue b/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
index 03d6ed9396..67a6b214ac 100644
--- a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
+++ b/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
@@ -1,12 +1,11 @@
-
-
+ /> -->
-
diff --git a/packages/entities/entities-plugins/src/components/PluginForm.vue b/packages/entities/entities-plugins/src/components/PluginForm.vue
index 6d59a95874..6aa94c4876 100644
--- a/packages/entities/entities-plugins/src/components/PluginForm.vue
+++ b/packages/entities/entities-plugins/src/components/PluginForm.vue
@@ -142,7 +142,9 @@ const initForm = (data: Record): void => {
}
const handleClickCancel = (): void => {
- router.push(props.config.cancelRoute)
+ if (props.config.cancelRoute) {
+ router.push(props.config.cancelRoute)
+ }
}
/* ---------------
diff --git a/packages/entities/entities-plugins/src/locales/en.json b/packages/entities/entities-plugins/src/locales/en.json
index f3b4d0e204..bb2ed6a7ec 100644
--- a/packages/entities/entities-plugins/src/locales/en.json
+++ b/packages/entities/entities-plugins/src/locales/en.json
@@ -1,5 +1,6 @@
{
"actions": {
+ "cancel": "Cancel",
"create": "New Plugin",
"create_custom": "Create",
"copy_id": "Copy ID",
@@ -7,12 +8,22 @@
"edit": "Edit",
"enable": "Enable",
"delete": "Delete",
+ "confirm_delete": "Yes, delete",
"view": "View Details",
- "configure_dynamic_ordering": "Configure Dynamic Ordering"
+ "configure_dynamic_ordering": "Configure Dynamic Ordering",
+ "go_to_plugins": "Go to Plugins"
},
"delete": {
"title": "Delete a Plugin",
- "description": "Are you sure you want to delete this plugin? This action cannot be reversed."
+ "custom_title": "Delete {name}",
+ "custom_plugin": "Custom Plugin",
+ "description": "Are you sure you want to delete this plugin? This action cannot be reversed.",
+ "plugin_schema_in_use_title": "Unable to Delete Custom Plugin",
+ "plugin_schema_in_use_message": "The custom plugin schema {name} is being used in this control plane, please delete any existing instances before you delete this schema.",
+ "confirmModalText1": "Are you sure you want to delete",
+ "confirmModalText2": "Please ensure this plugin is not in use. This delete action cannot be reversed.",
+ "confirm_text": "Type {name} to confirm your action",
+ "success_message": "Successfully deleted custom plugin {name}"
},
"copy": {
"success": "Copied {val} to clipboard",
From 7d874ca3b17411dee0020fe052b745f8f853132f Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Thu, 26 Oct 2023 17:46:38 -0400
Subject: [PATCH 06/40] fix(*): stylelint
---
.../entities-plugins/src/components/PluginCustomGrid.vue | 4 +++-
.../entities-plugins/src/components/PluginSelectCard.vue | 4 ++--
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue b/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
index 67a6b214ac..a27b3b2d9b 100644
--- a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
+++ b/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
@@ -93,5 +93,7 @@ const shouldCollapsedCustomPlugins = ref(true) */
diff --git a/packages/entities/entities-plugins/src/components/PluginSelectCard.vue b/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
index 4c628a6852..b1e32a6bef 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
@@ -210,9 +210,9 @@ const handleCustomClick = (): void => {
cursor: pointer;
}
- .header-wrapper {
+ .header-wrapper { // TODO: had to change from 2rem, does it work?
// maintain the specified height if slot has no content
- min-height: 2rem
+ min-height: 25px;
}
&-title {
From 4c77b7c63c803be87ac888725dc298712f958648 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Thu, 26 Oct 2023 18:07:21 -0400
Subject: [PATCH 07/40] fix(*): bump distSizeChecker limit
---
packages/entities/entities-plugins/package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/entities/entities-plugins/package.json b/packages/entities/entities-plugins/package.json
index 798a7de3c9..9a61c96de8 100644
--- a/packages/entities/entities-plugins/package.json
+++ b/packages/entities/entities-plugins/package.json
@@ -64,7 +64,7 @@
"extends": "../../../package.json"
},
"distSizeChecker": {
- "errorLimit": "1MB"
+ "errorLimit": "1.6MB"
},
"dependencies": {
"@kong-ui-public/copy-uuid": "workspace:^",
From 408c98840bde0547bdbe4f54adf75897c75980c3 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Fri, 27 Oct 2023 10:53:49 -0400
Subject: [PATCH 08/40] fix(*): another drop
---
.../src/components/PluginSelect.vue | 26 +++++++++++++++++--
.../src/components/PluginSelectGrid.vue | 21 ++-------------
.../entities-plugins/src/types/plugin-form.ts | 4 +--
3 files changed, 28 insertions(+), 23 deletions(-)
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index 96e4ca04ee..f77e7df1ec 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -14,6 +14,7 @@
aria-live="polite"
class="plugins-results-container"
>
+
$emit('loading', val)"
@plugin-clicked="(val: PluginType) => $emit('plugin-clicked', val)"
@@ -44,7 +46,12 @@
{{ t('plugins.select.tabs.custom.description') }}
-
+
+
+
+ $emit('loading', val)"
+ @plugin-clicked="(val: PluginType) => $emit('plugin-clicked', val)"
+ @plugin-list-updated="(val: PluginCardList) => pluginsList = val"
+ />
{
const customPlugins = filteredPlugins.value?.[PluginGroup.CUSTOM_PLUGINS] || []
// ADD CUSTOM_PLUGIN_CREATE as the first card if allowed creation
- return userCanCreate.value && !props.noRouteChange
+ return userCanCreate.value && !props.noRouteChange && props.config.createCustomRoute
? [{
id: 'custom-plugin-create',
name: t('plugins.select.tabs.custom.create.name'),
diff --git a/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue b/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
index d584ce45a6..baefc61b2e 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
@@ -179,25 +179,8 @@ const emitPluginData = (plugin: PluginType) => {
}
// used for scoped plugins
-// entityType is currently determined off of the route query or params
-const entityType = computed((): string => {
- const entity = String(route.query.entity_type || '')
-
- if (entity) {
- return entity
- } else if (route.params.gateway_service) {
- return 'service_id'
- } else if (route.params.route) {
- return 'route_id'
- } else if (route.params.consumer) {
- return 'consumer_id'
- } else if (route.params.consumer_group) {
- return 'consumer_group_id'
- }
-
- // global
- return ''
-})
+// entityType is currently determined off of the route query
+const entityType = computed((): string => String(route.query.entity_type || ''))
// remove custom plugin from original filteredPlugins
const nonCustomPlugins = computed((): PluginCardList => {
diff --git a/packages/entities/entities-plugins/src/types/plugin-form.ts b/packages/entities/entities-plugins/src/types/plugin-form.ts
index 6d9c4ee3c4..c5637b5465 100644
--- a/packages/entities/entities-plugins/src/types/plugin-form.ts
+++ b/packages/entities/entities-plugins/src/types/plugin-form.ts
@@ -11,9 +11,9 @@ export interface BasePluginFormConfig {
/** Konnect Plugin form config */
export interface KonnectPluginFormConfig extends BasePluginFormConfig, KonnectBaseFormConfig {
/** Route for creating a custom plugin */
- createCustomRoute: RouteLocationRaw
+ createCustomRoute?: RouteLocationRaw
/** A function that returns the route for editing a custom plugin */
- getCustomEditRoute: (id: string) => RouteLocationRaw
+ getCustomEditRoute?: (id: string) => RouteLocationRaw
}
/** Kong Manager Plugin form config */
From 025f7499173849f0fed0f56fce333a1264a50e71 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Fri, 27 Oct 2023 11:14:56 -0400
Subject: [PATCH 09/40] fix(*): onlyAvailablePlugins
---
.../src/components/PluginSelect.vue | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index f77e7df1ec..b19c92951d 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -33,7 +33,7 @@
:filtered-plugins="filteredPlugins"
:ignored-plugins="ignoredPlugins"
:no-route-change="noRouteChange"
- only-available-plugins
+ :only-available-plugins="onlyAvailablePlugins"
@loading="(val: boolean) => $emit('loading', val)"
@plugin-clicked="(val: PluginType) => $emit('plugin-clicked', val)"
@plugin-list-updated="(val: PluginCardList) => pluginsList = val"
@@ -87,7 +87,7 @@
:filtered-plugins="filteredPlugins"
:ignored-plugins="ignoredPlugins"
:no-route-change="noRouteChange"
- only-available-plugins
+ :only-available-plugins="onlyAvailablePlugins"
@loading="(val: boolean) => $emit('loading', val)"
@plugin-clicked="(val: PluginType) => $emit('plugin-clicked', val)"
@plugin-list-updated="(val: PluginCardList) => pluginsList = val"
@@ -157,6 +157,15 @@ const props = defineProps({
type: Boolean,
default: false,
},
+ /**
+ * @param {boolean} onlyAvailablePlugins checks kong config plugins.available_on_server and if
+ * onlyAvailablePlugins = true, then it will not show plugins from PluginMeta that are outside
+ * of the available_on_server array.
+ */
+ onlyAvailablePlugins: {
+ type: Boolean,
+ default: false,
+ },
ignoredPlugins: {
type: Array as PropType,
default: () => [],
From 39e3fbf1125f112addb744b0c9710c5d5a1ac8f7 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Fri, 27 Oct 2023 11:19:41 -0400
Subject: [PATCH 10/40] fix(*): example updates
---
.../sandbox/pages/PluginSelectPage.vue | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue b/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue
index 81ff226baa..685d8c0623 100644
--- a/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue
+++ b/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue
@@ -12,9 +12,11 @@
From bfdb75fa2808dbd446b7293b4da8e4c8de6e25fd Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Fri, 27 Oct 2023 11:32:40 -0400
Subject: [PATCH 11/40] fix(plugins): build error
---
.../entities-plugins/src/components/PluginSelectCard.vue | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/entities/entities-plugins/src/components/PluginSelectCard.vue b/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
index b1e32a6bef..05545a7f32 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
@@ -183,17 +183,17 @@ const handleCustomDelete = (): void => {
}
const handleCustomEdit = (pluginName: string): void => {
- if (props.config.app === 'konnect') {
+ if (props.config.app === 'konnect' && props.config.getCustomEditRoute) {
router.push(props.config.getCustomEditRoute(pluginName))
}
}
-// TODO: shouldn't this be router.push?
const handleCustomClick = (): void => {
+ // TODO: verify
// handle custom plugin card click only
// regular plugin card render as 'router-link' component which don't need this
- if (isCustomPlugin.value) {
- emit('custom-plugin-create')
+ if (props.config.app === 'konnect' && props.config.createCustomRoute && isCustomPlugin.value) {
+ router.push(props.config.createCustomRoute)
}
}
From 02a08ec9c7c140192fce698bc66746cff80bf2c3 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Fri, 27 Oct 2023 11:37:06 -0400
Subject: [PATCH 12/40] fix(*): cleanup
---
.../entities-plugins/src/components/PluginSelectCard.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/entities/entities-plugins/src/components/PluginSelectCard.vue b/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
index 05545a7f32..db3e5a346f 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
@@ -166,7 +166,7 @@ const handleCreateClick = (pluginName: string): void => {
router.push(props.config.getCreateRoute(pluginName))
}
-const emitPluginData = () => {
+const emitPluginData = (): void => {
emit('plugin-clicked', props.plugin)
}
From 99bfff1dab2dde2b04690ab2bae7b3281668306b Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Fri, 27 Oct 2023 12:56:11 -0400
Subject: [PATCH 13/40] fix(*): display logic
---
.../src/components/PluginSelectGrid.vue | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue b/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
index baefc61b2e..8caf6eb4b4 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
@@ -197,7 +197,7 @@ const buildPluginList = (): PluginCardList => {
// either grab all plugins from metadata file or use list of available plugins provided by API
return [...new Set(
Object.assign(
- Object.keys({ ...(!props.onlyAvailablePlugins && pluginMetaData) }),
+ Object.keys({ ...(!props.onlyAvailablePlugins ? pluginMetaData : {}) }),
availablePlugins.value,
),
)]
@@ -269,6 +269,8 @@ const buildPluginList = (): PluginCardList => {
plugins.push(plugin)
plugins.sort(sortAlpha('name'))
+ list[groupName] = plugins
+
return list
}, {})
}
@@ -294,11 +296,11 @@ const shouldCollapsed = ref>(PLUGIN_GROUPS_COLLAPSE_STAT
// text for plugin group "view x more" label
const triggerLabels = computed(() => {
return Object.keys(pluginsList.value).reduce((acc: TriggerLabels, pluginGroup: string): TriggerLabels => {
- const totalCount = getPluginCards(pluginGroup, 'all').length
- const hiddenCount = getPluginCards(pluginGroup, 'hidden').length
+ const totalCount = getPluginCards(pluginGroup, 'all')?.length || 0
+ const hiddenCount = getPluginCards(pluginGroup, 'hidden')?.length || 0
if (totalCount > PLUGINS_PER_ROW) {
- acc[pluginGroup as keyof TriggerLabels] = t('plugins.select.view_more', { hiddenCount })
+ acc[pluginGroup as keyof TriggerLabels] = t('plugins.select.view_more', { count: hiddenCount })
}
return acc
From 3c125a291300c71e7224a59d3ebd33df45b96cb3 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Fri, 27 Oct 2023 12:58:24 -0400
Subject: [PATCH 14/40] fix(*): fetch condition
---
.../src/components/PluginSelectGrid.vue | 32 ++++++++-----------
1 file changed, 13 insertions(+), 19 deletions(-)
diff --git a/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue b/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
index 8caf6eb4b4..264ac7b815 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
@@ -344,28 +344,22 @@ onMounted(async () => {
isLoading.value = true
emit('loading', isLoading.value)
- // only fetch available plugins if needed
- if (props.onlyAvailablePlugins) {
- try {
- const res = await axiosInstance.get(availablePluginsUrl.value)
-
- // TODO: endpoints temporarily return different formats
- if (props.config.app === 'konnect') {
- const { names: available } = res.data
- availablePlugins.value = available || []
- } else if (props.config.app === 'kongManager') {
- const { plugins: { available_on_server: aPlugins } } = res.data
- availablePlugins.value = aPlugins ? Object.keys(aPlugins) : []
- }
-
- pluginsList.value = buildPluginList()
- emit('plugin-list-updated', pluginsList.value)
- } catch (error: any) {
- fetchErrorMessage.value = getMessageFromError(error)
+ try {
+ const res = await axiosInstance.get(availablePluginsUrl.value)
+
+ // TODO: endpoints temporarily return different formats
+ if (props.config.app === 'konnect') {
+ const { names: available } = res.data
+ availablePlugins.value = available || []
+ } else if (props.config.app === 'kongManager') {
+ const { plugins: { available_on_server: aPlugins } } = res.data
+ availablePlugins.value = aPlugins ? Object.keys(aPlugins) : []
}
- } else {
+
pluginsList.value = buildPluginList()
emit('plugin-list-updated', pluginsList.value)
+ } catch (error: any) {
+ fetchErrorMessage.value = getMessageFromError(error)
}
isLoading.value = false
From 2bd4f355a9e0b852f793dfdbf59f085c97e63589 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Fri, 27 Oct 2023 13:23:54 -0400
Subject: [PATCH 15/40] fix(*): customize visible plugins
---
.../src/components/PluginCustomGrid.vue | 2 +-
.../src/components/PluginSelect.vue | 9 +++++++++
.../src/components/PluginSelectGrid.vue | 17 +++++++++++------
.../entities/entities-plugins/src/constants.ts | 1 -
4 files changed, 21 insertions(+), 8 deletions(-)
delete mode 100644 packages/entities/entities-plugins/src/constants.ts
diff --git a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue b/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
index a27b3b2d9b..150223b635 100644
--- a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
+++ b/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
@@ -7,7 +7,7 @@
:trigger-label="shouldCollapsedCustomPlugins ? triggerLabels[PLUGIN_GROUPS.CUSTOM_PLUGINS] : pluginHelpText.viewLess"
>
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index b19c92951d..43c2034ae3 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -34,6 +34,7 @@
:ignored-plugins="ignoredPlugins"
:no-route-change="noRouteChange"
:only-available-plugins="onlyAvailablePlugins"
+ :plugins-per-row="pluginsPerRow"
@loading="(val: boolean) => $emit('loading', val)"
@plugin-clicked="(val: PluginType) => $emit('plugin-clicked', val)"
@plugin-list-updated="(val: PluginCardList) => pluginsList = val"
@@ -88,6 +89,7 @@
:ignored-plugins="ignoredPlugins"
:no-route-change="noRouteChange"
:only-available-plugins="onlyAvailablePlugins"
+ :plugins-per-row="pluginsPerRow"
@loading="(val: boolean) => $emit('loading', val)"
@plugin-clicked="(val: PluginType) => $emit('plugin-clicked', val)"
@plugin-list-updated="(val: PluginCardList) => pluginsList = val"
@@ -174,6 +176,13 @@ const props = defineProps({
type: Object as PropType,
default: () => ({}),
},
+ /**
+ * Number of plugins to always have visible (never will be collapsed)
+ */
+ pluginsPerRow: {
+ type: Number,
+ default: 4,
+ },
})
defineEmits<{
diff --git a/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue b/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
index 264ac7b815..8881f8ea25 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
@@ -34,7 +34,6 @@
@@ -95,7 +94,6 @@ import {
type TriggerLabels,
} from '../types'
import { useAxios, useHelpers, useErrors } from '@kong-ui-public/entities-shared'
-import { PLUGINS_PER_ROW } from '../constants'
import composables from '../composables'
import endpoints from '../plugins-endpoints'
import PluginCardSkeleton from './PluginCardSkeleton.vue'
@@ -151,6 +149,13 @@ const props = defineProps({
type: Boolean,
default: false,
},
+ /**
+ * Number of plugins to always have visible (never will be collapsed)
+ */
+ pluginsPerRow: {
+ type: Number,
+ default: 4,
+ },
})
const emit = defineEmits<{
@@ -281,10 +286,10 @@ const getPluginCards = (group: string, type: 'all' | 'visible' | 'hidden') => {
if (type === 'all') {
return plugins
} else if (type === 'visible') {
- return plugins.slice(0, PLUGINS_PER_ROW)
+ return plugins.slice(0, props.pluginsPerRow)
}
- return plugins.slice(PLUGINS_PER_ROW)
+ return plugins.slice(props.pluginsPerRow)
}
const getGroupPluginCount = (group: string) => {
@@ -299,7 +304,7 @@ const triggerLabels = computed(() => {
const totalCount = getPluginCards(pluginGroup, 'all')?.length || 0
const hiddenCount = getPluginCards(pluginGroup, 'hidden')?.length || 0
- if (totalCount > PLUGINS_PER_ROW) {
+ if (totalCount > props.pluginsPerRow) {
acc[pluginGroup as keyof TriggerLabels] = t('plugins.select.view_more', { count: hiddenCount })
}
diff --git a/packages/entities/entities-plugins/src/constants.ts b/packages/entities/entities-plugins/src/constants.ts
deleted file mode 100644
index 5a86f4f285..0000000000
--- a/packages/entities/entities-plugins/src/constants.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const PLUGINS_PER_ROW = 3
From 77f984ca2b5c15e00d45bac3f9cbf1dfd87bbfa6 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Fri, 27 Oct 2023 17:32:04 -0400
Subject: [PATCH 16/40] fix(*): custom plugin support
---
.../entities-plugins/sandbox/index.ts | 10 +
.../sandbox/pages/PluginSelectPage.vue | 8 +
.../DeleteCustomPluginSchemaModal.vue | 2 +-
.../src/components/PluginCustomGrid.vue | 311 ++++++++++++++----
.../src/components/PluginSelect.vue | 309 ++++++++++++-----
.../src/components/PluginSelectCard.vue | 81 ++---
.../src/components/PluginSelectGrid.vue | 265 ++-------------
.../entities-plugins/src/locales/en.json | 3 +-
8 files changed, 563 insertions(+), 426 deletions(-)
diff --git a/packages/entities/entities-plugins/sandbox/index.ts b/packages/entities/entities-plugins/sandbox/index.ts
index cfd1e30b1a..7b1545a7e7 100644
--- a/packages/entities/entities-plugins/sandbox/index.ts
+++ b/packages/entities/entities-plugins/sandbox/index.ts
@@ -25,12 +25,22 @@ const init = async () => {
name: 'create-plugin',
component: () => import('./pages/FallbackPage.vue'),
},
+ {
+ path: '/custom-plugin/create',
+ name: 'create-custom-plugin',
+ component: () => import('./pages/FallbackPage.vue'),
+ },
{
path: '/plugin/:plugin/:id',
name: 'view-plugin',
component: () => import('./pages/PluginConfigCardPage.vue'),
props: true,
},
+ {
+ path: '/custom-plugin/:plugin/edit',
+ name: 'edit-custom-plugin',
+ component: () => import('./pages/FallbackPage.vue'),
+ },
{
path: '/plugin/:id/edit',
name: 'edit-plugin',
diff --git a/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue b/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue
index 685d8c0623..7567f6616b 100644
--- a/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue
+++ b/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue
@@ -40,6 +40,14 @@ const konnectConfig = ref({
// entity_type: 'consumer_id',
},
}),
+ createCustomRoute: { name: 'create-custom-plugin' },
+ getCustomEditRoute: (plugin: string) => ({
+ name: 'edit-custom-plugin',
+ params: {
+ control_plane_id: controlPlaneId.value,
+ plugin,
+ },
+ }),
})
const kongManagerConfig = ref({
diff --git a/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue b/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue
index 11702463fd..cd20fa4278 100644
--- a/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue
+++ b/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue
@@ -113,7 +113,7 @@ import { useErrors } from '@kong-ui-public/entities-shared'
const props = defineProps({
plugin: {
- type: Object as PropType<{name: string, id: string}>,
+ type: Object as PropType<{ name: string, id: string }>,
required: true,
},
})
diff --git a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue b/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
index 150223b635..3bc148cb3e 100644
--- a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
+++ b/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
@@ -1,99 +1,274 @@
-
-
+
+
+ {{ t('plugins.select.tabs.custom.empty_title') }}
+
+
+
+
+
+ {{ t('plugins.select.tabs.custom.empty_description') }}
+
+
+
+
+
+
-
+
-
-
-
- {{ plugin.disabledMessage }}
-
-
-
-
+ :can-delete-custom="canDeleteCustom"
+ :can-edit-custom="canEditCustom"
+ :config="config"
+ :no-route-change="noRouteChange"
+ :plugin="plugin"
+ @custom-plugin-delete="() => handleCustomPluginDelete(plugin)"
+ @plugin-clicked="emitPluginData"
+ />
-
-
-
- {{ plugin.disabledMessage }}
-
-
-
-
+ :can-delete-custom="canDeleteCustom"
+ :can-edit-custom="canEditCustom"
+ :config="config"
+ :no-route-change="noRouteChange"
+ :plugin="plugin"
+ @plugin-clicked="emitPluginData"
+ />
-->
+ @proceed="() => handleClose(true)"
+ />
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index 43c2034ae3..3f97cdc57c 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -10,7 +10,49 @@
/>
+
+
+
+
+ {{ fetchErrorMessage }}
+
+
+
+
+
+ {{ t('search.no_results', { filter }) }}
+
+
+
@@ -27,17 +69,11 @@
{{ t('plugins.select.tabs.kong.description') }}
$emit('loading', val)"
@plugin-clicked="(val: PluginType) => $emit('plugin-clicked', val)"
- @plugin-list-updated="(val: PluginCardList) => pluginsList = val"
/>
@@ -48,33 +84,16 @@
$emit('plugin-clicked', val)"
+ @revalidate="() => pluginsList = buildPluginList()"
/>
-
-
-
-
-
-
- {{ t('plugins.select.tabs.custom.empty_title') }}
-
-
-
-
-
- {{ t('plugins.select.tabs.custom.empty_description') }}
-
-
-
@@ -82,47 +101,32 @@
$emit('loading', val)"
@plugin-clicked="(val: PluginType) => $emit('plugin-clicked', val)"
- @plugin-list-updated="(val: PluginCardList) => pluginsList = val"
/>
-
-
-
-
- {{ t('search.no_results', { filter }) }}
- {{ t('errors.load_results') }}
-
-
-
diff --git a/packages/entities/entities-plugins/src/components/PluginSelectCard.vue b/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
index db3e5a346f..8867a03593 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
@@ -6,13 +6,13 @@
}"
:label="plugin.disabledMessage"
position-fixed
- @click="noRouteChange ? emitPluginData() : handleCreateClick(plugin.id)"
>
@@ -51,24 +50,22 @@
-
-
- {{ t('actions.edit') }}
-
-
-
-
- {{ t('actions.delete') }}
-
-
+
+ {{ t('actions.edit') }}
+
+
+ {{ t('actions.delete') }}
+
@@ -116,12 +113,10 @@ import { useRouter } from 'vue-router'
import { PluginGroup, type KongManagerPluginFormConfig, type KonnectPluginFormConfig, type PluginType } from '../types'
import { KUI_ICON_SIZE_30, KUI_COLOR_TEXT_NEUTRAL_STRONGER } from '@kong/design-tokens'
import composables from '../composables'
-import { PermissionsWrapper } from '@kong-ui-public/entities-shared'
import PluginIcon from './PluginIcon.vue'
const emit = defineEmits<{
(e: 'plugin-clicked', plugin: PluginType) : void,
- (e: 'custom-plugin-create'): void,
(e: 'custom-plugin-delete'): void,
}>()
@@ -136,17 +131,19 @@ const props = defineProps({
return true
},
},
- /** A synchronous or asynchronous function, that returns a boolean, that evaluates if the user can delete a given entity */
+ /**
+ * Whether or not user has rights to delete custom plugins
+ */
canDeleteCustom: {
- type: Function as PropType<() => boolean | Promise>,
- required: false,
- default: async () => true,
+ type: Boolean,
+ default: false,
},
- /** A synchronous or asynchronous function, that returns a boolean, that evaluates if the user can edit a given entity */
+ /**
+ * Whether or not user has rights to edit custom plugins
+ */
canEditCustom: {
- type: Function as PropType<() => boolean | Promise>,
- required: false,
- default: async () => true,
+ type: Boolean,
+ default: false,
},
plugin: {
type: Object as PropType,
@@ -162,8 +159,8 @@ const router = useRouter()
const { i18n: { t } } = composables.useI18n()
const controlPlaneId = computed((): string => props.config.app === 'konnect' ? props.config.controlPlaneId : '')
-const handleCreateClick = (pluginName: string): void => {
- router.push(props.config.getCreateRoute(pluginName))
+const handleCreateClick = (): void => {
+ router.push(props.config.getCreateRoute(props.plugin.id))
}
const emitPluginData = (): void => {
@@ -191,9 +188,12 @@ const handleCustomEdit = (pluginName: string): void => {
const handleCustomClick = (): void => {
// TODO: verify
// handle custom plugin card click only
- // regular plugin card render as 'router-link' component which don't need this
- if (props.config.app === 'konnect' && props.config.createCustomRoute && isCustomPlugin.value) {
- router.push(props.config.createCustomRoute)
+ if (props.config.app === 'konnect') {
+ if (isCreateCustomPlugin.value && props.config.createCustomRoute) {
+ router.push(props.config.createCustomRoute)
+ } else if (isCustomPlugin.value) {
+ handleCreateClick()
+ }
}
}
@@ -210,7 +210,7 @@ const handleCustomClick = (): void => {
cursor: pointer;
}
- .header-wrapper { // TODO: had to change from 2rem, does it work?
+ .header-wrapper {
// maintain the specified height if slot has no content
min-height: 25px;
}
@@ -232,6 +232,13 @@ const handleCustomClick = (): void => {
font-weight: $kui-font-weight-regular;
}
+ // TODO: do I still need this?
+ :deep(.k-card-body) {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ }
+
&-body {
background-color: $kui-color-background;
flex: 1;
diff --git a/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue b/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
index 8881f8ea25..2169efae72 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelectGrid.vue
@@ -1,32 +1,23 @@
-
-
+
+
+
+ {{ t('plugins.select.tabs.kong.empty_title') }}
+
+
+
- {{ fetchErrorMessage }}
+
+ {{ t('plugins.select.tabs.kong.empty_description') }}
+
@@ -79,24 +70,18 @@
From 5267dbf3d4fcccbf73b53168d6c1aa60651df8e6 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Fri, 27 Oct 2023 19:53:15 -0400
Subject: [PATCH 21/40] fix(*): docs
---
.../entities-plugins/docs/plugin-select.md | 83 +++++++++++++++++++
.../src/components/PluginSelect.vue | 5 +-
2 files changed, 87 insertions(+), 1 deletion(-)
diff --git a/packages/entities/entities-plugins/docs/plugin-select.md b/packages/entities/entities-plugins/docs/plugin-select.md
index b0adfb828e..7ac8d652e2 100644
--- a/packages/entities/entities-plugins/docs/plugin-select.md
+++ b/packages/entities/entities-plugins/docs/plugin-select.md
@@ -55,6 +55,18 @@ A grid component for selecting Plugins.
- default: `undefined`
- A function that returns the route for creating a specific plugin type.
+ - `createCustomRoute`:
+ - type: RouteLocationRaw
+ - required: `false`
+ - default: `undefined`
+ - The route for creating a custom plugin.
+
+ - `getCustomEditRoute`:
+ - type: `(plugin: string) => RouteLocationRaw`
+ - required: `false`
+ - default: `undefined`
+ - A function that returns the route for editing a custom plugin.
+
- `workspace`:
- type: `string`
- required: `true`
@@ -81,6 +93,77 @@ A grid component for selecting Plugins.
The base konnect or kongManger config.
+#### `canCreate`
+
+- type: `Function as PropType<() => boolean | Promise>`
+- required: `false`
+- default: `async () => true`
+
+A synchronous or asynchronous function, that returns a boolean, that evaluates if the user can create a new plugin.
+
+#### `canDeleteCustom`
+
+- type: `Function as PropType<(row: object) => boolean | Promise>`
+- required: `false`
+- default: `async () => true`
+
+A synchronous or asynchronous function, that returns a boolean, that evaluates if the user can delete a given custom plugin.
+
+#### `canEditCustom`
+
+- type: `Function as PropType<(row: object) => boolean | Promise>`
+- required: `false`
+- default: `async () => true`
+
+A synchronous or asynchronous function, that returns a boolean, that evaluates if the user can edit a given custom plugin.
+
+#### `noRouteChange`
+
+- type: `boolean`
+- required: `false`
+- default: `false`
+
+If true, let consuming component handle event when clicking on a plugin. Used in conjunction with `@plugin-clicked` event.
+
+#### `onlyAvailablePlugins`
+
+- type: `boolean`
+- required: `false`
+- default: `false`
+
+Checks the kong config plugins.available_on_server and if true, then it will not show plugins from PluginMeta that are outside of the available_on_server array.
+
+#### `ignoredPlugins`
+
+- type: `string[]`
+- required: `false`
+- default: '[]'
+
+An array of the plugin names. These are plugins that should not be displayed.
+
+#### `disabledPlugins`
+
+- type: `DisabledPlugin`
+- required: `false`
+- default: `{}`
+
+Plugins that should be disabled and their disabled messages.
+Example:
+
+```json
+{
+ 'acl': 'ACL is not supported for this entity type',
+}
+```
+
+#### `pluginsPerRow`
+
+- type: `number`
+- required: `false`
+- default: `4`
+
+Number of plugins to always have visible (never will be collapsed).
+
### Events
#### plugin-clicked
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index dd7ad293e4..7ab141b3ca 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -184,7 +184,10 @@ const props = defineProps({
default: () => [],
},
/**
- * Plugins that should be disabled and their disabled messages
+ * Plugins that should be disabled and their disabled messages.
+ * ex. {
+ * 'acl': 'ACL is not supported for this entity type',
+ * }
*/
disabledPlugins: {
type: Object as PropType,
From dec4e2527b3870f12e44c65d6326f46185d0ccf3 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Fri, 27 Oct 2023 20:07:08 -0400
Subject: [PATCH 22/40] fix(*): custom create
---
.../entities-plugins/docs/plugin-select.md | 4 ++--
.../src/components/PluginCustomGrid.vue | 4 ++--
.../src/components/PluginSelect.vue | 14 +++++++-------
3 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/packages/entities/entities-plugins/docs/plugin-select.md b/packages/entities/entities-plugins/docs/plugin-select.md
index 7ac8d652e2..ff78d9e980 100644
--- a/packages/entities/entities-plugins/docs/plugin-select.md
+++ b/packages/entities/entities-plugins/docs/plugin-select.md
@@ -93,13 +93,13 @@ A grid component for selecting Plugins.
The base konnect or kongManger config.
-#### `canCreate`
+#### `canCreateCustom`
- type: `Function as PropType<() => boolean | Promise>`
- required: `false`
- default: `async () => true`
-A synchronous or asynchronous function, that returns a boolean, that evaluates if the user can create a new plugin.
+A synchronous or asynchronous function, that returns a boolean, that evaluates if the user can create a new custom plugin.
#### `canDeleteCustom`
diff --git a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue b/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
index 3bc148cb3e..0c0ee94928 100644
--- a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
+++ b/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
@@ -102,7 +102,7 @@ const props = defineProps({
/**
* Whether or not user has rights to create custom plugins
*/
- canCreate: {
+ canCreateCustom: {
type: Boolean,
default: false,
},
@@ -164,7 +164,7 @@ const modifiedCustomPlugins = computed((): PluginType[] => {
const customPlugins: PluginType[] = JSON.parse(JSON.stringify(props.pluginList))[PluginGroup.CUSTOM_PLUGINS] || []
// ADD CUSTOM_PLUGIN_CREATE as the first card if allowed creation
- return props.canCreate && !props.noRouteChange && props.config.createCustomRoute
+ return props.canCreateCustom && !props.noRouteChange && props.config.createCustomRoute
? [{
id: 'custom-plugin-create',
name: t('plugins.select.tabs.custom.create.name'),
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index 7ab141b3ca..a8e20e9dea 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -84,7 +84,7 @@
boolean | Promise>,
required: false,
default: async () => true,
},
- /** A synchronous or asynchronous function, that returns a boolean, that evaluates if the user can delete a given entity */
+ /** A synchronous or asynchronous function, that returns a boolean, that evaluates if the user can delete a custom plugin */
canDeleteCustom: {
type: Function as PropType<() => boolean | Promise>,
required: false,
default: async () => true,
},
- /** A synchronous or asynchronous function, that returns a boolean, that evaluates if the user can edit a given entity */
+ /** A synchronous or asynchronous function, that returns a boolean, that evaluates if the user can edit custom plugin */
canEditCustom: {
type: Function as PropType<() => boolean | Promise>,
required: false,
@@ -405,12 +405,12 @@ watch(() => props.ignoredPlugins, (val, oldVal) => {
}
})
-const userCanCreate = ref(false)
+const userCanCreateCustom = ref(false)
const userCanEditCustom = ref(false)
const userCanDeleteCustom = ref(false)
onBeforeMount(async () => {
// Evaluate the user permissions
- userCanCreate.value = await props.canCreate()
+ userCanCreateCustom.value = await props.canCreateCustom()
userCanEditCustom.value = await props.canEditCustom()
userCanDeleteCustom.value = await props.canDeleteCustom()
})
From 76aa43a36c87a1d5261e99570c30bd92d2ff6135 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Mon, 30 Oct 2023 12:51:21 -0400
Subject: [PATCH 23/40] fix(*): custom delete modal
---
.../sandbox/pages/PluginSelectPage.vue | 10 +-
.../DeleteCustomPluginSchemaModal.vue | 146 ++++++------------
.../src/components/PluginCustomGrid.vue | 3 +
.../src/components/PluginSelect.vue | 2 +
.../src/components/PluginSelectCard.vue | 2 +-
.../src/components/PluginSelectGrid.vue | 1 -
.../entities-plugins/src/locales/en.json | 1 +
7 files changed, 60 insertions(+), 105 deletions(-)
diff --git a/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue b/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue
index e7c3e21d1a..93f14486a6 100644
--- a/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue
+++ b/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue
@@ -3,6 +3,7 @@
Konnect API
Kong Manager API
@@ -25,8 +26,8 @@ const konnectConfig = ref({
// Set the root `.env.development.local` variable to a control plane your PAT can access
controlPlaneId,
// force the scope
- entityType: 'services',
- entityId: '6f1ef200-d3d4-4979-9376-726f2216d90c',
+ // entityType: 'services',
+ // entityId: '6f1ef200-d3d4-4979-9376-726f2216d90c',
getCreateRoute: (plugin: string) => ({
name: 'create-plugin',
params: {
@@ -56,11 +57,14 @@ const kongManagerConfig = ref({
name: 'create-plugin',
params: {
control_plane_id: controlPlaneId.value,
- // TODO: is this right for KM?
plugin,
},
}),
})
+
+const handleDeleteSuccess = (): void => {
+ console.log('Custom plugin deleted')
+}
diff --git a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue b/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
index 143c6b31a3..bc8301b25d 100644
--- a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
+++ b/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
@@ -251,7 +251,6 @@ const handleClose = (revalidate?: boolean): void => {
margin-top: $kui-space-90;
row-gap: $kui-space-90;
- // TODO: do I still need this?
:deep(.kong-card) {
display: flex;
flex: 1 0 0;
diff --git a/packages/entities/entities-plugins/src/components/PluginSelectCard.vue b/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
index 225c22dc74..81d1f20c9c 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
@@ -186,7 +186,6 @@ const handleCustomEdit = (pluginName: string): void => {
}
const handleCustomClick = (): void => {
- // TODO: verify
// handle custom plugin card click only
if (props.config.app === 'konnect') {
if (isCreateCustomPlugin.value && props.config.createCustomRoute) {
@@ -232,7 +231,6 @@ const handleCustomClick = (): void => {
font-weight: $kui-font-weight-regular;
}
- // TODO: do I still need this?
:deep(.k-card-body) {
display: flex;
flex: 1;
From 62a2b6c1401becbd10fdee26202a3a408620fd38 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Mon, 30 Oct 2023 13:35:12 -0400
Subject: [PATCH 26/40] fix(*): custom delete modal docs
---
packages/entities/entities-plugins/docs/plugin-select.md | 4 ++++
.../entities-plugins/src/components/PluginCustomGrid.vue | 4 ++--
.../entities/entities-plugins/src/components/PluginSelect.vue | 4 ++--
3 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/packages/entities/entities-plugins/docs/plugin-select.md b/packages/entities/entities-plugins/docs/plugin-select.md
index ff78d9e980..ca30d8a90f 100644
--- a/packages/entities/entities-plugins/docs/plugin-select.md
+++ b/packages/entities/entities-plugins/docs/plugin-select.md
@@ -174,6 +174,10 @@ An `@plugin-clicked` event is emitted when a plugin in the selection grid is cli
A `@loading` event is emitted when loading state changes. The event payload is a boolean.
+#### delete-custom:success
+
+A `@delete-custom:success` event is emitted when custom plugin is successfully deleted. The event payload is the deleted plugin's name.
+
### Usage example
Please refer to the [sandbox](../sandbox/pages/PluginListPage.vue). The form is accessible by clicking the `+ New Plugin` button.
diff --git a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue b/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
index bc8301b25d..c192440934 100644
--- a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
+++ b/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
@@ -148,7 +148,7 @@ const props = defineProps({
const emit = defineEmits<{
(e: 'plugin-clicked', plugin: PluginType): void,
(e: 'revalidate'): void,
- (e: 'delete:success'): void,
+ (e: 'delete:success', pluginName: string): void,
}>()
const { i18n: { t } } = composables.useI18n()
@@ -213,7 +213,7 @@ const handleCustomPluginDelete = (plugin: PluginType): void => {
const handleClose = (revalidate?: boolean): void => {
if (revalidate) {
emit('revalidate')
- emit('delete:success')
+ emit('delete:success', selectedPlugin.value?.name || '')
}
openDeleteModal.value = false
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index 86d343c726..e36c3d1068 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -91,7 +91,7 @@
:no-route-change="noRouteChange"
:plugin-list="filteredPlugins"
:plugins-per-row="pluginsPerRow"
- @delete:success="$emit('delete-custom:success')"
+ @delete:success="(name: string) => $emit('delete-custom:success', name)"
@plugin-clicked="(val: PluginType) => $emit('plugin-clicked', val)"
@revalidate="() => pluginsList = buildPluginList()"
/>
@@ -206,7 +206,7 @@ const props = defineProps({
const emit = defineEmits<{
(e: 'loading', isLoading: boolean): void,
(e: 'plugin-clicked', plugin: PluginType): void,
- (e: 'delete-custom:success'): void,
+ (e: 'delete-custom:success', pluginName: string): void,
}>()
const route = useRoute()
From 353e116841b9fb420784f210cb141060c712cc0b Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Mon, 30 Oct 2023 13:40:34 -0400
Subject: [PATCH 27/40] fix(*): organize files
---
.../entities-plugins/src/components/PluginSelect.vue | 6 +++---
.../DeleteCustomPluginSchemaModal.vue | 4 ++--
.../{ => custom-plugins}/PluginCustomGrid.vue | 6 +++---
.../components/{ => select}/PluginCardSkeleton.vue | 0
.../src/components/{ => select}/PluginSelectCard.vue | 11 ++++++++---
.../src/components/{ => select}/PluginSelectGrid.vue | 4 ++--
6 files changed, 18 insertions(+), 13 deletions(-)
rename packages/entities/entities-plugins/src/components/{ => custom-plugins}/DeleteCustomPluginSchemaModal.vue (98%)
rename packages/entities/entities-plugins/src/components/{ => custom-plugins}/PluginCustomGrid.vue (98%)
rename packages/entities/entities-plugins/src/components/{ => select}/PluginCardSkeleton.vue (100%)
rename packages/entities/entities-plugins/src/components/{ => select}/PluginSelectCard.vue (97%)
rename packages/entities/entities-plugins/src/components/{ => select}/PluginSelectGrid.vue (98%)
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index e36c3d1068..d3b9a417c1 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -127,9 +127,9 @@ import {
import { useAxios, useHelpers, useErrors } from '@kong-ui-public/entities-shared'
import composables from '../composables'
import endpoints from '../plugins-endpoints'
-import PluginCardSkeleton from './PluginCardSkeleton.vue'
-import PluginCustomGrid from './PluginCustomGrid.vue'
-import PluginSelectGrid from './PluginSelectGrid.vue'
+import PluginCardSkeleton from './select/PluginCardSkeleton.vue'
+import PluginCustomGrid from './custom-plugins/PluginCustomGrid.vue'
+import PluginSelectGrid from './select/PluginSelectGrid.vue'
const props = defineProps({
/** The base konnect or kongManger config. Pass additional config props in the shared entity component as needed. */
diff --git a/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue b/packages/entities/entities-plugins/src/components/custom-plugins/DeleteCustomPluginSchemaModal.vue
similarity index 98%
rename from packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue
rename to packages/entities/entities-plugins/src/components/custom-plugins/DeleteCustomPluginSchemaModal.vue
index 128b14b5ee..1558ff7832 100644
--- a/packages/entities/entities-plugins/src/components/DeleteCustomPluginSchemaModal.vue
+++ b/packages/entities/entities-plugins/src/components/custom-plugins/DeleteCustomPluginSchemaModal.vue
@@ -57,8 +57,8 @@ import { ref, computed, type PropType } from 'vue'
import {
type KongManagerPluginFormConfig,
type KonnectPluginFormConfig,
-} from '../types'
-import composables from '../composables'
+} from '../../types'
+import composables from '../../composables'
import { useAxios, useErrors, EntityTypes, EntityDeleteModal } from '@kong-ui-public/entities-shared'
const props = defineProps({
diff --git a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue b/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue
similarity index 98%
rename from packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
rename to packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue
index c192440934..f0f133a504 100644
--- a/packages/entities/entities-plugins/src/components/PluginCustomGrid.vue
+++ b/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue
@@ -84,9 +84,9 @@ import {
type KonnectPluginFormConfig,
type PluginType,
type PluginCardList,
-} from '../types'
-import composables from '../composables'
-import PluginSelectCard from './PluginSelectCard.vue'
+} from '../../types'
+import composables from '../../composables'
+import PluginSelectCard from '../select/PluginSelectCard.vue'
import DeleteCustomPluginSchemaModal from './DeleteCustomPluginSchemaModal.vue'
const props = defineProps({
diff --git a/packages/entities/entities-plugins/src/components/PluginCardSkeleton.vue b/packages/entities/entities-plugins/src/components/select/PluginCardSkeleton.vue
similarity index 100%
rename from packages/entities/entities-plugins/src/components/PluginCardSkeleton.vue
rename to packages/entities/entities-plugins/src/components/select/PluginCardSkeleton.vue
diff --git a/packages/entities/entities-plugins/src/components/PluginSelectCard.vue b/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
similarity index 97%
rename from packages/entities/entities-plugins/src/components/PluginSelectCard.vue
rename to packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
index 81d1f20c9c..76cf726351 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelectCard.vue
+++ b/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
@@ -110,10 +110,15 @@
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index d3b9a417c1..3e36bae05b 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -65,20 +65,22 @@
@changed="(hash: string) => $router.replace({ hash })"
>
-
- {{ t('plugins.select.tabs.kong.description') }}
-
- $emit('plugin-clicked', val)"
- />
+
+
+ {{ t('plugins.select.tabs.kong.description') }}
+
+
$emit('plugin-clicked', val)"
+ />
+
-
+
{{ t('plugins.select.tabs.custom.description') }}
diff --git a/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue b/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
index 76cf726351..ca28a1f49d 100644
--- a/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
+++ b/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
@@ -2,17 +2,17 @@
props.config.app === 'konnect' ? props.config.controlPlaneId : '')
+const isDisabled = computed((): boolean => !!(!props.plugin.available || props.plugin.disabledMessage))
const handleCreateClick = (): void => {
router.push(props.config.getCreateRoute(props.plugin.id))
@@ -192,7 +193,7 @@ const handleCustomEdit = (pluginName: string): void => {
const handleCustomClick = (): void => {
// handle custom plugin card click only
- if (props.config.app === 'konnect') {
+ if (!isDisabled.value && props.config.app === 'konnect') {
if (isCreateCustomPlugin.value && props.config.createCustomRoute) {
router.push(props.config.createCustomRoute)
} else if (isCustomPlugin.value) {
diff --git a/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue b/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
index c36b38176e..bd78883e7a 100644
--- a/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
+++ b/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
@@ -1,5 +1,5 @@
-
+
Date: Tue, 31 Oct 2023 10:13:11 -0400
Subject: [PATCH 29/40] fix(plugins): test drop
---
.../entities-plugins/fixtures/mockData.ts | 23 +
.../src/components/PluginSelect.cy.ts | 570 ++++++++++++++++++
.../src/components/PluginSelect.vue | 2 +-
.../components/select/PluginSelectCard.vue | 1 +
.../components/select/PluginSelectGrid.vue | 1 +
5 files changed, 596 insertions(+), 1 deletion(-)
create mode 100644 packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
diff --git a/packages/entities/entities-plugins/fixtures/mockData.ts b/packages/entities/entities-plugins/fixtures/mockData.ts
index 69c8f953ee..3c0b93f1b3 100644
--- a/packages/entities/entities-plugins/fixtures/mockData.ts
+++ b/packages/entities/entities-plugins/fixtures/mockData.ts
@@ -73,6 +73,22 @@ export const paginate = (
}
}
+export const kmLimitedAvailablePlugins = {
+ plugins: {
+ enabled_in_cluster: [],
+ available_on_server: {
+ 'hmac-auth': {
+ version: '3.6.0',
+ priority: 1030,
+ },
+ 'ip-restriction': {
+ version: '3.6.0',
+ priority: 990,
+ },
+ },
+ },
+}
+
export const firstShownPlugin = 'basic-auth'
export const kmAvailablePlugins = {
plugins: {
@@ -388,6 +404,13 @@ export const customPluginsNames = [
'myplugin4',
]
+export const konnectLimitedAvailablePlugins = {
+ names: [
+ 'hmac-auth',
+ 'ip-restriction',
+ ],
+}
+
export const konnectAvailablePlugins = {
names: [
'acl',
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts b/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
new file mode 100644
index 0000000000..593ca3ac72
--- /dev/null
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
@@ -0,0 +1,570 @@
+// Cypress component test spec file
+import { PluginGroupArray, type KongManagerPluginFormConfig, type KonnectPluginFormConfig } from '../types'
+import {
+ kmAvailablePlugins,
+ kmLimitedAvailablePlugins,
+ konnectAvailablePlugins,
+ konnectLimitedAvailablePlugins,
+ firstShownPlugin,
+ customPluginsNames,
+} from '../../fixtures/mockData'
+import PluginSelect from './PluginSelect.vue'
+
+const baseConfigKonnect: KonnectPluginFormConfig = {
+ app: 'konnect',
+ apiBaseUrl: '/us/kong-api/konnect-api',
+ controlPlaneId: 'abc-123-i-love-cats',
+ // force the scope
+ // entityType: 'services',
+ // entityId: '6f1ef200-d3d4-4979-9376-726f2216d90c',
+ getCreateRoute: (plugin: string) => ({
+ name: 'create-plugin',
+ params: {
+ control_plane_id: 'abc-123-i-love-cats',
+ plugin,
+ },
+ }),
+ // custom plugins
+ createCustomRoute: { name: 'create-custom-plugin' },
+ getCustomEditRoute: (plugin: string) => ({
+ name: 'edit-custom-plugin',
+ params: {
+ control_plane_id: 'abc-123-i-love-cats',
+ plugin,
+ },
+ }),
+}
+
+const baseConfigKM:KongManagerPluginFormConfig = {
+ app: 'kongManager',
+ workspace: 'default',
+ apiBaseUrl: '/kong-manager',
+ // force the scope
+ // entityType: 'consumers',
+ // entityId: '123-abc-i-lover-cats',
+ getCreateRoute: (plugin: string) => ({
+ name: 'create-plugin',
+ params: {
+ plugin,
+ },
+ }),
+}
+
+describe('', () => {
+ describe('Kong Manager', () => {
+ const interceptKM = (params?: {
+ mockData?: object
+ alias?: string
+ }) => {
+ cy.intercept(
+ {
+ method: 'GET',
+ url: `${baseConfigKM.apiBaseUrl}/${baseConfigKM.workspace}/kong`,
+ },
+ {
+ statusCode: 200,
+ body: params?.mockData ?? kmAvailablePlugins,
+ },
+ ).as(params?.alias ?? 'getAvailablePlugins')
+ }
+
+ it('should show the select page', () => {
+ interceptKM()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKM,
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+ cy.get('.plugins-filter-input-container').should('be.visible')
+ cy.get('.plugin-select-grid').should('be.visible')
+ cy.getTestId(`${firstShownPlugin}-card`).should('be.visible')
+
+ // plugin group collapses show
+ for (const pluginGroup in PluginGroupArray.slice(-1)) {
+ cy.get(`${pluginGroup}-collapse`).should('be.visible')
+ }
+ // renders all plugins
+ for (const pluginName in kmAvailablePlugins.plugins.available_on_server) {
+ cy.getTestId(`${pluginName}-card`).should('exist')
+ }
+ })
+
+ it('should allow customizing the pluginsPerRow', () => {
+ const pluginsPerRow = 3
+
+ interceptKM()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKM,
+ pluginsPerRow,
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+ cy.get('.k-collapse-visible-content .plugin-card').should('have.length', pluginsPerRow)
+ })
+
+ it('should correctly render disabled plugins', () => {
+ const disabledPlugins = { 'basic-auth': 'This plugin is disabled' }
+
+ interceptKM()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKM,
+ disabledPlugins,
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+
+ for (const key in disabledPlugins) {
+ cy.get(`.plugin-card.disabled [data-testid="${key}-card"]`).should('be.visible')
+ }
+ })
+
+ it('should correctly hide ignored plugins', () => {
+ const ignoredPlugins = ['basic-auth']
+
+ interceptKM()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKM,
+ ignoredPlugins,
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+
+ ignoredPlugins.forEach((pluginName) => {
+ cy.getTestId(`${pluginName}-card`).should('not.exist')
+ })
+ })
+
+ it('should correctly render available plugins when isAvailableOnly is true', () => {
+ interceptKM({
+ mockData: kmLimitedAvailablePlugins,
+ })
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKM,
+ showAvailableOnly: true,
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+
+ // renders all plugins
+ for (const pluginName in kmLimitedAvailablePlugins.plugins.available_on_server) {
+ cy.getTestId(`${pluginName}-card`).should('exist')
+ }
+
+ // does not render a plugin when not available
+ cy.getTestId(`${firstShownPlugin}-card`).should('not.exist')
+ })
+
+ it('should allow filtering of plugins', () => {
+ interceptKM()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKM,
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+
+ // search
+ cy.getTestId('plugins-filter').should('be.visible')
+ cy.getTestId('plugins-filter').type(firstShownPlugin)
+
+ cy.getTestId(`${firstShownPlugin}-card`).should('be.visible')
+ cy.get('.plugin-card').should('have.length', 1)
+ })
+
+ it('should handle error state - available plugins failed to load', () => {
+ cy.intercept(
+ {
+ method: 'GET',
+ url: `${baseConfigKM.apiBaseUrl}/${baseConfigKM.workspace}/kong`,
+ },
+ {
+ statusCode: 500,
+ body: {},
+ },
+ ).as('getAvailablePlugins')
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKM,
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+
+ cy.getTestId('plugins-fetch-error').should('be.visible')
+ })
+
+ it('should handle empty state - invalid plugin name', () => {
+ interceptKM()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKM,
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+
+ // search
+ cy.getTestId('plugins-filter').should('be.visible')
+ cy.getTestId('plugins-filter').type('xxxxx')
+
+ cy.getTestId('plugins-empty-state').should('be.visible')
+ })
+
+ it('click event should be emitted when Plugin was clicked and noRouteChange is true', () => {
+ interceptKM()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKM,
+ onPluginClicked: cy.spy().as('onPluginClickedSpy'),
+ noRouteChange: true,
+ },
+ }).then(({ wrapper }) => wrapper)
+ .as('vueWrapper')
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.getTestId(`${firstShownPlugin}-card`).should('be.visible')
+ cy.getTestId(`${firstShownPlugin}-card`).click()
+ /* cy.get('@vueWrapper').then((wrapper: any) => wrapper.findComponent(PluginSelect)
+ .vm.$emit('submit'))
+ */
+ cy.wait('@pluginClicked')
+
+ cy.get('onPluginClickedSpy').should('have.been.calledOnce')
+ })
+ })
+
+ describe('Konnect', () => {
+ const interceptKonnect = (params?: {
+ mockData?: object
+ alias?: string
+ }) => {
+ cy.intercept(
+ {
+ method: 'GET',
+ url: `${baseConfigKonnect.apiBaseUrl}/api/runtime_groups/${baseConfigKonnect.controlPlaneId}/v1/available-plugins`,
+ },
+ {
+ statusCode: 200,
+ body: params?.mockData ?? konnectAvailablePlugins,
+ },
+ ).as(params?.alias ?? 'getAvailablePlugins')
+ }
+
+ describe('actions', () => {
+ it('should correctly render custom plugin actions', () => {
+ interceptKonnect()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKonnect,
+ canEditCustom: () => { },
+ canDeleteCustom: () => { },
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ // custom plugins
+ cy.getTestId('custom-tab').should('be.visible')
+ cy.getTestId('custom-tab').click()
+ cy.get('.custom-plugins-grid').should('be.visible')
+
+ cy.getTestId('overflow-actions-button').eq(0).click()
+ cy.getTestId('edit-plugin-schema').should('be.visible')
+ cy.getTestId('delete-plugin-schema').should('be.visible')
+ // negative test for create
+ cy.getTestId('custom-plugin-create-card').should('not.exist')
+ })
+
+ it('should render create card if user has create rights', () => {
+ interceptKonnect()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKonnect,
+ canCreateCustom: () => { },
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ // custom plugins
+ cy.getTestId('custom-tab').should('be.visible')
+ cy.getTestId('custom-tab').click()
+ cy.get('.custom-plugins-grid').should('be.visible')
+
+ cy.getTestId('custom-plugin-create-card').should('be.visible')
+ // negative test for edit and delete
+ cy.getTestId('custom-plugin-actions').should('not.exist')
+ })
+ })
+
+ it('should show the select page tabs', () => {
+ interceptKonnect()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKonnect,
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+ cy.get('.plugins-filter-input-container').should('be.visible')
+
+ // kong plugins
+ cy.getTestId('kong-tab').should('be.visible')
+ cy.get('.plugin-select-grid').should('be.visible')
+ // kong visible / custom not
+ cy.getTestId(`${firstShownPlugin}-card`).should('be.visible')
+ cy.getTestId(`${customPluginsNames[0]}-card`).should('not.be.visible')
+
+ // plugin group collapses show
+ for (const pluginGroup in PluginGroupArray.slice(-1)) {
+ cy.get(`${pluginGroup}-collapse`).should('be.visible')
+ }
+
+ // custom plugins
+ cy.getTestId('custom-tab').should('be.visible')
+ cy.getTestId('custom-tab').click()
+ cy.get('.custom-plugins-grid').should('be.visible')
+ // kong hidden / custom not
+ cy.getTestId(`${firstShownPlugin}-card`).should('not.be.visible')
+ cy.getTestId(`${customPluginsNames[0]}-card`).should('be.visible')
+
+ // renders all plugins (kong + custom)
+ for (const pluginName in konnectAvailablePlugins.names) {
+ cy.getTestId(`${pluginName}-card`).should('exist')
+ }
+ })
+
+ it('should allow customizing the pluginsPerRow', () => {
+ const pluginsPerRow = 3
+
+ interceptKonnect()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKonnect,
+ pluginsPerRow,
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+ cy.get('.k-collapse-visible-content .plugin-card').should('have.length', pluginsPerRow)
+ })
+
+ it('should correctly render disabled plugins', () => {
+ const disabledPlugins = { 'basic-auth': 'This plugin is disabled' }
+
+ interceptKonnect()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKonnect,
+ disabledPlugins,
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+
+ for (const key in disabledPlugins) {
+ cy.get(`.plugin-card.disabled [data-testid="${key}-card"]`).should('be.visible')
+ }
+ })
+
+ it('should correctly render ignored plugins', () => {
+ const ignoredPlugins = ['basic-auth']
+
+ interceptKonnect()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKonnect,
+ ignoredPlugins,
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+
+ ignoredPlugins.forEach((pluginName) => {
+ cy.getTestId(`${pluginName}-card`).should('not.exist')
+ })
+ })
+
+ it('should correctly render available plugins when isAvailableOnly is true', () => {
+ interceptKonnect({
+ mockData: konnectLimitedAvailablePlugins,
+ })
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKonnect,
+ showAvailableOnly: true,
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+
+ // renders all plugins
+ konnectLimitedAvailablePlugins.names.forEach((pluginName: string) => {
+ cy.getTestId(`${pluginName}-card`).should('exist')
+ })
+
+ // does not render a plugin when not available
+ cy.getTestId(`${firstShownPlugin}-card`).should('not.exist')
+ })
+
+ it('should allow filtering of plugins', () => {
+ interceptKonnect()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKonnect,
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+
+ // search
+ cy.getTestId('plugins-filter').should('be.visible')
+ cy.getTestId('plugins-filter').type(firstShownPlugin)
+
+ cy.getTestId(`${firstShownPlugin}-card`).should('be.visible')
+ cy.get('.plugin-card').should('have.length', 1)
+ })
+
+ it('should handle error state - available plugins failed to load', () => {
+ cy.intercept(
+ {
+ method: 'GET',
+ url: `${baseConfigKonnect.apiBaseUrl}/api/runtime_groups/${baseConfigKonnect.controlPlaneId}/v1/available-plugins`,
+ },
+ {
+ statusCode: 500,
+ body: {},
+ },
+ ).as('getAvailablePlugins')
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKonnect,
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+
+ cy.getTestId('plugins-fetch-error').should('be.visible')
+ })
+
+ it('should handle empty state - invalid plugin name', () => {
+ interceptKonnect()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKonnect,
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+
+ // search
+ cy.getTestId('plugins-filter').should('be.visible')
+ cy.getTestId('plugins-filter').type('xxxxx')
+
+ cy.getTestId('plugins-empty-state').should('be.visible')
+ })
+
+ it('click event should be emitted when Plugin was clicked and noRouteChange is true', () => {
+ interceptKonnect()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKonnect,
+ onPluginClicked: cy.spy().as('onPluginClickedSpy'),
+ noRouteChange: true,
+ },
+ }).then(({ wrapper }) => wrapper)
+ .as('vueWrapper')
+
+ cy.wait('@getAvailablePlugins')
+
+ cy.getTestId(`${firstShownPlugin}-card`).should('be.visible')
+ cy.getTestId(`${firstShownPlugin}-card`).click()
+
+ /* cy.get('@vueWrapper').then((wrapper: any) => wrapper.findComponent(EntityBaseForm)
+ .vm.$emit('submit')) */
+
+ cy.wait('@pluginClicked')
+
+ cy.get('onPluginClickedSpy').should('have.been.calledOnce')
+ })
+ })
+})
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index 3e36bae05b..15f31087ca 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -43,6 +43,7 @@
@@ -471,7 +472,6 @@ onMounted(async () => {
isLoading.value = false
emit('loading', isLoading.value)
-
})
diff --git a/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue b/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
index ca28a1f49d..96a1de5c16 100644
--- a/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
+++ b/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
@@ -30,6 +30,7 @@
#actions
>
diff --git a/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue b/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
index bd78883e7a..1e26b734d0 100644
--- a/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
+++ b/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
@@ -4,6 +4,7 @@
v-if="!Object.keys(nonCustomPlugins).length"
class="plugins-empty-state"
cta-is-hidden
+ data-testid="plugins-empty-state"
icon="stateGruceo"
icon-size="96"
>
From 57cc71c8775076edea7cb4fedbe2c4bcf671f066 Mon Sep 17 00:00:00 2001
From: Kai Arrowood
Date: Tue, 31 Oct 2023 10:21:30 -0400
Subject: [PATCH 30/40] fix(*): pr feedback
Co-authored-by: Adam DeHaven <2229946+adamdehaven@users.noreply.github.com>
---
packages/entities/entities-plugins/docs/plugin-select.md | 1 -
.../entities/entities-plugins/src/components/PluginSelect.vue | 1 +
packages/entities/entities-shared/src/composables/useHelpers.ts | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/entities/entities-plugins/docs/plugin-select.md b/packages/entities/entities-plugins/docs/plugin-select.md
index ca30d8a90f..ac01a6b758 100644
--- a/packages/entities/entities-plugins/docs/plugin-select.md
+++ b/packages/entities/entities-plugins/docs/plugin-select.md
@@ -29,7 +29,6 @@ A grid component for selecting Plugins.
- type: `Object as PropType`
- required: `true`
-- default: `undefined`
- properties:
- `app`:
- type: `'konnect' | 'kongManager'`
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index 15f31087ca..fe9f4a150b 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -413,6 +413,7 @@ watch(() => props.ignoredPlugins, (val, oldVal) => {
const userCanCreateCustom = ref(false)
const userCanEditCustom = ref(false)
const userCanDeleteCustom = ref(false)
+
onBeforeMount(async () => {
// Evaluate the user permissions
userCanCreateCustom.value = await props.canCreateCustom()
diff --git a/packages/entities/entities-shared/src/composables/useHelpers.ts b/packages/entities/entities-shared/src/composables/useHelpers.ts
index 8979a00552..28e0fb5e6b 100644
--- a/packages/entities/entities-shared/src/composables/useHelpers.ts
+++ b/packages/entities/entities-shared/src/composables/useHelpers.ts
@@ -16,7 +16,7 @@ export default function useHelpers() {
* @param {Object} b second object to compare
* @returns {Boolean} whether or not the objects are equal
*/
- const objectsAreEqual = (a: Record, b: Record) => {
+ const objectsAreEqual = (a: Record, b: Record): boolean => {
try {
return JSON.stringify(a) === JSON.stringify(b)
} catch (e) {
From 0291f0661c2d6848c31bbb2f72120aaf8623698b Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Tue, 31 Oct 2023 11:04:12 -0400
Subject: [PATCH 31/40] fix(*): pr feedback
---
.../DeleteCustomPluginSchemaModal.vue | 10 +++------
.../custom-plugins/PluginCustomGrid.vue | 10 ++++-----
.../components/select/PluginSelectGrid.vue | 4 ++--
.../src/composables/useHelpers.ts | 22 +++++++++----------
4 files changed, 21 insertions(+), 25 deletions(-)
diff --git a/packages/entities/entities-plugins/src/components/custom-plugins/DeleteCustomPluginSchemaModal.vue b/packages/entities/entities-plugins/src/components/custom-plugins/DeleteCustomPluginSchemaModal.vue
index 1558ff7832..05fc055bf8 100644
--- a/packages/entities/entities-plugins/src/components/custom-plugins/DeleteCustomPluginSchemaModal.vue
+++ b/packages/entities/entities-plugins/src/components/custom-plugins/DeleteCustomPluginSchemaModal.vue
@@ -11,7 +11,7 @@
need-confirm
:title="title"
visible
- @cancel="handleCancel"
+ @cancel="$emit('closed')"
@proceed="handleSubmit"
/>
@@ -23,7 +23,7 @@
is-visible
:title="title"
type="warning"
- @canceled="handleCancel"
+ @canceled="$emit('closed')"
>
@@ -43,7 +43,7 @@
appearance="outline"
class="cancel-button"
data-testid="delete-custom-plugin-form-cancel"
- @click="handleCancel"
+ @click="$emit('closed')"
>
{{ t('actions.cancel') }}
@@ -96,10 +96,6 @@ const isLoading = ref(false)
const errorMessage = ref('')
const isPluginSchemaInUse = ref(false)
-const handleCancel = (): void => {
- emit('closed')
-}
-
const handleSubmit = async (): Promise
=> {
isLoading.value = true
errorMessage.value = ''
diff --git a/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue b/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue
index f0f133a504..8e34c659b9 100644
--- a/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue
+++ b/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue
@@ -40,7 +40,7 @@
()
@@ -167,13 +167,13 @@ const modifiedCustomPlugins = computed((): PluginType[] => {
// ADD CUSTOM_PLUGIN_CREATE as the first card if allowed creation
return props.canCreateCustom && !props.noRouteChange && props.config.createCustomRoute
- ? [{
+ ? ([{
id: 'custom-plugin-create',
name: t('plugins.select.tabs.custom.create.name'),
available: true,
group: PluginGroup.CUSTOM_PLUGINS,
description: t('plugins.select.tabs.custom.create.description'),
- } as PluginType].concat(customPlugins)
+ }] as PluginType[]).concat(customPlugins)
: customPlugins
})
diff --git a/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue b/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
index 1e26b734d0..2bbf685a45 100644
--- a/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
+++ b/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
@@ -45,7 +45,7 @@
, b: Record): boolean => {
try {
return JSON.stringify(a) === JSON.stringify(b)
@@ -25,12 +25,12 @@ export default function useHelpers() {
}
/**
- * A comparator function that given a key, compares object values with that key, and returns the results of
- * localCompare on those values (see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare for reference)
- * Also checks for undefined, nulls and sub-Arrays.
- * @param {String} property the key to sort on
- * @returns {Function} a comparator function
- */
+ * A comparator function that given a key, compares object values with that key, and returns the results of
+ * localCompare on those values (see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare for reference)
+ * Also checks for undefined, nulls and sub-Arrays.
+ * @param {String} property the key to sort on
+ * @returns {Function} a comparator function
+ */
const sortAlpha = (property: string) => {
return (a: Record, b: Record) => {
let propertyA = a[property] === undefined || a[property] === null ? '' : a[property]
From 2fa4f9060f47d50e0b029c502f10ff5eb35f8d70 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Tue, 31 Oct 2023 11:26:56 -0400
Subject: [PATCH 32/40] fix(*): pr feedback
---
.../entities-plugins/docs/plugin-select.md | 6 +--
.../sandbox/pages/PluginSelectPage.vue | 4 +-
.../src/components/PluginSelect.cy.ts | 6 +--
.../src/components/PluginSelect.vue | 41 +++++++++----------
.../custom-plugins/PluginCustomGrid.vue | 20 ++++-----
.../components/select/PluginSelectCard.vue | 12 +++---
6 files changed, 44 insertions(+), 45 deletions(-)
diff --git a/packages/entities/entities-plugins/docs/plugin-select.md b/packages/entities/entities-plugins/docs/plugin-select.md
index ac01a6b758..67dec96d56 100644
--- a/packages/entities/entities-plugins/docs/plugin-select.md
+++ b/packages/entities/entities-plugins/docs/plugin-select.md
@@ -92,7 +92,7 @@ A grid component for selecting Plugins.
The base konnect or kongManger config.
-#### `canCreateCustom`
+#### `canCreateCustomPlugin`
- type: `Function as PropType<() => boolean | Promise>`
- required: `false`
@@ -100,7 +100,7 @@ The base konnect or kongManger config.
A synchronous or asynchronous function, that returns a boolean, that evaluates if the user can create a new custom plugin.
-#### `canDeleteCustom`
+#### `canDeleteCustomPlugin`
- type: `Function as PropType<(row: object) => boolean | Promise>`
- required: `false`
@@ -108,7 +108,7 @@ A synchronous or asynchronous function, that returns a boolean, that evaluates i
A synchronous or asynchronous function, that returns a boolean, that evaluates if the user can delete a given custom plugin.
-#### `canEditCustom`
+#### `canEditCustomPlugin`
- type: `Function as PropType<(row: object) => boolean | Promise>`
- required: `false`
diff --git a/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue b/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue
index 269cd8ab3a..3579c52a78 100644
--- a/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue
+++ b/packages/entities/entities-plugins/sandbox/pages/PluginSelectPage.vue
@@ -26,8 +26,8 @@ const konnectConfig = ref({
// Set the root `.env.development.local` variable to a control plane your PAT can access
controlPlaneId,
// force the scope
- // entityType: 'services',
- // entityId: '6f1ef200-d3d4-4979-9376-726f2216d90c',
+ entityType: 'services',
+ entityId: '6f1ef200-d3d4-4979-9376-726f2216d90c',
getCreateRoute: (plugin: string) => ({
name: 'create-plugin',
params: {
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts b/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
index 593ca3ac72..57255e65a2 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
@@ -302,8 +302,8 @@ describe('', () => {
cy.mount(PluginSelect, {
props: {
config: baseConfigKonnect,
- canEditCustom: () => { },
- canDeleteCustom: () => { },
+ canEditCustomPlugin: () => { },
+ canDeleteCustomPlugin: () => { },
},
})
@@ -327,7 +327,7 @@ describe('', () => {
cy.mount(PluginSelect, {
props: {
config: baseConfigKonnect,
- canCreateCustom: () => { },
+ canCreateCustomPlugin: () => { },
},
})
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index fe9f4a150b..2566f67cbc 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -87,9 +87,9 @@
boolean | Promise>,
required: false,
default: async () => true,
},
/** A synchronous or asynchronous function, that returns a boolean, that evaluates if the user can delete a custom plugin */
- canDeleteCustom: {
+ canDeleteCustomPlugin: {
type: Function as PropType<() => boolean | Promise>,
required: false,
default: async () => true,
},
/** A synchronous or asynchronous function, that returns a boolean, that evaluates if the user can edit custom plugin */
- canEditCustom: {
+ canEditCustomPlugin: {
type: Function as PropType<() => boolean | Promise>,
required: false,
default: async () => true,
@@ -410,33 +410,34 @@ watch(() => props.ignoredPlugins, (val, oldVal) => {
}
})
-const userCanCreateCustom = ref(false)
-const userCanEditCustom = ref(false)
-const userCanDeleteCustom = ref(false)
+watch((isLoading), (loading: boolean) => {
+ emit('loading', loading)
+})
+
+const usercanCreateCustomPlugin = ref(false)
+const usercanEditCustomPlugin = ref(false)
+const usercanDeleteCustomPlugin = ref(false)
onBeforeMount(async () => {
// Evaluate the user permissions
- userCanCreateCustom.value = await props.canCreateCustom()
- userCanEditCustom.value = await props.canEditCustom()
- userCanDeleteCustom.value = await props.canDeleteCustom()
+ usercanCreateCustomPlugin.value = await props.canCreateCustomPlugin()
+ usercanEditCustomPlugin.value = await props.canEditCustomPlugin()
+ usercanDeleteCustomPlugin.value = await props.canDeleteCustomPlugin()
})
onMounted(async () => {
- isLoading.value = true
hasError.value = false
fetchErrorMessage.value = ''
- emit('loading', isLoading.value)
-
try {
- const res = await axiosInstance.get(availablePluginsUrl.value)
+ const { data } = await axiosInstance.get(availablePluginsUrl.value)
// TODO: endpoints temporarily return different formats
if (props.config.app === 'konnect') {
- const { names: available } = res.data
+ const { names: available } = data
availablePlugins.value = available || []
} else if (props.config.app === 'kongManager') {
- const { plugins: { available_on_server: aPlugins } } = res.data
+ const { plugins: { available_on_server: aPlugins } } = data
availablePlugins.value = aPlugins ? Object.keys(aPlugins) : []
}
} catch (error: any) {
@@ -447,8 +448,7 @@ onMounted(async () => {
// fetch scoped entity to check for pre-existing plugins
if (fetchEntityPluginsUrl.value) {
try {
- const res = await axiosInstance.get(fetchEntityPluginsUrl.value)
- const { data } = res.data
+ const { data: { data } } = await axiosInstance.get(fetchEntityPluginsUrl.value)
if (data?.length) {
const eplugins = data.reduce((plugins: string[], plugin: Record) => {
@@ -472,7 +472,6 @@ onMounted(async () => {
}
isLoading.value = false
- emit('loading', isLoading.value)
})
diff --git a/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue b/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue
index 8e34c659b9..8a35856cac 100644
--- a/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue
+++ b/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue
@@ -41,12 +41,12 @@
handleCustomPluginDelete(plugin)"
+ @custom-plugin-delete="handleCustomPluginDelete(plugin)"
@plugin-clicked="emitPluginData"
/>
@@ -56,8 +56,8 @@
handleClose(true)"
+ @proceed="handleClose(true)"
/>
@@ -103,21 +103,21 @@ const props = defineProps({
/**
* Whether or not user has rights to create custom plugins
*/
- canCreateCustom: {
+ canCreateCustomPlugin: {
type: Boolean,
default: false,
},
/**
* Whether or not user has rights to delete custom plugins
*/
- canDeleteCustom: {
+ canDeleteCustomPlugin: {
type: Boolean,
default: false,
},
/**
* Whether or not user has rights to edit custom plugins
*/
- canEditCustom: {
+ canEditCustomPlugin: {
type: Boolean,
default: false,
},
@@ -166,7 +166,7 @@ const modifiedCustomPlugins = computed((): PluginType[] => {
const customPlugins: PluginType[] = JSON.parse(JSON.stringify(props.pluginList))[PluginGroup.CUSTOM_PLUGINS] || []
// ADD CUSTOM_PLUGIN_CREATE as the first card if allowed creation
- return props.canCreateCustom && !props.noRouteChange && props.config.createCustomRoute
+ return props.canCreateCustomPlugin && !props.noRouteChange && props.config.createCustomRoute
? ([{
id: 'custom-plugin-create',
name: t('plugins.select.tabs.custom.create.name'),
diff --git a/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue b/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
index 96a1de5c16..1a5340c006 100644
--- a/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
+++ b/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
@@ -26,7 +26,7 @@
{{ t('actions.edit') }}
()
const props = defineProps({
@@ -140,14 +140,14 @@ const props = defineProps({
/**
* Whether or not user has rights to delete custom plugins
*/
- canDeleteCustom: {
+ canDeleteCustomPlugin: {
type: Boolean,
default: false,
},
/**
* Whether or not user has rights to edit custom plugins
*/
- canEditCustom: {
+ canEditCustomPlugin: {
type: Boolean,
default: false,
},
From 135e170301c952508befb567248f693daa216f40 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Tue, 31 Oct 2023 12:24:49 -0400
Subject: [PATCH 33/40] fix(*): pr feedback
---
.../entities-plugins/docs/plugin-select.md | 2 +-
.../src/components/PluginSelect.vue | 13 +++++-------
.../custom-plugins/PluginCustomGrid.vue | 19 +++++------------
.../components/select/PluginSelectCard.vue | 13 ++++++++++--
.../components/select/PluginSelectGrid.vue | 21 +++++--------------
.../src/composables/usePluginHelpers.ts | 12 +++++++++++
6 files changed, 39 insertions(+), 41 deletions(-)
diff --git a/packages/entities/entities-plugins/docs/plugin-select.md b/packages/entities/entities-plugins/docs/plugin-select.md
index 67dec96d56..5e92ea9c5d 100644
--- a/packages/entities/entities-plugins/docs/plugin-select.md
+++ b/packages/entities/entities-plugins/docs/plugin-select.md
@@ -124,7 +124,7 @@ A synchronous or asynchronous function, that returns a boolean, that evaluates i
If true, let consuming component handle event when clicking on a plugin. Used in conjunction with `@plugin-clicked` event.
-#### `onlyAvailablePlugins`
+#### `availableOnServer`
- type: `boolean`
- required: `false`
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index 2566f67cbc..18cf6da6f8 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -172,11 +172,11 @@ const props = defineProps({
default: false,
},
/**
- * @param {boolean} onlyAvailablePlugins checks kong config plugins.available_on_server and if
- * onlyAvailablePlugins = true, then it will not show plugins from PluginMeta that are outside
+ * @param {boolean} availableOnServer checks kong config plugins.available_on_server and if
+ * availableOnServer = true, then it will not show plugins from PluginMeta that are outside
* of the available_on_server array.
*/
- onlyAvailablePlugins: {
+ availableOnServer: {
type: Boolean,
default: false,
},
@@ -274,12 +274,12 @@ const tabs = props.config.app === 'konnect'
const activeTab = ref(tabs.length ? route.hash || tabs[0].hash : '')
const buildPluginList = (): PluginCardList => {
- // If onlyAvailablePlugins is false, we included unavailable plugins from pluginMeta in addition to available plugins
+ // If availableOnServer is false, we included unavailable plugins from pluginMeta in addition to available plugins
// returning an array of unique plugin ids
// either grab all plugins from metadata file or use list of available plugins provided by API
return [...new Set(
Object.assign(
- Object.keys({ ...(!props.onlyAvailablePlugins ? pluginMetaData : {}) }),
+ Object.keys({ ...(!props.availableOnServer ? pluginMetaData : {}) }),
availablePlugins.value,
),
)]
@@ -426,9 +426,6 @@ onBeforeMount(async () => {
})
onMounted(async () => {
- hasError.value = false
- fetchErrorMessage.value = ''
-
try {
const { data } = await axiosInstance.get(availablePluginsUrl.value)
diff --git a/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue b/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue
index 8a35856cac..ae3cebc0c1 100644
--- a/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue
+++ b/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue
@@ -39,7 +39,7 @@
()
const { i18n: { t } } = composables.useI18n()
+const { getPluginCards } = composables.usePluginHelpers()
const shouldCollapsedCustomPlugins = ref(true)
const emitPluginData = (plugin: PluginType) => {
@@ -177,20 +178,10 @@ const modifiedCustomPlugins = computed((): PluginType[] => {
: customPlugins
})
-const getPluginCards = (type: 'all' | 'visible' | 'hidden') => {
- if (type === 'all') {
- return modifiedCustomPlugins.value
- } else if (type === 'visible') {
- return modifiedCustomPlugins.value.slice(0, props.pluginsPerRow)
- }
-
- return modifiedCustomPlugins.value.slice(props.pluginsPerRow)
-}
-
// text for plugin group "view x more" label
const triggerLabel = computed((): string => {
- const totalCount = getPluginCards('all')?.length || 0
- const hiddenCount = getPluginCards('hidden')?.length || 0
+ const totalCount = getPluginCards('all', modifiedCustomPlugins.value, props.pluginsPerRow)?.length
+ const hiddenCount = getPluginCards('hidden', modifiedCustomPlugins.value, props.pluginsPerRow)?.length
if (totalCount > props.pluginsPerRow) {
return t('plugins.select.view_more', { count: hiddenCount })
diff --git a/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue b/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
index 1a5340c006..c56110aea0 100644
--- a/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
+++ b/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
@@ -12,7 +12,7 @@
:data-testid="`${plugin.id}-card`"
:disabled="isDisabled"
has-hover
- @click="isDisabled || isCustomPlugin ? undefined : noRouteChange ? emitPluginData() : handleCreateClick()"
+ @click="isDisabled || isCustomPlugin ? undefined : handleClick()"
>
props.config.app === 'konnect' ? props.config.controlPlaneId : '')
const isDisabled = computed((): boolean => !!(!props.plugin.available || props.plugin.disabledMessage))
+const hasActions = computed((): boolean => !!(isCustomPlugin.value && !isCreateCustomPlugin.value && !props.noRouteChange && controlPlaneId.value && (props.canDeleteCustomPlugin || props.canEditCustomPlugin)))
const handleCreateClick = (): void => {
router.push(props.config.getCreateRoute(props.plugin.id))
}
+const handleClick = (): void => {
+ if (props.noRouteChange) {
+ emitPluginData()
+ } else {
+ handleCreateClick()
+ }
+}
+
const emitPluginData = (): void => {
emit('plugin-clicked', props.plugin)
}
diff --git a/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue b/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
index 2bbf685a45..dea9cd9c4c 100644
--- a/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
+++ b/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
@@ -44,7 +44,7 @@
()
const { i18n: { t } } = composables.useI18n()
+const { getPluginCards } = composables.usePluginHelpers()
const shouldCollapsed = ref>(PLUGIN_GROUPS_COLLAPSE_STATUS)
const emitPluginData = (plugin: PluginType) => {
@@ -140,18 +141,6 @@ const nonCustomPlugins = computed((): PluginCardList => {
return kongPlugins
})
-const getPluginCards = (group: string, type: 'all' | 'visible' | 'hidden') => {
- const plugins = nonCustomPlugins.value[group as keyof PluginCardList] || []
-
- if (type === 'all') {
- return plugins
- } else if (type === 'visible') {
- return plugins.slice(0, props.pluginsPerRow)
- }
-
- return plugins.slice(props.pluginsPerRow)
-}
-
const getGroupPluginCount = (group: string) => {
return nonCustomPlugins.value[group as keyof PluginCardList]?.length || 0
}
@@ -159,8 +148,8 @@ const getGroupPluginCount = (group: string) => {
// text for plugin group "view x more" label
const triggerLabels = computed(() => {
return Object.keys(nonCustomPlugins.value).reduce((acc: TriggerLabels, pluginGroup: string): TriggerLabels => {
- const totalCount = getPluginCards(pluginGroup, 'all')?.length || 0
- const hiddenCount = getPluginCards(pluginGroup, 'hidden')?.length || 0
+ const totalCount = getPluginCards('all', nonCustomPlugins.value[pluginGroup as keyof PluginCardList] || [], props.pluginsPerRow)?.length || 0
+ const hiddenCount = getPluginCards('hidden', nonCustomPlugins.value[pluginGroup as keyof PluginCardList] || [], props.pluginsPerRow)?.length || 0
if (totalCount > props.pluginsPerRow) {
acc[pluginGroup as keyof TriggerLabels] = t('plugins.select.view_more', { count: hiddenCount })
diff --git a/packages/entities/entities-plugins/src/composables/usePluginHelpers.ts b/packages/entities/entities-plugins/src/composables/usePluginHelpers.ts
index 205849c907..066834c4a3 100644
--- a/packages/entities/entities-plugins/src/composables/usePluginHelpers.ts
+++ b/packages/entities/entities-plugins/src/composables/usePluginHelpers.ts
@@ -1,5 +1,6 @@
import type { ConfigurationSchema } from '@kong-ui-public/entities-shared'
import { ConfigurationSchemaType } from '@kong-ui-public/entities-shared'
+import type { PluginType } from '../types'
export default function useHelpers() {
const METHOD_KEYS = ['methods', 'logout_methods']
@@ -88,7 +89,18 @@ export default function useHelpers() {
}
}
+ const getPluginCards = (type: 'all' | 'visible' | 'hidden', plugins: PluginType[], pluginsPerRow: number) => {
+ if (type === 'all') {
+ return plugins
+ } else if (type === 'visible') {
+ return plugins.slice(0, pluginsPerRow)
+ }
+
+ return plugins.slice(pluginsPerRow)
+ }
+
return {
setFieldType,
+ getPluginCards,
}
}
From ea3fbb0f67a4b6eb19c28486205c2f1ae1b209d3 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Tue, 31 Oct 2023 12:40:45 -0400
Subject: [PATCH 34/40] fix(*): pr feedback
---
.../entities-plugins/docs/plugin-select.md | 6 ++---
.../entities/entities-plugins/package.json | 3 ++-
.../src/components/PluginSelect.cy.ts | 8 +++---
.../src/components/PluginSelect.vue | 12 ++++-----
.../custom-plugins/PluginCustomGrid.vue | 12 ++++-----
.../components/select/PluginSelectCard.vue | 21 ++++++++-------
.../components/select/PluginSelectGrid.vue | 10 +++----
pnpm-lock.yaml | 26 +++++++------------
8 files changed, 47 insertions(+), 51 deletions(-)
diff --git a/packages/entities/entities-plugins/docs/plugin-select.md b/packages/entities/entities-plugins/docs/plugin-select.md
index 5e92ea9c5d..643c0b1658 100644
--- a/packages/entities/entities-plugins/docs/plugin-select.md
+++ b/packages/entities/entities-plugins/docs/plugin-select.md
@@ -116,13 +116,13 @@ A synchronous or asynchronous function, that returns a boolean, that evaluates i
A synchronous or asynchronous function, that returns a boolean, that evaluates if the user can edit a given custom plugin.
-#### `noRouteChange`
+#### `navigateOnClick`
- type: `boolean`
- required: `false`
-- default: `false`
+- default: `true`
-If true, let consuming component handle event when clicking on a plugin. Used in conjunction with `@plugin-clicked` event.
+If false, let consuming component handle event when clicking on a plugin. Used in conjunction with `@plugin-clicked` event.
#### `availableOnServer`
diff --git a/packages/entities/entities-plugins/package.json b/packages/entities/entities-plugins/package.json
index 9a61c96de8..3deee8e209 100644
--- a/packages/entities/entities-plugins/package.json
+++ b/packages/entities/entities-plugins/package.json
@@ -72,6 +72,7 @@
"@kong-ui-public/entities-consumers": "workspace:^",
"@kong-ui-public/entities-gateway-services": "workspace:^",
"@kong-ui-public/entities-routes": "workspace:^",
- "@kong-ui-public/entities-shared": "workspace:^"
+ "@kong-ui-public/entities-shared": "workspace:^",
+ "@kong/icons": "^1.7.8"
}
}
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts b/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
index 57255e65a2..e4d5495c52 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
@@ -253,14 +253,14 @@ describe('', () => {
cy.getTestId('plugins-empty-state').should('be.visible')
})
- it('click event should be emitted when Plugin was clicked and noRouteChange is true', () => {
+ it('click event should be emitted when Plugin was clicked and navigateOnClick is false', () => {
interceptKM()
cy.mount(PluginSelect, {
props: {
config: baseConfigKM,
onPluginClicked: cy.spy().as('onPluginClickedSpy'),
- noRouteChange: true,
+ navigateOnClick: false,
},
}).then(({ wrapper }) => wrapper)
.as('vueWrapper')
@@ -542,14 +542,14 @@ describe('', () => {
cy.getTestId('plugins-empty-state').should('be.visible')
})
- it('click event should be emitted when Plugin was clicked and noRouteChange is true', () => {
+ it('click event should be emitted when Plugin was clicked and navigateOnClick is false', () => {
interceptKonnect()
cy.mount(PluginSelect, {
props: {
config: baseConfigKonnect,
onPluginClicked: cy.spy().as('onPluginClickedSpy'),
- noRouteChange: true,
+ navigateOnClick: false,
},
}).then(({ wrapper }) => wrapper)
.as('vueWrapper')
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index 18cf6da6f8..b1b244622c 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -72,7 +72,7 @@
$emit('plugin-clicked', val)"
@@ -91,7 +91,7 @@
:can-delete-custom="usercanDeleteCustomPlugin"
:can-edit-custom="usercanEditCustomPlugin"
:config="config"
- :no-route-change="noRouteChange"
+ :navigate-on-click="navigateOnClick"
:plugin-list="filteredPlugins"
:plugins-per-row="pluginsPerRow"
@delete:success="(name: string) => $emit('delete-custom:success', name)"
@@ -106,7 +106,7 @@
$emit('plugin-clicked', val)"
@@ -164,12 +164,12 @@ const props = defineProps({
default: async () => true,
},
/**
- * @param {boolean} noRouteChange if true, let consuming component handle event when clicking on a plugin
+ * @param {boolean} navigateOnClick if false, let consuming component handle event when clicking on a plugin
* Used in conjunction with `@plugin-clicked` event
*/
- noRouteChange: {
+ navigateOnClick: {
type: Boolean,
- default: false,
+ default: true,
},
/**
* @param {boolean} availableOnServer checks kong config plugins.available_on_server and if
diff --git a/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue b/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue
index ae3cebc0c1..9d8e99e64b 100644
--- a/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue
+++ b/packages/entities/entities-plugins/src/components/custom-plugins/PluginCustomGrid.vue
@@ -44,7 +44,7 @@
:can-delete-custom="canDeleteCustomPlugin"
:can-edit-custom="canEditCustomPlugin"
:config="config"
- :no-route-change="noRouteChange"
+ :navigate-on-click="navigateOnClick"
:plugin="plugin"
@custom-plugin-delete="handleCustomPluginDelete(plugin)"
@plugin-clicked="emitPluginData"
@@ -59,7 +59,7 @@
:can-delete-custom="canDeleteCustomPlugin"
:can-edit-custom="canEditCustomPlugin"
:config="config"
- :no-route-change="noRouteChange"
+ :navigate-on-click="navigateOnClick"
:plugin="plugin"
@plugin-clicked="emitPluginData"
/>
@@ -129,12 +129,12 @@ const props = defineProps({
default: () => ({}),
},
/**
- * @param {boolean} noRouteChange if true, let consuming component handle event when clicking on a plugin
+ * @param {boolean} navigateOnClick if false, let consuming component handle event when clicking on a plugin
* Used in conjunction with `@plugin-clicked` event
*/
- noRouteChange: {
+ navigateOnClick: {
type: Boolean,
- default: false,
+ default: true,
},
/**
* Number of plugins to always have visible (never will be collapsed)
@@ -167,7 +167,7 @@ const modifiedCustomPlugins = computed((): PluginType[] => {
const customPlugins: PluginType[] = JSON.parse(JSON.stringify(props.pluginList))[PluginGroup.CUSTOM_PLUGINS] || []
// ADD CUSTOM_PLUGIN_CREATE as the first card if allowed creation
- return props.canCreateCustomPlugin && !props.noRouteChange && props.config.createCustomRoute
+ return props.canCreateCustomPlugin && props.navigateOnClick && props.config.createCustomRoute
? ([{
id: 'custom-plugin-create',
name: t('plugins.select.tabs.custom.create.name'),
diff --git a/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue b/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
index c56110aea0..b5156eb12a 100644
--- a/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
+++ b/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
@@ -41,11 +41,7 @@
size="small"
>
-
+
@@ -117,7 +113,8 @@ import {
type KonnectPluginFormConfig,
type PluginType,
} from '../../types'
-import { KUI_ICON_SIZE_30, KUI_COLOR_TEXT_NEUTRAL_STRONGER } from '@kong/design-tokens'
+import { KUI_ICON_SIZE_30 } from '@kong/design-tokens'
+import { MoreIcon } from '@kong/icons'
import composables from '../../composables'
import PluginIcon from '../PluginIcon.vue'
@@ -155,9 +152,9 @@ const props = defineProps({
type: Object as PropType,
required: true,
},
- noRouteChange: {
+ navigateOnClick: {
type: Boolean,
- default: false,
+ default: true,
},
})
@@ -165,14 +162,14 @@ const router = useRouter()
const { i18n: { t } } = composables.useI18n()
const controlPlaneId = computed((): string => props.config.app === 'konnect' ? props.config.controlPlaneId : '')
const isDisabled = computed((): boolean => !!(!props.plugin.available || props.plugin.disabledMessage))
-const hasActions = computed((): boolean => !!(isCustomPlugin.value && !isCreateCustomPlugin.value && !props.noRouteChange && controlPlaneId.value && (props.canDeleteCustomPlugin || props.canEditCustomPlugin)))
+const hasActions = computed((): boolean => !!(isCustomPlugin.value && !isCreateCustomPlugin.value && props.navigateOnClick && controlPlaneId.value && (props.canDeleteCustomPlugin || props.canEditCustomPlugin)))
const handleCreateClick = (): void => {
router.push(props.config.getCreateRoute(props.plugin.id))
}
const handleClick = (): void => {
- if (props.noRouteChange) {
+ if (!props.navigateOnClick) {
emitPluginData()
} else {
handleCreateClick()
@@ -221,6 +218,10 @@ const handleCustomClick = (): void => {
flex-flow: row-wrap;
max-width: 335px;
+ .actions-trigger {
+ color: $kui-color-text-neutral-stronger;
+ }
+
&.plugin-card-cursor-pointer {
cursor: pointer;
}
diff --git a/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue b/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
index dea9cd9c4c..2c096d2aa3 100644
--- a/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
+++ b/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
@@ -47,7 +47,7 @@
v-for="(plugin, index) in getPluginCards('visible', nonCustomPlugins[group as keyof PluginCardList] || [], pluginsPerRow)"
:key="`plugin-card-${index}`"
:config="config"
- :no-route-change="noRouteChange"
+ :navigate-on-click="navigateOnClick"
:plugin="plugin"
@plugin-clicked="emitPluginData"
/>
@@ -59,7 +59,7 @@
v-for="(plugin, index) in getPluginCards('hidden', nonCustomPlugins[group as keyof PluginCardList] || [], pluginsPerRow)"
:key="`plugin-card-${index}`"
:config="config"
- :no-route-change="noRouteChange"
+ :navigate-on-click="navigateOnClick"
:plugin="plugin"
@plugin-clicked="emitPluginData"
/>
@@ -104,12 +104,12 @@ const props = defineProps({
default: () => ({}),
},
/**
- * @param {boolean} noRouteChange if true, let consuming component handle event when clicking on a plugin
+ * @param {boolean} navigateOnClick if false, let consuming component handle event when clicking on a plugin
* Used in conjunction with `@plugin-clicked` event
*/
- noRouteChange: {
+ navigateOnClick: {
type: Boolean,
- default: false,
+ default: true,
},
/**
* Number of plugins to always have visible (never will be collapsed)
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e1ff6a0dbd..10fc058b6b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,9 +1,5 @@
lockfileVersion: '6.0'
-settings:
- autoInstallPeers: false
- excludeLinksFromLockfile: false
-
importers:
.:
@@ -666,6 +662,9 @@ importers:
'@kong-ui-public/entities-shared':
specifier: workspace:^
version: link:../entities-shared
+ '@kong/icons':
+ specifier: ^1.7.8
+ version: 1.7.8(vue@3.3.4)
devDependencies:
'@kong-ui-public/i18n':
specifier: workspace:^
@@ -2046,10 +2045,11 @@ packages:
resolution: {integrity: sha512-+VT9MeR3EcKXhst0KLoMme8dRYQef5skGaob1bR6xbyRXbwHG34ymLmYUeZ+ObIqISz7yTPGgarMJfDyzuX/YQ==}
engines: {node: '>=v16.20.2'}
peerDependencies:
+ axios: ^0.27.2
vue: '>= 3.3.4 < 4'
vue-router: ^4.2.4
dependencies:
- axios: 0.27.2
+ axios: 1.5.1
date-fns: 2.30.0
date-fns-tz: 2.0.0(date-fns@2.30.0)
focus-trap: 7.5.3
@@ -2062,8 +2062,6 @@ packages:
vue: 3.3.4
vue-draggable-next: 2.2.1(sortablejs@1.15.0)(vue@3.3.4)
vue-router: 4.2.5(vue@3.3.4)
- transitivePeerDependencies:
- - debug
/@kong/swagger-ui-kong-theme-universal@4.2.8(react-dom@17.0.2)(react@17.0.2)(vue-router@4.2.5)(vue@3.3.4):
resolution: {integrity: sha512-HS2Fjjd/tLWCmcEeRefzsDotsdNcF2d10L5ugzOYOuIMTRbCXq6wB7lTKJZQDCa7uy5syIeDuku1QSNpnmHOyw==}
@@ -2087,7 +2085,7 @@ packages:
util: 0.12.5
transitivePeerDependencies:
- '@babel/core'
- - debug
+ - axios
- mkdirp
- prop-types
- react-dom
@@ -4528,14 +4526,6 @@ packages:
resolution: {integrity: sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==}
dev: true
- /axios@0.27.2:
- resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
- dependencies:
- follow-redirects: 1.15.2
- form-data: 4.0.0
- transitivePeerDependencies:
- - debug
-
/axios@1.5.1:
resolution: {integrity: sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==}
dependencies:
@@ -14399,3 +14389,7 @@ packages:
/zenscroll@4.0.2:
resolution: {integrity: sha512-jEA1znR7b4C/NnaycInCU6h/d15ZzCd1jmsruqOKnZP6WXQSMH3W2GL+OXbkruslU4h+Tzuos0HdswzRUk/Vgg==}
dev: false
+
+settings:
+ autoInstallPeers: false
+ excludeLinksFromLockfile: false
From feef72fde09d997628004071da1c6fc03a3d1c95 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Tue, 31 Oct 2023 18:03:17 -0400
Subject: [PATCH 35/40] fix(*): tests
---
.../src/components/PluginSelect.cy.ts | 155 +++++++++---------
.../src/components/PluginSelect.vue | 6 +-
.../components/select/PluginSelectGrid.vue | 1 -
3 files changed, 85 insertions(+), 77 deletions(-)
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts b/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
index e4d5495c52..c2515102fd 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
@@ -1,5 +1,5 @@
// Cypress component test spec file
-import { PluginGroupArray, type KongManagerPluginFormConfig, type KonnectPluginFormConfig } from '../types'
+import { PluginGroupArray, PluginGroup, type KongManagerPluginFormConfig, type KonnectPluginFormConfig } from '../types'
import {
kmAvailablePlugins,
kmLimitedAvailablePlugins,
@@ -50,6 +50,19 @@ const baseConfigKM:KongManagerPluginFormConfig = {
}),
}
+// filter out these 3 groups because we currently don't have
+// any plugins for 'Deployment' and 'WebSocket Plugins'.
+// Custom plugins are not shown in Kong Manager and displayed
+// separately in Konnect.
+const PLUGIN_GROUPS_IN_USE = PluginGroupArray.filter((group: string) => {
+ if (group === PluginGroup.CUSTOM_PLUGINS || group === PluginGroup.DEPLOYMENT ||
+ group === PluginGroup.WEBSOCKET) {
+ return false
+ }
+
+ return true
+})
+
describe('', () => {
describe('Kong Manager', () => {
const interceptKM = (params?: {
@@ -86,9 +99,9 @@ describe('', () => {
cy.getTestId(`${firstShownPlugin}-card`).should('be.visible')
// plugin group collapses show
- for (const pluginGroup in PluginGroupArray.slice(-1)) {
- cy.get(`${pluginGroup}-collapse`).should('be.visible')
- }
+ PLUGIN_GROUPS_IN_USE.forEach((pluginGroup: string) => {
+ cy.get('[data-testid="k-collapse-title"]').should('contain.text', pluginGroup)
+ })
// renders all plugins
for (const pluginName in kmAvailablePlugins.plugins.available_on_server) {
cy.getTestId(`${pluginName}-card`).should('exist')
@@ -97,6 +110,7 @@ describe('', () => {
it('should allow customizing the pluginsPerRow', () => {
const pluginsPerRow = 3
+ const expectedCount = pluginsPerRow * PLUGIN_GROUPS_IN_USE.length
interceptKM()
@@ -111,7 +125,7 @@ describe('', () => {
cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
- cy.get('.k-collapse-visible-content .plugin-card').should('have.length', pluginsPerRow)
+ cy.get('.k-collapse-visible-content .plugin-card').should('have.length', expectedCount)
})
it('should correctly render disabled plugins', () => {
@@ -216,7 +230,7 @@ describe('', () => {
statusCode: 500,
body: {},
},
- ).as('getAvailablePlugins')
+ ).as('getAvailablePlugins2')
cy.mount(PluginSelect, {
props: {
@@ -224,11 +238,9 @@ describe('', () => {
},
})
- cy.wait('@getAvailablePlugins')
+ cy.wait('@getAvailablePlugins2')
cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
- cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
-
cy.getTestId('plugins-fetch-error').should('be.visible')
})
@@ -269,12 +281,11 @@ describe('', () => {
cy.getTestId(`${firstShownPlugin}-card`).should('be.visible')
cy.getTestId(`${firstShownPlugin}-card`).click()
- /* cy.get('@vueWrapper').then((wrapper: any) => wrapper.findComponent(PluginSelect)
- .vm.$emit('submit'))
- */
- cy.wait('@pluginClicked')
- cy.get('onPluginClickedSpy').should('have.been.calledOnce')
+ cy.get('@vueWrapper').then((wrapper: any) => wrapper.findComponent(PluginSelect)
+ .vm.$emit('plugin-clicked', {}))
+
+ cy.get('@onPluginClickedSpy').should('have.been.called')
})
})
@@ -295,55 +306,6 @@ describe('', () => {
).as(params?.alias ?? 'getAvailablePlugins')
}
- describe('actions', () => {
- it('should correctly render custom plugin actions', () => {
- interceptKonnect()
-
- cy.mount(PluginSelect, {
- props: {
- config: baseConfigKonnect,
- canEditCustomPlugin: () => { },
- canDeleteCustomPlugin: () => { },
- },
- })
-
- cy.wait('@getAvailablePlugins')
-
- // custom plugins
- cy.getTestId('custom-tab').should('be.visible')
- cy.getTestId('custom-tab').click()
- cy.get('.custom-plugins-grid').should('be.visible')
-
- cy.getTestId('overflow-actions-button').eq(0).click()
- cy.getTestId('edit-plugin-schema').should('be.visible')
- cy.getTestId('delete-plugin-schema').should('be.visible')
- // negative test for create
- cy.getTestId('custom-plugin-create-card').should('not.exist')
- })
-
- it('should render create card if user has create rights', () => {
- interceptKonnect()
-
- cy.mount(PluginSelect, {
- props: {
- config: baseConfigKonnect,
- canCreateCustomPlugin: () => { },
- },
- })
-
- cy.wait('@getAvailablePlugins')
-
- // custom plugins
- cy.getTestId('custom-tab').should('be.visible')
- cy.getTestId('custom-tab').click()
- cy.get('.custom-plugins-grid').should('be.visible')
-
- cy.getTestId('custom-plugin-create-card').should('be.visible')
- // negative test for edit and delete
- cy.getTestId('custom-plugin-actions').should('not.exist')
- })
- })
-
it('should show the select page tabs', () => {
interceptKonnect()
@@ -367,9 +329,9 @@ describe('', () => {
cy.getTestId(`${customPluginsNames[0]}-card`).should('not.be.visible')
// plugin group collapses show
- for (const pluginGroup in PluginGroupArray.slice(-1)) {
- cy.get(`${pluginGroup}-collapse`).should('be.visible')
- }
+ PLUGIN_GROUPS_IN_USE.forEach((pluginGroup: string) => {
+ cy.get('[data-testid="k-collapse-title"]').should('contain.text', pluginGroup)
+ })
// custom plugins
cy.getTestId('custom-tab').should('be.visible')
@@ -387,6 +349,7 @@ describe('', () => {
it('should allow customizing the pluginsPerRow', () => {
const pluginsPerRow = 3
+ const expectedCount = pluginsPerRow * PLUGIN_GROUPS_IN_USE.length
interceptKonnect()
@@ -401,7 +364,7 @@ describe('', () => {
cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
- cy.get('.k-collapse-visible-content .plugin-card').should('have.length', pluginsPerRow)
+ cy.get('.k-collapse-visible-content .plugin-card').should('have.length', expectedCount)
})
it('should correctly render disabled plugins', () => {
@@ -515,9 +478,8 @@ describe('', () => {
})
cy.wait('@getAvailablePlugins')
- cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
- cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
+ cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
cy.getTestId('plugins-fetch-error').should('be.visible')
})
@@ -559,12 +521,59 @@ describe('', () => {
cy.getTestId(`${firstShownPlugin}-card`).should('be.visible')
cy.getTestId(`${firstShownPlugin}-card`).click()
- /* cy.get('@vueWrapper').then((wrapper: any) => wrapper.findComponent(EntityBaseForm)
- .vm.$emit('submit')) */
+ cy.get('@vueWrapper').then((wrapper: any) => wrapper.findComponent(PluginSelect)
+ .vm.$emit('plugin-clicked', {}))
+
+ cy.get('@onPluginClickedSpy').should('have.been.called')
+ })
+
+ describe('actions', () => {
+ it('should correctly render custom plugin actions', () => {
+ interceptKonnect()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKonnect,
+ canEditCustomPlugin: () => { },
+ canDeleteCustomPlugin: () => { },
+ },
+ })
- cy.wait('@pluginClicked')
+ cy.wait('@getAvailablePlugins')
- cy.get('onPluginClickedSpy').should('have.been.calledOnce')
+ // custom plugins
+ cy.getTestId('custom-tab').should('be.visible')
+ cy.getTestId('custom-tab').click()
+ cy.get('.custom-plugins-grid').should('be.visible')
+
+ cy.getTestId('overflow-actions-button').eq(0).click()
+ cy.getTestId('edit-plugin-schema').should('be.visible')
+ cy.getTestId('delete-plugin-schema').should('be.visible')
+ // negative test for create
+ cy.getTestId('custom-plugin-create-card').should('not.exist')
+ })
+
+ it('should render create card if user has create rights', () => {
+ interceptKonnect()
+
+ cy.mount(PluginSelect, {
+ props: {
+ config: baseConfigKonnect,
+ canCreateCustomPlugin: () => { },
+ },
+ })
+
+ cy.wait('@getAvailablePlugins')
+
+ // custom plugins
+ cy.getTestId('custom-tab').should('be.visible')
+ cy.getTestId('custom-tab').click()
+ cy.get('.custom-plugins-grid').should('be.visible')
+
+ cy.getTestId('custom-plugin-create-card').should('be.visible')
+ // negative test for edit and delete
+ cy.getTestId('custom-plugin-actions').should('not.exist')
+ })
})
})
})
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index b1b244622c..bc55f116b6 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -30,8 +30,8 @@
@@ -239,7 +239,7 @@ const filteredPlugins = computed((): PluginCardList => {
const results = JSON.parse(JSON.stringify(pluginsList.value))
for (const type in pluginsList.value) {
- const matches = pluginsList.value[type as keyof PluginCardList]?.filter((plugin: PluginType) => plugin.name.toLowerCase().includes(query) || plugin.group.toLowerCase().includes(query)) || []
+ const matches = pluginsList.value[type as keyof PluginCardList]?.filter((plugin: PluginType) => plugin.name.toLowerCase().includes(query) || plugin.id.toLowerCase().includes(query) || plugin.group.toLowerCase().includes(query)) || []
if (!matches.length) {
delete results[type]
diff --git a/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue b/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
index 2c096d2aa3..d8c8b15517 100644
--- a/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
+++ b/packages/entities/entities-plugins/src/components/select/PluginSelectGrid.vue
@@ -30,7 +30,6 @@
From 1614b8448616d6a52a38387040a16d0bef552893 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Tue, 31 Oct 2023 21:46:21 -0400
Subject: [PATCH 36/40] fix(*): tests
---
.../entities-plugins/fixtures/mockData.ts | 150 +++++++++---------
.../src/components/PluginSelect.cy.ts | 73 ++++++---
.../src/components/PluginSelect.vue | 6 +-
.../custom-plugins/PluginCustomGrid.vue | 8 +-
4 files changed, 138 insertions(+), 99 deletions(-)
diff --git a/packages/entities/entities-plugins/fixtures/mockData.ts b/packages/entities/entities-plugins/fixtures/mockData.ts
index 3c0b93f1b3..72a763fd64 100644
--- a/packages/entities/entities-plugins/fixtures/mockData.ts
+++ b/packages/entities/entities-plugins/fixtures/mockData.ts
@@ -396,13 +396,86 @@ export const kmAvailablePlugins = {
}
// keep this list sorted alphabetically!!!
-export const customPluginsNames = [
- 'moesif',
+export const firstShownCustomPlugin = 'moesif'
+export const customPluginNames = [
+ firstShownCustomPlugin,
'myplugin',
'myplugin2',
'myplugin3',
'myplugin4',
]
+export const kongPluginNames = [
+ 'acl',
+ 'acme',
+ 'app-dynamics',
+ 'aws-lambda',
+ 'azure-functions',
+ firstShownPlugin,
+ 'bot-detection',
+ 'canary',
+ 'correlation-id',
+ 'cors',
+ 'datadog',
+ 'degraphql',
+ 'exit-transformer',
+ 'file-log',
+ 'forward-proxy',
+ 'graphql-proxy-cache-advanced',
+ 'graphql-rate-limiting-advanced',
+ 'grpc-gateway',
+ 'grpc-web',
+ 'hmac-auth',
+ 'http-log',
+ 'ip-restriction',
+ 'jq',
+ 'jwe-decrypt',
+ 'jwt',
+ 'kafka-log',
+ 'kafka-upstream',
+ 'key-auth',
+ 'ldap-auth',
+ 'ldap-auth-advanced',
+ 'loggly',
+ 'mocking',
+ 'mtls-auth',
+ 'oas-validation',
+ 'oauth2-introspection',
+ 'opa',
+ 'openid-connect',
+ 'opentelemetry',
+ 'post-function',
+ 'pre-function',
+ 'prometheus',
+ 'proxy-cache',
+ 'proxy-cache-advanced',
+ 'rate-limiting',
+ 'rate-limiting-advanced',
+ 'request-size-limiting',
+ 'request-termination',
+ 'request-transformer',
+ 'request-transformer-advanced',
+ 'request-validator',
+ 'response-ratelimiting',
+ 'response-transformer',
+ 'response-transformer-advanced',
+ 'route-by-header',
+ 'route-transformer-advanced',
+ 'saml',
+ 'session',
+ 'statsd',
+ 'statsd-advanced',
+ 'syslog',
+ 'tcp-log',
+ 'tls-handshake-modifier',
+ 'tls-metadata-headers',
+ 'udp-log',
+ // 'upstream-authenticator',
+ 'upstream-timeout',
+ 'websocket-size-limit',
+ 'websocket-validator',
+ 'xml-threat-protection',
+ 'zipkin',
+]
export const konnectLimitedAvailablePlugins = {
names: [
@@ -413,76 +486,7 @@ export const konnectLimitedAvailablePlugins = {
export const konnectAvailablePlugins = {
names: [
- 'acl',
- 'acme',
- 'app-dynamics',
- 'aws-lambda',
- 'azure-functions',
- firstShownPlugin,
- 'bot-detection',
- 'canary',
- 'correlation-id',
- 'cors',
- 'datadog',
- 'degraphql',
- 'exit-transformer',
- 'file-log',
- 'forward-proxy',
- 'graphql-proxy-cache-advanced',
- 'graphql-rate-limiting-advanced',
- 'grpc-gateway',
- 'grpc-web',
- 'hmac-auth',
- 'http-log',
- 'ip-restriction',
- 'jq',
- 'jwe-decrypt',
- 'jwt',
- 'kafka-log',
- 'kafka-upstream',
- 'key-auth',
- 'ldap-auth',
- 'ldap-auth-advanced',
- 'loggly',
- 'mocking',
- 'mtls-auth',
- ...customPluginsNames,
- 'oas-validation',
- 'oauth2-introspection',
- 'opa',
- 'openid-connect',
- 'opentelemetry',
- 'post-function',
- 'pre-function',
- 'prometheus',
- 'proxy-cache',
- 'proxy-cache-advanced',
- 'rate-limiting',
- 'rate-limiting-advanced',
- 'request-size-limiting',
- 'request-termination',
- 'request-transformer',
- 'request-transformer-advanced',
- 'request-validator',
- 'response-ratelimiting',
- 'response-transformer',
- 'response-transformer-advanced',
- 'route-by-header',
- 'route-transformer-advanced',
- 'saml',
- 'session',
- 'statsd',
- 'statsd-advanced',
- 'syslog',
- 'tcp-log',
- 'tls-handshake-modifier',
- 'tls-metadata-headers',
- 'udp-log',
- 'upstream-authenticator',
- 'upstream-timeout',
- 'websocket-size-limit',
- 'websocket-validator',
- 'xml-threat-protection',
- 'zipkin',
+ ...kongPluginNames,
+ ...customPluginNames,
],
}
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts b/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
index c2515102fd..63b5780fc7 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
@@ -6,8 +6,12 @@ import {
konnectAvailablePlugins,
konnectLimitedAvailablePlugins,
firstShownPlugin,
- customPluginsNames,
+ firstShownCustomPlugin,
+ kongPluginNames,
+ customPluginNames,
} from '../../fixtures/mockData'
+import type { Router } from 'vue-router'
+import { createMemoryHistory, createRouter } from 'vue-router'
import PluginSelect from './PluginSelect.vue'
const baseConfigKonnect: KonnectPluginFormConfig = {
@@ -290,6 +294,8 @@ describe('', () => {
})
describe('Konnect', () => {
+ // Create a new router instance for each test
+ let router: Router
const interceptKonnect = (params?: {
mockData?: object
alias?: string
@@ -306,6 +312,16 @@ describe('', () => {
).as(params?.alias ?? 'getAvailablePlugins')
}
+ beforeEach(() => {
+ // Initialize a new router before each test
+ router = createRouter({
+ routes: [
+ { path: '/', name: 'list-plugin', component: { template: 'ListPage
' } },
+ ],
+ history: createMemoryHistory(),
+ })
+ })
+
it('should show the select page tabs', () => {
interceptKonnect()
@@ -313,6 +329,7 @@ describe('', () => {
props: {
config: baseConfigKonnect,
},
+ router,
})
cy.wait('@getAvailablePlugins')
@@ -322,11 +339,16 @@ describe('', () => {
cy.get('.plugins-filter-input-container').should('be.visible')
// kong plugins
- cy.getTestId('kong-tab').should('be.visible')
+ cy.get('#kong-tab').should('be.visible')
cy.get('.plugin-select-grid').should('be.visible')
// kong visible / custom not
cy.getTestId(`${firstShownPlugin}-card`).should('be.visible')
- cy.getTestId(`${customPluginsNames[0]}-card`).should('not.be.visible')
+ cy.getTestId(`${firstShownCustomPlugin}-card`).should('not.exist')
+
+ // renders all plugins (kong)
+ kongPluginNames.forEach((pluginName: string) => {
+ cy.getTestId(`${pluginName}-card`).should('exist')
+ })
// plugin group collapses show
PLUGIN_GROUPS_IN_USE.forEach((pluginGroup: string) => {
@@ -334,17 +356,17 @@ describe('', () => {
})
// custom plugins
- cy.getTestId('custom-tab').should('be.visible')
- cy.getTestId('custom-tab').click()
+ cy.get('#custom-tab').should('be.visible')
+ cy.get('#custom-tab').click()
cy.get('.custom-plugins-grid').should('be.visible')
// kong hidden / custom not
- cy.getTestId(`${firstShownPlugin}-card`).should('not.be.visible')
- cy.getTestId(`${customPluginsNames[0]}-card`).should('be.visible')
+ cy.getTestId(`${firstShownPlugin}-card`).should('not.exist')
+ cy.getTestId(`${firstShownCustomPlugin}-card`).should('be.visible')
- // renders all plugins (kong + custom)
- for (const pluginName in konnectAvailablePlugins.names) {
+ // renders all plugins (custom)
+ customPluginNames.forEach((pluginName: string) => {
cy.getTestId(`${pluginName}-card`).should('exist')
- }
+ })
})
it('should allow customizing the pluginsPerRow', () => {
@@ -358,6 +380,7 @@ describe('', () => {
config: baseConfigKonnect,
pluginsPerRow,
},
+ router,
})
cy.wait('@getAvailablePlugins')
@@ -377,6 +400,7 @@ describe('', () => {
config: baseConfigKonnect,
disabledPlugins,
},
+ router,
})
cy.wait('@getAvailablePlugins')
@@ -399,6 +423,7 @@ describe('', () => {
config: baseConfigKonnect,
ignoredPlugins,
},
+ router,
})
cy.wait('@getAvailablePlugins')
@@ -421,6 +446,7 @@ describe('', () => {
config: baseConfigKonnect,
showAvailableOnly: true,
},
+ router,
})
cy.wait('@getAvailablePlugins')
@@ -444,6 +470,7 @@ describe('', () => {
props: {
config: baseConfigKonnect,
},
+ router,
})
cy.wait('@getAvailablePlugins')
@@ -475,6 +502,7 @@ describe('', () => {
props: {
config: baseConfigKonnect,
},
+ router,
})
cy.wait('@getAvailablePlugins')
@@ -490,6 +518,7 @@ describe('', () => {
props: {
config: baseConfigKonnect,
},
+ router,
})
cy.wait('@getAvailablePlugins')
@@ -513,6 +542,7 @@ describe('', () => {
onPluginClicked: cy.spy().as('onPluginClickedSpy'),
navigateOnClick: false,
},
+ router,
}).then(({ wrapper }) => wrapper)
.as('vueWrapper')
@@ -527,26 +557,28 @@ describe('', () => {
cy.get('@onPluginClickedSpy').should('have.been.called')
})
- describe('actions', () => {
+ describe('custom plugin actions', () => {
it('should correctly render custom plugin actions', () => {
interceptKonnect()
cy.mount(PluginSelect, {
props: {
config: baseConfigKonnect,
- canEditCustomPlugin: () => { },
- canDeleteCustomPlugin: () => { },
+ canCreateCustomPlugin: () => false,
+ canEditCustomPlugin: () => true,
+ canDeleteCustomPlugin: () => true,
},
+ router,
})
cy.wait('@getAvailablePlugins')
// custom plugins
- cy.getTestId('custom-tab').should('be.visible')
- cy.getTestId('custom-tab').click()
+ cy.get('#custom-tab').should('be.visible')
+ cy.get('#custom-tab').click()
cy.get('.custom-plugins-grid').should('be.visible')
- cy.getTestId('overflow-actions-button').eq(0).click()
+ cy.getTestId('custom-plugin-actions').eq(0).click()
cy.getTestId('edit-plugin-schema').should('be.visible')
cy.getTestId('delete-plugin-schema').should('be.visible')
// negative test for create
@@ -559,15 +591,18 @@ describe('', () => {
cy.mount(PluginSelect, {
props: {
config: baseConfigKonnect,
- canCreateCustomPlugin: () => { },
+ canCreateCustomPlugin: () => true,
+ canEditCustomPlugin: () => false,
+ canDeleteCustomPlugin: () => false,
},
+ router,
})
cy.wait('@getAvailablePlugins')
// custom plugins
- cy.getTestId('custom-tab').should('be.visible')
- cy.getTestId('custom-tab').click()
+ cy.get('#custom-tab').should('be.visible')
+ cy.get('#custom-tab').click()
cy.get('.custom-plugins-grid').should('be.visible')
cy.getTestId('custom-plugin-create-card').should('be.visible')
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.vue b/packages/entities/entities-plugins/src/components/PluginSelect.vue
index bc55f116b6..70d3b803ae 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.vue
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.vue
@@ -87,9 +87,9 @@
Date: Tue, 31 Oct 2023 22:31:11 -0400
Subject: [PATCH 37/40] fix(*): pr feedback
---
.../src/components/select/PluginCardSkeleton.vue | 11 +++++++++++
.../src/components/select/PluginSelectCard.vue | 2 +-
.../src/components/select/PluginSelectGrid.vue | 2 +-
3 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/packages/entities/entities-plugins/src/components/select/PluginCardSkeleton.vue b/packages/entities/entities-plugins/src/components/select/PluginCardSkeleton.vue
index 026b24e3fc..60521f460f 100644
--- a/packages/entities/entities-plugins/src/components/select/PluginCardSkeleton.vue
+++ b/packages/entities/entities-plugins/src/components/select/PluginCardSkeleton.vue
@@ -34,6 +34,14 @@ defineProps({
diff --git a/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue b/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
index fd00a23686..94c03d6f2a 100644
--- a/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
+++ b/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
@@ -216,7 +216,6 @@ const handleCustomClick = (): void => {
display: flex;
flex-basis: 100%;
flex-flow: row-wrap;
- width: 335px;
.actions-trigger {
color: $kui-color-text-neutral-stronger;
@@ -299,27 +298,44 @@ const handleCustomClick = (): void => {
height: 100%;
}
- :deep(.k-plugin-card-body) {
- display: flex;
- flex-direction: column;
+ @media (min-width: $kui-breakpoint-phablet) {
+ flex-basis: calc(50% - #{$kui-space-80});
}
- :deep(.k-card-header) {
+ @media (min-width: $kui-breakpoint-tablet) {
+ flex-basis: calc(33.33333% - #{$kui-space-80});
+ }
+
+ @media (min-width: $kui-breakpoint-laptop) {
+ flex-basis: calc(25% - #{$kui-space-80});
+ }
+}
+
+
+
From 2a6f8d18a5e10d3f2ff9444699d8439092273516 Mon Sep 17 00:00:00 2001
From: "kai.arrowood"
Date: Wed, 1 Nov 2023 00:14:30 -0400
Subject: [PATCH 40/40] fix(*): styles
---
.../src/components/PluginSelect.cy.ts | 8 +--
.../components/select/PluginSelectCard.vue | 53 +++++++------------
2 files changed, 24 insertions(+), 37 deletions(-)
diff --git a/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts b/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
index 63b5780fc7..ff5bf0d8dd 100644
--- a/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
+++ b/packages/entities/entities-plugins/src/components/PluginSelect.cy.ts
@@ -129,7 +129,7 @@ describe('', () => {
cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
- cy.get('.k-collapse-visible-content .plugin-card').should('have.length', expectedCount)
+ cy.get('.k-collapse-visible-content .plugin-card-content').should('have.length', expectedCount)
})
it('should correctly render disabled plugins', () => {
@@ -221,7 +221,7 @@ describe('', () => {
cy.getTestId('plugins-filter').type(firstShownPlugin)
cy.getTestId(`${firstShownPlugin}-card`).should('be.visible')
- cy.get('.plugin-card').should('have.length', 1)
+ cy.get('.plugin-card-content').should('have.length', 1)
})
it('should handle error state - available plugins failed to load', () => {
@@ -387,7 +387,7 @@ describe('', () => {
cy.get('.kong-ui-entities-plugin-select-form').should('be.visible')
cy.get('.kong-ui-entities-plugin-select-form .plugins-results-container').should('be.visible')
- cy.get('.k-collapse-visible-content .plugin-card').should('have.length', expectedCount)
+ cy.get('.k-collapse-visible-content .plugin-card-content').should('have.length', expectedCount)
})
it('should correctly render disabled plugins', () => {
@@ -483,7 +483,7 @@ describe('', () => {
cy.getTestId('plugins-filter').type(firstShownPlugin)
cy.getTestId(`${firstShownPlugin}-card`).should('be.visible')
- cy.get('.plugin-card').should('have.length', 1)
+ cy.get('.plugin-card-content').should('have.length', 1)
})
it('should handle error state - available plugins failed to load', () => {
diff --git a/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue b/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
index 94c03d6f2a..746a2ff6f4 100644
--- a/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
+++ b/packages/entities/entities-plugins/src/components/select/PluginSelectCard.vue
@@ -211,20 +211,18 @@ const handleCustomClick = (): void => {
-
-