Skip to content

Commit 27badfb

Browse files
committed
feat(entities): use declarative config as datasource
1 parent 8f3ec43 commit 27badfb

File tree

14 files changed

+414
-59
lines changed

14 files changed

+414
-59
lines changed

packages/entities/entities-routes/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"@kong/kongponents": "9.32.6",
3838
"@types/lodash.isequal": "^4.5.8",
3939
"axios": "^1.7.7",
40+
"js-yaml": "^4.1.0",
4041
"vite-plugin-monaco-editor": "^1.1.0",
4142
"vue": "^3.5.13",
4243
"vue-router": "^4.4.5"

packages/entities/entities-routes/sandbox/pages/RouteListPage.vue

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@
2727
<h2>Konnect Actions Outside</h2>
2828
<div id="kong-ui-app-page-header-action-button" />
2929

30+
<h2>Declarative Config</h2>
31+
<RouteList
32+
:key="declarativeKey"
33+
cache-identifier="declarative"
34+
:config="declarativeKonnectConfig"
35+
/>
36+
3037
<h2>Konnect API</h2>
3138
<RouteList
3239
v-if="permissions"
@@ -68,15 +75,58 @@
6875
</template>
6976

7077
<script setup lang="ts">
71-
import { ref, watch } from 'vue'
72-
import type { AxiosError } from 'axios'
73-
import { RouteList } from '../../src'
74-
import type { KonnectRouteListConfig, KongManagerRouteListConfig, EntityRow, CopyEventPayload } from '../../src'
7578
import type { PermissionsActions } from '@entities-shared-sandbox/components/SandboxPermissionsControl.vue'
7679
import SandboxPermissionsControl from '@entities-shared-sandbox/components/SandboxPermissionsControl.vue'
80+
import type { AxiosError } from 'axios'
81+
import { onMounted, reactive, ref, watch } from 'vue'
82+
import type { CopyEventPayload, EntityRow, KongManagerRouteListConfig, KonnectRouteListConfig } from '../../src'
83+
import { RouteList } from '../../src'
7784
7885
const controlPlaneId = import.meta.env.VITE_KONNECT_CONTROL_PLANE_ID || ''
7986
87+
const declarativeKonnectConfig = reactive<KonnectRouteListConfig>({
88+
app: 'konnect',
89+
apiBaseUrl: '/us/kong-api',
90+
controlPlaneId,
91+
createRoute: { name: 'create-route' },
92+
getViewRoute: () => ({ }),
93+
getEditRoute: () => ({ }),
94+
declarative: {
95+
config: {
96+
routes: [],
97+
services: [],
98+
listeners: [],
99+
},
100+
filterSchema: {
101+
name: {
102+
type: 'text',
103+
},
104+
matchPath: {
105+
type: 'text',
106+
},
107+
},
108+
},
109+
})
110+
111+
onMounted(() => {
112+
setTimeout(() => {
113+
declarativeKonnectConfig.declarative.config = {
114+
routes: [
115+
{
116+
name: 'example-route-1',
117+
match: {
118+
path: '/example-1',
119+
},
120+
policies: [],
121+
},
122+
],
123+
services: [],
124+
listeners: [],
125+
}
126+
declarativeKey.value++
127+
}, 1000)
128+
})
129+
80130
const konnectConfig = ref<KonnectRouteListConfig>({
81131
app: 'konnect',
82132
apiBaseUrl: '/us/kong-api', // `/{geo}/kong-api, with leading slash and no trailing slash
@@ -116,6 +166,7 @@ const kongManagerConfig = ref<KongManagerRouteListConfig>({
116166
117167
// Remount the tables in the sandbox when the permission props change; not needed outside of a sandbox
118168
const key = ref(1)
169+
const declarativeKey = ref(1)
119170
const permissions = ref<PermissionsActions | null>(null)
120171
121172
const isRouteListControlCollapsed = ref<boolean>(true)

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

Lines changed: 110 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<EntityFilter
2727
v-model="filterQuery"
2828
:config="filterConfig"
29+
@update:fuzzy-filters="fuzzyFilters = $event"
2930
/>
3031
</template>
3132
<!-- Create action -->
@@ -189,9 +190,23 @@
189190
{{ formatUnixTimeStamp(rowValue ?? row.created_at) }}
190191
</template>
191192

193+
<!-- Declarative columns -->
194+
<template #matchPath="{ row }">
195+
<span class="route-list-cell-match-path">
196+
{{ row.match.path }}
197+
</span>
198+
</template>
199+
200+
<template #policies="{ row }">
201+
{{ row.policies.length }}
202+
</template>
203+
192204
<!-- Row actions -->
193205
<template #actions="{ row }">
194-
<KClipboardProvider v-slot="{ copyToClipboard }">
206+
<KClipboardProvider
207+
v-if="!props.config.declarative"
208+
v-slot="{ copyToClipboard }"
209+
>
195210
<KDropdownItem
196211
data-testid="action-entity-copy-id"
197212
@click="copyId(row, copyToClipboard)"
@@ -207,20 +222,29 @@
207222
{{ t('actions.copy_json') }}
208223
</KDropdownItem>
209224
</KClipboardProvider>
210-
<PermissionsWrapper :auth-function="() => canRetrieve(row)">
225+
<PermissionsWrapper
226+
v-if="!props.config.declarative"
227+
:auth-function="() => canRetrieve(row)"
228+
>
211229
<KDropdownItem
212230
data-testid="action-entity-view"
213231
has-divider
214232
:item="getViewDropdownItem(row.id)"
215233
/>
216234
</PermissionsWrapper>
217-
<PermissionsWrapper :auth-function="() => canEdit(row)">
235+
<PermissionsWrapper
236+
v-if="!props.config.declarative"
237+
:auth-function="() => canEdit(row)"
238+
>
218239
<KDropdownItem
219240
data-testid="action-entity-edit"
220241
:item="getEditDropdownItem(row.id)"
221242
/>
222243
</PermissionsWrapper>
223-
<PermissionsWrapper :auth-function="() => canDelete(row)">
244+
<PermissionsWrapper
245+
v-if="!props.config.declarative"
246+
:auth-function="() => canDelete(row)"
247+
>
224248
<KDropdownItem
225249
danger
226250
data-testid="action-entity-delete"
@@ -248,47 +272,49 @@
248272
</template>
249273

250274
<script setup lang="ts">
251-
import type { PropType } from 'vue'
252-
import { computed, ref, watch, onBeforeMount } from 'vue'
253-
import type { AxiosError } from 'axios'
254-
import { useRouter } from 'vue-router'
255-
256-
import { BadgeMethodAppearances } from '@kong/kongponents'
257-
import type { BadgeMethodAppearance, HeaderTag } from '@kong/kongponents'
258-
import { AddIcon, ForwardIcon, BookIcon } from '@kong/icons'
275+
import type {
276+
BaseTableHeaders,
277+
DeclarativeRoute,
278+
EmptyStateOptions,
279+
ExactMatchFilterConfig,
280+
FilterFields,
281+
FuzzyMatchFilterConfig,
282+
FuzzyMatchFilters,
283+
TableErrorMessage,
284+
} from '@kong-ui-public/entities-shared'
259285
import {
260286
EntityBaseTable,
261287
EntityDeleteModal,
288+
EntityEmptyState,
262289
EntityFilter,
263290
EntityTypes,
264291
FetcherStatus,
265-
EntityEmptyState,
266292
PermissionsWrapper,
293+
TableTags,
267294
useAxios,
295+
useDeclarativeRoutesFetcher,
296+
useDeleteUrlBuilder,
268297
useFetcher,
269298
useTableState,
270-
useDeleteUrlBuilder,
271-
TableTags,
272299
} from '@kong-ui-public/entities-shared'
300+
import { KUI_COLOR_TEXT_DECORATIVE_AQUA, KUI_ICON_SIZE_50 } from '@kong/design-tokens'
301+
import { AddIcon, BookIcon, ForwardIcon } from '@kong/icons'
302+
import type { BadgeMethodAppearance, HeaderTag } from '@kong/kongponents'
303+
import { BadgeMethodAppearances } from '@kong/kongponents'
304+
import type { AxiosError } from 'axios'
305+
import type { PropType } from 'vue'
306+
import { computed, onBeforeMount, ref, watch } from 'vue'
307+
import { useRouter } from 'vue-router'
308+
import composables from '../composables'
309+
import endpoints from '../routes-endpoints'
273310
import type {
311+
CopyEventPayload,
312+
EntityRow,
274313
KongManagerRouteListConfig,
275314
KonnectRouteListConfig,
276-
EntityRow,
277-
CopyEventPayload,
278315
} from '../types'
279-
import type {
280-
BaseTableHeaders,
281-
EmptyStateOptions,
282-
ExactMatchFilterConfig,
283-
FilterFields,
284-
FuzzyMatchFilterConfig,
285-
TableErrorMessage,
286-
} from '@kong-ui-public/entities-shared'
287-
import '@kong-ui-public/entities-shared/dist/style.css'
288316
289-
import composables from '../composables'
290-
import endpoints from '../routes-endpoints'
291-
import { KUI_COLOR_TEXT_DECORATIVE_AQUA, KUI_ICON_SIZE_50 } from '@kong/design-tokens'
317+
import '@kong-ui-public/entities-shared/dist/style.css'
292318
293319
const emit = defineEmits<{
294320
(e: 'error', error: AxiosError): void
@@ -392,18 +418,23 @@ const disableSorting = computed((): boolean => props.config.app !== 'kongManager
392418
const fields: BaseTableHeaders = {
393419
// the Name column is non-hidable
394420
name: { label: t('routes.list.table_headers.name'), searchable: true, sortable: true, hidable: false },
395-
protocols: { label: t('routes.list.table_headers.protocols'), searchable: true },
396-
...!props.hideTraditionalColumns && {
397-
hosts: { label: t('routes.list.table_headers.hosts'), searchable: true },
398-
methods: { label: t('routes.list.table_headers.methods'), searchable: true },
399-
paths: { label: t('routes.list.table_headers.paths'), searchable: true },
400-
},
401-
...props.hasExpressionColumn && {
402-
expression: { label: t('routes.list.table_headers.expression'), tooltip: true },
421+
...props.config.declarative ? {
422+
matchPath: { label: 'Match Path', searchable: true },
423+
policies: { label: 'Policies', searchable: false, sortable: true },
424+
} : {
425+
protocols: { label: t('routes.list.table_headers.protocols'), searchable: true },
426+
...!props.hideTraditionalColumns && {
427+
hosts: { label: t('routes.list.table_headers.hosts'), searchable: true },
428+
methods: { label: t('routes.list.table_headers.methods'), searchable: true },
429+
paths: { label: t('routes.list.table_headers.paths'), searchable: true },
430+
},
431+
...props.hasExpressionColumn && {
432+
expression: { label: t('routes.list.table_headers.expression'), tooltip: true },
433+
},
434+
tags: { label: t('routes.list.table_headers.tags'), sortable: false },
435+
updated_at: { label: t('routes.list.table_headers.updated_at'), sortable: true },
436+
created_at: { label: t('routes.list.table_headers.created_at'), sortable: true },
403437
},
404-
tags: { label: t('routes.list.table_headers.tags'), sortable: false },
405-
updated_at: { label: t('routes.list.table_headers.updated_at'), sortable: true },
406-
created_at: { label: t('routes.list.table_headers.created_at'), sortable: true },
407438
}
408439
const defaultTablePreferences = {
409440
columnVisibility: {
@@ -433,7 +464,17 @@ const fetcherBaseUrl = computed<string>(() => {
433464
434465
const filterQuery = ref<string>('')
435466
const filterConfig = computed<InstanceType<typeof EntityFilter>['$props']['config']>(() => {
436-
const isExactMatch = (props.config.app === 'konnect' || props.config.isExactMatch)
467+
if (props.config.declarative) {
468+
const { name, matchPath } = fields
469+
470+
return {
471+
isExactMatch: false,
472+
fields: { name, matchPath },
473+
schema: props.config.declarative.filterSchema,
474+
} as FuzzyMatchFilterConfig
475+
}
476+
477+
const isExactMatch = props.config.app === 'konnect' || props.config.isExactMatch
437478
438479
if (isExactMatch) {
439480
return {
@@ -455,11 +496,34 @@ const filterConfig = computed<InstanceType<typeof EntityFilter>['$props']['confi
455496
} as FuzzyMatchFilterConfig
456497
})
457498
499+
// This is only used by the Declarative PoC
500+
// FIXME: This is not optimal as the filterSchema is passed in from the host app
501+
// This might lead to troublesome type mismatches
502+
const fuzzyFilters = ref<FuzzyMatchFilters<'name' | 'matchPath'>>()
503+
504+
const declarativeFilterFn = (route: DeclarativeRoute): boolean => {
505+
if (!fuzzyFilters.value) {
506+
return true
507+
}
508+
509+
const { name, matchPath } = fuzzyFilters.value
510+
if (name && !route.name.toLowerCase().includes(name.toLowerCase())) {
511+
return false
512+
}
513+
if (matchPath && !route.match.path.toLowerCase().includes(matchPath.toLowerCase())) {
514+
return false
515+
}
516+
517+
return true
518+
}
519+
458520
const {
459521
fetcher,
460522
fetcherState,
461523
fetcherCacheKey,
462-
} = useFetcher(computed(() => ({ ...props.config, cacheIdentifier: props.cacheIdentifier })), fetcherBaseUrl)
524+
} = props.config.declarative
525+
? useDeclarativeRoutesFetcher(() => props.config.declarative?.config, props.cacheIdentifier, declarativeFilterFn)
526+
: useFetcher(computed(() => ({ ...props.config, cacheIdentifier: props.cacheIdentifier })), fetcherBaseUrl)
463527
464528
const getCellAttrs = (params: Record<string, any>): Record<string, any> => {
465529
if (params.headerKey === 'expression') {
@@ -681,5 +745,9 @@ onBeforeMount(async () => {
681745
.route-list-cell-expression {
682746
font-family: $kui-font-family-code;
683747
}
748+
749+
.route-list-cell-match-path {
750+
font-family: $kui-font-family-code;
751+
}
684752
}
685753
</style>

packages/entities/entities-routes/src/types/route-list.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { RouteLocationRaw } from 'vue-router'
21
import type { FilterSchema, KongManagerBaseTableConfig, KonnectBaseTableConfig } from '@kong-ui-public/entities-shared'
2+
import type { RouteLocationRaw } from 'vue-router'
33

44
export interface BaseRouteListConfig {
55
/** Current service id if the RouteList in nested in the routes tab on a service detail page */
@@ -13,7 +13,8 @@ export interface BaseRouteListConfig {
1313
}
1414

1515
/** Konnect route list config */
16-
export interface KonnectRouteListConfig extends KonnectBaseTableConfig, BaseRouteListConfig {}
16+
export interface KonnectRouteListConfig extends KonnectBaseTableConfig, BaseRouteListConfig {
17+
}
1718

1819
/** Kong Manager route list config */
1920
export interface KongManagerRouteListConfig extends KongManagerBaseTableConfig, BaseRouteListConfig {

0 commit comments

Comments
 (0)