@adminforth/dashboard 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -54
- package/custom/api/dashboardApi.ts +9 -0
- package/custom/model/dashboard.types.ts +353 -2
- package/custom/queries/useWidgetData.ts +8 -4
- package/custom/runtime/DashboardRuntime.vue +2 -1
- package/custom/runtime/WidgetRenderer.vue +2 -1
- package/custom/runtime/WidgetShell.vue +8 -4
- package/custom/skills/adminforth-dashboard/SKILL.md +4 -4
- package/custom/widgets/chart/ChartWidget.vue +45 -12
- package/custom/widgets/chart/chart.types.ts +83 -0
- package/custom/widgets/chart/chart.utils.ts +2 -2
- package/custom/widgets/gauge-card/GaugeCardWidget.vue +63 -12
- package/custom/widgets/kpi-card/KpiCardWidget.vue +6 -8
- package/custom/widgets/pivot-table/PivotTableWidget.vue +32 -12
- package/custom/widgets/table/TableWidget.vue +155 -30
- package/dist/custom/api/dashboardApi.d.ts +7 -1
- package/dist/custom/api/dashboardApi.js +4 -6
- package/dist/custom/api/dashboardApi.ts +9 -0
- package/dist/custom/model/dashboard.types.d.ts +70 -1
- package/dist/custom/model/dashboard.types.js +173 -1
- package/dist/custom/model/dashboard.types.ts +353 -2
- package/dist/custom/queries/useDashboardConfig.d.ts +42 -2
- package/dist/custom/queries/useWidgetData.d.ts +44 -3
- package/dist/custom/queries/useWidgetData.js +3 -3
- package/dist/custom/queries/useWidgetData.ts +8 -4
- package/dist/custom/runtime/DashboardRuntime.vue +2 -1
- package/dist/custom/runtime/WidgetRenderer.vue +2 -1
- package/dist/custom/runtime/WidgetShell.vue +8 -4
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +4 -4
- package/dist/custom/widgets/chart/ChartWidget.vue +45 -12
- package/dist/custom/widgets/chart/chart.types.d.ts +15 -0
- package/dist/custom/widgets/chart/chart.types.js +46 -0
- package/dist/custom/widgets/chart/chart.types.ts +83 -0
- package/dist/custom/widgets/chart/chart.utils.d.ts +1 -1
- package/dist/custom/widgets/chart/chart.utils.js +2 -2
- package/dist/custom/widgets/chart/chart.utils.ts +2 -2
- package/dist/custom/widgets/gauge-card/GaugeCardWidget.vue +63 -12
- package/dist/custom/widgets/kpi-card/KpiCardWidget.vue +6 -8
- package/dist/custom/widgets/pivot-table/PivotTableWidget.vue +32 -12
- package/dist/custom/widgets/table/TableWidget.vue +155 -30
- package/dist/endpoint/widgets.d.ts +6 -1
- package/dist/endpoint/widgets.js +41 -6
- package/dist/schema/api.d.ts +874 -444
- package/dist/schema/api.js +11 -2
- package/dist/schema/widget.d.ts +538 -132
- package/dist/schema/widget.js +138 -14
- package/dist/services/widgetConfigValidator.js +26 -40
- package/dist/services/widgetDataService.d.ts +7 -14
- package/dist/services/widgetDataService.js +115 -11
- package/endpoint/widgets.ts +56 -6
- package/package.json +1 -1
- package/schema/api.ts +11 -1
- package/schema/widget.ts +145 -15
- package/services/widgetConfigValidator.ts +36 -44
- package/services/widgetDataService.ts +175 -28
|
@@ -24,6 +24,13 @@ export type DashboardWidgetDataResponse = {
|
|
|
24
24
|
data: unknown
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export type DashboardWidgetDataRequest = {
|
|
28
|
+
pagination?: {
|
|
29
|
+
page: number
|
|
30
|
+
pageSize: number
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
27
34
|
export class DashboardApiError extends Error {
|
|
28
35
|
validationErrors: DashboardWidgetConfigValidationError[]
|
|
29
36
|
|
|
@@ -204,10 +211,12 @@ export const dashboardApi = {
|
|
|
204
211
|
async getDashboardWidgetData(
|
|
205
212
|
slug: string,
|
|
206
213
|
widgetId: string,
|
|
214
|
+
request: DashboardWidgetDataRequest = {},
|
|
207
215
|
): Promise<DashboardWidgetDataResponse> {
|
|
208
216
|
return callDashboardWidgetDataApi('/adminapi/v1/dashboard/get_dashboard_widget_data', {
|
|
209
217
|
slug,
|
|
210
218
|
widgetId,
|
|
219
|
+
...request,
|
|
211
220
|
})
|
|
212
221
|
},
|
|
213
222
|
}
|
|
@@ -1,4 +1,33 @@
|
|
|
1
1
|
import type { ChartWidgetConfig } from '../widgets/chart/chart.types.js';
|
|
2
|
+
export type AggregationOperation = 'sum' | 'count' | 'avg' | 'min' | 'max' | 'median';
|
|
3
|
+
export type AggregationRule = {
|
|
4
|
+
operation: AggregationOperation;
|
|
5
|
+
field?: string;
|
|
6
|
+
};
|
|
7
|
+
export type GroupByRule = {
|
|
8
|
+
type: 'field';
|
|
9
|
+
field: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: 'date_trunc';
|
|
12
|
+
field: string;
|
|
13
|
+
truncation: 'day' | 'week' | 'month' | 'year';
|
|
14
|
+
timezone?: string;
|
|
15
|
+
};
|
|
16
|
+
export type ResourceWidgetDataSource = {
|
|
17
|
+
type: 'resource';
|
|
18
|
+
resourceId: string;
|
|
19
|
+
columns?: string[];
|
|
20
|
+
sort?: unknown;
|
|
21
|
+
filters?: unknown;
|
|
22
|
+
};
|
|
23
|
+
export type AggregateWidgetDataSource = {
|
|
24
|
+
type: 'aggregate';
|
|
25
|
+
resourceId: string;
|
|
26
|
+
aggregations: Record<string, AggregationRule>;
|
|
27
|
+
groupBy?: GroupByRule;
|
|
28
|
+
filters?: unknown;
|
|
29
|
+
};
|
|
30
|
+
export type WidgetDataSource = ResourceWidgetDataSource | AggregateWidgetDataSource;
|
|
2
31
|
export type DashboardConfig = {
|
|
3
32
|
version: number;
|
|
4
33
|
groups: DashboardGroupConfig[];
|
|
@@ -31,15 +60,55 @@ export type DashboardWidgetConfig = {
|
|
|
31
60
|
maxWidth?: number | null;
|
|
32
61
|
order: number;
|
|
33
62
|
target: DashboardWidgetTarget;
|
|
63
|
+
dataSource?: WidgetDataSource;
|
|
34
64
|
chart?: ChartWidgetConfig;
|
|
35
65
|
table?: unknown;
|
|
36
66
|
kpi_card?: unknown;
|
|
37
67
|
pivot_table?: unknown;
|
|
38
68
|
gauge_card?: unknown;
|
|
39
|
-
query?: unknown;
|
|
40
69
|
};
|
|
41
70
|
export type DashboardWidgetTableData = {
|
|
71
|
+
kind?: 'table';
|
|
72
|
+
columns: string[];
|
|
73
|
+
rows: Record<string, unknown>[];
|
|
74
|
+
pagination?: {
|
|
75
|
+
page: number;
|
|
76
|
+
pageSize: number;
|
|
77
|
+
total: number;
|
|
78
|
+
totalPages: number;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
export type DashboardWidgetAggregateData = {
|
|
82
|
+
kind: 'aggregate';
|
|
42
83
|
columns: string[];
|
|
43
84
|
rows: Record<string, unknown>[];
|
|
85
|
+
values?: Record<string, unknown>;
|
|
86
|
+
};
|
|
87
|
+
export type DashboardWidgetData = DashboardWidgetTableData | DashboardWidgetAggregateData;
|
|
88
|
+
export type NormalizedKpiCardWidgetConfig = {
|
|
89
|
+
valueField?: string;
|
|
90
|
+
labelField?: string;
|
|
91
|
+
prefix?: string;
|
|
92
|
+
suffix?: string;
|
|
93
|
+
};
|
|
94
|
+
export type NormalizedGaugeCardWidgetConfig = {
|
|
95
|
+
valueField?: string;
|
|
96
|
+
min?: number | string;
|
|
97
|
+
max?: number | string;
|
|
98
|
+
minField?: string;
|
|
99
|
+
maxField?: string;
|
|
100
|
+
suffix?: string;
|
|
101
|
+
color?: string;
|
|
102
|
+
};
|
|
103
|
+
export type NormalizedPivotTableWidgetConfig = {
|
|
104
|
+
rowField?: string;
|
|
105
|
+
columnField?: string;
|
|
106
|
+
valueField?: string;
|
|
107
|
+
aggregation?: 'count' | 'sum';
|
|
44
108
|
};
|
|
45
109
|
export declare function normalizeDashboardConfig(config: unknown): DashboardConfig;
|
|
110
|
+
export declare function normalizeDashboardWidgetConfig(config: unknown): unknown;
|
|
111
|
+
export declare function serializeDashboardWidgetConfigForEditor(widget: DashboardWidgetConfig): Record<string, unknown>;
|
|
112
|
+
export declare function normalizeKpiCardWidgetConfig(value: unknown): NormalizedKpiCardWidgetConfig | undefined;
|
|
113
|
+
export declare function normalizeGaugeCardWidgetConfig(value: unknown): NormalizedGaugeCardWidgetConfig | undefined;
|
|
114
|
+
export declare function normalizePivotTableWidgetConfig(value: unknown): NormalizedPivotTableWidgetConfig | undefined;
|
|
@@ -1,14 +1,186 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.normalizeDashboardConfig = normalizeDashboardConfig;
|
|
4
|
+
exports.normalizeDashboardWidgetConfig = normalizeDashboardWidgetConfig;
|
|
5
|
+
exports.serializeDashboardWidgetConfigForEditor = serializeDashboardWidgetConfigForEditor;
|
|
6
|
+
exports.normalizeKpiCardWidgetConfig = normalizeKpiCardWidgetConfig;
|
|
7
|
+
exports.normalizeGaugeCardWidgetConfig = normalizeGaugeCardWidgetConfig;
|
|
8
|
+
exports.normalizePivotTableWidgetConfig = normalizePivotTableWidgetConfig;
|
|
4
9
|
function normalizeDashboardConfig(config) {
|
|
5
10
|
const value = isRecord(config) ? config : {};
|
|
6
11
|
return {
|
|
7
12
|
version: typeof value.version === 'number' ? value.version : 1,
|
|
8
13
|
groups: Array.isArray(value.groups) ? value.groups : [],
|
|
9
|
-
widgets: Array.isArray(value.widgets)
|
|
14
|
+
widgets: Array.isArray(value.widgets)
|
|
15
|
+
? value.widgets.map((widget) => normalizeDashboardWidgetConfig(widget))
|
|
16
|
+
: [],
|
|
10
17
|
};
|
|
11
18
|
}
|
|
19
|
+
function normalizeDashboardWidgetConfig(config) {
|
|
20
|
+
if (!isRecord(config)) {
|
|
21
|
+
return config;
|
|
22
|
+
}
|
|
23
|
+
const normalized = Object.assign({}, config);
|
|
24
|
+
normalizeWidgetLayoutConfig(normalized);
|
|
25
|
+
if (normalized.table !== undefined) {
|
|
26
|
+
normalized.table = normalizeTableConfig(normalized.table);
|
|
27
|
+
}
|
|
28
|
+
if (normalized.data_source !== undefined) {
|
|
29
|
+
normalized.dataSource = normalizeWidgetDataSource(normalized.data_source);
|
|
30
|
+
}
|
|
31
|
+
const target = normalizeDashboardWidgetTarget(normalized.target);
|
|
32
|
+
if (target !== undefined) {
|
|
33
|
+
normalized.target = target;
|
|
34
|
+
}
|
|
35
|
+
return normalized;
|
|
36
|
+
}
|
|
37
|
+
function serializeDashboardWidgetConfigForEditor(widget) {
|
|
38
|
+
const serialized = Object.assign({}, widget);
|
|
39
|
+
if (Object.prototype.hasOwnProperty.call(serialized, 'minWidth')) {
|
|
40
|
+
serialized.min_width = widget.minWidth;
|
|
41
|
+
delete serialized.minWidth;
|
|
42
|
+
}
|
|
43
|
+
if (Object.prototype.hasOwnProperty.call(serialized, 'maxWidth')) {
|
|
44
|
+
serialized.max_width = widget.maxWidth;
|
|
45
|
+
delete serialized.maxWidth;
|
|
46
|
+
}
|
|
47
|
+
if (widget.table !== undefined) {
|
|
48
|
+
serialized.table = serializeTableConfigForEditor(widget.table);
|
|
49
|
+
}
|
|
50
|
+
if (widget.dataSource !== undefined) {
|
|
51
|
+
serialized.data_source = serializeWidgetDataSourceForEditor(widget.dataSource);
|
|
52
|
+
delete serialized.dataSource;
|
|
53
|
+
}
|
|
54
|
+
return serialized;
|
|
55
|
+
}
|
|
56
|
+
function normalizeKpiCardWidgetConfig(value) {
|
|
57
|
+
const config = asWidgetConfigRecord(value);
|
|
58
|
+
if (!config) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
const valueField = getStringField(config, 'value_field');
|
|
62
|
+
const labelField = getStringField(config, 'label_field');
|
|
63
|
+
const prefix = getStringField(config, 'prefix');
|
|
64
|
+
const suffix = getStringField(config, 'suffix');
|
|
65
|
+
return Object.assign(Object.assign(Object.assign(Object.assign({}, (valueField !== undefined ? { valueField } : {})), (labelField !== undefined ? { labelField } : {})), (prefix !== undefined ? { prefix } : {})), (suffix !== undefined ? { suffix } : {}));
|
|
66
|
+
}
|
|
67
|
+
function normalizeGaugeCardWidgetConfig(value) {
|
|
68
|
+
const config = asWidgetConfigRecord(value);
|
|
69
|
+
if (!config) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
const valueField = getStringField(config, 'value_field');
|
|
73
|
+
const minField = getStringField(config, 'min_field');
|
|
74
|
+
const maxField = getStringField(config, 'max_field');
|
|
75
|
+
const suffix = getStringField(config, 'suffix');
|
|
76
|
+
const color = getStringField(config, 'color');
|
|
77
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (valueField !== undefined ? { valueField } : {})), (config.min !== undefined ? { min: config.min } : {})), (config.max !== undefined ? { max: config.max } : {})), (minField !== undefined ? { minField } : {})), (maxField !== undefined ? { maxField } : {})), (suffix !== undefined ? { suffix } : {})), (color !== undefined ? { color } : {}));
|
|
78
|
+
}
|
|
79
|
+
function normalizePivotTableWidgetConfig(value) {
|
|
80
|
+
const config = asWidgetConfigRecord(value);
|
|
81
|
+
if (!config) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
const rowField = getStringField(config, 'row_field');
|
|
85
|
+
const columnField = getStringField(config, 'column_field');
|
|
86
|
+
const valueField = getStringField(config, 'value_field');
|
|
87
|
+
const aggregation = config.aggregation === 'count' || config.aggregation === 'sum'
|
|
88
|
+
? config.aggregation
|
|
89
|
+
: undefined;
|
|
90
|
+
return Object.assign(Object.assign(Object.assign(Object.assign({}, (rowField !== undefined ? { rowField } : {})), (columnField !== undefined ? { columnField } : {})), (valueField !== undefined ? { valueField } : {})), (aggregation !== undefined ? { aggregation } : {}));
|
|
91
|
+
}
|
|
92
|
+
function normalizeDashboardWidgetTarget(value) {
|
|
93
|
+
switch (value) {
|
|
94
|
+
case 'empty':
|
|
95
|
+
case 'table':
|
|
96
|
+
case 'chart':
|
|
97
|
+
case 'kpi_card':
|
|
98
|
+
case 'pivot_table':
|
|
99
|
+
case 'gauge_card':
|
|
100
|
+
return value;
|
|
101
|
+
default:
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function normalizeWidgetLayoutConfig(value) {
|
|
106
|
+
if (value.min_width !== undefined) {
|
|
107
|
+
value.minWidth = value.min_width;
|
|
108
|
+
}
|
|
109
|
+
if (value.max_width !== undefined) {
|
|
110
|
+
value.maxWidth = value.max_width;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function normalizeTableConfig(value) {
|
|
114
|
+
if (!isRecord(value)) {
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
const normalized = Object.assign({}, value);
|
|
118
|
+
if (normalized.page_size !== undefined) {
|
|
119
|
+
normalized.pageSize = normalized.page_size;
|
|
120
|
+
}
|
|
121
|
+
return normalized;
|
|
122
|
+
}
|
|
123
|
+
function normalizeWidgetDataSource(value) {
|
|
124
|
+
if (!isRecord(value) || typeof value.type !== 'string') {
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
const resourceId = typeof value.resource_id === 'string'
|
|
128
|
+
? value.resource_id
|
|
129
|
+
: undefined;
|
|
130
|
+
if (value.type === 'resource') {
|
|
131
|
+
return Object.assign(Object.assign(Object.assign(Object.assign({ type: 'resource' }, (resourceId !== undefined ? { resourceId } : {})), (value.columns !== undefined ? { columns: value.columns } : {})), (value.filters !== undefined ? { filters: value.filters } : {})), (value.sort !== undefined ? { sort: value.sort } : {}));
|
|
132
|
+
}
|
|
133
|
+
if (value.type === 'aggregate') {
|
|
134
|
+
const groupBy = normalizeGroupByRule(value.group_by);
|
|
135
|
+
return Object.assign(Object.assign(Object.assign(Object.assign({ type: 'aggregate' }, (resourceId !== undefined ? { resourceId } : {})), (value.aggregations !== undefined ? { aggregations: value.aggregations } : {})), (groupBy !== undefined ? { groupBy } : {})), (value.filters !== undefined ? { filters: value.filters } : {}));
|
|
136
|
+
}
|
|
137
|
+
return value;
|
|
138
|
+
}
|
|
139
|
+
function normalizeGroupByRule(value) {
|
|
140
|
+
if (!isRecord(value) || typeof value.type !== 'string') {
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
if (value.type === 'field') {
|
|
144
|
+
return Object.assign({ type: 'field' }, (value.field !== undefined ? { field: value.field } : {}));
|
|
145
|
+
}
|
|
146
|
+
if (value.type === 'date_trunc') {
|
|
147
|
+
return Object.assign(Object.assign(Object.assign({ type: 'date_trunc' }, (value.field !== undefined ? { field: value.field } : {})), (value.truncation !== undefined ? { truncation: value.truncation } : {})), (value.timezone !== undefined ? { timezone: value.timezone } : {}));
|
|
148
|
+
}
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
function serializeTableConfigForEditor(value) {
|
|
152
|
+
if (!isRecord(value)) {
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
const serialized = Object.assign({}, value);
|
|
156
|
+
if (Object.prototype.hasOwnProperty.call(serialized, 'pageSize')) {
|
|
157
|
+
serialized.page_size = serialized.pageSize;
|
|
158
|
+
delete serialized.pageSize;
|
|
159
|
+
}
|
|
160
|
+
return serialized;
|
|
161
|
+
}
|
|
162
|
+
function serializeWidgetDataSourceForEditor(value) {
|
|
163
|
+
if (value.type === 'resource') {
|
|
164
|
+
return Object.assign(Object.assign(Object.assign({ type: 'resource', resource_id: value.resourceId }, (value.columns !== undefined ? { columns: value.columns } : {})), (value.filters !== undefined ? { filters: value.filters } : {})), (value.sort !== undefined ? { sort: value.sort } : {}));
|
|
165
|
+
}
|
|
166
|
+
return Object.assign(Object.assign({ type: 'aggregate', resource_id: value.resourceId, aggregations: value.aggregations }, (value.groupBy !== undefined ? { group_by: serializeGroupByRuleForEditor(value.groupBy) } : {})), (value.filters !== undefined ? { filters: value.filters } : {}));
|
|
167
|
+
}
|
|
168
|
+
function serializeGroupByRuleForEditor(value) {
|
|
169
|
+
if (value.type === 'field') {
|
|
170
|
+
return {
|
|
171
|
+
type: 'field',
|
|
172
|
+
field: value.field,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return Object.assign({ type: 'date_trunc', field: value.field, truncation: value.truncation }, (value.timezone !== undefined ? { timezone: value.timezone } : {}));
|
|
176
|
+
}
|
|
177
|
+
function asWidgetConfigRecord(value) {
|
|
178
|
+
return isRecord(value) ? value : undefined;
|
|
179
|
+
}
|
|
180
|
+
function getStringField(record, key) {
|
|
181
|
+
const value = record[key];
|
|
182
|
+
return typeof value === 'string' ? value : undefined;
|
|
183
|
+
}
|
|
12
184
|
function isRecord(value) {
|
|
13
185
|
return typeof value === 'object' && value !== null;
|
|
14
186
|
}
|
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
import type { ChartWidgetConfig } from '../widgets/chart/chart.types.js'
|
|
2
2
|
|
|
3
|
+
export type AggregationOperation = 'sum' | 'count' | 'avg' | 'min' | 'max' | 'median'
|
|
4
|
+
|
|
5
|
+
export type AggregationRule = {
|
|
6
|
+
operation: AggregationOperation
|
|
7
|
+
field?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type GroupByRule =
|
|
11
|
+
| {
|
|
12
|
+
type: 'field'
|
|
13
|
+
field: string
|
|
14
|
+
}
|
|
15
|
+
| {
|
|
16
|
+
type: 'date_trunc'
|
|
17
|
+
field: string
|
|
18
|
+
truncation: 'day' | 'week' | 'month' | 'year'
|
|
19
|
+
timezone?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ResourceWidgetDataSource = {
|
|
23
|
+
type: 'resource'
|
|
24
|
+
resourceId: string
|
|
25
|
+
columns?: string[]
|
|
26
|
+
sort?: unknown
|
|
27
|
+
filters?: unknown
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type AggregateWidgetDataSource = {
|
|
31
|
+
type: 'aggregate'
|
|
32
|
+
resourceId: string
|
|
33
|
+
aggregations: Record<string, AggregationRule>
|
|
34
|
+
groupBy?: GroupByRule
|
|
35
|
+
filters?: unknown
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type WidgetDataSource = ResourceWidgetDataSource | AggregateWidgetDataSource
|
|
39
|
+
|
|
3
40
|
export type DashboardConfig = {
|
|
4
41
|
version: number
|
|
5
42
|
groups: DashboardGroupConfig[]
|
|
@@ -45,17 +82,57 @@ export type DashboardWidgetConfig = {
|
|
|
45
82
|
maxWidth?: number | null
|
|
46
83
|
order: number
|
|
47
84
|
target: DashboardWidgetTarget
|
|
85
|
+
dataSource?: WidgetDataSource
|
|
48
86
|
chart?: ChartWidgetConfig
|
|
49
87
|
table?: unknown
|
|
50
88
|
kpi_card?: unknown
|
|
51
89
|
pivot_table?: unknown
|
|
52
90
|
gauge_card?: unknown
|
|
53
|
-
query?: unknown
|
|
54
91
|
}
|
|
55
92
|
|
|
56
93
|
export type DashboardWidgetTableData = {
|
|
94
|
+
kind?: 'table'
|
|
95
|
+
columns: string[]
|
|
96
|
+
rows: Record<string, unknown>[]
|
|
97
|
+
pagination?: {
|
|
98
|
+
page: number
|
|
99
|
+
pageSize: number
|
|
100
|
+
total: number
|
|
101
|
+
totalPages: number
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type DashboardWidgetAggregateData = {
|
|
106
|
+
kind: 'aggregate'
|
|
57
107
|
columns: string[]
|
|
58
108
|
rows: Record<string, unknown>[]
|
|
109
|
+
values?: Record<string, unknown>
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export type DashboardWidgetData = DashboardWidgetTableData | DashboardWidgetAggregateData
|
|
113
|
+
|
|
114
|
+
export type NormalizedKpiCardWidgetConfig = {
|
|
115
|
+
valueField?: string
|
|
116
|
+
labelField?: string
|
|
117
|
+
prefix?: string
|
|
118
|
+
suffix?: string
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export type NormalizedGaugeCardWidgetConfig = {
|
|
122
|
+
valueField?: string
|
|
123
|
+
min?: number | string
|
|
124
|
+
max?: number | string
|
|
125
|
+
minField?: string
|
|
126
|
+
maxField?: string
|
|
127
|
+
suffix?: string
|
|
128
|
+
color?: string
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export type NormalizedPivotTableWidgetConfig = {
|
|
132
|
+
rowField?: string
|
|
133
|
+
columnField?: string
|
|
134
|
+
valueField?: string
|
|
135
|
+
aggregation?: 'count' | 'sum'
|
|
59
136
|
}
|
|
60
137
|
|
|
61
138
|
export function normalizeDashboardConfig(config: unknown): DashboardConfig {
|
|
@@ -64,8 +141,282 @@ export function normalizeDashboardConfig(config: unknown): DashboardConfig {
|
|
|
64
141
|
return {
|
|
65
142
|
version: typeof value.version === 'number' ? value.version : 1,
|
|
66
143
|
groups: Array.isArray(value.groups) ? (value.groups as DashboardGroupConfig[]) : [],
|
|
67
|
-
widgets: Array.isArray(value.widgets)
|
|
144
|
+
widgets: Array.isArray(value.widgets)
|
|
145
|
+
? value.widgets.map((widget) => normalizeDashboardWidgetConfig(widget) as DashboardWidgetConfig)
|
|
146
|
+
: [],
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function normalizeDashboardWidgetConfig(config: unknown) {
|
|
151
|
+
if (!isRecord(config)) {
|
|
152
|
+
return config
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const normalized: Record<string, unknown> = { ...config }
|
|
156
|
+
normalizeWidgetLayoutConfig(normalized)
|
|
157
|
+
|
|
158
|
+
if (normalized.table !== undefined) {
|
|
159
|
+
normalized.table = normalizeTableConfig(normalized.table)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (normalized.data_source !== undefined) {
|
|
163
|
+
normalized.dataSource = normalizeWidgetDataSource(normalized.data_source)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const target = normalizeDashboardWidgetTarget(normalized.target)
|
|
167
|
+
|
|
168
|
+
if (target !== undefined) {
|
|
169
|
+
normalized.target = target
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return normalized
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function serializeDashboardWidgetConfigForEditor(widget: DashboardWidgetConfig) {
|
|
176
|
+
const serialized: Record<string, unknown> = { ...widget }
|
|
177
|
+
|
|
178
|
+
if (Object.prototype.hasOwnProperty.call(serialized, 'minWidth')) {
|
|
179
|
+
serialized.min_width = widget.minWidth
|
|
180
|
+
delete serialized.minWidth
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (Object.prototype.hasOwnProperty.call(serialized, 'maxWidth')) {
|
|
184
|
+
serialized.max_width = widget.maxWidth
|
|
185
|
+
delete serialized.maxWidth
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (widget.table !== undefined) {
|
|
189
|
+
serialized.table = serializeTableConfigForEditor(widget.table)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (widget.dataSource !== undefined) {
|
|
193
|
+
serialized.data_source = serializeWidgetDataSourceForEditor(widget.dataSource)
|
|
194
|
+
delete serialized.dataSource
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return serialized
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function normalizeKpiCardWidgetConfig(value: unknown): NormalizedKpiCardWidgetConfig | undefined {
|
|
201
|
+
const config = asWidgetConfigRecord(value)
|
|
202
|
+
|
|
203
|
+
if (!config) {
|
|
204
|
+
return undefined
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const valueField = getStringField(config, 'value_field')
|
|
208
|
+
const labelField = getStringField(config, 'label_field')
|
|
209
|
+
const prefix = getStringField(config, 'prefix')
|
|
210
|
+
const suffix = getStringField(config, 'suffix')
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
...(valueField !== undefined ? { valueField } : {}),
|
|
214
|
+
...(labelField !== undefined ? { labelField } : {}),
|
|
215
|
+
...(prefix !== undefined ? { prefix } : {}),
|
|
216
|
+
...(suffix !== undefined ? { suffix } : {}),
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function normalizeGaugeCardWidgetConfig(value: unknown): NormalizedGaugeCardWidgetConfig | undefined {
|
|
221
|
+
const config = asWidgetConfigRecord(value)
|
|
222
|
+
|
|
223
|
+
if (!config) {
|
|
224
|
+
return undefined
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const valueField = getStringField(config, 'value_field')
|
|
228
|
+
const minField = getStringField(config, 'min_field')
|
|
229
|
+
const maxField = getStringField(config, 'max_field')
|
|
230
|
+
const suffix = getStringField(config, 'suffix')
|
|
231
|
+
const color = getStringField(config, 'color')
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
...(valueField !== undefined ? { valueField } : {}),
|
|
235
|
+
...(config.min !== undefined ? { min: config.min as number | string } : {}),
|
|
236
|
+
...(config.max !== undefined ? { max: config.max as number | string } : {}),
|
|
237
|
+
...(minField !== undefined ? { minField } : {}),
|
|
238
|
+
...(maxField !== undefined ? { maxField } : {}),
|
|
239
|
+
...(suffix !== undefined ? { suffix } : {}),
|
|
240
|
+
...(color !== undefined ? { color } : {}),
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function normalizePivotTableWidgetConfig(value: unknown): NormalizedPivotTableWidgetConfig | undefined {
|
|
245
|
+
const config = asWidgetConfigRecord(value)
|
|
246
|
+
|
|
247
|
+
if (!config) {
|
|
248
|
+
return undefined
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const rowField = getStringField(config, 'row_field')
|
|
252
|
+
const columnField = getStringField(config, 'column_field')
|
|
253
|
+
const valueField = getStringField(config, 'value_field')
|
|
254
|
+
const aggregation = config.aggregation === 'count' || config.aggregation === 'sum'
|
|
255
|
+
? config.aggregation
|
|
256
|
+
: undefined
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
...(rowField !== undefined ? { rowField } : {}),
|
|
260
|
+
...(columnField !== undefined ? { columnField } : {}),
|
|
261
|
+
...(valueField !== undefined ? { valueField } : {}),
|
|
262
|
+
...(aggregation !== undefined ? { aggregation } : {}),
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function normalizeDashboardWidgetTarget(value: unknown): DashboardWidgetTarget | undefined {
|
|
267
|
+
switch (value) {
|
|
268
|
+
case 'empty':
|
|
269
|
+
case 'table':
|
|
270
|
+
case 'chart':
|
|
271
|
+
case 'kpi_card':
|
|
272
|
+
case 'pivot_table':
|
|
273
|
+
case 'gauge_card':
|
|
274
|
+
return value
|
|
275
|
+
default:
|
|
276
|
+
return undefined
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function normalizeWidgetLayoutConfig(value: Record<string, unknown>) {
|
|
281
|
+
if (value.min_width !== undefined) {
|
|
282
|
+
value.minWidth = value.min_width
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (value.max_width !== undefined) {
|
|
286
|
+
value.maxWidth = value.max_width
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function normalizeTableConfig(value: unknown) {
|
|
291
|
+
if (!isRecord(value)) {
|
|
292
|
+
return value
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const normalized = { ...value }
|
|
296
|
+
|
|
297
|
+
if (normalized.page_size !== undefined) {
|
|
298
|
+
normalized.pageSize = normalized.page_size
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return normalized
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function normalizeWidgetDataSource(value: unknown) {
|
|
305
|
+
if (!isRecord(value) || typeof value.type !== 'string') {
|
|
306
|
+
return value
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const resourceId = typeof value.resource_id === 'string'
|
|
310
|
+
? value.resource_id
|
|
311
|
+
: undefined
|
|
312
|
+
|
|
313
|
+
if (value.type === 'resource') {
|
|
314
|
+
return {
|
|
315
|
+
type: 'resource',
|
|
316
|
+
...(resourceId !== undefined ? { resourceId } : {}),
|
|
317
|
+
...(value.columns !== undefined ? { columns: value.columns } : {}),
|
|
318
|
+
...(value.filters !== undefined ? { filters: value.filters } : {}),
|
|
319
|
+
...(value.sort !== undefined ? { sort: value.sort } : {}),
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (value.type === 'aggregate') {
|
|
324
|
+
const groupBy = normalizeGroupByRule(value.group_by)
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
type: 'aggregate',
|
|
328
|
+
...(resourceId !== undefined ? { resourceId } : {}),
|
|
329
|
+
...(value.aggregations !== undefined ? { aggregations: value.aggregations } : {}),
|
|
330
|
+
...(groupBy !== undefined ? { groupBy } : {}),
|
|
331
|
+
...(value.filters !== undefined ? { filters: value.filters } : {}),
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return value
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function normalizeGroupByRule(value: unknown) {
|
|
339
|
+
if (!isRecord(value) || typeof value.type !== 'string') {
|
|
340
|
+
return value
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (value.type === 'field') {
|
|
344
|
+
return {
|
|
345
|
+
type: 'field',
|
|
346
|
+
...(value.field !== undefined ? { field: value.field } : {}),
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (value.type === 'date_trunc') {
|
|
351
|
+
return {
|
|
352
|
+
type: 'date_trunc',
|
|
353
|
+
...(value.field !== undefined ? { field: value.field } : {}),
|
|
354
|
+
...(value.truncation !== undefined ? { truncation: value.truncation } : {}),
|
|
355
|
+
...(value.timezone !== undefined ? { timezone: value.timezone } : {}),
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return value
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function serializeTableConfigForEditor(value: unknown) {
|
|
363
|
+
if (!isRecord(value)) {
|
|
364
|
+
return value
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const serialized = { ...value }
|
|
368
|
+
|
|
369
|
+
if (Object.prototype.hasOwnProperty.call(serialized, 'pageSize')) {
|
|
370
|
+
serialized.page_size = serialized.pageSize
|
|
371
|
+
delete serialized.pageSize
|
|
68
372
|
}
|
|
373
|
+
|
|
374
|
+
return serialized
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function serializeWidgetDataSourceForEditor(value: WidgetDataSource) {
|
|
378
|
+
if (value.type === 'resource') {
|
|
379
|
+
return {
|
|
380
|
+
type: 'resource',
|
|
381
|
+
resource_id: value.resourceId,
|
|
382
|
+
...(value.columns !== undefined ? { columns: value.columns } : {}),
|
|
383
|
+
...(value.filters !== undefined ? { filters: value.filters } : {}),
|
|
384
|
+
...(value.sort !== undefined ? { sort: value.sort } : {}),
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return {
|
|
389
|
+
type: 'aggregate',
|
|
390
|
+
resource_id: value.resourceId,
|
|
391
|
+
aggregations: value.aggregations,
|
|
392
|
+
...(value.groupBy !== undefined ? { group_by: serializeGroupByRuleForEditor(value.groupBy) } : {}),
|
|
393
|
+
...(value.filters !== undefined ? { filters: value.filters } : {}),
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function serializeGroupByRuleForEditor(value: GroupByRule) {
|
|
398
|
+
if (value.type === 'field') {
|
|
399
|
+
return {
|
|
400
|
+
type: 'field',
|
|
401
|
+
field: value.field,
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
type: 'date_trunc',
|
|
407
|
+
field: value.field,
|
|
408
|
+
truncation: value.truncation,
|
|
409
|
+
...(value.timezone !== undefined ? { timezone: value.timezone } : {}),
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function asWidgetConfigRecord(value: unknown): Record<string, unknown> | undefined {
|
|
414
|
+
return isRecord(value) ? value : undefined
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function getStringField(record: Record<string, unknown>, key: string) {
|
|
418
|
+
const value = record[key]
|
|
419
|
+
return typeof value === 'string' ? value : undefined
|
|
69
420
|
}
|
|
70
421
|
|
|
71
422
|
function isRecord(value: unknown): value is Record<string, unknown> {
|