Skip to content

Commit 47e6732

Browse files
committed
feat(project-dashboard-clone): create project-dashboard clone action
Signed-off-by: samuel.park <[email protected]>
1 parent 36e1991 commit 47e6732

File tree

6 files changed

+180
-19
lines changed

6 files changed

+180
-19
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/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/project/v2/components/ProjectDashboard.vue

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const handleSelectToolset = (toolsetId: string|undefined) => {
4545
if (toolsetId === 'edit') projectPageModalStore.openDashboardNameEditModal(props.dashboardId);
4646
if (toolsetId === 'move') projectPageModalStore.openDashboardChangeFolderModal(props.dashboardId);
4747
if (toolsetId === 'delete') projectPageModalStore.openDashboardDeleteModal(props.dashboardId);
48+
if (toolsetId === 'clone') projectPageModalStore.openDashboardCloneModal(props.dashboardId);
4849
};
4950
</script>
5051

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<script lang="ts" setup>
2+
import { computed, watch } from 'vue';
3+
import { useRouter } from 'vue-router/composables';
4+
5+
import {
6+
PButtonModal, PFieldGroup, PTextInput,
7+
} from '@cloudforet/mirinae';
8+
import { getClonedName } from '@cloudforet/utils';
9+
10+
import { RESOURCE_GROUP } from '@/api-clients/_common/schema/constant';
11+
import type { DashboardCreateParams, DashboardModel } from '@/api-clients/dashboard/_types/dashboard-type';
12+
import { i18n } from '@/translations';
13+
14+
import { showErrorMessage } from '@/lib/helper/notice-alert-helper';
15+
16+
import { useFormValidator } from '@/common/composables/form-validator';
17+
18+
19+
import { useDashboardCloneAction } from '@/services/dashboard-shared/core/actions/use-dashboard-clone-action';
20+
import { useProjectDashboardQuery } from '@/services/project/v2/composables/queries/use-project-dashboard-query';
21+
import { useProjectOrGroupId } from '@/services/project/v2/composables/use-project-or-group-id';
22+
import { PROJECT_ROUTE_V2 } from '@/services/project/v2/routes/route-constant';
23+
import { useProjectPageModalStore } from '@/services/project/v2/stores/project-page-modal-store';
24+
import type { ProjectPageContextType } from '@/services/project/v2/types/project-page-context-type';
25+
26+
interface Props {
27+
projectGroupOrProjectId: string;
28+
}
29+
const props = defineProps<Props>();
30+
const router = useRouter();
31+
const projectPageModalStore = useProjectPageModalStore();
32+
const visible = computed(() => projectPageModalStore.state.dashboardCloneModalVisible);
33+
const dashboardId = computed(() => projectPageModalStore.state.targetId);
34+
const projectGroupOrProjectId = computed(() => props.projectGroupOrProjectId);
35+
const { projectGroupId, projectId } = useProjectOrGroupId(projectGroupOrProjectId);
36+
37+
/* Query */
38+
const {
39+
dashboardList,
40+
dashboardSharedList,
41+
invalidateAllQueries: invalidateDashboardList,
42+
} = useProjectDashboardQuery({
43+
projectId,
44+
projectGroupId,
45+
});
46+
47+
const projectContext = computed<ProjectPageContextType>(() => {
48+
if (projectGroupId.value) return 'PROJECT_GROUP';
49+
if (projectId.value) return 'PROJECT';
50+
return undefined;
51+
});
52+
const existingNameList = computed<string[]>(() => dashboardList.value.map((d) => d.name));
53+
const currentDashboard = computed<DashboardModel|undefined>(() => [...dashboardList.value, ...dashboardSharedList.value].find((d) => d.dashboard_id === dashboardId.value));
54+
55+
const {
56+
forms: {
57+
name,
58+
},
59+
setForm,
60+
invalidState,
61+
invalidTexts,
62+
isAllValid,
63+
} = useFormValidator({
64+
name: '',
65+
}, {
66+
name(value: string) {
67+
if (value.length > 100) return i18n.t('DASHBOARDS.FORM.VALIDATION_DASHBOARD_NAME_LENGTH');
68+
if (!value.trim().length) return i18n.t('DASHBOARDS.FORM.VALIDATION_DASHBOARD_NAME_INPUT');
69+
if (existingNameList.value.find((d) => d === value)) {
70+
return i18n.t('DASHBOARDS.FORM.VALIDATION_DASHBOARD_NAME_UNIQUE');
71+
}
72+
return '';
73+
},
74+
});
75+
76+
/* Event */
77+
const handleConfirm = async () => {
78+
if (!isAllValid) return;
79+
80+
const _sharedDashboard: DashboardCreateParams = {
81+
name: name.value,
82+
resource_group: projectContext.value === 'PROJECT_GROUP' ? RESOURCE_GROUP.WORKSPACE : RESOURCE_GROUP.PROJECT,
83+
};
84+
85+
if (projectContext.value === 'PROJECT_GROUP') {
86+
_sharedDashboard.project_group_id = projectGroupId.value;
87+
} else if (projectContext.value === 'PROJECT') {
88+
_sharedDashboard.project_id = projectId.value;
89+
}
90+
91+
mutate(_sharedDashboard as DashboardCreateParams);
92+
};
93+
94+
const { mutate, isPending: dashboardCloneLoading } = useDashboardCloneAction({
95+
dashboardId,
96+
onSuccess: async (data: DashboardModel) => {
97+
invalidateDashboardList();
98+
projectPageModalStore.closeDashboardCloneModal();
99+
await router.replace({
100+
name: PROJECT_ROUTE_V2._NAME,
101+
params: {
102+
projectGroupOrProjectId: projectGroupOrProjectId.value,
103+
dashboardId: data.dashboard_id,
104+
},
105+
}).catch(() => {});
106+
},
107+
onError: (e) => {
108+
showErrorMessage(i18n.t('DASHBOARDS.FORM.ALT_E_CLONE_DASHBOARD'), e);
109+
},
110+
});
111+
112+
watch(visible, (_visible) => {
113+
if (_visible) {
114+
const _clonedName = getClonedName(existingNameList.value, currentDashboard.value?.name ?? '');
115+
setForm('name', _clonedName);
116+
}
117+
}, { immediate: true });
118+
</script>
119+
120+
<template>
121+
<p-button-modal :visible="visible"
122+
:header-title="$t('DASHBOARDS.FORM.CLONE_TITLE')"
123+
size="sm"
124+
:disabled="!isAllValid"
125+
:loading="dashboardCloneLoading"
126+
class="dashboard-clone-modal"
127+
@confirm="handleConfirm"
128+
@close="projectPageModalStore.closeDashboardCloneModal"
129+
@cancel="projectPageModalStore.closeDashboardCloneModal"
130+
@closed="projectPageModalStore.resetTarget"
131+
>
132+
<template #body>
133+
<p-field-group :label="$t('DASHBOARDS.FORM.LABEL_DASHBOARD_NAME')"
134+
:invalid="invalidState.name"
135+
:invalid-text="invalidTexts.name"
136+
required
137+
>
138+
<template #default="{ invalid }">
139+
<p-text-input :value="name"
140+
:invalid="invalid"
141+
@update:value="setForm('name', $event)"
142+
/>
143+
</template>
144+
</p-field-group>
145+
</template>
146+
</p-button-modal>
147+
</template>
148+
149+
<style lang="postcss" scoped>
150+
.dashboard-clone-modal {
151+
.p-text-input {
152+
@apply w-full;
153+
}
154+
}
155+
</style>

Diff for: apps/web/src/services/project/v2/pages/ProjectMainPage.vue

+4
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const ProjectDashboardFolderFormModal = () => import('@/services/project/v2/comp
3636
const ProjectDashboardNameEditModal = () => import('@/services/project/v2/components/ProjectDashboardNameEditModal.vue');
3737
const ProjectDashboardChangeFolderModal = () => import('@/services/project/v2/components/ProjectDashboardChangeFolderModal.vue');
3838
const ProjectDashboardDeleteModal = () => import('@/services/project/v2/components/ProjectDashboardDeleteModal.vue');
39+
const ProjectDashboardCloneModal = () => import('@/services/project/v2/components/ProjectDashboardCloneModal.vue');
3940
const props = defineProps<{
4041
projectGroupOrProjectId?: string;
4142
dashboardId?: string;
@@ -165,6 +166,9 @@ const handleUpdateDashboardId = (id?: string) => {
165166
<project-dashboard-delete-modal v-if="projectPageModelStore.state.dashboardDeleteModalVisible"
166167
:project-group-or-project-id="props.projectGroupOrProjectId"
167168
/>
169+
<project-dashboard-clone-modal v-if="projectPageModelStore.state.dashboardCloneModalVisible"
170+
:project-group-or-project-id="props.projectGroupOrProjectId"
171+
/>
168172
</template>
169173
</div>
170174
</template>

Diff for: apps/web/src/services/project/v2/stores/project-page-modal-store.ts

+8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const useProjectPageModalStore = defineStore('project-page-modal', () =>
1818
dashboardNameEditModalVisible: false,
1919
dashboardChangeFolderModalVisible: false,
2020
dashboardDeleteModalVisible: false,
21+
dashboardCloneModalVisible: false,
2122
});
2223

2324
const actions = {
@@ -147,6 +148,13 @@ export const useProjectPageModalStore = defineStore('project-page-modal', () =>
147148
closeDashboardDeleteModal() {
148149
state.dashboardDeleteModalVisible = false;
149150
},
151+
openDashboardCloneModal(targetDashboardId: string) {
152+
state.targetId = targetDashboardId;
153+
state.dashboardCloneModalVisible = true;
154+
},
155+
closeDashboardCloneModal() {
156+
state.dashboardCloneModalVisible = false;
157+
},
150158
resetTarget() {
151159
state.targetId = undefined;
152160
state.targetType = undefined;

0 commit comments

Comments
 (0)