From 0aadbf24d37d74c2c0ba4c8823d673e9b1ab71d1 Mon Sep 17 00:00:00 2001 From: Yongtae Park Date: Thu, 10 Apr 2025 15:41:55 +0900 Subject: [PATCH 01/11] fix(dashboard-create): apply changed create field policy Signed-off-by: samuel.park --- .../components/DashboardCreateScopeForm.vue | 28 +++++++++++++++---- .../DashboardCreateStep2SingleCase.vue | 2 +- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/apps/web/src/services/dashboard-shared/dashboard-create/components/DashboardCreateScopeForm.vue b/apps/web/src/services/dashboard-shared/dashboard-create/components/DashboardCreateScopeForm.vue index 7ad4678a96..2a94122131 100644 --- a/apps/web/src/services/dashboard-shared/dashboard-create/components/DashboardCreateScopeForm.vue +++ b/apps/web/src/services/dashboard-shared/dashboard-create/components/DashboardCreateScopeForm.vue @@ -18,10 +18,10 @@ import { useUserStore } from '@/store/user/user-store'; import WorkspaceLogoIcon from '@/common/modules/navigations/top-bar/modules/top-bar-header/WorkspaceLogoIcon.vue'; +import { useDashboardRouteContext } from '@/services/dashboard-shared/_composables/use-dashboard-route-context'; import { useDashboardCreatePageStore } from '@/services/dashboard-shared/dashboard-create/stores/dashboard-create-page-store'; - interface BoardSet { value: 'WORKSPACE' | 'PRIVATE'; title: TranslateResult; @@ -39,6 +39,11 @@ const storeState = reactive({ isWorkspaceOwner: computed(() => userStore.state.currentRoleInfo?.roleType === ROLE_TYPE.WORKSPACE_OWNER), selectedWorkspace: computed(() => userWorkspaceState.getters.currentWorkspace), }); + +const { + entryPoint, +} = useDashboardRouteContext(); + const state = reactive({ dashboardScopeBoardSets: computed(() => { const boardSets: BoardSet[] = [ @@ -79,7 +84,9 @@ onMounted(() => { {{ $t('DASHBOARDS.CREATE.DASHBOARD_SCOPE') }} -
+
{
+
+ + + Only Public + {{ $t('(Everyone in this workspace)') }} + +
diff --git a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardControlButtons.vue b/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardControlButtons.vue similarity index 100% rename from apps/web/src/services/dashboards/components/dashboard-detail/DashboardControlButtons.vue rename to apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardControlButtons.vue diff --git a/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardDetailHeader.vue b/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardDetailHeader.vue new file mode 100644 index 0000000000..76ae0b2393 --- /dev/null +++ b/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardDetailHeader.vue @@ -0,0 +1,194 @@ + + + + + diff --git a/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardLabelsButton.vue b/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardLabelsButton.vue index 1fa7355318..728c6db185 100644 --- a/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardLabelsButton.vue +++ b/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardLabelsButton.vue @@ -2,18 +2,15 @@ import { onClickOutside } from '@vueuse/core'; import { computed, reactive, ref } from 'vue'; -import { useMutation, useQueryClient } from '@tanstack/vue-query'; - import { PButton, PBadge, PPopover } from '@cloudforet/mirinae'; -import type { PrivateDashboardModel } from '@/api-clients/dashboard/private-dashboard/schema/model'; -import type { PublicDashboardModel } from '@/api-clients/dashboard/public-dashboard/schema/model'; import ErrorHandler from '@/common/composables/error/errorHandler'; +import { useDashboardUpdateAction } from '@/services/dashboard-shared/actions/use-dashboard-update-action'; import DashboardLabels from '@/services/dashboard-shared/dashboard-detail/components/DashboardLabels.vue'; import { useDashboardGetQuery } from '@/services/dashboard-shared/dashboard-detail/composables/use-dashboard-get-query'; -import { useDashboardManageable } from '@/services/dashboards/composables/use-dashboard-manageable'; +import { useDashboardManageable } from '@/services/dashboard-shared/dashboard-detail/composables/use-dashboard-manageable'; @@ -26,15 +23,12 @@ const labelPopoverRef = ref(null); /* Query */ const { dashboard, - fetcher, - keys, } = useDashboardGetQuery({ dashboardId: computed(() => props.dashboardId), }); const { isManageable } = useDashboardManageable({ dashboardId: computed(() => props.dashboardId), }); -const queryClient = useQueryClient(); const state = reactive({ visible: false, dashboardLabels: computed(() => dashboard.value?.labels || []), @@ -42,20 +36,15 @@ const state = reactive({ }); const handleUpdateLabels = async (labels: string[]) => { - mutate({ + updateDashboard({ dashboard_id: props.dashboardId, labels, }); }; -const { mutate } = useMutation( +const { mutate: updateDashboard } = useDashboardUpdateAction( { - mutationFn: fetcher.updateDashboardFn, - onSuccess: (_dashboard: PublicDashboardModel|PrivateDashboardModel) => { - const isPrivate = _dashboard.dashboard_id.startsWith('private'); - const dashboardQueryKey = isPrivate ? keys.privateDashboardGetQueryKey : keys.publicDashboardGetQueryKey; - queryClient.invalidateQueries({ queryKey: dashboardQueryKey.value }); - }, + dashboardId: computed(() => props.dashboardId), onError: (e) => { ErrorHandler.handleError(e); }, diff --git a/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardRefreshDropdown.vue b/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardRefreshDropdown.vue index 7f01b154e3..7360c7a5e5 100644 --- a/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardRefreshDropdown.vue +++ b/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardRefreshDropdown.vue @@ -17,8 +17,8 @@ import type { PublicDashboardModel } from '@/api-clients/dashboard/public-dashbo import { i18n } from '@/translations'; import { useDashboardGetQuery } from '@/services/dashboard-shared/dashboard-detail/composables/use-dashboard-get-query'; +import { useDashboardManageable } from '@/services/dashboard-shared/dashboard-detail/composables/use-dashboard-manageable'; import { useDashboardDetailInfoStore } from '@/services/dashboard-shared/dashboard-detail/stores/dashboard-detail-info-store'; -import { useDashboardManageable } from '@/services/dashboards/composables/use-dashboard-manageable'; const REFRESH_INTERVAL_OPTIONS = Object.keys(REFRESH_INTERVAL_OPTIONS_MAP); diff --git a/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardToolsetDateDropdown.vue b/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardToolsetDateDropdown.vue index bdd7ba12a4..6df3225a5b 100644 --- a/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardToolsetDateDropdown.vue +++ b/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardToolsetDateDropdown.vue @@ -21,8 +21,8 @@ import { useI18nDayjs } from '@/common/composables/i18n-dayjs'; import DashboardToolsetDateCustomModal from '@/services/dashboard-shared/dashboard-detail/components/DashboardToolsetDateCustomModal.vue'; import { useDashboardGetQuery } from '@/services/dashboard-shared/dashboard-detail/composables/use-dashboard-get-query'; +import { useDashboardManageable } from '@/services/dashboard-shared/dashboard-detail/composables/use-dashboard-manageable'; import { useDashboardDetailInfoStore } from '@/services/dashboard-shared/dashboard-detail/stores/dashboard-detail-info-store'; -import { useDashboardManageable } from '@/services/dashboards/composables/use-dashboard-manageable'; interface Props { dateRange?: DateRange; diff --git a/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardWidgetContainerV2.vue b/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardWidgetContainerV2.vue index 30b28a5d94..9b0dba09c4 100644 --- a/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardWidgetContainerV2.vue +++ b/apps/web/src/services/dashboard-shared/dashboard-detail/components/DashboardWidgetContainerV2.vue @@ -51,9 +51,9 @@ import { useDashboardContainerWidth, } from '@/services/dashboard-shared/dashboard-detail/composables/use-dashboard-container-width'; import { useDashboardGetQuery } from '@/services/dashboard-shared/dashboard-detail/composables/use-dashboard-get-query'; +import { useDashboardManageable } from '@/services/dashboard-shared/dashboard-detail/composables/use-dashboard-manageable'; import { useDashboardWidgetListQuery } from '@/services/dashboard-shared/dashboard-detail/composables/use-dashboard-widget-list-query'; import { useDashboardDetailInfoStore } from '@/services/dashboard-shared/dashboard-detail/stores/dashboard-detail-info-store'; -import { useDashboardManageable } from '@/services/dashboards/composables/use-dashboard-manageable'; import type { AllReferenceTypeInfo } from '@/services/dashboards/stores/all-reference-type-info-store'; import { useAllReferenceTypeInfoStore, diff --git a/apps/web/src/services/dashboard-shared/dashboard-detail/composables/use-dashboard-manageable.ts b/apps/web/src/services/dashboard-shared/dashboard-detail/composables/use-dashboard-manageable.ts new file mode 100644 index 0000000000..f7dcc8320b --- /dev/null +++ b/apps/web/src/services/dashboard-shared/dashboard-detail/composables/use-dashboard-manageable.ts @@ -0,0 +1,84 @@ +import type { ComputedRef } from 'vue'; +import { computed, reactive } from 'vue'; + +import { RESOURCE_GROUP } from '@/api-clients/_common/schema/constant'; +import type { DashboardModel } from '@/api-clients/dashboard/_types/dashboard-type'; +import type { FolderModel } from '@/api-clients/dashboard/_types/folder-type'; +import type { PublicDashboardModel } from '@/api-clients/dashboard/public-dashboard/schema/model'; +import type { PublicFolderModel } from '@/api-clients/dashboard/public-folder/schema/model'; +import { ROLE_TYPE } from '@/api-clients/identity/role/constant'; + +import { useUserStore } from '@/store/user/user-store'; + +import { useDashboardRouteContext } from '@/services/dashboard-shared/_composables/use-dashboard-route-context'; +import { useDashboardGetQuery } from '@/services/dashboard-shared/dashboard-detail/composables/use-dashboard-get-query'; + +interface UseDashboardManageableOptions { + dashboardId: ComputedRef; +} + +interface UseDashboardManageableReturn { + isManageable: ComputedRef; + getDashboardManageable: (_dashboard?: DashboardModel) => boolean; + getFolderManageable: (_folder?: FolderModel) => boolean; +} + +export const useDashboardManageable = (options?: UseDashboardManageableOptions): UseDashboardManageableReturn => { + const userStore = useUserStore(); + const { dashboardId } = options ?? {}; + + const { entryPoint } = useDashboardRouteContext(); + + + const storeState = reactive({ + isWorkspaceOwner: computed(() => userStore.state.currentRoleInfo?.roleType === ROLE_TYPE.WORKSPACE_OWNER), + }); + + const { dashboard } = useDashboardGetQuery({ + dashboardId: computed(() => dashboardId?.value), + }); + + const isManageable = computed(() => getDashboardManageable(dashboard.value)); + + const getDashboardManageable = (_dashboard?: DashboardModel): boolean => { + if (!_dashboard) return false; + if (entryPoint.value === 'ADMIN') return true; + if (entryPoint.value === 'WORKSPACE') { + if (_dashboard.dashboard_id?.startsWith('private')) return true; + const publicDashboard = _dashboard as PublicDashboardModel; + if (publicDashboard?.shared && publicDashboard?.resource_group === RESOURCE_GROUP.DOMAIN) return false; + if (storeState.isWorkspaceOwner) return true; + return false; + } + if (entryPoint.value === 'PROJECT') { + const publicDashboard = _dashboard as PublicDashboardModel; + if (publicDashboard?.shared) return false; + return true; + } + return false; + }; + const getFolderManageable = (_folder?: FolderModel): boolean => { + if (!_folder) return false; + if (entryPoint.value === 'ADMIN') return true; + if (entryPoint.value === 'WORKSPACE') { + if (_folder.folder_id?.startsWith('private')) return true; + const publicFolder = _folder as PublicFolderModel; + if (publicFolder?.shared && publicFolder?.resource_group === RESOURCE_GROUP.DOMAIN) return false; + if (storeState.isWorkspaceOwner) return true; + return false; + } + if (entryPoint.value === 'PROJECT') { + const publicFolder = _folder as PublicFolderModel; + if (publicFolder?.shared) return false; + return true; + } + return false; + }; + + + return { + isManageable, + getDashboardManageable, + getFolderManageable, + }; +}; diff --git a/apps/web/src/services/dashboards/helpers/dashboard-share-helper.ts b/apps/web/src/services/dashboard-shared/helpers/dashboard-share-helper.ts similarity index 100% rename from apps/web/src/services/dashboards/helpers/dashboard-share-helper.ts rename to apps/web/src/services/dashboard-shared/helpers/dashboard-share-helper.ts diff --git a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardDetailHeader.vue b/apps/web/src/services/dashboards/components/dashboard-detail/DashboardDetailHeader.vue index 3a2dd74a0f..76ae0b2393 100644 --- a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardDetailHeader.vue +++ b/apps/web/src/services/dashboards/components/dashboard-detail/DashboardDetailHeader.vue @@ -7,45 +7,30 @@ import { import type { MenuItem } from '@cloudforet/mirinae/types/controls/context-menu/type'; import type { BadgeStyleType, BadgeType } from '@cloudforet/mirinae/types/data-display/badge/type'; -import type { DashboardScope } from '@/api-clients/dashboard/_types/dashboard-type'; -import { ROLE_TYPE } from '@/api-clients/identity/role/constant'; +import { RESOURCE_GROUP } from '@/api-clients/_common/schema/constant'; +import type { FolderModel } from '@/api-clients/dashboard/_types/folder-type'; import { i18n } from '@/translations'; -import { useAppContextStore } from '@/store/app-context/app-context-store'; -import { useUserStore } from '@/store/user/user-store'; import { gray } from '@/styles/colors'; +import { useDashboardRouteContext } from '@/services/dashboard-shared/_composables/use-dashboard-route-context'; +import DashboardControlButtons from '@/services/dashboard-shared/dashboard-detail/components/DashboardControlButtons.vue'; import DashboardLabelsButton from '@/services/dashboard-shared/dashboard-detail/components/DashboardLabelsButton.vue'; import { useDashboardGetQuery } from '@/services/dashboard-shared/dashboard-detail/composables/use-dashboard-get-query'; -import DashboardControlButtons from '@/services/dashboards/components/dashboard-detail/DashboardControlButtons.vue'; -import { useDashboardControlMenuItems } from '@/services/dashboards/composables/use-dashboard-control-menu-items'; -import { useDashboardFolderQuery } from '@/services/dashboards/composables/use-dashboard-folder-query'; -import { useDashboardManageable } from '@/services/dashboards/composables/use-dashboard-manageable'; -import { useDashboardQuery } from '@/services/dashboards/composables/use-dashboard-query'; -import { useDashboardPageControlStore } from '@/services/dashboards/stores/dashboard-page-control-store'; - - +import { useDashboardManageable } from '@/services/dashboard-shared/dashboard-detail/composables/use-dashboard-manageable'; +import { getControlDashboardMenuItems } from '@/services/dashboard-shared/helpers/dashboard-control-menu-helper'; interface Props { dashboardId: string; - templateName?: string; + folderItems?: Array; } -const props = defineProps(); -const dashboardPageControlStore = useDashboardPageControlStore(); -const appContextStore = useAppContextStore(); -const userStore = useUserStore(); - -/* Query */ -const { - publicDashboardList, - privateDashboardList, -} = useDashboardQuery(); -const { - publicFolderList, - privateFolderList, -} = useDashboardFolderQuery(); - +const props = withDefaults(defineProps(), { + folderItems: () => [], +}); +const emit = defineEmits<{(e: 'select-toolset', toolsetId: string|undefined): void; +}>(); +const { entryPoint } = useDashboardRouteContext(); const { dashboard } = useDashboardGetQuery({ dashboardId: computed(() => props.dashboardId), @@ -54,35 +39,14 @@ const { isManageable } = useDashboardManageable({ dashboardId: computed(() => props.dashboardId), }); -const queryState = reactive({ - publicDashboardItems: computed(() => { - const _v2DashboardItems = publicDashboardList.value.filter((d) => d.version !== '1.0'); - if (storeState.isAdminMode) return _v2DashboardItems; - return _v2DashboardItems.filter((d) => !(d.resource_group === 'DOMAIN' && !!d.shared && d.scope === 'PROJECT')); - }), - privateDashboardItems: computed(() => privateDashboardList.value.filter((d) => d.version !== '1.0')), - allDashboardItems: computed(() => [...queryState.publicDashboardItems, ...queryState.privateDashboardItems]), - publicFolderItems: computed(() => { - if (storeState.isAdminMode) return publicFolderList.value; - return publicFolderList.value.filter((d) => !(d.resource_group === 'DOMAIN' && !!d.shared && d.scope === 'PROJECT')); - }), - privateFolderItems: computed(() => privateFolderList.value), - allFolderItems: computed(() => [...queryState.publicFolderItems, ...queryState.privateFolderItems]), +const controlMenuItems = computed(() => { + if (!dashboard.value) return []; + return getControlDashboardMenuItems(props.dashboardId, isManageable.value, dashboard.value, entryPoint.value === 'PROJECT'); }); -const storeState = reactive({ - isAdminMode: computed(() => appContextStore.getters.isAdminMode), - isWorkspaceOwner: computed(() => userStore.state.currentRoleInfo?.roleType === ROLE_TYPE.WORKSPACE_OWNER), -}); -const { getControlMenuItems } = useDashboardControlMenuItems({ - isAdminMode: computed(() => storeState.isAdminMode), - isWorkspaceOwner: computed(() => storeState.isWorkspaceOwner), -}); const state = reactive({ isPrivate: computed(() => !!dashboard.value?.dashboard_id.startsWith('private')), isSharedDashboard: computed(() => !!dashboard.value?.shared), - sharedScope: computed(() => dashboard.value?.scope), - selectedSharedScope: 'WORKSPACE' as DashboardScope, showBadge: computed(() => { if (dashboard.value?.user_id) return true; return state.isSharedDashboard; @@ -90,43 +54,46 @@ const state = reactive({ badgeType: computed(() => 'subtle'), badgeStyleType: computed(() => { if (state.isPrivate) return 'gray150'; - if (state.sharedScope === 'PROJECT') return 'primary3'; return 'indigo100'; }), badgeText: computed(() => { if (props.dashboardId?.startsWith('private')) return i18n.t('DASHBOARDS.ALL_DASHBOARDS.PRIVATE'); if (state.isSharedDashboard) { - if (storeState.isAdminMode) { + if (entryPoint.value === 'ADMIN') { if (dashboard.value?.scope === 'PROJECT') { return i18n.t('DASHBOARDS.DETAIL.SHARED_TO_ALL_PROJECTS'); } return i18n.t('DASHBOARDS.DETAIL.SHARED_TO_WORKSPACES'); } - if (state.sharedScope === 'PROJECT') return i18n.t('DASHBOARDS.DETAIL.SHARED_TO_ALL_PROJECTS'); - return i18n.t('DASHBOARDS.DETAIL.SHARED_BY_ADMIN'); + if (entryPoint.value === 'WORKSPACE') { + return i18n.t('DASHBOARDS.DETAIL.SHARED_BY_ADMIN'); + } + if (entryPoint.value === 'PROJECT') { + if (dashboard.value?.resource_group === RESOURCE_GROUP.DOMAIN) return i18n.t('DASHBOARDS.DETAIL.SHARED_BY_ADMIN'); + return i18n.t('DASHBOARDS.DETAIL.SHARED_BY_WORKSPACE'); + } } return ''; }), folderName: computed(() => { - const _folderId = queryState.allDashboardItems.find((d) => d.dashboard_id === props.dashboardId)?.folder_id; - const folder = queryState.allFolderItems.find((d) => d.folder_id === _folderId); - return folder?.name; + if (props.folderItems.length > 0) { + const currentFolder = props.folderItems.find((d) => d.folder_id === dashboard.value?.folder_id); + return currentFolder?.name; + } + return undefined; }), }); /* Event */ const handleSelectItem = (item: MenuItem) => { - if (item.name === 'edit') dashboardPageControlStore.openEditNameModal(props.dashboardId); - if (item.name === 'clone') dashboardPageControlStore.openCloneModal(props.dashboardId); - if (item.name === 'move') dashboardPageControlStore.openMoveModal(props.dashboardId); - if (item.name === 'share') dashboardPageControlStore.openShareModal(props.dashboardId); - if (item.name === 'shareWithCode') dashboardPageControlStore.openShareWithCodeModal(props.dashboardId); - if (item.name === 'delete') dashboardPageControlStore.openDeleteModal(props.dashboardId); + emit('select-toolset', item.name); }; From 1f486096019c5f6328b6116a2095a10adc063808 Mon Sep 17 00:00:00 2001 From: Yongtae Park Date: Thu, 10 Apr 2025 15:45:39 +0900 Subject: [PATCH 03/11] feat(dashboard-shared-actions): create dashboard shared actions Signed-off-by: samuel.park --- .../use-dashboard-change-folder-action.ts | 55 ++++++++ .../actions/use-dashboard-clone-action.ts | 118 ++++++++++++++++++ .../actions/use-dashboard-delete-action.ts | 55 ++++++++ .../use-dashboard-folder-share-action.ts | 77 ++++++++++++ .../actions/use-dashboard-share-action.ts | 50 ++++++++ .../actions/use-dashboard-update-action.ts | 55 ++++++++ 6 files changed, 410 insertions(+) create mode 100644 apps/web/src/services/dashboard-shared/actions/use-dashboard-change-folder-action.ts create mode 100644 apps/web/src/services/dashboard-shared/actions/use-dashboard-clone-action.ts create mode 100644 apps/web/src/services/dashboard-shared/actions/use-dashboard-delete-action.ts create mode 100644 apps/web/src/services/dashboard-shared/actions/use-dashboard-folder-share-action.ts create mode 100644 apps/web/src/services/dashboard-shared/actions/use-dashboard-share-action.ts create mode 100644 apps/web/src/services/dashboard-shared/actions/use-dashboard-update-action.ts diff --git a/apps/web/src/services/dashboard-shared/actions/use-dashboard-change-folder-action.ts b/apps/web/src/services/dashboard-shared/actions/use-dashboard-change-folder-action.ts new file mode 100644 index 0000000000..c71023334c --- /dev/null +++ b/apps/web/src/services/dashboard-shared/actions/use-dashboard-change-folder-action.ts @@ -0,0 +1,55 @@ +import type { ComputedRef } from 'vue'; +import { computed } from 'vue'; + +import { + useMutation, useQueryClient, +} from '@tanstack/vue-query'; + +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'; + +interface UseDashboardChangeFolderActionOptions { + dashboardId: ComputedRef; + onSuccess?: (data: DashboardModel, variables: DashboardChangeFolderParams) => void|Promise; + onError?: (error: Error, variables: DashboardChangeFolderParams) => void|Promise; + onSettled?: (data: DashboardModel|undefined, error: Error|null, variables: DashboardChangeFolderParams) => void|Promise; +} + +export const useDashboardChangeFolderAction = (options: UseDashboardChangeFolderActionOptions) => { + const { publicDashboardAPI } = usePublicDashboardApi(); + const { privateDashboardAPI } = usePrivateDashboardApi(); + const queryClient = useQueryClient(); + const { withSuffix: publicDashboardGetQueryKey } = useServiceQueryKey('dashboard', 'public-dashboard', 'get'); + const { withSuffix: privateDashboardGetQueryKey } = useServiceQueryKey('dashboard', 'private-dashboard', 'get'); + + const { + dashboardId, onSuccess, onError, onSettled, + } = options; + + const isPrivate = computed(() => dashboardId.value?.startsWith('private')); + + const changeFolderFn = (params: DashboardChangeFolderParams): Promise => { + if (!dashboardId.value) throw new Error('Dashboard ID is not provided'); + const fetcher = isPrivate.value ? privateDashboardAPI.changeFolder : publicDashboardAPI.changeFolder; + return fetcher(params); + }; + + return useMutation({ + mutationFn: changeFolderFn, + onSuccess: async (data, variables) => { + const _dashboardId = variables.dashboard_id; + const _isPrivate = _dashboardId.startsWith('private'); + const dashboardListQueryKey = _isPrivate ? privateDashboardGetQueryKey(_dashboardId) : publicDashboardGetQueryKey(_dashboardId); + queryClient.invalidateQueries({ queryKey: dashboardListQueryKey }); + if (onSuccess) await onSuccess(data, variables); + }, + onError: (error, variables) => { + if (onError) onError(error, variables); + }, + onSettled: (data, error, variables) => { + if (onSettled) onSettled(data, error, variables); + }, + }); +}; diff --git a/apps/web/src/services/dashboard-shared/actions/use-dashboard-clone-action.ts b/apps/web/src/services/dashboard-shared/actions/use-dashboard-clone-action.ts new file mode 100644 index 0000000000..b13e1b430d --- /dev/null +++ b/apps/web/src/services/dashboard-shared/actions/use-dashboard-clone-action.ts @@ -0,0 +1,118 @@ +import type { ComputedRef } from 'vue'; + +import { useMutation } from '@tanstack/vue-query'; + +import type { ListResponse } from '@/api-clients/_common/schema/api-verbs/list'; +import { RESOURCE_GROUP } from '@/api-clients/_common/schema/constant'; +import type { DashboardCreateParams, DashboardModel } from '@/api-clients/dashboard/_types/dashboard-type'; +import type { WidgetModel } from '@/api-clients/dashboard/_types/widget-type'; +import { usePrivateDashboardApi } from '@/api-clients/dashboard/private-dashboard/composables/use-private-dashboard-api'; +import type { PrivateDashboardCreateParameters } from '@/api-clients/dashboard/private-dashboard/schema/api-verbs/create'; +import { usePrivateWidgetApi } from '@/api-clients/dashboard/private-widget/composables/use-private-widget-api'; +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 { usePublicWidgetApi } from '@/api-clients/dashboard/public-widget/composables/use-public-widget-api'; + +import { useAllReferenceStore } from '@/store/reference/all-reference-store'; +import { useUserStore } from '@/store/user/user-store'; + +import { useDashboardRouteContext } from '@/services/dashboard-shared/_composables/use-dashboard-route-context'; +import { getSharedDashboardLayouts } from '@/services/dashboard-shared/helpers/dashboard-share-helper'; + + +interface UseDashboardCloneActionOptions { + dashboardId: ComputedRef; + isPrivate?: ComputedRef; + onSuccess?: (data: DashboardModel, variables: DashboardCreateParams) => void|Promise; + onError?: (error: Error, variables: DashboardCreateParams) => void|Promise; + onSettled?: (data: DashboardModel|undefined, error: Error|null, variables: DashboardCreateParams) => void|Promise; +} + +export const useDashboardCloneAction = (options: UseDashboardCloneActionOptions) => { + const { publicDashboardAPI } = usePublicDashboardApi(); + const { privateDashboardAPI } = usePrivateDashboardApi(); + const { privateWidgetAPI } = usePrivateWidgetApi(); + const { publicWidgetAPI } = usePublicWidgetApi(); + const allReferenceStore = useAllReferenceStore(); + const userStore = useUserStore(); + const { + entryPoint, + } = useDashboardRouteContext(); + + const { + dashboardId, isPrivate, onSuccess, onError, onSettled, + } = options; + + + const getDashboardFn = async (): Promise => { + if (!dashboardId.value) throw new Error('Dashboard id not found'); + try { + if (dashboardId.value.startsWith('private')) { + return privateDashboardAPI.get({ dashboard_id: dashboardId.value }); + } + return publicDashboardAPI.get({ dashboard_id: dashboardId.value }); + } catch (error) { + throw new Error('Dashboard not found'); + } + }; + + const listWidgetFn = async (): Promise> => { + if (!dashboardId.value) throw new Error('Dashboard id not found'); + try { + if (dashboardId.value.startsWith('private')) { + return privateWidgetAPI.list({ dashboard_id: dashboardId.value }); + } + return publicWidgetAPI.list({ dashboard_id: dashboardId.value }); + } catch (error) { + throw new Error('Widget list not found'); + } + }; + + + const cloneDashboardFn = async (params: DashboardCreateParams): Promise => { + if (!dashboardId.value) throw new Error('Dashboard id not found'); + + const dashboard = await getDashboardFn(); + const widgetList = await listWidgetFn(); + + const _sharedLayouts = await getSharedDashboardLayouts(dashboard.layouts, widgetList.results || [], allReferenceStore.getters.costDataSource); + + const _sharedDashboard: DashboardCreateParams = { + name: params.name, + layouts: _sharedLayouts, + options: dashboard.options || {}, + labels: dashboard.labels || [], + tags: { created_by: userStore.state.userId }, + vars: dashboard.vars, + vars_schema: dashboard.vars_schema, + }; + if (entryPoint.value === 'ADMIN') { + (_sharedDashboard as PublicDashboardCreateParameters).resource_group = RESOURCE_GROUP.DOMAIN; + } else if (entryPoint.value === 'WORKSPACE') { + if (!isPrivate?.value) { + (_sharedDashboard as PublicDashboardCreateParameters).resource_group = RESOURCE_GROUP.WORKSPACE; + } + } else if (entryPoint.value === 'PROJECT') { + (_sharedDashboard as PublicDashboardCreateParameters).resource_group = RESOURCE_GROUP.PROJECT; + } + + + if (isPrivate?.value) { + return privateDashboardAPI.create(_sharedDashboard as PrivateDashboardCreateParameters); + } + return publicDashboardAPI.create(_sharedDashboard as PublicDashboardCreateParameters); + }; + + return useMutation({ + mutationFn: cloneDashboardFn, + onSuccess: async (data, variables) => { + if (onSuccess) await onSuccess(data, variables); + }, + onError: (error, variables) => { + if (onError) onError(error, variables); + }, + onSettled: (data, error, variables) => { + if (onSettled) onSettled(data, error, variables); + }, + }); +}; diff --git a/apps/web/src/services/dashboard-shared/actions/use-dashboard-delete-action.ts b/apps/web/src/services/dashboard-shared/actions/use-dashboard-delete-action.ts new file mode 100644 index 0000000000..6c94ab2e84 --- /dev/null +++ b/apps/web/src/services/dashboard-shared/actions/use-dashboard-delete-action.ts @@ -0,0 +1,55 @@ +import type { ComputedRef } from 'vue'; +import { computed } from 'vue'; + +import { + useMutation, useQueryClient, +} from '@tanstack/vue-query'; + +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'; + +interface UseDashboardDeleteActionOptions { + dashboardId: ComputedRef; + onSuccess?: (data: unknown, variables: DashboardDeleteParams) => void|Promise; + onError?: (error: Error, variables: DashboardDeleteParams) => void|Promise; + onSettled?: (data: unknown|undefined, error: Error|null, variables: DashboardDeleteParams) => void|Promise; +} + +export const useDashboardDeleteAction = (options: UseDashboardDeleteActionOptions) => { + const { publicDashboardAPI } = usePublicDashboardApi(); + const { privateDashboardAPI } = usePrivateDashboardApi(); + const queryClient = useQueryClient(); + const { withSuffix: publicDashboardGetQueryKey } = useServiceQueryKey('dashboard', 'public-dashboard', 'get'); + const { withSuffix: privateDashboardGetQueryKey } = useServiceQueryKey('dashboard', 'private-dashboard', 'get'); + + const { + dashboardId, onSuccess, onError, onSettled, + } = options; + + const isPrivate = computed(() => dashboardId.value?.startsWith('private')); + + const deleteDashboardFn = (params: DashboardDeleteParams) => { + if (!dashboardId.value) throw new Error('Dashboard ID is not provided'); + const fetcher = isPrivate.value ? privateDashboardAPI.delete : publicDashboardAPI.delete; + return fetcher(params); + }; + + return useMutation({ + mutationFn: deleteDashboardFn, + onSuccess: async (data, variables) => { + const _dashboardId = variables.dashboard_id; + const _isPrivate = _dashboardId.startsWith('private'); + const dashboardListQueryKey = _isPrivate ? privateDashboardGetQueryKey(_dashboardId) : publicDashboardGetQueryKey(_dashboardId); + queryClient.invalidateQueries({ queryKey: dashboardListQueryKey }); + if (onSuccess) await onSuccess(data, variables); + }, + onError: (error, variables) => { + if (onError) onError(error, variables); + }, + onSettled: (data, error, variables) => { + if (onSettled) onSettled(data, error, variables); + }, + }); +}; diff --git a/apps/web/src/services/dashboard-shared/actions/use-dashboard-folder-share-action.ts b/apps/web/src/services/dashboard-shared/actions/use-dashboard-folder-share-action.ts new file mode 100644 index 0000000000..11d90ac3fa --- /dev/null +++ b/apps/web/src/services/dashboard-shared/actions/use-dashboard-folder-share-action.ts @@ -0,0 +1,77 @@ +import type { ComputedRef } from 'vue'; + +import { + useMutation, useQueryClient, +} from '@tanstack/vue-query'; + +import { usePublicDashboardApi } from '@/api-clients/dashboard/public-dashboard/composables/use-public-dashboard-api'; +import { usePublicFolderApi } from '@/api-clients/dashboard/public-folder/composables/use-public-folder-api'; +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'; + +interface UseDashboardFolderShareActionOptions { + folderId: ComputedRef; + isShared: ComputedRef; + onSuccess?: (data: PublicFolderModel, variables: PublicFolderShareParameters|PublicFolderUnshareParameters) => void|Promise; + onError?: (error: Error, variables: PublicFolderShareParameters|PublicFolderUnshareParameters) => void|Promise; + onSettled?: (data: PublicFolderModel|undefined, error: Error|null, variables: PublicFolderShareParameters|PublicFolderUnshareParameters) => void|Promise; +} + +export const useDashboardFolderShareAction = (options: UseDashboardFolderShareActionOptions) => { + const { publicFolderAPI } = usePublicFolderApi(); + const { publicDashboardAPI } = usePublicDashboardApi(); + const queryClient = useQueryClient(); + const { withSuffix: publicFolderGetQueryKey } = useServiceQueryKey('dashboard', 'public-folder', 'get'); + const { withSuffix: publicDashbordGetQueryKey } = useServiceQueryKey('dashboard', 'public-dashboard', 'get'); + const { + folderId, isShared, onSuccess, onError, onSettled, + } = options; + + + const listPublicDashboard = async () => { + if (!folderId.value) throw new Error('Folder ID is not provided'); + try { + const _dashboardList = await publicDashboardAPI.list({ + folder_id: folderId.value, + }); + return _dashboardList; + } catch (error) { + console.error(error); + return { + results: [], + }; + } + }; + + const shareFolderFn = async (params: PublicFolderShareParameters|PublicFolderUnshareParameters): Promise => { + if (!folderId.value) throw new Error('Folder ID is not provided'); + if (isShared.value) return publicFolderAPI.unshare(params as PublicFolderUnshareParameters); + return publicFolderAPI.share(params as PublicFolderShareParameters); + }; + + return useMutation({ + mutationFn: shareFolderFn, + onSuccess: async (data, variables) => { + const _folderId = variables.folder_id; + queryClient.invalidateQueries({ queryKey: publicFolderGetQueryKey(_folderId) }); + if (!isShared.value) { + const _dashboardList = await listPublicDashboard(); + const _dashboardIds = _dashboardList.results?.map((dashboard) => dashboard.dashboard_id); + if (_dashboardIds) { + await Promise.all( + _dashboardIds.map((dashboardId) => queryClient.invalidateQueries({ queryKey: publicDashbordGetQueryKey(dashboardId) })), + ); + } + } + if (onSuccess) await onSuccess(data, variables); + }, + onError: (error, variables) => { + if (onError) onError(error, variables); + }, + onSettled: (data, error, variables) => { + if (onSettled) onSettled(data, error, variables); + }, + }); +}; diff --git a/apps/web/src/services/dashboard-shared/actions/use-dashboard-share-action.ts b/apps/web/src/services/dashboard-shared/actions/use-dashboard-share-action.ts new file mode 100644 index 0000000000..6b054efe9d --- /dev/null +++ b/apps/web/src/services/dashboard-shared/actions/use-dashboard-share-action.ts @@ -0,0 +1,50 @@ +import type { ComputedRef } from 'vue'; + +import { + useMutation, useQueryClient, +} from '@tanstack/vue-query'; + +import type { DashboardModel } from '@/api-clients/dashboard/_types/dashboard-type'; +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'; + +interface UseDashboardShareActionOptions { + dashboardId: ComputedRef; + isShared: ComputedRef; + onSuccess?: (data: DashboardModel, variables: PublicDashboardShareParameters|PublicDashboardUnshareParameters) => void|Promise; + onError?: (error: Error, variables: PublicDashboardShareParameters|PublicDashboardUnshareParameters) => void|Promise; + onSettled?: (data: DashboardModel|undefined, error: Error|null, variables: PublicDashboardShareParameters|PublicDashboardUnshareParameters) => void|Promise; +} + +export const useDashboardShareAction = (options: UseDashboardShareActionOptions) => { + const { publicDashboardAPI } = usePublicDashboardApi(); + const queryClient = useQueryClient(); + const { withSuffix: publicDashboardGetQueryKey } = useServiceQueryKey('dashboard', 'public-dashboard', 'get'); + + const { + dashboardId, isShared, onSuccess, onError, onSettled, + } = options; + + const shareDashboardFn = (params: PublicDashboardShareParameters|PublicDashboardUnshareParameters): Promise => { + if (!dashboardId.value) throw new Error('Dashboard ID is not provided'); + if (isShared.value) return publicDashboardAPI.unshare(params as PublicDashboardUnshareParameters); + return publicDashboardAPI.share(params as PublicDashboardShareParameters); + }; + + return useMutation({ + mutationFn: shareDashboardFn, + onSuccess: async (data, variables) => { + const _dashboardId = variables.dashboard_id; + queryClient.invalidateQueries({ queryKey: publicDashboardGetQueryKey(_dashboardId) }); + if (onSuccess) await onSuccess(data, variables); + }, + onError: (error, variables) => { + if (onError) onError(error, variables); + }, + onSettled: (data, error, variables) => { + if (onSettled) onSettled(data, error, variables); + }, + }); +}; diff --git a/apps/web/src/services/dashboard-shared/actions/use-dashboard-update-action.ts b/apps/web/src/services/dashboard-shared/actions/use-dashboard-update-action.ts new file mode 100644 index 0000000000..ce89341069 --- /dev/null +++ b/apps/web/src/services/dashboard-shared/actions/use-dashboard-update-action.ts @@ -0,0 +1,55 @@ +import type { ComputedRef } from 'vue'; +import { computed } from 'vue'; + +import { + useMutation, useQueryClient, +} from '@tanstack/vue-query'; + +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'; + +interface UseDashboardUpdateActionOptions { + dashboardId: ComputedRef; + onSuccess?: (data: DashboardModel, variables: DashboardUpdateParams) => void|Promise; + onError?: (error: Error, variables: DashboardUpdateParams) => void|Promise; + onSettled?: (data: DashboardModel|undefined, error: Error|null, variables: DashboardUpdateParams) => void|Promise; +} + +export const useDashboardUpdateAction = (options: UseDashboardUpdateActionOptions) => { + const { publicDashboardAPI } = usePublicDashboardApi(); + const { privateDashboardAPI } = usePrivateDashboardApi(); + const queryClient = useQueryClient(); + const { withSuffix: publicDashboardGetQueryKey } = useServiceQueryKey('dashboard', 'public-dashboard', 'get'); + const { withSuffix: privateDashboardGetQueryKey } = useServiceQueryKey('dashboard', 'private-dashboard', 'get'); + + const { + dashboardId, onSuccess, onError, onSettled, + } = options; + + const isPrivate = computed(() => dashboardId.value?.startsWith('private')); + + const updateDashboardFn = (params: DashboardUpdateParams): Promise => { + if (!dashboardId.value) throw new Error('Dashboard ID is not provided'); + const fetcher = isPrivate.value ? privateDashboardAPI.update : publicDashboardAPI.update; + return fetcher(params); + }; + + return useMutation({ + mutationFn: updateDashboardFn, + onSuccess: async (data, variables) => { + const _dashboardId = variables.dashboard_id; + const _isPrivate = _dashboardId.startsWith('private'); + const dashboardListQueryKey = _isPrivate ? privateDashboardGetQueryKey(_dashboardId) : publicDashboardGetQueryKey(_dashboardId); + queryClient.invalidateQueries({ queryKey: dashboardListQueryKey }); + if (onSuccess) await onSuccess(data, variables); + }, + onError: (error, variables) => { + if (onError) onError(error, variables); + }, + onSettled: (data, error, variables) => { + if (onSettled) onSettled(data, error, variables); + }, + }); +}; From af7b63b8877ada041f1551796adf6041d9d62b8e Mon Sep 17 00:00:00 2001 From: Yongtae Park Date: Thu, 10 Apr 2025 15:46:20 +0900 Subject: [PATCH 04/11] chore: add missing field to dashboard list verb schema Signed-off-by: samuel.park --- .../dashboard/private-dashboard/schema/api-verbs/list.ts | 1 + .../dashboard/public-dashboard/schema/api-verbs/list.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/web/src/api-clients/dashboard/private-dashboard/schema/api-verbs/list.ts b/apps/web/src/api-clients/dashboard/private-dashboard/schema/api-verbs/list.ts index 2656ae2b91..ab0ab87fd6 100644 --- a/apps/web/src/api-clients/dashboard/private-dashboard/schema/api-verbs/list.ts +++ b/apps/web/src/api-clients/dashboard/private-dashboard/schema/api-verbs/list.ts @@ -5,4 +5,5 @@ export interface PrivateDashboardListParameters { query?: Query; dashboard_id?: string; name?: string; + folder_id?: string; } diff --git a/apps/web/src/api-clients/dashboard/public-dashboard/schema/api-verbs/list.ts b/apps/web/src/api-clients/dashboard/public-dashboard/schema/api-verbs/list.ts index 3987bb3b5f..8fe6638e86 100644 --- a/apps/web/src/api-clients/dashboard/public-dashboard/schema/api-verbs/list.ts +++ b/apps/web/src/api-clients/dashboard/public-dashboard/schema/api-verbs/list.ts @@ -8,4 +8,5 @@ export interface PublicDashboardListParameters { workspace_id?: string; project_id?: string; project_group_id?: string; + folder_id?: string; } From 19b6c19ca72fa5624dcef23375d61cc6d182b022 Mon Sep 17 00:00:00 2001 From: Yongtae Park Date: Thu, 10 Apr 2025 15:47:35 +0900 Subject: [PATCH 05/11] fix(dashboard-actions-modal): apply dashboard-shared actions to dashboard service UI Signed-off-by: samuel.park --- .../components/DashboardDeleteModal.vue | 68 ++++++------- .../dashboard-detail/DashboardCloneModal.vue | 98 +++++-------------- .../DashboardFolderSingleMoveModal.vue | 52 +++------- .../DashboardNameEditModal.vue | 42 +++----- .../DashboardShareWithCodeModal.vue | 2 +- .../DashboardBundleCloneModal.vue | 2 +- .../DashboardBundleShareModal.vue | 35 +++---- 7 files changed, 98 insertions(+), 201 deletions(-) diff --git a/apps/web/src/services/dashboards/components/DashboardDeleteModal.vue b/apps/web/src/services/dashboards/components/DashboardDeleteModal.vue index da243f1dce..21a1c3a518 100644 --- a/apps/web/src/services/dashboards/components/DashboardDeleteModal.vue +++ b/apps/web/src/services/dashboards/components/DashboardDeleteModal.vue @@ -2,10 +2,8 @@ import { computed, reactive } from 'vue'; import { useRouter } from 'vue-router/composables'; -import { useMutation, useQueryClient } from '@tanstack/vue-query'; +import { useQueryClient } from '@tanstack/vue-query'; -import type { PrivateDashboardDeleteParameters } from '@/api-clients/dashboard/private-dashboard/schema/api-verbs/delete'; -import type { PublicDashboardDeleteParameters } from '@/api-clients/dashboard/public-dashboard/schema/api-verbs/delete'; import { i18n } from '@/translations'; import { useAppContextStore } from '@/store/app-context/app-context-store'; @@ -19,6 +17,7 @@ import { FAVORITE_TYPE } from '@/common/modules/favorites/favorite-button/type'; import { useRecentStore } from '@/common/modules/navigations/stores/recent-store'; import { RECENT_TYPE } from '@/common/modules/navigations/type'; +import { useDashboardDeleteAction } from '@/services/dashboard-shared/actions/use-dashboard-delete-action'; import { useDashboardQuery } from '@/services/dashboards/composables/use-dashboard-query'; import { ADMIN_DASHBOARDS_ROUTE } from '@/services/dashboards/routes/admin/route-constant'; import { DASHBOARDS_ROUTE } from '@/services/dashboards/routes/route-constant'; @@ -43,7 +42,6 @@ const appContextStore = useAppContextStore(); const router = useRouter(); /* Query */ const { - api, keys, } = useDashboardQuery(); const queryClient = useQueryClient(); @@ -68,44 +66,34 @@ const handleDeleteDashboardConfirm = async () => { }; /* Api */ -const deleteDashboardFn = (params: PublicDashboardDeleteParameters|PrivateDashboardDeleteParameters): Promise => { - const _isPrivate = params.dashboard_id.startsWith('private'); - if (_isPrivate) { - return api.privateDashboardAPI.delete(params as PrivateDashboardDeleteParameters); - } - return api.publicDashboardAPI.delete(params as PublicDashboardDeleteParameters); -}; - -const { mutate: deleteDashboard, isPending: loading } = useMutation( - { - mutationFn: deleteDashboardFn, - onSuccess: async (_, params) => { - const _isPrivate = params.dashboard_id.startsWith('private'); - const dashboardListQueryKey = _isPrivate ? keys.privateDashboardListQueryKey : keys.publicDashboardListQueryKey; - await queryClient.invalidateQueries({ queryKey: dashboardListQueryKey.value }); - await recentStore.deleteRecent({ - type: RECENT_TYPE.DASHBOARD, - itemId: props.dashboardId, +const { mutate: deleteDashboard, isPending: loading } = useDashboardDeleteAction({ + dashboardId: computed(() => props.dashboardId), + onSuccess: async (_, params) => { + const _isPrivate = params.dashboard_id.startsWith('private'); + const dashboardListQueryKey = _isPrivate ? keys.privateDashboardListQueryKey : keys.publicDashboardListQueryKey; + await queryClient.invalidateQueries({ queryKey: dashboardListQueryKey.value }); + await recentStore.deleteRecent({ + type: RECENT_TYPE.DASHBOARD, + itemId: props.dashboardId, + }); + const isFavoriteItem = favoriteGetters.dashboardItems.find((item) => item.itemId === params.dashboard_id); + if (isFavoriteItem) { + await favoriteStore.deleteFavorite({ + itemType: FAVORITE_TYPE.DASHBOARD, + workspaceId: storeState.currentWorkspaceId || '', + itemId: params.dashboard_id, }); - const isFavoriteItem = favoriteGetters.dashboardItems.find((item) => item.itemId === params.dashboard_id); - if (isFavoriteItem) { - await favoriteStore.deleteFavorite({ - itemType: FAVORITE_TYPE.DASHBOARD, - workspaceId: storeState.currentWorkspaceId || '', - itemId: params.dashboard_id, - }); - } - state.proxyVisible = false; - const dashboardRouteName = storeState.isAdminMode - ? ADMIN_DASHBOARDS_ROUTE._NAME - : DASHBOARDS_ROUTE._NAME; - await router.replace({ name: dashboardRouteName }).catch(() => {}); - }, - onError(error) { - ErrorHandler.handleRequestError(error, i18n.t('DASHBOARDS.FORM.ALT_E_DELETE_DASHBOARD')); - }, + } + state.proxyVisible = false; + const dashboardRouteName = storeState.isAdminMode + ? ADMIN_DASHBOARDS_ROUTE._NAME + : DASHBOARDS_ROUTE._NAME; + await router.replace({ name: dashboardRouteName }).catch(() => {}); }, -); + onError: (error) => { + ErrorHandler.handleRequestError(error, i18n.t('DASHBOARDS.FORM.ALT_E_DELETE_DASHBOARD')); + }, +}); diff --git a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardCloneModal.vue b/apps/web/src/services/dashboards/components/dashboard-detail/DashboardCloneModal.vue index af97f93af3..6c8fda480d 100644 --- a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardCloneModal.vue +++ b/apps/web/src/services/dashboards/components/dashboard-detail/DashboardCloneModal.vue @@ -2,20 +2,14 @@ import { computed, reactive, watch } from 'vue'; import { useRouter } from 'vue-router/composables'; -import { useMutation, useQueryClient } from '@tanstack/vue-query'; +import { useQueryClient } from '@tanstack/vue-query'; import { PButtonModal, PFieldGroup, PTextInput, PToggleButton, } from '@cloudforet/mirinae'; import { getClonedName } from '@cloudforet/utils'; -import { RESOURCE_GROUP } from '@/api-clients/_common/schema/constant'; import type { DashboardCreateParams, DashboardModel, DashboardType } from '@/api-clients/dashboard/_types/dashboard-type'; -import type { WidgetModel } from '@/api-clients/dashboard/_types/widget-type'; -import type { PrivateDashboardCreateParameters } from '@/api-clients/dashboard/private-dashboard/schema/api-verbs/create'; -import { usePrivateWidgetApi } from '@/api-clients/dashboard/private-widget/composables/use-private-widget-api'; -import type { PublicDashboardCreateParameters } from '@/api-clients/dashboard/public-dashboard/schema/api-verbs/create'; -import { usePublicWidgetApi } from '@/api-clients/dashboard/public-widget/composables/use-public-widget-api'; import { ROLE_TYPE } from '@/api-clients/identity/role/constant'; import { i18n } from '@/translations'; @@ -26,12 +20,11 @@ import { useUserStore } from '@/store/user/user-store'; import { showErrorMessage } from '@/lib/helper/notice-alert-helper'; -import ErrorHandler from '@/common/composables/error/errorHandler'; import { useFormValidator } from '@/common/composables/form-validator'; import { useProxyValue } from '@/common/composables/proxy-state'; +import { useDashboardCloneAction } from '@/services/dashboard-shared/actions/use-dashboard-clone-action'; import { useDashboardQuery } from '@/services/dashboards/composables/use-dashboard-query'; -import { getSharedDashboardLayouts } from '@/services/dashboards/helpers/dashboard-share-helper'; import { ADMIN_DASHBOARDS_ROUTE } from '@/services/dashboards/routes/admin/route-constant'; import { DASHBOARDS_ROUTE } from '@/services/dashboards/routes/route-constant'; @@ -47,15 +40,12 @@ const props = withDefaults(defineProps(), { }); const emit = defineEmits<{(e: 'update:visible', value: boolean): void; }>(); -const { privateWidgetAPI } = usePrivateWidgetApi(); -const { publicWidgetAPI } = usePublicWidgetApi(); /* Query */ const { publicDashboardList, privateDashboardList, keys, - api, } = useDashboardQuery(); const queryClient = useQueryClient(); @@ -111,22 +101,6 @@ const state = reactive({ }), }); -/* Api */ -const listDashboardWidgets = async (dashboardId: string): Promise => { - try { - const isPrivate = dashboardId.startsWith('private'); - const fetcher = isPrivate - ? privateWidgetAPI.list - : publicWidgetAPI.list; - const res = await fetcher({ - dashboard_id: dashboardId, - }); - return res.results || []; - } catch (e) { - ErrorHandler.handleError(e); - return []; - } -}; const handleUpdateVisible = (visible) => { state.proxyVisible = visible; @@ -138,62 +112,42 @@ const handleChangePrivate = (val: boolean) => { }; const handleConfirm = async () => { if (!isAllValid) return; - const _dashboardWidgets = await listDashboardWidgets(props.dashboardId); - const _sharedLayouts = await getSharedDashboardLayouts(state.targetDashboard.layouts, _dashboardWidgets, storeState.costDataSource); const _sharedDashboard: DashboardCreateParams = { name: name.value, - layouts: _sharedLayouts, - options: state.targetDashboard.options || {}, - labels: state.targetDashboard.labels || [], - tags: { created_by: userStore.state.userId }, - vars: state.targetDashboard.vars, - vars_schema: state.targetDashboard.vars_schema, }; if (storeState.isWorkspaceMember) { state.isPrivate = true; } else if (storeState.isAdminMode) { state.isPrivate = false; - (_sharedDashboard as PublicDashboardCreateParameters).resource_group = RESOURCE_GROUP.DOMAIN; - } else if (!state.isPrivate) { - (_sharedDashboard as PublicDashboardCreateParameters).resource_group = state.targetDashboard?.resource_group || RESOURCE_GROUP.WORKSPACE; - } - - mutate(_sharedDashboard as PrivateDashboardCreateParameters); -}; - -const createDashboard = (params: DashboardCreateParams): Promise => { - if (state.isPrivate) { - return api.privateDashboardAPI.create(params as PrivateDashboardCreateParameters); } - return api.publicDashboardAPI.create(params as PublicDashboardCreateParameters); + mutate(_sharedDashboard as DashboardCreateParams); }; -const { mutate, isPending: dashboardCloneLoading } = useMutation( - { - mutationFn: createDashboard, - onSuccess: (dashboard: DashboardModel) => { - const isPrivate = dashboard.dashboard_id.startsWith('private'); - const dashboardListQueryKey = isPrivate ? keys.privateDashboardListQueryKey : keys.publicDashboardListQueryKey; - queryClient.invalidateQueries({ queryKey: dashboardListQueryKey.value }); - }, - onError: (e) => { - showErrorMessage(i18n.t('DASHBOARDS.FORM.ALT_E_CLONE_DASHBOARD'), e); - }, - onSettled(data) { - state.proxyVisible = false; - if (data?.dashboard_id) { - const dashboardDetailRouteName = storeState.isAdminMode - ? ADMIN_DASHBOARDS_ROUTE.DETAIL._NAME - : DASHBOARDS_ROUTE.DETAIL._NAME; - router.push({ - name: dashboardDetailRouteName, - params: { dashboardId: data.dashboard_id }, - }).catch(() => {}); - } - }, +const { mutate, isPending: dashboardCloneLoading } = useDashboardCloneAction({ + dashboardId: computed(() => props.dashboardId), + isPrivate: computed(() => state.isPrivate), + onSuccess: (dashboard: DashboardModel) => { + const isPrivate = dashboard.dashboard_id.startsWith('private'); + const dashboardListQueryKey = isPrivate ? keys.privateDashboardListQueryKey : keys.publicDashboardListQueryKey; + queryClient.invalidateQueries({ queryKey: dashboardListQueryKey.value }); + }, + onError: (e) => { + showErrorMessage(i18n.t('DASHBOARDS.FORM.ALT_E_CLONE_DASHBOARD'), e); }, -); + onSettled(data) { + state.proxyVisible = false; + if (data?.dashboard_id) { + const dashboardDetailRouteName = storeState.isAdminMode + ? ADMIN_DASHBOARDS_ROUTE.DETAIL._NAME + : DASHBOARDS_ROUTE.DETAIL._NAME; + router.push({ + name: dashboardDetailRouteName, + params: { dashboardId: data.dashboard_id }, + }).catch(() => {}); + } + }, +}); watch(() => props.visible, (visible) => { if (visible) { diff --git a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardFolderSingleMoveModal.vue b/apps/web/src/services/dashboards/components/dashboard-detail/DashboardFolderSingleMoveModal.vue index be1ebd481e..12b2df5ac1 100644 --- a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardFolderSingleMoveModal.vue +++ b/apps/web/src/services/dashboards/components/dashboard-detail/DashboardFolderSingleMoveModal.vue @@ -3,7 +3,7 @@ import { computed, reactive, watch, } from 'vue'; -import { useMutation, useQueryClient } from '@tanstack/vue-query'; +import { useQueryClient } from '@tanstack/vue-query'; import { PButtonModal, PFieldGroup, PI, PSelectDropdown, @@ -11,14 +11,6 @@ import { import type { SelectDropdownMenuItem } from '@cloudforet/mirinae/types/controls/dropdown/select-dropdown/type'; import type { DashboardChangeFolderParams } from '@/api-clients/dashboard/_types/dashboard-type'; -import type { - PrivateDashboardChangeFolderParameters, -} from '@/api-clients/dashboard/private-dashboard/schema/api-verbs/change-folder'; -import type { PrivateFolderModel } from '@/api-clients/dashboard/private-folder/schema/model'; -import type { - PublicDashboardChangeFolderParameters, -} from '@/api-clients/dashboard/public-dashboard/schema/api-verbs/change-folder'; -import type { PublicFolderModel } from '@/api-clients/dashboard/public-folder/schema/model'; import { i18n } from '@/translations'; import { useAppContextStore } from '@/store/app-context/app-context-store'; @@ -27,14 +19,12 @@ import { showErrorMessage, showSuccessMessage } from '@/lib/helper/notice-alert- import { useProxyValue } from '@/common/composables/proxy-state'; +import { useDashboardChangeFolderAction } from '@/services/dashboard-shared/actions/use-dashboard-change-folder-action'; import { useDashboardFolderQuery } from '@/services/dashboards/composables/use-dashboard-folder-query'; import { useDashboardQuery } from '@/services/dashboards/composables/use-dashboard-query'; import { useDashboardPageControlStore } from '@/services/dashboards/stores/dashboard-page-control-store'; - - -type DashboardModel = PrivateFolderModel | PublicFolderModel; interface Props { visible: boolean; dashboardId: string; @@ -52,7 +42,6 @@ const dashboardPageControlStore = useDashboardPageControlStore(); const queryClient = useQueryClient(); const { keys, - api, } = useDashboardQuery(); const { publicFolderList, @@ -95,30 +84,21 @@ const state = reactive({ }); /* Api */ -const changeFolderFn = (params: PrivateDashboardChangeFolderParameters | PublicDashboardChangeFolderParameters): Promise => { - const _isPrivate = props.dashboardId.startsWith('private'); - if (_isPrivate) { - return api.privateDashboardAPI.changeFolder(params as PrivateDashboardChangeFolderParameters); - } - return api.publicDashboardAPI.changeFolder(params as PublicDashboardChangeFolderParameters); -}; -const { mutate: changeFolder } = useMutation( - { - mutationFn: changeFolderFn, - onSuccess: () => { - showSuccessMessage(i18n.t('DASHBOARDS.DETAIL.ALT_S_MOVE_DASHBOARD'), ''); - const isPrivate = props.dashboardId.startsWith('private'); - const dashboardListQueryKey = isPrivate ? keys.privateDashboardListQueryKey : keys.publicDashboardListQueryKey; - queryClient.invalidateQueries({ queryKey: dashboardListQueryKey.value }); - }, - onError: (e) => { - showErrorMessage(i18n.t('DASHBOARDS.DETAIL.ALT_E_MOVE_DASHBOARD'), e); - }, - onSettled: () => { - state.proxyVisible = false; - }, +const { mutate: changeFolder } = useDashboardChangeFolderAction({ + dashboardId: computed(() => props.dashboardId), + onSuccess: () => { + showSuccessMessage(i18n.t('DASHBOARDS.DETAIL.ALT_S_MOVE_DASHBOARD'), ''); + const isPrivate = props.dashboardId.startsWith('private'); + const dashboardListQueryKey = isPrivate ? keys.privateDashboardListQueryKey : keys.publicDashboardListQueryKey; + queryClient.invalidateQueries({ queryKey: dashboardListQueryKey.value }); }, -); + onError: (e) => { + showErrorMessage(i18n.t('DASHBOARDS.DETAIL.ALT_E_MOVE_DASHBOARD'), e); + }, + onSettled: () => { + state.proxyVisible = false; + }, +}); /* Event */ const handleFormConfirm = async () => { diff --git a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardNameEditModal.vue b/apps/web/src/services/dashboards/components/dashboard-detail/DashboardNameEditModal.vue index 44e79eaf5a..175549d945 100644 --- a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardNameEditModal.vue +++ b/apps/web/src/services/dashboards/components/dashboard-detail/DashboardNameEditModal.vue @@ -4,7 +4,7 @@ import { } from 'vue'; import { useRoute } from 'vue-router/composables'; -import { useMutation, useQueryClient } from '@tanstack/vue-query'; +import { useQueryClient } from '@tanstack/vue-query'; import { PButtonModal, PFieldGroup, PTextInput } from '@cloudforet/mirinae'; @@ -17,7 +17,7 @@ import ErrorHandler from '@/common/composables/error/errorHandler'; import { useFormValidator } from '@/common/composables/form-validator'; import { useProxyValue } from '@/common/composables/proxy-state'; -import { useDashboardGetQuery } from '@/services/dashboard-shared/dashboard-detail/composables/use-dashboard-get-query'; +import { useDashboardUpdateAction } from '@/services/dashboard-shared/actions/use-dashboard-update-action'; import { useDashboardQuery } from '@/services/dashboards/composables/use-dashboard-query'; @@ -41,12 +41,6 @@ const { keys, } = useDashboardQuery(); const queryClient = useQueryClient(); -const { - fetcher, - keys: dashboardDetailKeys, -} = useDashboardGetQuery({ - dashboardId: computed(() => props.dashboardId), -}); const route = useRoute(); const appContextStore = useAppContextStore(); @@ -92,26 +86,20 @@ const state = reactive({ isDetailPage: computed(() => route.params.dashboardId !== undefined), }); -const { mutate, isPending: loading } = useMutation( - { - mutationFn: fetcher.updateDashboardFn, - onSuccess: (_dashboard: DashboardModel) => { - const isPrivate = _dashboard.dashboard_id.startsWith('private'); - const dashboardListQueryKey = isPrivate ? keys.privateDashboardListQueryKey : keys.publicDashboardListQueryKey; - queryClient.invalidateQueries({ queryKey: dashboardListQueryKey.value }); - if (state.isDetailPage) { - const dashboardQueryKey = isPrivate ? dashboardDetailKeys.privateDashboardGetQueryKey : dashboardDetailKeys.publicDashboardGetQueryKey; - queryClient.invalidateQueries({ queryKey: dashboardQueryKey.value }); - } - }, - onError: (e) => { - ErrorHandler.handleRequestError(e, i18n.t('DASHBOARDS.FORM.ALT_E_EDIT_NAME')); - }, - onSettled() { - state.proxyVisible = false; - }, +const { mutate, isPending: loading } = useDashboardUpdateAction({ + dashboardId: computed(() => props.dashboardId), + onSuccess: (_dashboard: DashboardModel) => { + const isPrivate = _dashboard.dashboard_id.startsWith('private'); + const dashboardListQueryKey = isPrivate ? keys.privateDashboardListQueryKey : keys.publicDashboardListQueryKey; + queryClient.invalidateQueries({ queryKey: dashboardListQueryKey.value }); }, -); + onError: (e) => { + ErrorHandler.handleRequestError(e, i18n.t('DASHBOARDS.FORM.ALT_E_EDIT_NAME')); + }, + onSettled() { + state.proxyVisible = false; + }, +}); const handleConfirm = async () => { mutate({ diff --git a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardShareWithCodeModal.vue b/apps/web/src/services/dashboards/components/dashboard-detail/DashboardShareWithCodeModal.vue index e3aff72129..05f5025b96 100644 --- a/apps/web/src/services/dashboards/components/dashboard-detail/DashboardShareWithCodeModal.vue +++ b/apps/web/src/services/dashboards/components/dashboard-detail/DashboardShareWithCodeModal.vue @@ -18,8 +18,8 @@ import { copyAnyData } from '@/lib/helper/copy-helper'; import { useProxyValue } from '@/common/composables/proxy-state'; import { useDashboardWidgetListQuery } from '@/services/dashboard-shared/dashboard-detail/composables/use-dashboard-widget-list-query'; +import { getSharedDashboardLayouts } from '@/services/dashboard-shared/helpers/dashboard-share-helper'; import { useDashboardQuery } from '@/services/dashboards/composables/use-dashboard-query'; -import { getSharedDashboardLayouts } from '@/services/dashboards/helpers/dashboard-share-helper'; import type { SharedDashboardInfo } from '@/services/dashboards/types/shared-dashboard-type'; diff --git a/apps/web/src/services/dashboards/components/dashboard-main/DashboardBundleCloneModal.vue b/apps/web/src/services/dashboards/components/dashboard-main/DashboardBundleCloneModal.vue index 8708e301c6..550ce850ad 100644 --- a/apps/web/src/services/dashboards/components/dashboard-main/DashboardBundleCloneModal.vue +++ b/apps/web/src/services/dashboards/components/dashboard-main/DashboardBundleCloneModal.vue @@ -42,9 +42,9 @@ import { useProxyValue } from '@/common/composables/proxy-state'; import { gray } from '@/styles/colors'; +import { getSharedDashboardLayouts } from '@/services/dashboard-shared/helpers/dashboard-share-helper'; import { useDashboardFolderQuery } from '@/services/dashboards/composables/use-dashboard-folder-query'; import { useDashboardQuery } from '@/services/dashboards/composables/use-dashboard-query'; -import { getSharedDashboardLayouts } from '@/services/dashboards/helpers/dashboard-share-helper'; import { getSelectedDataTableItems } from '@/services/dashboards/helpers/dashboard-tree-data-helper'; import { useDashboardPageControlStore } from '@/services/dashboards/stores/dashboard-page-control-store'; import type { DashboardDataTableItem } from '@/services/dashboards/types/dashboard-folder-type'; diff --git a/apps/web/src/services/dashboards/components/dashboard-main/DashboardBundleShareModal.vue b/apps/web/src/services/dashboards/components/dashboard-main/DashboardBundleShareModal.vue index cbe5dad421..7fbe4e0d7e 100644 --- a/apps/web/src/services/dashboards/components/dashboard-main/DashboardBundleShareModal.vue +++ b/apps/web/src/services/dashboards/components/dashboard-main/DashboardBundleShareModal.vue @@ -2,18 +2,14 @@ import { computed, reactive, watch } from 'vue'; import type { TranslateResult } from 'vue-i18n'; -import { useMutation, useQueryClient } from '@tanstack/vue-query'; +import { useQueryClient } from '@tanstack/vue-query'; import { PDataTable, PI, PButtonModal, PRadioGroup, PRadio, PFieldGroup, } from '@cloudforet/mirinae'; import type { MenuItem } from '@cloudforet/mirinae/types/controls/context-menu/type'; -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 type { PublicDashboardModel } from '@/api-clients/dashboard/public-dashboard/schema/model'; -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 { i18n } from '@/translations'; @@ -26,6 +22,8 @@ import { useProxyValue } from '@/common/composables/proxy-state'; import { gray } from '@/styles/colors'; +import { useDashboardFolderShareAction } from '@/services/dashboard-shared/actions/use-dashboard-folder-share-action'; +import { useDashboardShareAction } from '@/services/dashboard-shared/actions/use-dashboard-share-action'; import { useDashboardFolderQuery } from '@/services/dashboards/composables/use-dashboard-folder-query'; import { useDashboardQuery } from '@/services/dashboards/composables/use-dashboard-query'; import { useDashboardPageControlStore } from '@/services/dashboards/stores/dashboard-page-control-store'; @@ -33,7 +31,6 @@ import type { DashboardDataTableItem } from '@/services/dashboards/types/dashboa - const TABLE_FIELDS = [ { name: 'name', label: 'Name' }, { name: 'location', label: 'Location' }, @@ -57,13 +54,11 @@ const dashboardPageControlStore = useDashboardPageControlStore(); const { publicDashboardList, privateDashboardList, - api: dashboardApi, keys: dashboardKeys, } = useDashboardQuery(); const { publicFolderList, privateFolderList, - api: folderApi, keys: folderKeys, } = useDashboardFolderQuery(); const queryClient = useQueryClient(); @@ -188,23 +183,13 @@ const unshareDashboard = () => { dashboard_id: props.dashboardId || '', }); }; -const folderSharefetcher = (params: PublicFolderShareParameters|PublicFolderUnshareParameters) => { - if (!state.isShared) { - return folderApi.publicFolderAPI.share(params as PublicFolderShareParameters); - } - return folderApi.publicFolderAPI.unshare(params as PublicFolderUnshareParameters); -}; -const dashboardShareFetcher = (params: PublicDashboardShareParameters|PublicDashboardUnshareParameters) => { - if (!state.isShared) { - return dashboardApi.publicDashboardAPI.share(params as PublicDashboardShareParameters); - } - return dashboardApi.publicDashboardAPI.unshare(params as PublicDashboardUnshareParameters); -}; -const { mutate: folderShareMutate, isPending: folderLoading } = useMutation({ - mutationFn: folderSharefetcher, +const { mutate: folderShareMutate, isPending: folderLoading } = useDashboardFolderShareAction({ + folderId: computed(() => state.targetFolderId), + isShared: computed(() => state.isShared), onSuccess: () => { queryClient.invalidateQueries({ queryKey: folderKeys.publicFolderListQueryKey.value }); + queryClient.invalidateQueries({ queryKey: dashboardKeys.privateDashboardListQueryKey.value }); if (state.isShared) showSuccessMessage(i18n.t('DASHBOARDS.DETAIL.ALT_S_SHARE_DASHBOARD'), ''); else showSuccessMessage(i18n.t('DASHBOARDS.DETAIL.ALT_S_UNSHARE_DASHBOARD'), ''); }, @@ -218,8 +203,10 @@ const { mutate: folderShareMutate, isPending: folderLoading } = useMutation({ }, }); -const { mutate: dashboardShareMutate, isPending: dashboardLoading } = useMutation({ - mutationFn: dashboardShareFetcher, + +const { mutate: dashboardShareMutate, isPending: dashboardLoading } = useDashboardShareAction({ + dashboardId: computed(() => props.dashboardId), + isShared: computed(() => state.isShared), onSuccess: () => { queryClient.invalidateQueries({ queryKey: dashboardKeys.publicDashboardListQueryKey.value }); if (state.isShared) showSuccessMessage(i18n.t('DASHBOARDS.DETAIL.ALT_S_SHARE_DASHBOARD'), ''); From d5be6db09c6d5bc054c145c1ef991ee1b4033820 Mon Sep 17 00:00:00 2001 From: Yongtae Park Date: Thu, 10 Apr 2025 15:48:47 +0900 Subject: [PATCH 06/11] refactor(dashboard-control-menu): refactor dashboard control menu generation Signed-off-by: samuel.park --- .../helpers/dashboard-control-menu-helper.ts | 95 ++++++++++ .../dashboard-folder/DashboardFolderTree.vue | 8 +- .../DashboardFolderTreeItem.vue | 37 ++-- .../dashboard-main/DashboardLSBTree.vue | 28 ++- .../use-dashboard-control-menu-items.ts | 174 ------------------ .../helpers/dashboard-tree-data-helper.ts | 13 +- .../dashboards/types/dashboard-folder-type.ts | 20 +- 7 files changed, 161 insertions(+), 214 deletions(-) create mode 100644 apps/web/src/services/dashboard-shared/helpers/dashboard-control-menu-helper.ts delete mode 100644 apps/web/src/services/dashboards/composables/use-dashboard-control-menu-items.ts diff --git a/apps/web/src/services/dashboard-shared/helpers/dashboard-control-menu-helper.ts b/apps/web/src/services/dashboard-shared/helpers/dashboard-control-menu-helper.ts new file mode 100644 index 0000000000..4033544b8b --- /dev/null +++ b/apps/web/src/services/dashboard-shared/helpers/dashboard-control-menu-helper.ts @@ -0,0 +1,95 @@ +import type { MenuItem } from '@cloudforet/mirinae/types/controls/context-menu/type'; + +import type { DashboardModel } from '@/api-clients/dashboard/_types/dashboard-type'; +import type { FolderModel } from '@/api-clients/dashboard/_types/folder-type'; +import { i18n } from '@/translations'; + +const _getShareMenuItems = (isShared: boolean): MenuItem[] => [ + { + type: 'item', + name: 'share', + label: isShared ? i18n.t('DASHBOARDS.ALL_DASHBOARDS.UNSHARE') : i18n.t('DASHBOARDS.ALL_DASHBOARDS.SHARE'), + icon: 'ic_share', + }, +]; + +export const getControlDashboardMenuItems = ( + dashboardId: string, + manageable: boolean, + dashboard: DashboardModel, + isProject = false, +): MenuItem[] => { + const _isPrivate = dashboardId.startsWith('private'); + const _isDeprecated = dashboard.version === '1.0'; + const _isShared = !!dashboard?.shared; + if (!manageable) { + if (_isDeprecated) return []; + return [ + { + type: 'item', name: 'clone', label: i18n.t('DASHBOARDS.DETAIL.CLONE'), icon: 'ic_clone', + }, + ]; + } + if (_isDeprecated) { + return [ + { + type: 'item', name: 'delete', label: i18n.t('DASHBOARDS.ALL_DASHBOARDS.DELETE'), icon: 'ic_delete', + }, + ]; + } + + const _shareContextMenuItems: MenuItem[] = [ + { + type: 'item', name: 'shareWithCode', label: i18n.t('DASHBOARDS.DETAIL.SHARE_WITH_CODE'), icon: 'ic_share-code', + }, + ...(_isPrivate ? [] : _getShareMenuItems(_isShared)), + { type: 'divider', name: 'divider' }, + ]; + + return [ + { + type: 'item', + name: 'edit', + label: i18n.t('DASHBOARDS.ALL_DASHBOARDS.EDIT_NAME'), + icon: 'ic_edit-text', + }, + { + type: 'item', name: 'clone', label: i18n.t('DASHBOARDS.DETAIL.CLONE'), icon: 'ic_clone', + }, + { + type: 'item', name: 'move', label: i18n.t('DASHBOARDS.DETAIL.MOVE'), icon: 'ic_move', + }, + { type: 'divider', name: 'divider' }, + ...(isProject ? [] : _shareContextMenuItems), + { + type: 'item', name: 'delete', label: i18n.t('DASHBOARDS.ALL_DASHBOARDS.DELETE'), icon: 'ic_delete', + }, + ]; +}; + +export const getControlFolderMenuItems = (folderId: string, manageable: boolean, folder: FolderModel): MenuItem[] => { + const _isPrivate = folderId.startsWith('private'); + const _isShared = !!folder?.shared; + if (!manageable) { + return [ + { + type: 'item', name: 'clone', label: i18n.t('DASHBOARDS.DETAIL.CLONE'), icon: 'ic_clone', + }, + ]; + } + + return [ + { + type: 'item', name: 'edit', label: i18n.t('DASHBOARDS.ALL_DASHBOARDS.EDIT_NAME'), icon: 'ic_edit-text', + }, + { + type: 'item', name: 'clone', label: i18n.t('DASHBOARDS.DETAIL.CLONE'), icon: 'ic_clone', + }, + { type: 'divider', name: 'divider' }, + ...(_isPrivate ? [] : _getShareMenuItems(_isShared)), + { type: 'divider', name: 'divider' }, + { + type: 'item', name: 'delete', label: i18n.t('DASHBOARDS.ALL_DASHBOARDS.DELETE'), icon: 'ic_delete', + }, + ]; +}; diff --git a/apps/web/src/services/dashboards/components/dashboard-folder/DashboardFolderTree.vue b/apps/web/src/services/dashboards/components/dashboard-folder/DashboardFolderTree.vue index aaa345db76..c7519136f1 100644 --- a/apps/web/src/services/dashboards/components/dashboard-folder/DashboardFolderTree.vue +++ b/apps/web/src/services/dashboards/components/dashboard-folder/DashboardFolderTree.vue @@ -120,13 +120,13 @@ const handleSelectTreeItem = (node: TreeNode, value: [boo state.proxySelectedIdMap[child.id] = _isSelected; }); } - } else if (node.data.folderId) { + } else if (node.data.folder_id) { if (_isSelected) { - state.proxySelectedIdMap[node.data.folderId] = true; + state.proxySelectedIdMap[node.data.folder_id] = true; } else { - const _folderNode = props.dashboardTreeData.find((n) => n.data.id === node.data.folderId); + const _folderNode = props.dashboardTreeData.find((n) => n.data.id === node.data.folder_id); if (_folderNode?.children?.every((child) => !state.proxySelectedIdMap[child.id])) { - state.proxySelectedIdMap[node.data.folderId] = false; + state.proxySelectedIdMap[node.data.folder_id] = false; } } } diff --git a/apps/web/src/services/dashboards/components/dashboard-folder/DashboardFolderTreeItem.vue b/apps/web/src/services/dashboards/components/dashboard-folder/DashboardFolderTreeItem.vue index 7448012044..6b35646234 100644 --- a/apps/web/src/services/dashboards/components/dashboard-folder/DashboardFolderTreeItem.vue +++ b/apps/web/src/services/dashboards/components/dashboard-folder/DashboardFolderTreeItem.vue @@ -10,6 +10,8 @@ import type { MenuItem } from '@cloudforet/mirinae/types/controls/context-menu/t import type { QueryTag } from '@cloudforet/mirinae/types/controls/search/query-search-tags/type'; import type { TreeNode } from '@cloudforet/mirinae/types/data-display/tree/tree-view/type'; +import type { DashboardModel } from '@/api-clients/dashboard/_types/dashboard-type'; +import type { FolderModel } from '@/api-clients/dashboard/_types/folder-type'; import { ROLE_TYPE } from '@/api-clients/identity/role/constant'; import { i18n } from '@/translations'; @@ -23,13 +25,13 @@ import { FAVORITE_TYPE } from '@/common/modules/favorites/favorite-button/type'; import { gray, indigo, violet } from '@/styles/colors'; -import { useDashboardControlMenuItems } from '@/services/dashboards/composables/use-dashboard-control-menu-items'; +import { useDashboardManageable } from '@/services/dashboard-shared/dashboard-detail/composables/use-dashboard-manageable'; +import { getControlDashboardMenuItems, getControlFolderMenuItems } from '@/services/dashboard-shared/helpers/dashboard-control-menu-helper'; import { ADMIN_DASHBOARDS_ROUTE } from '@/services/dashboards/routes/admin/route-constant'; import { DASHBOARDS_ROUTE } from '@/services/dashboards/routes/route-constant'; import { useDashboardPageControlStore } from '@/services/dashboards/stores/dashboard-page-control-store'; import type { DashboardTreeDataType } from '@/services/dashboards/types/dashboard-folder-type'; - interface Props { treeData: TreeNode; // for dashboard create page @@ -48,10 +50,7 @@ const dashboardPageControlStore = useDashboardPageControlStore(); const dashboardPageControlState = dashboardPageControlStore.state; const userStore = useUserStore(); -const { getControlMenuItems } = useDashboardControlMenuItems({ - isAdminMode: computed(() => storeState.isAdminMode), - isWorkspaceOwner: computed(() => storeState.isWorkspaceOwner), -}); +const { getDashboardManageable, getFolderManageable } = useDashboardManageable(); const storeState = reactive({ isAdminMode: computed(() => appContextStore.getters.isAdminMode), @@ -67,24 +66,38 @@ const state = reactive({ /* Util */ const getSharedColor = (node: TreeNode): string|undefined => { - if (node.data.shared) { - if (node.data.scope === 'PROJECT') return violet[500]; + if (node.data?.shared) { + if (node.data?.scope === 'PROJECT') return violet[500]; return indigo[500]; } return undefined; }; const getSharedText = (node: TreeNode): TranslateResult|undefined => { - if (node.data.shared) { + if (node.data?.shared) { if (storeState.isAdminMode) { - if (node.data.scope === 'PROJECT') return i18n.t('DASHBOARDS.DETAIL.SHARED_TO_ALL_PROJECTS'); + if (node.data?.scope === 'PROJECT') return i18n.t('DASHBOARDS.DETAIL.SHARED_TO_ALL_PROJECTS'); return i18n.t('DASHBOARDS.DETAIL.SHARED_TO_WORKSPACES'); } - if (node.data.scope === 'PROJECT') return i18n.t('DASHBOARDS.DETAIL.SHARED_TO_ALL_PROJECTS'); + if (node.data?.scope === 'PROJECT') return i18n.t('DASHBOARDS.DETAIL.SHARED_TO_ALL_PROJECTS'); return i18n.t('DASHBOARDS.DETAIL.SHARED_BY_ADMIN'); } return undefined; }; +const getControlMenuItems = (node: TreeNode): MenuItem[] => { + if (node.data.type === 'DASHBOARD') { + const _dashboard = node.data as DashboardModel; + const _manageable = getDashboardManageable(_dashboard); + return getControlDashboardMenuItems(node.data.id, _manageable, _dashboard); + } + if (node.data.type === 'FOLDER') { + const _folder = node.data as FolderModel; + const _manageable = getFolderManageable(_folder); + return getControlFolderMenuItems(node.data.id, _manageable, _folder); + } + return []; +}; + /* Event */ const handleClickTreeItem = (): void => { if (props.treeData.data.type === 'FOLDER') { @@ -165,7 +178,7 @@ const handleSelectControlButton = (id: string, item: MenuItem) => { > appContextStore.getters.isAdminMode), isWorkspaceOwner: computed(() => userStore.state.currentRoleInfo?.roleType === ROLE_TYPE.WORKSPACE_OWNER), }); -const { getControlMenuItems } = useDashboardControlMenuItems({ - isAdminMode: computed(() => storeState.isAdminMode), - isWorkspaceOwner: computed(() => storeState.isWorkspaceOwner), -}); + +const { getDashboardManageable, getFolderManageable } = useDashboardManageable(); + const state = reactive({ currentParentPathIds: [] as string[], currentFolderId: undefined as string|undefined, @@ -79,6 +80,21 @@ const init = (dashboardId?: string, _onMounted?: boolean) => { } }; +const getControlMenuItems = (node: TreeNode): MenuItem[] => { + if (node.data.type === 'DASHBOARD') { + const _dashboard = node.data as DashboardModel; + const _manageable = getDashboardManageable(_dashboard); + return getControlDashboardMenuItems(node.data.id, _manageable, _dashboard); + } + if (node.data.type === 'FOLDER') { + const _folder = node.data as FolderModel; + const _manageable = getFolderManageable(_folder); + return getControlFolderMenuItems(node.data.id, _manageable, _folder); + } + return []; +}; + + /* Event */ const handleClickTreeItem = (node: TreeNode) => { if (node.data.type === 'FOLDER') { @@ -140,7 +156,7 @@ onMounted(() => {
; - isWorkspaceOwner: ComputedRef; -} -interface UseDashboardControlMenuItemsReturn { - getControlMenuItems: (id: string) => ComputedRef|MenuItem[]; -} - -export const useDashboardControlMenuItems = ({ - isAdminMode, isWorkspaceOwner, -}: UseDashboardControlMenuItems): UseDashboardControlMenuItemsReturn => { - const appContextStore = useAppContextStore(); - - /* Query */ - const { - publicDashboardList, - privateDashboardList, - } = useDashboardQuery(); - const { - publicFolderList, - privateFolderList, - } = useDashboardFolderQuery(); - - const queryState = reactive({ - publicDashboardItems: computed(() => { - const _v2DashboardItems = publicDashboardList.value.filter((d) => d.version !== '1.0'); - if (appContextStore.getters.isAdminMode) return _v2DashboardItems; - return _v2DashboardItems.filter((d) => !(d.resource_group === 'DOMAIN' && !!d.shared && d.scope === 'PROJECT')); - }), - privateDashboardItems: computed(() => privateDashboardList.value.filter((d) => d.version !== '1.0')), - dashboardList: computed(() => [...queryState.publicDashboardItems, ...queryState.privateDashboardItems]), - publicFolderItems: computed(() => { - if (appContextStore.getters.isAdminMode) return publicFolderList.value; - return publicFolderList.value.filter((d) => !(d.resource_group === 'DOMAIN' && !!d.shared && d.scope === 'PROJECT')); - }), - privateFolderItems: computed(() => privateFolderList.value), - folderList: computed(() => [...queryState.publicFolderItems, ...queryState.privateFolderItems]), - }); - - const _getDashboardManageable = (dashboard: DashboardModel): boolean => { - if (dashboard.dashboard_id.startsWith('private')) return true; - if (isAdminMode.value) return true; - if (isWorkspaceOwner.value) { - return dashboard?.workspace_id !== '*'; - } - return false; - }; - const _getFolderManageable = (folder: FolderModel): boolean => { - if (folder.folder_id.startsWith('private')) return true; - if (isAdminMode.value) return true; - if (isWorkspaceOwner.value) { - return folder?.workspace_id !== '*'; - } - return false; - }; - const _getDashboardControlMenuItems = (dashboardId: string): ComputedRef|MenuItem[] => { - const _isPrivate = dashboardId.startsWith('private'); - const _dashboard = queryState.dashboardList.find((item) => item.dashboard_id === dashboardId); - if (!_dashboard) return []; - - const _isDeprecated = _dashboard.version === '1.0'; - const _isDashboardManageable = _getDashboardManageable(_dashboard); - - if (!_isDashboardManageable) { - if (_isDeprecated) return []; - return computed(() => ([ - { - type: 'item', name: 'clone', label: i18n.t('DASHBOARDS.DETAIL.CLONE'), icon: 'ic_clone', - }, - ])); - } - if (_isDeprecated) { - return computed(() => ([{ - type: 'item', name: 'delete', label: i18n.t('DASHBOARDS.ALL_DASHBOARDS.DELETE'), icon: 'ic_delete', - }])); - } - - const _isShared = !!_dashboard.shared; - const _shareMenuItems: MenuItem[] = [ - { - type: 'item', - name: 'share', - label: _isShared ? i18n.t('DASHBOARDS.ALL_DASHBOARDS.UNSHARE_DASHBOARD') : i18n.t('DASHBOARDS.ALL_DASHBOARDS.SHARE_DASHBOARD'), - icon: 'ic_share', - }, - ]; - return computed(() => ([ - { - type: 'item', - name: 'edit', - label: i18n.t('DASHBOARDS.ALL_DASHBOARDS.EDIT_NAME'), - icon: 'ic_edit-text', - }, - { - type: 'item', name: 'clone', label: i18n.t('DASHBOARDS.DETAIL.CLONE'), icon: 'ic_clone', - }, - { - type: 'item', name: 'move', label: i18n.t('DASHBOARDS.DETAIL.MOVE'), icon: 'ic_move', - }, - { type: 'divider', name: 'divider' }, - { - type: 'item', name: 'shareWithCode', label: i18n.t('DASHBOARDS.DETAIL.SHARE_WITH_CODE'), icon: 'ic_share-code', - }, - ...(_isPrivate ? [] : _shareMenuItems), - { type: 'divider', name: 'divider' }, - { - type: 'item', name: 'delete', label: i18n.t('DASHBOARDS.ALL_DASHBOARDS.DELETE'), icon: 'ic_delete', - }, - ])); - }; - const _getFolderControlMenuItems = (folderId: string): ComputedRef|MenuItem[] => { - const _isPrivate = folderId.startsWith('private'); - const _folder = queryState.folderList.find((item) => item.folder_id === folderId); - if (!_folder) return []; - - const _isFolderManageable = _getFolderManageable(_folder); - if (!_isFolderManageable) { - return computed(() => ([ - { - type: 'item', name: 'clone', label: i18n.t('DASHBOARDS.DETAIL.CLONE'), icon: 'ic_clone', - }, - ])); - } - - const _isShared = !!_folder?.shared; - const _shareMenuItems: MenuItem[] = [ - { - type: 'item', - name: 'share', - label: _isShared ? i18n.t('DASHBOARDS.ALL_DASHBOARDS.UNSHARE') : i18n.t('DASHBOARDS.ALL_DASHBOARDS.SHARE'), - icon: 'ic_share', - }, - ]; - return computed(() => ([ - { - type: 'item', name: 'edit', label: i18n.t('DASHBOARDS.ALL_DASHBOARDS.EDIT_NAME'), icon: 'ic_edit-text', - }, - { - type: 'item', name: 'clone', label: i18n.t('DASHBOARDS.DETAIL.CLONE'), icon: 'ic_clone', - }, - { type: 'divider', name: 'divider' }, - ...(_isPrivate ? [] : _shareMenuItems), - { type: 'divider', name: 'divider' }, - { - type: 'item', name: 'delete', label: i18n.t('DASHBOARDS.ALL_DASHBOARDS.DELETE'), icon: 'ic_delete', - }, - ])); - }; - const getControlMenuItems = (id: string): ComputedRef|MenuItem[] => { - if (id.includes('folder')) return toValue(_getFolderControlMenuItems(id)); - return toValue(_getDashboardControlMenuItems(id)); - }; - - return { - getControlMenuItems, - }; -}; diff --git a/apps/web/src/services/dashboards/helpers/dashboard-tree-data-helper.ts b/apps/web/src/services/dashboards/helpers/dashboard-tree-data-helper.ts index 342af2ee72..ba2ad5ef9d 100644 --- a/apps/web/src/services/dashboards/helpers/dashboard-tree-data-helper.ts +++ b/apps/web/src/services/dashboards/helpers/dashboard-tree-data-helper.ts @@ -18,12 +18,9 @@ export const getDashboardTreeData = (folderList: FolderModel[], dashboardList: D name: d.name, type: 'FOLDER', id: d.folder_id, - shared: d?.shared, - scope: d?.scope, - projectId: d?.project_id, - workspaceId: d?.workspace_id, createdBy: d.tags?.created_by, isNew: newIdList?.includes(d.folder_id), + ...d, }, children: [], }; @@ -36,15 +33,9 @@ export const getDashboardTreeData = (folderList: FolderModel[], dashboardList: D name: d.name, id: d.dashboard_id, type: 'DASHBOARD', - folderId: d.folder_id, - shared: d?.shared, - scope: d?.scope, - projectId: d?.project_id, - workspaceId: d?.workspace_id, - userId: d?.user_id, createdBy: d.tags?.created_by, isNew: newIdList?.includes(d.dashboard_id), - labels: d?.labels, + ...d, }, }; }); diff --git a/apps/web/src/services/dashboards/types/dashboard-folder-type.ts b/apps/web/src/services/dashboards/types/dashboard-folder-type.ts index 0cda85e08e..81024dd7ab 100644 --- a/apps/web/src/services/dashboards/types/dashboard-folder-type.ts +++ b/apps/web/src/services/dashboards/types/dashboard-folder-type.ts @@ -1,16 +1,22 @@ import type { TreeData } from '@cloudforet/mirinae/types/data-display/tree/tree-view/type'; +import type { DashboardModel } from '@/api-clients/dashboard/_types/dashboard-type'; +import type { FolderModel } from '@/api-clients/dashboard/_types/folder-type'; -export interface DashboardTreeDataType extends TreeData { + +export type DashboardTreeDataType = TreeDataDashboard | TreeDataFolder; + + +type TreeDataDashboard = TreeDataBase & DashboardModel & { + type: 'DASHBOARD'; +}; +type TreeDataFolder = TreeDataBase & FolderModel & { + type: 'FOLDER'; +}; +export interface TreeDataBase extends TreeData { name: string; type: 'FOLDER'| 'DASHBOARD'; id: string; - folderId?: string; - shared?: boolean; - scope?: string; - projectId?: string; - workspaceId?: string; - userId?: string; createdBy?: string; isNew?: boolean; labels?: string[]; From 586235818942c5c211c7848b93ffd3d61f4f11a5 Mon Sep 17 00:00:00 2001 From: Wanjin Noh Date: Thu, 10 Apr 2025 15:51:29 +0900 Subject: [PATCH 07/11] feat: enhance ProjectOverview and summary components Signed-off-by: Wanjin Noh --- .../project/v2/components/ProjectOverview.vue | 22 ++- .../constants/workspace-home-constant.ts | 10 -- .../shared/components/AccountSummary.vue | 72 ++++---- .../shared/components/AssetSummary.vue | 82 ++++----- .../AssetSummaryDailyUpdateItem.vue | 104 +++++------ .../components/AssetSummaryDailyUpdates.vue | 52 ++---- .../components/AssetSummaryProvider.vue | 28 +-- .../components/AssetSummaryProviderItem.vue | 25 ++- .../shared/components/CostSummary.vue | 11 +- .../shared/components/EmptySummaryData.vue | 21 ++- .../composables/use-asset-daily-updates.ts | 130 ++++++++++++++ .../use-asset-summary-providers.ts | 163 +++++++++++++----- .../composables/use-metric-data-query.ts | 106 ------------ .../composables/use-project-ids-from-group.ts | 16 ++ .../shared/constants/cost-summary-constant.ts | 5 + .../shared/constants/summary-type-constant.ts | 5 + .../shared/types/asset-daily-updates-type.ts | 12 ++ .../shared/types/asset-provider-type.ts | 9 + .../shared/types/summary-type.ts | 3 + .../store/workspace-home-page-store.ts | 46 +---- .../types/workspace-home-type.ts | 18 +- 21 files changed, 492 insertions(+), 448 deletions(-) rename apps/web/src/services/workspace-home/{ => shared}/components/AssetSummaryDailyUpdateItem.vue (54%) rename apps/web/src/services/workspace-home/{ => shared}/components/AssetSummaryProviderItem.vue (84%) create mode 100644 apps/web/src/services/workspace-home/shared/composables/use-asset-daily-updates.ts delete mode 100644 apps/web/src/services/workspace-home/shared/composables/use-metric-data-query.ts create mode 100644 apps/web/src/services/workspace-home/shared/composables/use-project-ids-from-group.ts create mode 100644 apps/web/src/services/workspace-home/shared/constants/cost-summary-constant.ts create mode 100644 apps/web/src/services/workspace-home/shared/constants/summary-type-constant.ts create mode 100644 apps/web/src/services/workspace-home/shared/types/asset-daily-updates-type.ts create mode 100644 apps/web/src/services/workspace-home/shared/types/asset-provider-type.ts create mode 100644 apps/web/src/services/workspace-home/shared/types/summary-type.ts diff --git a/apps/web/src/services/project/v2/components/ProjectOverview.vue b/apps/web/src/services/project/v2/components/ProjectOverview.vue index 7ce33c9fe1..c415a26b58 100644 --- a/apps/web/src/services/project/v2/components/ProjectOverview.vue +++ b/apps/web/src/services/project/v2/components/ProjectOverview.vue @@ -1,4 +1,6 @@ diff --git a/apps/web/src/services/workspace-home/constants/workspace-home-constant.ts b/apps/web/src/services/workspace-home/constants/workspace-home-constant.ts index c7dc27d53b..a0651d72d7 100644 --- a/apps/web/src/services/workspace-home/constants/workspace-home-constant.ts +++ b/apps/web/src/services/workspace-home/constants/workspace-home-constant.ts @@ -55,16 +55,6 @@ export const WORKSPACE_HOME_DATA_TYPE = { BILLING: 'Billing', } as const; -export const SUMMARY_DATA_TYPE = { - ASSET: 'Asset', - COST: 'Cost', - ACCOUNT: 'Account', -} as const; -export const COST_SUMMARY_STATE_TYPE = { - AGGREGATING: 'AGGREGATING', - CONFIRM: 'CONFIRM', - ESTIMATED: 'ESTIMATED', -} as const; export const SERVICE_ACCOUNT_SUMMARY_STATE_COLOR = { ACTIVE: { iconColor: styles.green[600], diff --git a/apps/web/src/services/workspace-home/shared/components/AccountSummary.vue b/apps/web/src/services/workspace-home/shared/components/AccountSummary.vue index fc26ac9cbd..5959732696 100644 --- a/apps/web/src/services/workspace-home/shared/components/AccountSummary.vue +++ b/apps/web/src/services/workspace-home/shared/components/AccountSummary.vue @@ -8,15 +8,16 @@ import type { EChartsType } from 'echarts/core'; import { init } from 'echarts/core'; import { countBy, isEmpty, map } from 'lodash'; -import { SpaceConnector } from '@cloudforet/core-lib/space-connector'; import { PStatus, PFieldTitle, PLazyImg, PDivider, PLink, PSpinner, PProgressBar, } from '@cloudforet/mirinae'; import { numberFormatter } from '@cloudforet/utils'; -import type { ListResponse } from '@/api-clients/_common/schema/api-verbs/list'; +import { useScopedQuery } from '@/api-clients/_common/composables/use-scoped-query'; +import { useServiceAccountApi } from '@/api-clients/identity/service-account/composables/use-service-account-api'; import type { ServiceAccountListParameters } from '@/api-clients/identity/service-account/schema/api-verbs/list'; import type { ServiceAccountModel } from '@/api-clients/identity/service-account/schema/model'; +import { useServiceQueryKey } from '@/query/query-key/use-service-query-key'; import { i18n } from '@/translations'; import { useUserWorkspaceStore } from '@/store/app-context/workspace/user-workspace-store'; @@ -29,22 +30,18 @@ import type { PageAccessMap } from '@/lib/access-control/config'; import { assetUrlConverter } from '@/lib/helper/asset-helper'; import { MENU_ID } from '@/lib/menu/config'; -import ErrorHandler from '@/common/composables/error/errorHandler'; - import { SERVICE_ACCOUNT_ROUTE } from '@/services/service-account/routes/route-constant'; import { serviceAccountStateSummaryFormatter } from '@/services/workspace-home/composables/use-workspace-home'; -import { SUMMARY_DATA_TYPE } from '@/services/workspace-home/constants/workspace-home-constant'; import EmptySummaryData from '@/services/workspace-home/shared/components/EmptySummaryData.vue'; +import { SUMMARY_DATA_TYPE } from '@/services/workspace-home/shared/constants/summary-type-constant'; +import type { EmptyData } from '@/services/workspace-home/shared/types/empty-data-type'; import type { WidgetStyleType } from '@/services/workspace-home/shared/types/widget-style-type'; -import type { EmptyData } from '@/services/workspace-home/types/workspace-home-type'; const props = withDefaults(defineProps<{ - projectGroupId?: string; - projectId?: string; + projectIds?: string[]; styleType?: WidgetStyleType; }>(), { - projectGroupId: undefined, - projectId: undefined, + projectIds: undefined, styleType: 'default', }); @@ -64,8 +61,27 @@ const storeState = reactive({ provider: computed(() => allReferenceGetters.provider), pageAccessPermissionMap: computed(() => userStore.getters.pageAccessPermissionMap), }); + +const { serviceAccountAPI } = useServiceAccountApi(); +const { key: serviceAccountQueryKey, params } = useServiceQueryKey('identity', 'service-account', 'list', { + params: computed(() => ({ + query: { + filter: props.projectIds?.length ? [{ + k: 'project_id', + v: props.projectIds, + o: 'in', + }] : undefined, + }, + })), +}); +const { data: serviceAccountData, isLoading: isLoadingServiceAccount } = useScopedQuery({ + queryKey: serviceAccountQueryKey, + queryFn: () => serviceAccountAPI.list(params.value), + staleTime: 1000 * 60 * 5, // 5 minutes + gcTime: 1000 * 60 * 30, // 30 minutes +}, ['WORKSPACE']); + const state = reactive({ - loading: true, accessLink: computed(() => !isEmpty(storeState.pageAccessPermissionMap[MENU_ID.SERVICE_ACCOUNT])), writableServiceAccount: computed(() => storeState.pageAccessPermissionMap[MENU_ID.SERVICE_ACCOUNT].write), emptyData: computed(() => { @@ -80,8 +96,8 @@ const state = reactive({ } return result; }), - items: [] as ServiceAccountModel[], - itemsTotalCount: 0, + items: computed(() => serviceAccountData.value?.results || []), + itemsTotalCount: computed(() => serviceAccountData.value?.total_count || 0), itemsByState: computed(() => { const stateCounts = countBy(state.items, 'state'); return map(stateCounts, (count, i) => ({ @@ -136,11 +152,11 @@ const state = reactive({ tooltip: { trigger: 'item', position: 'right', - formatter: (params) => { - const _name = storeState.provider[params.name].label; - const _value = numberFormatter(params.value) || ''; - const percent = getPercent(params.value, state.itemsTotalCount); - return `${params.marker} ${_name}: ${_value} (${percent}%)`; + formatter: (p) => { + const _name = storeState.provider[p.name].label; + const _value = numberFormatter(p.value) || ''; + const percent = getPercent(p.value, state.itemsTotalCount); + return `${p.marker} ${_name}: ${_value} (${percent}%)`; }, }, series: [ @@ -164,20 +180,6 @@ const getPercent = (value: number, total: number) => { const roundedValue = Math.ceil(_value * 100) / 100; return parseFloat(roundedValue.toFixed(2)); }; -const fetchServiceAccountList = async () => { - state.loading = true; - try { - const { results, total_count } = await SpaceConnector.clientV2.identity.serviceAccount.list>(); - state.items = results || []; - state.itemsTotalCount = total_count; - } catch (e) { - ErrorHandler.handleError(e); - state.items = []; - state.itemsTotalCount = 0; - } finally { - state.loading = false; - } -}; watch([() => state.totalChartData, () => totalChartContext.value], ([, chartCtx]) => { if (chartCtx) { @@ -192,10 +194,6 @@ watch([() => state.providerChartData, () => providerChartContext.value], ([, cha } }, { immediate: true }); -watch([() => storeState.currentWorkspaceId, () => storeState.provider], async ([currentWorkspaceId]) => { - if (!currentWorkspaceId) return; - await fetchServiceAccountList(); -}, { immediate: true });