diff --git a/packages/entities/entities-routes/package.json b/packages/entities/entities-routes/package.json index 134dde872e..1d6b4cba2f 100644 --- a/packages/entities/entities-routes/package.json +++ b/packages/entities/entities-routes/package.json @@ -37,6 +37,7 @@ "@kong/kongponents": "9.32.6", "@types/lodash.isequal": "^4.5.8", "axios": "^1.7.7", + "js-yaml": "^4.1.0", "vite-plugin-monaco-editor": "^1.1.0", "vue": "^3.5.13", "vue-router": "^4.4.5" diff --git a/packages/entities/entities-routes/sandbox/pages/RouteListPage.vue b/packages/entities/entities-routes/sandbox/pages/RouteListPage.vue index f8d24825ea..bc5dd8be3f 100644 --- a/packages/entities/entities-routes/sandbox/pages/RouteListPage.vue +++ b/packages/entities/entities-routes/sandbox/pages/RouteListPage.vue @@ -27,6 +27,13 @@

Konnect Actions Outside

+

Declarative Config

+ +

Konnect API

diff --git a/packages/entities/entities-shared/src/composables/index.ts b/packages/entities/entities-shared/src/composables/index.ts index 8810f51680..5711edb919 100644 --- a/packages/entities/entities-shared/src/composables/index.ts +++ b/packages/entities/entities-shared/src/composables/index.ts @@ -13,11 +13,13 @@ import useTruncationDetector from './useTruncationDetector' import useValidators from './useValidators' import { useSchemaProvider, useSubSchema } from './useSchema' import useTableState from './useTableState' +import { useDeclarativeRoutesFetcher } from './useDeclarativeFetcher' // All composables must be exported as part of the default object for Cypress test stubs export default { useAxios, useDebouncedFilter, + useDeclarativeRoutesFetcher, useDeleteUrlBuilder, useErrors, useExternalLinkCreator, diff --git a/packages/entities/entities-shared/src/composables/useDeclarativeFetcher.ts b/packages/entities/entities-shared/src/composables/useDeclarativeFetcher.ts new file mode 100644 index 0000000000..3e11a183ee --- /dev/null +++ b/packages/entities/entities-shared/src/composables/useDeclarativeFetcher.ts @@ -0,0 +1,117 @@ +import type { TableDataFetcherParams } from '@kong/kongponents' +import { ref, toValue, type MaybeRefOrGetter } from 'vue' +import { FetcherStatus, type DeclarativeConfig, type DeclarativeRoute, type DeclarativeRouteSortableKey, type FetcherResponse, type FetcherState } from '../types' +import { useFetcherCacheKey } from './useFetcher' + +export function useDeclarativeRoutesFetcher( + config: MaybeRefOrGetter, + cacheIdentifier: string, + filterFn: (route: DeclarativeRoute) => boolean, +) { + const state = ref({ + status: FetcherStatus.Idle, + }) + const fetcherCacheKey = useFetcherCacheKey(cacheIdentifier) + + const fetcher = async (fetcherParams: TableDataFetcherParams): Promise => { + // Note: Declarative fetchers do not have an InitialLoad status + state.value = { status: FetcherStatus.Loading } + + try { + const configValue = toValue(config) + if (!configValue) { + const response: FetcherResponse = { + data: [], + total: 0, + } + + state.value = { + status: FetcherStatus.NoRecords, + response, + } + + return response + } + + let results = configValue.routes.filter(filterFn) + + if (fetcherParams.sortColumnKey) { + const sortKey = fetcherParams.sortColumnKey as DeclarativeRouteSortableKey + switch (sortKey) { + case 'name': + results = results.sort((a, b) => + fetcherParams.sortColumnOrder === 'desc' + ? b.name.localeCompare(a.name) + : a.name.localeCompare(b.name), + ) + break + case 'match': + results = results.sort((a, b) => + fetcherParams.sortColumnOrder === 'desc' + ? b.match.path.localeCompare(a.match.path) + : a.match.path.localeCompare(b.match.path), + ) + break + default: + throw new Error(`Unsupported sortColumnKey "${fetcherParams.sortColumnKey}"`) + } + } + + let pagination: FetcherResponse['pagination'] | undefined = undefined + if (fetcherParams.pageSize) { + const pageSize = fetcherParams.pageSize + if (pageSize < 1 || !Number.isInteger(pageSize)) { + throw new Error(`Expected pageSize to be a positive integer, got ${fetcherParams.pageSize}`) + } + + const page = fetcherParams.page ?? 1 + if (page < 1 || !Number.isInteger(page)) { + throw new Error(`Expected page to be a positive integer, got ${page}`) + } + + pagination = { offset: 'page' } + + results = results.slice((page - 1) * pageSize, page * pageSize) + } + + const response = { + data: results, + total: configValue.routes.length, + pagination, + } + + if (results.length === 0 && !fetcherParams.query) { + state.value = { + status: FetcherStatus.NoRecords, + response, + } + } else { + state.value = { + status: FetcherStatus.Idle, + response, + } + } + + return response + } catch (error: any) { + const response: FetcherResponse = { + data: [], + total: 0, + } + + state.value = { + status: FetcherStatus.Error, + response, + error: error.response ? error : { response: error }, + } + + return response + } + } + + return { + fetcher, + fetcherState: state, + fetcherCacheKey, + } +} diff --git a/packages/entities/entities-shared/src/index.ts b/packages/entities/entities-shared/src/index.ts index 0bce1608ae..1a17a7265d 100644 --- a/packages/entities/entities-shared/src/index.ts +++ b/packages/entities/entities-shared/src/index.ts @@ -18,13 +18,65 @@ import TableTags from './components/common/TableTags.vue' import composables from './composables' // Extract specific composables to export -const { useAxios, useDeleteUrlBuilder, useErrors, useExternalLinkCreator, useFetchUrlBuilder, useFetcher, useFetcherCacheKey, useDebouncedFilter, useStringHelpers, useHelpers, useGatewayFeatureSupported, useTruncationDetector, useValidators, useSchemaProvider, useTableState } = composables +const { + useAxios, + useDebouncedFilter, + useDeclarativeRoutesFetcher, + useDeleteUrlBuilder, + useErrors, + useExternalLinkCreator, + useFetcher, + useFetcherCacheKey, + useFetchUrlBuilder, + useGatewayFeatureSupported, + useHelpers, + useSchemaProvider, + useStringHelpers, + useTableState, + useTruncationDetector, + useValidators, +} = composables // Components -export { EntityBaseConfigCard, ConfigCardItem, ConfigCardDisplay, InternalLinkItem, EntityBaseForm, EntityBaseTable, EntityDeleteModal, EntityFilter, EntityToggleModal, PermissionsWrapper, EntityFormSection, EntityLink, EntityEmptyState, JsonCodeBlock, TerraformCodeBlock, YamlCodeBlock, TableTags } +export { + ConfigCardDisplay, + ConfigCardItem, + EntityBaseConfigCard, + EntityBaseForm, + EntityBaseTable, + EntityDeleteModal, + EntityEmptyState, + EntityFilter, + EntityFormSection, + EntityLink, + EntityToggleModal, + InternalLinkItem, + JsonCodeBlock, + PermissionsWrapper, + TableTags, + TerraformCodeBlock, + YamlCodeBlock, +} // Composables -export { useAxios, useDeleteUrlBuilder, useErrors, useExternalLinkCreator, useFetchUrlBuilder, useFetcher, useFetcherCacheKey, useDebouncedFilter, useStringHelpers, useHelpers, useGatewayFeatureSupported, useTruncationDetector, useValidators, useSchemaProvider, useTableState } +export { + useAxios, + useDebouncedFilter, + useDeclarativeRoutesFetcher, + useDeleteUrlBuilder, + useErrors, + useExternalLinkCreator, + useFetcher, + useFetcherCacheKey, + useFetchUrlBuilder, + useGatewayFeatureSupported, + useHelpers, + useSchemaProvider, + useStringHelpers, + useTableState, + useTruncationDetector, + useValidators, +} // Types export * from './types' diff --git a/packages/entities/entities-shared/src/types/declarative-config.ts b/packages/entities/entities-shared/src/types/declarative-config.ts new file mode 100644 index 0000000000..0f100d72fc --- /dev/null +++ b/packages/entities/entities-shared/src/types/declarative-config.ts @@ -0,0 +1,41 @@ +export interface DeclarativePolicy { + name: string + id?: string + condition?: string + config: Record +} + +export interface DeclarativeListener { + port: number + protocol: string + policies: DeclarativePolicy[] +} + +export interface DeclarativeService { + name: string + policies: DeclarativePolicy[] +} + +export interface DeclarativeRouteMatch { + path: string +} + +export interface DeclarativeRoute { + name: string + match: DeclarativeRouteMatch + policies: DeclarativePolicy[] +} + +export interface DeclarativeWasmComponent { + name: string + binary: string // base64 encoded binary +} + +export interface DeclarativeConfig { + listeners: DeclarativeListener[] + routes: DeclarativeRoute[] + services: DeclarativeService[] + wasm_components?: DeclarativeWasmComponent[] +} + +export type DeclarativeRouteSortableKey = keyof Pick diff --git a/packages/entities/entities-shared/src/types/entity-base-table.ts b/packages/entities/entities-shared/src/types/entity-base-table.ts index 4668299f3a..87b8f23195 100644 --- a/packages/entities/entities-shared/src/types/entity-base-table.ts +++ b/packages/entities/entities-shared/src/types/entity-base-table.ts @@ -1,6 +1,6 @@ +import type { TableState } from '@kong/kongponents/dist/types' import type { RouteLocationRaw } from 'vue-router' import type { Field, KongManagerConfig, KonnectConfig } from './index' -import type { TableState } from '@kong/kongponents/dist/types' export interface KonnectBaseTableConfig extends KonnectConfig { /** Additional message to show when there are no records */ diff --git a/packages/entities/entities-shared/src/types/entity-filter.ts b/packages/entities/entities-shared/src/types/entity-filter.ts index 3f6a2a286c..be751c2dc0 100644 --- a/packages/entities/entities-shared/src/types/entity-filter.ts +++ b/packages/entities/entities-shared/src/types/entity-filter.ts @@ -44,3 +44,6 @@ export interface FuzzyMatchFilterConfig extends BaseFilterConfig { /** Fuzzy match filter schema */ schema: FilterSchema } + +/** Structured filters for fuzzy matching */ +export type FuzzyMatchFilters = Record diff --git a/packages/entities/entities-shared/src/types/index.ts b/packages/entities/entities-shared/src/types/index.ts index 9567cfdf50..4a47afee4d 100644 --- a/packages/entities/entities-shared/src/types/index.ts +++ b/packages/entities/entities-shared/src/types/index.ts @@ -4,6 +4,7 @@ // Example: export * from './app-config' export * from './base' +export * from './declarative-config' export * from './entity-delete-modal' export * from './entity-base-form' export * from './entity-base-table' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 812be6a943..f4ee277c1d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1071,6 +1071,9 @@ importers: axios: specifier: ^1.7.7 version: 1.7.7 + js-yaml: + specifier: ^4.1.0 + version: 4.1.0 vite-plugin-monaco-editor: specifier: ^1.1.0 version: 1.1.0(monaco-editor@0.52.2) @@ -1903,6 +1906,7 @@ packages: '@evilmartians/lefthook@1.8.2': resolution: {integrity: sha512-SZdQk3W9q7tcJwnSwEMUubQqVIK7SHxv52hEAnV7o3nPI+xKcmd+rN0hZIJg07wjBaJRAjzdvoQySKQQYPW5Qw==} + cpu: [x64, arm64, ia32] os: [darwin, linux, win32] hasBin: true @@ -8270,7 +8274,7 @@ packages: right-pad@1.0.1: resolution: {integrity: sha512-bYBjgxmkvTAfgIYy328fmkwhp39v8lwVgWhhrzxPV3yHtcSqyYKe9/XOhvW48UFjATg3VuJbpsp5822ACNvkmw==} engines: {node: '>= 0.10'} - deprecated: Use String.prototype.padEnd() instead + deprecated: Please use String.prototype.padEnd() over this package. rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}