diff --git a/apps/web/src/api-clients/_common/composables/use-scoped-query.ts b/apps/web/src/api-clients/_common/composables/use-scoped-query.ts new file mode 100644 index 0000000000..c2d8d12eef --- /dev/null +++ b/apps/web/src/api-clients/_common/composables/use-scoped-query.ts @@ -0,0 +1,82 @@ +/** + * useScopedQuery - A custom wrapper around `useQuery` to enforce scope-based API fetching. + * + * ## Why this hook exists? + * This hook was created to integrate **scope-based API access control** with Vue Query. + * It ensures that queries are only executed when the user's granted scope matches the required scope. + * Additionally, it automatically handles loading states and prevents unnecessary queries. + * + * ## Functionality + * - Extends `useQuery` with **grant scope validation**. + * - Runs queries only when **the user’s scope is valid** and the app is **ready**. + * - Uses Vue’s **reactivity** to dynamically compute the `enabled` state. + * - Supports both **static and reactive `enabled` values**. + * + * ## Parameters: + * - `options`: Standard **Vue Query options** (`UseQueryOptions`). + * - `requiredScopes`: A list of **required grant scopes** to determine if the query should execute. + * + * ## Supported Query Options: + * The following options are commonly used within our team: + * - `queryKey`: **Unique key** to identify the query in the cache. + * - `queryFn`: **Function** to fetch data from the API. + * - `select`: **Transform function** to modify response data. + * - `initialData`: **Default value** for the query when no data is available. + * - `staleTime`: **Cache duration** before the query becomes stale. + * - `enabled`: **Boolean or reactive ref** to control when the query should execute. + * + * ## Example Usage: + * + * const myQuery = useScopedQuery({ + * queryKey: ['dashboard', dashboardId], + * queryFn: () => fetchDashboardData(dashboardId), + * select: (data) => data.results, + * initialData: { results: [] }, + * staleTime: 1000 * 60 * 5, // 5 minutes + * enabled: computed(() => isUserAuthorized.value), + * }, ['DOMAIN', 'WORKSPACE']); + * + */ + + +import type { MaybeRef } from '@vueuse/core'; +import { toValue } from '@vueuse/core'; +import { computed, reactive } from 'vue'; + +import type { + UseQueryOptions, +} from '@tanstack/vue-query'; +import { useQuery } from '@tanstack/vue-query'; + +import type { GrantScope } from '@/schema/identity/token/type'; + +import { useAppContextStore } from '@/store/app-context/app-context-store'; +import { useUserStore } from '@/store/user/user-store'; + +export const useScopedQuery = ( + options: UseQueryOptions, + requiredScopes: GrantScope[] = [], +) => { + const appContextStore = useAppContextStore(); + const userStore = useUserStore(); + + const _state = reactive({ + currentGrantScope: computed(() => userStore.state.currentGrantInfo?.scope || 'USER'), + isLoading: computed(() => appContextStore.getters.globalGrantLoading), + isValidScope: computed(() => requiredScopes.includes(_state.currentGrantScope)), + }); + + const queryEnabled = computed(() => { + const _inheritedEnabled = options?.enabled as MaybeRef | undefined; + + if (_inheritedEnabled !== undefined && !toValue(_inheritedEnabled)) { + return false; + } + return _state.isValidScope && !_state.isLoading; + }); + + return useQuery({ + ...options, + enabled: queryEnabled, + }); +}; diff --git a/apps/web/src/common/modules/widgets/_composables/use-widget-form-query.ts b/apps/web/src/common/modules/widgets/_composables/use-widget-form-query.ts index e5abff7f54..80070fb3fb 100644 --- a/apps/web/src/common/modules/widgets/_composables/use-widget-form-query.ts +++ b/apps/web/src/common/modules/widgets/_composables/use-widget-form-query.ts @@ -2,8 +2,9 @@ import type { ComputedRef } from 'vue'; import { computed } from 'vue'; import type { QueryClient, QueryKey } from '@tanstack/vue-query'; -import { useQuery, useQueryClient } from '@tanstack/vue-query'; +import { useQueryClient } from '@tanstack/vue-query'; +import { useScopedQuery } from '@/api-clients/_common/composables/use-scoped-query'; import type { WidgetModel, WidgetUpdateParams } from '@/api-clients/dashboard/_types/widget-type'; import { usePrivateDataTableApi } from '@/api-clients/dashboard/private-data-table/composables/use-private-data-table-api'; import { usePrivateWidgetApi } from '@/api-clients/dashboard/private-widget/composables/use-private-widget-api'; @@ -101,23 +102,23 @@ export const useWidgetFormQuery = ({ ]); /* Querys */ - const publicWidgetQuery = useQuery({ + const publicWidgetQuery = useScopedQuery({ queryKey: _publicWidgetQueryKey, queryFn: () => publicWidgetAPI.get({ widget_id: widgetId?.value as string, }), enabled: computed(() => !!widgetId?.value && !isPrivate.value && !preventLoad), staleTime: STALE_TIME, - }); - const privateWidgetQuery = useQuery({ + }, ['DOMAIN', 'WORKSPACE']); + const privateWidgetQuery = useScopedQuery({ queryKey: _privateWidgetQueryKey, queryFn: () => privateWidgetAPI.get({ widget_id: widgetId?.value as string, }), enabled: computed(() => !!widgetId?.value && isPrivate.value && !preventLoad), staleTime: STALE_TIME, - }); - const publicDataTableListQuery = useQuery({ + }, ['WORKSPACE']); + const publicDataTableListQuery = useScopedQuery({ queryKey: _publicDataTableListQueryKey, queryFn: () => publicDataTableAPI.list({ widget_id: widgetId?.value as string, @@ -127,8 +128,8 @@ export const useWidgetFormQuery = ({ initialData: DEFAULT_LIST_DATA, initialDataUpdatedAt: 0, staleTime: STALE_TIME, - }); - const privateDataTableListQuery = useQuery({ + }, ['DOMAIN', 'WORKSPACE']); + const privateDataTableListQuery = useScopedQuery({ queryKey: _privateDataTableListQueryKey, queryFn: () => privateDataTableAPI.list({ widget_id: widgetId?.value as string, @@ -138,7 +139,7 @@ export const useWidgetFormQuery = ({ initialData: DEFAULT_LIST_DATA, initialDataUpdatedAt: 0, staleTime: STALE_TIME, - }); + }, ['WORKSPACE']); /* Fetchers */ const updateDataTableFn = (params: DataTableUpdateParameters): Promise => { @@ -164,7 +165,7 @@ export const useWidgetFormQuery = ({ return { widget: computed(() => (isPrivate.value ? privateWidgetQuery.data.value : publicWidgetQuery.data.value)), - dataTableList: computed(() => (isPrivate.value ? privateDataTableListQuery.data.value : publicDataTableListQuery.data.value)), + dataTableList: computed(() => (isPrivate.value ? privateDataTableListQuery.data.value : publicDataTableListQuery.data.value) ?? []), dataTableListLoading, widgetLoading, api: { diff --git a/apps/web/src/common/modules/widgets/_widgets/clustered-column-chart/ClusteredColumnChart.vue b/apps/web/src/common/modules/widgets/_widgets/clustered-column-chart/ClusteredColumnChart.vue index 6c960dfba4..ff90f85cbe 100644 --- a/apps/web/src/common/modules/widgets/_widgets/clustered-column-chart/ClusteredColumnChart.vue +++ b/apps/web/src/common/modules/widgets/_widgets/clustered-column-chart/ClusteredColumnChart.vue @@ -167,6 +167,7 @@ const state = reactive({ return { start: _start, end: _end }; }), dateFormat: computed(() => DATE_FORMAT?.[widgetOptionsState.dateFormatInfo?.format]?.[widgetOptionsState.granularityInfo?.granularity]), + dataTableLoading: false, }); const widgetOptionsState = reactive({ @@ -222,7 +223,7 @@ const queryResult = useQuery({ staleTime: WIDGET_LOAD_STALE_TIME, }); -const widgetLoading = computed(() => queryResult.isFetching.value); +const widgetLoading = computed(() => queryResult.isFetching.value || state.dataTableLoading); const errorMessage = computed(() => { if (!state.dataTable) return i18n.t('COMMON.WIDGETS.NO_DATA_TABLE_ERROR_MESSAGE'); return queryResult.error?.value?.message; @@ -317,6 +318,7 @@ useResizeObserver(chartContext, throttle(() => { watch(() => props.dataTableId, async (newDataTableId) => { if (!newDataTableId) return; + state.dataTableLoading = true; const fetcher = state.isPrivateWidget ? api.privateDataTableAPI.get : api.publicDataTableAPI.get; @@ -324,6 +326,8 @@ watch(() => props.dataTableId, async (newDataTableId) => { state.dataTable = await fetcher({ data_table_id: newDataTableId }); } catch (e) { ErrorHandler.handleError(e); + } finally { + state.dataTableLoading = false; } }, { immediate: true }); defineExpose({ diff --git a/apps/web/src/common/modules/widgets/_widgets/color-coded-heatmap/ColorCodedHeatmap.vue b/apps/web/src/common/modules/widgets/_widgets/color-coded-heatmap/ColorCodedHeatmap.vue index 47a3ddf710..91a9deac3f 100644 --- a/apps/web/src/common/modules/widgets/_widgets/color-coded-heatmap/ColorCodedHeatmap.vue +++ b/apps/web/src/common/modules/widgets/_widgets/color-coded-heatmap/ColorCodedHeatmap.vue @@ -65,6 +65,7 @@ const state = reactive({ return widgetContentWidth / 8 < BOX_MIN_WIDTH ? BOX_MIN_WIDTH : widgetContentWidth / 8; }), legendList: [] as WidgetLegend[], + dataTableLoading: false, }); const widgetOptionsState = reactive({ @@ -118,7 +119,7 @@ const queryResult = useQuery({ staleTime: WIDGET_LOAD_STALE_TIME, }); -const widgetLoading = computed(() => queryResult.isFetching.value); +const widgetLoading = computed(() => queryResult.isFetching.value || state.dataTableLoading); const errorMessage = computed(() => { if (!state.dataTable) return i18n.t('COMMON.WIDGETS.NO_DATA_TABLE_ERROR_MESSAGE'); return queryResult.error?.value?.message; @@ -175,6 +176,7 @@ watch(() => widgetOptionsState.formatRulesInfo, async () => { watch(() => props.dataTableId, async (newDataTableId) => { if (!newDataTableId) return; + state.dataTableLoading = true; const fetcher = state.isPrivateWidget ? api.privateDataTableAPI.get : api.publicDataTableAPI.get; @@ -182,6 +184,8 @@ watch(() => props.dataTableId, async (newDataTableId) => { state.dataTable = await fetcher({ data_table_id: newDataTableId }); } catch (e) { ErrorHandler.handleError(e); + } finally { + state.dataTableLoading = false; } }, { immediate: true }); defineExpose({ diff --git a/apps/web/src/common/modules/widgets/_widgets/color-coded-table-heatmap/ColorCodedTableHeatmap.vue b/apps/web/src/common/modules/widgets/_widgets/color-coded-table-heatmap/ColorCodedTableHeatmap.vue index 553ff4edaf..95de3a38a7 100644 --- a/apps/web/src/common/modules/widgets/_widgets/color-coded-table-heatmap/ColorCodedTableHeatmap.vue +++ b/apps/web/src/common/modules/widgets/_widgets/color-coded-table-heatmap/ColorCodedTableHeatmap.vue @@ -103,6 +103,7 @@ const state = reactive({ } return { start: _start, end: _end }; }), + dataTableLoading: false, }); const widgetOptionsState = reactive({ @@ -155,7 +156,7 @@ const queryResult = useQuery({ staleTime: WIDGET_LOAD_STALE_TIME, }); -const widgetLoading = computed(() => queryResult.isFetching.value); +const widgetLoading = computed(() => queryResult.isFetching.value || state.dataTableLoading); const errorMessage = computed(() => { if (!state.dataTable) return i18n.t('COMMON.WIDGETS.NO_DATA_TABLE_ERROR_MESSAGE'); return queryResult.error?.value?.message; @@ -241,6 +242,7 @@ watch(() => widgetOptionsState, () => { watch(() => props.dataTableId, async (newDataTableId) => { if (!newDataTableId) return; + state.dataTableLoading = true; const fetcher = state.isPrivateWidget ? api.privateDataTableAPI.get : api.publicDataTableAPI.get; @@ -248,6 +250,8 @@ watch(() => props.dataTableId, async (newDataTableId) => { state.dataTable = await fetcher({ data_table_id: newDataTableId }); } catch (e) { ErrorHandler.handleError(e); + } finally { + state.dataTableLoading = false; } }, { immediate: true }); defineExpose({ diff --git a/apps/web/src/common/modules/widgets/_widgets/gauge/Gauge.vue b/apps/web/src/common/modules/widgets/_widgets/gauge/Gauge.vue index 198c76b990..f34d6ee2ba 100644 --- a/apps/web/src/common/modules/widgets/_widgets/gauge/Gauge.vue +++ b/apps/web/src/common/modules/widgets/_widgets/gauge/Gauge.vue @@ -129,6 +129,7 @@ const state = reactive({ }); return _color; }), + dataTableLoading: false, }); const widgetOptionsState = reactive({ @@ -179,7 +180,7 @@ const queryResult = useQuery({ staleTime: WIDGET_LOAD_STALE_TIME, }); -const widgetLoading = computed(() => queryResult.isFetching.value); +const widgetLoading = computed(() => queryResult.isFetching.value || state.dataTableLoading); const errorMessage = computed(() => { if (!state.dataTable) return i18n.t('COMMON.WIDGETS.NO_DATA_TABLE_ERROR_MESSAGE'); return queryResult.error?.value?.message; @@ -209,6 +210,7 @@ watch([() => state.data, () => props.widgetOptions], ([newData]) => { }, { immediate: true }); watch(() => props.dataTableId, async (newDataTableId) => { if (!newDataTableId) return; + state.dataTableLoading = true; const fetcher = state.isPrivateWidget ? api.privateDataTableAPI.get : api.publicDataTableAPI.get; @@ -216,6 +218,8 @@ watch(() => props.dataTableId, async (newDataTableId) => { state.dataTable = await fetcher({ data_table_id: newDataTableId }); } catch (e) { ErrorHandler.handleError(e); + } finally { + state.dataTableLoading = false; } }, { immediate: true }); defineExpose({ diff --git a/apps/web/src/common/modules/widgets/_widgets/geo-map/GeoMap.vue b/apps/web/src/common/modules/widgets/_widgets/geo-map/GeoMap.vue index 0a6f36e2f3..06d5363f78 100644 --- a/apps/web/src/common/modules/widgets/_widgets/geo-map/GeoMap.vue +++ b/apps/web/src/common/modules/widgets/_widgets/geo-map/GeoMap.vue @@ -108,6 +108,7 @@ const state = reactive({ }, ], })), + dataTableLoading: false, }); const widgetOptionsState = reactive({ @@ -156,7 +157,7 @@ const queryResult = useQuery({ staleTime: WIDGET_LOAD_STALE_TIME, }); -const widgetLoading = computed(() => queryResult.isFetching.value); +const widgetLoading = computed(() => queryResult.isFetching.value || state.dataTableLoading); const errorMessage = computed(() => { if (!state.dataTable) return i18n.t('COMMON.WIDGETS.NO_DATA_TABLE_ERROR_MESSAGE'); return queryResult.error?.value?.message; @@ -216,6 +217,7 @@ useResizeObserver(chartContext, throttle(() => { watch(() => props.dataTableId, async (newDataTableId) => { if (!newDataTableId) return; + state.dataTableLoading = true; const fetcher = state.isPrivateWidget ? api.privateDataTableAPI.get : api.publicDataTableAPI.get; @@ -223,6 +225,8 @@ watch(() => props.dataTableId, async (newDataTableId) => { state.dataTable = await fetcher({ data_table_id: newDataTableId }); } catch (e) { ErrorHandler.handleError(e); + } finally { + state.dataTableLoading = false; } }, { immediate: true }); defineExpose({ diff --git a/apps/web/src/common/modules/widgets/_widgets/heatmap/Heatmap.vue b/apps/web/src/common/modules/widgets/_widgets/heatmap/Heatmap.vue index 5e574f1650..93070770f4 100644 --- a/apps/web/src/common/modules/widgets/_widgets/heatmap/Heatmap.vue +++ b/apps/web/src/common/modules/widgets/_widgets/heatmap/Heatmap.vue @@ -169,6 +169,7 @@ const state = reactive({ } return { start: _start, end: _end }; }), + dataTableLoading: false, }); const widgetOptionsState = reactive({ @@ -220,7 +221,7 @@ const queryResult = useQuery({ staleTime: WIDGET_LOAD_STALE_TIME, }); -const widgetLoading = computed(() => queryResult.isFetching.value); +const widgetLoading = computed(() => queryResult.isFetching.value || state.dataTableLoading); const errorMessage = computed(() => { if (!state.dataTable) return i18n.t('COMMON.WIDGETS.NO_DATA_TABLE_ERROR_MESSAGE'); return queryResult.error?.value?.message; @@ -268,6 +269,7 @@ useResizeObserver(chartContext, throttle(() => { watch(() => props.dataTableId, async (newDataTableId) => { if (!newDataTableId) return; + state.dataTableLoading = true; const fetcher = state.isPrivateWidget ? api.privateDataTableAPI.get : api.publicDataTableAPI.get; @@ -275,6 +277,8 @@ watch(() => props.dataTableId, async (newDataTableId) => { state.dataTable = await fetcher({ data_table_id: newDataTableId }); } catch (e) { ErrorHandler.handleError(e); + } finally { + state.dataTableLoading = false; } }, { immediate: true }); defineExpose({ diff --git a/apps/web/src/common/modules/widgets/_widgets/line-chart/LineChart.vue b/apps/web/src/common/modules/widgets/_widgets/line-chart/LineChart.vue index f573169817..6b9487773e 100644 --- a/apps/web/src/common/modules/widgets/_widgets/line-chart/LineChart.vue +++ b/apps/web/src/common/modules/widgets/_widgets/line-chart/LineChart.vue @@ -169,6 +169,7 @@ const state = reactive({ return { start: _start, end: _end }; }), dateFormat: computed(() => DATE_FORMAT?.[widgetOptionsState.dateFormatInfo?.format]?.[widgetOptionsState.granularityInfo?.granularity]), + dataTableLoading: false, }); const widgetOptionsState = reactive({ @@ -227,7 +228,7 @@ const queryResult = useQuery({ staleTime: WIDGET_LOAD_STALE_TIME, }); -const widgetLoading = computed(() => queryResult.isFetching.value); +const widgetLoading = computed(() => queryResult.isFetching.value || state.dataTableLoading); const errorMessage = computed(() => { if (!state.dataTable) return i18n.t('COMMON.WIDGETS.NO_DATA_TABLE_ERROR_MESSAGE'); return queryResult.error?.value?.message; @@ -298,6 +299,7 @@ useResizeObserver(chartContext, throttle(() => { watch(() => props.dataTableId, async (newDataTableId) => { if (!newDataTableId) return; + state.dataTableLoading = true; const fetcher = state.isPrivateWidget ? api.privateDataTableAPI.get : api.publicDataTableAPI.get; @@ -305,6 +307,8 @@ watch(() => props.dataTableId, async (newDataTableId) => { state.dataTable = await fetcher({ data_table_id: newDataTableId }); } catch (e) { ErrorHandler.handleError(e); + } finally { + state.dataTableLoading = false; } }, { immediate: true }); defineExpose({ diff --git a/apps/web/src/common/modules/widgets/_widgets/number-card/NumberCard.vue b/apps/web/src/common/modules/widgets/_widgets/number-card/NumberCard.vue index d4d4545b3d..af8bc4929b 100644 --- a/apps/web/src/common/modules/widgets/_widgets/number-card/NumberCard.vue +++ b/apps/web/src/common/modules/widgets/_widgets/number-card/NumberCard.vue @@ -128,6 +128,7 @@ const state = reactive({ const _previousDateRange = getPreviousDateRange(widgetOptionsState.granularityInfo?.granularity, dateRange.value); return _previousDateRange; }), + dataTableLoading: false, }); const widgetOptionsState = reactive({ @@ -225,8 +226,8 @@ const queryResults = useQueries({ ], }); -const widgetLoading = computed(() => queryResults.value?.[0].isFetching); -const previousLoading = computed(() => queryResults.value?.[1].isLoading); +const widgetLoading = computed(() => queryResults.value?.[0].isFetching || state.dataTableLoading); +const previousLoading = computed(() => queryResults.value?.[1].isFetching || state.dataTableLoading); const errorMessage = computed(() => { if (!state.dataTable) return i18n.t('COMMON.WIDGETS.NO_DATA_TABLE_ERROR_MESSAGE'); return queryResults.value?.[0].error?.message; @@ -245,6 +246,7 @@ useResizeObserver(valueTextRef, throttle(() => { watch(() => props.dataTableId, async (newDataTableId) => { if (!newDataTableId) return; + state.dataTableLoading = true; const fetcher = state.isPrivateWidget ? api.privateDataTableAPI.get : api.publicDataTableAPI.get; @@ -252,6 +254,8 @@ watch(() => props.dataTableId, async (newDataTableId) => { state.dataTable = await fetcher({ data_table_id: newDataTableId }); } catch (e) { ErrorHandler.handleError(e); + } finally { + state.dataTableLoading = false; } }, { immediate: true }); defineExpose({ diff --git a/apps/web/src/common/modules/widgets/_widgets/pie-chart/PieChart.vue b/apps/web/src/common/modules/widgets/_widgets/pie-chart/PieChart.vue index 03eadc27c4..63fca9b4c1 100644 --- a/apps/web/src/common/modules/widgets/_widgets/pie-chart/PieChart.vue +++ b/apps/web/src/common/modules/widgets/_widgets/pie-chart/PieChart.vue @@ -181,6 +181,7 @@ const state = reactive({ const _dateFormat = (props.widgetOptions?.dateFormat?.value as DateFormatValue)?.format || 'MMM DD, YYYY'; return DATE_FORMAT?.[_dateFormat]?.[widgetOptionsState.granularityInfo?.granularity]; }), + dataTableLoading: false, }); const widgetOptionsState = reactive({ @@ -239,7 +240,7 @@ const queryResult = useQuery({ staleTime: WIDGET_LOAD_STALE_TIME, }); -const widgetLoading = computed(() => queryResult.isFetching.value); +const widgetLoading = computed(() => queryResult.isFetching.value || state.dataTableLoading); const errorMessage = computed(() => { if (!state.dataTable) return i18n.t('COMMON.WIDGETS.NO_DATA_TABLE_ERROR_MESSAGE'); return queryResult.error?.value?.message; @@ -293,6 +294,7 @@ useResizeObserver(chartContext, throttle(() => { }, 500)); watch(() => props.dataTableId, async (newDataTableId) => { if (!newDataTableId) return; + state.dataTableLoading = true; const fetcher = state.isPrivateWidget ? api.privateDataTableAPI.get : api.publicDataTableAPI.get; @@ -300,6 +302,8 @@ watch(() => props.dataTableId, async (newDataTableId) => { state.dataTable = await fetcher({ data_table_id: newDataTableId }); } catch (e) { ErrorHandler.handleError(e); + } finally { + state.dataTableLoading = false; } }, { immediate: true }); defineExpose({ diff --git a/apps/web/src/common/modules/widgets/_widgets/sankey-chart/SankeyChart.vue b/apps/web/src/common/modules/widgets/_widgets/sankey-chart/SankeyChart.vue index 098db40ad2..c020dcbced 100644 --- a/apps/web/src/common/modules/widgets/_widgets/sankey-chart/SankeyChart.vue +++ b/apps/web/src/common/modules/widgets/_widgets/sankey-chart/SankeyChart.vue @@ -124,6 +124,7 @@ const state = reactive({ const _dateFormat = (props.widgetOptions?.dateFormat?.value as DateFormatValue)?.format || 'MMM DD, YYYY'; return DATE_FORMAT?.[_dateFormat]?.[widgetOptionsState.granularityInfo?.granularity]; }), + dataTableLoading: false, }); const widgetOptionsState = reactive({ @@ -180,7 +181,7 @@ const queryResult = useQuery({ staleTime: WIDGET_LOAD_STALE_TIME, }); -const widgetLoading = computed(() => queryResult.isFetching.value); +const widgetLoading = computed(() => queryResult.isFetching.value || state.dataTableLoading); const errorMessage = computed(() => { if (!state.dataTable) return i18n.t('COMMON.WIDGETS.NO_DATA_TABLE_ERROR_MESSAGE'); return queryResult.error?.value?.message; @@ -241,6 +242,7 @@ useResizeObserver(chartContext, throttle(() => { }, 500)); watch(() => props.dataTableId, async (newDataTableId) => { if (!newDataTableId) return; + state.dataTableLoading = true; const fetcher = state.isPrivateWidget ? api.privateDataTableAPI.get : api.publicDataTableAPI.get; @@ -248,6 +250,8 @@ watch(() => props.dataTableId, async (newDataTableId) => { state.dataTable = await fetcher({ data_table_id: newDataTableId }); } catch (e) { ErrorHandler.handleError(e); + } finally { + state.dataTableLoading = false; } }, { immediate: true }); defineExpose({ diff --git a/apps/web/src/common/modules/widgets/_widgets/stacked-column-chart/StackedColumnChart.vue b/apps/web/src/common/modules/widgets/_widgets/stacked-column-chart/StackedColumnChart.vue index c94aa2953e..2f1c8bcb48 100644 --- a/apps/web/src/common/modules/widgets/_widgets/stacked-column-chart/StackedColumnChart.vue +++ b/apps/web/src/common/modules/widgets/_widgets/stacked-column-chart/StackedColumnChart.vue @@ -164,6 +164,7 @@ const state = reactive({ return { start: _start, end: _end }; }), dateFormat: computed(() => DATE_FORMAT?.[widgetOptionsState.dateFormatInfo?.format]?.[widgetOptionsState.granularityInfo?.granularity]), + dataTableLoading: false, }); const widgetOptionsState = reactive({ @@ -220,7 +221,7 @@ const queryResult = useQuery({ staleTime: WIDGET_LOAD_STALE_TIME, }); -const widgetLoading = computed(() => queryResult.isFetching.value); +const widgetLoading = computed(() => queryResult.isFetching.value || state.dataTableLoading); const errorMessage = computed(() => { if (!state.dataTable) return i18n.t('COMMON.WIDGETS.NO_DATA_TABLE_ERROR_MESSAGE'); return queryResult.error?.value?.message; @@ -290,6 +291,7 @@ useResizeObserver(chartContext, throttle(() => { watch(() => props.dataTableId, async (newDataTableId) => { if (!newDataTableId) return; + state.dataTableLoading = true; const fetcher = state.isPrivateWidget ? api.privateDataTableAPI.get : api.publicDataTableAPI.get; @@ -297,6 +299,8 @@ watch(() => props.dataTableId, async (newDataTableId) => { state.dataTable = await fetcher({ data_table_id: newDataTableId }); } catch (e) { ErrorHandler.handleError(e); + } finally { + state.dataTableLoading = false; } }, { immediate: true }); defineExpose({ diff --git a/apps/web/src/common/modules/widgets/_widgets/stacked-horizontal-bar-chart/StackedHorizontalBarChart.vue b/apps/web/src/common/modules/widgets/_widgets/stacked-horizontal-bar-chart/StackedHorizontalBarChart.vue index caef648526..651c5f5a4a 100644 --- a/apps/web/src/common/modules/widgets/_widgets/stacked-horizontal-bar-chart/StackedHorizontalBarChart.vue +++ b/apps/web/src/common/modules/widgets/_widgets/stacked-horizontal-bar-chart/StackedHorizontalBarChart.vue @@ -159,6 +159,7 @@ const state = reactive({ const _dateFormat = (props.widgetOptions?.dateFormat?.value as DateFormatValue)?.format || 'MMM DD, YYYY'; return DATE_FORMAT?.[_dateFormat]?.[widgetOptionsState.granularityInfo?.granularity]; }), + dataTableLoading: false, }); const widgetOptionsState = reactive({ @@ -214,7 +215,7 @@ const queryResult = useQuery({ staleTime: WIDGET_LOAD_STALE_TIME, }); -const widgetLoading = computed(() => queryResult.isFetching.value); +const widgetLoading = computed(() => queryResult.isFetching.value || state.dataTableLoading); const errorMessage = computed(() => { if (!state.dataTable) return i18n.t('COMMON.WIDGETS.NO_DATA_TABLE_ERROR_MESSAGE'); return queryResult.error?.value?.message; @@ -283,6 +284,7 @@ useResizeObserver(chartContext, throttle(() => { watch(() => props.dataTableId, async (newDataTableId) => { if (!newDataTableId) return; + state.dataTableLoading = true; const fetcher = state.isPrivateWidget ? api.privateDataTableAPI.get : api.publicDataTableAPI.get; @@ -290,6 +292,8 @@ watch(() => props.dataTableId, async (newDataTableId) => { state.dataTable = await fetcher({ data_table_id: newDataTableId }); } catch (e) { ErrorHandler.handleError(e); + } finally { + state.dataTableLoading = false; } }, { immediate: true }); defineExpose({ diff --git a/apps/web/src/common/modules/widgets/_widgets/table/Table.vue b/apps/web/src/common/modules/widgets/_widgets/table/Table.vue index 9d8a6a8915..885816d5de 100644 --- a/apps/web/src/common/modules/widgets/_widgets/table/Table.vue +++ b/apps/web/src/common/modules/widgets/_widgets/table/Table.vue @@ -116,6 +116,7 @@ const state = reactive({ return basicFields; }), isPivotDataTable: computed(() => state.dataTable?.operator === DATA_TABLE_OPERATOR.PIVOT), + dataTableLoading: false, }); const widgetOptionsState = reactive({ @@ -238,7 +239,7 @@ const queryResults = useQueries({ ], }); -const widgetLoading = computed(() => queryResults.value?.[0].isFetching); +const widgetLoading = computed(() => queryResults.value?.[0].isFetching || state.dataTableLoading); const errorMessage = computed(() => { if (!state.dataTable) return i18n.t('COMMON.WIDGETS.NO_DATA_TABLE_ERROR_MESSAGE'); return queryResults.value?.[0].error?.message as string; @@ -286,6 +287,7 @@ const handleUpdateThisPage = async (newPage: number) => { watch(() => props.dataTableId, async (newDataTableId) => { if (!newDataTableId) return; + state.dataTableLoading = true; const fetcher = state.isPrivateWidget ? api.privateDataTableAPI.get : api.publicDataTableAPI.get; @@ -293,6 +295,8 @@ watch(() => props.dataTableId, async (newDataTableId) => { state.dataTable = await fetcher({ data_table_id: newDataTableId }); } catch (e) { ErrorHandler.handleError(e); + } finally { + state.dataTableLoading = false; } }, { immediate: true }); defineExpose({ diff --git a/apps/web/src/common/modules/widgets/_widgets/treemap/Treemap.vue b/apps/web/src/common/modules/widgets/_widgets/treemap/Treemap.vue index df39536023..1a3d76210f 100644 --- a/apps/web/src/common/modules/widgets/_widgets/treemap/Treemap.vue +++ b/apps/web/src/common/modules/widgets/_widgets/treemap/Treemap.vue @@ -126,6 +126,7 @@ const state = reactive({ const _dateFormat = (props.widgetOptions?.dateFormat?.value as DateFormatValue)?.format || 'MMM DD, YYYY'; return DATE_FORMAT?.[_dateFormat]?.[widgetOptionsState.granularityInfo?.granularity]; }), + dataTableLoading: false, }); const widgetOptionsState = reactive({ @@ -180,7 +181,7 @@ const queryResult = useQuery({ staleTime: WIDGET_LOAD_STALE_TIME, }); -const widgetLoading = computed(() => queryResult.isFetching.value); +const widgetLoading = computed(() => queryResult.isFetching.value || state.dataTableLoading); const errorMessage = computed(() => { if (!state.dataTable) return i18n.t('COMMON.WIDGETS.NO_DATA_TABLE_ERROR_MESSAGE'); return queryResult.error?.value?.message; @@ -244,6 +245,7 @@ useResizeObserver(chartContext, throttle(() => { watch(() => props.dataTableId, async (newDataTableId) => { if (!newDataTableId) return; + state.dataTableLoading = true; const fetcher = state.isPrivateWidget ? api.privateDataTableAPI.get : api.publicDataTableAPI.get; @@ -251,6 +253,8 @@ watch(() => props.dataTableId, async (newDataTableId) => { state.dataTable = await fetcher({ data_table_id: newDataTableId }); } catch (e) { ErrorHandler.handleError(e); + } finally { + state.dataTableLoading = false; } }, { immediate: true }); defineExpose({ diff --git a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardLabelsButton.vue b/apps/web/src/services/dashboards/components/dashboard-detail/DashboardLabelsButton.vue index e9427d48f6..9a49fc420f 100644 --- a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardLabelsButton.vue +++ b/apps/web/src/services/dashboards/components/dashboard-detail/DashboardLabelsButton.vue @@ -14,6 +14,7 @@ import ErrorHandler from '@/common/composables/error/errorHandler'; import DashboardLabels from '@/services/dashboards/components/dashboard-detail/DashboardLabels.vue'; import { useDashboardDetailQuery } from '@/services/dashboards/composables/use-dashboard-detail-query'; import { useDashboardManageable } from '@/services/dashboards/composables/use-dashboard-manageable'; +import { useDashboardDetailInfoStore } from '@/services/dashboards/stores/dashboard-detail-info-store'; @@ -23,7 +24,8 @@ interface Props { const props = defineProps(); const labelPopoverRef = ref(null); - +const dashboardDetailStore = useDashboardDetailInfoStore(); +const dashboardDetailState = dashboardDetailStore.state; /* Query */ const { dashboard, @@ -31,10 +33,10 @@ const { keys, queryClient, } = useDashboardDetailQuery({ - dashboardId: computed(() => props.dashboardId), + dashboardId: computed(() => dashboardDetailState.dashboardId), }); const { isManageable } = useDashboardManageable({ - dashboardId: computed(() => props.dashboardId), + dashboardId: computed(() => dashboardDetailState.dashboardId), }); const state = reactive({ visible: false, diff --git a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardRefreshDropdown.vue b/apps/web/src/services/dashboards/components/dashboard-detail/DashboardRefreshDropdown.vue index 58f26d30bb..eb8fde3eab 100644 --- a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardRefreshDropdown.vue +++ b/apps/web/src/services/dashboards/components/dashboard-detail/DashboardRefreshDropdown.vue @@ -43,10 +43,10 @@ const dashboardDetailState = dashboardDetailStore.state; const { dashboard, keys, fetcher, queryClient, } = useDashboardDetailQuery({ - dashboardId: computed(() => props.dashboardId), + dashboardId: computed(() => dashboardDetailState.dashboardId), }); const { isManageable } = useDashboardManageable({ - dashboardId: computed(() => props.dashboardId), + dashboardId: computed(() => dashboardDetailState.dashboardId), }); const state = reactive({ diff --git a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardWidgetContainerV2.vue b/apps/web/src/services/dashboards/components/dashboard-detail/DashboardWidgetContainerV2.vue index ef48fa4cb5..70e677ecdc 100644 --- a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardWidgetContainerV2.vue +++ b/apps/web/src/services/dashboards/components/dashboard-detail/DashboardWidgetContainerV2.vue @@ -106,7 +106,7 @@ const state = reactive({ mountedWidgetMap: {} as Record, intersectedWidgetMap: {} as Record, isAllWidgetsMounted: computed(() => Object.values(state.mountedWidgetMap).every((d) => d)), - refinedWidgetInfoList: [] as RefinedWidgetInfo[], + refinedWidgetInfoList: computed(() => getRefinedWidgetInfoList(widgetList.value, containerWidth.value)), overlayType: 'EDIT' as 'EDIT' | 'EXPAND', showExpandOverlay: false, remountWidgetId: undefined as string|undefined, @@ -418,9 +418,9 @@ const handleClickAddWidget = () => { }; /* Watcher */ -watch([widgetList, () => dashboard.value?.layouts], ([widgets]) => { - state.refinedWidgetInfoList = getRefinedWidgetInfoList(widgets); -}); +// watch([widgetList, () => dashboard.value?.layouts], ([widgets]) => { +// state.refinedWidgetInfoList = getRefinedWidgetInfoList(widgets); +// }); watch(() => widgetGenerateState.showOverlay, async (showOverlay) => { if (!showOverlay && widgetGenerateState.overlayType !== 'EXPAND') { state.remountWidgetId = widgetGenerateState.latestWidgetId; @@ -444,10 +444,10 @@ watch(widgetList, (dashboardWidgets) => { }); } }, { immediate: true }); -watch(() => containerWidth.value, (_containerWidth) => { - if (!state.refinedWidgetInfoList?.length) return; - state.refinedWidgetInfoList = getResizedWidgetInfoList(state.refinedWidgetInfoList, _containerWidth); -}); +// watch(() => containerWidth.value, (_containerWidth) => { +// if (!state.refinedWidgetInfoList?.length) return; +// state.refinedWidgetInfoList = getResizedWidgetInfoList(state.refinedWidgetInfoList, _containerWidth); +// }); defineExpose({ refreshAllWidget, }); diff --git a/apps/web/src/services/dashboards/composables/use-dashboard-detail-query.ts b/apps/web/src/services/dashboards/composables/use-dashboard-detail-query.ts index ed2796fdcc..fb35f870d5 100644 --- a/apps/web/src/services/dashboards/composables/use-dashboard-detail-query.ts +++ b/apps/web/src/services/dashboards/composables/use-dashboard-detail-query.ts @@ -2,8 +2,9 @@ import type { ComputedRef } from 'vue'; import { computed } from 'vue'; import type { QueryClient, QueryKey } from '@tanstack/vue-query'; -import { useQuery, useQueryClient } from '@tanstack/vue-query'; +import { useQueryClient } from '@tanstack/vue-query'; +import { useScopedQuery } from '@/api-clients/_common/composables/use-scoped-query'; import type { DashboardModel, DashboardUpdateParams } from '@/api-clients/dashboard/_types/dashboard-type'; import type { WidgetModel, WidgetUpdateParams } from '@/api-clients/dashboard/_types/widget-type'; import { usePrivateDashboardApi } from '@/api-clients/dashboard/private-dashboard/composables/use-private-dashboard-api'; @@ -80,23 +81,23 @@ export const useDashboardDetailQuery = ({ ]); /* Querys */ - const publicDashboardQuery = useQuery({ + const publicDashboardQuery = useScopedQuery({ queryKey: _publicDashboardQueryKey, queryFn: () => publicDashboardAPI.get({ dashboard_id: dashboardId.value as string, }), enabled: computed(() => !!dashboardId.value && !isPrivate.value), staleTime: STALE_TIME, - }); - const privateDashboardQuery = useQuery({ + }, ['DOMAIN', 'WORKSPACE']); + const privateDashboardQuery = useScopedQuery({ queryKey: _privateDashboardQueryKey, queryFn: () => privateDashboardAPI.get({ dashboard_id: dashboardId.value as string, }), enabled: computed(() => !!dashboardId.value && isPrivate.value), staleTime: STALE_TIME, - }); - const publicWidgetListQuery = useQuery({ + }, ['WORKSPACE']); + const publicWidgetListQuery = useScopedQuery({ queryKey: _publicWidgetListQueryKey, queryFn: () => publicWidgetAPI.list({ dashboard_id: dashboardId.value as string, @@ -106,8 +107,8 @@ export const useDashboardDetailQuery = ({ initialData: DEFAULT_LIST_DATA, initialDataUpdatedAt: 0, staleTime: STALE_TIME, - }); - const privateWidgetListQuery = useQuery({ + }, ['DOMAIN', 'WORKSPACE']); + const privateWidgetListQuery = useScopedQuery({ queryKey: _privateWidgetListQueryKey, queryFn: () => privateWidgetAPI.list({ dashboard_id: dashboardId.value as string, @@ -117,7 +118,7 @@ export const useDashboardDetailQuery = ({ initialData: DEFAULT_LIST_DATA, initialDataUpdatedAt: 0, staleTime: STALE_TIME, - }); + }, ['WORKSPACE']); /* Functions */ const updateDashboardFn = (params: DashboardUpdateParams): Promise => { @@ -145,7 +146,7 @@ export const useDashboardDetailQuery = ({ return { dashboard: computed(() => (isPrivate.value ? privateDashboardQuery.data?.value : publicDashboardQuery.data?.value)), - widgetList: computed(() => (isPrivate.value ? privateWidgetListQuery.data?.value : publicWidgetListQuery.data?.value)), + widgetList: computed(() => (isPrivate.value ? privateWidgetListQuery.data?.value : publicWidgetListQuery.data?.value) ?? []), isLoading, isError, api: { diff --git a/apps/web/src/services/dashboards/composables/use-dashboard-query.ts b/apps/web/src/services/dashboards/composables/use-dashboard-query.ts index aedef6620d..35a7770788 100644 --- a/apps/web/src/services/dashboards/composables/use-dashboard-query.ts +++ b/apps/web/src/services/dashboards/composables/use-dashboard-query.ts @@ -4,10 +4,12 @@ import { } from 'vue'; import type { QueryKey } from '@tanstack/vue-query'; -import { type QueryClient, useQuery, useQueryClient } from '@tanstack/vue-query'; +import { type QueryClient, useQueryClient } from '@tanstack/vue-query'; import { ApiQueryHelper } from '@cloudforet/core-lib/space-connector/helper'; +import { useScopedQuery } from '@/api-clients/_common/composables/use-scoped-query'; +import type { ListResponse } from '@/api-clients/_common/schema/api-verbs/list'; import type { FolderModel, FolderUpdateParams } from '@/api-clients/dashboard/_types/folder-type'; import { usePrivateDashboardApi } from '@/api-clients/dashboard/private-dashboard/composables/use-private-dashboard-api'; import type { PrivateDashboardModel } from '@/api-clients/dashboard/private-dashboard/schema/model'; @@ -104,7 +106,7 @@ export const useDashboardQuery = (): UseDashboardQueryReturn => { /* Querys */ - const publicDashboardListQuery = useQuery({ + const publicDashboardListQuery = useScopedQuery, unknown, PublicDashboardModel[]>({ queryKey: _publicDashboardListQueryKey, queryFn: () => publicDashboardAPI.list({ query: { @@ -112,12 +114,12 @@ export const useDashboardQuery = (): UseDashboardQueryReturn => { sort: [{ key: 'created_at', desc: true }], }, }), - select: (data) => data?.results || [], + select: (data) => data?.results ?? [], initialData: DEFAULT_LIST_DATA, initialDataUpdatedAt: 0, staleTime: STALE_TIME, - }); - const privateDashboardListQuery = useQuery({ + }, ['DOMAIN', 'WORKSPACE']); + const privateDashboardListQuery = useScopedQuery, unknown, PrivateDashboardModel[]>({ queryKey: _privateDashboardListQueryKey, queryFn: () => privateDashboardAPI.list({ query: { @@ -129,8 +131,8 @@ export const useDashboardQuery = (): UseDashboardQueryReturn => { initialDataUpdatedAt: 0, staleTime: STALE_TIME, enabled: computed(() => !_state.isAdminMode), - }); - const publicFolderListQuery = useQuery({ + }, ['WORKSPACE']); + const publicFolderListQuery = useScopedQuery, unknown, PublicFolderModel[]>({ queryKey: _publicFolderListQueryKey, queryFn: () => publicFolderAPI.list({ query: { @@ -142,8 +144,8 @@ export const useDashboardQuery = (): UseDashboardQueryReturn => { initialData: DEFAULT_LIST_DATA, initialDataUpdatedAt: 0, staleTime: STALE_TIME, - }); - const privateFolderListQuery = useQuery({ + }, ['DOMAIN', 'WORKSPACE']); + const privateFolderListQuery = useScopedQuery, unknown, PrivateFolderModel[]>({ queryKey: _privateFolderListQueryKey, queryFn: () => privateFolderAPI.list({ query: { @@ -155,7 +157,7 @@ export const useDashboardQuery = (): UseDashboardQueryReturn => { initialDataUpdatedAt: 0, staleTime: STALE_TIME, enabled: computed(() => !_state.isAdminMode), - }); + }, ['WORKSPACE']); /* Fetchers */ const updateFolderFn = (params: FolderUpdateParams): Promise => { @@ -184,10 +186,10 @@ export const useDashboardQuery = (): UseDashboardQueryReturn => { || publicFolderListQuery.isFetching.value || privateFolderListQuery.isFetching.value); return { - publicDashboardList: publicDashboardListQuery.data, - privateDashboardList: privateDashboardListQuery.data, - publicFolderList: publicFolderListQuery.data, - privateFolderList: privateFolderListQuery.data, + publicDashboardList: computed(() => publicDashboardListQuery.data.value ?? []), + privateDashboardList: computed(() => privateDashboardListQuery.data.value ?? []), + publicFolderList: computed(() => publicFolderListQuery.data.value ?? []), + privateFolderList: computed(() => privateFolderListQuery.data.value ?? []), isLoading, keys: { publicDashboardListQueryKey: _publicDashboardListQueryKey, diff --git a/apps/web/src/services/dashboards/stores/dashboard-detail-info-store.ts b/apps/web/src/services/dashboards/stores/dashboard-detail-info-store.ts index c3ca17a807..31e75014d7 100644 --- a/apps/web/src/services/dashboards/stores/dashboard-detail-info-store.ts +++ b/apps/web/src/services/dashboards/stores/dashboard-detail-info-store.ts @@ -3,10 +3,6 @@ import { computed, reactive } from 'vue'; import { cloneDeep, isEmpty, isEqual } from 'lodash'; import { defineStore } from 'pinia'; -import { SpaceConnector } from '@cloudforet/core-lib/space-connector'; -import { getCancellableFetcher } from '@cloudforet/core-lib/space-connector/cancellable-fetcher'; - -import type { ListResponse } from '@/api-clients/_common/schema/api-verbs/list'; import type { DashboardLayoutWidgetInfo, DashboardModel, @@ -17,13 +13,9 @@ import type { DashboardVars, DashboardGlobalVariablesSchema, } from '@/api-clients/dashboard/_types/dashboard-type'; -import type { PrivateDashboardGetParameters } from '@/api-clients/dashboard/private-dashboard/schema/api-verbs/get'; import type { PrivateDashboardModel } from '@/api-clients/dashboard/private-dashboard/schema/model'; -import type { PrivateWidgetListParameters } from '@/api-clients/dashboard/private-widget/schema/api-verbs/list'; import type { PrivateWidgetModel } from '@/api-clients/dashboard/private-widget/schema/model'; -import type { PublicDashboardGetParameters } from '@/api-clients/dashboard/public-dashboard/schema/api-verbs/get'; import type { PublicDashboardModel } from '@/api-clients/dashboard/public-dashboard/schema/model'; -import type { PublicWidgetListParameters } from '@/api-clients/dashboard/public-widget/schema/api-verbs/list'; import type { PublicWidgetModel } from '@/api-clients/dashboard/public-widget/schema/model'; import { ROLE_TYPE } from '@/schema/identity/role/constant'; @@ -33,12 +25,8 @@ import { useUserStore } from '@/store/user/user-store'; import getRandomId from '@/lib/random-id-generator'; import WorkspaceVariableModel from '@/lib/variable-models/managed-model/resource-model/workspace-variable-model'; -import ErrorHandler from '@/common/composables/error/errorHandler'; - import { MANAGED_DASHBOARD_VARIABLES_SCHEMA } from '@/services/dashboards/constants/dashboard-managed-variables-schema'; -type WidgetModel = PublicWidgetModel | PrivateWidgetModel; -type GetDashboardParameters = PublicDashboardGetParameters | PrivateDashboardGetParameters; const DEFAULT_REFRESH_INTERVAL = '5m'; export const DASHBOARD_DEFAULT = Object.freeze<{ options: DashboardOptions }>({ options: { @@ -247,37 +235,6 @@ export const useDashboardDetailInfoStore = defineStore('dashboard-detail-info', state.variables = _variables; state.variablesInitMap = _variablesInitMap; }; - const privateDashboardGetFetcher = getCancellableFetcher(SpaceConnector.clientV2.dashboard.privateDashboard.get); - const publicDashboardGetFetcher = getCancellableFetcher(SpaceConnector.clientV2.dashboard.publicDashboard.get); - const getDashboardInfo = async ({ dashboardId, fetchWidgets = false }: { dashboardId: undefined|string, fetchWidgets?: boolean}) => { - if (dashboardId === state.dashboardId || dashboardId === undefined) return; - - const isPrivate = dashboardId?.startsWith('private'); - const fetcher = isPrivate ? privateDashboardGetFetcher : publicDashboardGetFetcher; - // WARN:: from under this line, beware using originState. originState could reference irrelevant dashboard data - state.dashboardId = dashboardId; - state.loadingDashboard = true; - if (fetchWidgets) setDashboardWidgets([]); - try { - const params: GetDashboardParameters = { dashboard_id: dashboardId as string }; - const { status, response } = await fetcher(params); - if (status === 'succeed') { - if (response.version === '1.0') { - setDashboardInfoStoreState(response); - } else { - setDashboardInfoStoreStateV2(response); - } - if (fetchWidgets) { - await listDashboardWidgets(); - } - } - } catch (e) { - reset(); - throw e; - } finally { - state.loadingDashboard = false; - } - }; // HACK: only for 1.0 dashboard const resetVariables = (originVariables?: DashboardVariables, originVariablesSchema?: DashboardVariablesSchema) => { const _originVariables: DashboardVariables = originVariables ?? state.variables ?? {}; @@ -310,23 +267,6 @@ export const useDashboardDetailInfoStore = defineStore('dashboard-detail-info', setVariables(_variables); setVariablesInitMap(_variablesInitMap); }; - // HACK: only for Project Dashboard - const listDashboardWidgets = async () => { - if (!state.dashboardId) return; - try { - const isPrivate = state.dashboardId.startsWith('private'); - const fetcher = isPrivate - ? SpaceConnector.clientV2.dashboard.privateWidget.list - : SpaceConnector.clientV2.dashboard.publicWidget.list; - const res = await fetcher>({ - dashboard_id: state.dashboardId, - }); - state.dashboardWidgets = res.results || []; - return; - } catch (e) { - ErrorHandler.handleError(e); - } - }; const mutations = { setOptions, @@ -342,9 +282,7 @@ export const useDashboardDetailInfoStore = defineStore('dashboard-detail-info', }; const actions = { reset, - getDashboardInfo, resetVariables, - listDashboardWidgets, setDashboardInfoStoreStateV2, setDashboardInfoStoreState, }; diff --git a/apps/web/src/services/project-v1/pages/ProjectDashboardPage.vue b/apps/web/src/services/project-v1/pages/ProjectDashboardPage.vue index dc5531ae98..920dcd29d1 100644 --- a/apps/web/src/services/project-v1/pages/ProjectDashboardPage.vue +++ b/apps/web/src/services/project-v1/pages/ProjectDashboardPage.vue @@ -4,6 +4,7 @@ import { onUnmounted, ref, watch, } from 'vue'; +import { useRoute } from 'vue-router/composables'; import { PSkeleton } from '@cloudforet/mirinae'; @@ -36,29 +37,35 @@ const { getProperRouteLocation } = useProperRouteLocation(); const dashboardDetailStore = useDashboardDetailInfoStore(); const dashboardDetailState = dashboardDetailStore.state; const widgetContainerRef = ref(null); +const route = useRoute(); + /* Query */ const { dashboard, widgetList, isError, isLoading, + keys, + queryClient, } = useDashboardDetailQuery({ - dashboardId: computed(() => props.dashboardId), + dashboardId: computed(() => route.params.dashboardId), }); const handleRefresh = async () => { - await dashboardDetailStore.listDashboardWidgets(); + await queryClient.invalidateQueries({ queryKey: keys.publicWidgetListQueryKey.value }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore if (widgetContainerRef.value) widgetContainerRef.value.refreshAllWidget(); }; -watch(dashboard, (_dashboard) => { +watch([dashboard, () => route.params], ([_dashboard]) => { if (_dashboard) { dashboardDetailStore.reset(); dashboardDetailStore.setDashboardInfoStoreStateV2({ ..._dashboard, - project_id: props.id, + project_id: route.params.id, }); } -}); +}, { immediate: true }); watch(widgetList, (_widgetList) => { if (_widgetList.length) { dashboardDetailStore.setDashboardWidgets(_widgetList);