From 72088fac1cd7bafb45b8b772ca0561cab099ac8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piggy=20Park=20=28=EB=B0=95=EC=9A=A9=ED=83=9C=29?= Date: Tue, 27 May 2025 12:30:55 +0900 Subject: [PATCH 01/27] feat(reference-model): create integrated `Reference Model` (#5877) * chore(query-key): separate query-key helper Signed-off-by: samuel.park * feat(reference-query-key): create reference query-key composable Signed-off-by: samuel.park * chore: small fix Signed-off-by: samuel.park * feat(query-cache-watcher): create query-cache watcher composable Signed-off-by: samuel.park * feat(reference-model-list): create reference-model list composable Signed-off-by: samuel.park * chore: edit composable name Signed-off-by: samuel.park * feat(reference-model): create reference-model type & config Signed-off-by: samuel.park * feat(reference-model-map): create reference-model map data composable Signed-off-by: samuel.park * feat(reference-query): create reference-model query (originally variable model) Signed-off-by: samuel.park * feat(reference-model): create reference-model composable (DSL) Signed-off-by: samuel.park * feat(reference-model): create reference sync composable (for api-clients) Signed-off-by: samuel.park * chore: edit composable name Signed-off-by: samuel.park * chore: create reference-model query type Signed-off-by: samuel.park * feat(reference-model): create reference-model query helper (variable model) Signed-off-by: samuel.park * chore: small fix Signed-off-by: samuel.park * chore: edit typo Signed-off-by: samuel.park --------- Signed-off-by: samuel.park --- ...uery-key-helper.ts => query-key-helper.ts} | 9 ++ .../query-key/use-reference-query-key.ts | 52 +++++++ .../query/query-key/use-service-query-key.ts | 6 +- .../core/common/use-reference-model-sync.ts | 71 ++++++++++ .../core/common/use-watched-query-cache.ts | 32 +++++ .../reference-list/use-reference-full-list.ts | 47 +++++++ .../use-batched-reference-fetch.ts | 68 +++++++++ .../core/reference-map/use-reference-map.ts | 89 ++++++++++++ .../use-reference-query-list.ts | 94 +++++++++++++ .../use-reference-query-stat.ts | 99 +++++++++++++ .../reference-query/use-reference-query.ts | 24 ++++ .../reference/core/use-reference-model.ts | 40 ++++++ .../helpers/reference-model-menu-handler.ts | 74 ++++++++++ apps/web/src/query/reference/metric/config.ts | 8 ++ .../src/query/reference/reference-config.ts | 130 ++++++++++++++++++ .../reference/types/reference-data-type.ts | 41 ++++++ .../reference/types/reference-query-type.ts | 19 +++ .../query/reference/types/reference-type.ts | 44 ++++++ 18 files changed, 944 insertions(+), 3 deletions(-) rename apps/web/src/query/query-key/_helpers/{immutable-query-key-helper.ts => query-key-helper.ts} (67%) create mode 100644 apps/web/src/query/query-key/use-reference-query-key.ts create mode 100644 apps/web/src/query/reference/core/common/use-reference-model-sync.ts create mode 100644 apps/web/src/query/reference/core/common/use-watched-query-cache.ts create mode 100644 apps/web/src/query/reference/core/reference-list/use-reference-full-list.ts create mode 100644 apps/web/src/query/reference/core/reference-map/use-batched-reference-fetch.ts create mode 100644 apps/web/src/query/reference/core/reference-map/use-reference-map.ts create mode 100644 apps/web/src/query/reference/core/reference-query/use-reference-query-list.ts create mode 100644 apps/web/src/query/reference/core/reference-query/use-reference-query-stat.ts create mode 100644 apps/web/src/query/reference/core/reference-query/use-reference-query.ts create mode 100644 apps/web/src/query/reference/core/use-reference-model.ts create mode 100644 apps/web/src/query/reference/helpers/reference-model-menu-handler.ts create mode 100644 apps/web/src/query/reference/metric/config.ts create mode 100644 apps/web/src/query/reference/reference-config.ts create mode 100644 apps/web/src/query/reference/types/reference-data-type.ts create mode 100644 apps/web/src/query/reference/types/reference-query-type.ts create mode 100644 apps/web/src/query/reference/types/reference-type.ts diff --git a/apps/web/src/query/query-key/_helpers/immutable-query-key-helper.ts b/apps/web/src/query/query-key/_helpers/query-key-helper.ts similarity index 67% rename from apps/web/src/query/query-key/_helpers/immutable-query-key-helper.ts rename to apps/web/src/query/query-key/_helpers/query-key-helper.ts index 299510c4e6..dbe845a610 100644 --- a/apps/web/src/query/query-key/_helpers/immutable-query-key-helper.ts +++ b/apps/web/src/query/query-key/_helpers/query-key-helper.ts @@ -1,3 +1,5 @@ +import type { QueryKeyArray } from '@/query/query-key/_types/query-key-type'; + export const createImmutableObjectKeyItem = (obj: T): T => { if (obj === null || typeof obj !== 'object') { return obj; @@ -14,3 +16,10 @@ export const createImmutableObjectKeyItem = (obj: T): T => { return Object.freeze(immutableObj) as T; }; + +export const normalizeQueryKeyPart = (key: unknown): QueryKeyArray => { + if (Array.isArray(key)) { + return key; + } + return [key]; +}; diff --git a/apps/web/src/query/query-key/use-reference-query-key.ts b/apps/web/src/query/query-key/use-reference-query-key.ts new file mode 100644 index 0000000000..4c22df5836 --- /dev/null +++ b/apps/web/src/query/query-key/use-reference-query-key.ts @@ -0,0 +1,52 @@ +import type { ComputedRef } from 'vue'; +import { computed } from 'vue'; + +import { useQueryKeyAppContext } from '@/query/query-key/_composable/use-app-context-query-key'; +import { createImmutableObjectKeyItem, normalizeQueryKeyPart } from '@/query/query-key/_helpers/query-key-helper'; +import type { QueryKeyArray } from '@/query/query-key/_types/query-key-type'; + + +type ContextKeyType = string|unknown[]|object; + +type UseReferenceQueryKeyResult = { + key: ComputedRef; + withSuffix: (arg: ContextKeyType) => QueryKeyArray; +}; + +export const useReferenceQueryKey = (resource: string, queryVerb?: 'list'|'stat'): UseReferenceQueryKeyResult => { + // Runtime validation for development environment + if (import.meta.env.DEV) { + if (!resource) { + console.warn('Required parameters (resource) must be provided'); + } + } + + const queryKeyAppContext = useQueryKeyAppContext(); + + const queryKey = computed(() => { + const key = [ + ...queryKeyAppContext.value, + resource, + ]; + if (queryVerb) { + key.push(queryVerb); + } + return key; + }); + + const suffixCache = new WeakMap(); + return { + key: queryKey, + withSuffix: (arg) => { + if (typeof arg === 'object' && arg !== null) { + const cached = suffixCache.get(arg); + if (cached) return cached; + + const result = [...queryKey.value, ...normalizeQueryKeyPart(createImmutableObjectKeyItem(arg))]; + suffixCache.set(arg, result); + return result; + } + return [...queryKey.value, arg]; + }, + }; +}; diff --git a/apps/web/src/query/query-key/use-service-query-key.ts b/apps/web/src/query/query-key/use-service-query-key.ts index 33cfbb8a62..5b8673aaaf 100644 --- a/apps/web/src/query/query-key/use-service-query-key.ts +++ b/apps/web/src/query/query-key/use-service-query-key.ts @@ -4,7 +4,7 @@ import { computed } from 'vue'; import { omitPageFromLoadParams, omitPageQueryParams } from '@/query/pagination/pagination-query-helper'; import { useQueryKeyAppContext } from '@/query/query-key/_composable/use-app-context-query-key'; -import { createImmutableObjectKeyItem } from '@/query/query-key/_helpers/immutable-query-key-helper'; +import { createImmutableObjectKeyItem, normalizeQueryKeyPart } from '@/query/query-key/_helpers/query-key-helper'; import type { QueryKeyArray, ResourceName, ServiceName, Verb, } from '@/query/query-key/_types/query-key-type'; @@ -88,7 +88,7 @@ export const useServiceQueryKey = { const resolvedContextKey = toValue(contextKey); return resolvedContextKey - ? _normalizeQueryKeyPart(createImmutableObjectKeyItem(resolvedContextKey)) + ? normalizeQueryKeyPart(createImmutableObjectKeyItem(resolvedContextKey)) : []; }); @@ -121,7 +121,7 @@ export const useServiceQueryKey = = () => Promise; +type callbackForReferenceRefresh = () => Promise; + +export const useReferenceModelSync = (resourceType: ReferenceKeyType) => { + const queryClient = useQueryClient(); + + const withReferenceUpdate = async ( + mutationCallback: MutationCallback, + ): Promise => { + const response = await mutationCallback(); + await _updateReferenceCache(resourceType, response, queryClient); + return response; + }; + + const withReferenceRefresh = async ( + mutationCallback: callbackForReferenceRefresh, + ): Promise => { + await mutationCallback(); + const { key: referenceQueryKey } = useReferenceQueryKey(resourceType); + await queryClient.invalidateQueries({ queryKey: referenceQueryKey.value }); + }; + + return { withReferenceUpdate, withReferenceRefresh }; +}; + + +const _updateReferenceCache = async ( + resourceType: ReferenceKeyType, + newData: T, + queryClient: QueryClient, +) => { + const { key: referenceQueryKey, withSuffix } = useReferenceQueryKey(resourceType); + + const idKey = referenceConfigMap[resourceType].idKey; + + if (!idKey || !newData[idKey]) { + throw new Error(`Invalid resource key or data for type: ${resourceType}`); + } + + queryClient.setQueryData(referenceQueryKey, (oldData: T[] | undefined) => { + const currentResults = oldData ?? []; + + if (newData == null) { + return oldData; + } + + const existingItemIndex = currentResults.findIndex( + (item) => item?.[idKey] === newData[idKey], + ); + + if (existingItemIndex > -1) { + const updatedResults = [...currentResults]; + updatedResults[existingItemIndex] = newData; + return updatedResults; + } + + return [...currentResults, newData]; + }); + + queryClient.invalidateQueries({ queryKey: withSuffix('stat') }); + queryClient.invalidateQueries({ queryKey: withSuffix('list') }); +}; diff --git a/apps/web/src/query/reference/core/common/use-watched-query-cache.ts b/apps/web/src/query/reference/core/common/use-watched-query-cache.ts new file mode 100644 index 0000000000..bb269b535b --- /dev/null +++ b/apps/web/src/query/reference/core/common/use-watched-query-cache.ts @@ -0,0 +1,32 @@ +import type { UnwrapRef } from 'vue'; +import { onUnmounted, ref } from 'vue'; + +import { hashKey } from '@tanstack/vue-query'; + +import { referenceQueryClient as queryClient } from '@/query/clients'; +import type { QueryKeyArray } from '@/query/query-key/_types/query-key-type'; + + +export const useWatchedQueryCache = (queryKey: QueryKeyArray) => { + const data = ref(queryClient.getQueryData(queryKey)); + + const queryKeyHash = hashKey(queryKey); + + const unsubscribe = queryClient.getQueryCache().subscribe((event) => { + if ( + event.type === 'updated' + && event.query.queryHash === queryKeyHash + && event.query.state.status === 'success' + ) { + data.value = event.query.state.data as UnwrapRef; + } + }); + + onUnmounted(() => { + unsubscribe(); + }); + + return { + data, + }; +}; diff --git a/apps/web/src/query/reference/core/reference-list/use-reference-full-list.ts b/apps/web/src/query/reference/core/reference-list/use-reference-full-list.ts new file mode 100644 index 0000000000..8cda25515a --- /dev/null +++ b/apps/web/src/query/reference/core/reference-list/use-reference-full-list.ts @@ -0,0 +1,47 @@ +import { computed, ref } from 'vue'; + +import { useQuery } from '@tanstack/vue-query'; + +import { useReferenceQueryKey } from '@/query/query-key/use-reference-query-key'; +import type { ReferenceFetchInfo, ReferenceKeyType } from '@/query/reference/types/reference-type'; + + +export const useReferenceFullList = >( + resourceKey: ReferenceKeyType, + fetchInfo: ReferenceFetchInfo, + transform: (item: T) => R, +) => { + const { listFetchFn } = fetchInfo; + const { key: queryKey } = useReferenceQueryKey(resourceKey); + const hasTriggered = ref(false); + + const { + data, isFetched, isFetching, refetch, + } = useQuery({ + queryKey, + queryFn: async () => { + const response = await listFetchFn({ + query: { + only: fetchInfo.only, + }, + }); + return response.results || []; + }, + select: (item) => item.map(transform), + enabled: computed(() => hasTriggered.value), + refetchOnWindowFocus: true, + staleTime: 1000 * 60 * 5, + gcTime: 1000 * 60 * 10, + }); + + return { + referenceList: computed(() => { + if (!hasTriggered.value && !isFetched.value && !data.value) { + refetch(); + hasTriggered.value = true; + } + return data.value || []; + }), + isFetching, + }; +}; diff --git a/apps/web/src/query/reference/core/reference-map/use-batched-reference-fetch.ts b/apps/web/src/query/reference/core/reference-map/use-batched-reference-fetch.ts new file mode 100644 index 0000000000..bb1f2d3d99 --- /dev/null +++ b/apps/web/src/query/reference/core/reference-map/use-batched-reference-fetch.ts @@ -0,0 +1,68 @@ +import { + ref, +} from 'vue'; + +import { referenceQueryClient as queryClient } from '@/query/clients'; +import type { QueryKeyArray } from '@/query/query-key/_types/query-key-type'; + + +const DEBOUNCE_MS = 300; +const BATCH_SIZE = 10; +const MAX_BATCH_SIZE = 30; + +export function useBatchedReferenceFetch( + queryKey: QueryKeyArray, + fetchFn: (ids: string[]) => Promise, + getId: (item: T) => string, +) { + const pendingSet = ref(new Set()); + let debounceTimer: ReturnType | null = null; + + const enqueue = (id: string) => { + if (!id || pendingSet.value.has(id)) return; + + pendingSet.value.add(id); + + if (pendingSet.value.size >= BATCH_SIZE) { + _triggerFetch(); + } else { + if (debounceTimer) clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => { + _triggerFetch(); + }, DEBOUNCE_MS); + } + }; + + const _triggerFetch = async () => { + if (debounceTimer) { + clearTimeout(debounceTimer); + debounceTimer = null; + } + + + const idsToFetch = Array.from(pendingSet.value); + pendingSet.value.clear(); + if (idsToFetch.length === 0) return; + + const cached = queryClient.getQueryData(queryKey) || []; + const cachedIds = new Set(cached.map(getId)); + const ids = idsToFetch.filter((id) => !cachedIds.has(id)); + + const chunks = _chunkArray(ids, MAX_BATCH_SIZE); + + const fetchedArrays = await Promise.all(chunks.map(fetchFn)); + const fetched = fetchedArrays.flat(); + + const uniqueFetched = fetched.filter((item) => !cachedIds.has(getId(item))); + const merged = [...cached, ...uniqueFetched]; + + queryClient.setQueryData(queryKey, merged); + uniqueFetched.forEach((item) => cachedIds.add(getId(item))); + }; + + return { + enqueue, + }; +} + +const _chunkArray = (array: string[], size: number): string[][] => Array.from({ length: Math.ceil(array.length / size) }, (_, i) => array.slice(i * size, i * size + size)); diff --git a/apps/web/src/query/reference/core/reference-map/use-reference-map.ts b/apps/web/src/query/reference/core/reference-map/use-reference-map.ts new file mode 100644 index 0000000000..21498a28ed --- /dev/null +++ b/apps/web/src/query/reference/core/reference-map/use-reference-map.ts @@ -0,0 +1,89 @@ +import { + computed, +} from 'vue'; + +import { useReferenceQueryKey } from '@/query/query-key/use-reference-query-key'; +import { referenceConfigMap } from '@/query/reference/reference-config'; +import type { ReferenceFetchInfo, ReferenceKeyType } from '@/query/reference/types/reference-type'; + +import type { ReferenceMap } from '@/store/reference/type'; + +import { useWatchedQueryCache } from '../common/use-watched-query-cache'; +import { useBatchedReferenceFetch } from './use-batched-reference-fetch'; + +export const useReferenceMap = >( + resourceKey: ReferenceKeyType, + fetchInfo: ReferenceFetchInfo, + transform: (item: T) => R, +) => { + const _config = referenceConfigMap[resourceKey]; + + if (!_config) { + throw new Error(`Invalid reference key - map : ${resourceKey}`); + } + + const { listFetchFn } = fetchInfo; + const { key: queryKey } = useReferenceQueryKey(resourceKey); + + // Utills + const getId = (item: T) => { + if (!_config.idKey) { + throw new Error(`[getId] Invalid resource key: ${resourceKey}`); + } + return item[_config.idKey]; + }; + const batchedFecher = async (ids: string[]) => { + if (!_config.idKey) { + throw new Error(`[batchedFetcher] Invalid resource key: ${resourceKey}`); + } + let params: any = { + query: { + filter: [ + { + k: _config.idKey, + o: 'in', + v: ids, + }, + ], + }, + }; + if (fetchInfo.only) { + params = { + ...params, + query: { + ...params.query, + only: fetchInfo.only, + }, + }; + } + const response = await listFetchFn(params); + return response.results || []; + }; + + // Core + const { enqueue } = useBatchedReferenceFetch( + queryKey.value, + batchedFecher, + getId, + ); + const { data: cachedData } = useWatchedQueryCache(queryKey.value); + + // Computed + const _cachedMap = computed(() => (cachedData.value || []).reduce((acc, item) => { + acc[getId(item as T)] = transform(item as T); + return acc; + }, {} as ReferenceMap)); + + + const proxyMap = new Proxy({}, { + get(_, id: string) { + const cache = _cachedMap.value; + if (!(id in cache)) enqueue(id); + return cache[id]; + }, + }); + + return { + referenceMap: computed(() => proxyMap as ReferenceMap), + }; +}; diff --git a/apps/web/src/query/reference/core/reference-query/use-reference-query-list.ts b/apps/web/src/query/reference/core/reference-query/use-reference-query-list.ts new file mode 100644 index 0000000000..8b68884549 --- /dev/null +++ b/apps/web/src/query/reference/core/reference-query/use-reference-query-list.ts @@ -0,0 +1,94 @@ +import type { ConsoleFilterOperator } from '@cloudforet/core-lib/query/type'; +import { ApiQueryHelper } from '@cloudforet/core-lib/space-connector/helper'; + +import type { ListResponse } from '@/api-clients/_common/schema/api-verbs/list'; +import { referenceQueryClient as queryClient } from '@/query/clients'; +import { useReferenceQueryKey } from '@/query/query-key/use-reference-query-key'; +import { referenceConfigMap } from '@/query/reference/reference-config'; +import type { ReferenceQueryParams, ReferenceQueryResponse } from '@/query/reference/types/reference-query-type'; +import type { ReferenceFetchInfo, ReferenceKeyType } from '@/query/reference/types/reference-type'; + +export const useReferenceQueryList = ( + resourceKey: ReferenceKeyType, + fetchInfo: ReferenceFetchInfo, +) => { + const _config = referenceConfigMap[resourceKey]; + + if (!_config) { + throw new Error(`Invalid reference key - list : ${resourceKey}`); + } + + const { withSuffix: referenceListQueryKey } = useReferenceQueryKey(resourceKey, 'list'); + + const { + listFetchFn, + searchTargets = [_config.idKey, _config.nameKey], + only = [_config.idKey, _config.nameKey], + nameFormatter = (d: T) => d[_config.nameKey], + } = fetchInfo; + + const _getListParams = (params: ReferenceQueryParams = {}) => { + const apiQueryHelper = new ApiQueryHelper(); + apiQueryHelper.setFilters([ + { k: _config.idKey, v: [null, ''], o: '!=' }, + ]); + + if (params.options) { + Object.entries(params.options).forEach(([key, value]) => { + apiQueryHelper.addFilter({ k: key, v: value, o: '=' }); + }); + } + + if (params.search) { + const searchFilters = searchTargets.map((key) => ({ + k: key, + v: params.search ?? '', + o: '' as ConsoleFilterOperator, + })); + apiQueryHelper.setOrFilters(searchFilters); + } + if (params.start !== undefined && params.limit !== undefined) { + apiQueryHelper.setPage(params.start, params.limit); + } + + return { + query: { + only, + ...apiQueryHelper.data, + }, + }; + }; + + + const _convertToReferenceQueryList = (response: ListResponse, params: ReferenceQueryParams): ReferenceQueryResponse => { + let more = false; + if (params.start !== undefined && params.limit !== undefined && response.total_count !== undefined) { + more = (params.start * params.limit) < response.total_count; + } + return { + results: response.results ? response.results.map((d) => ({ key: d[_config.idKey], name: nameFormatter(d), data: d })) : [], + more, + title: _config.name, + }; + }; + + + const listReferenceQuery = async (params: ReferenceQueryParams = {}): Promise> => { + const queryParams = _getListParams(params); + const queryKey = referenceListQueryKey(queryParams); + + const data = await queryClient.ensureQueryData({ + queryKey, + queryFn: () => listFetchFn(queryParams), + staleTime: 1000 * 60 * 5, + }); + + return _convertToReferenceQueryList(data, params); + }; + + return { + listReferenceQuery, + }; +}; + + diff --git a/apps/web/src/query/reference/core/reference-query/use-reference-query-stat.ts b/apps/web/src/query/reference/core/reference-query/use-reference-query-stat.ts new file mode 100644 index 0000000000..e8ec7aabc1 --- /dev/null +++ b/apps/web/src/query/reference/core/reference-query/use-reference-query-stat.ts @@ -0,0 +1,99 @@ +import { ApiQueryHelper } from '@cloudforet/core-lib/space-connector/helper'; + +import type { ListResponse } from '@/api-clients/_common/schema/api-verbs/list'; +import { referenceQueryClient as queryClient } from '@/query/clients'; +import { useReferenceQueryKey } from '@/query/query-key/use-reference-query-key'; +import { referenceConfigMap } from '@/query/reference/reference-config'; +import type { ReferenceQueryParams, ReferenceQueryResponse } from '@/query/reference/types/reference-query-type'; +import type { ReferenceFetchInfo, ReferenceKeyType } from '@/query/reference/types/reference-type'; + +export const useReferenceQueryStat = ( + resourceKey: ReferenceKeyType, + fetchInfo: ReferenceFetchInfo, +) => { + if (!referenceConfigMap[resourceKey]) { + throw new Error(`Invalid reference key - stat : ${resourceKey}`); + } + + const { withSuffix: referenceStatQueryKey } = useReferenceQueryKey(resourceKey, 'stat'); + + const { + statFetchFn, + } = fetchInfo; + + const _getStatParams = (params: ReferenceQueryParams = {}, dataKey: string) => { + const apiQueryHelper = new ApiQueryHelper(); + + // Additional Filter (ex. data_source_id) + if (params.options) { + apiQueryHelper.setFilters([]); + Object.entries(params.options).forEach(([key, value]) => { + apiQueryHelper.addFilter({ k: key, v: value, o: '=' }); + }); + apiQueryHelper.addFilter({ k: dataKey, v: [null, ''], o: '!=' }); + } else { + apiQueryHelper.setFilters([ + { k: dataKey, v: [null, ''], o: '!=' }, + ]); + } + + + if (params.search) { + apiQueryHelper.addFilter({ k: dataKey, v: params.search, o: '' }); + } + if (params.filters) { + apiQueryHelper.addFilter({ k: dataKey, v: params.filters, o: '=' }); + } + + if (params.start !== undefined && params.limit !== undefined) { + apiQueryHelper.setPage(params.start, params.limit); + } + + return { + query: { + distinct: dataKey, + ...apiQueryHelper.data, + }, + }; + }; + + const _convertToReferenceQueryStat = (response: ListResponse, params: ReferenceQueryParams, dataKey: string): ReferenceQueryResponse => { + let more = false; + if (params.start !== undefined && params.limit !== undefined && response.total_count !== undefined) { + more = (params.start - 1 + params.limit) < response.total_count; + } + + return { + results: (response.results ?? []).map((d) => ({ key: d, name: d, data: d })), + more, + title: dataKey, + }; + }; + + const statReferenceQuery = (dataKey: string) => { + const _dataKey = dataKey; + return async (params: ReferenceQueryParams = {}): Promise> => { + if (!statFetchFn) { + console.warn('This resource does not support stat fetch'); + return { results: [], more: false }; + } + const queryParams = _getStatParams(params, _dataKey); + const queryKey = referenceStatQueryKey(queryParams); + + const data = await queryClient.ensureQueryData({ + queryKey, + queryFn: () => statFetchFn(queryParams), + staleTime: 1000 * 60 * 5, + }); + + return _convertToReferenceQueryStat(data, params, dataKey); + }; + }; + + + return { + statReferenceQuery, + }; +}; + + diff --git a/apps/web/src/query/reference/core/reference-query/use-reference-query.ts b/apps/web/src/query/reference/core/reference-query/use-reference-query.ts new file mode 100644 index 0000000000..0b8e8e0526 --- /dev/null +++ b/apps/web/src/query/reference/core/reference-query/use-reference-query.ts @@ -0,0 +1,24 @@ +import { referenceConfigMap } from '@/query/reference/reference-config'; +import type { ReferenceFetchInfo, ReferenceKeyType } from '@/query/reference/types/reference-type'; + +import { useReferenceQueryList } from './use-reference-query-list'; +import { useReferenceQueryStat } from './use-reference-query-stat'; + +export const useReferenceQuery = ( + resourceKey: ReferenceKeyType, + fetchInfo: ReferenceFetchInfo, +) => { + if (!referenceConfigMap[resourceKey]) { + throw new Error(`Invalid reference key: ${resourceKey}`); + } + + const { listReferenceQuery } = useReferenceQueryList(resourceKey, fetchInfo); + const { statReferenceQuery } = useReferenceQueryStat(resourceKey, fetchInfo); + + return { + listReferenceQuery, + statReferenceQuery, + }; +}; + + diff --git a/apps/web/src/query/reference/core/use-reference-model.ts b/apps/web/src/query/reference/core/use-reference-model.ts new file mode 100644 index 0000000000..41082dfc31 --- /dev/null +++ b/apps/web/src/query/reference/core/use-reference-model.ts @@ -0,0 +1,40 @@ +import { computed } from 'vue'; + + +import { useReferenceFullList } from '@/query/reference/core/reference-list/use-reference-full-list'; +import { useReferenceMap } from '@/query/reference/core/reference-map/use-reference-map'; +import { useReferenceQuery } from '@/query/reference/core/reference-query/use-reference-query'; +import type { ReferenceFetchInfo, ReferenceKeyType } from '@/query/reference/types/reference-type'; + +export const useReferenceModel = >( + resourceKey: ReferenceKeyType, + fetchInfo: ReferenceFetchInfo, + transform: (item: T) => R, +) => { + const { referenceMap } = useReferenceMap( + resourceKey, + fetchInfo, + transform, + ); + const { referenceList, isFetching } = useReferenceFullList( + resourceKey, + fetchInfo, + transform, + ); + + const { + listReferenceQuery, + statReferenceQuery, + } = useReferenceQuery( + resourceKey, + fetchInfo, + ); + + return { + map: referenceMap, + allItems: referenceList, + fetchList: listReferenceQuery, + fetchStat: statReferenceQuery, + loading: computed(() => isFetching.value || referenceList.value.length === 0), + }; +}; diff --git a/apps/web/src/query/reference/helpers/reference-model-menu-handler.ts b/apps/web/src/query/reference/helpers/reference-model-menu-handler.ts new file mode 100644 index 0000000000..d09448ef29 --- /dev/null +++ b/apps/web/src/query/reference/helpers/reference-model-menu-handler.ts @@ -0,0 +1,74 @@ +import type { Ref } from 'vue'; +import { isRef } from 'vue'; + +import type { MenuAttachHandler } from '@cloudforet/mirinae/types/hooks/use-context-menu-attach/use-context-menu-attach'; + +import type { ReferenceQueryParams, ReferenceQueryResponse } from '@/query/reference/types/reference-query-type'; + +import ErrorHandler from '@/common/composables/error/errorHandler'; + + + +export interface ReferenceModelMenuHandlerInfo { + fetchFn: (params?: ReferenceQueryParams) => Promise>; + title?: string; +} + +type Options = Record; +interface Item { + label: string; + name: string; + data: T; +} +export const getReferenceModelMenuHandler = (referenceModelInfoList: ReferenceModelMenuHandlerInfo[], options: Options|Ref = {}): MenuAttachHandler> => { + const _referenceModelInfoList = referenceModelInfoList; + return async (inputText: string, pageStart, pageLimit, filters, resultIndex) => { + const _query = { + start: pageStart, + limit: pageLimit ?? 10, + search: inputText, + filters: filters?.length ? filters.map((f) => f.name as string) : undefined, + options: isRef(options) ? options.value : options, + }; + + // if resultIndex is empty, it means that the handler is called for the first time. so, we need to call all referenceModels' list(). + if (resultIndex === undefined) { + const responses = await Promise.all(_referenceModelInfoList.map(({ fetchFn }) => fetchFn(_query))); + const handlerResults = responses.map((result, resIndex) => ({ + results: result.results.map((d) => ({ + name: d.key, + label: d.name, + data: d.data, + })), + more: result.more, + title: _referenceModelInfoList[resIndex].title || result.title, + })); + + return handlerResults; + } + + // if resultIndex is given, just call the specific referenceModel's list(). + const referenceModelInfo = _referenceModelInfoList[resultIndex]; + + if (!referenceModelInfo) { + ErrorHandler.handleError(new Error(`No reference model found for index ${resultIndex}`)); + return []; + } + const response = await referenceModelInfo.fetchFn(_query); + if (!response) { + ErrorHandler.handleError(new Error(`No response from reference model for index ${resultIndex}`)); + return []; + } + return _referenceModelInfoList.map((modelInfo, i) => { + if (i !== resultIndex) return { results: [], title: response.title || modelInfo.title }; + return { + results: response.results.map((d) => ({ + name: d.key, label: d.name, data: d.data, + })), + more: response.more, + title: response.title || modelInfo.title, + }; + }); + }; +}; + diff --git a/apps/web/src/query/reference/metric/config.ts b/apps/web/src/query/reference/metric/config.ts new file mode 100644 index 0000000000..5048000f34 --- /dev/null +++ b/apps/web/src/query/reference/metric/config.ts @@ -0,0 +1,8 @@ +import type { ReferenceConfig } from '@/query/reference/types/reference-type'; + +export const metricConfig: ReferenceConfig = { + name: 'Metric', + resourceKey: 'metric', + idKey: 'metric_id', + nameKey: 'name', +}; diff --git a/apps/web/src/query/reference/reference-config.ts b/apps/web/src/query/reference/reference-config.ts new file mode 100644 index 0000000000..2c5c411240 --- /dev/null +++ b/apps/web/src/query/reference/reference-config.ts @@ -0,0 +1,130 @@ +import { metricConfig } from '@/query/reference/metric/config'; + +export const referenceConfigMap = { + // cloudServiceType: { + // key: 'cloud_service_type', + // idKey: 'cloud_service_type_id', + // name: 'Cloud Service Type', + // }, + // cloudServiceQuerySet: { + // key: 'cloud_service_query_set', + // idKey: 'cloud_service_query_set_id', + // name: 'Cloud Service Query Set', + // }, + // collector: { + // key: 'collector', + // idKey: 'collector_id', + // name: 'Collector', + // }, + // costDataSource: { + // key: 'cost_data_source', + // idKey: 'cost_data_source_id', + // name: 'Cost Data Source', + // }, + // plugin: { + // key: 'plugin', + // idKey: 'plugin_id', + // name: 'Plugin', + // }, + // projectGroup: { + // key: 'project_group', + // idKey: 'project_group_id', + // name: 'Project Group', + // }, + // project: { + // key: 'project', + // idKey: 'project_id', + // name: 'Project', + // }, + // protocol: { + // key: 'protocol', + // idKey: 'protocol_id', + // name: 'Protocol', + // }, + // provider: { + // key: 'provider', + // idKey: 'provider_id', + // name: 'Provider', + // }, + // publicDashboard: { + // key: 'public_dashboard', + // idKey: 'public_dashboard_id', + // name: 'Public Dashboard', + // }, + // publicFolder: { + // key: 'public_folder', + // idKey: 'public_folder_id', + // name: 'Public Folder', + // }, + // region: { + // key: 'region', + // idKey: 'region_code', + // name: 'Region', + // }, + // secret: { + // key: 'secret', + // idKey: 'secret_id', + // name: 'Secret', + // }, + // serviceAccount: { + // key: 'service_account', + // idKey: 'service_account_id', + // name: 'Service Account', + // }, + // trustedAccount: { + // key: 'trusted_account', + // idKey: 'trusted_account_id', + // name: 'Trusted Account', + // }, + // user: { + // key: 'user', + // idKey: 'user_id', + // name: 'User', + // }, + // userGroup: { + // key: 'user_group', + // idKey: 'user_group_id', + // name: 'User Group', + // }, + // webhook: { + // key: 'webhook', + // idKey: 'webhook_id', + // name: 'Webhook', + // }, + // workspace: { + // key: 'workspace', + // idKey: 'workspace_id', + // name: 'Workspace', + // }, + // escalationPolicy: { + // key: 'escalation_policy', + // idKey: 'escalation_policy_id', + // name: 'Escalation Policy', + // }, + metric: metricConfig, + // namespace: { + // key: 'namespace', + // idKey: 'namespace_id', + // name: 'Namespace', + // }, + // workspaceGroup: { + // key: 'workspace_group', + // idKey: 'workspace_group_id', + // name: 'Workspace Group', + // }, + // role: { + // key: 'role', + // idKey: 'role_id', + // name: 'Role', + // }, + // service: { + // key: 'service', + // idKey: 'service_id', + // name: 'Service', + // }, + // app: { + // key: 'app', + // idKey: 'app_id', + // name: 'App', + // }, +}; diff --git a/apps/web/src/query/reference/types/reference-data-type.ts b/apps/web/src/query/reference/types/reference-data-type.ts new file mode 100644 index 0000000000..5ffab86d11 --- /dev/null +++ b/apps/web/src/query/reference/types/reference-data-type.ts @@ -0,0 +1,41 @@ +export interface ReferenceItem> { + key?: string; + label?: string; + name?: string; + color?: string; + icon?: string; + provider?: string; + continent?: { + continent_code?: string; + continent_label?: string; + latitude?: number; + longitude?: number; + }; + latitude?: string; + longitude?: string; + data?: Data; + description?: string; + link?: string; +} + +export type ReferenceMap = Record; + +export interface ReferenceState> { + items: Items; +} + +export interface ReferenceRootState { + isAllLoaded: boolean; +} + +export interface ReferenceLoadOptions { + lazyLoad?: boolean; + force?: boolean; +} + +export interface ReferenceTypeInfo { + type: string; // 'project' + key: string; // project_id + name: string; // Project + referenceMap: ReferenceMap; +} diff --git a/apps/web/src/query/reference/types/reference-query-type.ts b/apps/web/src/query/reference/types/reference-query-type.ts new file mode 100644 index 0000000000..4074a6a989 --- /dev/null +++ b/apps/web/src/query/reference/types/reference-query-type.ts @@ -0,0 +1,19 @@ +export interface ReferenceQueryParams { + search?: string; + start?: number; + limit?: number; + filters?: string[]; // to filter selected items + options?: Record; // for custom options by config +} + +export interface ReferenceQueryResponse { + results: Value[]; + more?: boolean; + title?: string; +} + +export interface Value { + key: string; + name: string; + data?: T; +} diff --git a/apps/web/src/query/reference/types/reference-type.ts b/apps/web/src/query/reference/types/reference-type.ts new file mode 100644 index 0000000000..f3a0ab5251 --- /dev/null +++ b/apps/web/src/query/reference/types/reference-type.ts @@ -0,0 +1,44 @@ +import type { ListResponse } from '@/api-clients/_common/schema/api-verbs/list'; + +export type ReferenceKeyType = +'cloud_service_type' +|'cloud_service_query_set' +|'collector' +|'cost_data_source' +|'plugin' +|'project_group' +|'project' +|'protocol' +|'provider' +|'public_dashboard' +|'public_folder' +|'region' +|'secret' +|'service_account' +|'trusted_account' +|'user' +|'user_group' +|'webhook' +|'workspace' +|'escalation_policy' +|'metric' +|'namespace' +|'workspace_group' +|'role' +|'service' +|'app'; + +export interface ReferenceFetchInfo { + listFetchFn: (params: any) => Promise>; + statFetchFn?: (params: any) => Promise>; + only?: string[]; + searchTargets?: string[]; + nameFormatter?: (...args: any) => string; +} + +export interface ReferenceConfig { + name: string; + resourceKey: ReferenceKeyType; + idKey: string; + nameKey: string; +} From 676801ea9a6b874f6e2b66549ff4c65aae93ee3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piggy=20Park=20=28=EB=B0=95=EC=9A=A9=ED=83=9C=29?= Date: Thu, 19 Jun 2025 16:10:26 +0900 Subject: [PATCH 02/27] feat(reference-model): create new reference model (#5941) * chore: move file architecture Signed-off-by: samuel.park * feat(reference-model): create reference-model core Signed-off-by: samuel.park * chore: small fix Signed-off-by: samuel.park * chore: refactor dir architecture Signed-off-by: samuel.park * chore: apply changed query file path Signed-off-by: samuel.park * fix(reference-model): refactor interface to make it easy to read Signed-off-by: samuel.park * refactor(reference-model): separate reference-model composable & small fix Signed-off-by: samuel.park --------- Signed-off-by: samuel.park --- .../use-global-dashboard-query.ts | 4 +- .../WidgetFormDataSourcePopover.vue | 2 +- .../WidgetFormDataTableCardAddContents.vue | 2 +- ...dgetFormDataTableCardTransformContents.vue | 2 +- .../_components/WidgetFormOverlayStep2.vue | 2 +- .../use-data-table-cascade-update.ts | 2 +- .../_composables/use-data-table-load-query.ts | 4 +- .../use-widget-data-table-list-query.ts | 4 +- .../use-widget-data-table-query.ts | 4 +- .../_composables/use-widget-load-query.ts | 4 +- .../widgets/_composables/use-widget-query.ts | 4 +- .../__tests__/use-service-query-key.test.ts | 0 .../_composable/use-app-context-query-key.ts | 0 .../query-key/_helpers/query-key-helper.ts | 2 +- .../query-key/types}/query-key-type.ts | 0 .../query-key/use-reference-query-key.ts | 6 +- .../query-key/use-service-query-key.md | 0 .../query-key/use-service-query-key.ts | 22 ++-- .../core/common/use-reference-model-sync.ts | 71 ----------- .../reference-list/use-reference-full-list.ts | 2 +- .../use-batched-reference-fetch.ts | 68 ----------- .../core/reference-map/use-reference-map.ts | 89 -------------- .../use-reference-query-list.ts | 2 +- .../use-reference-query-stat.ts | 2 +- .../_internal/use-batched-reference-fetch.ts | 110 ++++++++++++++++++ .../_internal/use-reference-full-fetch.ts | 47 ++++++++ .../_internal/use-reference-reactive-cache.ts | 38 ++++++ .../composables/use-reference-model.ts | 38 ++++++ .../reference-model/types/reference-type.ts | 26 +++++ .../reference-model/utils/reference-helper.ts | 20 ++++ .../utils/reference-proxy-helper.ts | 28 +++++ .../composable/use-resource-cache-sync.ts | 68 +++++++++++ .../shared/composable/use-resource-info.ts | 23 ++++ .../shared/contants/resource-config-map.ts | 57 +++++++++ .../shared/types/resource-type.ts | 20 ++++ .../pagination/pagination-query-helper.ts | 0 .../pagination/use-scoped-pagination-query.ts | 6 +- .../use-scoped-infinite-query.ts | 2 +- .../use-scoped-query.ts | 2 +- .../use-watched-query-cache.ts | 3 +- .../use-dashboard-change-folder-mutation.ts | 2 +- .../use-dashboard-delete-mutation.ts | 2 +- .../use-dashboard-folder-delete-mutation.ts | 2 +- .../use-dashboard-folder-share-mutation.ts | 2 +- .../mutations/use-dashboard-share-mutation.ts | 2 +- .../use-dashboard-update-mutation.ts | 2 +- .../use-dashboard-template-query.ts | 4 +- .../DashboardCreateStep2BundleCase.vue | 2 +- .../DashboardCreateStep2SingleCase.vue | 2 +- .../composables/use-dashboard-get-query.ts | 6 +- .../use-dashboard-widget-list-query.ts | 4 +- .../v2/components/AlertDetailTabsTimeline.vue | 4 +- .../ServiceDetailTabsNotifications.vue | 4 +- .../ServiceDetailTabsNotificationsDetail.vue | 4 +- ...viceDetailTabsOverviewEscalationPolicy.vue | 4 +- .../ServiceDetailTabsWebhookDetail.vue | 11 +- .../use-alert-list-pagination-query.ts | 4 +- .../use-service-list-pagination-query.ts | 4 +- .../use-webhook-list-pagination-query.ts | 4 +- .../components/CloudServiceAlertsTab.vue | 4 +- ...dvancedSettingsCostReportConfiguration.vue | 2 +- .../AdvancedSettingsSetAdjustmentsOverlay.vue | 2 +- .../queries/use-cost-analysis-query.ts | 4 +- .../queries/use-cost-query-set-query.ts | 4 +- .../queries/use-cost-report-config-query.ts | 4 +- .../use-report-adjustment-policy-query.ts | 4 +- .../queries/use-report-adjustment-query.ts | 4 +- .../use-dashboard-bundle-delete-workflow.ts | 2 +- .../composables/use-dashboard-folder-query.ts | 4 +- .../composables/use-dashboard-query.ts | 4 +- .../composables/use-dashboard-search-query.ts | 4 +- .../iam/components/SelectChannelCard.vue | 4 +- .../UserGroupChannelSetInputForm.vue | 4 +- ...rGroupManagementTabNotificationChannel.vue | 6 +- .../components/UserGroupManagementTable.vue | 4 +- .../components/NotificationAddForm.vue | 2 +- .../components/NotificationAddFormData.vue | 4 +- .../components/NotificationChannelItem.vue | 2 +- .../components/NotificationChannelList.vue | 4 +- .../my-page/composables/notification-item.ts | 2 +- .../ops-flow/components/BoardTaskComment.vue | 2 +- .../ops-flow/components/BoardTaskTable.vue | 4 +- .../components/CommentDeleteModal.vue | 2 +- .../ops-flow/components/TaskAssignModal.vue | 2 +- .../components/TaskCategoryDeleteModal.vue | 2 +- .../ops-flow/components/TaskCategoryForm.vue | 2 +- .../components/TaskContentBaseForm.vue | 2 +- .../ops-flow/components/TaskDeleteModal.vue | 2 +- .../components/TaskStatusDeleteModal.vue | 2 +- .../components/TaskStatusSetDefaultModal.vue | 2 +- .../ops-flow/components/TaskStatusTree.vue | 2 +- .../components/TaskTypeDeleteModal.vue | 2 +- .../composables/use-associated-tasks-query.ts | 2 +- .../composables/use-categories-query.ts | 2 +- .../composables/use-current-category.ts | 2 +- .../composables/use-package-category-bind.ts | 2 +- .../ops-flow/composables/use-package-query.ts | 2 +- .../composables/use-package-workspace-bind.ts | 2 +- .../composables/use-packages-query.ts | 2 +- .../use-status-option-form-mutations.ts | 2 +- .../composables/use-task-events-query.ts | 2 +- .../ops-flow/composables/use-task-query.ts | 2 +- .../use-task-type-form-mutations.ts | 2 +- .../composables/use-task-type-query.ts | 2 +- .../composables/use-task-types-query.ts | 2 +- .../ops-flow/pages/TaskCreatePage.vue | 2 +- .../ops-flow/pages/TaskDetailPage.vue | 2 +- .../queries/use-project-group-query.ts | 4 +- .../queries/use-project-groups-query.ts | 4 +- .../ProjectMainProjectGroupFormModal.vue | 2 +- .../components/ProjectAndGroupListPanel.vue | 4 +- .../v2/components/ProjectDeleteModal.vue | 2 +- .../v2/components/ProjectGroupCreateModal.vue | 2 +- .../v2/components/ProjectGroupRenameModal.vue | 2 +- .../v2/components/ProjectMoveModal.vue | 2 +- .../use-project-children-list-query.ts | 4 +- .../use-project-dashboard-folder-query.ts | 4 +- .../queries/use-project-dashboard-query.ts | 4 +- .../use-project-dashboard-search-query.ts | 4 +- .../composables/queries/use-project-query.ts | 4 +- .../queries/use-workspace-users-query.ts | 4 +- .../shared/components/AccountSummary.vue | 4 +- .../composables/use-asset-daily-updates.ts | 4 +- .../use-asset-summary-providers.ts | 4 +- .../shared/composables/use-cost-chart-data.ts | 4 +- .../composables/use-cost-data-source-query.ts | 4 +- .../use-cost-report-config-query.ts | 4 +- 127 files changed, 651 insertions(+), 404 deletions(-) rename apps/web/src/query/{ => core}/query-key/__tests__/use-service-query-key.test.ts (100%) rename apps/web/src/query/{ => core}/query-key/_composable/use-app-context-query-key.ts (100%) rename apps/web/src/query/{ => core}/query-key/_helpers/query-key-helper.ts (88%) rename apps/web/src/query/{query-key/_types => core/query-key/types}/query-key-type.ts (100%) rename apps/web/src/query/{ => core}/query-key/use-reference-query-key.ts (86%) rename apps/web/src/query/{ => core}/query-key/use-service-query-key.md (100%) rename apps/web/src/query/{ => core}/query-key/use-service-query-key.ts (92%) delete mode 100644 apps/web/src/query/reference/core/common/use-reference-model-sync.ts delete mode 100644 apps/web/src/query/reference/core/reference-map/use-batched-reference-fetch.ts delete mode 100644 apps/web/src/query/reference/core/reference-map/use-reference-map.ts create mode 100644 apps/web/src/query/resource-query/reference-model/composables/_internal/use-batched-reference-fetch.ts create mode 100644 apps/web/src/query/resource-query/reference-model/composables/_internal/use-reference-full-fetch.ts create mode 100644 apps/web/src/query/resource-query/reference-model/composables/_internal/use-reference-reactive-cache.ts create mode 100644 apps/web/src/query/resource-query/reference-model/composables/use-reference-model.ts create mode 100644 apps/web/src/query/resource-query/reference-model/types/reference-type.ts create mode 100644 apps/web/src/query/resource-query/reference-model/utils/reference-helper.ts create mode 100644 apps/web/src/query/resource-query/reference-model/utils/reference-proxy-helper.ts create mode 100644 apps/web/src/query/resource-query/shared/composable/use-resource-cache-sync.ts create mode 100644 apps/web/src/query/resource-query/shared/composable/use-resource-info.ts create mode 100644 apps/web/src/query/resource-query/shared/contants/resource-config-map.ts create mode 100644 apps/web/src/query/resource-query/shared/types/resource-type.ts rename apps/web/src/query/{ => service-query}/pagination/pagination-query-helper.ts (100%) rename apps/web/src/query/{ => service-query}/pagination/use-scoped-pagination-query.ts (95%) rename apps/web/src/query/{composables => service-query}/use-scoped-infinite-query.ts (98%) rename apps/web/src/query/{composables => service-query}/use-scoped-query.ts (98%) rename apps/web/src/query/{reference/core/common => shared}/use-watched-query-cache.ts (90%) diff --git a/apps/web/src/common/composables/global-dashboard/use-global-dashboard-query.ts b/apps/web/src/common/composables/global-dashboard/use-global-dashboard-query.ts index d7f5654ded..d5aaf6b1c9 100644 --- a/apps/web/src/common/composables/global-dashboard/use-global-dashboard-query.ts +++ b/apps/web/src/common/composables/global-dashboard/use-global-dashboard-query.ts @@ -7,8 +7,8 @@ import { ApiQueryHelper } from '@cloudforet/core-lib/space-connector/helper'; import { usePrivateDashboardApi } from '@/api-clients/dashboard/private-dashboard/composables/use-private-dashboard-api'; import { usePublicDashboardApi } from '@/api-clients/dashboard/public-dashboard/composables/use-public-dashboard-api'; -import { useScopedQuery } from '@/query/composables/use-scoped-query'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; +import { useScopedQuery } from '@/query/service-query/use-scoped-query'; import { useAppContextStore } from '@/store/app-context/app-context-store'; diff --git a/apps/web/src/common/modules/widgets/_components/WidgetFormDataSourcePopover.vue b/apps/web/src/common/modules/widgets/_components/WidgetFormDataSourcePopover.vue index c6d897c2c9..6b13212100 100644 --- a/apps/web/src/common/modules/widgets/_components/WidgetFormDataSourcePopover.vue +++ b/apps/web/src/common/modules/widgets/_components/WidgetFormDataSourcePopover.vue @@ -12,7 +12,7 @@ import { import type { WidgetCreateParams, WidgetModel } from '@/api-clients/dashboard/_types/widget-type'; import type { DataTableAddParameters } from '@/api-clients/dashboard/public-data-table/schema/api-verbs/add'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; import { i18n } from '@/translations'; import { useAllReferenceStore } from '@/store/reference/all-reference-store'; diff --git a/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardAddContents.vue b/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardAddContents.vue index 9a4269820e..db6879bbf0 100644 --- a/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardAddContents.vue +++ b/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardAddContents.vue @@ -17,7 +17,7 @@ import type { PrivateDataTableModel } from '@/api-clients/dashboard/private-data import type { DataTableDeleteParameters } from '@/api-clients/dashboard/public-data-table/schema/api-verbs/delete'; import type { DataTableUpdateParameters } from '@/api-clients/dashboard/public-data-table/schema/api-verbs/update'; import type { PublicDataTableModel } from '@/api-clients/dashboard/public-data-table/schema/model'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; import { i18n } from '@/translations'; import { useAllReferenceStore } from '@/store/reference/all-reference-store'; diff --git a/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformContents.vue b/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformContents.vue index 1f4f445748..b2e70030ae 100644 --- a/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformContents.vue +++ b/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformContents.vue @@ -14,7 +14,7 @@ import type { PrivateDataTableModel } from '@/api-clients/dashboard/private-data import type { DataTableDeleteParameters } from '@/api-clients/dashboard/public-data-table/schema/api-verbs/delete'; import type { DataTableTransformParameters } from '@/api-clients/dashboard/public-data-table/schema/api-verbs/transform'; import type { PublicDataTableModel } from '@/api-clients/dashboard/public-data-table/schema/model'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; import { i18n } from '@/translations'; import { showErrorMessage, showSuccessMessage } from '@/lib/helper/notice-alert-helper'; diff --git a/apps/web/src/common/modules/widgets/_components/WidgetFormOverlayStep2.vue b/apps/web/src/common/modules/widgets/_components/WidgetFormOverlayStep2.vue index 68b1b67d40..bd89162de7 100644 --- a/apps/web/src/common/modules/widgets/_components/WidgetFormOverlayStep2.vue +++ b/apps/web/src/common/modules/widgets/_components/WidgetFormOverlayStep2.vue @@ -16,7 +16,7 @@ import type { } from '@/api-clients/dashboard/_types/dashboard-type'; import { usePrivateDashboardApi } from '@/api-clients/dashboard/private-dashboard/composables/use-private-dashboard-api'; import { usePublicDashboardApi } from '@/api-clients/dashboard/public-dashboard/composables/use-public-dashboard-api'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; import { i18n } from '@/translations'; import { useAppContextStore } from '@/store/app-context/app-context-store'; diff --git a/apps/web/src/common/modules/widgets/_composables/use-data-table-cascade-update.ts b/apps/web/src/common/modules/widgets/_composables/use-data-table-cascade-update.ts index f703cca0cd..ef6b807a18 100644 --- a/apps/web/src/common/modules/widgets/_composables/use-data-table-cascade-update.ts +++ b/apps/web/src/common/modules/widgets/_composables/use-data-table-cascade-update.ts @@ -6,7 +6,7 @@ import { import { useQueryClient } from '@tanstack/vue-query'; import type { ListResponse } from '@/api-clients/_common/schema/api-verbs/list'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; import ErrorHandler from '@/common/composables/error/errorHandler'; import { useWidgetDataTableListQuery } from '@/common/modules/widgets/_composables/use-widget-data-table-list-query'; diff --git a/apps/web/src/common/modules/widgets/_composables/use-data-table-load-query.ts b/apps/web/src/common/modules/widgets/_composables/use-data-table-load-query.ts index 7e13c6ef66..57baafce2a 100644 --- a/apps/web/src/common/modules/widgets/_composables/use-data-table-load-query.ts +++ b/apps/web/src/common/modules/widgets/_composables/use-data-table-load-query.ts @@ -4,8 +4,8 @@ import { computed } from 'vue'; import { usePrivateDataTableApi } from '@/api-clients/dashboard/private-data-table/composables/use-private-data-table-api'; import { usePublicDataTableApi } from '@/api-clients/dashboard/public-data-table/composables/use-public-data-table-api'; import type { DataTableLoadParameters } from '@/api-clients/dashboard/public-data-table/schema/api-verbs/load'; -import { useScopedQuery } from '@/query/composables/use-scoped-query'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; +import { useScopedQuery } from '@/query/service-query/use-scoped-query'; import { WIDGET_LOAD_STALE_TIME } from '@/common/modules/widgets/_constants/widget-constant'; diff --git a/apps/web/src/common/modules/widgets/_composables/use-widget-data-table-list-query.ts b/apps/web/src/common/modules/widgets/_composables/use-widget-data-table-list-query.ts index 30d299184e..21804a922f 100644 --- a/apps/web/src/common/modules/widgets/_composables/use-widget-data-table-list-query.ts +++ b/apps/web/src/common/modules/widgets/_composables/use-widget-data-table-list-query.ts @@ -7,8 +7,8 @@ import { usePrivateDataTableApi } from '@/api-clients/dashboard/private-data-tab import { usePublicDataTableApi } from '@/api-clients/dashboard/public-data-table/composables/use-public-data-table-api'; import type { DataTableListParameters } from '@/api-clients/dashboard/public-data-table/schema/api-verbs/list'; import type { DataTableUpdateParameters } from '@/api-clients/dashboard/public-data-table/schema/api-verbs/update'; -import { useScopedQuery } from '@/query/composables/use-scoped-query'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; +import { useScopedQuery } from '@/query/service-query/use-scoped-query'; import type { DataTableModel } from '@/common/modules/widgets/types/widget-data-table-type'; diff --git a/apps/web/src/common/modules/widgets/_composables/use-widget-data-table-query.ts b/apps/web/src/common/modules/widgets/_composables/use-widget-data-table-query.ts index 959b66eaa7..8cb791c2f6 100644 --- a/apps/web/src/common/modules/widgets/_composables/use-widget-data-table-query.ts +++ b/apps/web/src/common/modules/widgets/_composables/use-widget-data-table-query.ts @@ -3,8 +3,8 @@ import { computed } from 'vue'; import { usePrivateDataTableApi } from '@/api-clients/dashboard/private-data-table/composables/use-private-data-table-api'; import { usePublicDataTableApi } from '@/api-clients/dashboard/public-data-table/composables/use-public-data-table-api'; -import { useScopedQuery } from '@/query/composables/use-scoped-query'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; +import { useScopedQuery } from '@/query/service-query/use-scoped-query'; const STALE_TIME = 1000 * 60 * 5; // 5 minutes diff --git a/apps/web/src/common/modules/widgets/_composables/use-widget-load-query.ts b/apps/web/src/common/modules/widgets/_composables/use-widget-load-query.ts index 6505b30313..cf71ae912e 100644 --- a/apps/web/src/common/modules/widgets/_composables/use-widget-load-query.ts +++ b/apps/web/src/common/modules/widgets/_composables/use-widget-load-query.ts @@ -3,8 +3,8 @@ import { computed, type ComputedRef } from 'vue'; import type { WidgetLoadParams, WidgetLoadSumParams } from '@/api-clients/dashboard/_types/widget-type'; import { usePrivateWidgetApi } from '@/api-clients/dashboard/private-widget/composables/use-private-widget-api'; import { usePublicWidgetApi } from '@/api-clients/dashboard/public-widget/composables/use-public-widget-api'; -import { useScopedQuery } from '@/query/composables/use-scoped-query'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; +import { useScopedQuery } from '@/query/service-query/use-scoped-query'; import { WIDGET_LOAD_STALE_TIME } from '@/common/modules/widgets/_constants/widget-constant'; diff --git a/apps/web/src/common/modules/widgets/_composables/use-widget-query.ts b/apps/web/src/common/modules/widgets/_composables/use-widget-query.ts index e06be200e0..8f5916948d 100644 --- a/apps/web/src/common/modules/widgets/_composables/use-widget-query.ts +++ b/apps/web/src/common/modules/widgets/_composables/use-widget-query.ts @@ -8,8 +8,8 @@ import { usePrivateWidgetApi } from '@/api-clients/dashboard/private-widget/comp import type { PrivateWidgetGetParameters } from '@/api-clients/dashboard/private-widget/schema/api-verbs/get'; import { usePublicWidgetApi } from '@/api-clients/dashboard/public-widget/composables/use-public-widget-api'; import type { PublicWidgetGetParameters } from '@/api-clients/dashboard/public-widget/schema/api-verbs/get'; -import { useScopedQuery } from '@/query/composables/use-scoped-query'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; +import { useScopedQuery } from '@/query/service-query/use-scoped-query'; const STALE_TIME = 1000 * 60 * 5; diff --git a/apps/web/src/query/query-key/__tests__/use-service-query-key.test.ts b/apps/web/src/query/core/query-key/__tests__/use-service-query-key.test.ts similarity index 100% rename from apps/web/src/query/query-key/__tests__/use-service-query-key.test.ts rename to apps/web/src/query/core/query-key/__tests__/use-service-query-key.test.ts diff --git a/apps/web/src/query/query-key/_composable/use-app-context-query-key.ts b/apps/web/src/query/core/query-key/_composable/use-app-context-query-key.ts similarity index 100% rename from apps/web/src/query/query-key/_composable/use-app-context-query-key.ts rename to apps/web/src/query/core/query-key/_composable/use-app-context-query-key.ts diff --git a/apps/web/src/query/query-key/_helpers/query-key-helper.ts b/apps/web/src/query/core/query-key/_helpers/query-key-helper.ts similarity index 88% rename from apps/web/src/query/query-key/_helpers/query-key-helper.ts rename to apps/web/src/query/core/query-key/_helpers/query-key-helper.ts index dbe845a610..4cf3509344 100644 --- a/apps/web/src/query/query-key/_helpers/query-key-helper.ts +++ b/apps/web/src/query/core/query-key/_helpers/query-key-helper.ts @@ -1,4 +1,4 @@ -import type { QueryKeyArray } from '@/query/query-key/_types/query-key-type'; +import type { QueryKeyArray } from '@/query/core/query-key/types/query-key-type'; export const createImmutableObjectKeyItem = (obj: T): T => { if (obj === null || typeof obj !== 'object') { diff --git a/apps/web/src/query/query-key/_types/query-key-type.ts b/apps/web/src/query/core/query-key/types/query-key-type.ts similarity index 100% rename from apps/web/src/query/query-key/_types/query-key-type.ts rename to apps/web/src/query/core/query-key/types/query-key-type.ts diff --git a/apps/web/src/query/query-key/use-reference-query-key.ts b/apps/web/src/query/core/query-key/use-reference-query-key.ts similarity index 86% rename from apps/web/src/query/query-key/use-reference-query-key.ts rename to apps/web/src/query/core/query-key/use-reference-query-key.ts index 4c22df5836..16f4bbb833 100644 --- a/apps/web/src/query/query-key/use-reference-query-key.ts +++ b/apps/web/src/query/core/query-key/use-reference-query-key.ts @@ -1,9 +1,9 @@ import type { ComputedRef } from 'vue'; import { computed } from 'vue'; -import { useQueryKeyAppContext } from '@/query/query-key/_composable/use-app-context-query-key'; -import { createImmutableObjectKeyItem, normalizeQueryKeyPart } from '@/query/query-key/_helpers/query-key-helper'; -import type { QueryKeyArray } from '@/query/query-key/_types/query-key-type'; +import { useQueryKeyAppContext } from '@/query/core/query-key/_composable/use-app-context-query-key'; +import { createImmutableObjectKeyItem, normalizeQueryKeyPart } from '@/query/core/query-key/_helpers/query-key-helper'; +import type { QueryKeyArray } from '@/query/core/query-key/types/query-key-type'; type ContextKeyType = string|unknown[]|object; diff --git a/apps/web/src/query/query-key/use-service-query-key.md b/apps/web/src/query/core/query-key/use-service-query-key.md similarity index 100% rename from apps/web/src/query/query-key/use-service-query-key.md rename to apps/web/src/query/core/query-key/use-service-query-key.md diff --git a/apps/web/src/query/query-key/use-service-query-key.ts b/apps/web/src/query/core/query-key/use-service-query-key.ts similarity index 92% rename from apps/web/src/query/query-key/use-service-query-key.ts rename to apps/web/src/query/core/query-key/use-service-query-key.ts index 5b8673aaaf..505297dcf0 100644 --- a/apps/web/src/query/query-key/use-service-query-key.ts +++ b/apps/web/src/query/core/query-key/use-service-query-key.ts @@ -2,12 +2,12 @@ import { toValue } from '@vueuse/core'; import type { Ref, ComputedRef } from 'vue'; import { computed } from 'vue'; -import { omitPageFromLoadParams, omitPageQueryParams } from '@/query/pagination/pagination-query-helper'; -import { useQueryKeyAppContext } from '@/query/query-key/_composable/use-app-context-query-key'; -import { createImmutableObjectKeyItem, normalizeQueryKeyPart } from '@/query/query-key/_helpers/query-key-helper'; +import { useQueryKeyAppContext } from '@/query/core/query-key/_composable/use-app-context-query-key'; +import { createImmutableObjectKeyItem, normalizeQueryKeyPart } from '@/query/core/query-key/_helpers/query-key-helper'; import type { QueryKeyArray, ResourceName, ServiceName, Verb, -} from '@/query/query-key/_types/query-key-type'; +} from '@/query/core/query-key/types/query-key-type'; +import { omitPageFromLoadParams, omitPageQueryParams } from '@/query/service-query/pagination/pagination-query-helper'; // Cache for debug logs @@ -129,14 +129,12 @@ export const useServiceQueryKey = ; }; - - -const _normalizeQueryKeyPart = (key: unknown): QueryKeyArray => { - if (Array.isArray(key)) { - return key; - } - return [key]; -}; +// const _normalizeQueryKeyPart = (key: unknown): QueryKeyArray => { +// if (Array.isArray(key)) { +// return key; +// } +// return [key]; +// }; const _omitPageParamsByVerb = >(verb: Verb, params = {}) => { if (verb === 'load') return omitPageFromLoadParams(params); diff --git a/apps/web/src/query/reference/core/common/use-reference-model-sync.ts b/apps/web/src/query/reference/core/common/use-reference-model-sync.ts deleted file mode 100644 index fe2fe7f956..0000000000 --- a/apps/web/src/query/reference/core/common/use-reference-model-sync.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type { QueryClient } from '@tanstack/vue-query'; -import { useQueryClient } from '@tanstack/vue-query'; - -import { useReferenceQueryKey } from '@/query/query-key/use-reference-query-key'; -import { referenceConfigMap } from '@/query/reference/reference-config'; -import type { ReferenceKeyType } from '@/query/reference/types/reference-type'; - - - -type MutationCallback = () => Promise; -type callbackForReferenceRefresh = () => Promise; - -export const useReferenceModelSync = (resourceType: ReferenceKeyType) => { - const queryClient = useQueryClient(); - - const withReferenceUpdate = async ( - mutationCallback: MutationCallback, - ): Promise => { - const response = await mutationCallback(); - await _updateReferenceCache(resourceType, response, queryClient); - return response; - }; - - const withReferenceRefresh = async ( - mutationCallback: callbackForReferenceRefresh, - ): Promise => { - await mutationCallback(); - const { key: referenceQueryKey } = useReferenceQueryKey(resourceType); - await queryClient.invalidateQueries({ queryKey: referenceQueryKey.value }); - }; - - return { withReferenceUpdate, withReferenceRefresh }; -}; - - -const _updateReferenceCache = async ( - resourceType: ReferenceKeyType, - newData: T, - queryClient: QueryClient, -) => { - const { key: referenceQueryKey, withSuffix } = useReferenceQueryKey(resourceType); - - const idKey = referenceConfigMap[resourceType].idKey; - - if (!idKey || !newData[idKey]) { - throw new Error(`Invalid resource key or data for type: ${resourceType}`); - } - - queryClient.setQueryData(referenceQueryKey, (oldData: T[] | undefined) => { - const currentResults = oldData ?? []; - - if (newData == null) { - return oldData; - } - - const existingItemIndex = currentResults.findIndex( - (item) => item?.[idKey] === newData[idKey], - ); - - if (existingItemIndex > -1) { - const updatedResults = [...currentResults]; - updatedResults[existingItemIndex] = newData; - return updatedResults; - } - - return [...currentResults, newData]; - }); - - queryClient.invalidateQueries({ queryKey: withSuffix('stat') }); - queryClient.invalidateQueries({ queryKey: withSuffix('list') }); -}; diff --git a/apps/web/src/query/reference/core/reference-list/use-reference-full-list.ts b/apps/web/src/query/reference/core/reference-list/use-reference-full-list.ts index 8cda25515a..2d2de80faf 100644 --- a/apps/web/src/query/reference/core/reference-list/use-reference-full-list.ts +++ b/apps/web/src/query/reference/core/reference-list/use-reference-full-list.ts @@ -2,7 +2,7 @@ import { computed, ref } from 'vue'; import { useQuery } from '@tanstack/vue-query'; -import { useReferenceQueryKey } from '@/query/query-key/use-reference-query-key'; +import { useReferenceQueryKey } from '@/query/core/query-key/use-reference-query-key'; import type { ReferenceFetchInfo, ReferenceKeyType } from '@/query/reference/types/reference-type'; diff --git a/apps/web/src/query/reference/core/reference-map/use-batched-reference-fetch.ts b/apps/web/src/query/reference/core/reference-map/use-batched-reference-fetch.ts deleted file mode 100644 index bb1f2d3d99..0000000000 --- a/apps/web/src/query/reference/core/reference-map/use-batched-reference-fetch.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { - ref, -} from 'vue'; - -import { referenceQueryClient as queryClient } from '@/query/clients'; -import type { QueryKeyArray } from '@/query/query-key/_types/query-key-type'; - - -const DEBOUNCE_MS = 300; -const BATCH_SIZE = 10; -const MAX_BATCH_SIZE = 30; - -export function useBatchedReferenceFetch( - queryKey: QueryKeyArray, - fetchFn: (ids: string[]) => Promise, - getId: (item: T) => string, -) { - const pendingSet = ref(new Set()); - let debounceTimer: ReturnType | null = null; - - const enqueue = (id: string) => { - if (!id || pendingSet.value.has(id)) return; - - pendingSet.value.add(id); - - if (pendingSet.value.size >= BATCH_SIZE) { - _triggerFetch(); - } else { - if (debounceTimer) clearTimeout(debounceTimer); - debounceTimer = setTimeout(() => { - _triggerFetch(); - }, DEBOUNCE_MS); - } - }; - - const _triggerFetch = async () => { - if (debounceTimer) { - clearTimeout(debounceTimer); - debounceTimer = null; - } - - - const idsToFetch = Array.from(pendingSet.value); - pendingSet.value.clear(); - if (idsToFetch.length === 0) return; - - const cached = queryClient.getQueryData(queryKey) || []; - const cachedIds = new Set(cached.map(getId)); - const ids = idsToFetch.filter((id) => !cachedIds.has(id)); - - const chunks = _chunkArray(ids, MAX_BATCH_SIZE); - - const fetchedArrays = await Promise.all(chunks.map(fetchFn)); - const fetched = fetchedArrays.flat(); - - const uniqueFetched = fetched.filter((item) => !cachedIds.has(getId(item))); - const merged = [...cached, ...uniqueFetched]; - - queryClient.setQueryData(queryKey, merged); - uniqueFetched.forEach((item) => cachedIds.add(getId(item))); - }; - - return { - enqueue, - }; -} - -const _chunkArray = (array: string[], size: number): string[][] => Array.from({ length: Math.ceil(array.length / size) }, (_, i) => array.slice(i * size, i * size + size)); diff --git a/apps/web/src/query/reference/core/reference-map/use-reference-map.ts b/apps/web/src/query/reference/core/reference-map/use-reference-map.ts deleted file mode 100644 index 21498a28ed..0000000000 --- a/apps/web/src/query/reference/core/reference-map/use-reference-map.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { - computed, -} from 'vue'; - -import { useReferenceQueryKey } from '@/query/query-key/use-reference-query-key'; -import { referenceConfigMap } from '@/query/reference/reference-config'; -import type { ReferenceFetchInfo, ReferenceKeyType } from '@/query/reference/types/reference-type'; - -import type { ReferenceMap } from '@/store/reference/type'; - -import { useWatchedQueryCache } from '../common/use-watched-query-cache'; -import { useBatchedReferenceFetch } from './use-batched-reference-fetch'; - -export const useReferenceMap = >( - resourceKey: ReferenceKeyType, - fetchInfo: ReferenceFetchInfo, - transform: (item: T) => R, -) => { - const _config = referenceConfigMap[resourceKey]; - - if (!_config) { - throw new Error(`Invalid reference key - map : ${resourceKey}`); - } - - const { listFetchFn } = fetchInfo; - const { key: queryKey } = useReferenceQueryKey(resourceKey); - - // Utills - const getId = (item: T) => { - if (!_config.idKey) { - throw new Error(`[getId] Invalid resource key: ${resourceKey}`); - } - return item[_config.idKey]; - }; - const batchedFecher = async (ids: string[]) => { - if (!_config.idKey) { - throw new Error(`[batchedFetcher] Invalid resource key: ${resourceKey}`); - } - let params: any = { - query: { - filter: [ - { - k: _config.idKey, - o: 'in', - v: ids, - }, - ], - }, - }; - if (fetchInfo.only) { - params = { - ...params, - query: { - ...params.query, - only: fetchInfo.only, - }, - }; - } - const response = await listFetchFn(params); - return response.results || []; - }; - - // Core - const { enqueue } = useBatchedReferenceFetch( - queryKey.value, - batchedFecher, - getId, - ); - const { data: cachedData } = useWatchedQueryCache(queryKey.value); - - // Computed - const _cachedMap = computed(() => (cachedData.value || []).reduce((acc, item) => { - acc[getId(item as T)] = transform(item as T); - return acc; - }, {} as ReferenceMap)); - - - const proxyMap = new Proxy({}, { - get(_, id: string) { - const cache = _cachedMap.value; - if (!(id in cache)) enqueue(id); - return cache[id]; - }, - }); - - return { - referenceMap: computed(() => proxyMap as ReferenceMap), - }; -}; diff --git a/apps/web/src/query/reference/core/reference-query/use-reference-query-list.ts b/apps/web/src/query/reference/core/reference-query/use-reference-query-list.ts index 8b68884549..f3b8ee1d1f 100644 --- a/apps/web/src/query/reference/core/reference-query/use-reference-query-list.ts +++ b/apps/web/src/query/reference/core/reference-query/use-reference-query-list.ts @@ -3,7 +3,7 @@ import { ApiQueryHelper } from '@cloudforet/core-lib/space-connector/helper'; import type { ListResponse } from '@/api-clients/_common/schema/api-verbs/list'; import { referenceQueryClient as queryClient } from '@/query/clients'; -import { useReferenceQueryKey } from '@/query/query-key/use-reference-query-key'; +import { useReferenceQueryKey } from '@/query/core/query-key/use-reference-query-key'; import { referenceConfigMap } from '@/query/reference/reference-config'; import type { ReferenceQueryParams, ReferenceQueryResponse } from '@/query/reference/types/reference-query-type'; import type { ReferenceFetchInfo, ReferenceKeyType } from '@/query/reference/types/reference-type'; diff --git a/apps/web/src/query/reference/core/reference-query/use-reference-query-stat.ts b/apps/web/src/query/reference/core/reference-query/use-reference-query-stat.ts index e8ec7aabc1..eb71397628 100644 --- a/apps/web/src/query/reference/core/reference-query/use-reference-query-stat.ts +++ b/apps/web/src/query/reference/core/reference-query/use-reference-query-stat.ts @@ -2,7 +2,7 @@ import { ApiQueryHelper } from '@cloudforet/core-lib/space-connector/helper'; import type { ListResponse } from '@/api-clients/_common/schema/api-verbs/list'; import { referenceQueryClient as queryClient } from '@/query/clients'; -import { useReferenceQueryKey } from '@/query/query-key/use-reference-query-key'; +import { useReferenceQueryKey } from '@/query/core/query-key/use-reference-query-key'; import { referenceConfigMap } from '@/query/reference/reference-config'; import type { ReferenceQueryParams, ReferenceQueryResponse } from '@/query/reference/types/reference-query-type'; import type { ReferenceFetchInfo, ReferenceKeyType } from '@/query/reference/types/reference-type'; diff --git a/apps/web/src/query/resource-query/reference-model/composables/_internal/use-batched-reference-fetch.ts b/apps/web/src/query/resource-query/reference-model/composables/_internal/use-batched-reference-fetch.ts new file mode 100644 index 0000000000..1b26fa507c --- /dev/null +++ b/apps/web/src/query/resource-query/reference-model/composables/_internal/use-batched-reference-fetch.ts @@ -0,0 +1,110 @@ +import type { ComputedRef } from 'vue'; +import { + ref, +} from 'vue'; + +import { referenceQueryClient as queryClient } from '@/query/clients'; +import type { QueryKeyArray } from '@/query/core/query-key/types/query-key-type'; +import { createResourceIdResolver } from '@/query/resource-query/reference-model/utils/reference-helper'; +import { useResourceInfo } from '@/query/resource-query/shared/composable/use-resource-info'; +import type { ResourceKeyType, ResourceCacheType } from '@/query/resource-query/shared/types/resource-type'; + + +/* +* DEBOUNCE_MS : Minimum wait time (ms) before triggering fetch after the last enqueue call +* BATCH_SIZE : Minimum number of IDs to accumulate before triggering an immediate fetch +* MAX_BATCH_SIZE : Maximum number of IDs to include in a single fetch batch +* */ +const DEBOUNCE_MS = 300; +const BATCH_SIZE = 15; +const MAX_BATCH_SIZE = 45; + +export function useBatchedReferenceFetch>( + resourceKey: ResourceKeyType, + queryKey: ComputedRef, + fetchOptions: { only: string[] } = { only: [] }, +) { + const { config, api } = useResourceInfo(resourceKey); + const { only = [] } = fetchOptions; + const getId = createResourceIdResolver(resourceKey); + + const batchedFetcher = async (ids: string[]): Promise => { + if (!config.idKey) { + throw new Error(`[batchedFetcher] Invalid resource key: ${resourceKey}`); + } + let params: any = { + query: { + filter: [ + { + k: config.idKey, + o: 'in', + v: ids, + }, + ], + }, + }; + if (only && only.length > 0) { + params = { + ...params, + query: { + ...params.query, + only, + }, + }; + } + const response = await api.list(params); + return response.results || []; + }; + + + const pendingSet = ref(new Set()); + let debounceTimer: ReturnType | null = null; + + const enqueue = (id: string) => { + if (!id || pendingSet.value.has(id)) return; + + pendingSet.value.add(id); + + if (pendingSet.value.size >= BATCH_SIZE) { + _triggerFetch(); + } else { + if (debounceTimer) clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => { + _triggerFetch(); + }, DEBOUNCE_MS); + } + }; + + const _triggerFetch = async () => { + if (debounceTimer) { + clearTimeout(debounceTimer); + debounceTimer = null; + } + + const idsToFetch = Array.from(pendingSet.value); + pendingSet.value.clear(); + if (idsToFetch.length === 0) return; + + const cached = queryClient.getQueryData>(queryKey.value) || {}; + const cachedIds = new Set(Object.keys(cached)); + const ids = idsToFetch.filter((id) => !cachedIds.has(id)); + + const chunks = _chunkArray(ids, MAX_BATCH_SIZE); + + const fetchedArrays = await Promise.all(chunks.map(batchedFetcher)); + const fetched = fetchedArrays.flat(); + + const uniqueFetched = fetched.filter((item) => !cachedIds.has(getId(item))); + const merged = { ...cached }; + uniqueFetched.forEach((item) => { + merged[getId(item)] = item; + }); + queryClient.setQueryData(queryKey.value, merged); + }; + + return { + fetcher: enqueue, + }; +} + +const _chunkArray = (array: string[], size: number): string[][] => Array.from({ length: Math.ceil(array.length / size) }, (_, i) => array.slice(i * size, i * size + size)); diff --git a/apps/web/src/query/resource-query/reference-model/composables/_internal/use-reference-full-fetch.ts b/apps/web/src/query/resource-query/reference-model/composables/_internal/use-reference-full-fetch.ts new file mode 100644 index 0000000000..f205e83a2a --- /dev/null +++ b/apps/web/src/query/resource-query/reference-model/composables/_internal/use-reference-full-fetch.ts @@ -0,0 +1,47 @@ +import { referenceQueryClient as queryClient } from '@/query/clients'; +import type { QueryKeyArray } from '@/query/core/query-key/types/query-key-type'; +import type { ReferenceConfig } from '@/query/resource-query/reference-model/types/reference-type'; +import { createResourceIdResolver } from '@/query/resource-query/reference-model/utils/reference-helper'; +import { useResourceInfo } from '@/query/resource-query/shared/composable/use-resource-info'; +import type { ResourceCacheType, ResourceKeyType } from '@/query/resource-query/shared/types/resource-type'; + + +export const useReferenceFullFetch = >( + resourceKey: ResourceKeyType, + queryKey: QueryKeyArray, + referenceConfig: ReferenceConfig, +) => { + const { api } = useResourceInfo(resourceKey); + const { only } = referenceConfig; + + + // Utills + const getId = createResourceIdResolver(resourceKey); + + const fullFetcher = async (): Promise => { + let params: any = {}; + if (only) { + params = { + query: { + only, + }, + }; + } + const response = await api.list(params); + return (response.results as T[]) || []; + }; + + const fetcher = async () => { + const results = await fullFetcher(); + const map: ResourceCacheType = {}; + results.forEach((item) => { + map[getId(item)] = item; + }); + queryClient.setQueryData(queryKey, map); + }; + + + return { + fetcher, + }; +}; diff --git a/apps/web/src/query/resource-query/reference-model/composables/_internal/use-reference-reactive-cache.ts b/apps/web/src/query/resource-query/reference-model/composables/_internal/use-reference-reactive-cache.ts new file mode 100644 index 0000000000..52cdd182fc --- /dev/null +++ b/apps/web/src/query/resource-query/reference-model/composables/_internal/use-reference-reactive-cache.ts @@ -0,0 +1,38 @@ +import type { ComputedRef, Ref } from 'vue'; +import { + ref, reactive, watchEffect, +} from 'vue'; + + +import { isEqual } from 'lodash'; + +import type { QueryKeyArray } from '@/query/core/query-key/types/query-key-type'; +import type { ReferenceItem } from '@/query/resource-query/reference-model/types/reference-type'; +import { useWatchedQueryCache } from '@/query/shared/use-watched-query-cache'; + + +export const useReferenceReactiveCache = , R extends ReferenceItem>( + queryKey: ComputedRef, + adaptor: (arg: T) => ReferenceItem, +) => { + const referenceMapRefs = reactive>>({}); + + const { data: cachedData } = useWatchedQueryCache>(queryKey.value); + + watchEffect(() => { + if (cachedData.value) { + Object.entries(cachedData.value).forEach(([id, item]) => { + const referenceData = adaptor(item) as R; + if (!referenceMapRefs[id]) { + referenceMapRefs[id] = ref(referenceData) as Ref; + } else if (!isEqual(referenceMapRefs[id].value, referenceData)) { + referenceMapRefs[id].value = referenceData; + } + }); + } + }); + + return { + referenceMapRefs, + }; +}; diff --git a/apps/web/src/query/resource-query/reference-model/composables/use-reference-model.ts b/apps/web/src/query/resource-query/reference-model/composables/use-reference-model.ts new file mode 100644 index 0000000000..90ad79be08 --- /dev/null +++ b/apps/web/src/query/resource-query/reference-model/composables/use-reference-model.ts @@ -0,0 +1,38 @@ + +import { + ref, +} from 'vue'; + +import { useReferenceQueryKey } from '@/query/core/query-key/use-reference-query-key'; +import { useBatchedReferenceFetch } from '@/query/resource-query/reference-model/composables/_internal/use-batched-reference-fetch'; +import { useReferenceReactiveCache } from '@/query/resource-query/reference-model/composables/_internal/use-reference-reactive-cache'; +import type { ReferenceItem } from '@/query/resource-query/reference-model/types/reference-type'; +import { makeReferenceProxy } from '@/query/resource-query/reference-model/utils/reference-proxy-helper'; +import type { ResourceKeyType } from '@/query/resource-query/shared/types/resource-type'; + +export const useReferenceModel = , R extends ReferenceItem>( + resourceKey: ResourceKeyType, + referenceAdaptor: (arg: T) => ReferenceItem, + fetchOptions: { only: string[] } = { only: [] }, +) => { + const { key: queryKey } = useReferenceQueryKey(resourceKey); + + const { fetcher: enqueue } = useBatchedReferenceFetch(resourceKey, queryKey, fetchOptions); + + const { referenceMapRefs } = useReferenceReactiveCache(queryKey, referenceAdaptor); + + const referenceMap = makeReferenceProxy>({}, (_, id: string) => { + if (!(id in referenceMapRefs)) { + enqueue(id); + referenceMapRefs[id] = ref(undefined); + } + return referenceMapRefs[id].value; + }); + + return { + referenceMap, + }; +}; + + +// TODO: add referenceModel DX guidelines diff --git a/apps/web/src/query/resource-query/reference-model/types/reference-type.ts b/apps/web/src/query/resource-query/reference-model/types/reference-type.ts new file mode 100644 index 0000000000..61cd2f04cd --- /dev/null +++ b/apps/web/src/query/resource-query/reference-model/types/reference-type.ts @@ -0,0 +1,26 @@ +export interface ReferenceItem> { + key: string; + label: string; + name: string; + color?: string; + icon?: string; + provider?: string; + continent?: { + continent_code?: string; + continent_label?: string; + latitude?: number; + longitude?: number; + }; + latitude?: string; + longitude?: string; + data: Data; + description?: string; + link?: string; +} + +export type ReferenceMap = Record; + +export interface ReferenceConfig { + transform: (arg: T) => ReferenceItem; + only: string[]; +} diff --git a/apps/web/src/query/resource-query/reference-model/utils/reference-helper.ts b/apps/web/src/query/resource-query/reference-model/utils/reference-helper.ts new file mode 100644 index 0000000000..2d24f1fae5 --- /dev/null +++ b/apps/web/src/query/resource-query/reference-model/utils/reference-helper.ts @@ -0,0 +1,20 @@ + +import { RESOURCE_CONFIG_MAP } from '@/query/resource-query/shared/contants/resource-config-map'; +import type { ResourceKeyType } from '@/query/resource-query/shared/types/resource-type'; + +export function createResourceIdResolver(resourceKey: ResourceKeyType): (item: T) => string { + const config = RESOURCE_CONFIG_MAP[resourceKey]; + + if (!config || !config.idKey) { + throw new Error(`[createResourceIdResolver] Invalid or missing idKey for resource key: "${resourceKey}"`); + } + + return (item: T): string => { + const id = item[config.idKey]; + if (typeof id !== 'string') { + throw new Error(`[createResourceIdResolver] Expected string id for resource "${resourceKey}", got: ${typeof id}`); + } + return id; + }; +} + diff --git a/apps/web/src/query/resource-query/reference-model/utils/reference-proxy-helper.ts b/apps/web/src/query/resource-query/reference-model/utils/reference-proxy-helper.ts new file mode 100644 index 0000000000..176289fdfa --- /dev/null +++ b/apps/web/src/query/resource-query/reference-model/utils/reference-proxy-helper.ts @@ -0,0 +1,28 @@ +const IGNORED_KEYS = new Set([ + '__ob__', + '__v_isRef', + '__v_isReactive', + '__v_raw', + '_isVue', + '__v_readonly', + '__v_isReadonly', + 'state', + 'effect', + 'currentRoute', + 'toJSON', + 'toString', + 'render', + 'constructor', + Symbol.toPrimitive, + Symbol.iterator, +]); + +export const makeReferenceProxy = ( + target: T, + baseGet: (target: T, prop: string) => any, +) => new Proxy(target, { + get(_, id: string) { + if (IGNORED_KEYS.has(id) || typeof id !== 'string') return undefined; + return baseGet(_, id); + }, + }); diff --git a/apps/web/src/query/resource-query/shared/composable/use-resource-cache-sync.ts b/apps/web/src/query/resource-query/shared/composable/use-resource-cache-sync.ts new file mode 100644 index 0000000000..1b31c54a24 --- /dev/null +++ b/apps/web/src/query/resource-query/shared/composable/use-resource-cache-sync.ts @@ -0,0 +1,68 @@ +import type { QueryClient } from '@tanstack/vue-query'; +import { useQueryClient } from '@tanstack/vue-query'; + +import { useReferenceQueryKey } from '@/query/core/query-key/use-reference-query-key'; +import { RESOURCE_CONFIG_MAP } from '@/query/resource-query/shared/contants/resource-config-map'; +import type { ResourceCacheType, ResourceKeyType } from '@/query/resource-query/shared/types/resource-type'; + + + +type MutationCallback = () => Promise; +type callbackForReferenceRefresh = () => Promise; + +export const useResourceCacheSync = >(resourceType: ResourceKeyType) => { + const queryClient = useQueryClient(); + + const mutateWithResourceCacheUpdate = async ( + mutationCallback: MutationCallback, + ): Promise => { + const response = await mutationCallback(); + await _updateResourceCache(resourceType, response, queryClient); + return response; + }; + + const refreshResourceCache = async ( + mutationCallback: callbackForReferenceRefresh, + ): Promise => { + await mutationCallback(); + const { key: referenceQueryKey } = useReferenceQueryKey(resourceType); + await queryClient.invalidateQueries({ queryKey: referenceQueryKey.value }); + }; + + return { mutateWithResourceCacheUpdate, refreshResourceCache }; +}; + + +const _updateResourceCache = async >( + resourceType: ResourceKeyType, + newData: T, + queryClient: QueryClient, +) => { + const { key: referenceQueryKey, withSuffix } = useReferenceQueryKey(resourceType); + + const idKey = RESOURCE_CONFIG_MAP[resourceType].idKey; + + if (!idKey || typeof newData !== 'object' || newData === null || !(idKey in newData)) { + throw new Error(`Invalid resource key or data for type: ${resourceType}`); + } + + queryClient.setQueryData>(referenceQueryKey, (oldData: ResourceCacheType|undefined) => { + const currentResults = oldData ?? {}; + const newDataId = newData[idKey]; + + if (newDataId in currentResults) { + const updatedResults = { ...currentResults }; + updatedResults[newDataId] = newData; + return updatedResults; + } + + return { + ...currentResults, + [newDataId]: newData, + }; + }); + await Promise.all([ + queryClient.invalidateQueries({ queryKey: withSuffix('stat') }), + queryClient.invalidateQueries({ queryKey: withSuffix('list') }), + ]); +}; diff --git a/apps/web/src/query/resource-query/shared/composable/use-resource-info.ts b/apps/web/src/query/resource-query/shared/composable/use-resource-info.ts new file mode 100644 index 0000000000..f1e43dc4c0 --- /dev/null +++ b/apps/web/src/query/resource-query/shared/composable/use-resource-info.ts @@ -0,0 +1,23 @@ +import { useProjectGroupApi } from '@/api-clients/identity/project-group/composables/use-project-group-api'; +import { useProjectApi } from '@/api-clients/identity/project/composables/use-project-api'; +import { useRoleApi } from '@/api-clients/identity/role/composables/use-role-api'; +import { useServiceAccountApi } from '@/api-clients/identity/service-account/composables/use-service-account-api'; +import { RESOURCE_CONFIG_MAP } from '@/query/resource-query/shared/contants/resource-config-map'; +import type { ResourceKeyType } from '@/query/resource-query/shared/types/resource-type'; + + +export const useResourceInfo = (resourceKey: ResourceKeyType) => { + const referenceConfig = RESOURCE_CONFIG_MAP[resourceKey]; + + const resourceApiMap = { + project: useProjectApi, + projectGroup: useProjectGroupApi, + role: useRoleApi, + serviceAccount: useServiceAccountApi, + }; + + return { + config: referenceConfig, + api: resourceApiMap[resourceKey]()[`${resourceKey}API`], + }; +}; diff --git a/apps/web/src/query/resource-query/shared/contants/resource-config-map.ts b/apps/web/src/query/resource-query/shared/contants/resource-config-map.ts new file mode 100644 index 0000000000..06241c02c4 --- /dev/null +++ b/apps/web/src/query/resource-query/shared/contants/resource-config-map.ts @@ -0,0 +1,57 @@ +export const RESOURCE_CONFIG_MAP = { + project: { + name: 'Project', + resourceKey: 'project', + idKey: 'project_id', + nameKey: 'name', + }, + projectGroup: { + name: 'Project Group', + resourceKey: 'projectGroup', + idKey: 'project_group_id', + nameKey: 'name', + }, + app: { + name: 'App', + resourceKey: 'app', + idKey: 'app_id', + nameKey: 'name', + }, + role: { + name: 'Role', + resourceKey: 'role', + idKey: 'role_id', + nameKey: 'name', + }, + serviceAccount: { + name: 'Service Account', + resourceKey: 'serviceAccount', + idKey: 'service_account_id', + nameKey: 'name', + }, + workspace: { + name: 'Workspace', + resourceKey: 'workspace', + idKey: 'workspace_id', + nameKey: 'name', + }, + workspaceGroup: { + name: 'Workspace Group', + resourceKey: 'workspace_group', + idKey: 'workspace_group_id', + nameKey: 'name', + }, + user: { + name: 'User', + resourceKey: 'user', + idKey: 'user_id', + nameKey: 'name', + }, + // user + metric: { + name: 'Metric', + resourceKey: 'metric', + idKey: 'metric_id', + nameKey: 'name', + }, +} as const; diff --git a/apps/web/src/query/resource-query/shared/types/resource-type.ts b/apps/web/src/query/resource-query/shared/types/resource-type.ts new file mode 100644 index 0000000000..0c995bb4bd --- /dev/null +++ b/apps/web/src/query/resource-query/shared/types/resource-type.ts @@ -0,0 +1,20 @@ +// import type { ListResponse } from '@/api-clients/_common/schema/api-verbs/list'; +import type { RESOURCE_CONFIG_MAP } from '@/query/resource-query/shared/contants/resource-config-map'; + +export type ResourceKeyType = keyof typeof RESOURCE_CONFIG_MAP; +// export interface ReferenceFetchInfo { +// listFetchFn: (params: any) => Promise>; +// statFetchFn?: (params: any) => Promise>; +// only?: string[]; +// searchTargets?: string[]; +// nameFormatter?: (...args: any) => string; +// } +// +// export interface ReferenceConfig { +// name: string; +// resourceKey: ResourceKeyType; +// idKey: string; +// nameKey: string; +// } + +export type ResourceCacheType> = Record; diff --git a/apps/web/src/query/pagination/pagination-query-helper.ts b/apps/web/src/query/service-query/pagination/pagination-query-helper.ts similarity index 100% rename from apps/web/src/query/pagination/pagination-query-helper.ts rename to apps/web/src/query/service-query/pagination/pagination-query-helper.ts diff --git a/apps/web/src/query/pagination/use-scoped-pagination-query.ts b/apps/web/src/query/service-query/pagination/use-scoped-pagination-query.ts similarity index 95% rename from apps/web/src/query/pagination/use-scoped-pagination-query.ts rename to apps/web/src/query/service-query/pagination/use-scoped-pagination-query.ts index 6cf27a19d4..d4cb1ceaed 100644 --- a/apps/web/src/query/pagination/use-scoped-pagination-query.ts +++ b/apps/web/src/query/service-query/pagination/use-scoped-pagination-query.ts @@ -7,9 +7,9 @@ import type { InfiniteData } from '@tanstack/vue-query'; import { useQueryClient, type UseInfiniteQueryOptions } from '@tanstack/vue-query'; import type { GrantScope } from '@/api-clients/identity/token/schema/type'; -import { useScopedInfiniteQuery } from '@/query/composables/use-scoped-infinite-query'; -import { addPageToVerbParams } from '@/query/pagination/pagination-query-helper'; -import type { QueryKeyArray } from '@/query/query-key/_types/query-key-type'; +import type { QueryKeyArray } from '@/query/core/query-key/types/query-key-type'; +import { addPageToVerbParams } from '@/query/service-query/pagination/pagination-query-helper'; +import { useScopedInfiniteQuery } from '@/query/service-query/use-scoped-infinite-query'; /** diff --git a/apps/web/src/query/composables/use-scoped-infinite-query.ts b/apps/web/src/query/service-query/use-scoped-infinite-query.ts similarity index 98% rename from apps/web/src/query/composables/use-scoped-infinite-query.ts rename to apps/web/src/query/service-query/use-scoped-infinite-query.ts index a9d076521a..4bd649c237 100644 --- a/apps/web/src/query/composables/use-scoped-infinite-query.ts +++ b/apps/web/src/query/service-query/use-scoped-infinite-query.ts @@ -42,7 +42,7 @@ import { } from '@tanstack/vue-query'; import type { GrantScope } from '@/api-clients/identity/token/schema/type'; -import type { QueryKeyArray } from '@/query/query-key/_types/query-key-type'; +import type { QueryKeyArray } from '@/query/core/query-key/types/query-key-type'; import { useAppContextStore } from '@/store/app-context/app-context-store'; import { useAuthorizationStore } from '@/store/authorization/authorization-store'; diff --git a/apps/web/src/query/composables/use-scoped-query.ts b/apps/web/src/query/service-query/use-scoped-query.ts similarity index 98% rename from apps/web/src/query/composables/use-scoped-query.ts rename to apps/web/src/query/service-query/use-scoped-query.ts index 2ee342c174..52d976533d 100644 --- a/apps/web/src/query/composables/use-scoped-query.ts +++ b/apps/web/src/query/service-query/use-scoped-query.ts @@ -39,7 +39,7 @@ import { } from '@tanstack/vue-query'; import type { GrantScope } from '@/api-clients/identity/token/schema/type'; -import type { QueryKeyArray } from '@/query/query-key/_types/query-key-type'; +import type { QueryKeyArray } from '@/query/core/query-key/types/query-key-type'; import { useAppContextStore } from '@/store/app-context/app-context-store'; import { useAuthorizationStore } from '@/store/authorization/authorization-store'; diff --git a/apps/web/src/query/reference/core/common/use-watched-query-cache.ts b/apps/web/src/query/shared/use-watched-query-cache.ts similarity index 90% rename from apps/web/src/query/reference/core/common/use-watched-query-cache.ts rename to apps/web/src/query/shared/use-watched-query-cache.ts index bb269b535b..871bee9d53 100644 --- a/apps/web/src/query/reference/core/common/use-watched-query-cache.ts +++ b/apps/web/src/query/shared/use-watched-query-cache.ts @@ -4,8 +4,7 @@ import { onUnmounted, ref } from 'vue'; import { hashKey } from '@tanstack/vue-query'; import { referenceQueryClient as queryClient } from '@/query/clients'; -import type { QueryKeyArray } from '@/query/query-key/_types/query-key-type'; - +import type { QueryKeyArray } from '@/query/core/query-key/types/query-key-type'; export const useWatchedQueryCache = (queryKey: QueryKeyArray) => { const data = ref(queryClient.getQueryData(queryKey)); diff --git a/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-change-folder-mutation.ts b/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-change-folder-mutation.ts index b74f1dd12e..a77b42afed 100644 --- a/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-change-folder-mutation.ts +++ b/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-change-folder-mutation.ts @@ -5,7 +5,7 @@ import { import type { DashboardChangeFolderParams, DashboardModel } from '@/api-clients/dashboard/_types/dashboard-type'; import { usePrivateDashboardApi } from '@/api-clients/dashboard/private-dashboard/composables/use-private-dashboard-api'; import { usePublicDashboardApi } from '@/api-clients/dashboard/public-dashboard/composables/use-public-dashboard-api'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; interface UseDashboardChangeFolderMutationOptions { onSuccess?: (data: DashboardModel, variables: DashboardChangeFolderParams) => void|Promise; diff --git a/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-delete-mutation.ts b/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-delete-mutation.ts index 0d511adfe7..560262030e 100644 --- a/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-delete-mutation.ts +++ b/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-delete-mutation.ts @@ -6,7 +6,7 @@ import { import type { DashboardDeleteParams } from '@/api-clients/dashboard/_types/dashboard-type'; import { usePrivateDashboardApi } from '@/api-clients/dashboard/private-dashboard/composables/use-private-dashboard-api'; import { usePublicDashboardApi } from '@/api-clients/dashboard/public-dashboard/composables/use-public-dashboard-api'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; interface UseDashboardDeleteMutationOptions { onSuccess?: (data: unknown, variables: DashboardDeleteParams) => void|Promise; diff --git a/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-folder-delete-mutation.ts b/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-folder-delete-mutation.ts index d9e636d7dc..4c1c3a93af 100644 --- a/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-folder-delete-mutation.ts +++ b/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-folder-delete-mutation.ts @@ -5,7 +5,7 @@ import { import type { FolderDeleteParams } from '@/api-clients/dashboard/_types/folder-type'; import { usePrivateFolderApi } from '@/api-clients/dashboard/private-folder/composables/use-private-folder-api'; import { usePublicFolderApi } from '@/api-clients/dashboard/public-folder/composables/use-public-folder-api'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; interface UseDashboardFolderDeleteMutationOptions { onSuccess?: (data: unknown, variables: FolderDeleteParams) => void|Promise; diff --git a/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-folder-share-mutation.ts b/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-folder-share-mutation.ts index 6545e44f3a..6867475d3d 100644 --- a/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-folder-share-mutation.ts +++ b/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-folder-share-mutation.ts @@ -9,7 +9,7 @@ import { usePublicFolderApi } from '@/api-clients/dashboard/public-folder/compos import type { PublicFolderShareParameters } from '@/api-clients/dashboard/public-folder/schema/api-verbs/share'; import type { PublicFolderUnshareParameters } from '@/api-clients/dashboard/public-folder/schema/api-verbs/unshare'; import type { PublicFolderModel } from '@/api-clients/dashboard/public-folder/schema/model'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; interface UseDashboardFolderShareMutationOptions { isShared: ComputedRef; diff --git a/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-share-mutation.ts b/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-share-mutation.ts index d388e74bc6..b79431527c 100644 --- a/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-share-mutation.ts +++ b/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-share-mutation.ts @@ -8,7 +8,7 @@ import type { DashboardModel } from '@/api-clients/dashboard/_types/dashboard-ty import { usePublicDashboardApi } from '@/api-clients/dashboard/public-dashboard/composables/use-public-dashboard-api'; import type { PublicDashboardShareParameters } from '@/api-clients/dashboard/public-dashboard/schema/api-verbs/share'; import type { PublicDashboardUnshareParameters } from '@/api-clients/dashboard/public-dashboard/schema/api-verbs/unshare'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; interface UseDashboardShareMutationOptions { isShared: ComputedRef; diff --git a/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-update-mutation.ts b/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-update-mutation.ts index ae289013d0..44ebea5e73 100644 --- a/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-update-mutation.ts +++ b/apps/web/src/services/_shared/dashboard/core/composables/mutations/use-dashboard-update-mutation.ts @@ -5,7 +5,7 @@ import { import type { DashboardModel, DashboardUpdateParams } from '@/api-clients/dashboard/_types/dashboard-type'; import { usePrivateDashboardApi } from '@/api-clients/dashboard/private-dashboard/composables/use-private-dashboard-api'; import { usePublicDashboardApi } from '@/api-clients/dashboard/public-dashboard/composables/use-public-dashboard-api'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; interface UseDashboardUpdateMutationOptions { onSuccess?: (data: DashboardModel, variables: DashboardUpdateParams) => void|Promise; diff --git a/apps/web/src/services/_shared/dashboard/dashboard-create/composables/use-dashboard-template-query.ts b/apps/web/src/services/_shared/dashboard/dashboard-create/composables/use-dashboard-template-query.ts index 044d82bff0..e2ba390d31 100644 --- a/apps/web/src/services/_shared/dashboard/dashboard-create/composables/use-dashboard-template-query.ts +++ b/apps/web/src/services/_shared/dashboard/dashboard-create/composables/use-dashboard-template-query.ts @@ -2,8 +2,8 @@ import { computed } from 'vue'; import { useDashboardTemplateApi } from '@/api-clients/repository/dashboard-template/composables/use-dashboard-template-api'; import type { DashboardTemplateListParameters } from '@/api-clients/repository/dashboard-template/schema/api-verbs/list'; -import { useScopedQuery } from '@/query/composables/use-scoped-query'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; +import { useScopedQuery } from '@/query/service-query/use-scoped-query'; export const useDashboardTemplateQuery = () => { const { dashboardTemplateAPI } = useDashboardTemplateApi(); diff --git a/apps/web/src/services/_shared/dashboard/dashboard-create/contextual-components/DashboardCreateStep2BundleCase.vue b/apps/web/src/services/_shared/dashboard/dashboard-create/contextual-components/DashboardCreateStep2BundleCase.vue index ece80b7ef4..03b22a97af 100644 --- a/apps/web/src/services/_shared/dashboard/dashboard-create/contextual-components/DashboardCreateStep2BundleCase.vue +++ b/apps/web/src/services/_shared/dashboard/dashboard-create/contextual-components/DashboardCreateStep2BundleCase.vue @@ -18,7 +18,7 @@ import { usePrivateDashboardApi } from '@/api-clients/dashboard/private-dashboar import { usePublicDashboardApi } from '@/api-clients/dashboard/public-dashboard/composables/use-public-dashboard-api'; import type { PublicDashboardCreateParameters } from '@/api-clients/dashboard/public-dashboard/schema/api-verbs/create'; import { ROLE_TYPE } from '@/api-clients/identity/role/constant'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; import { i18n } from '@/translations'; import { useAuthorizationStore } from '@/store/authorization/authorization-store'; diff --git a/apps/web/src/services/_shared/dashboard/dashboard-create/contextual-components/DashboardCreateStep2SingleCase.vue b/apps/web/src/services/_shared/dashboard/dashboard-create/contextual-components/DashboardCreateStep2SingleCase.vue index 4fea5b92b7..6647a99746 100644 --- a/apps/web/src/services/_shared/dashboard/dashboard-create/contextual-components/DashboardCreateStep2SingleCase.vue +++ b/apps/web/src/services/_shared/dashboard/dashboard-create/contextual-components/DashboardCreateStep2SingleCase.vue @@ -22,7 +22,7 @@ import type { } from '@/api-clients/dashboard/private-dashboard/schema/api-verbs/create'; import { usePublicDashboardApi } from '@/api-clients/dashboard/public-dashboard/composables/use-public-dashboard-api'; import type { PublicDashboardCreateParameters } from '@/api-clients/dashboard/public-dashboard/schema/api-verbs/create'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; import { i18n } from '@/translations'; import { useUserStore } from '@/store/user/user-store'; diff --git a/apps/web/src/services/_shared/dashboard/dashboard-detail/composables/use-dashboard-get-query.ts b/apps/web/src/services/_shared/dashboard/dashboard-detail/composables/use-dashboard-get-query.ts index 33596ac86e..7299a7e632 100644 --- a/apps/web/src/services/_shared/dashboard/dashboard-detail/composables/use-dashboard-get-query.ts +++ b/apps/web/src/services/_shared/dashboard/dashboard-detail/composables/use-dashboard-get-query.ts @@ -12,9 +12,9 @@ import { usePublicDashboardApi } from '@/api-clients/dashboard/public-dashboard/ import type { PublicDashboardGetParameters } from '@/api-clients/dashboard/public-dashboard/schema/api-verbs/get'; import type { PublicDashboardUpdateParameters } from '@/api-clients/dashboard/public-dashboard/schema/api-verbs/update'; import type { PublicDashboardModel } from '@/api-clients/dashboard/public-dashboard/schema/model'; -import { useScopedQuery } from '@/query/composables/use-scoped-query'; -import type { QueryKeyArray } from '@/query/query-key/_types/query-key-type'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import type { QueryKeyArray } from '@/query/core/query-key/types/query-key-type'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; +import { useScopedQuery } from '@/query/service-query/use-scoped-query'; const STALE_TIME = 1000 * 60 * 5; diff --git a/apps/web/src/services/_shared/dashboard/dashboard-detail/composables/use-dashboard-widget-list-query.ts b/apps/web/src/services/_shared/dashboard/dashboard-detail/composables/use-dashboard-widget-list-query.ts index 7b16f0867e..c40b4063ef 100644 --- a/apps/web/src/services/_shared/dashboard/dashboard-detail/composables/use-dashboard-widget-list-query.ts +++ b/apps/web/src/services/_shared/dashboard/dashboard-detail/composables/use-dashboard-widget-list-query.ts @@ -12,8 +12,8 @@ import { usePublicWidgetApi } from '@/api-clients/dashboard/public-widget/compos import type { PublicWidgetListParameters } from '@/api-clients/dashboard/public-widget/schema/api-verbs/list'; import type { PublicWidgetUpdateParameters } from '@/api-clients/dashboard/public-widget/schema/api-verbs/update'; import type { PublicWidgetModel } from '@/api-clients/dashboard/public-widget/schema/model'; -import { useScopedQuery } from '@/query/composables/use-scoped-query'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; +import { useScopedQuery } from '@/query/service-query/use-scoped-query'; const DEFAULT_LIST_DATA = { results: [] }; const STALE_TIME = 1000 * 60 * 5; diff --git a/apps/web/src/services/alert-manager/v2/components/AlertDetailTabsTimeline.vue b/apps/web/src/services/alert-manager/v2/components/AlertDetailTabsTimeline.vue index cd3397d526..f94c4ab14f 100644 --- a/apps/web/src/services/alert-manager/v2/components/AlertDetailTabsTimeline.vue +++ b/apps/web/src/services/alert-manager/v2/components/AlertDetailTabsTimeline.vue @@ -15,8 +15,8 @@ import { useAlertApi } from '@/api-clients/alert-manager/alert/composables/use-a import { ALERT_HISTORY_ACTION } from '@/api-clients/alert-manager/alert/schema/constants'; import type { AlertHistoryModel } from '@/api-clients/alert-manager/alert/schema/model'; import type { AlertHistoryActionType } from '@/api-clients/alert-manager/alert/schema/type'; -import { useScopedQuery } from '@/query/composables/use-scoped-query'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; +import { useScopedQuery } from '@/query/service-query/use-scoped-query'; import { i18n } from '@/translations'; import { useAllReferenceStore } from '@/store/reference/all-reference-store'; diff --git a/apps/web/src/services/alert-manager/v2/components/ServiceDetailTabsNotifications.vue b/apps/web/src/services/alert-manager/v2/components/ServiceDetailTabsNotifications.vue index a5fd26423a..f50cc1545b 100644 --- a/apps/web/src/services/alert-manager/v2/components/ServiceDetailTabsNotifications.vue +++ b/apps/web/src/services/alert-manager/v2/components/ServiceDetailTabsNotifications.vue @@ -27,8 +27,8 @@ import { SERVICE_CHANNEL_TYPE, } from '@/api-clients/alert-manager/service-channel/schema/constants'; import type { ServiceChannelModel } from '@/api-clients/alert-manager/service-channel/schema/model'; -import { useScopedPaginationQuery } from '@/query/pagination/use-scoped-pagination-query'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; +import { useScopedPaginationQuery } from '@/query/service-query/pagination/use-scoped-pagination-query'; import { i18n } from '@/translations'; import { FILE_NAME_PREFIX } from '@/lib/excel-export/constant'; diff --git a/apps/web/src/services/alert-manager/v2/components/ServiceDetailTabsNotificationsDetail.vue b/apps/web/src/services/alert-manager/v2/components/ServiceDetailTabsNotificationsDetail.vue index c7f5853795..f5d4cc1ac5 100644 --- a/apps/web/src/services/alert-manager/v2/components/ServiceDetailTabsNotificationsDetail.vue +++ b/apps/web/src/services/alert-manager/v2/components/ServiceDetailTabsNotificationsDetail.vue @@ -15,8 +15,8 @@ import { } from '@/api-clients/alert-manager/service-channel/schema/constants'; import type { ServiceChannelModel } from '@/api-clients/alert-manager/service-channel/schema/model'; import type { ServiceChannelScheduleInfoType, ServiceChannelScheduleDayType } from '@/api-clients/alert-manager/service-channel/schema/type'; -import { useScopedQuery } from '@/query/composables/use-scoped-query'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; +import { useScopedQuery } from '@/query/service-query/use-scoped-query'; import { i18n } from '@/translations'; import type { UserGroupReferenceMap } from '@/store/reference/user-group-reference-store'; diff --git a/apps/web/src/services/alert-manager/v2/components/ServiceDetailTabsOverviewEscalationPolicy.vue b/apps/web/src/services/alert-manager/v2/components/ServiceDetailTabsOverviewEscalationPolicy.vue index 914c27d4ab..da04b957fb 100644 --- a/apps/web/src/services/alert-manager/v2/components/ServiceDetailTabsOverviewEscalationPolicy.vue +++ b/apps/web/src/services/alert-manager/v2/components/ServiceDetailTabsOverviewEscalationPolicy.vue @@ -12,8 +12,8 @@ import { import { useEscalationPolicyApi } from '@/api-clients/alert-manager/escalation-policy/composables/use-escalation-policy-api'; import type { EscalationPolicyModel } from '@/api-clients/alert-manager/escalation-policy/schema/model'; import type { EscalationPolicyRulesType } from '@/api-clients/alert-manager/escalation-policy/schema/type'; -import { useScopedQuery } from '@/query/composables/use-scoped-query'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; +import { useScopedQuery } from '@/query/service-query/use-scoped-query'; import { useServiceGetQuery } from '@/services/alert-manager/v2/composables/use-service-get-query'; import { SERVICE_DETAIL_TABS } from '@/services/alert-manager/v2/constants/common-constant'; diff --git a/apps/web/src/services/alert-manager/v2/components/ServiceDetailTabsWebhookDetail.vue b/apps/web/src/services/alert-manager/v2/components/ServiceDetailTabsWebhookDetail.vue index e8afe6011b..a443731aeb 100644 --- a/apps/web/src/services/alert-manager/v2/components/ServiceDetailTabsWebhookDetail.vue +++ b/apps/web/src/services/alert-manager/v2/components/ServiceDetailTabsWebhookDetail.vue @@ -21,9 +21,9 @@ import { useWebhookApi } from '@/api-clients/alert-manager/webhook/composables/u import type { WebhookUpdateMessageFormatParameters } from '@/api-clients/alert-manager/webhook/schema/api-verbs/update-message-format'; import type { WebhookModel, WebhookListErrorsModel } from '@/api-clients/alert-manager/webhook/schema/model'; import type { WebhookMessageFormatType } from '@/api-clients/alert-manager/webhook/schema/type'; -import { useScopedQuery } from '@/query/composables/use-scoped-query'; -import { useScopedPaginationQuery } from '@/query/pagination/use-scoped-pagination-query'; -import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; +import { useServiceQueryKey } from '@/query/core/query-key/use-service-query-key'; +import { useScopedPaginationQuery } from '@/query/service-query/pagination/use-scoped-pagination-query'; +import { useScopedQuery } from '@/query/service-query/use-scoped-query'; import type { PluginGetParameters } from '@/schema/repository/plugin/api-verbs/get'; import type { PluginModel } from '@/schema/repository/plugin/model'; import type { RepositoryListParameters } from '@/schema/repository/repository/api-verbs/list'; @@ -184,6 +184,9 @@ const handleChange = async (options: any = {}) => { if (options.sortDesc !== undefined) messageState.sortDesc = options.sortDesc; if (options.queryTags !== undefined) queryTagHelper.setQueryTags(options.queryTags); }; +const handleRefresh = () => { + queryClient.invalidateQueries({ queryKey: webhookErrorListQueryKey.value }); +}; const setWebhookDetail = () => { if (!storeState.selectedWebhookId) return; @@ -349,7 +352,7 @@ watch(() => storeState.selectedWebhookId, async () => { :items="state.refinedErrorList" class="w-full border-none" @change="handleChange" - @refresh="queryClient.invalidateQueries({ queryKey: webhookErrorListQueryKey.value })" + @refresh="handleRefresh" > - + + +