Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: store global user preferences size and format #3457

Merged
merged 22 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions packages/kuma-gui/features/application/MainNavigation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,36 @@ Feature: application / MainNavigation
And the URL contains "page=1&size=50"
And the URL doesn't contain "mesh=default"

Scenario: Pagination from localStorage
Given the localStorage
"""
kumahq.kuma-gui:/:
params:
size: 75
"""
When I visit the "/meshes" URL
Then the URL contains "size=75"

Scenario: Format from localStorage
Given the localStorage
"""
kumahq.kuma-gui:/:
params:
format: yaml
"""
And the URL "/meshes/default/meshservices" responds with
"""
body:
items:
- name: monitor-proxy-0.kuma-demo
labels:
kuma.io/display-name: monitor-proxy-0
k8s.kuma.io/namespace: kuma-demo
"""
When I visit the "/meshes/default/services/mesh-services/monitor-proxy-0.kuma-demo" URL
Then the URL contains "format=yaml"
And the "[data-testid='k-code-block']" element exists

Scenario: History navigation
Given the environment
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,17 @@
:src="uri(sources, '/me/:route', {
route: props.name,
})"
@change="resolve"
v-slot="{ data: me }"
@change="(value) => {
meStored = value
return resolve(value)
}"
v-slot="{ data: me, refresh: _refresh }"
>
<template
:ref="() => {
refresh = _refresh
}"
/>
<slot
v-if="me && submit && redirected"
:id="UniqueId"
Expand All @@ -53,9 +61,9 @@
</DataSource>
</div>
</template>
<script lang="ts" setup generic="T extends Record<string, string | number | boolean> = {}">
<script lang="ts" setup generic="T extends Record<string, string | number | boolean | typeof Number | typeof String> = {}">
import { computed, provide, inject, ref, watch, onBeforeUnmount, reactive, useAttrs } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router'

import { ROUTE_VIEW_PARENT, ROUTE_VIEW_ROOT } from '.'
import { useCan, useI18n, uniqueId, useEnv, get } from '../../index'
Expand Down Expand Up @@ -92,17 +100,20 @@ const env = useEnv()
const can = useCan()
const uri = useUri()


let resolve: (resolved: object) => void
const meResponse = new Promise((r) => resolve = r)
const meResponse = new Promise((r) => {
resolve = r
})
const meStored = ref<any>({})

const htmlAttrs = useAttrs()
const { t } = useI18n()
const route = useRoute()
const router = useRouter()
const sym = Symbol('route-view')

type Params = { [P in keyof T]: T[P] }
type PrimitiveParams = Record<string, string | number | boolean>
type Params = { [P in keyof T]: T[P] extends NumberConstructor ? number : T[P] extends StringConstructor ? string : T[P] }
type RouteReplaceParams = Parameters<typeof router['push']>

const setTitle = createTitleSetter(document)
Expand Down Expand Up @@ -130,6 +141,7 @@ class UniqueId {

const name = computed(() => props.name)
const submit = ref((_args: any) => {})
const refresh = ref(() => {})
const title = ref<HTMLDivElement | null>(null)
const titles = new Map<symbol, string>()
const attributes = new Map<symbol, SupportedAttrs>()
Expand Down Expand Up @@ -172,21 +184,27 @@ const routeView = {
setAttrs([...attributes.values()])
},
}
const routeParams = reactive<Params>(structuredClone(props.params) as Params)
johncowen marked this conversation as resolved.
Show resolved Hide resolved
const routeParams = reactive<Params>({} as Params)

onBeforeRouteUpdate(() => {
// Make sure to always update the me storage
refresh.value()
})

// when any URL params change, normalize/validate/default and reset our actual application params
const redirected = ref<boolean>(false)
watch(() => {
return Object.keys(props.params).map((item) => { return route.params[item] || route.query[item] })
}, async () => {
const stored = await meResponse
await meResponse
const stored = get(meStored, 'value.params', {})

// merge params in order of importance/priority:
// 1. Anything stored by the user in storage
// 2. URL query params
// 3. URL path params
const params = {
...get(stored, 'params', {}),
...stored,
...route.query,
...route.params,
}
Expand All @@ -195,13 +213,11 @@ watch(() => {
// 1. Ignore any `?param[]=0` type params, we just take the first one
// 2. Using normalizeUrlParam and the type information from RouteView :params convert things to the correct type i.e. null > false
// 3. Use RouteView :params to set any params that are empty, i.e. use RouteView :params as defaults.
Object.entries({
...props.params,
}).reduce((prev, [prop, def]) => {
Object.entries(props.params).reduce((prev, [prop, def]) => {
const param = urlParam(typeof params[prop] === 'undefined' ? '' : params[prop])
prev[prop] = normalizeUrlParam(param, def)
return prev
}, routeParams as Record<string, string | number | boolean>)
}, routeParams as PrimitiveParams)

// only one first load, if any params are missing from the URL/query
// redirect/add the query params to the URL.
Expand All @@ -213,7 +229,7 @@ watch(() => {
prev[key] = value
}
return prev
}, {} as Record<string, string | boolean | undefined>)
}, {} as Partial<PrimitiveParams>)
if (Object.keys(params).length > 0) {
router.replace({
query: cleanQuery(params, route.query),
Expand All @@ -223,22 +239,28 @@ watch(() => {
}
}, { immediate: true })

type RouteParams = Record<string, string | boolean | number | undefined>
let newParams: RouteParams = {}
let newParams: Partial<PrimitiveParams> = {}

const routerPush = (params: RouteParams) => {
const routerPush = (params: Partial<PrimitiveParams>) => {
router.push({
name: props.name,
query: cleanQuery(params, route.query),
})
newParams = {}
}

const routeUpdate = (params: RouteParams): void => {
const routeUpdate = (params: Partial<PrimitiveParams>): void => {
newParams = {
...newParams,
...params,
}

for (const key of ['size', 'format']) {
if(newParams[key]) {
submit.value({ params: { [key]: newParams[key] }, global: true })
}
}

routerPush(newParams)
}

Expand Down
23 changes: 14 additions & 9 deletions packages/kuma-gui/src/app/application/utilities/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import jsYaml from 'js-yaml'

type URLParamDefault = string | number | boolean
type URLParamDefault = string | number | boolean | NumberConstructor | StringConstructor
type URLParamValues = string | number | boolean
type URLParamValue = string | null

export const runInDebug = (func: () => void) => {
Expand Down Expand Up @@ -90,20 +91,24 @@ export const urlParam = function <T extends URLParamValue> (param: T | T[]): T {
}

//
export const normalizeUrlParam = (param: URLParamValue, def: URLParamDefault): URLParamDefault => {
export const normalizeUrlParam = (param: URLParamValue, definition: URLParamDefault): URLParamValues => {
switch (true) {
case typeof def === 'boolean':
return param === null ? true : def
case typeof def === 'number': {
const value = param === null || param.length === 0 ? def : Number(decodeURIComponent(param))
case definition === Number:
return Number(param)
case definition === String:
return String(param)
johncowen marked this conversation as resolved.
Show resolved Hide resolved
case typeof definition === 'boolean':
return param === null ? true : definition
case typeof definition === 'number': {
const value = param === null || param.length === 0 ? definition : Number(decodeURIComponent(param))
if (isNaN(value)) {
return Number(def)
return Number(definition)
} else {
return value
}
}
case typeof def === 'string': {
return param === null || param.length === 0 ? def : decodeURIComponent(param)
case typeof definition === 'string': {
return param === null || param.length === 0 ? definition : decodeURIComponent(param)
}
}
throw new TypeError('URL parameters can only be string | number | boolean')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
name="data-plane-list-view"
:params="{
page: 1,
size: 50,
size: Number,
dataplaneType: 'all',
s: '',
mesh: '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
codeSearch: '',
codeFilter: false,
codeRegExp: false,
format: 'structured',
format: String,
}"
v-slot="{ route, t }"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
codeSearch: '',
codeFilter: false,
codeRegExp: false,
format: 'structured',
format: String,
}"
v-slot="{ route, t, uri, can }"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
name="external-service-list-view"
:params="{
page: 1,
size: 50,
size: Number,
mesh: '',
}"
v-slot="{ route, t, me, uri }"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
gateway: '',
listener: '',
page: 1,
size: 50,
size: Number,
s: '',
dataPlane: '',
}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
name="builtin-gateway-list-view"
:params="{
page: 1,
size: 50,
size: Number,
mesh: '',
gateway: '',
}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
codeSearch: '',
codeFilter: false,
codeRegExp: false,
format: 'structured',
format: String,
}"
v-slot="{ route, t }"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
mesh: '',
service: '',
page: 1,
size: 50,
size: Number,
s: '',
dataPlane: '',
}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
name="delegated-gateway-list-view"
:params="{
page: 1,
size: 50,
size: Number,
mesh: '',
}"
v-slot="{ route, t, me, uri }"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
codeSearch: '',
codeFilter: false,
codeRegExp: false,
format: 'structured',
format: String,
}"
v-slot="{ route, t, can }"
>
Expand Down
8 changes: 5 additions & 3 deletions packages/kuma-gui/src/app/me/sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@ export const sources = ({ get, set }: Storage) => {
{
params: {
size: 50,
format: 'structured',
},
},
app,
route,
)
},
'/me/:route/:data': async (params) => {
const json = JSON.parse(params.data)
const res = merge<object>(await get(params.route), json)
set(params.route, res)
const { global: useGlobal = false, ...json } = JSON.parse(params.data)
johncowen marked this conversation as resolved.
Show resolved Hide resolved
const targetRoute = useGlobal ? '/' : params.route
const res = merge<object>(await get(targetRoute), json)
set(targetRoute, res)
},
})
}
2 changes: 1 addition & 1 deletion packages/kuma-gui/src/app/meshes/views/MeshListView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
name="mesh-list-view"
:params="{
page: 1,
size: 50,
size: Number,
mesh: '',
}"
v-slot="{ route, t, me, uri }"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
name="policy-detail-view"
:params="{
page: 1,
size: 50,
size: Number,
s: '',
mesh: '',
policy: '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
name="policy-list-view"
:params="{
page: 1,
size: 50,
size: Number,
mesh: '',
policyPath: '',
policy: '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
codeSearch: '',
codeFilter: false,
codeRegExp: false,
format: 'structured',
format: String,
}"
v-slot="{ route, t }"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
name="mesh-external-service-list-view"
:params="{
page: 1,
size: 50,
size: Number,
mesh: '',
service: '',
}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
codeSearch: '',
codeFilter: false,
codeRegExp: false,
format: 'structured',
format: String,
}"
v-slot="{ route, t, can }"
>
Expand Down
Loading
Loading