diff --git a/quickwit/quickwit-ui/package.json b/quickwit/quickwit-ui/package.json index 7f0806823b3..f6e569e27ff 100644 --- a/quickwit/quickwit-ui/package.json +++ b/quickwit/quickwit-ui/package.json @@ -10,6 +10,7 @@ "@emotion/react": "11.14.0", "@emotion/styled": "11.14.1", "@monaco-editor/react": "4.7.0", + "@openapi-contrib/openapi-schema-to-json-schema": "5.1.0", "@mui/icons-material": "7.3.5", "@mui/lab": "7.0.1-beta.19", "@mui/material": "7.3.5", diff --git a/quickwit/quickwit-ui/src/components/JsonEditor.tsx b/quickwit/quickwit-ui/src/components/JsonEditor.tsx index a3c88d44bd8..4762eeafeba 100644 --- a/quickwit/quickwit-ui/src/components/JsonEditor.tsx +++ b/quickwit/quickwit-ui/src/components/JsonEditor.tsx @@ -12,17 +12,42 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { BeforeMount, Editor, OnMount } from "@monaco-editor/react"; -import { useCallback } from "react"; +import { BeforeMount, Editor, OnMount, useMonaco } from "@monaco-editor/react"; +import { useCallback, useEffect, useId } from "react"; import { EDITOR_THEME } from "../utils/theme"; export function JsonEditor({ content, + readOnly = true, resizeOnMount, + jsonSchema, + onContentEdited, }: { content: unknown; + readOnly?: boolean; resizeOnMount: boolean; + jsonSchema?: object; + onContentEdited?: (value: unknown) => void; }) { + const monaco = useMonaco(); + const arbitraryFilename = "inmemory://" + useId(); + + // Apply json schema + useEffect(() => { + if (!monaco || !jsonSchema) return; + + monaco.languages.json.jsonDefaults.setDiagnosticsOptions({ + validate: true, + schemas: [ + { + uri: "http://quickwit-schema.json", + fileMatch: [arbitraryFilename], + schema: jsonSchema, + }, + ], + }); + }, [monaco, jsonSchema, arbitraryFilename]); + // Setting editor height based on lines height and count to stretch and fit its content. const onMount: OnMount = useCallback( (editor) => { @@ -53,12 +78,18 @@ export function JsonEditor({ return ( { + try { + if (value) onContentEdited?.(JSON.parse(value)); + } catch (err) {} + }} beforeMount={beforeMount} onMount={onMount} options={{ - readOnly: true, + readOnly: readOnly, fontFamily: "monospace", overviewRulerBorder: false, overviewRulerLanes: 0, diff --git a/quickwit/quickwit-ui/src/components/JsonEditorEditable.tsx b/quickwit/quickwit-ui/src/components/JsonEditorEditable.tsx new file mode 100644 index 00000000000..9c444a0d002 --- /dev/null +++ b/quickwit/quickwit-ui/src/components/JsonEditorEditable.tsx @@ -0,0 +1,98 @@ +// Copyright 2021-Present Datadog, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import CheckIcon from "@mui/icons-material/Check"; +import EditIcon from "@mui/icons-material/Edit"; +import { Box, Button, Chip, Stack } from "@mui/material"; +import { useEffect, useState } from "react"; +import { JsonEditor } from "./JsonEditor"; + +type JsonEditorEditableProps = { + saving: boolean; + pristine: boolean; + onSave: () => void; +} & React.ComponentProps; + +/** + * wrapper around JsonEditor that displays edit actions + */ +export function JsonEditorEditable({ + saving, + pristine, + onSave, + ...jsonEditorProps +}: JsonEditorEditableProps) { + const wasSaving = useDelayedTruthyValue(saving, 1000); + const showSuccess = !saving && wasSaving; + + return ( + + + + {pristine && !showSuccess && ( + } + label="Editable" + size="small" + variant="filled" + /> + )} + {(!pristine || showSuccess) && ( + + )} + + + ); +} + +/** + * Returns the value immediately when truthy, but delays returning falsy values by delayMs. + * Useful for showing success states briefly after an operation completes. + */ +function useDelayedTruthyValue(value: T, delayMs: number): T { + const [delayedValue, setDelayedValue] = useState(value); + + useEffect(() => { + if (value) { + setDelayedValue(value); + } else { + const timeout = setTimeout(() => { + setDelayedValue(value); + }, delayMs); + + return () => clearTimeout(timeout); + } + }, [value, delayMs]); + + return value || delayedValue; +} diff --git a/quickwit/quickwit-ui/src/services/client.ts b/quickwit/quickwit-ui/src/services/client.ts index cc7643b6687..2e4476a5153 100644 --- a/quickwit/quickwit-ui/src/services/client.ts +++ b/quickwit/quickwit-ui/src/services/client.ts @@ -103,13 +103,25 @@ export class Client { return this.fetch(`${this.apiRoot()}indexes`, {}); } + // TODO unit test + async updateIndexConfig( + indexId: string, + indexConfig: IndexMetadata["index_config"], + ): Promise { + return this.fetch( + `${this.apiRoot()}indexes/${indexId}`, + { method: "PUT" }, + JSON.stringify(indexConfig), + ); + } + async fetch( url: string, params: RequestInit, body: string | null = null, ): Promise { if (body !== null) { - params.method = "POST"; + params.method = params.method ?? "POST"; params.body = body; params.headers = { ...params.headers, diff --git a/quickwit/quickwit-ui/src/services/jsonShema.ts b/quickwit/quickwit-ui/src/services/jsonShema.ts new file mode 100644 index 00000000000..5f12554d639 --- /dev/null +++ b/quickwit/quickwit-ui/src/services/jsonShema.ts @@ -0,0 +1,52 @@ +// Copyright 2021-Present Datadog, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { openapiSchemaToJsonSchema } from "@openapi-contrib/openapi-schema-to-json-schema"; +import React from "react"; + +/** + * return the json schema for the given component + * based on the openapi schema at /openapi.json + * + * @param ref is a path to the component, usually starting with #/components/schemas/... + */ +export const useJsonSchema = (ref: string) => { + const [openApiSchema, setOpenApiSchema] = React.useState(null); + + console.log(openApiSchema); + + React.useEffect(() => { + schemaPromise = schemaPromise || fetchOpenApiSchema(); + schemaPromise.then(setOpenApiSchema); + }, []); + + const jsonShema = React.useMemo(() => { + if (!openApiSchema) return null; + return openapiSchemaToJsonSchema({ + ...openApiSchema, + $ref: ref, + }); + }, [openApiSchema, ref]); + + return jsonShema; +}; + +let schemaPromise: Promise | null = null; +export const fetchOpenApiSchema = async () => { + const response = await fetch("/openapi.json"); + if (!response.ok) { + throw new Error(`Failed to fetch OpenAPI schema: ${response.statusText}`); + } + return await response.json(); +}; diff --git a/quickwit/quickwit-ui/src/views/ClusterView.tsx b/quickwit/quickwit-ui/src/views/ClusterView.tsx index f3c6146cf29..424a354a995 100644 --- a/quickwit/quickwit-ui/src/views/ClusterView.tsx +++ b/quickwit/quickwit-ui/src/views/ClusterView.tsx @@ -24,6 +24,7 @@ import { import Loader from "../components/Loader"; import ErrorResponseDisplay from "../components/ResponseErrorDisplay"; import { Client } from "../services/client"; +import { useJsonSchema } from "../services/jsonShema"; import { Cluster, ResponseError } from "../utils/models"; function ClusterView() { @@ -56,9 +57,18 @@ function ClusterView() { if (loading || cluster == null) { return ; } - return ; + return ( + + ); }; + const jsonSchema = + useJsonSchema("#/components/schemas/ClusterSnapshot") ?? undefined; + return ( diff --git a/quickwit/quickwit-ui/src/views/IndexView.tsx b/quickwit/quickwit-ui/src/views/IndexView.tsx index d7d1646c3d4..fe04676a2c8 100644 --- a/quickwit/quickwit-ui/src/views/IndexView.tsx +++ b/quickwit/quickwit-ui/src/views/IndexView.tsx @@ -15,11 +15,12 @@ import { TabContext, TabList, TabPanel } from "@mui/lab"; import { Box, styled, Tab, Typography } from "@mui/material"; import Link, { LinkProps } from "@mui/material/Link"; -import React, { useCallback, useEffect, useMemo, useState } from "react"; -import { Link as RouterLink, useParams } from "react-router"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { Link as RouterLink, useParams, useSearchParams } from "react-router"; import ApiUrlFooter from "../components/ApiUrlFooter"; import { IndexSummary } from "../components/IndexSummary"; import { JsonEditor } from "../components/JsonEditor"; +import { JsonEditorEditable } from "../components/JsonEditorEditable"; import { FullBoxContainer, QBreadcrumbs, @@ -27,11 +28,8 @@ import { } from "../components/LayoutUtils"; import Loader from "../components/Loader"; import { Client } from "../services/client"; -import { Index } from "../utils/models"; - -export type ErrorResult = { - error: string; -}; +import { useJsonSchema } from "../services/jsonShema"; +import { Index, IndexMetadata } from "../utils/models"; const CustomTabPanel = styled(TabPanel)` padding-left: 0; @@ -51,35 +49,31 @@ function LinkRouter(props: LinkRouterProps) { function IndexView() { const { indexId } = useParams(); - const [loading, setLoading] = useState(false); - const [, setLoadingError] = useState(null); - const [tabIndex, setTabIndex] = useState("1"); - const [index, setIndex] = useState(); - const quickwitClient = useMemo(() => new Client(), []); + const { loading, updating, index, updateIndexConfig } = useIndex(indexId); + const [searchParams, setSearchParams] = useSearchParams(); + + const validTabs = [ + "summary", + "sources", + "doc-mapping", + "indexing-settings", + "search-settings", + "retention-settings", + "splits", + ] as const; - const handleTabIndexChange = (_: React.SyntheticEvent, newValue: string) => { - setTabIndex(newValue); + type TabValue = (typeof validTabs)[number]; + + const isValidTab = (value: string | null): value is TabValue => { + return validTabs.includes(value as TabValue); }; - const fetchIndex = useCallback(() => { - setLoading(true); - if (indexId === undefined) { - console.warn("`indexId` should always be set."); - return; - } else { - quickwitClient.getIndex(indexId).then( - (fetchedIndex) => { - setLoadingError(null); - setLoading(false); - setIndex(fetchedIndex); - }, - (error) => { - setLoading(false); - setLoadingError({ error: error }); - }, - ); - } - }, [indexId, quickwitClient]); + const tabFromUrl = searchParams.get("tab"); + const tab = isValidTab(tabFromUrl) ? tabFromUrl : "summary"; + + const setTab = (newTab: TabValue) => { + setSearchParams({ tab: newTab }); + }; const renderFetchIndexResult = () => { if (loading || index === undefined) { @@ -94,53 +88,53 @@ function IndexView() { height: "calc(100% - 48px)", }} > - + - - - - - - - - + setTab(newTab)} + aria-label="Index tabs" + > + + + + + + + - - + + - - + + - - + + - - + - - + - - + - - + + @@ -148,10 +142,6 @@ function IndexView() { } }; - useEffect(() => { - fetchIndex(); - }, [fetchIndex]); - return ( @@ -169,3 +159,215 @@ function IndexView() { } export default IndexView; + +function SummaryTab({ index }: { index: Index }) { + return ; +} + +function SourcesTab({ index }: { index: Index }) { + const jsonSchema = + useJsonSchema( + "#/components/schemas/IndexMetadataV0_8/properties/sources", + ) ?? undefined; + + return ( + + ); +} + +function DocMappingTab({ index }: { index: Index }) { + const jsonSchema = + useJsonSchema("#/components/schemas/DocMapping") ?? undefined; + return ( + + ); +} + +function IndexingSettingsTab({ + index, + updateIndexConfig, + updating, +}: { + index: Index; + updateIndexConfig: (indexConfig: IndexMetadata["index_config"]) => void; + updating: boolean; +}) { + const jsonSchema = + useJsonSchema("#/components/schemas/IndexingSettings") ?? undefined; + + const initialValue = index.metadata.index_config.indexing_settings; + const [edited, setEdited] = useState(null); + const pristine = + edited === null || JSON.stringify(edited) === JSON.stringify(initialValue); + + return ( + + updateIndexConfig({ + ...index.metadata.index_config, + indexing_settings: edited, + } as IndexMetadata["index_config"]) + } + pristine={pristine} + saving={updating} + /> + ); +} + +function SearchSettingsTab({ + index, + updateIndexConfig, + updating, +}: { + index: Index; + updateIndexConfig: (indexConfig: IndexMetadata["index_config"]) => void; + updating: boolean; +}) { + const jsonSchema = + useJsonSchema("#/components/schemas/SearchSettings") ?? undefined; + + const initialValue = index.metadata.index_config.search_settings; + const [edited, setEdited] = useState(null); + const pristine = + edited === null || JSON.stringify(edited) === JSON.stringify(initialValue); + + return ( + + updateIndexConfig({ + ...index.metadata.index_config, + search_settings: edited, + } as IndexMetadata["index_config"]) + } + pristine={pristine} + saving={updating} + /> + ); +} + +function RetentionSettingsTab({ + index, + updateIndexConfig, + updating, +}: { + index: Index; + updateIndexConfig: (indexConfig: IndexMetadata["index_config"]) => void; + updating: boolean; +}) { + const jsonSchema = + useJsonSchema("#/components/schemas/RetentionPolicy") ?? undefined; + + const initialValue = index.metadata.index_config.retention || {}; + const [edited, setEdited] = useState(null); + const pristine = + edited === null || JSON.stringify(edited) === JSON.stringify(initialValue); + + return ( + + updateIndexConfig({ + ...index.metadata.index_config, + retention: edited, + } as IndexMetadata["index_config"]) + } + pristine={pristine} + saving={updating} + /> + ); +} + +function SplitsTab({ index }: { index: Index }) { + const splitShema = useJsonSchema("#/components/schemas/Split"); + const jsonSchema = + (splitShema && { + ...splitShema, + $ref: undefined, + type: "array", + items: { $ref: "#/components/schemas/Split" }, + }) ?? + undefined; + + return ( + + ); +} + +/** + * Fetches and manages index data + */ +const useIndex = (indexId: string | undefined) => { + const quickwitClient = useMemo(() => new Client(), []); + + const onError = useMemo( + () => (err: unknown) => alert((err as any)?.message ?? err?.toString()), + [], + ); + + const [index, setIndex] = useState(); + const [updating, setUpdating] = useState(false); + + useEffect(() => { + if (!indexId) return; + + const abortController = new AbortController(); + + quickwitClient + .getIndex(indexId) + .then((index) => { + if (!abortController.signal.aborted) setIndex(index); + }) + .catch(onError); + + return () => abortController.abort(); + }, [indexId, quickwitClient, onError]); + + const updateIndexConfig = useCallback( + (indexConfig: IndexMetadata["index_config"]) => { + setUpdating(true); + + quickwitClient + .updateIndexConfig(indexConfig.index_id, indexConfig) + .then((metadata) => { + setIndex((i) => + i?.metadata.index_config.index_id === metadata.index_config.index_id + ? { ...i, metadata } + : i, + ); + }) + .catch(onError) + .finally(() => setUpdating(false)); + }, + [quickwitClient, onError], + ); + + if (!indexId) return { loading: false, index: undefined }; + + if (index?.metadata.index_config.index_id !== indexId) + return { loading: true }; + + return { loading: false, updating, index: index, updateIndexConfig }; +}; diff --git a/quickwit/quickwit-ui/yarn.lock b/quickwit/quickwit-ui/yarn.lock index a706a58bb7c..5812fc7bd60 100644 --- a/quickwit/quickwit-ui/yarn.lock +++ b/quickwit/quickwit-ui/yarn.lock @@ -1805,6 +1805,11 @@ resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz#9bdad8176be7811ad148d1f8772359041f46c6c5" integrity sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA== +"@fastify/busboy@^2.0.0": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" + integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== + "@isaacs/balanced-match@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" @@ -2374,6 +2379,19 @@ "@emnapi/runtime" "^1.4.3" "@tybys/wasm-util" "^0.10.0" +"@openapi-contrib/openapi-schema-to-json-schema@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@openapi-contrib/openapi-schema-to-json-schema/-/openapi-schema-to-json-schema-5.1.0.tgz#4f6047830fbe471d7b7c01d02a1150f691af4c88" + integrity sha512-MJnq+CxD8JAufiJoa8RK6D/8P45MEBe0teUi30TNoHRrI6MZRNgetK2Y2IfDXWGLTHMopb1d9GHonqlV2Yvztg== + dependencies: + "@types/json-schema" "^7.0.12" + "@types/lodash" "^4.14.195" + "@types/node" "^20.4.1" + fast-deep-equal "^3.1.3" + lodash "^4.17.21" + openapi-typescript "^5.4.1" + yargs "^17.7.2" + "@pkgr/core@^0.2.9": version "0.2.9" resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.9.tgz#d229a7b7f9dac167a156992ef23c7f023653f53b" @@ -3459,6 +3477,16 @@ "@types/tough-cookie" "*" parse5 "^7.0.0" +"@types/json-schema@^7.0.12": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/lodash@^4.14.195": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.21.tgz#b806831543d696b14f8112db600ea9d3a1df6ea4" + integrity sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ== + "@types/node@*": version "18.11.18" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" @@ -3476,6 +3504,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.6.tgz#26da694f75cdb057750f49d099da5e3f3824cb3e" integrity sha512-wf3Vz+jCmOQ2HV1YUJuCWdL64adYxumkrxtc+H1VUQlnQI04+5HtH+qZCOE21lBE7gIrt+CwX2Wv8Acrw5Ak6w== +"@types/node@^20.4.1": + version "20.19.25" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.25.tgz#467da94a2fd966b57cc39c357247d68047611190" + integrity sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ== + dependencies: + undici-types "~6.21.0" + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -5033,6 +5068,11 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== +fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + fast-json-patch@^3.0.0-1: version "3.1.1" resolved "https://registry.yarnpkg.com/fast-json-patch/-/fast-json-patch-3.1.1.tgz#85064ea1b1ebf97a3f7ad01e23f9337e72c66947" @@ -5291,6 +5331,16 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globalyzer@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465" + integrity sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q== + +globrex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" + integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== + gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -6451,6 +6501,11 @@ mime-types@^2.1.12, mime-types@~2.1.19: dependencies: mime-db "1.52.0" +mime@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" + integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -6658,6 +6713,18 @@ openapi-server-url-templating@^1.3.0: dependencies: apg-lite "^1.0.4" +openapi-typescript@^5.4.1: + version "5.4.2" + resolved "https://registry.yarnpkg.com/openapi-typescript/-/openapi-typescript-5.4.2.tgz#a3ea30f172119ae8a8ead62920ac19956b59e5a4" + integrity sha512-tHeRv39Yh7brqJpbUntdjtUaXrTHmC4saoyTLU/0J2I8LEFQYDXRLgnmWTMiMOB2GXugJiqHa5n9sAyd6BRqiA== + dependencies: + js-yaml "^4.1.0" + mime "^3.0.0" + prettier "^2.6.2" + tiny-glob "^0.2.9" + undici "^5.4.0" + yargs-parser "^21.0.1" + ospath@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" @@ -6841,6 +6908,11 @@ postcss@^8.5.6: picocolors "^1.1.1" source-map-js "^1.2.1" +prettier@^2.6.2: + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + pretty-bytes@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" @@ -7790,6 +7862,14 @@ through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== +tiny-glob@^0.2.9: + version "0.2.9" + resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2" + integrity sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg== + dependencies: + globalyzer "0.1.0" + globrex "^0.1.2" + tinyglobby@^0.2.15: version "0.2.15" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" @@ -7979,6 +8059,18 @@ undici-types@~5.25.1: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3" integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA== +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +undici@^5.4.0: + version "5.29.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.29.0.tgz#419595449ae3f2cdcba3580a2e8903399bd1f5a3" + integrity sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg== + dependencies: + "@fastify/busboy" "^2.0.0" + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -8323,7 +8415,7 @@ yaml@^1.10.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yargs-parser@^21.1.1: +yargs-parser@^21.0.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==