From d9994cb2546eae3d9575e2305d251bf929fa7baf Mon Sep 17 00:00:00 2001
From: Yongtae Park
Date: Tue, 25 Feb 2025 12:58:13 +0900
Subject: [PATCH 1/3] feat(unified-cost): create unified cost data source in
data table creating
Signed-off-by: samuel.park
---
.../WidgetFormDataSourcePopover.vue | 69 ++++++++++++++++---
.../WidgetFormDataTableCardAddContents.vue | 21 ++++--
.../WidgetFormDataTableCardAddForm.vue | 17 +++--
.../WidgetFormDataTableCardFilters.vue | 8 +--
.../WidgetFormUnifiedCostDataSourcePopper.vue | 62 +++++++++++++++++
.../widgets/_constants/data-table-constant.ts | 1 +
.../modules/widgets/types/widget-model.ts | 5 ++
7 files changed, 157 insertions(+), 26 deletions(-)
create mode 100644 apps/web/src/common/modules/widgets/_components/WidgetFormUnifiedCostDataSourcePopper.vue
diff --git a/apps/web/src/common/modules/widgets/_components/WidgetFormDataSourcePopover.vue b/apps/web/src/common/modules/widgets/_components/WidgetFormDataSourcePopover.vue
index a8e776b2dc..0ce69f54b1 100644
--- a/apps/web/src/common/modules/widgets/_components/WidgetFormDataSourcePopover.vue
+++ b/apps/web/src/common/modules/widgets/_components/WidgetFormDataSourcePopover.vue
@@ -30,6 +30,8 @@ import ErrorHandler from '@/common/composables/error/errorHandler';
import WidgetFormAssetSecurityDataSourcePopper
from '@/common/modules/widgets/_components/WidgetFormAssetSecurityDataSourcePopper.vue';
import WidgetFormCostDataSourcePopper from '@/common/modules/widgets/_components/WidgetFormCostDataSourcePopper.vue';
+import WidgetFormUnifiedCostDataSourcePopper
+ from '@/common/modules/widgets/_components/WidgetFormUnifiedCostDataSourcePopper.vue';
import { useWidgetFormQuery } from '@/common/modules/widgets/_composables/use-widget-form-query';
import {
DATA_SOURCE_DOMAIN,
@@ -104,6 +106,9 @@ const state = reactive({
if (state.selectedDataSourceDomain === DATA_SOURCE_DOMAIN.COST) {
return !state.selectedCostDataSourceId || !state.selectedCostDataType;
}
+ if (state.selectedDataSourceDomain === DATA_SOURCE_DOMAIN.UNIFIED_COST) {
+ return !state.selectedUnifiedCostDataType;
+ }
if ([DATA_SOURCE_DOMAIN.ASSET, DATA_SOURCE_DOMAIN.SECURITY].includes(state.selectedDataSourceDomain)) {
return !state.selectedMetricId;
}
@@ -112,6 +117,7 @@ const state = reactive({
// cost
selectedCostDataSourceId: undefined as undefined|string,
selectedCostDataType: undefined as undefined|string,
+ selectedUnifiedCostDataType: undefined as undefined|string,
// asset & security
selectedMetricId: undefined as undefined|string,
selectedNamespace: computed(() => {
@@ -236,6 +242,7 @@ const { mutateAsync: addDataTable, isPending: dataTableAddLoading } = useMutatio
const resetSelectedDataSource = () => {
state.selectedCostDataSourceId = undefined;
state.selectedCostDataType = undefined;
+ state.selectedUnifiedCostDataType = undefined;
state.selectedMetricId = undefined;
};
@@ -287,9 +294,11 @@ const handleConfirmDataSource = async () => {
}
if (state.selectedPopperCondition === DATA_TABLE_TYPE.ADDED) {
- const dataTableBaseName = state.selectedDataSourceDomain === DATA_SOURCE_DOMAIN.COST
- ? `${storeState.costDataSources[state.selectedCostDataSourceId].name} - ${state.selectedCostDataTypeLabel}`
- : `${state.selectedNamespace.name} - ${storeState.metrics[state.selectedMetricId]?.label}`;
+ let dataTableBaseName: string;
+ if (state.selectedDataSourceDomain === DATA_SOURCE_DOMAIN.COST) dataTableBaseName = `${storeState.costDataSources[state.selectedCostDataSourceId].name} - ${state.selectedCostDataTypeLabel}`;
+ else if (state.selectedDataSourceDomain === DATA_SOURCE_DOMAIN.UNIFIED_COST) dataTableBaseName = 'Unified Cost';
+ else dataTableBaseName = `${state.selectedNamespace.name} - ${storeState.metrics[state.selectedMetricId]?.label}`;
+
const addParameters = {
widget_id: widgetGenerateState.widgetId as string,
source_type: state.selectedDataSourceDomain,
@@ -313,7 +322,13 @@ const handleConfirmDataSource = async () => {
data_key: state.selectedCostDataType,
},
};
- const assetOptions = {
+ const unifiedCostOptions: DataTableAddOptions = {
+ data_name: dataTableBaseName,
+ UNIFIED_COST: {
+ data_key: state.selectedUnifiedCostDataType,
+ },
+ };
+ const assetOptions: DataTableAddOptions = {
data_name: storeState.metrics[state.selectedMetricId]?.label,
data_unit: storeState.metrics[state.selectedMetricId]?.data.unit,
ASSET: {
@@ -325,13 +340,19 @@ const handleConfirmDataSource = async () => {
widgetGenerateStore.setDataTableCreateLoading(true);
state.showPopover = false;
- await addDataTable({
+ const mergedParams = {
...addParameters,
vars: dashboard.value?.vars || {},
- options: {
- ...state.selectedDataSourceDomain === DATA_SOURCE_DOMAIN.COST ? costOptions : assetOptions,
- },
- });
+ };
+ if (state.selectedDataSourceDomain === DATA_SOURCE_DOMAIN.COST) {
+ mergedParams.options = costOptions;
+ } else if (state.selectedDataSourceDomain === DATA_SOURCE_DOMAIN.UNIFIED_COST) {
+ mergedParams.options = unifiedCostOptions;
+ } else {
+ mergedParams.options = assetOptions;
+ }
+
+ await addDataTable(mergedParams);
emit('scroll');
}
widgetGenerateStore.setDataTableCreateLoading(false);
@@ -413,6 +434,29 @@ watch(() => state.showPopover, (val) => {
+
+
+
+
+
+ {{ i18n.t('DASHBOARDS.WIDGET.OVERLAY.STEP_1.UNIFIED_COST') }}
+
+
+
+
{{ i18n.t('DASHBOARDS.WIDGET.OVERLAY.STEP_1.INVENTORY') }}
@@ -445,6 +489,9 @@ watch(() => state.showPopover, (val) => {
:selected-cost-data-source-id.sync="state.selectedCostDataSourceId"
:selected-cost-data-type.sync="state.selectedCostDataType"
/>
+
state.showPopover, (val) => {
.data-source-popover-content {
display: flex;
flex-direction: column;
- min-width: 43.5rem;
+ min-width: 44rem;
height: 26rem;
.top-part {
display: flex;
@@ -657,7 +704,7 @@ watch(() => state.showPopover, (val) => {
@apply border-r border-gray-200;
display: flex;
flex-direction: column;
- min-width: 11.5rem;
+ min-width: 12rem;
height: 100%;
padding: 1rem 0.75rem;
diff --git a/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardAddContents.vue b/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardAddContents.vue
index b6b67531db..149e8d3604 100644
--- a/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardAddContents.vue
+++ b/apps/web/src/common/modules/widgets/_components/WidgetFormDataTableCardAddContents.vue
@@ -143,7 +143,7 @@ const state = reactive({
}),
filterFormKey: getRandomId(),
optionsChanged: computed(() => {
- const sourceKeyChanged = state.selectedSourceEndItem !== originDataState.sourceKey;
+ const sourceKeyChanged = state.sourceType !== DATA_SOURCE_DOMAIN.UNIFIED_COST && state.selectedSourceEndItem !== originDataState.sourceKey;
const groupByChanged = !isEqual(state.selectedGroupByItems, originDataState.groupBy);
const groupByTagsChanged = !isEqual(state.selectedGroupByTagsMap, originDataState.groupByTagsMap);
const filterChanged = !isEqual(state.filter, originDataState.filter);
@@ -175,7 +175,11 @@ const validationState = reactive({
});
const originDataState = reactive({
- sourceKey: computed(() => (state.sourceType === DATA_SOURCE_DOMAIN.COST ? props.item.options[DATA_SOURCE_DOMAIN.COST]?.data_key : props.item.options[DATA_SOURCE_DOMAIN.ASSET]?.metric_id)),
+ sourceKey: computed(() => {
+ if (state.sourceType === DATA_SOURCE_DOMAIN.COST) return props.item.options[DATA_SOURCE_DOMAIN.COST]?.data_key;
+ if (state.sourceType === DATA_SOURCE_DOMAIN.UNIFIED_COST) return 'cost';
+ return props.item.options[DATA_SOURCE_DOMAIN.ASSET]?.metric_id;
+ }),
groupBy: computed(() => ((props.item.options as DataTableAddOptions).group_by ?? []).map((group) => ({
name: group.key,
label: group.name,
@@ -302,9 +306,10 @@ const updateDataTable = async (): Promise => {
return undefined;
}
- const domainOptions = state.sourceType === DATA_SOURCE_DOMAIN.COST
- ? { data_source_id: state.dataSourceId, data_key: state.selectedSourceEndItem }
- : { metric_id: state.selectedSourceEndItem };
+ let domainOptions;
+ if (state.sourceType === DATA_SOURCE_DOMAIN.COST) domainOptions = { data_source_id: state.dataSourceId, data_key: state.selectedSourceEndItem };
+ if (state.sourceType === DATA_SOURCE_DOMAIN.UNIFIED_COST) domainOptions = { data_key: 'cost' };
+ if (state.sourceType === DATA_SOURCE_DOMAIN.ASSET) domainOptions = { metric_id: state.selectedSourceEndItem };
const costGroupBy = state.selectedGroupByItems.map((group) => ({
key: group.name,
@@ -313,7 +318,8 @@ const updateDataTable = async (): Promise => {
}));
const metricLabelsInfo = storeState.metrics[state.metricId ?? '']?.data?.labels_info;
const assetGroupBy = (metricLabelsInfo ?? []).filter((label) => state.selectedGroupByItems.map((group) => group.name).includes(label.key));
- const groupBy = state.sourceType === DATA_SOURCE_DOMAIN.COST ? costGroupBy : assetGroupBy;
+
+ const groupBy = (state.sourceType === DATA_SOURCE_DOMAIN.COST || state.sourceType === DATA_SOURCE_DOMAIN.UNIFIED_COST) ? costGroupBy : assetGroupBy;
GROUP_BY_INFO_ITEMS_FOR_TAGS.forEach((tag) => {
const groupByTags = groupBy.find((group) => group.key === tag.key);
@@ -532,7 +538,8 @@ defineExpose({
:selected="props.selected"
:data-table-name.sync="dataTableNameState.dataTableName"
/>
- ;
@@ -87,7 +88,7 @@ const storeState = reactive({
});
const { allItems: costDataSourceMenuItems } = useCostDataSourceFilterMenuItems({
isAdminMode: computed(() => storeState.isAdminMode),
- costDataSource: computed(() => storeState.costDataSources[props.sourceId]),
+ costDataSource: computed(() => storeState.costDataSources[props.sourceId ?? '']),
});
const state = reactive({
@@ -149,12 +150,20 @@ const groupByState = reactive({
if (props.sourceType === DATA_SOURCE_DOMAIN.COST) {
return costDataSourceMenuItems.value.filter((d) => d.name !== 'project_group_id');
}
+ if (props.sourceType === DATA_SOURCE_DOMAIN.UNIFIED_COST) {
+ const groupByItemValueList = Object.values(GROUP_BY_ITEM_MAP);
+ if (!storeState.isAdminMode) return groupByItemValueList.filter((d) => d.name !== 'workspace_id').map((d) => ({ name: d.name, label: d.label }));
+ return groupByItemValueList.map((d) => ({ name: d.name, label: d.label }));
+ }
return [...assetFilterState.metricItems];
}),
- filterItems: computed(() => {
+ filterItems: computed