Skip to content

Commit

Permalink
feat(widget-field-value-manager): create widget-field-value-manager (#…
Browse files Browse the repository at this point in the history
…5115)

* feat(widget-field-value-manager): create widget-field-value-manager

Signed-off-by: samuel.park <[email protected]>

* chore: create temporary type

Signed-off-by: samuel.park <[email protected]>

* fix: add widget-config constructor args & create validator registry

Signed-off-by: samuel.park <[email protected]>

* chore: fix typo

Signed-off-by: samuel.park <[email protected]>

* feat: create new dataField field component

Signed-off-by: samuel.park <[email protected]>

* chore: add annotation

Signed-off-by: samuel.park <[email protected]>

* chore: small fix

Signed-off-by: samuel.park <[email protected]>

---------

Signed-off-by: samuel.park <[email protected]>
  • Loading branch information
piggggggggy authored Dec 3, 2024
1 parent c6a98a8 commit 7605393
Show file tree
Hide file tree
Showing 16 changed files with 318 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import categoryBy from '@/common/modules/widgets/_widget-fields/category-by/Widg
import colorSchema from '@/common/modules/widgets/_widget-fields/color-schema/WidgetFieldColorSchema.vue';
import comparison from '@/common/modules/widgets/_widget-fields/comparison/WidgetFieldComparison.vue';
import customTableColumnWidth from '@/common/modules/widgets/_widget-fields/custom-table-column-width/WidgetFieldCustomTableColumnWidth.vue';
import dateAggregationOptions from '@/common/modules/widgets/_widget-fields/data-aggregation-options/WidgetFieldDateAggregationOptions.vue';
import dataFieldHeatmapColor from '@/common/modules/widgets/_widget-fields/data-field-heatmap-color/WidgetFieldDataFieldHeatmapColor.vue';
import dataField from '@/common/modules/widgets/_widget-fields/data-field/WidgetFieldDataField.vue';
import dateAggregationOptions from '@/common/modules/widgets/_widget-fields/date-aggregation-options/WidgetFieldDateAggregationOptions.vue';
import dateFormat from '@/common/modules/widgets/_widget-fields/date-format/WidgetFieldDateFormat.vue';
import dateRange from '@/common/modules/widgets/_widget-fields/date-range/WidgetFieldDateRange.vue';
import displayAnnotation from '@/common/modules/widgets/_widget-fields/display-annotation/WidgetFieldDisplayAnnotation.vue';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import type { FieldValueValidator, 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 originData: WidgetFieldValueMap;

private modifiedData: WidgetFieldValueMap;

private fieldValidators: Record<keyof WidgetFieldTypeMap, FieldValueValidator<any>>;

private validationErrors: Record<string, string> = {};

constructor(
widgetConfig: WidgetConfig,
originData: WidgetFieldValueMap,
fieldValidators: Record<keyof WidgetFieldTypeMap, FieldValueValidator<any>>,
) {
this.widgetConfig = widgetConfig;
this.originData = originData;
this.modifiedData = { ...originData };
this.fieldValidators = fieldValidators;
}

setFieldValue<Key extends keyof WidgetFieldTypeMap>(key: Key, value: WidgetFieldTypeMap[Key]['value']): boolean {
const field = this.modifiedData[key] || this.originData[key];
if (!field) {
throw new Error(`Field "${key}" does not exist.`);
}

this.modifiedData[key] = { ...field, value };

const validator = this.fieldValidators[key];
if (validator) {
const isValid = validator(this.modifiedData[key], this.widgetConfig);
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).forEach(([key, field]) => {
const validator = this.fieldValidators[key];
if (validator && !validator(field, this.widgetConfig)) {
this.validationErrors[key] = `Invalid value for field "${key}"`;
isValid = false;
}
});

return isValid;
}

getValidationErrors(): Record<string, string> {
return this.validationErrors;
}

get computedData(): WidgetFieldValueMap {
return new Proxy(this.modifiedData, {
get: (target, key) => target[key as keyof WidgetFieldValueMap],
});
}

resetToOrigin(): void {
this.modifiedData = { ...this.originData };
this.validationErrors = {};
}

setOrigin(data: WidgetFieldValueMap): void {
this.originData = { ...data };
this.modifiedData = { ...data };
this.validationErrors = {};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import type { AdvancedFormatRulesValue } from '@/common/modules/widgets/_widget-fields/advanced-format-rules/type';
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 { DateAggregationOptionsValue } from '@/common/modules/widgets/_widget-fields/date-aggregation-options/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 { LineByValue } from '@/common/modules/widgets/_widget-fields/line-by/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 { ProgressBarValue } from '@/common/modules/widgets/_widget-fields/progress-bar/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 { TableColumnWidthValue } from '@/common/modules/widgets/_widget-fields/table-column-width/type';
import type { TableDataFieldValue } from '@/common/modules/widgets/_widget-fields/table-data-field/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<T extends WidgetFieldValue> = (fieldValue: T, widgetConfig: WidgetConfig) => boolean;

export interface WidgetFieldValueMap {
[fieldKey: string]: WidgetFieldValue;
}

export interface WidgetFieldValue<T = any> {
value: T;
meta?: Record<string, any>;
}

export interface WidgetFieldTypeMap {
xAxis: WidgetFieldValue<XAxisValue>;
yAxis: WidgetFieldValue<YAxisValue>;
widgetHeight: WidgetFieldValue<WidgetHeightValue>;
total: WidgetFieldValue<TotalValue>;
tooltipNumberFormat: WidgetFieldValue<TooltipNumberFormatValue>;
textWrap: WidgetFieldValue<TextWrapValue>;
tableDataField: WidgetFieldValue<TableDataFieldValue>;
tableColumnWidth: WidgetFieldValue<TableColumnWidthValue>;
subTotal: WidgetFieldValue<SubTotalValue>;
stackBy: WidgetFieldValue<StackByValue>;
progressBar: WidgetFieldValue<ProgressBarValue>;
pieChartType: WidgetFieldValue<PieChartTypeValue>;
numberFormat: WidgetFieldValue<NumberFormatValue>;
missingValue: WidgetFieldValue<MissingValueValue>;
min: WidgetFieldValue<MinValue>;
max: WidgetFieldValue<MaxValue>;
lineBy: WidgetFieldValue<LineByValue>;
legend: WidgetFieldValue<LegendValue>;
icon: WidgetFieldValue<IconValue>;
header: WidgetFieldValue<WidgetHeaderValue>;
groupBy: WidgetFieldValue<GroupByValue>;
granularity: WidgetFieldValue<GranularityValue>;
formatRules: WidgetFieldValue<FormatRulesValue>;
displaySeriesLabel: WidgetFieldValue<DisplaySeriesLabelValue>;
displayAnnotation: WidgetFieldValue<DisplayAnnotationValue>;
dateRange: WidgetFieldValue<DateRangeValue>;
dateFormat: WidgetFieldValue<DateFormatValue>;
dataFieldHeatmapColor: WidgetFieldValue<DataFieldHeatmapColorValue>;
dataField: WidgetFieldValue<DataFieldValue>;
dateAggregationOptions: WidgetFieldValue<DateAggregationOptionsValue>;
customTableColumnWidth: WidgetFieldValue<CustomTableColumnWidthValue>;
comparison: WidgetFieldValue<ComparisonValue>;
colorSchema: WidgetFieldValue<ColorSchemaValue>;
categoryBy: WidgetFieldValue<CategoryByValue>;
advancedFormatRules: WidgetFieldValue<AdvancedFormatRulesValue>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { FieldValueValidator } from '@/common/modules/widgets/_widget-field-value-manager/type';
import type { DataFieldOptions, DataFieldValue } from '@/common/modules/widgets/_widget-fields/data-field/type';
import type { WidgetConfig } from '@/common/modules/widgets/types/widget-config-type';

interface WidgetValidatorRegistry {
[fieldKey: string]: FieldValueValidator<any>;
}

export const widgetValidatorRegistry: WidgetValidatorRegistry = {
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;
},
};

const integrateFieldsSchema = (requiredFieldsSchema: WidgetConfig['requiredFieldsSchema'], optionalFieldsSchema: WidgetConfig['optionalFieldsSchema']) => ({
...requiredFieldsSchema,
...optionalFieldsSchema,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<!-- TODO: Edit component file name -->

<script lang="ts" setup>
import {
computed, reactive,
} from 'vue';

import { PSelectDropdown, PFieldGroup } from '@cloudforet/mirinae';
import type { MenuItem } from '@cloudforet/mirinae/types/controls/context-menu/type';

import { useWidgetGenerateStore } from '@/common/modules/widgets/_store/widget-generate-store';
import type { DataFieldOptions, DataFieldValue } from '@/common/modules/widgets/_widget-fields/data-field/type';
import type {
_WidgetFieldComponentProps,
} from '@/common/modules/widgets/types/widget-field-type';

const FIELD_KEY = 'dataField';
const props = withDefaults(defineProps<_WidgetFieldComponentProps<DataFieldOptions>>(), {
widgetFieldSchema: () => ({}),
});
const widgetGenerateStore = useWidgetGenerateStore();
const widgetGenerateGetters = widgetGenerateStore.getters;


const state = reactive({
fieldValue: computed<DataFieldValue>(() => props.manager.computedData[FIELD_KEY].value),
multiselectable: computed(() => props.widgetFieldSchema?.options?.multiSelectable),
menuItems: computed<MenuItem[]>(() => {
const dataInfoList = Object.keys(widgetGenerateGetters.selectedDataTable?.data_info ?? {}) ?? [];
return dataInfoList.map((d) => ({
name: d,
label: d,
}));
}),
invalid: computed(() => props.manager.getValidationErrors()[FIELD_KEY]),

selectedItem: computed<MenuItem[]|string|undefined>(() => {
if (!state.menuItems.length) return undefined;
if (state.multiselectable) {
return convertToMenuItem((state.fieldValue.data ?? []) as string[]);
}
return (state.fieldValue.data as string) ?? state.menuItems[0]?.name;
}),
});

/* Event */
const handleChangeValue = (val: string|MenuItem[]) => {
const newValue = Array.isArray(val) ? val.map((item) => item.name as string) : val;
props.manager.setFieldValue(FIELD_KEY, { data: newValue });
};

const convertToMenuItem = (data: string[]) => data.map((d) => ({
name: d,
label: d,
}));

</script>

<template>
<div class="widget-field-data-field">
<p-field-group :label="$t('DASHBOARDS.WIDGET.OVERLAY.STEP_2.DATA_FIELD')"
required
>
<p-select-dropdown :menu="state.menuItems"
:selected="state.selectedItem"
:multi-selectable="state.multiselectable"
:show-select-marker="state.multiselectable"
:invalid="state.invalid"
appearance-type="badge"
block
@update:selected="handleChangeValue"
/>
</p-field-group>
</div>
</template>

<style lang="postcss" scoped>
/* custom design-system component - p-field-group */
:deep(.p-field-group) {
margin-bottom: 0;
}
</style>
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@

export interface DataFieldOptions {
multiSelectable?: boolean;
}

export interface DataFieldValue {
data: string|string[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import type { MenuItem } from '@cloudforet/mirinae/types/controls/context-menu/t
import { i18n } from '@/translations';
import { useProxyValue } from '@/common/composables/proxy-state';
import { DATE_AGGREGATION_OPTIONS } from '@/common/modules/widgets/_widget-fields/data-aggregation-options/constant';
import { DATE_AGGREGATION_OPTIONS } from '@/common/modules/widgets/_widget-fields/date-aggregation-options/constant';
import type {
DateAggregationOptionsOptions,
DateAggregationOptionsValue,
DateAggregationOtionsType,
} from '@/common/modules/widgets/_widget-fields/data-aggregation-options/type';
} from '@/common/modules/widgets/_widget-fields/date-aggregation-options/type';
import type {
WidgetFieldComponentProps,
WidgetFieldComponentEmit,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DATE_AGGREGATION_OPTIONS } from '@/common/modules/widgets/_widget-fields/data-aggregation-options/constant';
import type { DATE_AGGREGATION_OPTIONS } from '@/common/modules/widgets/_widget-fields/date-aggregation-options/constant';

export interface DateAggregationOptionsValue {
value: DateAggregationOtionsType;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

export interface GranularityValue {
granularity: 'YEARLY' | 'MONTHLY' | 'DAILY';
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@

export interface MaxOptions {
default?: number;
}

export interface MaxValue {
max: number;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@

export interface MinOptions {
default?: number;
}

export interface MinValue {
min: number;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@

export interface PieChartTypeOptions {
default?: string;
}

export interface PieChartTypeValue {
type: 'pie' | 'donut';
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ export interface SubTotalOptions {
default?: boolean;
toggle?: boolean;
}


export interface SubTotalValue {
toggleValue: boolean;
freeze: boolean;
}
11 changes: 10 additions & 1 deletion apps/web/src/common/modules/widgets/types/widget-field-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import type { DateRange } from '@/schema/dashboard/_types/dashboard-type';
import type { PrivateDataTableModel } from '@/schema/dashboard/private-data-table/model';
import type { PublicDataTableModel } from '@/schema/dashboard/public-data-table/model';

import type WidgetFieldValueManager from '@/common/modules/widgets/_widget-field-value-manager';
import type { AdvancedFormatRulesOptions } from '@/common/modules/widgets/_widget-fields/advanced-format-rules/type';
import type { CategoryByOptions } from '@/common/modules/widgets/_widget-fields/category-by/type';
import type { ColorSchemaOptions } from '@/common/modules/widgets/_widget-fields/color-schema/type';
import type { ComparisonOptions } from '@/common/modules/widgets/_widget-fields/comparison/type';
import type { CustomTableColumnWidthOptions } from '@/common/modules/widgets/_widget-fields/custom-table-column-width/type';
import type { DateAggregationOptionsOptions } from '@/common/modules/widgets/_widget-fields/data-aggregation-options/type';
import type { DataFieldHeatmapColorOptions } 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 { DateAggregationOptionsOptions } from '@/common/modules/widgets/_widget-fields/date-aggregation-options/type';
import type { DateFormatOptions } from '@/common/modules/widgets/_widget-fields/date-format/type';
import type { DateRangeOptions } from '@/common/modules/widgets/_widget-fields/date-range/type';
import type { DisplaySeriesLabelOptions } from '@/common/modules/widgets/_widget-fields/display-series-label/type';
Expand Down Expand Up @@ -79,6 +80,14 @@ export interface WidgetFieldComponentProps<FieldOptions, FieldValue = any> {
dateRange?: DateRange;
}

// TODO: replace this with WidgetFieldComponentProps
export interface _WidgetFieldComponentProps<FieldOptions> {
widgetFieldSchema: WidgetFieldSchema<FieldOptions>;
manager: WidgetFieldValueManager;
widgetConfig: WidgetConfig;
widgetId: string;
}

export interface WidgetFieldComponentEmit<ValueType> {
(e: 'update:value', value: ValueType): void;
(e: 'update:is-valid', value: boolean): void;
Expand Down
Loading

0 comments on commit 7605393

Please sign in to comment.