Skip to content

Commit d483825

Browse files
authored
feat: implement serializeQueryParams utility based on Openapi Spec (#51)
1 parent 0ae7386 commit d483825

9 files changed

Lines changed: 2778 additions & 1678 deletions

File tree

package-lock.json

Lines changed: 2560 additions & 1655 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/dts/auto-imports.d.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,24 @@ declare global {
1212
const EffectScope: typeof import('vue')['EffectScope']
1313
const colorMethods: typeof import('../src/utils/constant')['colorMethods']
1414
const computed: typeof import('vue')['computed']
15+
const computedDeepDiff: typeof import('@data-fair/lib-vue/deep-diff.js')['computedDeepDiff']
1516
const createApp: typeof import('vue')['createApp']
1617
const customRef: typeof import('vue')['customRef']
1718
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
1819
const defineComponent: typeof import('vue')['defineComponent']
20+
const dfDateMatchFilter: typeof import('@data-fair/lib-vuetify/date-match-filter.vue')['default']
21+
const dfDateRangePicker: typeof import('@data-fair/lib-vuetify/date-range-picker.vue')['default']
22+
const dfLangSwitcher: typeof import('@data-fair/lib-vuetify/lang-switcher.vue')['default']
23+
const dfNavigationRight: typeof import('@data-fair/lib-vuetify/navigation-right.vue')['default']
24+
const dfOwnerAvatar: typeof import('@data-fair/lib-vuetify/owner-avatar.vue')['default']
25+
const dfOwnerPick: typeof import('@data-fair/lib-vuetify/owner-pick.vue')['default']
1926
const dfPersonalMenu: typeof import('@data-fair/lib-vuetify/personal-menu.vue')['default']
27+
const dfSearchAddress: typeof import('@data-fair/lib-vuetify/search-address.vue')['default']
28+
const dfThemeSwitcher: typeof import('@data-fair/lib-vuetify/theme-switcher.vue')['default']
2029
const dfTutorialAlert: typeof import('@data-fair/lib-vuetify/tutorial-alert.vue')['default']
21-
const dfUserAvatar: typeof import('@data-fair/lib-vuetify/user-avatar.vue')['default']
30+
const dfUiNotif: typeof import('@data-fair/lib-vuetify/ui-notif.vue')['default']
31+
const dfUiNotifAlert: typeof import('@data-fair/lib-vuetify/ui-notif-alert.vue')['default']
32+
const dfUserAvatar: typeof import('@data-fair/lib-vuetify/ui-user-avatar.vue')['default']
2233
const effectScope: typeof import('vue')['effectScope']
2334
const endpointQuerySchemaBase: typeof import('../src/utils/transform')['endpointQuerySchemaBase']
2435
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
@@ -68,6 +79,8 @@ declare global {
6879
const ref: typeof import('vue')['ref']
6980
const resolveComponent: typeof import('vue')['resolveComponent']
7081
const resolveExamples: typeof import('../src/utils/transform')['resolveExamples']
82+
const resolveParameters: typeof import('../src/utils/transform')['resolveParameters']
83+
const serializeQueryParams: typeof import('../src/utils/serialize')['serializeQueryParams']
7184
const shallowReactive: typeof import('vue')['shallowReactive']
7285
const shallowReadonly: typeof import('vue')['shallowReadonly']
7386
const shallowRef: typeof import('vue')['shallowRef']
@@ -102,6 +115,7 @@ declare global {
102115
const useUiNotif: typeof import('@data-fair/lib-vue/ui-notif.js')['useUiNotif']
103116
const useWS: typeof import('@data-fair/lib-vue/ws.js')['useWS']
104117
const watch: typeof import('vue')['watch']
118+
const watchDeepDiff: typeof import('@data-fair/lib-vue/deep-diff.js')['watchDeepDiff']
105119
const watchEffect: typeof import('vue')['watchEffect']
106120
const watchPostEffect: typeof import('vue')['watchPostEffect']
107121
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
@@ -125,13 +139,24 @@ declare module 'vue' {
125139
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
126140
readonly colorMethods: UnwrapRef<typeof import('../src/utils/constant')['colorMethods']>
127141
readonly computed: UnwrapRef<typeof import('vue')['computed']>
142+
readonly computedDeepDiff: UnwrapRef<typeof import('@data-fair/lib-vue/deep-diff.js')['computedDeepDiff']>
128143
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
129144
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
130145
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
131146
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
147+
readonly dfDateMatchFilter: UnwrapRef<typeof import('@data-fair/lib-vuetify/date-match-filter.vue')['default']>
148+
readonly dfDateRangePicker: UnwrapRef<typeof import('@data-fair/lib-vuetify/date-range-picker.vue')['default']>
149+
readonly dfLangSwitcher: UnwrapRef<typeof import('@data-fair/lib-vuetify/lang-switcher.vue')['default']>
150+
readonly dfNavigationRight: UnwrapRef<typeof import('@data-fair/lib-vuetify/navigation-right.vue')['default']>
151+
readonly dfOwnerAvatar: UnwrapRef<typeof import('@data-fair/lib-vuetify/owner-avatar.vue')['default']>
152+
readonly dfOwnerPick: UnwrapRef<typeof import('@data-fair/lib-vuetify/owner-pick.vue')['default']>
132153
readonly dfPersonalMenu: UnwrapRef<typeof import('@data-fair/lib-vuetify/personal-menu.vue')['default']>
154+
readonly dfSearchAddress: UnwrapRef<typeof import('@data-fair/lib-vuetify/search-address.vue')['default']>
155+
readonly dfThemeSwitcher: UnwrapRef<typeof import('@data-fair/lib-vuetify/theme-switcher.vue')['default']>
133156
readonly dfTutorialAlert: UnwrapRef<typeof import('@data-fair/lib-vuetify/tutorial-alert.vue')['default']>
134-
readonly dfUserAvatar: UnwrapRef<typeof import('@data-fair/lib-vuetify/user-avatar.vue')['default']>
157+
readonly dfUiNotif: UnwrapRef<typeof import('@data-fair/lib-vuetify/ui-notif.vue')['default']>
158+
readonly dfUiNotifAlert: UnwrapRef<typeof import('@data-fair/lib-vuetify/ui-notif-alert.vue')['default']>
159+
readonly dfUserAvatar: UnwrapRef<typeof import('@data-fair/lib-vuetify/ui-user-avatar.vue')['default']>
135160
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
136161
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
137162
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
@@ -178,6 +203,8 @@ declare module 'vue' {
178203
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
179204
readonly ref: UnwrapRef<typeof import('vue')['ref']>
180205
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
206+
readonly resolveParameters: UnwrapRef<typeof import('../src/utils/transform')['resolveParameters']>
207+
readonly serializeQueryParams: UnwrapRef<typeof import('../src/utils/serialize')['serializeQueryParams']>
181208
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
182209
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
183210
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
@@ -212,6 +239,7 @@ declare module 'vue' {
212239
readonly useUiNotif: UnwrapRef<typeof import('@data-fair/lib-vue/ui-notif.js')['useUiNotif']>
213240
readonly useWS: UnwrapRef<typeof import('@data-fair/lib-vue/ws.js')['useWS']>
214241
readonly watch: UnwrapRef<typeof import('vue')['watch']>
242+
readonly watchDeepDiff: UnwrapRef<typeof import('@data-fair/lib-vue/deep-diff.js')['watchDeepDiff']>
215243
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
216244
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
217245
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>

ui/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@
1717
"@data-fair/lib-vue": "^1.17.2",
1818
"@data-fair/lib-vuetify": "^1.9.0",
1919
"@intlify/unplugin-vue-i18n": "^6.0.5",
20-
"@koumoul/vjsf": "^3.12.2",
20+
"@koumoul/vjsf": "^3.22.1",
2121
"@mdi/js": "^7.4.47",
2222
"@types/config": "^3.3.5",
2323
"@types/js-yaml": "^4.0.9",
2424
"@types/prismjs": "^1.26.5",
25+
"@types/statuses": "^2.0.6",
2526
"@vitejs/plugin-vue": "^5.1.4",
2627
"@vueuse/core": "^11.2.0",
2728
"dayjs": "^1.11.13",
@@ -30,6 +31,7 @@
3031
"marked": "^15.0.6",
3132
"prismjs": "^1.30.0",
3233
"sass-embedded": "^1.80.5",
34+
"statuses": "^2.0.1",
3335
"unplugin-auto-import": "^0.18.3",
3436
"unplugin-vue-components": "^0.27.4",
3537
"unplugin-vue-router": "^0.10.8",

ui/public/examples/fulltest.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,23 @@
607607
"enum": ["value1", "value2", "value3"]
608608
}
609609
},
610+
{
611+
"name": "testObject",
612+
"in": "query",
613+
"description": "Tous les paramètres de cet objet doivent commencer par un préfixe :\n\n - `eq_`\n - `lg_`.\n\nIls seront transformés en simples paramètres de query lors de la requête (ex: `?eq_param1=value1&lg_param2=value2`).",
614+
"schema": {
615+
"title": "Un sous-objet",
616+
"type": "object",
617+
"patternProperties": {
618+
"eq_(.*)": {
619+
"type": "string"
620+
},
621+
"lg_(.*)": {
622+
"type": "number"
623+
}
624+
}
625+
}
626+
},
610627
{
611628
"name": "testArrayOneOf",
612629
"in": "query",

ui/src/components/operation-panel-right.vue

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
<template>
2-
<v-tabs
3-
v-model="panelRight"
4-
>
2+
<v-tabs v-model="panelRight">
53
<v-tab value="serverResponse">
64
{{ t('serverResponse') }}
75
</v-tab>
8-
<v-tab
9-
value="responses"
10-
>
6+
<v-tab value="responses">
117
{{ t('responses') }}
128
</v-tab>
139
</v-tabs>
@@ -76,15 +72,22 @@
7672

7773
<template v-if="responseData">
7874
<h4>
79-
<v-chip
80-
:color="getCodeColors(responseData.status)"
81-
:text="responseData.status"
82-
density="compact"
83-
variant="elevated"
84-
label
85-
/>
86-
{{ responseData.statusText }}
75+
{{ t('requestUrl') }}
8776
</h4>
77+
<p class="text-break">
78+
{{ requestUrl }}
79+
</p>
80+
<h4>
81+
{{ t('status') }}
82+
</h4>
83+
<v-chip
84+
:color="getCodeColors(responseData.status)"
85+
:text="responseData.status"
86+
density="compact"
87+
variant="elevated"
88+
label
89+
/>
90+
{{ responseData.statusText || getDefaultStatusText(responseData.status) }}
8891
<template v-if="responseData.body">
8992
<h4>{{ t('responseBody') }}</h4>
9093
<span
@@ -144,10 +147,12 @@
144147
<script setup lang="ts">
145148
import type { Operation } from '#api/types'
146149
import YAML from 'js-yaml'
150+
import status from 'statuses'
147151
148152
const { operation, method, responseData, loading, isValid } = defineProps<{
149153
operation: Operation
150154
method: string
155+
requestUrl: string
151156
responseData: Record<string, any> | null
152157
loading: boolean
153158
isValid: boolean | null
@@ -185,6 +190,14 @@ const getCodeColors = (status: string) => {
185190
}
186191
}
187192
193+
/*
194+
* Get default status text for HTTP status codes using the statuses library
195+
*/
196+
const getDefaultStatusText = (statusCode: number | string): string => {
197+
const code = typeof statusCode === 'string' ? parseInt(statusCode) : statusCode
198+
return status.message[code] || 'Unknown Status'
199+
}
200+
188201
</script>
189202

190203
<i18n lang="yaml">
@@ -194,22 +207,26 @@ const getCodeColors = (status: string) => {
194207
execute: "Execute"
195208
noDocsResponses: "No response documentation."
196209
parsingFailed: "Unable to parse response."
210+
requestUrl: "Request URL"
197211
responseBody: "Response Body"
198212
responseHeaders: "Response Headers"
199213
responses: Responses
200214
serverResponse: "Try it out"
201215
sensitiveOperation: "Sensitive operation"
216+
status: "Status"
202217
fr:
203218
cancel: "Annuler"
204219
deleteConfirmation: "Êtes-vous sûr de vouloir effectuer cette requête de suppression ?"
205220
execute: "Exécuter"
206221
noDocsResponses: "Aucune documentation de réponse."
207222
parsingFailed: "Impossible de parser la réponse."
223+
requestUrl: "URL de la requête"
208224
responseBody: "Corps de la réponse"
209225
responseHeaders: "En-têtes de la réponse"
210226
responses: Réponses
211227
serverResponse: "Essayer"
212228
sensitiveOperation: "Opération sensible"
229+
status: "Statut"
213230
</i18n>
214231

215232
<style scoped>

ui/src/components/operation.vue

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@
166166
<operation-panel-right
167167
:operation="operation"
168168
:method="method"
169+
:request-url="requestUrl"
169170
:response-data="responseData"
170171
:loading="loading"
171172
:is-valid="isValid"
@@ -178,7 +179,6 @@
178179
<script setup lang="ts">
179180
import type { GenericEndpointQuery, Operation } from '#api/types'
180181
import type { Parameter } from '~/utils/transform'
181-
import type { SchemaObject } from 'ajv'
182182
183183
import Vjsf, { type Options as VjsfOptions } from '@koumoul/vjsf'
184184
import { marked } from 'marked'
@@ -194,8 +194,10 @@ const { operation, pathItemParameters, serverUrl, method, path } = defineProps<{
194194
const { t } = useI18n()
195195
196196
const endpointQueryValues = ref<GenericEndpointQuery>({})
197-
const endpointQuerySchema = ref<SchemaObject>(getVJSFSchema(operation, pathItemParameters))
197+
const endpointQuerySchema = getVJSFSchema(operation, pathItemParameters)
198+
console.log(endpointQuerySchema)
198199
const responseData = ref<Record<string, any> | null>(null)
200+
const requestUrl = ref<string>('')
199201
const panelLeft = ref<string>('parameters')
200202
const schemaOrExamplesTab = ref<string>('schema')
201203
const exampleSelected = ref<string>('default')
@@ -215,7 +217,7 @@ const fullPath = computed(() => {
215217
// Build URL with query parameters
216218
let url = `${serverUrl || ''}${fullPath}`
217219
if (endpointQueryValues.value.query && Object.keys(endpointQueryValues.value.query).length > 0) {
218-
const queryParams = new URLSearchParams(endpointQueryValues.value.query).toString()
220+
const queryParams = serializeQueryParams(endpointQueryValues.value.query, resolveParameters(pathItemParameters, operation.parameters as Parameter[])).toString()
219221
url += `?${queryParams}`
220222
}
221223
return url
@@ -224,6 +226,7 @@ const fullPath = computed(() => {
224226
async function executeRequest () {
225227
loading.value = true
226228
responseData.value = null
229+
requestUrl.value = fullPath.value
227230
228231
// Prepare headers
229232
const headers: Record<string, string> = {}
@@ -328,7 +331,8 @@ const vjsfOptions: VjsfOptions = {
328331
useExamples: 'help',
329332
useTitle: 'hint' as const,
330333
validateOn: 'blur',
331-
xI18n: true
334+
xI18n: true,
335+
confirmDeleteItem: false
332336
}
333337
334338
if ($uiConfig.useSimpleDirectory) {

ui/src/components/tree.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
</template>
3838

3939
<script setup lang="ts">
40-
import { VTreeview } from 'vuetify/labs/VTreeview'
4140
4241
const { jsonSchema } = defineProps<{
4342
jsonSchema: any

0 commit comments

Comments
 (0)