diff --git a/apps/web/package.json b/apps/web/package.json index af2ed36885..ea5575e75e 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -29,6 +29,7 @@ "@cloudforet/utils": "*", "@gtm-support/vue2-gtm": "^1.3.0", "@lottiefiles/vue-lottie-player": "^1.1.0", + "@tanstack/vue-query": "^5.62.7", "@tiptap/core": "^2.0.3", "@tiptap/extension-color": "^2.0.3", "@tiptap/extension-link": "^2.0.3", diff --git a/apps/web/src/common/modules/widgets/_components/WidgetCustomLagend.vue b/apps/web/src/common/modules/widgets/_components/WidgetCustomLegend.vue similarity index 100% rename from apps/web/src/common/modules/widgets/_components/WidgetCustomLagend.vue rename to apps/web/src/common/modules/widgets/_components/WidgetCustomLegend.vue diff --git a/apps/web/src/common/modules/widgets/_components/WidgetFieldDropdownAndMax.vue b/apps/web/src/common/modules/widgets/_components/WidgetFieldDropdownAndMax.vue index 4288a2e7fc..52403db62a 100644 --- a/apps/web/src/common/modules/widgets/_components/WidgetFieldDropdownAndMax.vue +++ b/apps/web/src/common/modules/widgets/_components/WidgetFieldDropdownAndMax.vue @@ -1,6 +1,6 @@ diff --git a/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformFormWrapper.vue b/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformFormWrapper.vue new file mode 100644 index 0000000000..49489dc448 --- /dev/null +++ b/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformFormWrapper.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformJoin.vue b/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformJoin.vue new file mode 100644 index 0000000000..6f935843d4 --- /dev/null +++ b/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformJoin.vue @@ -0,0 +1,245 @@ + + + + + diff --git a/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformPivotForm.vue b/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformPivotForm.vue new file mode 100644 index 0000000000..1d38025cda --- /dev/null +++ b/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformPivotForm.vue @@ -0,0 +1,415 @@ + + + + + diff --git a/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformQuery.vue b/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformQuery.vue new file mode 100644 index 0000000000..e7398e564b --- /dev/null +++ b/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformQuery.vue @@ -0,0 +1,181 @@ + + + + + diff --git a/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformValueMapping.vue b/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformValueMapping.vue new file mode 100644 index 0000000000..b6b95b2126 --- /dev/null +++ b/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardTransformValueMapping.vue @@ -0,0 +1,307 @@ + + + + + diff --git a/apps/web/src/common/modules/widgets/_components/WidgetFormOverlayPreviewTable.vue b/apps/web/src/common/modules/widgets/_components/WidgetFormOverlayPreviewTable.vue index fb20c40eda..852ce2469d 100644 --- a/apps/web/src/common/modules/widgets/_components/WidgetFormOverlayPreviewTable.vue +++ b/apps/web/src/common/modules/widgets/_components/WidgetFormOverlayPreviewTable.vue @@ -4,22 +4,30 @@ import { computed, onUnmounted, reactive, watch, } from 'vue'; +import { useQuery } from '@tanstack/vue-query'; import bytes from 'bytes'; +import { SpaceConnector } from '@cloudforet/core-lib/space-connector'; import { PToolbox, PI, PSelectDropdown, PEmpty, PSpinner, } from '@cloudforet/mirinae'; import type { MenuItem } from '@cloudforet/mirinae/src/controls/context-menu/type'; -import type { ToolboxOptions } from '@cloudforet/mirinae/src/controls/toolbox/type'; import { byteFormatter, numberFormatter } from '@cloudforet/utils'; +import type { ListResponse } from '@/schema/_common/api-verbs/list'; import type { Page } from '@/schema/_common/type'; +import type { PrivateDataTableModel } from '@/schema/dashboard/private-data-table/model'; +import type { DataTableLoadParameters } from '@/schema/dashboard/public-data-table/api-verbs/load'; +import type { PublicDataTableModel } from '@/schema/dashboard/public-data-table/model'; import { i18n } from '@/translations'; import { useAllReferenceStore } from '@/store/reference/all-reference-store'; import type { ProjectReferenceMap } from '@/store/reference/project-reference-store'; -import { REFERENCE_FIELD_MAP } from '@/common/modules/widgets/_constants/widget-constant'; +import { DATA_TABLE_OPERATOR } from '@/common/modules/widgets/_constants/data-table-constant'; +import { REFERENCE_FIELD_MAP, WIDGET_LOAD_STALE_TIME } from '@/common/modules/widgets/_constants/widget-constant'; +import { normalizeAndSerialize } from '@/common/modules/widgets/_helpers/global-variable-helper'; +import { sortObjectByKeys } from '@/common/modules/widgets/_helpers/widget-data-table-helper'; import { sortWidgetTableFields } from '@/common/modules/widgets/_helpers/widget-helper'; import { useWidgetGenerateStore } from '@/common/modules/widgets/_store/widget-generate-store'; import type { DataInfo } from '@/common/modules/widgets/types/widget-model'; @@ -29,6 +37,10 @@ import { gray, white } from '@/styles/colors'; import { SIZE_UNITS } from '@/services/asset-inventory/constants/asset-analysis-constant'; import { GRANULARITY } from '@/services/cost-explorer/constants/cost-explorer-constant'; import type { Granularity } from '@/services/cost-explorer/types/cost-explorer-query-type'; +import { useDashboardDetailInfoStore } from '@/services/dashboards/stores/dashboard-detail-info-store'; + + + interface PreviewTableField { type: 'LABEL' | 'DATA' | 'DIVIDER'; @@ -36,18 +48,23 @@ interface PreviewTableField { sortKey?: string; } +type DataTableLoadData = ListResponse<{ + [key: string]: string|number; +}>; + + +type DataTableModel = PublicDataTableModel|PrivateDataTableModel; + const widgetGenerateStore = useWidgetGenerateStore(); const widgetGenerateState = widgetGenerateStore.state; const widgetGenerateGetters = widgetGenerateStore.getters; const allReferenceStore = useAllReferenceStore(); +const dashboardDetailStore = useDashboardDetailInfoStore(); +const dashboardDetailGetters = dashboardDetailStore.getters; const storeState = reactive({ - previewData: computed(() => widgetGenerateState.previewData), - selectedDataTableId: computed(() => widgetGenerateState.selectedDataTableId), - selectedDataTable: computed(() => widgetGenerateGetters.selectedDataTable), - loading: computed(() => widgetGenerateState.dataTableLoadLoading), - dataTableUpdating: computed(() => widgetGenerateState.dataTableUpdating), - selectedGranularity: computed(() => widgetGenerateState.selectedPreviewGranularity), + selectedDataTableId: computed(() => widgetGenerateState.selectedDataTableId), + selectedDataTable: computed(() => widgetGenerateGetters.selectedDataTable), dataTableLoadFailed: computed(() => widgetGenerateState.dataTableLoadFailed), // reference project: computed(() => allReferenceStore.getters.project), @@ -57,18 +74,26 @@ const storeState = reactive({ }); const state = reactive({ - data: undefined, - labelFields: computed(() => (storeState.loading ? [] : sortWidgetTableFields(Object.keys(storeState.selectedDataTable?.labels_info ?? {})))), - dataFields: computed(() => (storeState.loading ? [] : sortWidgetTableFields(Object.keys(storeState.selectedDataTable?.data_info ?? {})))), + data: computed(() => queryResult?.data?.value || null), + labelFields: computed(() => (dataTableLoading.value === true ? [] : sortWidgetTableFields(Object.keys(storeState.selectedDataTable?.labels_info ?? {})))), + dataFields: computed(() => (dataTableLoading.value === true ? [] : sortWidgetTableFields(Object.keys(storeState.selectedDataTable?.data_info ?? {})))), dataInfo: computed(() => storeState.selectedDataTable?.data_info), + isPivot: computed(() => storeState.selectedDataTable?.operator === DATA_TABLE_OPERATOR.PIVOT), + isAutoTypeColumnPivot: computed(() => state.isPivot && !!storeState.selectedDataTable?.options?.[DATA_TABLE_OPERATOR.PIVOT]?.limit), + // pivotSortKeys: computed(() => (state.isPivot ? storeState.selectedDataTable?.sort_keys ?? [] : [])), fields: computed(() => { - if (!storeState.selectedDataTableId || !storeState.previewData?.results?.length) { + if (!storeState.selectedDataTableId || !state.data?.results?.length) { return [{ type: 'DIVIDER', name: '', }]; } + // const sortBySortKeys = (targetArray: string[]): string[] => sortBy(targetArray, (item) => { + // const index = state.pivotSortKeys.indexOf(item); + // return index === -1 ? Infinity : index; + // }); + return [ ...state.labelFields.map((key) => ({ type: 'LABEL', @@ -76,7 +101,7 @@ const state = reactive({ sortKey: key, })) .filter((field) => { - const _granularity = storeState.selectedGranularity; + const _granularity = state.selectedGranularity; if (state.labelFields.some((d) => d === 'Year' || d === 'Month' || d === 'Date')) { if (_granularity === GRANULARITY.DAILY && (field.name === 'Year' || field.name === 'Month')) return false; if (_granularity === GRANULARITY.MONTHLY && (field.name === 'Year' || field.name === 'Day')) return false; @@ -113,49 +138,41 @@ const state = reactive({ label: 'Yearly', }, ])), + selectedGranularity: GRANULARITY.MONTHLY, dividerStyle: computed(() => ({ padding: '0', width: '1px', 'min-width': '1px', - backgroundColor: storeState.selectedDataTableId && storeState.previewData?.results?.length && !storeState.loading ? gray[900] : white, + backgroundColor: storeState.selectedDataTableId && state.data?.results?.length && !dataTableLoading.value ? gray[900] : white, })), thisPage: 1, + pageSize: 15, + page: computed(() => { + if (state.thisPage || state.pageSize) return { start: 1 + ((state.thisPage - 1) * state.pageSize), limit: state.pageSize }; + return undefined; + }), }); const emptyState = reactive({ isUnavailableDataTable: computed(() => storeState.selectedDataTable?.state === 'UNAVAILABLE'), title: computed(() => { if (!storeState.selectedDataTableId) return i18n.t('COMMON.WIDGETS.PREVIEW_TABLE_EMPTY_TITLE'); - if (storeState.dataTableLoadFailed && emptyState.isUnavailableDataTable) return i18n.t('DASHBOARDS.WIDGET.DATA_TABLE_LOAD_INVALID_GLOBAL_VARIALBE_TITLE'); + // if (storeState.dataTableLoadFailed && emptyState.isUnavailableDataTable) return i18n.t('DASHBOARDS.WIDGET.DATA_TABLE_LOAD_INVALID_GLOBAL_VARIALBE_TITLE'); return ''; }), description: computed(() => { if (!storeState.selectedDataTableId) return i18n.t('COMMON.WIDGETS.PREVIEW_TABLE_EMPTY_DESC'); - if (storeState.dataTableLoadFailed && emptyState.isUnavailableDataTable) return i18n.t('DASHBOARDS.WIDGET.DATA_TABLE_LOAD_INVALID_GLOBAL_VARIABLE_DESC'); - if (!storeState.previewData?.results?.length) return i18n.t('DASHBOARDS.WIDGET.NO_DATA'); + if (isError.value === true) return errorMessage.value; + // if (storeState.dataTableLoadFailed && emptyState.isUnavailableDataTable) return i18n.t('DASHBOARDS.WIDGET.DATA_TABLE_LOAD_INVALID_GLOBAL_VARIABLE_DESC'); + if (!state.data?.results?.length) return i18n.t('DASHBOARDS.WIDGET.NO_DATA'); return ''; }), }); /* Events */ const handleSelectGranularity = async (granularity: Granularity) => { - if (!storeState.selectedDataTableId) return; - widgetGenerateStore.setSelectedPreviewGranularity(granularity); - await widgetGenerateStore.loadDataTable({ - data_table_id: storeState.selectedDataTableId, - }); + state.selectedGranularity = granularity; }; -const handleChangeToolbox = async (options: ToolboxOptions) => { - if (!storeState.selectedDataTableId) return; - let page = undefined as Page|undefined; - if (options.pageStart) page = { start: options.pageStart, limit: state.thisPage * 15 }; - if (options.pageLimit) page = { start: 1, limit: options.pageLimit }; - await widgetGenerateStore.loadDataTable({ - data_table_id: storeState.selectedDataTableId, - page, - sort: state.sortBy, - }); -}; const handleClickSort = async (sortKey: string) => { let resultSortBy: { key: string; desc: boolean }[]; @@ -165,11 +182,6 @@ const handleClickSort = async (sortKey: string) => { resultSortBy = [{ key: sortKey, desc: true }]; } state.sortBy = resultSortBy; - if (!storeState.selectedDataTableId) return; - await widgetGenerateStore.loadDataTable({ - data_table_id: storeState.selectedDataTableId, - sort: resultSortBy, - }); state.thisPage = 1; }; @@ -214,20 +226,52 @@ const getSortIcon = (field: PreviewTableField) => { // return `( ${value} ${key} )`; // }; -watch(() => storeState.selectedDataTableId, async (dataTableId) => { +const fetchWidgetData = async (params: DataTableLoadParameters): Promise => { + const defaultFetcher = storeState.selectedDataTableId?.startsWith('private') + ? SpaceConnector.clientV2.dashboard.privateDataTable.load + : SpaceConnector.clientV2.dashboard.publicDataTable.load; + const res = await defaultFetcher(params); + return res; +}; + +const queryKey = computed(() => [ + 'data-table-load', + storeState.selectedDataTableId, + { + granularity: state.selectedGranularity, + dataTableOptions: JSON.stringify(sortObjectByKeys(storeState.selectedDataTable?.options ?? {})), + sortBy: state.sortBy, + thisPage: state.thisPage, + pageSize: state.pageSize, + vars: normalizeAndSerialize(dashboardDetailGetters.dashboardInfo?.vars ?? {}), + }, +]); + +const queryResult = useQuery({ + queryKey, + queryFn: () => fetchWidgetData({ + data_table_id: storeState.selectedDataTableId, + granularity: state.selectedGranularity, + sort: state.sortBy, + page: state.page, + vars: dashboardDetailGetters.dashboardInfo?.vars, + }), + enabled: computed(() => storeState.selectedDataTableId !== undefined), + staleTime: WIDGET_LOAD_STALE_TIME, + retry: 2, +}); + +const dataTableLoading = computed(() => queryResult.isLoading.value || queryResult.isFetching.value); +const isError = computed(() => queryResult.isError.value); +const errorMessage = computed(() => queryResult.error?.value?.message); + + +watch([() => storeState.selectedDataTableId, () => storeState.selectedDataTable], async ([dataTableId]) => { if (!dataTableId) return; state.thisPage = 1; state.sortBy = []; - widgetGenerateStore.setDataTableUpdating(true); - await widgetGenerateStore.loadDataTable({}); }, { immediate: true }); -watch(() => storeState.dataTableUpdating, () => { - if (storeState.dataTableUpdating) { - state.thisPage = 1; - state.sortBy = []; - } -}); onUnmounted(() => { widgetGenerateStore.setDataTableLoadFailed(false); @@ -241,10 +285,9 @@ onUnmounted(() => { :searchable="false" :refreshable="false" :page-size-options="[15, 30, 45]" - :page-size="15" + :page-size.sync="state.pageSize" :this-page.sync="state.thisPage" - :total-count="storeState.previewData.total_count" - @change="handleChangeToolbox" + :total-count="state.data?.total_count" > @@ -363,9 +327,6 @@ onMounted(() => {

{{ $t('DASHBOARDS.WIDGET.OVERLAY.STEP_2.VALIDATION_MODAL.GEO_MAP_DESC') }}

-

- {{ $t('DASHBOARDS.WIDGET.OVERLAY.STEP_2.VALIDATION_MODAL.PROGRESS_CARD_DESC') }} -

{{ $t('DASHBOARDS.WIDGET.OVERLAY.STEP_2.VALIDATION_MODAL.DESC', { number: state.formErrorModalValue ?? state.defaultValidationConfig?.defaultMaxCount, diff --git a/apps/web/src/common/modules/widgets/_components/WidgetFrame.vue b/apps/web/src/common/modules/widgets/_components/WidgetFrame.vue index 661a826446..a8c10d12b5 100644 --- a/apps/web/src/common/modules/widgets/_components/WidgetFrame.vue +++ b/apps/web/src/common/modules/widgets/_components/WidgetFrame.vue @@ -16,12 +16,13 @@ import { i18n } from '@/translations'; import { WIDGET_WIDTH_STR_MAP } from '@/common/modules/widgets/_constants/widget-display-constant'; import { WIDGET_HEIGHT } from '@/common/modules/widgets/_constants/widget-field-constant'; -import type { WidgetFrameEmit } from '@/common/modules/widgets/types/widget-display-type'; +import type { WidgetHeightValue } from '@/common/modules/widgets/_widget-fields/widget-height/type'; +import type { WidgetEmit } from '@/common/modules/widgets/types/widget-display-type'; import type { WidgetFrameProps } from '@/common/modules/widgets/types/widget-frame-type'; const props = defineProps(); -const emit = defineEmits(); +const emit = defineEmits(); const router = useRouter(); const state = reactive({ isFull: computed(() => props.size === WIDGET_SIZE.full), @@ -55,6 +56,10 @@ const state = reactive({ label: WIDGET_WIDTH_STR_MAP[size], }))), unitText: computed(() => Object.values(props.unitMap || {}).join(', ') || '--'), + widgetHeight: computed(() => { + const widgetHeight = props.widgetOptions?.widgetHeight?.value as WidgetHeightValue; + return widgetHeight?.type ?? WIDGET_HEIGHT.default; + }), }); /* Event */ @@ -96,7 +101,7 @@ watch(() => state.etcMenuVisible, (_etcMenuVisible) => { :class="{ full: state.isFull, [props.size]: props.size, - [`widget-height-${props.widgetOptions?.widgetHeight?.value ?? WIDGET_HEIGHT.default}`]: true, + [`widget-height-${state.widgetHeight}`]: true, }" :style="{ width: (props.width && !state.isFull) ? `${props.width}px` : '100%'}" > diff --git a/apps/web/src/common/modules/widgets/_components/__WidgetFieldDropdownAndMax.vue b/apps/web/src/common/modules/widgets/_components/__WidgetFieldDropdownAndMax.vue new file mode 100644 index 0000000000..4288a2e7fc --- /dev/null +++ b/apps/web/src/common/modules/widgets/_components/__WidgetFieldDropdownAndMax.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/apps/web/src/common/modules/widgets/_composables/use-granularity-menu-items.ts b/apps/web/src/common/modules/widgets/_composables/use-granularity-menu-items.ts deleted file mode 100644 index 52f14a948f..0000000000 --- a/apps/web/src/common/modules/widgets/_composables/use-granularity-menu-items.ts +++ /dev/null @@ -1,122 +0,0 @@ -import type { ComputedRef, UnwrapRef } from 'vue'; -import { - computed, reactive, toRefs, watch, -} from 'vue'; - -import { get } from 'lodash'; - -import type { MenuItem } from '@cloudforet/mirinae/types/controls/context-menu/type'; - -import type { Granularity } from '@/schema/dashboard/_types/widget-type'; - -import { DATE_FIELD } from '@/common/modules/widgets/_constants/widget-constant'; -import { sortWidgetTableFields } from '@/common/modules/widgets/_helpers/widget-helper'; -import type { WidgetFieldComponentProps, WidgetFieldOptions, WidgetFieldName } from '@/common/modules/widgets/types/widget-field-type'; -import type { WidgetFieldValues } from '@/common/modules/widgets/types/widget-field-value-type'; -import type { LabelsInfo } from '@/common/modules/widgets/types/widget-model'; - -interface UseGranularityMenuItemState { - selectedValue: ComputedRef; - granularity: UnwrapRef; - labelInfo: ComputedRef; - isDateSeparated: ComputedRef; - labelsMenuItem: ComputedRef; -} - -const labelsInfoValueRouteMap: { - [key in WidgetFieldName]?: string; -} = { - tableDataField: 'value', - xAxis: 'value', - yAxis: 'value', - stackBy: 'value', - lineBy: 'value', - groupBy: 'value', - categoryBy: 'value', -}; - -type DateField = typeof DATE_FIELD[keyof typeof DATE_FIELD]; - -export const useGranularityMenuItem = (props: WidgetFieldComponentProps, fieldName?: WidgetFieldName) => { - const _state = reactive({ - selectedValueList: [] as string[], - currentSelectedValue: [] as string|string[], - refinedSelectedValueList: computed(() => _state.selectedValueList.filter((item) => { - if (Array.isArray(_state.currentSelectedValue)) { - return !_state.currentSelectedValue.includes(item); - } - return item !== _state.currentSelectedValue; - })), - usedLabelsField: computed(() => { - const usedLabelsInfo: DateField[] = []; - _state.refinedSelectedValueList.forEach((item) => { - if (Object.values(DATE_FIELD).includes(item)) { - usedLabelsInfo.push(item); - } - }); - return usedLabelsInfo; - }), - }); - - const state = reactive({ - selectedValue: computed(() => (fieldName ? props.allValueMap[fieldName] : undefined)), - granularity: 'MONTHLY', - labelInfo: computed(() => props.dataTable?.labels_info ?? {}), - isDateSeparated: computed(() => [DATE_FIELD.DAY, DATE_FIELD.MONTH, DATE_FIELD.YEAR].every((item) => Object.keys(state.labelInfo).includes(item))), - labelsMenuItem: computed(() => { - if (!state.labelInfo) return undefined; - const originLabelsMenuItem = sortWidgetTableFields(Object.keys(state.labelInfo)).map((key) => { - if (Object.values(DATE_FIELD).includes(key)) { - return ({ - name: key, - label: key, - // disabled: _state.usedLabelsField.includes(key), - }); - } - return ({ - name: key, - label: key, - }); - }); - if (state.isDateSeparated) { - const dateRemovedLabelsMenuItem = originLabelsMenuItem.filter((item) => item.name !== DATE_FIELD.DATE); - if (state.granularity === 'MONTHLY') { - return dateRemovedLabelsMenuItem.filter((item) => item.name !== DATE_FIELD.DAY && item.name !== DATE_FIELD.YEAR); - } if (state.granularity === 'YEARLY') { - return dateRemovedLabelsMenuItem.filter((item) => item.name !== DATE_FIELD.DAY && item.name !== DATE_FIELD.MONTH); - } - return dateRemovedLabelsMenuItem.filter((item) => item.name !== DATE_FIELD.MONTH && item.name !== DATE_FIELD.YEAR); - } - return originLabelsMenuItem; - }), - }); - watch(() => props.allValueMap, (formValueMap) => { - const currentFieldValue: string|string[] = get(formValueMap, `${fieldName}.${labelsInfoValueRouteMap[fieldName ?? '']}`) ?? ''; - if (Array.isArray(currentFieldValue)) { - // eslint-disable-next-line max-len - const isSameValueWithBefore = !currentFieldValue.every((item) => ((Array.isArray(_state.currentSelectedValue)) ? _state.currentSelectedValue.includes(item) : _state.currentSelectedValue === item)); - const isSameLengthWithBefore = currentFieldValue.length === _state.currentSelectedValue.length; - if (!isSameValueWithBefore || !isSameLengthWithBefore) { - _state.currentSelectedValue = currentFieldValue; - } - } else if (_state.currentSelectedValue !== currentFieldValue) { - _state.currentSelectedValue = currentFieldValue ?? []; - } - const newValueList:string[] = []; - Object.entries(formValueMap ?? {}).forEach(([key, value]) => { - const onlyFormValue = get(value, labelsInfoValueRouteMap[key]); - if (Array.isArray(onlyFormValue)) { - onlyFormValue.forEach((item) => { - newValueList.push(item); - }); - } else newValueList.push(onlyFormValue); - }); - const isSameValueListWithBefore = newValueList.every((item, index) => _state.selectedValueList[index] === item); - if (!isSameValueListWithBefore) _state.selectedValueList = newValueList; - if (state.granularity !== formValueMap?.granularity) state.granularity = formValueMap?.granularity ?? 'MONTHLY'; - }); - - return { - ...toRefs(state), - }; -}; diff --git a/apps/web/src/common/modules/widgets/_composables/use-widget-date-range.ts b/apps/web/src/common/modules/widgets/_composables/use-widget-date-range.ts index b43d172f7f..511edccbfa 100644 --- a/apps/web/src/common/modules/widgets/_composables/use-widget-date-range.ts +++ b/apps/web/src/common/modules/widgets/_composables/use-widget-date-range.ts @@ -4,18 +4,21 @@ import { computed, reactive, toRef } from 'vue'; import dayjs from 'dayjs'; import { getDateFormat } from '@/common/modules/widgets/_helpers/widget-date-helper'; +import { DATE_RANGE_ADVANCED_OPERATOR_MAP } from '@/common/modules/widgets/_widget-fields/date-range/constant'; import type { DateRangeValue, DateRangeValueType } from '@/common/modules/widgets/_widget-fields/date-range/type'; +import type { GranularityValue } from '@/common/modules/widgets/_widget-fields/granularity/type'; import type { DateRange } from '@/common/modules/widgets/types/widget-data-type'; interface UseWidgetDateRangeOptions { dateRangeFieldValue: ComputedRef; baseOnDate: ComputedRef; - granularity: ComputedRef; + granularity: ComputedRef; usePreviewFormat?: boolean; } interface UseWidgetDateRangeState { + granularity: ComputedRef; isInherit: ComputedRef; dateRangeOptions: ComputedRef; dateRange: ComputedRef; @@ -29,11 +32,12 @@ interface UseWidgetDateRangeReturnType { export const useWidgetDateRange = (options: UseWidgetDateRangeOptions): UseWidgetDateRangeReturnType => { const { baseOnDate: _baseOnDate, - granularity: _granularity, + granularity: _granularityInfo, usePreviewFormat: _usePreviewFormat, } = options; const state = reactive({ + granularity: computed(() => _granularityInfo.value?.granularity ?? 'MONTHLY'), isInherit: computed(() => !!options.dateRangeFieldValue.value?.inherit), dateRangeOptions: computed(() => options.dateRangeFieldValue.value?.options || 'auto'), baseOnDate: computed(() => (state.isInherit ? _baseOnDate.value : undefined)), @@ -42,11 +46,17 @@ export const useWidgetDateRange = (options: UseWidgetDateRangeOptions): UseWidge const { start: _start, end: _end } = state.dateRangeOptions || {}; if (dateRangePresetKey === 'custom') { - return state.isInherit ? getWidgetDateRangeByCustomRelativeNumberUnit(_granularity.value, state.baseOnDate, _start, _end) - : getWidgetDateRangeByCustomFixedDateRange(_granularity.value, _start, _end); + return state.isInherit ? getWidgetDateRangeByCustomRelativeNumberUnit(state.granularity, state.baseOnDate, _start, _end) + : getWidgetDateRangeByCustomFixedDateRange(state.granularity, _start, _end); + } if (dateRangePresetKey === 'advanced') { + const { start_operator: _startOperator, end_operator: _endOperator } = state.dateRangeOptions || {}; + const startValue: number = _startOperator === DATE_RANGE_ADVANCED_OPERATOR_MAP.ADD ? _start || 0 : (_start || 0) * -1; + const endValue: number = _endOperator === DATE_RANGE_ADVANCED_OPERATOR_MAP.ADD ? _end || 0 : (_end || 0) * -1; + + return getWidgetDateRangeByAdvancedDiffValue(state.granularity, state.baseOnDate, startValue, endValue, _usePreviewFormat); } - return getWidgetDateRangeByPresetKey(_granularity.value, state.baseOnDate, dateRangePresetKey, _usePreviewFormat); + return getWidgetDateRangeByPresetKey(state.granularity, state.baseOnDate, dateRangePresetKey, _usePreviewFormat); }), }); @@ -211,6 +221,44 @@ export const useWidgetDateRange = (options: UseWidgetDateRangeOptions): UseWidge }; }; + const getWidgetDateRangeByAdvancedDiffValue = ( + granularity: string, + basedOnDate: string|undefined, + startValue: number, + endValue: number, + usePreviewFormat?: boolean, + ): DateRange => { + const _dateFormat = getDateFormat(granularity); + const baseDate = basedOnDate ? dayjs.utc(basedOnDate).endOf('month') : dayjs.utc(basedOnDate); + + // Results + let _start = ''; + let _end = ''; + + if (granularity === 'YEARLY') { + _start = baseDate.add(startValue, 'year').startOf('year').format('YYYY-MM-DD'); + _end = baseDate.add(endValue, 'year').endOf('year').format('YYYY-MM-DD'); + } else if (granularity === 'MONTHLY') { + _start = baseDate.add(startValue, 'month').startOf('month').format('YYYY-MM-DD'); + _end = baseDate.add(endValue, 'month').endOf('month').format('YYYY-MM-DD'); + } else { + _start = baseDate.add(startValue, 'day').format('YYYY-MM-DD'); + _end = baseDate.add(endValue, 'day').format('YYYY-MM-DD'); + } + + if (usePreviewFormat) { + return { + start: _start, + end: _end, + }; + } + + return { + start: dayjs.utc(_start).format(_dateFormat), + end: dayjs.utc(_end).format(_dateFormat), + }; + }; + return { dateRange: toRef(state, 'dateRange'), diff --git a/apps/web/src/common/modules/widgets/_composables/use-widget-frame.ts b/apps/web/src/common/modules/widgets/_composables/use-widget-frame.ts index 645ec2242b..4d3629d80d 100644 --- a/apps/web/src/common/modules/widgets/_composables/use-widget-frame.ts +++ b/apps/web/src/common/modules/widgets/_composables/use-widget-frame.ts @@ -21,7 +21,9 @@ import { DATA_SOURCE_DOMAIN, DATA_TABLE_TYPE } from '@/common/modules/widgets/_c import { getWidgetConfig } from '@/common/modules/widgets/_helpers/widget-config-helper'; import type { DisplayAnnotationValue } from '@/common/modules/widgets/_widget-fields/display-annotation/type'; import type { DateRange } from '@/common/modules/widgets/types/widget-data-type'; -import type { WidgetFrameEmit, WidgetProps, WidgetSize } from '@/common/modules/widgets/types/widget-display-type'; +import type { + WidgetEmit, WidgetProps, WidgetSize, +} from '@/common/modules/widgets/types/widget-display-type'; import type { WidgetFieldName } from '@/common/modules/widgets/types/widget-field-type'; import type { WidgetFieldValues } from '@/common/modules/widgets/types/widget-field-value-type'; import type { FullDataLink, WidgetFrameProps } from '@/common/modules/widgets/types/widget-frame-type'; @@ -35,9 +37,9 @@ import { COST_EXPLORER_ROUTE } from '@/services/cost-explorer/routes/route-const interface OverridableWidgetFrameState { dateRange?: DateRange | ComputedRef; - errorMessage?: string | ComputedRef; - widgetLoading?: boolean | ComputedRef; - noData?: boolean | ComputedRef; + errorMessage?: ComputedRef; + widgetLoading?: ComputedRef; + noData?: ComputedRef; } type DataTableModel = PublicDataTableModel | PrivateDataTableModel; const { getProperRouteLocation } = useProperRouteLocation(); @@ -134,18 +136,18 @@ const getFullDataLocation = (dataTable: DataTableModel, widgetOptions?: Record, - emit: WidgetFrameEmit, + emit: WidgetEmit, overrides: OverridableWidgetFrameState = {}, ) => { const _state = reactive({ widgetConfig: computed(() => getWidgetConfig(props.widgetName)), - showWidgetHeader: computed(() => props.widgetOptions?.widgetHeader?.toggleValue || false), + showWidgetHeader: computed(() => props.widgetOptions?.widgetHeader?.value?.toggleValue || false), title: computed(() => { - if (_state.showWidgetHeader) return props.widgetOptions?.widgetHeader?.title; + if (_state.showWidgetHeader) return props.widgetOptions?.widgetHeader?.value?.title; return undefined; }), description: computed(() => { - if (_state.showWidgetHeader) return props.widgetOptions?.widgetHeader?.description; + if (_state.showWidgetHeader) return props.widgetOptions?.widgetHeader?.value?.description; return undefined; }), size: computed(() => { @@ -191,7 +193,7 @@ export const useWidgetFrame = ( return overrides.noData?.value || false; }), annotation: computed(() => { - const _displayAnnotation = props.widgetOptions?.displayAnnotation as DisplayAnnotationValue; + const _displayAnnotation = props.widgetOptions?.displayAnnotation?.value as DisplayAnnotationValue; if (!_displayAnnotation?.toggleValue) return undefined; return _displayAnnotation?.annotation; }), diff --git a/apps/web/src/common/modules/widgets/_composables/use-widget-init-and-refresh.ts b/apps/web/src/common/modules/widgets/_composables/use-widget-init-and-refresh.ts index 64783f1507..8d2ef17115 100644 --- a/apps/web/src/common/modules/widgets/_composables/use-widget-init-and-refresh.ts +++ b/apps/web/src/common/modules/widgets/_composables/use-widget-init-and-refresh.ts @@ -5,23 +5,22 @@ import { import { isEqual } from 'lodash'; -import type { APIErrorToast } from '@/common/composables/error/errorHandler'; import type { WidgetProps, WidgetEmit, } from '@/common/modules/widgets/types/widget-display-type'; -interface UseWidgetInitAndRefreshOptions { +interface UseWidgetInitAndRefreshOptions { props: WidgetProps; emit: WidgetEmit; - loadWidget: (...args: any) => Promise; + loadWidget: (...args: any) => Promise|void; } -export const useWidgetInitAndRefresh = ({ +export const useWidgetInitAndRefresh = ({ props, emit, loadWidget, -}: UseWidgetInitAndRefreshOptions): void => { +}: UseWidgetInitAndRefreshOptions): void => { const initiated = ref(false); const stopVariablesWatch = watch(() => props.dashboardVars, async (after, before) => { diff --git a/apps/web/src/common/modules/widgets/_composables/use-widget-options-complex-validation.ts b/apps/web/src/common/modules/widgets/_composables/use-widget-options-complex-validation.ts index 6d20ec7d6e..4241010915 100644 --- a/apps/web/src/common/modules/widgets/_composables/use-widget-options-complex-validation.ts +++ b/apps/web/src/common/modules/widgets/_composables/use-widget-options-complex-validation.ts @@ -6,17 +6,15 @@ import type { TranslateResult } from 'vue-i18n'; import { i18n } from '@/translations'; -import type { GroupByValue, GroupByOptions } from '@/common/modules/widgets/_widget-fields/group-by/type'; -import type { TableDataFieldValue } from '@/common/modules/widgets/_widget-fields/table-data-field/type'; -import type { XAxisValue } from '@/common/modules/widgets/_widget-fields/x-axis/type'; -import type { YAxisValue } from '@/common/modules/widgets/_widget-fields/y-axis/type'; +import type { WidgetFieldValue } from '@/common/modules/widgets/_widget-field-value-manager/type'; +import type { GroupByOptions, GroupByValue } from '@/common/modules/widgets/_widget-fields/group-by/type'; import type { WidgetConfig } from '@/common/modules/widgets/types/widget-config-type'; import type { WidgetFieldValues, } from '@/common/modules/widgets/types/widget-field-value-type'; -type OptionsValueMap = Record; +type OptionsValueMap = Record|undefined>; interface WidgetOptionValidationProps { optionValueMap: Ref; widgetConfig: Ref; @@ -43,7 +41,7 @@ export const useWidgetOptionsComplexValidation = ({ invalidText: computed(() => { if (_state.widgetConfig.widgetName === 'geoMap') { const fixedVal = (_state.widgetConfig.requiredFieldsSchema.groupBy?.options as GroupByOptions)?.fixedValue; - const val = (_state.valueMap?.groupBy as GroupByValue)?.value; + const val = (_state.valueMap?.groupBy?.value as GroupByValue)?.data; if (fixedVal) { if ((Array.isArray(val) && !val.includes(fixedVal)) || val !== fixedVal) { return i18n.t('COMMON.WIDGETS.FORM.WIDGET_VALIDATION_WARNING_DESC_GEO_MAP'); @@ -57,7 +55,7 @@ export const useWidgetOptionsComplexValidation = ({ const getRequiredFieldValidation = (valueMap: OptionsValueMap, config: WidgetConfig): boolean => { if (config.widgetName === 'geoMap') { const fixedVal = (config.requiredFieldsSchema.groupBy?.options as GroupByOptions)?.fixedValue; - const val = (valueMap?.groupBy as GroupByValue)?.value; + const val = (valueMap?.groupBy?.value as GroupByValue)?.data; if (fixedVal) { if (Array.isArray(val)) return val.includes(fixedVal); return val === fixedVal; @@ -68,26 +66,7 @@ export const useWidgetOptionsComplexValidation = ({ const getDuplicatedLabelInfoValidation = (valueMap: OptionsValueMap, config: WidgetConfig): boolean => { let isValid = true; // Label Info Fields Value Duplicate Validation (Table Widget) - if (config.widgetName === 'table') { - const groupByField = 'groupBy'; - const tableDataField = 'tableDataField'; - const groupByFieldValue = valueMap[groupByField] as GroupByValue; - const tableDataFieldValue = valueMap[tableDataField] as TableDataFieldValue; - const allValueExist = groupByFieldValue?.value && !!groupByFieldValue.value.length - && (tableDataFieldValue?.staticFieldInfo?.fieldValue || tableDataFieldValue?.dynamicFieldInfo?.fieldValue); - if (tableDataFieldValue?.fieldType === 'dynamicField' && allValueExist) { - isValid = !(groupByFieldValue?.value ?? []).includes(tableDataFieldValue.dynamicFieldInfo?.fieldValue as string); - } - } else if (['clusteredColumnChart', 'lineChart', 'stackedAreaChart', 'stackedColumnChart', 'stackedHorizontalBarChart', 'heatmap', 'colorCodedTableHeatmap'].includes(config.widgetName)) { - let fieldValue = valueMap.xAxis as XAxisValue; - if (config.widgetName === 'stackedHorizontalBarChart') { - fieldValue = valueMap.yAxis as YAxisValue; - } - const tableDataFieldValue = valueMap.tableDataField as TableDataFieldValue; - if (tableDataFieldValue?.fieldType === 'dynamicField') { - isValid = fieldValue?.value !== tableDataFieldValue?.dynamicFieldInfo?.fieldValue; - } - } else { + if (config.widgetName !== 'table') { // Label Info Fields Value Duplicate Validation (Except Table Widget) const allFields = [..._state.requiredFields, ..._state.optionalFields]; const labelInfoFields = allFields.filter((field) => config.requiredFieldsSchema[field]?.options?.dataTarget === 'labels_info' diff --git a/apps/web/src/common/modules/widgets/_constants/data-table-constant.ts b/apps/web/src/common/modules/widgets/_constants/data-table-constant.ts index c9dddd2628..28d755dfe4 100644 --- a/apps/web/src/common/modules/widgets/_constants/data-table-constant.ts +++ b/apps/web/src/common/modules/widgets/_constants/data-table-constant.ts @@ -1,3 +1,7 @@ +import type { + AddLabelsOptions, + ConcatOptions, EvalOptions, JoinOptions, PivotOptions, QueryOptions, ValueMappingOptions, +} from '@/common/modules/widgets/types/widget-model'; import { GROUP_BY } from '@/services/cost-explorer/constants/cost-explorer-constant'; @@ -19,6 +23,9 @@ export const DATA_TABLE_OPERATOR = { QUERY: 'QUERY', // AGGREGATE: 'AGGREGATE', EVAL: 'EVAL', + PIVOT: 'PIVOT', + ADD_LABELS: 'ADD_LABELS', + VALUE_MAPPING: 'VALUE_MAPPING', } as const; export const JOIN_TYPE = { @@ -31,7 +38,7 @@ export const JOIN_TYPE = { // NOTE: temporary solution for the global variable export const MANAGED_GLOBAL_VARIABLE = [GROUP_BY.WORKSPACE, GROUP_BY.PROJECT, GROUP_BY.SERVICE_ACCOUNT, GROUP_BY.REGION] as string[]; -export const EVAL_EXPRESSION_TYPE = { +export const DATA_TABLE_FIELD_TYPE = { LABEL: 'LABEL', DATA: 'DATA', } as const; @@ -65,3 +72,63 @@ export const DATA_TABLE_QUERY_OPERATOR = { } as const; export const KEYWORD_FILTER_DISABLED_KEYS = [GROUP_BY.PROJECT, GROUP_BY.WORKSPACE, GROUP_BY.SERVICE_ACCOUNT]; + +export const DEFAULT_TRANSFORM_DATA_TABLE_VALUE_MAP = { + PIVOT: { + data_table_id: undefined, + fields: { + labels: [], + data: undefined, + column: undefined, + }, + select: undefined, + limit: 5, + function: 'sum', + order_by: { + type: 'key', + desc: false, + }, + } as PivotOptions, + CONCAT: { + data_tables: [], + } as ConcatOptions, + JOIN: { + data_tables: [], + how: undefined, + left_keys: [''], + right_keys: [''], + } as JoinOptions, + EVAL: { + data_table_id: undefined, + expressions: [ + { + name: '', + field_type: DATA_TABLE_FIELD_TYPE.DATA, + expression: '', + }, + ], + } as EvalOptions, + QUERY: { + data_table_id: undefined, + conditions: [''], + // operator: 'AND', + } as QueryOptions, + ADD_LABELS: { + data_table_id: undefined, + labels: { '': '' }, + } as AddLabelsOptions, + VALUE_MAPPING: { + data_table_id: undefined, + name: '', + field_type: DATA_TABLE_FIELD_TYPE.LABEL, + cases: [ + { + key: '', + value: '', + operator: 'eq', + match: '', + }, + ], + else: '', + } as ValueMappingOptions, +}; diff --git a/apps/web/src/common/modules/widgets/_constants/widget-components-constant.ts b/apps/web/src/common/modules/widgets/_constants/widget-components-constant.ts index a324458415..f09c16559b 100644 --- a/apps/web/src/common/modules/widgets/_constants/widget-components-constant.ts +++ b/apps/web/src/common/modules/widgets/_constants/widget-components-constant.ts @@ -46,9 +46,9 @@ export const WIDGET_COMPONENTS: Record = { colorCodedTableHeatmap: () => ({ component: import('@/common/modules/widgets/_widgets/color-coded-table-heatmap/ColorCodedTableHeatmap.vue'), }), - // progressCard: () => ({ - // component: import('@/common/modules/widgets/_widgets/progress-card/ProgressCard.vue'), - // }), + sankeyChart: () => ({ + component: import('@/common/modules/widgets/_widgets/sankey-chart/SankeyChart.vue'), + }), }; export const WIDGET_COMPONENT_ICON_MAP: Record = { @@ -66,5 +66,5 @@ export const WIDGET_COMPONENT_ICON_MAP: Record = { gauge: 'ic_chart-gauge', colorCodedHeatmap: 'ic_chart-color-heatmap', colorCodedTableHeatmap: 'ic_chart-heatmap-table', - // progressCard: 'ic_chart-progress-card', + sankeyChart: 'ic_chart-sankey-chart', }; diff --git a/apps/web/src/common/modules/widgets/_constants/widget-config-list-constant.ts b/apps/web/src/common/modules/widgets/_constants/widget-config-list-constant.ts index f9d08b7526..4136f63219 100644 --- a/apps/web/src/common/modules/widgets/_constants/widget-config-list-constant.ts +++ b/apps/web/src/common/modules/widgets/_constants/widget-config-list-constant.ts @@ -7,7 +7,7 @@ import heatmap from '@/common/modules/widgets/_widgets/heatmap/widget-config'; import lineChart from '@/common/modules/widgets/_widgets/line-chart/widget-config'; import numberCard from '@/common/modules/widgets/_widgets/number-card/widget-config'; import pieChart from '@/common/modules/widgets/_widgets/pie-chart/widget-config'; -// import progressCard from '@/common/modules/widgets/_widgets/progress-card/widget-config'; +import sankeyChart from '@/common/modules/widgets/_widgets/sankey-chart/widget-config'; import stackedAreaChart from '@/common/modules/widgets/_widgets/stacked-area-chart/widget-config'; import stackedColumnChart from '@/common/modules/widgets/_widgets/stacked-column-chart/widget-config'; import stackedHorizontalBarChart from '@/common/modules/widgets/_widgets/stacked-horizontal-bar-chart/widget-config'; @@ -32,7 +32,7 @@ export const CONSOLE_WIDGET_CONFIG_KEYS = [ 'table', 'stackedAreaChart', 'gauge', - // 'progressCard', + 'sankeyChart', ] as const; export const CONSOLE_WIDGET_CONFIG: Record> = { @@ -50,5 +50,5 @@ export const CONSOLE_WIDGET_CONFIG: Record, Component> = { dateRange, - dateAggregationOptions, dataField, - tableDataField, xAxis, yAxis, stackBy, - lineBy, groupBy, categoryBy, min, max, formatRules, - advancedFormatRules, legend, - progressBar, icon, subTotal, total, comparison, + tableColumnComparison, granularity, colorSchema, pieChartType, @@ -72,4 +65,5 @@ export const WIDGET_FIELD_COMPONENTS: Record, Component missingValue, widgetHeight, tooltipNumberFormat, + sankeyDimensions, }; diff --git a/apps/web/src/common/modules/widgets/_constants/widget-field-constant.ts b/apps/web/src/common/modules/widgets/_constants/widget-field-constant.ts index c790410ad3..abffd32688 100644 --- a/apps/web/src/common/modules/widgets/_constants/widget-field-constant.ts +++ b/apps/web/src/common/modules/widgets/_constants/widget-field-constant.ts @@ -1,13 +1,10 @@ import { green, red } from '@/styles/colors'; export const FORMAT_RULE_TYPE = { - threshold: 'threshold', + numberThreshold: 'numberThreshold', percentThreshold: 'percentThreshold', -} as const; - -export const ADVANCED_FORMAT_RULE_TYPE = { textThreshold: 'textThreshold', - field: 'field', + textNumberThreshold: 'textNumberThreshold', } as const; export const COLOR_SCHEMA = { @@ -111,3 +108,5 @@ export const WIDGET_HEIGHT = { default: 'default', full: 'full', }; + +export const SUB_TOTAL_NAME = 'Sub Total'; diff --git a/apps/web/src/common/modules/widgets/_helpers/global-variable-helper.ts b/apps/web/src/common/modules/widgets/_helpers/global-variable-helper.ts index 4e0a183e2a..db9af2fb89 100644 --- a/apps/web/src/common/modules/widgets/_helpers/global-variable-helper.ts +++ b/apps/web/src/common/modules/widgets/_helpers/global-variable-helper.ts @@ -1,7 +1,27 @@ import type { MenuItem } from '@cloudforet/mirinae/types/controls/context-menu/type'; +import type { DashboardVars } from '@/schema/dashboard/_types/dashboard-type'; + export const isGlobalVariableFormat = (value: string | MenuItem[]): boolean => { if (Array.isArray(value)) return false; const regex = /^\{\{\s*global\.[^\s]+\s*\}\}$/; return regex.test(value); }; + +export const normalizeAndSerialize = (obj: DashboardVars = {}): string => { + const sortedKeys = Object.keys(obj).sort(); + + const normalizedObj: DashboardVars = sortedKeys.reduce((acc, key) => { + const value = obj[key]; + + if (Array.isArray(value)) { + acc[key] = [...value].sort(); + } else { + acc[key] = value; + } + + return acc; + }, {} as DashboardVars); + + return JSON.stringify(normalizedObj); +}; diff --git a/apps/web/src/common/modules/widgets/_helpers/widget-data-table-helper.ts b/apps/web/src/common/modules/widgets/_helpers/widget-data-table-helper.ts index a81bd49271..5960c5299e 100644 --- a/apps/web/src/common/modules/widgets/_helpers/widget-data-table-helper.ts +++ b/apps/web/src/common/modules/widgets/_helpers/widget-data-table-helper.ts @@ -1,6 +1,8 @@ import type { PrivateDataTableModel } from '@/schema/dashboard/private-data-table/model'; import type { PublicDataTableModel } from '@/schema/dashboard/public-data-table/model'; +import type { DataTableOptions } from '@/common/modules/widgets/types/widget-model'; + export const getDuplicatedDataTableName = (name: string, dataTables: Partial[]): string => { let _name = name; const _regex = /^(.*?)\s*\((\d+)\)$/i; @@ -19,3 +21,23 @@ export const getDuplicatedDataTableName = (name: string, dataTables: Partial { + if (!dataTable) return true; + const _dataInfoKeys = Object.keys(dataTable.data_info || {}); + const _labelsInfoKeys = Object.keys(dataTable.labels_info || {}); + const _keys = _dataInfoKeys.concat(_labelsInfoKeys); + return !_keys.includes(fieldName); +}; + +export const sortObjectByKeys = (obj: DataTableOptions): DataTableOptions => { + if (obj === null || typeof obj !== 'object') return obj; + + const sorted = {} as DataTableOptions; + Object.keys(obj) + .sort() + .forEach((key) => { + sorted[key] = sortObjectByKeys(obj[key]); + }); + return sorted; +}; diff --git a/apps/web/src/common/modules/widgets/_helpers/widget-date-helper.ts b/apps/web/src/common/modules/widgets/_helpers/widget-date-helper.ts index 4650611381..063b3ca7ea 100644 --- a/apps/web/src/common/modules/widgets/_helpers/widget-date-helper.ts +++ b/apps/web/src/common/modules/widgets/_helpers/widget-date-helper.ts @@ -1,12 +1,9 @@ import type { ManipulateType } from 'dayjs'; import dayjs from 'dayjs'; -import { orderBy, sum } from 'lodash'; import { DATE_FORMAT } from '@/common/modules/widgets/_constants/widget-field-constant'; -import { isDateField } from '@/common/modules/widgets/_helpers/widget-field-helper'; import type { DateFormat } from '@/common/modules/widgets/_widget-fields/date-format/type'; -import type { TableDataFieldValue } from '@/common/modules/widgets/_widget-fields/table-data-field/type'; -import type { DateRange, DynamicFieldData } from '@/common/modules/widgets/types/widget-data-type'; +import type { DateRange } from '@/common/modules/widgets/types/widget-data-type'; import type { AllReferenceTypeInfo } from '@/services/dashboards/stores/all-reference-type-info-store'; @@ -158,102 +155,3 @@ export const getFormattedDate = (date: string, dateFormat: string): string => { if (dateFormatsWithMMM.includes(dateFormat)) return dayjs.utc(date).locale('en').format(dateFormat); return dayjs.utc(date).format(dateFormat); }; - - -export const getRefinedDynamicFieldData = (rawData: DynamicFieldData, dynamicFieldInfo: TableDataFieldValue['dynamicFieldInfo'], xAxisField: string): [any[], string[]] => { - if (!rawData?.results?.length) return [[], []]; - - const valueType = dynamicFieldInfo?.valueType; - const valueCount = dynamicFieldInfo?.count || 0; - const criteria = dynamicFieldInfo?.criteria as string; - const dataField = dynamicFieldInfo?.fieldValue as string; - const dynamicFieldValue = dynamicFieldInfo?.fixedValue || []; - - const _refinedResults: any[] = []; - let _seriesFields: string[] = []; - if (valueType === 'fixed') { - let _etcExists = false; - rawData.results.forEach((result) => { - const _filteredData = (result[criteria] || []).filter((d) => dynamicFieldValue.includes(d[dataField])); - - // etc data - const _etcData = (result[criteria] || []).filter((d) => !dynamicFieldValue.includes(d[dataField])); - const _etcValueSum = sum(_etcData.map((v) => v.value || 0)); - if (_etcValueSum > 0) _etcExists = true; - - _refinedResults.push({ - ...result, - [criteria]: [ - ..._filteredData, - { [dataField]: 'etc', value: _etcValueSum }, - ], - }); - }); - _seriesFields = [...dynamicFieldValue]; - if (_etcExists) _seriesFields.push('etc'); - } else { - let _etcExists = false; - const _seriesFieldsSet = new Set(); - - rawData.results?.forEach((result) => { - let _refinedData: any[] = []; - if (isDateField(dataField)) { - _refinedData = orderBy(result[criteria], dataField, 'desc') ?? []; - } else { - _refinedData = orderBy(result[criteria], 'value', 'desc') ?? []; - } - _refinedData = _refinedData.slice(0, valueCount); - _refinedData.forEach((v) => { - _seriesFieldsSet.add(v[dataField]); - }); - _seriesFields = Array.from(_seriesFieldsSet); - if (isDateField(dataField)) _seriesFields.sort(); - - // etc data - const _etcData = (result[criteria] || []).filter((d) => !_seriesFields.includes(d[dataField])); - const _etcValueSum = sum(_etcData.map((v) => v.value || 0)); - if (_etcValueSum > 0) _etcExists = true; - - _refinedResults.push({ - [criteria]: [ - ..._refinedData, - { [dataField]: 'etc', value: _etcValueSum }, - ], - [xAxisField]: result[xAxisField], - }); - }); - - - if (!isDateField(dataField) && _etcExists) _seriesFields.push('etc'); - } - - return [_refinedResults, _seriesFields]; -}; -export const getRefinedHeatmapDynamicFieldData = (rawData: DynamicFieldData, dynamicFieldInfo: TableDataFieldValue['dynamicFieldInfo']): string[] => { - if (!rawData?.results?.length) return []; - - const valueType = dynamicFieldInfo?.valueType; - const valueCount = dynamicFieldInfo?.count || 0; - const criteria = dynamicFieldInfo?.criteria as string; - const dataField = dynamicFieldInfo?.fieldValue as string; - const dynamicFieldValue = dynamicFieldInfo?.fixedValue || []; - - let _seriesFields: string[] = []; - if (valueType === 'fixed') { - _seriesFields = [...dynamicFieldValue]; - } else { - const _subTotalResults: Record = {}; - rawData.results.forEach((result) => { - if (!Array.isArray(result[criteria])) return; - result[criteria]?.forEach((d) => { - if (d[dataField] in _subTotalResults) { - _subTotalResults[d[dataField]] += d.value; - } else { - _subTotalResults[d[dataField]] = d.value; - } - }); - }); - _seriesFields = orderBy(Object.entries(_subTotalResults), 1, 'desc').slice(0, valueCount).map(([k]) => k); - } - return _seriesFields; -}; diff --git a/apps/web/src/common/modules/widgets/_helpers/widget-field-helper.ts b/apps/web/src/common/modules/widgets/_helpers/widget-field-helper.ts index a0f03bff5b..40eda85cd0 100644 --- a/apps/web/src/common/modules/widgets/_helpers/widget-field-helper.ts +++ b/apps/web/src/common/modules/widgets/_helpers/widget-field-helper.ts @@ -1,6 +1,7 @@ import type { MenuItem } from '@cloudforet/mirinae/types/controls/context-menu/type'; import { DATE_FIELD } from '@/common/modules/widgets/_constants/widget-constant'; +import type { WidgetConfig } from '@/common/modules/widgets/types/widget-config-type'; import type { DateFieldType } from '@/common/modules/widgets/types/widget-data-type'; @@ -25,3 +26,8 @@ export const getInitialSelectedMenuItem = (menuItems: MenuItem[], data: string[] export const isDateField = (fieldName?: DateFieldType) => fieldName && Object.values(DATE_FIELD).includes(fieldName); export const isIncludingDateField = (fieldNames: string[]) => Object.values(DATE_FIELD).some((field) => fieldNames.includes(field)); + +export const integrateFieldsSchema = (requiredFieldsSchema: WidgetConfig['requiredFieldsSchema'], optionalFieldsSchema: WidgetConfig['optionalFieldsSchema']) => ({ + ...requiredFieldsSchema, + ...optionalFieldsSchema, +}); diff --git a/apps/web/src/common/modules/widgets/_helpers/widget-helper.ts b/apps/web/src/common/modules/widgets/_helpers/widget-helper.ts index 4ba2d1cc0e..d71466b35c 100644 --- a/apps/web/src/common/modules/widgets/_helpers/widget-helper.ts +++ b/apps/web/src/common/modules/widgets/_helpers/widget-helper.ts @@ -11,7 +11,7 @@ import ErrorHandler from '@/common/composables/error/errorHandler'; import { DATE_FIELD } from '@/common/modules/widgets/_constants/widget-constant'; import { NUMBER_FORMAT } from '@/common/modules/widgets/_constants/widget-field-constant'; import { getWidgetConfig } from '@/common/modules/widgets/_helpers/widget-config-helper'; -import type { NumberFormatValue } from '@/common/modules/widgets/_widget-fields/number-format/type'; +import type { NumberFormatInfo } from '@/common/modules/widgets/_widget-fields/number-format/type'; import type { WidgetType } from '@/common/modules/widgets/types/widget-model'; import { SIZE_UNITS } from '@/services/asset-inventory/constants/asset-analysis-constant'; @@ -44,9 +44,9 @@ export const sortWidgetTableFields = (fields: string[]): string[] => { }; /* Widget Number Format */ -export const getFormattedNumber = (val: number, dataField: string, numberFormatValue?: NumberFormatValue, unit?: string): string => { - if (!numberFormatValue) return numberFormatter(val) || '--'; - const _targetNumberFormat = numberFormatValue[dataField]; +export const getFormattedNumber = (val: number, numberFormatInfo?: NumberFormatInfo, unit?: string): string => { + if (!numberFormatInfo) return numberFormatter(val) || '--'; + const _targetNumberFormat = numberFormatInfo; switch (_targetNumberFormat?.format) { case NUMBER_FORMAT.AUTO: if (unit && SIZE_UNITS.includes(unit)) { diff --git a/apps/web/src/common/modules/widgets/_helpers/widget-load-helper.ts b/apps/web/src/common/modules/widgets/_helpers/widget-load-helper.ts index 0d723b42a3..4a62cea577 100644 --- a/apps/web/src/common/modules/widgets/_helpers/widget-load-helper.ts +++ b/apps/web/src/common/modules/widgets/_helpers/widget-load-helper.ts @@ -2,9 +2,8 @@ import dayjs from 'dayjs'; import type { Query } from '@cloudforet/core-lib/space-connector/type'; +import { SUB_TOTAL_NAME } from '@/common/modules/widgets/_constants/widget-field-constant'; import { getDateFormat, getTimeUnit } from '@/common/modules/widgets/_helpers/widget-date-helper'; -import { isDateField } from '@/common/modules/widgets/_helpers/widget-field-helper'; -import type { TableDataFieldValue } from '@/common/modules/widgets/_widget-fields/table-data-field/type'; import type { DateRange } from '@/common/modules/widgets/types/widget-data-type'; @@ -38,41 +37,12 @@ export const getWidgetLoadApiQueryDateRange = (granularity: string, dateRange: D return dateRange; }; -export const getWidgetLoadApiQuery = (dataFieldInfo: TableDataFieldValue, xAxisField: string): Record => { - const _dataFieldType = dataFieldInfo?.fieldType; - const _dataField = _dataFieldType === 'staticField' ? dataFieldInfo?.staticFieldInfo?.fieldValue : dataFieldInfo?.dynamicFieldInfo?.fieldValue; - const _criteria = dataFieldInfo?.dynamicFieldInfo?.criteria; - const _dynamicFixedFieldValue = dataFieldInfo?.dynamicFieldInfo?.fixedValue; - - const _fields = {}; - let _groupBy: string[] = [xAxisField]; - let _field_group: string[] = []; - let _sort: Query['sort'] = []; - let _filter: Query['filter'] = []; - if (_dataFieldType === 'staticField') { - _dataField?.forEach((field) => { - _fields[field] = { key: field, operator: 'sum' }; - }); - _sort = _groupBy.includes('Date') ? [{ key: 'Date', desc: false }] : _dataField?.map((field) => ({ key: field, desc: true })); - } else { - _fields[_criteria] = { key: _criteria, operator: 'sum' }; - _field_group = [_dataField]; - _groupBy = [..._groupBy, _dataField]; - _sort = _groupBy.includes('Date') && !_field_group.includes('Date') ? [{ key: 'Date', desc: false }] : [{ key: `_total_${_criteria}`, desc: true }]; +export const getWidgetLoadApiQuerySort = (xAxisField: string, dataField: string[], isPivot?: boolean): Query['sort'] => { + if (xAxisField === 'Date') { + return [{ key: 'Date', desc: true }]; } - if (isDateField(_dataField) && _dataFieldType === 'dynamicField' && _dynamicFixedFieldValue?.length) { - _dynamicFixedFieldValue.sort(); - _filter = [{ - k: _dataField, - v: _dynamicFixedFieldValue, - o: 'in', - }]; + if (isPivot) { + return [{ key: SUB_TOTAL_NAME, desc: true }]; } - return { - fields: _fields, - groupBy: _groupBy, - field_group: _field_group, - sort: _sort, - filter: _filter, - }; + return dataField.map((field) => ({ key: field, desc: true })); }; diff --git a/apps/web/src/common/modules/widgets/_store/widget-generate-store.ts b/apps/web/src/common/modules/widgets/_store/widget-generate-store.ts index 6eaedde154..b592ea8acf 100644 --- a/apps/web/src/common/modules/widgets/_store/widget-generate-store.ts +++ b/apps/web/src/common/modules/widgets/_store/widget-generate-store.ts @@ -1,19 +1,17 @@ import { computed, reactive } from 'vue'; +import { cloneDeep } from 'lodash'; import { defineStore } from 'pinia'; import { SpaceConnector } from '@cloudforet/core-lib/space-connector'; import type { ListResponse } from '@/schema/_common/api-verbs/list'; -import { GRANULARITY } from '@/schema/dashboard/_constants/widget-constant'; -import type { Granularity } from '@/schema/dashboard/_types/widget-type'; import type { PrivateDataTableModel } from '@/schema/dashboard/private-data-table/model'; import type { PrivateWidgetUpdateParameters } from '@/schema/dashboard/private-widget/api-verbs/update'; import type { PrivateWidgetModel } from '@/schema/dashboard/private-widget/model'; import type { DataTableAddParameters } from '@/schema/dashboard/public-data-table/api-verbs/add'; import type { DataTableDeleteParameters } from '@/schema/dashboard/public-data-table/api-verbs/delete'; import type { DataTableListParameters } from '@/schema/dashboard/public-data-table/api-verbs/list'; -import type { DataTableLoadParameters } from '@/schema/dashboard/public-data-table/api-verbs/load'; import type { DataTableTransformParameters } from '@/schema/dashboard/public-data-table/api-verbs/transform'; import type { DataTableUpdateParameters } from '@/schema/dashboard/public-data-table/api-verbs/update'; import type { PublicDataTableModel } from '@/schema/dashboard/public-data-table/model'; @@ -24,7 +22,11 @@ import { showErrorMessage } from '@/lib/helper/notice-alert-helper'; import getRandomId from '@/lib/random-id-generator'; import ErrorHandler from '@/common/composables/error/errorHandler'; -import { DATA_TABLE_TYPE } from '@/common/modules/widgets/_constants/data-table-constant'; +import { + DATA_TABLE_OPERATOR, + DATA_TABLE_TYPE, + DEFAULT_TRANSFORM_DATA_TABLE_VALUE_MAP, +} from '@/common/modules/widgets/_constants/data-table-constant'; import { getWidgetConfig } from '@/common/modules/widgets/_helpers/widget-config-helper'; import { getDuplicatedDataTableName } from '@/common/modules/widgets/_helpers/widget-data-table-helper'; import { sanitizeWidgetOptions } from '@/common/modules/widgets/_helpers/widget-helper'; @@ -34,7 +36,7 @@ import type { WidgetFieldValues } from '@/common/modules/widgets/types/widget-fi import type { DataTableOperator, WidgetState, - DataTableTransformOptions, + DataTableTransformOptions, ConcatOptions, JoinOptions, } from '@/common/modules/widgets/types/widget-model'; import { useDashboardDetailInfoStore } from '@/services/dashboards/stores/dashboard-detail-info-store'; @@ -43,6 +45,11 @@ import { useDashboardDetailInfoStore } from '@/services/dashboards/stores/dashbo type DataTableModel = PublicDataTableModel|PrivateDataTableModel; type WidgetModel = PublicWidgetModel|PrivateWidgetModel; type WidgetUpdateParameters = PublicWidgetUpdateParameters|PrivateWidgetUpdateParameters; +interface DataTableReference { + data_table_id: string; + parents: string[]; + children: string[]; +} export const useWidgetGenerateStore = defineStore('widget-generate', () => { const dashboardDetailStore = useDashboardDetailInfoStore(); const dashboardDetailGetters = dashboardDetailStore.getters; @@ -62,10 +69,7 @@ export const useWidgetGenerateStore = defineStore('widget-generate', () => { // Data Table selectedDataTableId: undefined as undefined | string, dataTables: [] as Partial[], - selectedPreviewGranularity: GRANULARITY.MONTHLY as Granularity, previewData: { results: [], total_count: 0 } as ListResponse, - dataTableUpdating: false, - dataTableLoadLoading: false, dataTableCreateLoading: false, joinRestrictedMap: {} as JoinRestrictedMap, // Flag for handling Join type EXCEPTION RESTRICTION cases. (duplicated data field). Example - { '{dataTalbeId}': true, } allDataTableInvalidMap: {} as Record, // Flag for handling all data table invalid cases. Example - { '{dataTalbeId}': true, } @@ -80,6 +84,73 @@ export const useWidgetGenerateStore = defineStore('widget-generate', () => { }), widgetState: computed(() => state.widget?.state), allDataTableInvalid: computed(() => Object.values(state.allDataTableInvalidMap).some((invalid) => invalid)), + dataTableReferenceMap: computed>(() => { + const referenceMap = {} as Record; + const savedDataTables = state.dataTables.filter((dataTable) => dataTable.data_table_id && !dataTable.data_table_id.startsWith('UNSAVED-')) as DataTableModel[]; + const MULTIPE_DATA_TABLE_OPERATORS = [DATA_TABLE_OPERATOR.CONCAT, DATA_TABLE_OPERATOR.JOIN]; + savedDataTables.forEach((dataTable) => { + if (!referenceMap[dataTable.data_table_id]) { + referenceMap[dataTable.data_table_id] = setIniitialDataTableReferenceProperty(dataTable.data_table_id); + } + + if (dataTable.data_type === DATA_TABLE_TYPE.TRANSFORMED) { + if (MULTIPE_DATA_TABLE_OPERATORS.includes(dataTable.operator)) { + const [firstReferenceDataTableId, secondReferenceDataTableId] = (dataTable.options[dataTable.operator] as ConcatOptions|JoinOptions).data_tables; + if (!referenceMap[firstReferenceDataTableId]) { + referenceMap[firstReferenceDataTableId] = setIniitialDataTableReferenceProperty(firstReferenceDataTableId); + } + if (!referenceMap[secondReferenceDataTableId]) { + referenceMap[secondReferenceDataTableId] = setIniitialDataTableReferenceProperty(secondReferenceDataTableId); + } + referenceMap[firstReferenceDataTableId] = { + ...referenceMap[firstReferenceDataTableId], + children: [ + ...referenceMap[firstReferenceDataTableId].children, + dataTable.data_table_id, + ], + }; + referenceMap[secondReferenceDataTableId] = { + ...referenceMap[secondReferenceDataTableId], + children: [ + ...referenceMap[secondReferenceDataTableId].children, + dataTable.data_table_id, + ], + }; + referenceMap[dataTable.data_table_id] = { + ...referenceMap[dataTable.data_table_id], + parents: [ + firstReferenceDataTableId, + secondReferenceDataTableId, + ], + }; + } else { + const referenceDataTableId = dataTable.options[dataTable.operator].data_table_id; + if (!referenceMap[referenceDataTableId]) { + referenceMap[referenceDataTableId] = setIniitialDataTableReferenceProperty(referenceDataTableId); + } + referenceMap[referenceDataTableId] = { + ...referenceMap[referenceDataTableId], + children: [ + ...referenceMap[referenceDataTableId].children, + dataTable.data_table_id, + ], + }; + referenceMap[dataTable.data_table_id] = { + ...referenceMap[dataTable.data_table_id], + parents: [referenceDataTableId], + }; + } + } + }); + return referenceMap; + }), + }); + + /* Helper */ + const setIniitialDataTableReferenceProperty = (dataTableId: string): DataTableReference => ({ + data_table_id: dataTableId, + parents: [], + children: [], }); /* Mutations */ @@ -113,12 +184,6 @@ export const useWidgetGenerateStore = defineStore('widget-generate', () => { const setWidgetValidMap = (widgetValidMap: Record) => { state.widgetValidMap = widgetValidMap; }; - const setSelectedPreviewGranularity = (granularity: Granularity) => { - state.selectedPreviewGranularity = granularity; - }; - const setDataTableUpdating = (status: boolean) => { - state.dataTableUpdating = status; - }; const setDataTableCreateLoading = (status: boolean) => { state.dataTableCreateLoading = status; }; @@ -143,8 +208,6 @@ export const useWidgetGenerateStore = defineStore('widget-generate', () => { setSize, setWidgetFormValueMap, setWidgetValidMap, - setSelectedPreviewGranularity, - setDataTableUpdating, setJoinRestrictedMap, setAllDataTableInvalidMap, setDataTableCreateLoading, @@ -181,7 +244,7 @@ export const useWidgetGenerateStore = defineStore('widget-generate', () => { const result = await fetcher(parameters); state.dataTables.push(result); return result; - } catch (e) { + } catch (e: any) { setDataTableCreateLoading(false); // DataTable Loading Failed Case showErrorMessage(e.message, e); ErrorHandler.handleError(e); @@ -211,25 +274,13 @@ export const useWidgetGenerateStore = defineStore('widget-generate', () => { }, createUnsavedTransformDataTable: (operatorType: DataTableOperator) => { const options = { - JOIN: { - data_tables: [], - how: undefined, - }, - CONCAT: { - data_tables: [], - }, - AGGREGATE: { - data_table_id: undefined, - group_by: [], - }, - QUERY: { - data_table_id: undefined, - conditions: [], - }, - EVAL: { - data_table_id: undefined, - formulas: [], - }, + JOIN: cloneDeep(DEFAULT_TRANSFORM_DATA_TABLE_VALUE_MAP.JOIN), + CONCAT: cloneDeep(DEFAULT_TRANSFORM_DATA_TABLE_VALUE_MAP.CONCAT), + QUERY: cloneDeep(DEFAULT_TRANSFORM_DATA_TABLE_VALUE_MAP.QUERY), + EVAL: cloneDeep(DEFAULT_TRANSFORM_DATA_TABLE_VALUE_MAP.EVAL), + PIVOT: cloneDeep(DEFAULT_TRANSFORM_DATA_TABLE_VALUE_MAP.PIVOT), + ADD_LABELS: cloneDeep(DEFAULT_TRANSFORM_DATA_TABLE_VALUE_MAP.ADD_LABELS), + VALUE_MAPPING: cloneDeep(DEFAULT_TRANSFORM_DATA_TABLE_VALUE_MAP.VALUE_MAPPING), }; const unsavedTransformData = { data_table_id: `UNSAVED-${getRandomId()}`, @@ -238,7 +289,7 @@ export const useWidgetGenerateStore = defineStore('widget-generate', () => { operator: operatorType, options: { [operatorType]: options[operatorType], - }, + } as DataTableTransformOptions, state: 'AVAILABLE', } as Partial; state.dataTables.push(unsavedTransformData); @@ -268,35 +319,8 @@ export const useWidgetGenerateStore = defineStore('widget-generate', () => { } state.dataTables = state.dataTables.map((dataTable) => (dataTable.data_table_id === result.data_table_id ? result : dataTable)); - // Update Referenced Transformed DataTable - if (!preventReferenceUpdating) { - const referencedDataTableIds = [] as string[]; - state.dataTables.forEach((dataTable) => { - const transformDataTalbeOptions = dataTable.options as DataTableTransformOptions; - const isReferenced = dataTable.data_type === 'TRANSFORMED' - && !dataTable?.data_table_id?.startsWith('UNSAVED-') - && ( - transformDataTalbeOptions?.JOIN?.data_tables?.includes(updateParams.data_table_id) - || transformDataTalbeOptions?.CONCAT?.data_tables?.includes(updateParams.data_table_id) - || transformDataTalbeOptions?.QUERY?.data_table_id === updateParams.data_table_id - || transformDataTalbeOptions?.EVAL?.data_table_id === updateParams.data_table_id - ); - if (isReferenced) referencedDataTableIds.push(dataTable.data_table_id as string); - }); - if (referencedDataTableIds.length) { - await Promise.all(referencedDataTableIds.map((dataTableId) => { - const dataTable = state.dataTables.find((_dataTable) => _dataTable.data_table_id === dataTableId) as PublicDataTableModel|PrivateDataTableModel; - actions.updateDataTable({ - data_table_id: dataTable.data_table_id, - name: dataTable.name, - options: { - ...dataTable.options, - }, - }); - return null; - })); - } - } + // Cascade Update Referenced Transformed DataTable + if (!preventReferenceUpdating) await actions.cascadeUpdateDataTable(result.data_table_id); return result; } catch (e: any) { @@ -305,6 +329,30 @@ export const useWidgetGenerateStore = defineStore('widget-generate', () => { return undefined; } }, + cascadeUpdateDataTable: async (dataTableId: string) => { + const isPrivate = state.widgetId.startsWith('private'); + const fetcher = isPrivate + ? SpaceConnector.clientV2.dashboard.privateDataTable.update + : SpaceConnector.clientV2.dashboard.publicDataTable.update; + + const children = getters.dataTableReferenceMap[dataTableId].children; + return children.reduce((chain, childId) => chain.then(async () => { + const currentDataTable = state.dataTables.find( + (_dataTable) => _dataTable.data_table_id === childId, + ) as DataTableModel; + + const result = await fetcher({ + data_table_id: childId, + name: currentDataTable?.name, + options: { + ...(currentDataTable?.options ?? {}), + }, + }); + state.dataTables = state.dataTables.map((dataTable) => (dataTable.data_table_id === result.data_table_id ? result : dataTable)); + + return actions.cascadeUpdateDataTable(childId); + }), Promise.resolve()); + }, deleteDataTable: async (deleteParams: DataTableDeleteParameters, unsaved?: boolean) => { if (unsaved) { state.dataTables = state.dataTables.filter((dataTable) => dataTable.data_table_id !== deleteParams.data_table_id); @@ -331,60 +379,24 @@ export const useWidgetGenerateStore = defineStore('widget-generate', () => { ErrorHandler.handleError(e); } }, - loadDataTable: async (loadParams: Partial) => { - const isPrivate = state.widgetId.startsWith('private'); - const fetcher = isPrivate - ? SpaceConnector.clientV2.dashboard.privateDataTable.load[]>> - : SpaceConnector.clientV2.dashboard.publicDataTable.load[]>>; - try { - state.dataTableLoadLoading = true; - const _granularity = state.selectedPreviewGranularity || 'MONTHLY'; - let _sort = loadParams.sort; - const dataTable = state.dataTables.find((_dataTable) => _dataTable.data_table_id === (loadParams.data_table_id || state.selectedDataTableId as string)); - if (!_sort || (_sort && _sort.length === 0)) { - const labelsInfoList = Object.keys(dataTable?.labels_info ?? {}); - if (labelsInfoList.includes('Date')) _sort = [{ key: 'Date', desc: false }]; - else if (_granularity === 'DAILY') _sort = [{ key: 'Day', desc: false }]; - else if (_granularity === 'MONTHLY') _sort = [{ key: 'Month', desc: false }]; - else if (_granularity === 'YEARLY') _sort = [{ key: 'Year', desc: false }]; - } - const { results, total_count } = await fetcher({ - granularity: _granularity, - page: { - start: 1, - limit: 15, - }, - ...loadParams, - sort: _sort, - data_table_id: loadParams.data_table_id || state.selectedDataTableId as string, // for fetching without data_table_id - vars: dashboardDetailGetters.dashboardInfo?.vars || {}, - }); - state.previewData = { results: results ?? [], total_count: total_count ?? 0 }; - setDataTableLoadFailed(false); - } catch (e) { - state.previewData = { results: [], total_count: 0 }; - setDataTableLoadFailed(true); - ErrorHandler.handleError(e); - } finally { - state.dataTableUpdating = false; - state.dataTableLoadLoading = false; - } - }, - updateWidget: async (updateParams: Partial) => { + updateWidget: async (updateParams: Partial): Promise => { const isPrivate = state.widgetId.startsWith('private'); const fetcher = isPrivate ? SpaceConnector.clientV2.dashboard.privateWidget.update : SpaceConnector.clientV2.dashboard.publicWidget.update; const sanitizedOptions = sanitizeWidgetOptions(updateParams.options ?? {}, updateParams.widget_type ?? 'table'); try { - state.widget = await fetcher({ + const result = await fetcher({ widget_id: state.widgetId, ...updateParams, options: sanitizedOptions, // Sanitize Wrong Options }); + state.widget = result; + return result; } catch (e: any) { showErrorMessage(e.message, e); ErrorHandler.handleError(e); + return undefined; } }, /* Step 2 */ @@ -400,13 +412,13 @@ export const useWidgetGenerateStore = defineStore('widget-generate', () => { state.widgetFormValueMap = {}; state.allDataTableInvalidMap = {}; }, - setWidgetForm: (widgetInfo?: WidgetModel) => { + setWidgetForm: (widgetInfo: WidgetModel) => { state.selectedWidgetName = widgetInfo?.widget_type || 'table'; - const _widgetConfig = getWidgetConfig(widgetInfo?.widget_type || 'table'); + const _widgetConfig = getWidgetConfig(widgetInfo.widget_type || 'table'); state.widget = widgetInfo; - state.widgetId = widgetInfo?.widget_id || ''; + state.widgetId = widgetInfo.widget_id; state.size = widgetInfo?.size || _widgetConfig?.meta?.sizes[0] || 'full'; - state.selectedDataTableId = widgetInfo?.data_table_id || undefined; + state.selectedDataTableId = widgetInfo?.data_table_id; state.widgetFormValueMap = widgetInfo?.options || {}; }, }; diff --git a/apps/web/src/common/modules/widgets/_widget-field-value-manager/constant/default-value-registry.ts b/apps/web/src/common/modules/widgets/_widget-field-value-manager/constant/default-value-registry.ts new file mode 100644 index 0000000000..84aabb2fcc --- /dev/null +++ b/apps/web/src/common/modules/widgets/_widget-field-value-manager/constant/default-value-registry.ts @@ -0,0 +1,514 @@ +import { DATA_TABLE_OPERATOR } from '@/common/modules/widgets/_constants/data-table-constant'; +import { + COLOR_SCHEMA, DATA_FIELD_HEATMAP_COLOR, DATE_FORMAT, DEFAULT_COMPARISON_COLOR, NUMBER_FORMAT, TABLE_DEFAULT_MINIMUM_WIDTH, WIDGET_HEIGHT, +} from '@/common/modules/widgets/_constants/widget-field-constant'; +import { integrateFieldsSchema } from '@/common/modules/widgets/_helpers/widget-field-helper'; +import { sortWidgetTableFields } from '@/common/modules/widgets/_helpers/widget-helper'; +import type { FieldDefaultValueConvertor, WidgetFieldTypeMap } from '@/common/modules/widgets/_widget-field-value-manager/type'; +import type { CategoryByOptions } from '@/common/modules/widgets/_widget-fields/category-by/type'; +import type { _ColorSchemaOptions as ColorSchemaOptions } from '@/common/modules/widgets/_widget-fields/color-schema/type'; +import type { ComparisonOptions } from '@/common/modules/widgets/_widget-fields/comparison/type'; +import type { DataFieldHeatmapColorOptions, DataFieldHeatmapColorValue } from '@/common/modules/widgets/_widget-fields/data-field-heatmap-color/type'; +import type { DataFieldOptions } from '@/common/modules/widgets/_widget-fields/data-field/type'; +import type { DateFormatOptions } from '@/common/modules/widgets/_widget-fields/date-format/type'; +import type { FormatRulesOptions } from '@/common/modules/widgets/_widget-fields/format-rules/type'; +import type { GroupByOptions } from '@/common/modules/widgets/_widget-fields/group-by/type'; +import { ICON_FIELD_ITEMS } from '@/common/modules/widgets/_widget-fields/icon/constant'; +import type { IconOptions } from '@/common/modules/widgets/_widget-fields/icon/type'; +import type { LegendOptions } from '@/common/modules/widgets/_widget-fields/legend/type'; +import type { MaxOptions } from '@/common/modules/widgets/_widget-fields/max/type'; +import type { MinOptions } from '@/common/modules/widgets/_widget-fields/min/type'; +import type { MissingValueOptions } from '@/common/modules/widgets/_widget-fields/missing-value/type'; +import type { NumberFormatOptions, NumberFormatValue } from '@/common/modules/widgets/_widget-fields/number-format/type'; +import type { PieChartTypeOptions } from '@/common/modules/widgets/_widget-fields/pie-chart-type/type'; +import type { SankeyDimensionsOptions } from '@/common/modules/widgets/_widget-fields/sankey-dimensions/type'; +import type { StackByOptions } from '@/common/modules/widgets/_widget-fields/stack-by/type'; +import type { SubTotalOptions } from '@/common/modules/widgets/_widget-fields/sub-total/type'; +import type { TableColumnComparisonOptions } from '@/common/modules/widgets/_widget-fields/table-column-comparison/type'; +import type { TableColumnWidthOptions } from '@/common/modules/widgets/_widget-fields/table-column-width/type'; +import type { TextWrapOptions } from '@/common/modules/widgets/_widget-fields/text-wrap/type'; +import type { TooltipNumberFormatOptions } from '@/common/modules/widgets/_widget-fields/tooltip-number-format/type'; +import type { TotalOptions } from '@/common/modules/widgets/_widget-fields/total/type'; +import type { WidgetHeightOptions } from '@/common/modules/widgets/_widget-fields/widget-height/type'; +import type { XAxisOptions } from '@/common/modules/widgets/_widget-fields/x-axis/type'; +import type { YAxisOptions } from '@/common/modules/widgets/_widget-fields/y-axis/type'; + +import { gray } from '@/styles/colors'; + +type DefaultValueRegistry = Record; + +export const widgetFieldDefaultValueMap: DefaultValueRegistry = { + dataField: {}, + formatRules: { + baseColor: gray[200], + rules: [], + }, + categoryBy: {}, + stackBy: {}, + xAxis: {}, + yAxis: {}, + colorSchema: { + colorName: 'Coral', + colorValue: COLOR_SCHEMA.Coral, + }, + comparison: { + decreaseColor: DEFAULT_COMPARISON_COLOR.DECREASE, + increaseColor: DEFAULT_COMPARISON_COLOR.INCREASE, + format: 'all', + toggleValue: true, + }, + tableColumnComparison: { + decreaseColor: DEFAULT_COMPARISON_COLOR.DECREASE, + increaseColor: DEFAULT_COMPARISON_COLOR.INCREASE, + format: 'fixed', + toggleValue: true, + fields: [], + }, + customTableColumnWidth: { + widthInfos: [], + }, + dataFieldHeatmapColor: {}, + dateFormat: { + format: Object.keys(DATE_FORMAT)[0], + }, + dateRange: { + inherit: true, + options: { + value: 'auto', + }, + }, + displayAnnotation: { + toggleValue: false, + }, + displaySeriesLabel: { + toggleValue: false, + }, + granularity: { + granularity: 'MONTHLY', + }, + groupBy: {}, + widgetHeader: { + toggleValue: false, + }, + icon: { + toggleValue: true, + icon: { name: 'ic_circle-filled', label: 'Circle' }, + color: gray[900], + }, + legend: { + toggleValue: true, + position: 'right', + }, + max: { + max: 0, + }, + min: { + min: 0, + }, + missingValue: { + type: 'lineToZero', + }, + numberFormat: {}, + pieChartType: { + type: 'pie', + }, + subTotal: { + toggleValue: false, + }, + total: { + toggleValue: false, + }, + tableColumnWidth: { + minimumWidth: TABLE_DEFAULT_MINIMUM_WIDTH, + widthType: 'auto', + fixedWidth: undefined, + }, + textWrap: { + toggleValue: false, + }, + tooltipNumberFormat: { + toggleValue: false, + }, + widgetHeight: { + type: WIDGET_HEIGHT.default, + }, + sankeyDimensions: { + data: [], + }, +} as const; + +export type WidgetFieldDefaultValueSetterRegistry = { + [K in keyof WidgetFieldTypeMap]: FieldDefaultValueConvertor; +}; + +export const widgetFieldDefaultValueSetterRegistry: WidgetFieldDefaultValueSetterRegistry = { + dataField: (widgetConfig, dataTable) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const dataFieldOptions = (_fieldsSchema.dataField?.options ?? {}) as DataFieldOptions; + + const result = widgetFieldDefaultValueMap.dataField; + + const isPivotDataTable = dataTable.operator === DATA_TABLE_OPERATOR.PIVOT; + if (isPivotDataTable) { // if pivot dataTable, always multiSelectable + return { + data: [dataTable.options.PIVOT?.fields?.column], + }; + } + + const fieldKeys = sortWidgetTableFields(Object.keys(dataTable?.data_info ?? {})); + + if (dataFieldOptions.multiSelectable) { + result.data = dataFieldOptions.allSelected ? fieldKeys : [fieldKeys?.[0]]; + } else { + result.data = fieldKeys?.[0]; + } + + + return result; + }, + formatRules: (widgetConfig, dataTable) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const formatRulesOptions = (_fieldsSchema.formatRules?.options ?? {}) as FormatRulesOptions; + + let result = widgetFieldDefaultValueMap.formatRules; + + if (formatRulesOptions.default || formatRulesOptions.baseColor) { + result = { + ...result, + rules: formatRulesOptions.default ?? widgetFieldDefaultValueMap.formatRules.rules, + baseColor: formatRulesOptions.baseColor ?? widgetFieldDefaultValueMap.formatRules.baseColor, + }; + } + if (formatRulesOptions.useField && formatRulesOptions.dataTarget) { + const fieldKeys = sortWidgetTableFields(Object.keys(dataTable?.[formatRulesOptions.dataTarget] ?? {})); + result = { + ...result, + field: fieldKeys?.[0], + }; + } + return result; + }, + categoryBy: (widgetConfig, dataTable) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const categoryByOptions = (_fieldsSchema.categoryBy?.options ?? {}) as CategoryByOptions; + + const result = widgetFieldDefaultValueMap.categoryBy; + + const fieldKeys = sortWidgetTableFields(Object.keys(dataTable?.[categoryByOptions.dataTarget] ?? {})); + + return { + ...result, + data: fieldKeys?.[0], + count: categoryByOptions.defaultMaxCount, + }; + }, + stackBy: (widgetConfig, dataTable) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const stackByOptions = (_fieldsSchema.stackBy?.options ?? {}) as StackByOptions; + + const result = widgetFieldDefaultValueMap.categoryBy; + + const fieldKeys = sortWidgetTableFields(Object.keys(dataTable?.[stackByOptions.dataTarget] ?? {})); + + return { + ...result, + data: fieldKeys?.[0], + count: stackByOptions.defaultMaxCount, + }; + }, + xAxis: (widgetConfig, dataTable) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const xAxisOptions = (_fieldsSchema.xAxis?.options ?? {}) as XAxisOptions; + + const result = widgetFieldDefaultValueMap.categoryBy; + + const fieldKeys = sortWidgetTableFields(Object.keys(dataTable?.[xAxisOptions.dataTarget] ?? {})); + + return { + ...result, + data: fieldKeys?.[0], + count: xAxisOptions.defaultMaxCount, + }; + }, + yAxis: (widgetConfig, dataTable) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const yAxisOptions = (_fieldsSchema.yAxis?.options ?? {}) as YAxisOptions; + + const result = widgetFieldDefaultValueMap.categoryBy; + + const fieldKeys = sortWidgetTableFields(Object.keys(dataTable?.[yAxisOptions.dataTarget] ?? {})); + + return { + ...result, + data: fieldKeys?.[0], + count: yAxisOptions.defaultMaxCount, + }; + }, + colorSchema: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const colorSchemaOptions = (_fieldsSchema.colorSchema?.options ?? {}) as ColorSchemaOptions; + + if (colorSchemaOptions.default) { + return { + colorName: colorSchemaOptions.default, + colorValue: COLOR_SCHEMA[colorSchemaOptions.default], + }; + } + return widgetFieldDefaultValueMap.colorSchema; + }, + comparison: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const comparisonOptions = (_fieldsSchema.comparison?.options ?? {}) as ComparisonOptions; + + if (comparisonOptions.toggle) { + return widgetFieldDefaultValueMap.comparison; + } + + return { + toggleValue: false, + }; + }, + tableColumnComparison: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const tableColumnComparisonOptions = (_fieldsSchema.tableColumnComparison?.options ?? {}) as TableColumnComparisonOptions; + + if (tableColumnComparisonOptions.toggle) { + return widgetFieldDefaultValueMap.tableColumnComparison; + } + + return { + toggleValue: false, + }; + }, + customTableColumnWidth: () => widgetFieldDefaultValueMap.customTableColumnWidth, + dataFieldHeatmapColor: (widgetConfig, dataTable) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const dataFieldheatmapColorOptions = (_fieldsSchema.dataFieldHeatmapColor?.options ?? {}) as DataFieldHeatmapColorOptions; + const isPivotDataTable = dataTable.operator === DATA_TABLE_OPERATOR.PIVOT; + const columnFieldForPivot = dataTable.options.PIVOT?.fields?.column as string; + + const dataKeys = Object.keys(dataTable?.data_info ?? {}) as string[]; + const fieldKeys = isPivotDataTable ? [columnFieldForPivot] : dataKeys; + + const result: DataFieldHeatmapColorValue = widgetFieldDefaultValueMap.dataFieldHeatmapColor; + fieldKeys.forEach((key) => { + result[key] = { + colorInfo: dataFieldheatmapColorOptions?.default ?? DATA_FIELD_HEATMAP_COLOR.NONE.key, + }; + }); + return result; + }, + dateFormat: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const dateFormatOptions = (_fieldsSchema.dateFormat?.options ?? {}) as DateFormatOptions; + + + if (dateFormatOptions?.default) { + return { + format: dateFormatOptions.default, + }; + } + + return widgetFieldDefaultValueMap.dateFormat; + }, + dateRange: () => widgetFieldDefaultValueMap.dateRange, + displayAnnotation: () => widgetFieldDefaultValueMap.displayAnnotation, + displaySeriesLabel: () => widgetFieldDefaultValueMap.displaySeriesLabel, + granularity: () => widgetFieldDefaultValueMap.granularity, + groupBy: (widgetConfig, dataTable) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const groupByOptions = (_fieldsSchema.groupBy?.options ?? {}) as GroupByOptions; + + const result = widgetFieldDefaultValueMap.groupBy; + + let fieldKeys = sortWidgetTableFields(Object.keys(dataTable?.[groupByOptions.dataTarget] ?? {})); + if (groupByOptions.fixedValue) { + fieldKeys = fieldKeys.filter((key) => key === groupByOptions.fixedValue); + } + + result.data = groupByOptions.multiSelectable ? [fieldKeys?.[0]] : fieldKeys?.[0]; + + if (!groupByOptions.hideCount) { + result.count = groupByOptions.defaultMaxCount ? groupByOptions.defaultMaxCount : 5; + } + + return result; + }, + widgetHeader: () => widgetFieldDefaultValueMap.widgetHeader, + icon: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const iconOptions = (_fieldsSchema.icon?.options ?? {}) as IconOptions; + + const initialValue = widgetFieldDefaultValueMap.icon; + + if (iconOptions.toggle) { + const defaultIcon = ICON_FIELD_ITEMS.find((item) => item.name === iconOptions.default); + + return { + ...initialValue, + toggleValue: true, + icon: iconOptions.default ? defaultIcon : initialValue.icon, + }; + } + return undefined; + }, + legend: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const legendOptions = (_fieldsSchema.legend?.options ?? {}) as LegendOptions; + + const initialValue = widgetFieldDefaultValueMap.legend; + + if (legendOptions.toggle) { + return { + toggleValue: true, + position: legendOptions.showPositionField ? initialValue.position : undefined, + }; + } + return undefined; + }, + max: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const maxOptions = (_fieldsSchema.max?.options ?? {}) as MaxOptions; + + if (maxOptions.default !== undefined) { + return { + max: maxOptions.default, + }; + } + return widgetFieldDefaultValueMap.max; + }, + min: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const minOptions = (_fieldsSchema.min?.options ?? {}) as MinOptions; + + if (minOptions.default !== undefined) { + return { + min: minOptions.default, + }; + } + return widgetFieldDefaultValueMap.min; + }, + missingValue: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const missingValueOptions = (_fieldsSchema.missingValue?.options ?? {}) as MissingValueOptions; + + if (missingValueOptions.default) { + return { + type: missingValueOptions.default, + }; + } + return widgetFieldDefaultValueMap.missingValue; + }, + numberFormat: (widgetConfig, dataTable) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const numberFormatOptions = (_fieldsSchema.numberFormat?.options ?? {}) as NumberFormatOptions; + const isPivotDataTable = dataTable.operator === DATA_TABLE_OPERATOR.PIVOT; + const columnFieldForPivot = dataTable.options.PIVOT?.fields?.column as string; + + const dataKeys = Object.keys(dataTable?.data_info ?? {}) as string[]; + const fieldKeys = isPivotDataTable ? [columnFieldForPivot] : dataKeys; + + const result: NumberFormatValue = widgetFieldDefaultValueMap.numberFormat; + fieldKeys.forEach((key) => { + result[key] = { + format: numberFormatOptions.default ?? NUMBER_FORMAT.AUTO, + }; + }); + return result; + }, + pieChartType: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const pieChartTypeOptions = (_fieldsSchema.pieChartType?.options ?? {}) as PieChartTypeOptions; + + if (pieChartTypeOptions.default) { + return { + type: pieChartTypeOptions.default, + }; + } + + return widgetFieldDefaultValueMap.pieChartType; + }, + subTotal: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const subTotalOptions = (_fieldsSchema.subTotal?.options ?? {}) as SubTotalOptions; + + if (subTotalOptions.toggle) { + return { + toggleValue: true, + freeze: subTotalOptions.default ?? false, + }; + } + + return widgetFieldDefaultValueMap.subTotal; + }, + total: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const totalOptions = (_fieldsSchema.total?.options ?? {}) as TotalOptions; + + if (totalOptions.toggle) { + return { + toggleValue: true, + freeze: totalOptions.default ?? false, + }; + } + + return widgetFieldDefaultValueMap.total; + }, + tableColumnWidth: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const tableColumnWidthOptions = (_fieldsSchema.tableColumnWidth?.options ?? {}) as TableColumnWidthOptions; + + const initialValue = widgetFieldDefaultValueMap.tableColumnWidth; + + if (tableColumnWidthOptions.defaultMinimumWidth || tableColumnWidthOptions.defaultFixedWidth) { + return { + minimumWidth: tableColumnWidthOptions.defaultMinimumWidth ? tableColumnWidthOptions.defaultMinimumWidth : initialValue.minimumWidth, + widthType: tableColumnWidthOptions?.defaultFixedWidth ? 'fixed' : 'auto', + fixedWidth: tableColumnWidthOptions?.defaultFixedWidth, + }; + } + return initialValue; + }, + textWrap: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const textWrapWidthOptions = (_fieldsSchema.textWrap?.options ?? {}) as TextWrapOptions; + + if (textWrapWidthOptions.toggle) { + return { + toggleValue: true, + }; + } + return widgetFieldDefaultValueMap.textWrap; + }, + tooltipNumberFormat: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const tooltipNumberFormatOptions = (_fieldsSchema.tooltipNumberFormat?.options ?? {}) as TooltipNumberFormatOptions; + + if (tooltipNumberFormatOptions.default) { + return { + toggleValue: true, + }; + } + return widgetFieldDefaultValueMap.tooltipNumberFormat; + }, + widgetHeight: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const widgetHeightOptions = (_fieldsSchema.widgetHeight?.options ?? {}) as WidgetHeightOptions; + + if (widgetHeightOptions.default) { + return { + type: widgetHeightOptions.default, + }; + } + return widgetFieldDefaultValueMap.widgetHeight; + }, + sankeyDimensions: (widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const sankeyDimensionsOptions = (_fieldsSchema.sankeyDimensions?.options ?? {}) as SankeyDimensionsOptions; + + return { + data: [], + count: sankeyDimensionsOptions.defaultMaxCount, + }; + }, +}; diff --git a/apps/web/src/common/modules/widgets/_widget-field-value-manager/constant/validator-registry.ts b/apps/web/src/common/modules/widgets/_widget-field-value-manager/constant/validator-registry.ts new file mode 100644 index 0000000000..e08d1a2ba4 --- /dev/null +++ b/apps/web/src/common/modules/widgets/_widget-field-value-manager/constant/validator-registry.ts @@ -0,0 +1,187 @@ +import { FORMAT_RULE_TYPE } from '@/common/modules/widgets/_constants/widget-field-constant'; +import { integrateFieldsSchema } from '@/common/modules/widgets/_helpers/widget-field-helper'; +import type { FieldValueValidator } from '@/common/modules/widgets/_widget-field-value-manager/type'; +import type { CategoryByValue, CategoryByOptions } from '@/common/modules/widgets/_widget-fields/category-by/type'; +import type { ColorSchemaValue } from '@/common/modules/widgets/_widget-fields/color-schema/type'; +import type { ComparisonValue } from '@/common/modules/widgets/_widget-fields/comparison/type'; +import type { CustomTableColumnWidthValue } from '@/common/modules/widgets/_widget-fields/custom-table-column-width/type'; +import type { DataFieldOptions, DataFieldValue } from '@/common/modules/widgets/_widget-fields/data-field/type'; +import { DAILY_ENABLED_VALUES, MONTHLY_ENABLED_VALUES, YEARLY_ENABLED_VALUES } from '@/common/modules/widgets/_widget-fields/date-range/constant'; +import { checkInvalidCustomValue } from '@/common/modules/widgets/_widget-fields/date-range/helper'; +import type { DateRangeValue } from '@/common/modules/widgets/_widget-fields/date-range/type'; +import type { DisplayAnnotationValue } from '@/common/modules/widgets/_widget-fields/display-annotation/type'; +import type { DisplaySeriesLabelValue } from '@/common/modules/widgets/_widget-fields/display-series-label/type'; +import type { FormatRulesOptions, FormatRulesValue } from '@/common/modules/widgets/_widget-fields/format-rules/type'; +import type { GranularityValue } from '@/common/modules/widgets/_widget-fields/granularity/type'; +import type { GroupByOptions, GroupByValue } from '@/common/modules/widgets/_widget-fields/group-by/type'; +import type { WidgetHeaderValue } from '@/common/modules/widgets/_widget-fields/header/type'; +import { ICON_FIELD_ITEMS } from '@/common/modules/widgets/_widget-fields/icon/constant'; +import type { IconValue } from '@/common/modules/widgets/_widget-fields/icon/type'; +import type { MaxValue } from '@/common/modules/widgets/_widget-fields/max/type'; +import type { MinValue } from '@/common/modules/widgets/_widget-fields/min/type'; +import type { SankeyDimensionsOptions, SankeyDimensionsValue } from '@/common/modules/widgets/_widget-fields/sankey-dimensions/type'; +import type { StackByValue, StackByOptions } from '@/common/modules/widgets/_widget-fields/stack-by/type'; +import type { TableColumnComparisonValue } from '@/common/modules/widgets/_widget-fields/table-column-comparison/type'; +import type { TableColumnWidthValue } from '@/common/modules/widgets/_widget-fields/table-column-width/type'; +import type { XAxisValue, XAxisOptions } from '@/common/modules/widgets/_widget-fields/x-axis/type'; +import type { YAxisValue, YAxisOptions } from '@/common/modules/widgets/_widget-fields/y-axis/type'; + +export interface WidgetValidatorRegistry { + [fieldKey: string]: FieldValueValidator; +} + +export const widgetValidatorRegistry: WidgetValidatorRegistry = { + dateRange: (fieldValue: DateRangeValue, widgetConfig, allValueMap) => { + const _dateRangeType = fieldValue.options?.value; + const _granularity = allValueMap?.granularity?.value?.granularity; + if (!_dateRangeType || !_granularity) return false; + if (checkInvalidCustomValue(fieldValue, _granularity).invalid) return false; + if (_granularity === 'MONTHLY' && !MONTHLY_ENABLED_VALUES.includes(_dateRangeType)) return false; + if (_granularity === 'DAILY' && !DAILY_ENABLED_VALUES.includes(_dateRangeType)) return false; + if (_granularity === 'YEARLY' && !YEARLY_ENABLED_VALUES.includes(_dateRangeType)) return false; + + return true; + }, + dataField: (fieldValue: DataFieldValue, widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const dataFieldOptions = (_fieldsSchema.dataField?.options ?? {}) as DataFieldOptions; + if (dataFieldOptions.multiSelectable) { + return Array.isArray(fieldValue.data) && !!fieldValue.data.length; + } + return !!fieldValue.data; + }, + formatRules: (fieldValue: FormatRulesValue, widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const formatRulesOptions = (_fieldsSchema.formatRules?.options ?? {}) as FormatRulesOptions; + const type = formatRulesOptions.formatRulesType; + + if (type === FORMAT_RULE_TYPE.textThreshold) { + return fieldValue.rules.every((d) => !!d.text && !!d.color); + } + if (type === FORMAT_RULE_TYPE.numberThreshold || type === FORMAT_RULE_TYPE.percentThreshold) { + return fieldValue.rules.every((d) => !!d.number && !!d.color); + } + if (type === FORMAT_RULE_TYPE.textNumberThreshold) { + return fieldValue.rules.every((d) => !!d.text && !!d.number && !!d.color); + } + if (formatRulesOptions.useField) { + return !!fieldValue.field; + } + return true; + }, + categoryBy: (fieldValue: CategoryByValue, widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const categoryByOptions = (_fieldsSchema.categoryBy?.options ?? {}) as CategoryByOptions; + if (!fieldValue.data || !fieldValue.count || fieldValue.count < 0 || fieldValue.count > categoryByOptions.max) return false; + return true; + }, + stackBy: (fieldValue: StackByValue, widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const stackByOptions = (_fieldsSchema.stackBy?.options ?? {}) as StackByOptions; + if (!fieldValue.data || !fieldValue.count || fieldValue.count < 0 || fieldValue.count > stackByOptions.max) return false; + return true; + }, + xAxis: (fieldValue: XAxisValue, widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const xAxisOptions = (_fieldsSchema.xAxis?.options ?? {}) as XAxisOptions; + if (!fieldValue.data || !fieldValue.count || fieldValue.count < 0 || fieldValue.count > xAxisOptions.max) return false; + return true; + }, + yAxis: (fieldValue: YAxisValue, widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const yAxisOptions = (_fieldsSchema.yAxis?.options ?? {}) as YAxisOptions; + if (!fieldValue.data || !fieldValue.count || fieldValue.count < 0 || fieldValue.count > yAxisOptions.max) return false; + return true; + }, + colorSchema: (fieldValue: ColorSchemaValue) => { + if (!fieldValue.colorName || !fieldValue.colorValue?.length) return false; + return true; + }, + comparison: (fieldValue: ComparisonValue) => { + if (fieldValue.toggleValue) { + return !!fieldValue.decreaseColor && !!fieldValue.increaseColor && !!fieldValue.format; + } + return true; + }, + tableColumnComparison: (fieldValue: TableColumnComparisonValue) => { + if (fieldValue.toggleValue) { + return !!fieldValue.decreaseColor && !!fieldValue.increaseColor && !!fieldValue.format && !!fieldValue.fields?.length; + } + return true; + }, + customTableColumnWidth: (fieldValue: CustomTableColumnWidthValue) => { + if (fieldValue.widthInfos?.length) { + return fieldValue.widthInfos.every((d) => !!d.fieldKey && d.width >= 0) + || fieldValue.widthInfos.map((d) => d.fieldKey).length === new Set(fieldValue.widthInfos.map((d) => d.fieldKey)).size; + } + return true; + }, + dataFieldHeatmapColor: () => true, + dateFormat: () => true, + displayAnnotation: (fieldValue: DisplayAnnotationValue) => { + if (!fieldValue.toggleValue) return true; + return !!fieldValue.annotation; + }, + displaySeriesLabel: (fieldValue: DisplaySeriesLabelValue) => { + if (!fieldValue.toggleValue || fieldValue.rotate === undefined) return true; + if (fieldValue.rotate < -90 || fieldValue.rotate > 90) return false; + return true; + }, + granularity: (fieldValue: GranularityValue) => { + if (!fieldValue.granularity) return false; + return true; + }, + groupBy: (fieldValue: GroupByValue, widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const groupByOptions = (_fieldsSchema.groupBy?.options ?? {}) as GroupByOptions; + if (groupByOptions.fixedValue && fieldValue.data !== groupByOptions.fixedValue) return false; + if (groupByOptions.hideCount && !!fieldValue.count) return false; + if (!groupByOptions.hideCount && groupByOptions.max && groupByOptions.defaultMaxCount && (!fieldValue.count || fieldValue.count > groupByOptions.max)) return false; + if (groupByOptions.multiSelectable && (!Array.isArray(fieldValue.data) || !fieldValue.data.length)) return false; + if (groupByOptions.excludeDateField && fieldValue.data === 'Date') return false; + return !!fieldValue.data; + }, + sankeyDimensions: (fieldValue: SankeyDimensionsValue, widgetConfig) => { + const _fieldsSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const sankeyDimensionsOptions = (_fieldsSchema.sankeyDimensions?.options ?? {}) as SankeyDimensionsOptions; + if (!fieldValue.data || fieldValue.data.length !== 2 || !fieldValue.count || fieldValue.count > sankeyDimensionsOptions.max) return false; + return true; + }, + widgetHeader: (fieldValue: WidgetHeaderValue) => { + if (!fieldValue.toggleValue) return true; + return !!fieldValue.title?.trim(); + }, + icon: (fieldValue: IconValue) => { + if (!fieldValue.toggleValue) return true; + return !!fieldValue.color && !!fieldValue.icon && ICON_FIELD_ITEMS.some((item) => item.name === fieldValue.icon?.name); + }, + legend: () => true, + max: (fieldValue: MaxValue) => { + if (typeof fieldValue.max !== 'number') return false; + if (fieldValue.max < 0) return false; + return true; + }, + min: (fieldValue: MinValue) => { + if (typeof fieldValue.min !== 'number') return false; + if (fieldValue.min < 0) return false; + return true; + }, + missingValue: () => true, + numberFormat: () => true, + pieChartType: () => true, + subTotal: () => true, + total: () => true, + tableColumnWidth: (fieldValue: TableColumnWidthValue) => { + if (!fieldValue.minimumWidth) return false; + if (fieldValue.widthType === 'fixed') { + if (!fieldValue.fixedWidth) return false; + if (fieldValue.fixedWidth < fieldValue.minimumWidth) return false; + } + return true; + }, + textWrap: () => true, + tooltipNumberFormat: () => true, + widgetHeight: () => true, +}; + + diff --git a/apps/web/src/common/modules/widgets/_widget-field-value-manager/index.ts b/apps/web/src/common/modules/widgets/_widget-field-value-manager/index.ts new file mode 100644 index 0000000000..d88a61b20c --- /dev/null +++ b/apps/web/src/common/modules/widgets/_widget-field-value-manager/index.ts @@ -0,0 +1,130 @@ +import { ref } from 'vue'; + +import { cloneDeep } from 'lodash'; + +import type { PrivateDataTableModel } from '@/schema/dashboard/private-data-table/model'; +import type { PublicDataTableModel } from '@/schema/dashboard/public-data-table/model'; + +import { integrateFieldsSchema } from '@/common/modules/widgets/_helpers/widget-field-helper'; +import { widgetFieldDefaultValueSetterRegistry } from '@/common/modules/widgets/_widget-field-value-manager/constant/default-value-registry'; +import { widgetValidatorRegistry } from '@/common/modules/widgets/_widget-field-value-manager/constant/validator-registry'; +import type { WidgetFieldTypeMap, WidgetFieldValueMap } from '@/common/modules/widgets/_widget-field-value-manager/type'; +import type { WidgetConfig } from '@/common/modules/widgets/types/widget-config-type'; + + +export default class WidgetFieldValueManager { + private widgetConfig: WidgetConfig; + + private dataTable: PublicDataTableModel|PrivateDataTableModel; + + private originData = ref({}); + + private validationErrors: Record = {}; + + private modifiedData = ref({}); + + private widgetInvalid: boolean; + + static applyDefaultValue( + originData: WidgetFieldValueMap, + widgetConfig: WidgetConfig, + dataTable?: PublicDataTableModel|PrivateDataTableModel, + ): WidgetFieldValueMap { + const result: WidgetFieldValueMap = cloneDeep({ ...originData }); + + const integratedFieldSchema = integrateFieldsSchema(widgetConfig.requiredFieldsSchema, widgetConfig.optionalFieldsSchema); + const widgetFieldKeys = ['widgetHeader', ...Object.keys(integratedFieldSchema)]; + Object.entries(widgetFieldDefaultValueSetterRegistry).forEach(([key, defaultValueSetter]) => { + if (widgetFieldKeys.includes(key) && !result[key]) { + result[key] = { value: defaultValueSetter(widgetConfig, dataTable) }; + } + }); + + return result; + } + + constructor( + widgetConfig: WidgetConfig, + dataTable: PublicDataTableModel|PrivateDataTableModel, + originData: WidgetFieldValueMap, + ) { + console.debug('WidgetFieldValueManager.constructor()', widgetConfig, dataTable, originData); + this.widgetConfig = widgetConfig; + this.dataTable = dataTable; + this.widgetInvalid = !dataTable; + this.originData.value = originData; + this.modifiedData.value = WidgetFieldValueManager.applyDefaultValue(originData, widgetConfig, dataTable); + } + + get data(): WidgetFieldValueMap { + return this.modifiedData.value; + } + + setFieldValue(key: Key, value: WidgetFieldTypeMap[Key]['value']): boolean { + const field = this.modifiedData.value[key] || this.originData.value[key]; + if (!field) { + throw new Error(`Field "${key}" does not exist.`); + } + + this.modifiedData.value = { + ...this.modifiedData.value, + [key]: { + value: cloneDeep(value), + }, + }; + + const validator = widgetValidatorRegistry[key]; + if (validator) { + const isValid = validator(this.modifiedData.value[key].value, this.widgetConfig, this.modifiedData.value); + if (!isValid) { + this.validationErrors[key as string] = `Invalid value for field "${key}"`; + return false; + } + } + + delete this.validationErrors[key as string]; + return true; + } + + validateAll(): boolean { + this.validationErrors = {}; + let isValid = true; + + Object.entries(this.modifiedData.value ?? {}).forEach(([key, field]) => { + const validator = widgetValidatorRegistry[key]; + if (validator && !validator(field.value, this.widgetConfig, this.modifiedData.value)) { + this.validationErrors[key] = `Invalid value for field "${key}"`; + isValid = false; + } + }); + + return isValid; + } + + updateOriginData(data: WidgetFieldValueMap): void { + this.originData.value = { ...data }; + this.modifiedData.value = { ...WidgetFieldValueManager.applyDefaultValue(data, this.widgetConfig, this.dataTable) }; + this.validationErrors = {}; + } + + private updateWidgetConfig(widgetConfig: WidgetConfig): void { + this.widgetConfig = widgetConfig; + } + + private updateModifiedData(data: WidgetFieldValueMap): void { + this.modifiedData.value = { ...data }; + } + + updateWidgetType(newWidgetConfig: WidgetConfig): void { + this.updateWidgetConfig(newWidgetConfig); + this.updateModifiedData(WidgetFieldValueManager.applyDefaultValue({}, newWidgetConfig, this.dataTable)); + this.validationErrors = {}; + } + + updateDataTableAndOriginData(dataTable: PublicDataTableModel|PrivateDataTableModel, data: WidgetFieldValueMap): void { + this.dataTable = dataTable; + this.originData.value = { ...data }; + this.updateModifiedData(WidgetFieldValueManager.applyDefaultValue(data, this.widgetConfig, dataTable)); + this.validationErrors = {}; + } +} diff --git a/apps/web/src/common/modules/widgets/_widget-field-value-manager/type.ts b/apps/web/src/common/modules/widgets/_widget-field-value-manager/type.ts new file mode 100644 index 0000000000..7063255d9e --- /dev/null +++ b/apps/web/src/common/modules/widgets/_widget-field-value-manager/type.ts @@ -0,0 +1,83 @@ +import type { PrivateDataTableModel } from '@/schema/dashboard/private-data-table/model'; +import type { PublicDataTableModel } from '@/schema/dashboard/public-data-table/model'; + +import type { CategoryByValue } from '@/common/modules/widgets/_widget-fields/category-by/type'; +import type { ColorSchemaValue } from '@/common/modules/widgets/_widget-fields/color-schema/type'; +import type { ComparisonValue } from '@/common/modules/widgets/_widget-fields/comparison/type'; +import type { CustomTableColumnWidthValue } from '@/common/modules/widgets/_widget-fields/custom-table-column-width/type'; +import type { DataFieldHeatmapColorValue } from '@/common/modules/widgets/_widget-fields/data-field-heatmap-color/type'; +import type { DataFieldValue } from '@/common/modules/widgets/_widget-fields/data-field/type'; +import type { DateFormatValue } from '@/common/modules/widgets/_widget-fields/date-format/type'; +import type { DateRangeValue } from '@/common/modules/widgets/_widget-fields/date-range/type'; +import type { DisplayAnnotationValue } from '@/common/modules/widgets/_widget-fields/display-annotation/type'; +import type { DisplaySeriesLabelValue } from '@/common/modules/widgets/_widget-fields/display-series-label/type'; +import type { FormatRulesValue } from '@/common/modules/widgets/_widget-fields/format-rules/type'; +import type { GranularityValue } from '@/common/modules/widgets/_widget-fields/granularity/type'; +import type { GroupByValue } from '@/common/modules/widgets/_widget-fields/group-by/type'; +import type { WidgetHeaderValue } from '@/common/modules/widgets/_widget-fields/header/type'; +import type { IconValue } from '@/common/modules/widgets/_widget-fields/icon/type'; +import type { LegendValue } from '@/common/modules/widgets/_widget-fields/legend/type'; +import type { MaxValue } from '@/common/modules/widgets/_widget-fields/max/type'; +import type { MinValue } from '@/common/modules/widgets/_widget-fields/min/type'; +import type { MissingValueValue } from '@/common/modules/widgets/_widget-fields/missing-value/type'; +import type { NumberFormatValue } from '@/common/modules/widgets/_widget-fields/number-format/type'; +import type { PieChartTypeValue } from '@/common/modules/widgets/_widget-fields/pie-chart-type/type'; +import type { SankeyDimensionsValue } from '@/common/modules/widgets/_widget-fields/sankey-dimensions/type'; +import type { StackByValue } from '@/common/modules/widgets/_widget-fields/stack-by/type'; +import type { SubTotalValue } from '@/common/modules/widgets/_widget-fields/sub-total/type'; +import type { TableColumnComparisonValue } from '@/common/modules/widgets/_widget-fields/table-column-comparison/type'; +import type { TableColumnWidthValue } from '@/common/modules/widgets/_widget-fields/table-column-width/type'; +import type { TextWrapValue } from '@/common/modules/widgets/_widget-fields/text-wrap/type'; +import type { TooltipNumberFormatValue } from '@/common/modules/widgets/_widget-fields/tooltip-number-format/type'; +import type { TotalValue } from '@/common/modules/widgets/_widget-fields/total/type'; +import type { WidgetHeightValue } from '@/common/modules/widgets/_widget-fields/widget-height/type'; +import type { XAxisValue } from '@/common/modules/widgets/_widget-fields/x-axis/type'; +import type { YAxisValue } from '@/common/modules/widgets/_widget-fields/y-axis/type'; +import type { WidgetConfig } from '@/common/modules/widgets/types/widget-config-type'; + +export type FieldValueValidator = (fieldValue: T, widgetConfig: WidgetConfig, allValueMap?: WidgetFieldValueMap) => boolean; +export type FieldDefaultValueConvertor = (widgetConfig: WidgetConfig, dataTable: PublicDataTableModel|PrivateDataTableModel) => WidgetFieldTypeMap[T]['value']; + +export interface WidgetFieldValueMap { + [fieldKey: string]: WidgetFieldValue; +} + +export interface WidgetFieldValue { + value?: T; + meta?: Record; +} + +export interface WidgetFieldTypeMap { + xAxis: WidgetFieldValue; + yAxis: WidgetFieldValue; + widgetHeight: WidgetFieldValue; + total: WidgetFieldValue; + tooltipNumberFormat: WidgetFieldValue; + textWrap: WidgetFieldValue; + tableColumnWidth: WidgetFieldValue; + subTotal: WidgetFieldValue; + stackBy: WidgetFieldValue; + pieChartType: WidgetFieldValue; + numberFormat: WidgetFieldValue; + missingValue: WidgetFieldValue; + min: WidgetFieldValue; + max: WidgetFieldValue; + legend: WidgetFieldValue; + icon: WidgetFieldValue; + widgetHeader: WidgetFieldValue; + groupBy: WidgetFieldValue; + granularity: WidgetFieldValue; + formatRules: WidgetFieldValue; + displaySeriesLabel: WidgetFieldValue; + displayAnnotation: WidgetFieldValue; + dateRange: WidgetFieldValue; + dateFormat: WidgetFieldValue; + dataFieldHeatmapColor: WidgetFieldValue; + dataField: WidgetFieldValue; + customTableColumnWidth: WidgetFieldValue; + comparison: WidgetFieldValue; + tableColumnComparison: WidgetFieldValue; + colorSchema: WidgetFieldValue; + categoryBy: WidgetFieldValue; + sankeyDimensions: WidgetFieldValue; +} diff --git a/apps/web/src/common/modules/widgets/_widget-fields/advanced-format-rules/WidgetFieldAdvancedFormatRules.vue b/apps/web/src/common/modules/widgets/_widget-fields/advanced-format-rules/WidgetFieldAdvancedFormatRules.vue deleted file mode 100644 index cfcd224ba3..0000000000 --- a/apps/web/src/common/modules/widgets/_widget-fields/advanced-format-rules/WidgetFieldAdvancedFormatRules.vue +++ /dev/null @@ -1,243 +0,0 @@ - - - - - diff --git a/apps/web/src/common/modules/widgets/_widget-fields/advanced-format-rules/type.ts b/apps/web/src/common/modules/widgets/_widget-fields/advanced-format-rules/type.ts deleted file mode 100644 index a62e278580..0000000000 --- a/apps/web/src/common/modules/widgets/_widget-fields/advanced-format-rules/type.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { ADVANCED_FORMAT_RULE_TYPE } from '@/common/modules/widgets/_constants/widget-field-constant'; -import type { FormatRulesValue } from '@/common/modules/widgets/_widget-fields/format-rules/type'; - -export interface AdvancedFormatRulesValue { - field?: string; - value: FormatRulesValue[]; - baseColor?: string; -} - -export type AdvancedFormatRulesType = typeof ADVANCED_FORMAT_RULE_TYPE[keyof typeof ADVANCED_FORMAT_RULE_TYPE]; - -export interface AdvancedFormatRulesOptions { - formatRulesType: AdvancedFormatRulesType; - description?: string; - baseColor?: string; -} diff --git a/apps/web/src/common/modules/widgets/_widget-fields/category-by/WidgetFieldCategoryBy.vue b/apps/web/src/common/modules/widgets/_widget-fields/category-by/WidgetFieldCategoryBy.vue index fc6b31af32..dac5490b46 100644 --- a/apps/web/src/common/modules/widgets/_widget-fields/category-by/WidgetFieldCategoryBy.vue +++ b/apps/web/src/common/modules/widgets/_widget-fields/category-by/WidgetFieldCategoryBy.vue @@ -7,24 +7,25 @@ import { PFieldGroup } from '@cloudforet/mirinae'; import type { MenuItem } from '@cloudforet/mirinae/types/controls/context-menu/type'; import WidgetFieldDropdownAndMax from '@/common/modules/widgets/_components/WidgetFieldDropdownAndMax.vue'; -import { useGranularityMenuItem } from '@/common/modules/widgets/_composables/use-granularity-menu-items'; import { sortWidgetTableFields } from '@/common/modules/widgets/_helpers/widget-helper'; -import type { CategoryByValue, CategoryByOptions } from '@/common/modules/widgets/_widget-fields/category-by/type'; +import { useWidgetGenerateStore } from '@/common/modules/widgets/_store/widget-generate-store'; +import type { + CategoryByOptions, +} from '@/common/modules/widgets/_widget-fields/category-by/type'; import type { - WidgetFieldComponentEmit, WidgetFieldComponentProps, } from '@/common/modules/widgets/types/widget-field-type'; +const FIELD_KEY = 'categoryBy'; +const props = defineProps>(); +const widgetGenerateStore = useWidgetGenerateStore(); +const widgetGenerateGetters = widgetGenerateStore.getters; -const props = defineProps>(); -const emit = defineEmits>(); -const { labelsMenuItem } = useGranularityMenuItem(props, 'groupBy'); const state = reactive({ menuItems: computed(() => { - const dataTarget = props.widgetFieldSchema?.options?.dataTarget ?? 'labels_info'; - if (!props.dataTable) return []; - if (dataTarget === 'labels_info') return labelsMenuItem.value; - const dataInfoList = sortWidgetTableFields(Object.keys(props.dataTable[dataTarget] ?? {})) ?? []; + const dataTarget = props.widgetFieldSchema?.options?.dataTarget; + if (!widgetGenerateGetters.selectedDataTable || !dataTarget) return []; + const dataInfoList = sortWidgetTableFields(Object.keys(widgetGenerateGetters.selectedDataTable?.[dataTarget] ?? {})) ?? []; return dataInfoList.map((d) => ({ name: d, label: d, @@ -32,15 +33,6 @@ const state = reactive({ }), }); -/* Event */ -const handleUpdateSelect = (val: CategoryByValue) => { - emit('update:value', val); -}; - -/* Watcher */ -const handleIsValid = (isValid: boolean) => { - emit('update:is-valid', isValid); -};