Skip to content

Commit 0c1a68a

Browse files
authored
Merge pull request #5760 from cloudforet-io/feature-project-dashboards
feat(project-dashboard): create project-dashboard actions
2 parents e1569f5 + e423160 commit 0c1a68a

18 files changed

+962
-72
lines changed

Diff for: apps/web/src/services/dashboard-shared/core/actions/use-dashboard-clone-action.ts

+1-15
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type { ComputedRef } from 'vue';
33
import { useMutation } from '@tanstack/vue-query';
44

55
import type { ListResponse } from '@/api-clients/_common/schema/api-verbs/list';
6-
import { RESOURCE_GROUP } from '@/api-clients/_common/schema/constant';
76
import type { DashboardCreateParams, DashboardModel } from '@/api-clients/dashboard/_types/dashboard-type';
87
import type { WidgetModel } from '@/api-clients/dashboard/_types/widget-type';
98
import { usePrivateDashboardApi } from '@/api-clients/dashboard/private-dashboard/composables/use-private-dashboard-api';
@@ -16,7 +15,6 @@ import { usePublicWidgetApi } from '@/api-clients/dashboard/public-widget/compos
1615
import { useAllReferenceStore } from '@/store/reference/all-reference-store';
1716
import { useUserStore } from '@/store/user/user-store';
1817

19-
import { useDashboardRouteContext } from '@/services/dashboard-shared/core/composables/use-dashboard-route-context';
2018
import { getSharedDashboardLayouts } from '@/services/dashboard-shared/core/helpers/dashboard-share-helper';
2119

2220

@@ -35,9 +33,6 @@ export const useDashboardCloneAction = (options: UseDashboardCloneActionOptions)
3533
const { publicWidgetAPI } = usePublicWidgetApi();
3634
const allReferenceStore = useAllReferenceStore();
3735
const userStore = useUserStore();
38-
const {
39-
entryPoint,
40-
} = useDashboardRouteContext();
4136

4237
const {
4338
dashboardId, isPrivate, onSuccess, onError, onSettled,
@@ -78,23 +73,14 @@ export const useDashboardCloneAction = (options: UseDashboardCloneActionOptions)
7873
const _sharedLayouts = await getSharedDashboardLayouts(dashboard.layouts, widgetList.results || [], allReferenceStore.getters.costDataSource);
7974

8075
const _sharedDashboard: DashboardCreateParams = {
81-
name: params.name,
76+
...params,
8277
layouts: _sharedLayouts,
8378
options: dashboard.options || {},
8479
labels: dashboard.labels || [],
8580
tags: { created_by: userStore.state.userId },
8681
vars: dashboard.vars,
8782
vars_schema: dashboard.vars_schema,
8883
};
89-
if (entryPoint.value === 'ADMIN') {
90-
(_sharedDashboard as PublicDashboardCreateParameters).resource_group = RESOURCE_GROUP.DOMAIN;
91-
} else if (entryPoint.value === 'WORKSPACE') {
92-
if (!isPrivate?.value) {
93-
(_sharedDashboard as PublicDashboardCreateParameters).resource_group = RESOURCE_GROUP.WORKSPACE;
94-
}
95-
} else if (entryPoint.value === 'PROJECT') {
96-
(_sharedDashboard as PublicDashboardCreateParameters).resource_group = RESOURCE_GROUP.PROJECT;
97-
}
9884

9985

10086
if (isPrivate?.value) {

Diff for: apps/web/src/services/dashboard-shared/core/actions/use-dashboard-delete-action.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ export const useDashboardDeleteAction = (options: UseDashboardDeleteActionOption
4141
onSuccess: async (data, variables) => {
4242
const _dashboardId = variables.dashboard_id;
4343
const _isPrivate = _dashboardId.startsWith('private');
44-
const dashboardListQueryKey = _isPrivate ? privateDashboardGetQueryKey(_dashboardId) : publicDashboardGetQueryKey(_dashboardId);
45-
queryClient.invalidateQueries({ queryKey: dashboardListQueryKey });
44+
const dashboardGetQueryKey = _isPrivate ? privateDashboardGetQueryKey(_dashboardId) : publicDashboardGetQueryKey(_dashboardId);
45+
queryClient.invalidateQueries({ queryKey: dashboardGetQueryKey });
4646
if (onSuccess) await onSuccess(data, variables);
4747
},
4848
onError: (error, variables) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import type { ComputedRef } from 'vue';
2+
import { computed } from 'vue';
3+
4+
import {
5+
useMutation, useQueryClient,
6+
} from '@tanstack/vue-query';
7+
8+
import type { DashboardDeleteParams, DashboardListParams } from '@/api-clients/dashboard/_types/dashboard-type';
9+
import type { FolderDeleteParams } from '@/api-clients/dashboard/_types/folder-type';
10+
import { usePrivateDashboardApi } from '@/api-clients/dashboard/private-dashboard/composables/use-private-dashboard-api';
11+
import { usePrivateFolderApi } from '@/api-clients/dashboard/private-folder/composables/use-private-folder-api';
12+
import { usePublicDashboardApi } from '@/api-clients/dashboard/public-dashboard/composables/use-public-dashboard-api';
13+
import { usePublicFolderApi } from '@/api-clients/dashboard/public-folder/composables/use-public-folder-api';
14+
import { useServiceQueryKey } from '@/query/query-key/use-service-query-key';
15+
16+
interface UseDashboardFolderDeleteActionOptions {
17+
folderId: ComputedRef<string|undefined>;
18+
onSuccess?: (data: unknown, variables: FolderDeleteParams) => void|Promise<void>;
19+
onError?: (error: Error, variables: FolderDeleteParams) => void|Promise<void>;
20+
onSettled?: (data: unknown|undefined, error: Error|null, variables: FolderDeleteParams) => void|Promise<void>;
21+
}
22+
23+
export const useDashboardFolderDeleteAction = (options: UseDashboardFolderDeleteActionOptions) => {
24+
const { publicFolderAPI } = usePublicFolderApi();
25+
const { privateFolderAPI } = usePrivateFolderApi();
26+
const { publicDashboardAPI } = usePublicDashboardApi();
27+
const { privateDashboardAPI } = usePrivateDashboardApi();
28+
const queryClient = useQueryClient();
29+
const { withSuffix: publicFolderGetQueryKey } = useServiceQueryKey('dashboard', 'public-folder', 'get');
30+
const { withSuffix: privateFolderGetQueryKey } = useServiceQueryKey('dashboard', 'private-folder', 'get');
31+
const { withSuffix: publicDashboardGetQueryKey } = useServiceQueryKey('dashboard', 'public-dashboard', 'get');
32+
const { withSuffix: privateDashboardGetQueryKey } = useServiceQueryKey('dashboard', 'private-dashboard', 'get');
33+
34+
const {
35+
folderId, onSuccess, onError, onSettled,
36+
} = options;
37+
38+
const isPrivate = computed(() => folderId.value?.startsWith('private'));
39+
40+
const listDashboardFn = (params: DashboardListParams) => {
41+
if (!folderId.value) throw new Error('Folder ID is not provided');
42+
const fetcher = isPrivate.value ? privateDashboardAPI.list : publicDashboardAPI.list;
43+
return fetcher(params);
44+
};
45+
const deleteDashboardFn = (params: DashboardDeleteParams) => {
46+
if (!params.dashboard_id) throw new Error('Dashboard ID is not provided');
47+
const fetcher = isPrivate.value ? privateDashboardAPI.delete : publicDashboardAPI.delete;
48+
return fetcher(params);
49+
};
50+
51+
const deleteDashboardListByFolderId = async (_folderId: string) => {
52+
const _dashboardList = await listDashboardFn({
53+
folder_id: _folderId,
54+
});
55+
const _dashboardIds = _dashboardList.results?.map((d) => d.dashboard_id);
56+
if (!_dashboardIds) return;
57+
const _dashboardDeletePromises = _dashboardIds.map(async (id) => {
58+
const _isPrivate = id.startsWith('private');
59+
await deleteDashboardFn({ dashboard_id: id });
60+
queryClient.invalidateQueries({ queryKey: _isPrivate ? privateDashboardGetQueryKey(id) : publicDashboardGetQueryKey(id) });
61+
});
62+
await Promise.all(_dashboardDeletePromises);
63+
};
64+
65+
const deleteFolderFn = (params: FolderDeleteParams) => {
66+
if (!folderId.value) throw new Error('Folder ID is not provided');
67+
const fetcher = isPrivate.value ? privateFolderAPI.delete : publicFolderAPI.delete;
68+
return fetcher(params);
69+
};
70+
71+
return useMutation({
72+
mutationFn: deleteFolderFn,
73+
onSuccess: async (data, variables) => {
74+
const _folderId = variables.folder_id;
75+
const _isPrivate = _folderId.startsWith('private');
76+
const folderListQueryKey = _isPrivate ? privateFolderGetQueryKey(_folderId) : publicFolderGetQueryKey(_folderId);
77+
queryClient.invalidateQueries({ queryKey: folderListQueryKey });
78+
79+
await deleteDashboardListByFolderId(_folderId);
80+
81+
if (onSuccess) await onSuccess(data, variables);
82+
},
83+
onError: (error, variables) => {
84+
if (onError) onError(error, variables);
85+
},
86+
onSettled: (data, error, variables) => {
87+
if (onSettled) onSettled(data, error, variables);
88+
},
89+
});
90+
};

Diff for: apps/web/src/services/dashboard-shared/dashboard-detail/DashboardDetailBody.vue

+2-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import { PROJECT_ROUTE_V2 } from '@/services/project/v2/routes/route-constant';
4242
interface Props {
4343
dashboardId: string;
4444
dashboardItems?: Array<DashboardModel>;
45-
folderItems?: Array<DashboardFolderModel>;
45+
folderItems: Array<DashboardFolderModel>;
4646
}
4747
4848
const props = withDefaults(defineProps<Props>(), {
@@ -194,6 +194,7 @@ onUnmounted(() => {
194194
<template>
195195
<div class="dashboard-detail-body">
196196
<dashboard-detail-header :dashboard-id="props.dashboardId"
197+
:folder-items="props.folderItems"
197198
@select-toolset="handleSelectToolset"
198199
/>
199200
<p-divider v-if="entryPoint !== 'PROJECT'"

Diff for: apps/web/src/services/dashboards/components/dashboard-detail/DashboardCloneModal.vue

+11-4
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import {
99
} from '@cloudforet/mirinae';
1010
import { getClonedName } from '@cloudforet/utils';
1111
12+
import { RESOURCE_GROUP } from '@/api-clients/_common/schema/constant';
1213
import type { DashboardCreateParams, DashboardModel, DashboardType } from '@/api-clients/dashboard/_types/dashboard-type';
14+
import type { PublicDashboardCreateParameters } from '@/api-clients/dashboard/public-dashboard/schema/api-verbs/create';
1315
import { ROLE_TYPE } from '@/api-clients/identity/role/constant';
1416
import { i18n } from '@/translations';
1517
1618
import { useAppContextStore } from '@/store/app-context/app-context-store';
17-
import { useAllReferenceStore } from '@/store/reference/all-reference-store';
18-
import type { CostDataSourceReferenceMap } from '@/store/reference/cost-data-source-reference-store';
1919
import { useUserStore } from '@/store/user/user-store';
2020
2121
import { showErrorMessage } from '@/lib/helper/notice-alert-helper';
@@ -51,7 +51,6 @@ const queryClient = useQueryClient();
5151
5252
const router = useRouter();
5353
const appContextStore = useAppContextStore();
54-
const allReferenceStore = useAllReferenceStore();
5554
const userStore = useUserStore();
5655
const {
5756
forms: {
@@ -77,7 +76,6 @@ const storeState = reactive({
7776
isAdminMode: computed(() => appContextStore.getters.isAdminMode),
7877
isWorkspaceOwner: computed<boolean>(() => userStore.state.currentRoleInfo?.roleType === ROLE_TYPE.WORKSPACE_OWNER),
7978
isWorkspaceMember: computed<boolean>(() => userStore.state.currentRoleInfo?.roleType === ROLE_TYPE.WORKSPACE_MEMBER),
80-
costDataSource: computed<CostDataSourceReferenceMap>(() => allReferenceStore.getters.costDataSource),
8179
});
8280
const state = reactive({
8381
proxyVisible: useProxyValue('visible', props, emit),
@@ -121,6 +119,15 @@ const handleConfirm = async () => {
121119
} else if (storeState.isAdminMode) {
122120
state.isPrivate = false;
123121
}
122+
123+
if (!state.isPrivate) {
124+
if (storeState.isAdminMode) {
125+
(_sharedDashboard as PublicDashboardCreateParameters).resource_group = RESOURCE_GROUP.DOMAIN;
126+
} else {
127+
(_sharedDashboard as PublicDashboardCreateParameters).resource_group = RESOURCE_GROUP.WORKSPACE;
128+
}
129+
}
130+
124131
mutate(_sharedDashboard as DashboardCreateParams);
125132
};
126133

Diff for: apps/web/src/services/dashboards/components/dashboard-detail/DashboardDetailHeader.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import { useDashboardGetQuery } from '@/services/dashboard-shared/dashboard-deta
2323
2424
interface Props {
2525
dashboardId: string;
26-
folderItems?: Array<FolderModel>;
26+
folderItems: Array<FolderModel>;
2727
}
2828
const props = withDefaults(defineProps<Props>(), {
2929
folderItems: () => [],

Diff for: apps/web/src/services/dashboards/helpers/dashboard-tree-data-helper.ts

+33-39
Original file line numberDiff line numberDiff line change
@@ -8,64 +8,58 @@ import type { FolderModel } from '@/api-clients/dashboard/_types/folder-type';
88
import type { DashboardTreeDataType, DashboardDataTableItem } from '@/services/dashboards/types/dashboard-folder-type';
99

1010

11-
export const getDashboardTreeData = (folderList: FolderModel[], dashboardList: DashboardModel[], newIdList?: string[]): TreeNode<DashboardTreeDataType>[] => {
12-
const nodes: Record<string, TreeNode<DashboardTreeDataType>> = {};
13-
folderList.forEach((d) => {
14-
nodes[d.folder_id] = {
15-
id: d.folder_id,
11+
export const getDashboardTreeData = (
12+
folderList: FolderModel[],
13+
dashboardList: DashboardModel[],
14+
newIdList?: string[],
15+
): TreeNode<DashboardTreeDataType>[] => {
16+
const folderNodeMap: Record<string, TreeNode<DashboardTreeDataType>> = {};
17+
const rootNodes: TreeNode<DashboardTreeDataType>[] = [];
18+
19+
folderList.forEach((folder) => {
20+
const folderNode: TreeNode<DashboardTreeDataType> = {
21+
id: folder.folder_id,
1622
depth: 0,
1723
data: {
18-
name: d.name,
24+
id: folder.folder_id,
25+
name: folder.name,
1926
type: 'FOLDER',
20-
id: d.folder_id,
21-
createdBy: d.tags?.created_by,
22-
isNew: newIdList?.includes(d.folder_id),
23-
...d,
27+
createdBy: folder.tags?.created_by,
28+
isNew: newIdList?.includes(folder.folder_id),
29+
...folder,
2430
},
2531
children: [],
2632
};
33+
folderNodeMap[folder.folder_id] = folderNode;
34+
rootNodes.push(folderNode);
2735
});
28-
dashboardList.forEach((d) => {
29-
nodes[d.dashboard_id] = {
30-
id: d.dashboard_id,
36+
37+
dashboardList.forEach((dashboard) => {
38+
const dashboardNode: TreeNode<DashboardTreeDataType> = {
39+
id: dashboard.dashboard_id,
3140
depth: 0,
3241
data: {
33-
name: d.name,
34-
id: d.dashboard_id,
42+
id: dashboard.dashboard_id,
43+
name: dashboard.name,
3544
type: 'DASHBOARD',
36-
createdBy: d.tags?.created_by,
37-
isNew: newIdList?.includes(d.dashboard_id),
38-
...d,
45+
createdBy: dashboard.tags?.created_by,
46+
isNew: newIdList?.includes(dashboard.dashboard_id),
47+
...dashboard,
3948
},
4049
};
41-
});
4250

43-
const rootNodes: TreeNode<DashboardTreeDataType>[] = [];
44-
const setDepth = (node, depth) => {
45-
node.depth = depth;
46-
if (!node.children) return;
47-
node.children.forEach((child) => {
48-
setDepth(child, depth + 1);
49-
});
50-
};
51-
Object.values(nodes).forEach((node) => {
52-
const folderId = node.data?.folderId;
53-
if (!folderId) {
54-
rootNodes.push(node);
55-
setDepth(node, 0);
51+
const folderNode = dashboard.folder_id ? folderNodeMap[dashboard.folder_id] : undefined;
52+
53+
if (folderNode) {
54+
dashboardNode.depth = 1;
55+
folderNode.children.push(dashboardNode);
5656
} else {
57-
const parentNode = nodes[folderId];
58-
if (parentNode) {
59-
parentNode.children = parentNode.children || [];
60-
parentNode.children.push(node);
61-
setDepth(node, parentNode.depth + 1);
62-
}
57+
rootNodes.push(dashboardNode);
6358
}
6459
});
6560

6661
return rootNodes;
6762
};
68-
6963
export const getSelectedDataTableItems = (folderItems: FolderModel[], dashboardItems: DashboardModel[], selectedIdMap: Record<string, boolean>): DashboardDataTableItem[] => {
7064
const _results: DashboardDataTableItem[] = [];
7165
const _selectedIdList: string[] = Object.keys(selectedIdMap).filter((key) => selectedIdMap[key]);

Diff for: apps/web/src/services/project/v2/components/ProjectDashboard.vue

+9-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type { DashboardModel, DashboardFolderModel } from '@/api-clients/dashboa
1111
import DashboardDetailBody from '@/services/dashboard-shared/dashboard-detail/DashboardDetailBody.vue';
1212
import { useProjectDashboardFolderQuery } from '@/services/project/v2/composables/queries/use-project-dashboard-folder-query';
1313
import { useProjectDashboardQuery } from '@/services/project/v2/composables/queries/use-project-dashboard-query';
14+
import { useProjectPageModalStore } from '@/services/project/v2/stores/project-page-modal-store';
1415
1516
interface Props {
1617
projectId?: string;
@@ -19,7 +20,7 @@ interface Props {
1920
}
2021
const props = defineProps<Props>();
2122
22-
23+
const projectPageModalStore = useProjectPageModalStore();
2324
/* Query */
2425
const {
2526
dashboardList,
@@ -40,13 +41,20 @@ const {
4041
const dashboardItems = computed<Array<DashboardModel>>(() => [...dashboardSharedList.value, ...dashboardList.value]);
4142
const dashboardFolderItems = computed<Array<DashboardFolderModel>>(() => [...dashboardFolderSharedList.value, ...dashboardFolderList.value]);
4243
44+
const handleSelectToolset = (toolsetId: string|undefined) => {
45+
if (toolsetId === 'edit') projectPageModalStore.openDashboardNameEditModal(props.dashboardId);
46+
if (toolsetId === 'move') projectPageModalStore.openDashboardChangeFolderModal(props.dashboardId);
47+
if (toolsetId === 'delete') projectPageModalStore.openDashboardDeleteModal(props.dashboardId);
48+
if (toolsetId === 'clone') projectPageModalStore.openDashboardCloneModal(props.dashboardId);
49+
};
4350
</script>
4451

4552
<template>
4653
<div class="project-dashboard">
4754
<dashboard-detail-body :dashboard-id="props.dashboardId"
4855
:dashboard-items="dashboardItems"
4956
:folder-items="dashboardFolderItems"
57+
@select-toolset="handleSelectToolset"
5058
/>
5159
</div>
5260
</template>

0 commit comments

Comments
 (0)