Skip to content

Commit b95a9eb

Browse files
authored
feat(entities-shared): JSON/YAML forms [KHCP-9967][KHCP-9968][KHCP-9986] (#1006)
* feat(entities-shared): create a KSlideout wrapper around JSON/YAML for forms [KHCP-9892] * refactor(entities-shared): refactor JsonCodeBlock and YamlCodeBlock for reuse across config tables and forms * fix(entities-shared): fix props and refs for JSON/YAML * fix(entities-shared): add styles to slideout * fix(entities-shared): make jsonContent and yamlContent reactive * fix(entities-shared): fix style to match figma * fix(entities-shared): hide behind Milestone2 FF * test(entities-shared): add component tests * fix(entities-shared): update sandbox * fix(entities-shared): hide get api endpoint behind Milestone2 FF * fix(entities-shared): hide behind Milestone2 FF * fix(entities-shared): hide created_at and updated_at from json/yaml config * test(entities-shared): fix component tests * fix(entities-shared): fix jsonOrYaml record * fix(entities-shared): fix config in EntityBaseForm and check in ConfigCardDisplay * fix(entities-shared): strip placeholder id from post request url * fix(entities-shared): fix incorrect slot * test(entities-shared): add new component tests for JsonCodeBlock and YamlCodeBlock * fix(entities-shared): stop slideout close click from propogating * test(entities-shared): add new component tests for JsonCodeBlock and YamlCodeBlock * fix(entities-shared): cleanup * fix(entities-shared): add code comments and types
1 parent b38f94a commit b95a9eb

File tree

33 files changed

+583
-139
lines changed

33 files changed

+583
-139
lines changed

packages/entities/entities-certificates/src/components/CACertificateForm.vue

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
:edit-id="certificateId"
77
:error-message="form.errorMessage"
88
:fetch-url="fetchUrl"
9+
:form-fields="form.fields"
910
:is-readonly="form.isReadonly"
1011
@cancel="handleClickCancel"
1112
@fetch:error="(err: any) => $emit('error', err)"

packages/entities/entities-certificates/src/components/CertificateForm.vue

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
:edit-id="certificateId"
77
:error-message="form.errorMessage"
88
:fetch-url="fetchUrl"
9+
:form-fields="form.fields"
910
:is-readonly="form.isReadonly"
1011
@cancel="handleClickCancel"
1112
@fetch:error="(err: any) => $emit('error', err)"

packages/entities/entities-consumer-groups/src/components/ConsumerGroupForm.vue

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
:edit-id="consumerGroupId"
77
:error-message="state.errorMessage || fetchError || preValidateErrorMessage"
88
:fetch-url="fetchUrl"
9+
:form-fields="state.fields"
910
:is-readonly="state.readonly"
1011
@cancel="cancelHandler"
1112
@fetch:error="fetchErrorHandler($event)"

packages/entities/entities-consumers/src/components/ConsumerForm.vue

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
:edit-id="consumerId"
77
:error-message="state.errorMessage"
88
:fetch-url="fetchUrl"
9+
:form-fields="state.fields"
910
:is-readonly="state.readonly"
1011
@cancel="cancelHandler"
1112
@fetch:error="fetchErrorHandler($event)"

packages/entities/entities-gateway-services/src/components/GatewayServiceForm.vue

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
:edit-id="gatewayServiceId"
77
:error-message="form.errorMessage"
88
:fetch-url="fetchUrl"
9+
:form-fields="form.fields"
910
:is-readonly="form.isReadonly"
1011
@cancel="handleClickCancel"
1112
@fetch:error="(err: any) => $emit('error', err)"

packages/entities/entities-key-sets/src/components/KeySetForm.vue

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
:edit-id="keySetId"
77
:error-message="form.errorMessage"
88
:fetch-url="fetchUrl"
9+
:form-fields="form.fields"
910
:is-readonly="form.isReadonly"
1011
@cancel="handleClickCancel"
1112
@fetch:error="(err: any) => $emit('error', err)"

packages/entities/entities-keys/src/components/KeyForm.vue

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
:edit-id="keyId"
77
:error-message="form.errorMessage || fetchKeySetsErrorMessage"
88
:fetch-url="fetchUrl"
9+
:form-fields="form.fields"
910
:is-readonly="form.isReadonly"
1011
@cancel="handleClickCancel"
1112
@fetch:error="(err: any) => $emit('error', err)"

packages/entities/entities-plugins/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
"extends": "../../../package.json"
6666
},
6767
"distSizeChecker": {
68-
"errorLimit": "2.0MB"
68+
"errorLimit": "2.5MB"
6969
},
7070
"dependencies": {
7171
"@kong-ui-public/copy-uuid": "workspace:^",

packages/entities/entities-plugins/sandbox/pages/PluginFormPage.vue

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ const konnectConfig = ref<KonnectPluginFormConfig>({
5050
// entityId: '6f1ef200-d3d4-4979-9376-726f2216d90c',
5151
backRoute: { name: 'select-plugin' },
5252
cancelRoute: { name: 'home' },
53+
jsonYamlMilestone2Enabled: true,
5354
})
5455
5556
const kongManagerConfig = ref<KongManagerPluginFormConfig>({

packages/entities/entities-plugins/src/components/PluginForm.vue

+93-3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
:edit-id="pluginId"
2626
:error-message="form.errorMessage"
2727
:fetch-url="fetchUrl"
28+
:form-fields="form.fields"
2829
:is-readonly="form.isReadonly"
2930
@cancel="handleClickCancel"
3031
@fetch:error="(err: any) => $emit('error', err)"
@@ -54,9 +55,22 @@
5455
- if isWizardStep is true we don't want any buttons displayed (default EntityBaseForm buttons included)
5556
-->
5657
<div v-if="isWizardStep" />
57-
<div v-else>
58+
<div
59+
v-else
60+
class="plugin-form-actions"
61+
>
62+
<div v-if="config.jsonYamlMilestone2Enabled">
63+
<KButton
64+
appearance="tertiary"
65+
data-testid="form-view-configuration"
66+
@click="toggle()"
67+
>
68+
{{ t('actions.view_configuration') }}
69+
</KButton>
70+
</div>
5871
<KButton
5972
appearance="secondary"
73+
class="form-action-button"
6074
data-testid="form-cancel"
6175
:disabled="form.isReadonly"
6276
type="reset"
@@ -67,7 +81,7 @@
6781
<KButton
6882
v-if="formType === EntityBaseFormType.Create && config.backRoute"
6983
appearance="secondary"
70-
class="form-back-button"
84+
class="form-action-button"
7185
data-testid="form-back"
7286
:disabled="form.isReadonly"
7387
@click="handleClickBack"
@@ -86,6 +100,35 @@
86100
</div>
87101
</template>
88102
</EntityBaseForm>
103+
<KSlideout
104+
close-button-alignment="end"
105+
data-testid="form-view-configuration-slideout"
106+
:has-overlay="false"
107+
:is-visible="isToggled"
108+
prevent-close-on-blur
109+
:title="t('view_configuration.title')"
110+
@close="toggle"
111+
>
112+
<div>
113+
{{ t('view_configuration.message') }}
114+
</div>
115+
<KTabs
116+
data-testid="form-view-configuration-slideout-tabs"
117+
:tabs="tabs"
118+
>
119+
<template #json>
120+
<JsonCodeBlock
121+
:config="config"
122+
:fetcher-url="submitUrl"
123+
:json-record="form.fields"
124+
:request-method="props.pluginId ? 'put' : 'post'"
125+
/>
126+
</template>
127+
<template #yaml>
128+
<YamlCodeBlock :yaml-record="form.fields" />
129+
</template>
130+
</KTabs>
131+
</KSlideout>
89132
</div>
90133
</template>
91134

@@ -111,6 +154,9 @@ import endpoints from '../plugins-endpoints'
111154
import composables from '../composables'
112155
import { ArrayStringFieldSchema } from '../composables/plugin-schemas/ArrayStringFieldSchema'
113156
import PluginEntityForm from './PluginEntityForm.vue'
157+
import type { Tab } from '@kong/kongponents'
158+
import JsonCodeBlock from '../../../entities-shared/src/components/common/JsonCodeBlock.vue'
159+
import YamlCodeBlock from '../../../entities-shared/src/components/common/YamlCodeBlock.vue'
114160
115161
const emit = defineEmits<{
116162
(e: 'error:fetch-schema', error: AxiosError): void,
@@ -199,6 +245,7 @@ const { axiosInstance } = useAxios({
199245
headers: props.config.requestHeaders,
200246
})
201247
248+
const isToggled = ref(false)
202249
const formType = computed((): EntityBaseFormType => props.pluginId ? EntityBaseFormType.Edit : EntityBaseFormType.Create)
203250
const schema = ref<Record<string, any> | null>(null)
204251
const treatAsCredential = computed((): boolean => !!(props.credential && props.config.entityId))
@@ -221,6 +268,17 @@ const form = reactive<PluginFormState>({
221268
errorMessage: '',
222269
})
223270
271+
const tabs = ref<Tab[]>([
272+
{
273+
title: t('view_configuration.yaml'),
274+
hash: '#yaml',
275+
},
276+
{
277+
title: t('view_configuration.json'),
278+
hash: '#json',
279+
},
280+
])
281+
224282
const fetchUrl = computed((): string => {
225283
if (treatAsCredential.value) { // credential
226284
let submitEndpoint = endpoints.form[props.config.app].credential[formType.value]
@@ -241,6 +299,10 @@ const fetchUrl = computed((): string => {
241299
}
242300
})
243301
302+
const toggle = (): void => {
303+
isToggled.value = !isToggled.value
304+
}
305+
244306
const entityMap = computed((): Record<string, PluginEntityInfo> => {
245307
const consumerId = (props.config.entityType === 'consumers' && props.config.entityId) || record.value?.consumer?.id
246308
const consumerGroupId = (props.config.entityType === 'consumer_groups' && props.config.entityId) || record.value?.consumer_group?.id
@@ -1035,8 +1097,36 @@ onBeforeMount(async () => {
10351097
.kong-ui-entities-plugin-form-container {
10361098
width: 100%;
10371099
1038-
.form-back-button {
1100+
.form-action-button {
10391101
margin-left: $kui-space-60;
10401102
}
1103+
1104+
.plugin-form-actions {
1105+
display: flex;
1106+
}
1107+
1108+
& :deep(.k-slideout-title) {
1109+
color: $kui-color-text !important;
1110+
font-size: $kui-font-size-70 !important;
1111+
font-weight: $kui-font-weight-bold !important;
1112+
line-height: $kui-line-height-60 !important;
1113+
margin-bottom: $kui-space-60 !important;
1114+
}
1115+
1116+
& :deep(.k-card.content-card) {
1117+
padding: $kui-space-0 $kui-space-60 !important;
1118+
}
1119+
1120+
& :deep(.tab-item > div.tab-link.has-panels) {
1121+
color: $kui-color-text-neutral !important;
1122+
font-size: $kui-font-size-30 !important;
1123+
font-weight: $kui-font-weight-bold !important;
1124+
line-height: $kui-line-height-40 !important;
1125+
}
1126+
1127+
& :deep(.tab-item.active > div.tab-link.has-panels) {
1128+
color: $kui-color-text !important;
1129+
font-weight: $kui-font-weight-semibold !important;
1130+
}
10411131
}
10421132
</style>

packages/entities/entities-plugins/src/components/custom-plugins/DeleteCustomPluginSchemaModal.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
<template #action-buttons>
4141
<div>
4242
<KButton
43-
appearance="outline"
43+
appearance="tertiary"
4444
class="cancel-button"
4545
data-testid="delete-custom-plugin-form-cancel"
4646
@click="$emit('closed')"

packages/entities/entities-plugins/src/locales/en.json

+7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"actions": {
33
"back": "Back",
44
"cancel": "Cancel",
5+
"view_configuration": "View Configuration",
56
"create": "New Plugin",
67
"create_custom": "Create",
78
"copy_id": "Copy ID",
@@ -16,6 +17,12 @@
1617
"go_to_plugins": "Go to Plugins",
1718
"save": "Save"
1819
},
20+
"view_configuration": {
21+
"title": "Configuration",
22+
"message": "Configuring your API has never been easier. Use YAML for human-friendly simplicity or JSON for machine-readability. Streamline your settings effortlessly.",
23+
"yaml": "YAML",
24+
"json": "JSON"
25+
},
1926
"delete": {
2027
"title": "Delete a Plugin",
2128
"custom_title": "Delete {name}",

packages/entities/entities-routes/src/components/RouteForm.vue

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
:edit-id="routeId"
77
:error-message="form.errorMessage || fetchServicesErrorMessage"
88
:fetch-url="fetchUrl"
9+
:form-fields="form.fields"
910
:is-readonly="form.isReadonly"
1011
@cancel="cancelHandler"
1112
@fetch:error="fetchErrorHandler"

packages/entities/entities-shared/docs/config-card-display.md

+10
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ List types of element in the item object.
5454

5555
The base record configuration when format is either JSON or YAML.
5656

57+
#### `fetcherUrl`
58+
59+
- type: `String`
60+
- required: `false`
61+
- default: `''`
62+
63+
Fetcher url for the entity with the filled-in controlPlaneId, workspace, and entity id.
64+
65+
ex. `https://cloud.konghq.com/us/gateway-manager/91e192e0-5981-4662-a37d-7b24272c9da3/routes/0af86198-9822-46e0-9028-47b173caf4aa`
66+
5767
### Usage example
5868

5969
Please refer to the [sandbox](../sandbox/pages/ConfigCardDisplayPage.vue).

packages/entities/entities-shared/docs/entity-base-form.md

+7
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ A Boolean to indicate if the form can be submitted. Used to track form validatio
114114

115115
If form submission fails, this is the error message to display.
116116

117+
#### `formFields`
118+
119+
- type: `Object as PropType<Record<string, any>>`
120+
- required: `true`
121+
122+
A record to indicate the form fields present in a form. Used to populate the Configuration JSON/YAML code blocks.
123+
117124
### Events
118125

119126
#### loading

packages/entities/entities-shared/sandbox/pages/ConfigCardDisplayPage.vue

+18-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99

1010
<h2>Format: JSON</h2>
1111
<ConfigCardDisplay
12-
fetcher-url="https://cloud.konghq.com/us/gateway-manager/91e192e0-5981-4662-a37d-7b24272c9da3/routes/0af86198-9822-46e0-9028-47b173caf4aa"
12+
:config="konnectConfig"
13+
:fetcher-url="`${konnectConfig?.apiBaseUrl}${konnectFetchUrl}`"
1314
format="json"
1415
:record="record"
1516
/>
@@ -23,8 +24,24 @@
2324
</template>
2425

2526
<script setup lang="ts">
27+
import { ref } from 'vue'
2628
import { ConfigCardDisplay } from '../../src'
2729
30+
import type { KonnectBaseEntityConfig } from '../../src'
31+
32+
const controlPlaneId = import.meta.env.VITE_KONNECT_CONTROL_PLANE_ID || ''
33+
const entityId = 'ce83dd74-6455-40a9-b944-0f393c7ee22c'
34+
const konnectFetchUrl = ref(`/api/runtime_groups/${controlPlaneId}/services/${entityId}`)
35+
36+
const konnectConfig = ref<KonnectBaseEntityConfig>({
37+
app: 'konnect',
38+
apiBaseUrl: '/us/kong-api/konnect-api', // `/{geo}/kong-api`, with leading slash and no trailing slash; Consuming app would pass in something like `https://us.api.konghq.com`
39+
// Set the root `.env.development.local` variable to a control plane your PAT can access
40+
controlPlaneId,
41+
entityId,
42+
jsonYamlMilestone2Enabled: true,
43+
})
44+
2845
const item = {
2946
basic:
3047
[

packages/entities/entities-shared/sandbox/pages/EntityBaseFormPage.vue

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
:can-submit="canSubmit"
55
:config="konnectConfig"
66
:error-message="form.errorMessage"
7+
:fetch-url="konnectFetchUrl"
8+
:form-fields="form.fields"
79
:is-readonly="form.isReadonly"
810
@cancel="handleCancel"
911
@submit="handleSave"
@@ -46,13 +48,15 @@ import type { KonnectBaseFormConfig } from '../../src'
4648
import { EntityBaseForm, EntityFormSection } from '../../src'
4749
4850
const controlPlaneId = import.meta.env.VITE_KONNECT_CONTROL_PLANE_ID || ''
51+
const konnectFetchUrl = ref(`/api/runtime_groups/${controlPlaneId}/services`)
4952
5053
const konnectConfig = ref<KonnectBaseFormConfig>({
5154
app: 'konnect',
5255
apiBaseUrl: '/us/kong-api/konnect-api', // `/{geo}/kong-api`, with leading slash and no trailing slash; Consuming app would pass in something like `https://us.api.konghq.com`
5356
// Set the root `.env.development.local` variable to a control plane your PAT can access
5457
controlPlaneId,
5558
cancelRoute: { name: '/' },
59+
jsonYamlMilestone2Enabled: true,
5660
})
5761
5862
const canSubmit = computed((): boolean => !!form.fields.name)

0 commit comments

Comments
 (0)