Skip to content

Commit

Permalink
feat(mesh-service-detail-view): add dpp status and hostnames (#3497)
Browse files Browse the repository at this point in the history
This PR introduces both status of DPPs and hostnames of a mesh service
and hostnames for mesh-external and mesh-multizone-service. The status
of DPPs is already part of the mesh service API
(`status.dataplaneProxies`) and the hostnames are exposed by the
`_hostnames` API.
The order of placement of the two new cards is first hostnames and then
the status of DPPs so the status of DPPs is directly above the listing
of DPPs.

Additionally I've updated the generated openapi-typescript types from
[kuma/master/docs/generated/openapi.yaml](https://github.com/kumahq/kuma/blob/master/docs/generated/openapi.yaml).

The endpoint for services `_hostnames` is part of the api specs and
fully typed. The `:serviceType` parameter is a union type and requires
one of `meshservices`, `meshmultizoneservices` or
`meshexternalservices`. While I'd prefer to have `defineSources`
consider the types from the specs, it would require several changes in
other parts. So for now to satisfy TypeScript and catch any mismatches
I've added a type guard like in other places and throw an error in case
an incorrect value is provided.


![image](https://github.com/user-attachments/assets/b182a2a7-3aff-44a3-b559-106efa812852)

---

I decided to keep the different mocks of `meshservices`,
`meshexternalservices` and `meshmultizoneservices` in separate files,
although the code is almost the same. I found that it's a little easier
to find and probably easier to extend in the future when things might
further differ. If we find in the future that this becomes cumbersome to
maintain, we can definitely think of merging those files into one.

Closes #3322

---------

Signed-off-by: schogges <[email protected]>
  • Loading branch information
schogges authored Feb 6, 2025
1 parent 821642d commit a596791
Show file tree
Hide file tree
Showing 16 changed files with 3,231 additions and 1,972 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Feature: application / MainNavigation
kumahq.kuma-gui:/:
params:
format: yaml
"""
"""
And the URL "/meshes/default/meshservices" responds with
"""
body:
Expand Down
22 changes: 22 additions & 0 deletions packages/kuma-gui/features/mesh/services/Item.feature
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Feature: mesh / services / item
| action | $item:nth-child(1) [data-action] |
| input-search | [data-testid='filter-bar-filter-input'] |
| button-search | [data-testid='filter-bar-submit-query-button'] |
| hostnames | [data-testid='hostnames-collection'] tbody tr |

Scenario Outline: Shows correct tabs for service type <ServiceType>
Given the URL "/meshes/default/service-insights/firewall-1" responds with
Expand Down Expand Up @@ -131,3 +132,24 @@ Feature: mesh / services / item
When I click the "$action-group" element
And I click the "$view" element
Then the URL contains "/meshes/default/data-planes/fake-dataplane/overview"

Rule: With a mesh service of type mesh-service, mesh-multi-zone-service or mesh-external-service

Scenario: Hostnames listing exists for different mesh service types
Given the URL "<API>" responds with
"""
body:
items:
- hostname: my-meshservice.<SVC>.mesh.local
zones:
- name: zone-1
total: 1
"""
When I visit the "<URL>" URL
Then the "$hostnames" element exists

Examples:
| API | URL | SVC |
| /meshes/default/meshservices/my-meshservice/_hostnames | /meshes/default/services/mesh-services/my-meshservice/overview | svc |
| /meshes/default/meshexternalservices/my-meshservice/_hostnames | /meshes/default/services/mesh-external-services/my-meshservice/overview | extsvc |
| /meshes/default/meshmultizoneservices/my-meshservice/_hostnames | /meshes/default/services/mesh-multi-zone-services/my-meshservice/overview | mzsvc |
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Feature: mesh / mesh-services / item
Given the CSS selectors
| Alias | Selector |
| dataplanes | [data-testid='data-plane-collection'] tbody tr |
| hostnames | [data-testid='hostnames-collection'] tbody tr |

Scenario: The dataplane table exists
Given the environment
Expand All @@ -21,3 +22,17 @@ Feature: mesh / mesh-services / item
"""
When I visit the "/meshes/default/services/mesh-services/firewall-1/overview" URL
Then the "$dataplanes" element exists 1 times

Scenario: Status of DPPs shows the correct values
Given the URL "/meshes/default/meshservices/my-meshservice" responds with
"""
body:
status:
dataplaneProxies:
connected: 3
healthy: 2
total: 4
"""
When I visit the "/meshes/default/services/mesh-services/my-meshservice/overview" URL
Then the "[data-testid='connected-dpps']" element contains "3/4"
And the "[data-testid='healthy-dpps']" element contains "2"
21 changes: 21 additions & 0 deletions packages/kuma-gui/src/app/services/data/Hostname.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { components } from '@/types/auto-generated.d'

type GeneratedHostnames = components['responses']['InspectHostnamesResponse']['content']['application/json']
type GeneratedHostname = components['responses']['InspectHostnamesResponse']['content']['application/json']['items'][number]

export const Hostname = {
fromObject(item: GeneratedHostname) {
return item
},

fromCollection(collection: GeneratedHostnames) {
const items = Array.isArray(collection.items) ? collection.items.map(Hostname.fromObject) : []

return {
...collection,
items,
total: collection.total ?? items.length,
}
},
}
export type Hostname = ReturnType<typeof Hostname['fromObject']>
1 change: 1 addition & 0 deletions packages/kuma-gui/src/app/services/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
export * from './MeshService'
export * from './MeshMultiZoneService'
export * from './MeshExternalService'
export * from './Hostname'

export type ExternalService = PartialExternalService & {
config: PartialExternalService
Expand Down
8 changes: 8 additions & 0 deletions packages/kuma-gui/src/app/services/locales/en-us/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ services:
data_plane_proxies: Data Plane Proxies
no_matching_external_service: 'No matching ExternalService was found for service {name}'
empty: None
dpp-status:
title: Status of Data Plane Proxies
connected: Connected
healthy: Healthy
hostnames:
title: Hostnames
hostname: Hostname
zone: Zone
href:
docs: '{KUMA_DOCS_URL}/introduction/architecture/?{KUMA_UTM_QUERY_PARAMS}#services-and-pods'
internal-service:
Expand Down
27 changes: 27 additions & 0 deletions packages/kuma-gui/src/app/services/sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
MeshExternalService,
ExternalService,
ServiceInsight,
Hostname,
} from './data'
import type { DataSourceResponse } from '@/app/application'
import { defineSources } from '@/app/application/services/data-source'
Expand All @@ -21,11 +22,16 @@ export type ServiceInsightCollectionSource = DataSourceResponse<ServiceInsightCo

export type ExternalServiceSource = DataSourceResponse<ExternalService | null>

const includes = <T extends readonly string[]>(arr: T, item: string): item is T[number] => {
return arr.includes(item as T[number])
}

export const sources = (api: KumaApi) => {
const http = createClient<paths>({
baseUrl: '',
fetch: api.client.fetch,
})

return defineSources({
'/meshes/:mesh/mesh-services': async (params) => {
const { mesh, size } = params
Expand Down Expand Up @@ -222,5 +228,26 @@ export const sources = (api: KumaApi) => {
format: 'kubernetes',
})
},

'/meshes/:mesh/:serviceType/:serviceName/_hostnames': async (params) => {
const { mesh, serviceType, serviceName } = params
const isValidServiceType = includes(['meshservices', 'meshexternalservices', 'meshmultizoneservices'] as const, serviceType)

if(!isValidServiceType) {
throw new Error(`Incorrect value for :serviceType, got ${serviceType}.`)
}

const response = await http.GET('/meshes/{mesh}/{serviceType}/{serviceName}/_hostnames', {
params: {
path: {
mesh,
serviceType,
serviceName,
},
},
})

return Hostname.fromCollection(response.data!)
},
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
<RouteView
name="mesh-external-service-detail-view"
:params="{
mesh: '',
service: '',
codeSearch: '',
codeFilter: false,
codeRegExp: false,
}"
v-slot="{ route, can, t }"
v-slot="{ route, can, t, uri, me }"
>
<AppView>
<XLayout type="stack">
Expand Down Expand Up @@ -99,6 +101,68 @@
</DefinitionCard>
</XAboutCard>



<XCard>
<template #title>
{{ t('services.detail.hostnames.title') }}
</template>

<DataLoader
:src="uri(servicesSources, '/meshes/:mesh/:serviceType/:serviceName/_hostnames', {
mesh: route.params.mesh,
serviceType: 'meshexternalservices',
serviceName: route.params.service,
})"
>
<template #loadable="{ data: hostnames }">
<DataCollection
type="hostnames"
:items="hostnames?.items ?? [undefined]"
>
<AppCollection
type="hostnames-collection"
data-testid="hostnames-collection"
:items="hostnames?.items"
:headers="[
{ ...me.get('headers.hostname'), label: t('services.detail.hostnames.hostname'), key: 'hostname' },
{ ...me.get('headers.zones'), label: t('services.detail.hostnames.zone'), key: 'zones' },
]"
@resize="me.set"
>
<template #hostname="{ row: item }">
<b>
<XCopyButton
:text="item.hostname"
/>
</b>
</template>
<template #zones="{ row: item }">
<XLayout type="separated">
<XBadge
v-for="(zone, index) of item.zones"
:key="index"
appearance="decorative"
>
<XAction
:to="{
name: 'zone-cp-detail-view',
params: {
zone: zone.name,
},
}"
>
{{ zone.name }}
</XAction>
</XBadge>
</XLayout>
</template>
</AppCollection>
</DataCollection>
</template>
</DataLoader>
</XCard>

<ResourceCodeBlock
:resource="props.data.config"
is-searchable
Expand Down Expand Up @@ -128,7 +192,9 @@

<script lang="ts" setup>
import type { MeshExternalService } from '../data'
import AppCollection from '@/app/application/components/app-collection/AppCollection.vue'
import DefinitionCard from '@/app/common/DefinitionCard.vue'
import { sources as servicesSources } from '@/app/services/sources'
import ResourceCodeBlock from '@/app/x/components/x-code-block/ResourceCodeBlock.vue'
const props = defineProps<{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
codeFilter: false,
codeRegExp: false,
}"
v-slot="{ route, t }"
v-slot="{ route, t, uri, me }"
>
<AppView>
<XLayout type="stack">
Expand Down Expand Up @@ -65,6 +65,66 @@
</template>
</DefinitionCard>
</XAboutCard>

<XCard>
<template #title>
{{ t('services.detail.hostnames.title') }}
</template>

<DataLoader
:src="uri(servicesSources, '/meshes/:mesh/:serviceType/:serviceName/_hostnames', {
mesh: route.params.mesh,
serviceType: 'meshmultizoneservices',
serviceName: route.params.service,
})"
>
<template #loadable="{ data: hostnames }">
<DataCollection
type="hostnames"
:items="hostnames?.items ?? [undefined]"
>
<AppCollection
type="hostnames-collection"
data-testid="hostnames-collection"
:items="hostnames?.items"
:headers="[
{ ...me.get('headers.hostname'), label: t('services.detail.hostnames.hostname'), key: 'hostname' },
{ ...me.get('headers.zones'), label: t('services.detail.hostnames.zone'), key: 'zones' },
]"
@resize="me.set"
>
<template #hostname="{ row: item }">
<b>
<XCopyButton
:text="item.hostname"
/>
</b>
</template>
<template #zones="{ row: item }">
<XLayout type="separated">
<XBadge
v-for="(zone, index) of item.zones"
:key="index"
appearance="decorative"
>
<XAction
:to="{
name: 'zone-cp-detail-view',
params: {
zone: zone.name,
},
}"
>
{{ zone.name }}
</XAction>
</XBadge>
</XLayout>
</template>
</AppCollection>
</DataCollection>
</template>
</DataLoader>
</XCard>

<div>
<ResourceCodeBlock
Expand Down Expand Up @@ -97,7 +157,9 @@

<script lang="ts" setup>
import type { MeshMultiZoneService } from '../data'
import AppCollection from '@/app/application/components/app-collection/AppCollection.vue'
import DefinitionCard from '@/app/common/DefinitionCard.vue'
import { sources as servicesSources } from '@/app/services/sources'
import ResourceCodeBlock from '@/app/x/components/x-code-block/ResourceCodeBlock.vue'
const props = defineProps<{
Expand Down
Loading

0 comments on commit a596791

Please sign in to comment.