@adminforth/dashboard 1.5.0 → 1.6.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.
Files changed (50) hide show
  1. package/custom/api/dashboardApi.ts +137 -1
  2. package/custom/model/dashboard.types.ts +32 -22
  3. package/custom/runtime/DashboardRuntime.vue +2 -3
  4. package/custom/skills/adminforth-dashboard/SKILL.md +66 -10
  5. package/custom/widgets/KpiCardWidget.vue +172 -9
  6. package/custom/widgets/chart/ChartWidget.vue +5 -5
  7. package/custom/widgets/registry.ts +4 -4
  8. package/dist/custom/api/dashboardApi.d.ts +46 -1
  9. package/dist/custom/api/dashboardApi.js +90 -0
  10. package/dist/custom/api/dashboardApi.ts +137 -1
  11. package/dist/custom/model/dashboard.types.d.ts +30 -14
  12. package/dist/custom/model/dashboard.types.js +2 -2
  13. package/dist/custom/model/dashboard.types.ts +32 -22
  14. package/dist/custom/queries/useDashboardConfig.d.ts +106 -104
  15. package/dist/custom/queries/useWidgetData.d.ts +106 -104
  16. package/dist/custom/runtime/DashboardRuntime.vue +2 -3
  17. package/dist/custom/skills/adminforth-dashboard/SKILL.md +66 -10
  18. package/dist/custom/widgets/KpiCardWidget.vue +172 -9
  19. package/dist/custom/widgets/chart/ChartWidget.vue +5 -5
  20. package/dist/custom/widgets/registry.js +4 -4
  21. package/dist/custom/widgets/registry.ts +4 -4
  22. package/dist/endpoint/widgets.js +99 -14
  23. package/dist/schema/api.d.ts +11426 -1634
  24. package/dist/schema/api.js +118 -21
  25. package/dist/schema/widget.d.ts +425 -1980
  26. package/dist/schema/widget.js +13 -374
  27. package/dist/schema/widgets/charts.d.ts +1689 -0
  28. package/dist/schema/widgets/charts.js +92 -0
  29. package/dist/schema/widgets/common.d.ts +275 -0
  30. package/dist/schema/widgets/common.js +171 -0
  31. package/dist/schema/widgets/gauge-card.d.ts +172 -0
  32. package/dist/schema/widgets/gauge-card.js +28 -0
  33. package/dist/schema/widgets/kpi-card.d.ts +212 -0
  34. package/dist/schema/widgets/kpi-card.js +43 -0
  35. package/dist/schema/widgets/pivot-table.d.ts +196 -0
  36. package/dist/schema/widgets/pivot-table.js +17 -0
  37. package/dist/schema/widgets/table.d.ts +130 -0
  38. package/dist/schema/widgets/table.js +12 -0
  39. package/dist/services/widgetDataService.js +96 -2
  40. package/endpoint/widgets.ts +173 -26
  41. package/package.json +1 -1
  42. package/schema/api.ts +148 -22
  43. package/schema/widget.ts +43 -425
  44. package/schema/widgets/charts.ts +113 -0
  45. package/schema/widgets/common.ts +194 -0
  46. package/schema/widgets/gauge-card.ts +34 -0
  47. package/schema/widgets/kpi-card.ts +49 -0
  48. package/schema/widgets/pivot-table.ts +24 -0
  49. package/schema/widgets/table.ts +18 -0
  50. package/services/widgetDataService.ts +129 -3
@@ -50,6 +50,12 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
50
50
  calc: string;
51
51
  as: string;
52
52
  })[] | undefined;
53
+ sparkline?: {
54
+ field: string;
55
+ grain: import("../model/dashboard.types.js").TimeGrain;
56
+ as: string;
57
+ fill_missing?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
58
+ } | undefined;
53
59
  filters?: any;
54
60
  group_by?: (string | {
55
61
  field: string;
@@ -63,16 +69,6 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
63
69
  }[] | undefined;
64
70
  limit?: number | undefined;
65
71
  offset?: number | undefined;
66
- time_series?: {
67
- field: string;
68
- grain: import("../model/dashboard.types.js").TimeGrain;
69
- timezone?: string | undefined;
70
- } | undefined;
71
- period?: {
72
- field: string;
73
- gte?: any;
74
- lt?: any;
75
- } | undefined;
76
72
  bucket?: {
77
73
  field: string;
78
74
  buckets: {
@@ -154,6 +150,12 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
154
150
  calc: string;
155
151
  as: string;
156
152
  })[] | undefined;
153
+ sparkline?: {
154
+ field: string;
155
+ grain: import("../model/dashboard.types.js").TimeGrain;
156
+ as: string;
157
+ fill_missing?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
158
+ } | undefined;
157
159
  filters?: any;
158
160
  group_by?: (string | {
159
161
  field: string;
@@ -167,16 +169,6 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
167
169
  }[] | undefined;
168
170
  limit?: number | undefined;
169
171
  offset?: number | undefined;
170
- time_series?: {
171
- field: string;
172
- grain: import("../model/dashboard.types.js").TimeGrain;
173
- timezone?: string | undefined;
174
- } | undefined;
175
- period?: {
176
- field: string;
177
- gte?: any;
178
- lt?: any;
179
- } | undefined;
180
172
  bucket?: {
181
173
  field: string;
182
174
  buckets: {
@@ -231,8 +223,29 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
231
223
  text?: string | undefined;
232
224
  field?: string | undefined;
233
225
  } | undefined;
234
- comparison?: any;
235
- sparkline?: any;
226
+ comparison?: {
227
+ field: string;
228
+ format?: import("../model/dashboard.types.js").ValueFormat | undefined;
229
+ positive_is_good?: boolean | undefined;
230
+ compact?: {
231
+ show?: boolean | undefined;
232
+ template?: string | undefined;
233
+ } | undefined;
234
+ tooltip?: {
235
+ label?: string | undefined;
236
+ template?: string | undefined;
237
+ } | undefined;
238
+ } | undefined;
239
+ sparkline?: {
240
+ type?: "line" | undefined;
241
+ field: string;
242
+ x: string;
243
+ show_axes?: boolean | undefined;
244
+ show_labels?: boolean | undefined;
245
+ fill?: {
246
+ type?: "gradient" | "solid" | undefined;
247
+ } | undefined;
248
+ } | undefined;
236
249
  };
237
250
  query: {
238
251
  resource: string;
@@ -249,6 +262,12 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
249
262
  calc: string;
250
263
  as: string;
251
264
  })[] | undefined;
265
+ sparkline?: {
266
+ field: string;
267
+ grain: import("../model/dashboard.types.js").TimeGrain;
268
+ as: string;
269
+ fill_missing?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
270
+ } | undefined;
252
271
  filters?: any;
253
272
  group_by?: (string | {
254
273
  field: string;
@@ -262,16 +281,6 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
262
281
  }[] | undefined;
263
282
  limit?: number | undefined;
264
283
  offset?: number | undefined;
265
- time_series?: {
266
- field: string;
267
- grain: import("../model/dashboard.types.js").TimeGrain;
268
- timezone?: string | undefined;
269
- } | undefined;
270
- period?: {
271
- field: string;
272
- gte?: any;
273
- lt?: any;
274
- } | undefined;
275
284
  bucket?: {
276
285
  field: string;
277
286
  buckets: {
@@ -334,6 +343,12 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
334
343
  calc: string;
335
344
  as: string;
336
345
  })[] | undefined;
346
+ sparkline?: {
347
+ field: string;
348
+ grain: import("../model/dashboard.types.js").TimeGrain;
349
+ as: string;
350
+ fill_missing?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
351
+ } | undefined;
337
352
  filters?: any;
338
353
  group_by?: (string | {
339
354
  field: string;
@@ -347,16 +362,6 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
347
362
  }[] | undefined;
348
363
  limit?: number | undefined;
349
364
  offset?: number | undefined;
350
- time_series?: {
351
- field: string;
352
- grain: import("../model/dashboard.types.js").TimeGrain;
353
- timezone?: string | undefined;
354
- } | undefined;
355
- period?: {
356
- field: string;
357
- gte?: any;
358
- lt?: any;
359
- } | undefined;
360
365
  bucket?: {
361
366
  field: string;
362
367
  buckets: {
@@ -416,6 +421,12 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
416
421
  calc: string;
417
422
  as: string;
418
423
  })[] | undefined;
424
+ sparkline?: {
425
+ field: string;
426
+ grain: import("../model/dashboard.types.js").TimeGrain;
427
+ as: string;
428
+ fill_missing?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
429
+ } | undefined;
419
430
  filters?: any;
420
431
  group_by?: (string | {
421
432
  field: string;
@@ -429,16 +440,6 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
429
440
  }[] | undefined;
430
441
  limit?: number | undefined;
431
442
  offset?: number | undefined;
432
- time_series?: {
433
- field: string;
434
- grain: import("../model/dashboard.types.js").TimeGrain;
435
- timezone?: string | undefined;
436
- } | undefined;
437
- period?: {
438
- field: string;
439
- gte?: any;
440
- lt?: any;
441
- } | undefined;
442
443
  bucket?: {
443
444
  field: string;
444
445
  buckets: {
@@ -504,6 +505,12 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
504
505
  calc: string;
505
506
  as: string;
506
507
  })[] | undefined;
508
+ sparkline?: {
509
+ field: string;
510
+ grain: import("../model/dashboard.types.js").TimeGrain;
511
+ as: string;
512
+ fill_missing?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
513
+ } | undefined;
507
514
  filters?: any;
508
515
  group_by?: (string | {
509
516
  field: string;
@@ -517,16 +524,6 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
517
524
  }[] | undefined;
518
525
  limit?: number | undefined;
519
526
  offset?: number | undefined;
520
- time_series?: {
521
- field: string;
522
- grain: import("../model/dashboard.types.js").TimeGrain;
523
- timezone?: string | undefined;
524
- } | undefined;
525
- period?: {
526
- field: string;
527
- gte?: any;
528
- lt?: any;
529
- } | undefined;
530
527
  bucket?: {
531
528
  field: string;
532
529
  buckets: {
@@ -608,6 +605,12 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
608
605
  calc: string;
609
606
  as: string;
610
607
  })[] | undefined;
608
+ sparkline?: {
609
+ field: string;
610
+ grain: import("../model/dashboard.types.js").TimeGrain;
611
+ as: string;
612
+ fill_missing?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
613
+ } | undefined;
611
614
  filters?: any;
612
615
  group_by?: (string | {
613
616
  field: string;
@@ -621,16 +624,6 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
621
624
  }[] | undefined;
622
625
  limit?: number | undefined;
623
626
  offset?: number | undefined;
624
- time_series?: {
625
- field: string;
626
- grain: import("../model/dashboard.types.js").TimeGrain;
627
- timezone?: string | undefined;
628
- } | undefined;
629
- period?: {
630
- field: string;
631
- gte?: any;
632
- lt?: any;
633
- } | undefined;
634
627
  bucket?: {
635
628
  field: string;
636
629
  buckets: {
@@ -685,8 +678,29 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
685
678
  text?: string | undefined;
686
679
  field?: string | undefined;
687
680
  } | undefined;
688
- comparison?: any;
689
- sparkline?: any;
681
+ comparison?: {
682
+ field: string;
683
+ format?: import("../model/dashboard.types.js").ValueFormat | undefined;
684
+ positive_is_good?: boolean | undefined;
685
+ compact?: {
686
+ show?: boolean | undefined;
687
+ template?: string | undefined;
688
+ } | undefined;
689
+ tooltip?: {
690
+ label?: string | undefined;
691
+ template?: string | undefined;
692
+ } | undefined;
693
+ } | undefined;
694
+ sparkline?: {
695
+ type?: "line" | undefined;
696
+ field: string;
697
+ x: string;
698
+ show_axes?: boolean | undefined;
699
+ show_labels?: boolean | undefined;
700
+ fill?: {
701
+ type?: "gradient" | "solid" | undefined;
702
+ } | undefined;
703
+ } | undefined;
690
704
  };
691
705
  query: {
692
706
  resource: string;
@@ -703,6 +717,12 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
703
717
  calc: string;
704
718
  as: string;
705
719
  })[] | undefined;
720
+ sparkline?: {
721
+ field: string;
722
+ grain: import("../model/dashboard.types.js").TimeGrain;
723
+ as: string;
724
+ fill_missing?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
725
+ } | undefined;
706
726
  filters?: any;
707
727
  group_by?: (string | {
708
728
  field: string;
@@ -716,16 +736,6 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
716
736
  }[] | undefined;
717
737
  limit?: number | undefined;
718
738
  offset?: number | undefined;
719
- time_series?: {
720
- field: string;
721
- grain: import("../model/dashboard.types.js").TimeGrain;
722
- timezone?: string | undefined;
723
- } | undefined;
724
- period?: {
725
- field: string;
726
- gte?: any;
727
- lt?: any;
728
- } | undefined;
729
739
  bucket?: {
730
740
  field: string;
731
741
  buckets: {
@@ -788,6 +798,12 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
788
798
  calc: string;
789
799
  as: string;
790
800
  })[] | undefined;
801
+ sparkline?: {
802
+ field: string;
803
+ grain: import("../model/dashboard.types.js").TimeGrain;
804
+ as: string;
805
+ fill_missing?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
806
+ } | undefined;
791
807
  filters?: any;
792
808
  group_by?: (string | {
793
809
  field: string;
@@ -801,16 +817,6 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
801
817
  }[] | undefined;
802
818
  limit?: number | undefined;
803
819
  offset?: number | undefined;
804
- time_series?: {
805
- field: string;
806
- grain: import("../model/dashboard.types.js").TimeGrain;
807
- timezone?: string | undefined;
808
- } | undefined;
809
- period?: {
810
- field: string;
811
- gte?: any;
812
- lt?: any;
813
- } | undefined;
814
820
  bucket?: {
815
821
  field: string;
816
822
  buckets: {
@@ -870,6 +876,12 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
870
876
  calc: string;
871
877
  as: string;
872
878
  })[] | undefined;
879
+ sparkline?: {
880
+ field: string;
881
+ grain: import("../model/dashboard.types.js").TimeGrain;
882
+ as: string;
883
+ fill_missing?: Record<string, import("../model/dashboard.types.js").JsonValue> | undefined;
884
+ } | undefined;
873
885
  filters?: any;
874
886
  group_by?: (string | {
875
887
  field: string;
@@ -883,16 +895,6 @@ export declare function useWidgetData(slug: Ref<string>, widgetId: Ref<string>,
883
895
  }[] | undefined;
884
896
  limit?: number | undefined;
885
897
  offset?: number | undefined;
886
- time_series?: {
887
- field: string;
888
- grain: import("../model/dashboard.types.js").TimeGrain;
889
- timezone?: string | undefined;
890
- } | undefined;
891
- period?: {
892
- field: string;
893
- gte?: any;
894
- lt?: any;
895
- } | undefined;
896
898
  bucket?: {
897
899
  field: string;
898
900
  buckets: {
@@ -208,7 +208,6 @@ import type {
208
208
  DashboardConfig,
209
209
  DashboardGroupConfig,
210
210
  EditableDashboardGroupConfig,
211
- EditableDashboardWidgetConfig,
212
211
  DashboardGroupMoveDirection,
213
212
  DashboardWidgetConfig,
214
213
  DashboardWidgetMoveDirection,
@@ -414,13 +413,13 @@ async function saveWidgetConfig() {
414
413
  try {
415
414
  widgetConfigError.value = ''
416
415
  widgetConfigFieldErrors.value = []
417
- const widgetConfig = parseYaml(widgetConfigCode.value) as EditableDashboardWidgetConfig
416
+ const widgetConfig = parseYaml(widgetConfigCode.value) as DashboardWidgetConfig
418
417
 
419
418
  applyDashboardResponse(
420
419
  await dashboardApi.setWidgetConfig(
421
420
  props.dashboardSlug,
422
421
  editingWidgetId.value,
423
- widgetConfig,
422
+ serializeDashboardWidgetConfigForEditor(widgetConfig),
424
423
  ),
425
424
  )
426
425
  closeWidgetConfigEditor()
@@ -19,7 +19,6 @@ Dashboard root, groups, and widgets are different entities.
19
19
  - Widget tools use widgetId and change target/query/card/chart/table/pivot.
20
20
  - Never call dashboard_set_dashboard_group_config to configure a widget.
21
21
  - Never call dashboard_add_dashboard_group to configure a widget.
22
- - If widget target, label, query, chart, card, table, pivot, variables, formulas, filters, or display fields must change, use dashboard_set_widget_config.
23
22
 
24
23
  ## Tool routing
25
24
 
@@ -27,7 +26,16 @@ Dashboard root, groups, and widgets are different entities.
27
26
  - Add group: dashboard_add_dashboard_group
28
27
  - Rename group: dashboard_set_dashboard_group_config
29
28
  - Add widget slot: dashboard_add_dashboard_widget
30
- - Configure widget: dashboard_set_widget_config
29
+ - Configure table widget: dashboard_configure_table_widget
30
+ - Configure KPI card widget: dashboard_configure_kpi_card_widget
31
+ - Configure gauge card widget: dashboard_configure_gauge_card_widget
32
+ - Configure line chart widget: dashboard_configure_line_chart_widget
33
+ - Configure bar chart widget: dashboard_configure_bar_chart_widget
34
+ - Configure stacked bar chart widget: dashboard_configure_stacked_bar_chart_widget
35
+ - Configure pie chart widget: dashboard_configure_pie_chart_widget
36
+ - Configure histogram chart widget: dashboard_configure_histogram_chart_widget
37
+ - Configure funnel chart widget: dashboard_configure_funnel_chart_widget
38
+ - Configure pivot table widget: dashboard_configure_pivot_table_widget
31
39
  - Move/remove widget/group: matching move/remove tool
32
40
  - Load widget data: dashboard_get_dashboard_widget_data
33
41
 
@@ -42,7 +50,7 @@ Before creating a group, call dashboard_get_config and check existing groups.
42
50
  - If no matching group exists, call dashboard_add_dashboard_group at most once for that requested group.
43
51
  - After dashboard_add_dashboard_group succeeds, extract the new groupId from the returned dashboard response.
44
52
  - If the group needs a label, call dashboard_set_dashboard_group_config once with that groupId.
45
- - After that, the next mutation must be dashboard_add_dashboard_widget or dashboard_set_widget_config, not another dashboard_add_dashboard_group.
53
+ - After that, the next mutation must be dashboard_add_dashboard_widget or a widget configure tool, not another dashboard_add_dashboard_group.
46
54
 
47
55
  Never call dashboard_add_dashboard_group repeatedly for the same user request. If you are about to create a second group for the same label/section, stop and report:
48
56
 
@@ -57,7 +65,7 @@ For any request to create KPI/chart/table/pivot/gauge widgets:
57
65
  3. if needed, rename group once
58
66
  4. for each widget:
59
67
  - dashboard_add_dashboard_widget
60
- - immediately dashboard_set_widget_config for the returned widgetId
68
+ - immediately configure it with the matching dashboard_configure_*_widget tool
61
69
  - confirm target is not empty
62
70
  5. dashboard_get_config
63
71
  6. validate all requested widgets are configured
@@ -79,7 +87,7 @@ This is incomplete for KPI/chart/table/pivot/gauge/spend/revenue/usage widgets.
79
87
 
80
88
  If the same mutation tool is about to be called with the same payload twice, stop and reassess.
81
89
  If dashboard_add_dashboard_group repeats for the same requested group, stop and reuse the groupId from dashboard_get_config.
82
- If dashboard_set_dashboard_group_config repeats while new widgets are target: empty, use dashboard_set_widget_config instead.
90
+ If dashboard_set_dashboard_group_config repeats while new widgets are target: empty, use a widget configure tool instead.
83
91
  After 2 repeated no-op mutations, stop with an explicit error.
84
92
 
85
93
  ## State machine
@@ -90,22 +98,43 @@ dashboard_get_config
90
98
  -> maybe dashboard_add_dashboard_group
91
99
  -> maybe dashboard_set_dashboard_group_config
92
100
  -> dashboard_add_dashboard_widget
93
- -> dashboard_set_widget_config
101
+ -> matching dashboard_configure_*_widget tool
94
102
  -> repeat only the two widget steps for more widgets
95
103
  -> dashboard_get_config
96
104
 
97
105
  Allowed repeats:
98
106
  - dashboard_add_dashboard_widget may repeat once per requested widget.
99
- - dashboard_set_widget_config may repeat once per requested widget.
107
+ - widget configure tools may repeat once per requested widget.
100
108
 
101
109
  Forbidden repeats:
102
110
  - dashboard_add_dashboard_group for the same requested group.
103
111
  - dashboard_set_dashboard_group_config with the same label/groupId.
104
112
 
105
113
  Forbidden substitutions:
106
- - dashboard_set_dashboard_group_config instead of set widget config.
114
+ - dashboard_set_dashboard_group_config instead of a widget configure tool.
107
115
  - dashboard_add_dashboard_group instead of add widget.
108
116
 
117
+ ## Specialized widget tools
118
+
119
+ Available specialized tools:
120
+ - dashboard_configure_table_widget
121
+ - dashboard_configure_kpi_card_widget
122
+ - dashboard_configure_gauge_card_widget
123
+ - dashboard_configure_pivot_table_widget
124
+ - dashboard_configure_line_chart_widget
125
+ - dashboard_configure_bar_chart_widget
126
+ - dashboard_configure_stacked_bar_chart_widget
127
+ - dashboard_configure_pie_chart_widget
128
+ - dashboard_configure_histogram_chart_widget
129
+ - dashboard_configure_funnel_chart_widget
130
+
131
+ Each specialized tool accepts:
132
+ - slug
133
+ - widgetId
134
+ - config
135
+
136
+ The config is the normal widget config for that target without server-owned id, group_id, and order.
137
+
109
138
  ## Widget config keys
110
139
 
111
140
  Use current keys:
@@ -116,22 +145,49 @@ Use table for table.
116
145
  Use pivot for pivot_table.
117
146
 
118
147
  Use target, not type.
148
+ For charts, use target: chart and chart.type for the concrete chart kind.
119
149
  Use query, not dataSource.
120
150
  Use resource, not resourceId.
121
151
 
122
152
  ## Query shape rules
123
153
 
124
- Use query.steps only for funnel charts. Do not use query.steps for kpi_card, gauge_card, table, pivot_table, or normal bar/line/stacked/pie charts.
154
+ Use dashboard_configure_funnel_chart_widget for funnel charts and set query.steps.
155
+ Do not use query.steps for kpi_card, gauge_card, table, pivot_table, line, bar, stacked bar, pie, or histogram charts.
125
156
 
126
157
  For kpi_card and normal charts, use:
127
158
  - query.resource
128
159
  - query.select
129
160
  - optional query.filters
130
161
  - optional query.group_by
131
- - optional query.period
132
162
  - optional query.order_by
133
163
  - optional query.calcs
134
164
 
165
+ ## Date range rules
166
+
167
+ Use only query.filters for time ranges.
168
+ Never use fixed ISO dates for rolling dashboard periods.
169
+ Never use query.period, period.range, query.time_series, or time_series.range.
170
+
171
+ For rolling ranges, use this exact filter shape:
172
+
173
+ filters:
174
+ and:
175
+ - field: created_at
176
+ gte:
177
+ now_minus: 30d
178
+ - field: created_at
179
+ lt:
180
+ now: true
181
+
182
+ Supported relative duration suffixes:
183
+ - h for hours
184
+ - d for days
185
+ - w for weeks
186
+ - mo for months
187
+ - y for years
188
+
189
+ For today/yesterday/last 7 days comparisons, create separate aggregate select items with separate filters and aliases. Do not hard-code calendar dates.
190
+
135
191
  Calculations run after selected fields and aggregates are loaded into a row. Therefore:
136
192
  - aggregate real resource fields first
137
193
  - then calculate derived fields from those aggregate aliases