Skip to content

Commit b5e8233

Browse files
authored
fix(dashboard): solve dashboard list bug & refactor dataTable updating ux (#5656)
* fix(dashboard): solve dashboard list bug Signed-off-by: samuel.park <[email protected]> * fix(widget-clone): solve widget clone bug Signed-off-by: samuel.park <[email protected]> * fix(data-table): refactor updating dataTable function Signed-off-by: samuel.park <[email protected]> * fix(data-table): refactor dataTable updating (apply all) Signed-off-by: samuel.park <[email protected]> * chore: small fix Signed-off-by: samuel.park <[email protected]> * fix(data-table): improve dataTable creating ux Signed-off-by: samuel.park <[email protected]> --------- Signed-off-by: samuel.park <[email protected]>
1 parent 3648fda commit b5e8233

12 files changed

+179
-140
lines changed

apps/web/src/common/modules/widgets/_components/WidgetFormDataSourcePopover.vue

+6-2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ const dashboardDetailStore = useDashboardDetailInfoStore();
5454
const dashboardDetailState = dashboardDetailStore.state;
5555
const userStore = useUserStore();
5656
57+
const emit = defineEmits<{(e: 'scroll'): void;}>();
58+
5759
/* Query */
5860
const {
5961
dataTableList,
@@ -246,7 +248,7 @@ const handleClickDataSourceDomain = (domainName: DataTableSourceType) => {
246248
state.selectedDataSourceDomain = domainName;
247249
resetSelectedDataSource();
248250
};
249-
const handleCreateUnsavedTransform = (operator: DataTableOperator) => {
251+
const handleCreateUnsavedTransform = async (operator: DataTableOperator) => {
250252
const unsavedTransformData = {
251253
data_table_id: `UNSAVED-${getRandomId()}`,
252254
name: getDuplicatedDataTableName(`${operator} Data`, dataTableList.value),
@@ -260,13 +262,14 @@ const handleCreateUnsavedTransform = (operator: DataTableOperator) => {
260262
261263
const _isPrivate = widgetGenerateState.widgetId?.startsWith('private');
262264
const dataTableListQueryKey = _isPrivate ? widgetQueryKeys.privateDataTableListQueryKey : widgetQueryKeys.publicDataTableListQueryKey;
263-
queryClient.setQueryData(dataTableListQueryKey.value, (oldData: ListResponse<DataTableModel>) => (oldData.results?.length ? {
265+
await queryClient.setQueryData(dataTableListQueryKey.value, (oldData: ListResponse<DataTableModel>) => (oldData.results?.length ? {
264266
...oldData, results: [...oldData.results, unsavedTransformData],
265267
} : {
266268
...oldData, results: [unsavedTransformData],
267269
}));
268270
269271
state.showPopover = false;
272+
emit('scroll');
270273
};
271274
272275
const handleSelectPopperCondition = (condition: DataTableDataType) => {
@@ -329,6 +332,7 @@ const handleConfirmDataSource = async () => {
329332
...state.selectedDataSourceDomain === DATA_SOURCE_DOMAIN.COST ? costOptions : assetOptions,
330333
},
331334
});
335+
emit('scroll');
332336
}
333337
widgetGenerateStore.setDataTableCreateLoading(false);
334338
};

apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCard.vue

+5-2
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,15 @@ const LOADING_STATE = 'LOADING';
2020
interface Props {
2121
item?: PublicDataTableModel|PrivateDataTableModel;
2222
loadingCard?: boolean;
23+
loading?: boolean;
2324
}
2425
2526
const props = defineProps<Props>();
2627
const widgetGenerateStore = useWidgetGenerateStore();
2728
const widgetGenerateState = widgetGenerateStore.state;
2829
29-
const addContents = ref<WidgetFormDataTableCardAddContents|null>(null);
30-
const transformContents = ref<WidgetFormDataTableCardTransformContents|null>(null);
30+
const addContents = ref<typeof WidgetFormDataTableCardAddContents|null>(null);
31+
const transformContents = ref<typeof WidgetFormDataTableCardTransformContents|null>(null);
3132
3233
const state = reactive({
3334
dataTableId: computed<string|undefined>(() => props.item?.data_table_id),
@@ -56,11 +57,13 @@ defineExpose({
5657
ref="addContents"
5758
:item="props.item"
5859
:selected="state.selected"
60+
:loading="props.loading"
5961
/>
6062
<widget-form-data-table-card-transform-contents v-else-if="state.dataType === DATA_TABLE_TYPE.TRANSFORMED"
6163
ref="transformContents"
6264
:item="props.item"
6365
:selected="state.selected"
66+
:loading="props.loading"
6467
/>
6568
</div>
6669
</template>

apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardAddContents.vue

+15-17
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import { useDashboardDetailInfoStore } from '@/services/dashboards/stores/dashbo
5858
interface Props {
5959
selected: boolean;
6060
item: PublicDataTableModel|PrivateDataTableModel;
61+
loading?: boolean;
6162
}
6263
6364
type DataTableModel = PublicDataTableModel|PrivateDataTableModel;
@@ -246,7 +247,7 @@ const invalidateLoadQueries = async (data: DataTableModel) => {
246247
],
247248
});
248249
};
249-
const { mutateAsync: updateDataTableAndCascadeUpdate } = useMutation({
250+
const { mutateAsync: updateDataTableMutation } = useMutation({
250251
mutationFn: fetcher.updateDataTableFn,
251252
onSuccess: async (data) => {
252253
const dataTableListQueryKey = state.isPrivate ? keys.privateDataTableListQueryKey : keys.publicDataTableListQueryKey;
@@ -265,7 +266,6 @@ const { mutateAsync: updateDataTableAndCascadeUpdate } = useMutation({
265266
return oldData;
266267
});
267268
await invalidateLoadQueries(data);
268-
await cascadeUpdateDataTable(data.data_table_id);
269269
270270
setInitialDataTableForm();
271271
state.filterFormKey = getRandomId();
@@ -284,6 +284,9 @@ const { mutateAsync: updateWidget } = useMutation({
284284
? keys.privateWidgetQueryKey
285285
: keys.publicWidgetQueryKey;
286286
queryClient.setQueryData(widgetQueryKey.value, () => data);
287+
288+
showSuccessMessage(i18n.t('COMMON.WIDGETS.DATA_TABLE.FORM.UPDATE_DATA_TALBE_INVALID_SUCCESS'), '');
289+
widgetGenerateStore.setSelectedDataTableId(state.dataTableId);
287290
},
288291
onError: (e) => {
289292
showErrorMessage(e.message, e);
@@ -351,18 +354,7 @@ const updateDataTable = async (): Promise<DataTableModel|undefined> => {
351354
timediff: getTimeDiffValue(),
352355
},
353356
};
354-
355-
const result = await updateDataTableAndCascadeUpdate(updateParams);
356-
if (widget.value?.state === 'ACTIVE') {
357-
const _widgetOptions = cloneDeep(widget.value.options);
358-
const sanitizedOptions = sanitizeWidgetOptions(_widgetOptions, widget.value.widget_type, result);
359-
await updateWidget({
360-
widget_id: widgetGenerateState.widgetId,
361-
state: 'INACTIVE',
362-
options: sanitizedOptions,
363-
});
364-
}
365-
return result;
357+
return updateDataTableMutation(updateParams);
366358
};
367359
const deleteDataTableFn = (params: DataTableDeleteParameters): Promise<void> => {
368360
if (params.data_table_id.startsWith('private')) {
@@ -447,8 +439,14 @@ const handleUpdateDataTable = async () => {
447439
state.loading = true;
448440
const result = await updateDataTable();
449441
if (result) {
450-
showSuccessMessage(i18n.t('COMMON.WIDGETS.DATA_TABLE.FORM.UPDATE_DATA_TALBE_INVALID_SUCCESS'), '');
451-
widgetGenerateStore.setSelectedDataTableId(state.dataTableId);
442+
const _widgetOptions = cloneDeep(widget.value?.options);
443+
const sanitizedOptions = sanitizeWidgetOptions(_widgetOptions, widget.value?.widget_type, result);
444+
await updateWidget({
445+
widget_id: widgetGenerateState.widgetId,
446+
state: 'INACTIVE',
447+
options: sanitizedOptions,
448+
});
449+
await cascadeUpdateDataTable(result.data_table_id);
452450
}
453451
setTimeout(() => {
454452
state.loading = false;
@@ -557,7 +555,7 @@ defineExpose({
557555
/>
558556
<widget-form-data-table-card-footer :disabled="validationState.dataTableApplyInvalid"
559557
:changed="state.optionsChanged"
560-
:loading="state.loading"
558+
:loading="state.loading || props.loading"
561559
@delete="handleClickDeleteDataTable"
562560
@reset="handleClickResetDataTable"
563561
@update="handleUpdateDataTable"

apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformContents.vue

+15-16
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ import { useDashboardDetailInfoStore } from '@/services/dashboards/stores/dashbo
7070
interface Props {
7171
selected: boolean;
7272
item: PublicDataTableModel|PrivateDataTableModel;
73+
loading?: boolean;
7374
}
7475
type DataTableModel = PublicDataTableModel|PrivateDataTableModel;
7576
@@ -228,11 +229,10 @@ const invalidateLoadQueries = async (data: DataTableModel) => {
228229
],
229230
});
230231
};
231-
const { mutateAsync: updateDataTableAndCascadeUpdate } = useMutation({
232+
const { mutateAsync: updateDataTableMutation } = useMutation({
232233
mutationFn: fetcher.updateDataTableFn,
233234
onSuccess: async (data) => {
234235
await syncDataTableList(data);
235-
await cascadeUpdateDataTable(data.data_table_id);
236236
await invalidateLoadQueries(data);
237237
238238
setFailStatus(false);
@@ -250,6 +250,8 @@ const { mutateAsync: updateWidget } = useMutation({
250250
? keys.privateWidgetQueryKey
251251
: keys.publicWidgetQueryKey;
252252
queryClient.setQueryData(widgetQueryKey.value, () => data);
253+
showSuccessMessage(i18n.t('COMMON.WIDGETS.DATA_TABLE.FORM.UPDATE_DATA_TALBE_INVALID_SUCCESS'), '');
254+
widgetGenerateStore.setSelectedDataTableId(state.dataTableId);
253255
},
254256
onError: (e) => {
255257
showErrorMessage(e.message, e);
@@ -368,17 +370,8 @@ const updateDataTable = async (): Promise<DataTableModel|undefined> => {
368370
name: state.dataTableName,
369371
options: { [state.operator]: options() },
370372
};
371-
const result = await updateDataTableAndCascadeUpdate(updateParams);
372-
if (widget.value?.state === 'ACTIVE') {
373-
const _widgetOptions = cloneDeep(widget.value.options);
374-
const sanitizedOptions = sanitizeWidgetOptions(_widgetOptions, widget.value.widget_type, result);
375-
await updateWidget({
376-
widget_id: widgetGenerateState.widgetId,
377-
state: 'INACTIVE',
378-
options: sanitizedOptions,
379-
});
380-
}
381-
return result;
373+
374+
return updateDataTableMutation(updateParams);
382375
};
383376
const deleteDataTableFn = (params: DataTableDeleteParameters): Promise<void> => {
384377
if (params.data_table_id.startsWith('private')) {
@@ -431,8 +424,14 @@ const handleUpdateDataTable = async () => {
431424
state.loading = true;
432425
const result = await updateDataTable();
433426
if (result) {
434-
showSuccessMessage(i18n.t('COMMON.WIDGETS.DATA_TABLE.FORM.UPDATE_DATA_TALBE_INVALID_SUCCESS'), '');
435-
widgetGenerateStore.setSelectedDataTableId(result.data_table_id);
427+
const _widgetOptions = cloneDeep(widget.value?.options);
428+
const sanitizedOptions = sanitizeWidgetOptions(_widgetOptions, widget.value?.widget_type, result);
429+
await updateWidget({
430+
widget_id: widgetGenerateState.widgetId,
431+
state: 'INACTIVE',
432+
options: sanitizedOptions,
433+
});
434+
await cascadeUpdateDataTable(result.data_table_id);
436435
}
437436
setTimeout(() => {
438437
state.loading = false;
@@ -560,7 +559,7 @@ defineExpose({
560559
/>
561560
<widget-form-data-table-card-footer :disabled="state.applyDisabled"
562561
:changed="state.optionsChanged"
563-
:loading="state.loading"
562+
:loading="state.loading || props.loading"
564563
@delete="handleClickDeleteDataTable"
565564
@reset="handleClickResetDataTable"
566565
@update="handleUpdateDataTable"

apps/web/src/common/modules/widgets/_components/WidgetFormOverlay.vue

+3
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ const handleClickContinue = async () => {
133133
_updateParams.state = 'INACTIVE';
134134
}
135135
let widgetType = widget.value?.widget_type ?? 'table';
136+
if (widgetGenerateState.selectedWidgetName) {
137+
widgetType = widgetGenerateState.selectedWidgetName;
138+
}
136139
if (UNSUPPORTED_CHARTS_IN_PIVOT.includes(widgetType)) {
137140
widgetType = 'table';
138141
_updateParams.widget_type = widgetType;

apps/web/src/common/modules/widgets/_components/WidgetFormOverlayStep1.vue

+35-11
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ import WidgetFormDataSourcePopover from '@/common/modules/widgets/_components/Wi
2020
import WidgetFormDataTableCard from '@/common/modules/widgets/_components/WidgetFormDataTableCard.vue';
2121
import WidgetFormOverlayPreviewTable from '@/common/modules/widgets/_components/WidgetFormOverlayPreviewTable.vue';
2222
import { useWidgetFormQuery } from '@/common/modules/widgets/_composables/use-widget-form-query';
23+
import {
24+
createDataTableReferenceMap,
25+
getDataTableReferenceMapExecutionOrder,
26+
} from '@/common/modules/widgets/_helpers/widget-data-table-helper';
2327
import { useWidgetGenerateStore } from '@/common/modules/widgets/_store/widget-generate-store';
2428
2529
import { violet } from '@/styles/colors';
@@ -31,6 +35,7 @@ const widgetGenerateGetters = widgetGenerateStore.getters;
3135
3236
const dataTableContentsRef = ref<HTMLElement|null>(null);
3337
const dataTableCardRef = ref<typeof WidgetFormDataTableCard[]>([]);
38+
const scrollContainerRef = ref<HTMLElement|null>(null);
3439
3540
/* Query */
3641
const {
@@ -64,25 +69,41 @@ const displayState = reactive({
6469
resizing: false,
6570
clientY: null,
6671
transition: false,
72+
loading: false,
6773
});
6874
69-
7075
/* Event */
71-
7276
const handleClickAllApply = async () => {
77+
displayState.loading = true;
7378
try {
74-
dataTableCardRef.value.forEach((_ref) => {
75-
if (_ref) {
76-
if (typeof _ref?.updateDataTable === 'function') {
77-
_ref?.updateDataTable();
78-
}
79-
}
80-
});
79+
const dataTableReferenceMap = createDataTableReferenceMap(dataTableList.value);
80+
const executionOrderList = getDataTableReferenceMapExecutionOrder(dataTableReferenceMap);
81+
82+
const dataTableRefs = Object.fromEntries(
83+
dataTableCardRef.value
84+
.filter((_ref) => _ref)
85+
.map((_ref) => [_ref.item?.data_table_id, _ref]),
86+
);
87+
88+
await Promise.allSettled(
89+
executionOrderList.map(async (dataTableId) => {
90+
const dataTableRef = dataTableRefs[dataTableId];
91+
if (dataTableRef) await dataTableRef.updateDataTable();
92+
}),
93+
);
94+
8195
showSuccessMessage(i18n.t('COMMON.WIDGETS.DATA_TABLE.APPLY_ALL_SUCCESS'), '');
8296
} catch (e) {
8397
ErrorHandler.handleError(e);
98+
} finally {
99+
displayState.loading = false;
84100
}
85101
};
102+
const handleScrollDataTableContainer = () => {
103+
if (!scrollContainerRef.value) return;
104+
const { scrollWidth } = scrollContainerRef.value;
105+
scrollContainerRef.value.scrollTo({ left: scrollWidth, behavior: 'smooth' });
106+
};
86107
87108
/* Hide Toggle */
88109
const offTransition = () => { displayState.transition = false; };
@@ -145,17 +166,20 @@ onMounted(async () => {
145166
class="data-table-contents"
146167
>
147168
<div class="data-table-area">
148-
<div class="data-table-scroll-wrapper">
169+
<div ref="scrollContainerRef"
170+
class="data-table-scroll-wrapper"
171+
>
149172
<div class="data-table-contents-wrapper">
150173
<widget-form-data-table-card v-for="(dataTable, index) in displayState.dataTablesSortedByCreatedAt"
151174
:ref="el => dataTableCardRef[index] = el"
152175
:key="`data-table-${dataTable.data_table_id}`"
153176
:item="dataTable"
177+
:loading="displayState.loading"
154178
/>
155179
<widget-form-data-table-card v-if="widgetGenerateState.dataTableCreateLoading"
156180
loading-card
157181
/>
158-
<widget-form-data-source-popover />
182+
<widget-form-data-source-popover @scroll="handleScrollDataTableContainer" />
159183
<div v-if="!dataTableList.length"
160184
class="empty-data-table-guide"
161185
>

0 commit comments

Comments
 (0)