Skip to content

Commit 4edb2de

Browse files
committed
feat(lint,spec): os validate refuses a non-datetime duration column; docs, changeset, ledger
- lint: `measure-duration-field-type-refused` at `duration.from` / `.to`, with an `os explain` entry; `dataset-field-unknown` checks both columns. - spec: `DATASET_DURATION_FIELD_TYPE`; the unit's type alias shares its schema's name; liveness drills `duration.{from,to,unit}`. - docs: the duration measure in analytics.mdx and validating-metadata.mdx. - changeset: minor on spec, service-analytics and lint. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AKPixnkg91qCkQCVFDJuqW
1 parent c06b33c commit 4edb2de

21 files changed

Lines changed: 585 additions & 39 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/service-analytics': minor
4+
'@objectstack/lint': minor
5+
---
6+
7+
feat(spec,analytics,lint): a dataset measure can aggregate the time between two datetime fields of each record
8+
9+
Clause-②: yes (widening)
10+
Metadata-protocol: added
11+
12+
- **`@objectstack/spec`**: `DatasetMeasureSchema` gains an optional `duration: { from, to, unit }`, in place of `field`. `from` and `to` name two `datetime` fields of the dataset's own object by their bare names, `unit` is `seconds`, `minutes`, `hours` or `days`, and the measure's `aggregate` is `avg`, `min`, `max` or `sum` of `to` minus `from`. A relationship path, an expression or the row wildcard in either column, the same column twice, another unit, a counting aggregate, or a `field`, `derived` or `currency` beside it is refused at parse. New exports: `DatasetMeasureDurationSchema`, `DatasetDurationUnit`, `DATASET_DURATION_UNIT_SECONDS`, `DATASET_DURATION_AGGREGATES`, `DATASET_DURATION_FIELD_TYPE` and the `DatasetMeasureDuration` / `DatasetDurationUnit` types.
13+
- **`@objectstack/service-analytics`**: the dataset compile refuses a duration column declared anything but `datetime` (`400 DATASET_INVALID`, naming the column and its type). The native-SQL strategy computes the duration as one statement on SQLite, PostgreSQL and MySQL, with the same number on each: a record missing either instant is left out, a `to` before its `from` counts as negative, and over a group with no such record `sum` answers `0` and the others `null`. Measure filters, derived measures, ordering and the draft preview serve it. A query the native strategy does not serve (a time dimension bucketed by `granularity`, a field-to-field filter, an object served through engine middleware or an external datasource, a non-SQL datasource) is refused with `400 INVALID_FIELD` naming the measure. Its two columns pass the field-level read gate like any measure's field. The temporal half of the aggregate × field-type refusal now names the duration measure instead of a stored "days open" number.
14+
- **`@objectstack/lint`**: `os validate` refuses a duration column not declared `datetime` with the new gating rule `measure-duration-field-type-refused` (at `datasets[N].measures[M].duration.from` / `.to`), with an `os explain` entry. `dataset-field-unknown` now also reports a duration column the object does not declare.
15+
16+
What to do: nothing. A dataset without `duration` parses, compiles and answers exactly as before.

‎content/docs/data-modeling/analytics.mdx‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ export const SalesDataset = defineDataset({
6464
{ name: 'won_amount', aggregate: 'sum', field: 'amount', filter: { stage: 'closed_won' } },
6565
// Derived measure — references OTHER measures by name only (no raw fields/SQL).
6666
{ name: 'win_rate', derived: { op: 'ratio', of: ['won_amount', 'revenue'] }, format: '0.0%' },
67+
// Duration measure — the time between two datetime fields of each record.
68+
{ name: 'avg_days_to_close', aggregate: 'avg', duration: { from: 'created_at', to: 'closed_at', unit: 'days' } },
6769
],
6870
});
6971
```
@@ -94,6 +96,38 @@ export default defineStack({
9496
- **RLS / tenant scoping is enforced by the runtime**, per joined object — never
9597
declared in the dataset. There is one place to reason about access.
9698

99+
### Duration measures
100+
101+
A measure can aggregate the time between two `datetime` fields of the same
102+
record (time to resolve, time to first response, cycle time) with `duration`
103+
in place of `field`:
104+
105+
```ts
106+
{ name: 'avg_hours_to_resolve', aggregate: 'avg', duration: { from: 'created_at', to: 'resolved_at', unit: 'hours' } }
107+
```
108+
109+
- `from` and `to` name two `datetime` fields of the dataset's own object, by
110+
their bare names. A relationship path, an expression, or a `date`, `time`,
111+
number or text field is refused: `os validate` reports
112+
`measure-duration-field-type-refused`, and the dataset compile answers
113+
`400 DATASET_INVALID`.
114+
- `unit` is `seconds`, `minutes`, `hours` or `days`, and the measure answers a
115+
number of them (`1.5` hours). `aggregate` is `avg`, `min`, `max` or `sum`.
116+
- A record missing either instant is left out, as a null is left out of an
117+
`avg`. A `to` earlier than its `from` counts as a negative duration. Over a
118+
group with no record carrying both, `sum` answers `0` and the others answer
119+
nothing.
120+
- A measure `filter`, and a `derived` measure naming the duration measure in
121+
`of`, work as they do for any measure.
122+
- It runs as one SQL statement on SQLite, PostgreSQL and MySQL, and answers the
123+
same number on each. A query the service cannot compile to one such
124+
statement is refused with `400 INVALID_FIELD` naming the measure, rather than
125+
answered another way: a time dimension bucketed by `granularity` (a monthly
126+
trend), a field-to-field comparison in a filter, an object served through
127+
engine middleware or an external datasource, or a datasource that is not SQL.
128+
- Comparing each record's duration against a value held on another object (an
129+
SLA target per ticket type, say) is not part of it.
130+
97131
## Binding a dashboard widget
98132

99133
A widget selects dimensions/measures by name. Its presentation-scope `filter`

‎content/docs/deployment/validating-metadata.mdx‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,16 @@ on the object the last hop reaches — the join the cube declares for that hop,
249249
else the lookup's `reference`. A measure of an expression type (`number`,
250250
`string`, `boolean`) and the row wildcard `'*'` are not judged by this check.
251251

252+
A **duration measure** (`duration: { from, to, unit }`, the time between two
253+
fields of each record) is judged by the same check under a third id. Each of
254+
`from` and `to` must name a field declared `datetime`; one declared anything
255+
else (a `date`, a `time`, a number, text) is refused
256+
(`measure-duration-field-type-refused`) at
257+
`datasets[N].measures[M].duration.from` or `.to`, because it holds no instant
258+
to measure from. The analytics service refuses the same column with
259+
`400 DATASET_INVALID` when a query is built. A name the object does not declare
260+
is reported as `dataset-field-unknown` instead.
261+
252262
### 7. Navigation exposing objects nobody can read
253263

254264
Navigation and permissions are separate metadata, each valid on its own — so an

‎content/docs/references/index.mdx‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Protocol reference — every schema by module
33
navTitle: Protocol Reference
4-
description: Every schema published by @objectstack/spec — 1549 schemas across 14 protocol modules
4+
description: Every schema published by @objectstack/spec — 1551 schemas across 14 protocol modules
55
---
66

77
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -33,8 +33,8 @@ counts are sums of the rows they head. Regenerate with
3333
| [Shared Protocol](/docs/references/shared) | 10 | 31 | Primitives used across every protocol — identifiers, HTTP, expressions, error maps, enums. |
3434
| [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. |
3535
| [System Protocol](/docs/references/system) | 36 | 285 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. |
36-
| [UI Protocol](/docs/references/ui) | 16 | 169 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
37-
| **Total** | **198** | **1549** | 14 protocol modules |
36+
| [UI Protocol](/docs/references/ui) | 16 | 171 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. |
37+
| **Total** | **198** | **1551** | 14 protocol modules |
3838

3939
---
4040

@@ -365,7 +365,7 @@ The runtime environment — logging, jobs, cache, metrics, notifications, i18n a
365365

366366
## UI Protocol
367367

368-
**Source:** `packages/spec/src/ui/` · **Import:** `@objectstack/spec/ui` · **16 pages, 169 schemas**
368+
**Source:** `packages/spec/src/ui/` · **Import:** `@objectstack/spec/ui` · **16 pages, 171 schemas**
369369

370370
Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer.
371371

@@ -378,7 +378,7 @@ Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI lay
378378
| [`chart.zod.ts`](/docs/references/ui/chart) | `ChartAggregate`, `ChartAggregateFunction`, `ChartAnnotation`, `ChartAxis`, `ChartConfig`, `ChartDrillDown`, `ChartGroupBy`, `ChartInteraction`, `ChartSeries`, `ChartType` |
379379
| [`component.zod.ts`](/docs/references/ui/component) | `ActionButtonProps`, `ActionGroupProps`, `ActionIconProps`, `ActionMenuProps`, `ElementButtonProps`, `ElementDefinitionListProps`, `ElementFilterProps`, `ElementFormProps`, `ElementImageProps`, `ElementMetadataViewerProps`, `ElementNumberProps`, `ElementRecordPickerProps`, `ElementRepeaterProps`, `ElementTextInputProps`, `ElementTextProps`, `ObjectCalendarProps`, `ObjectFormProps`, `ObjectGanttProps`, `ObjectGridProps`, `ObjectKanbanProps`, `ObjectMapProps`, `ObjectMasterDetailFormProps`, `ObjectMetricProps`, `ObjectTimelineProps`, `ObjectTreeProps`, `PageAccordionProps`, `PageCardProps`, `PageContainerProps`, `PageHeaderProps`, `PageTabsProps`, `RecordActivityProps`, `RecordAlertAction`, `RecordAlertProps`, `RecordChatterProps`, `RecordDetailsProps`, `RecordHighlightsField`, `RecordHighlightsProps`, `RecordHistoryProps`, `RecordLineItemsProps`, `RecordPathProps`, `RecordQuickActionsProps`, `RecordReferenceRailProps`, `RecordRelatedListProps`, `ReferenceRailEntry` |
380380
| [`dashboard.zod.ts`](/docs/references/ui/dashboard) | `Dashboard`, `DashboardHeader`, `DashboardHeaderAction`, `DashboardWidget`, `DashboardWidgetChartConfig`, `DashboardWidgetOptions`, `GlobalFilter`, `GlobalFilterOptionsFrom`, `WidgetActionType`, `WidgetColorVariant` |
381-
| [`dataset.zod.ts`](/docs/references/ui/dataset) | `Dataset`, `DatasetDimension`, `DatasetMeasure`, `DerivedMeasureOp` |
381+
| [`dataset.zod.ts`](/docs/references/ui/dataset) | `Dataset`, `DatasetDimension`, `DatasetDurationUnit`, `DatasetMeasure`, `DatasetMeasureDuration`, `DerivedMeasureOp` |
382382
| [`expression-bindable-text-keys.zod.ts`](/docs/references/ui/expression-bindable-text-keys) | `ExpressionBindableTextKey` |
383383
| [`i18n.zod.ts`](/docs/references/ui/i18n) | `AriaProps`, `I18nLabel`, `InlineLocaleMap` |
384384
| [`notification.zod.ts`](/docs/references/ui/notification) | `NotificationPosition`, `NotificationSeverity`, `NotificationType` |

‎content/docs/references/ui/dataset.mdx‎

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Dataset schema — UI Protocol reference
33
navTitle: Dataset
4-
description: "Analytics Dataset — the one semantic layer. Reference for Dataset, DatasetDimension, DatasetMeasure, DerivedMeasureOp: every property with its type and default."
4+
description: "Analytics Dataset — the one semantic layer. Reference for Dataset, DatasetDimension, DatasetDurationUnit and 3 more: every property with its type and default."
55
---
66

77
{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
@@ -34,8 +34,8 @@ layer is absorbed/retired in a later phase (D-A).
3434
## TypeScript Usage
3535

3636
```typescript
37-
import { DatasetSchema, DatasetDimensionSchema, DatasetMeasureSchema, DerivedMeasureOp } from '@objectstack/spec/ui';
38-
import type { Dataset, DatasetDimension, DatasetMeasure } from '@objectstack/spec/ui';
37+
import { DatasetSchema, DatasetDimensionSchema, DatasetDurationUnit, DatasetMeasureSchema, DatasetMeasureDurationSchema, DerivedMeasureOp } from '@objectstack/spec/ui';
38+
import type { Dataset, DatasetDimension, DatasetMeasure, DatasetMeasureDuration } from '@objectstack/spec/ui';
3939

4040
// Validate data
4141
const result = DatasetSchema.parse(data);
@@ -89,6 +89,7 @@ const result = DatasetSchema.parse(data);
8989
| **format** | `string` | optional | Numeral pattern for a NUMERIC measure — grouping, decimals, percent; e.g. "0,0.00", "0.0%". An amount takes its symbol from `currency`, not from a "$" in the pattern. A DATE-valued measure never reads a date pattern: `"YYYY-MM-DD"` renders that arm's default face. A date or datetime value reads `format` as a display style — `short` or `relative`, honoured on both. |
9090
| **currency** | `string` | optional | Display currency code (ISO 4217) |
9191
| **derived** | `{ op: Enum<'ratio' \| 'sum' \| 'difference' \| 'product'>; of: string[] }` | optional | |
92+
| **duration** | `{ from: string; to: string; unit: Enum<'seconds' \| 'minutes' \| 'hours' \| 'days'> }` | optional | Time between two datetime columns of each record (`{ from, to, unit }`), reduced by `aggregate` (avg / min / max / sum). Records with either instant null are excluded. Replaces `field`. |
9293

9394
### Nested Shape: `Dataset.protection`
9495

@@ -115,6 +116,18 @@ const result = DatasetSchema.parse(data);
115116
| **nullLabel** | `string \| Record<string, string>` | optional | Label for the bucket of rows whose value for this dimension is null (e.g. "System" on a user axis). Display copy only: grouping is unchanged. When absent, consumers fall back to their own default label for that bucket. |
116117

117118

119+
---
120+
121+
## DatasetDurationUnit
122+
123+
### Allowed Values
124+
125+
* `seconds`
126+
* `minutes`
127+
* `hours`
128+
* `days`
129+
130+
118131
---
119132

120133
## DatasetMeasure
@@ -131,6 +144,28 @@ const result = DatasetSchema.parse(data);
131144
| **format** | `string` | optional | Numeral pattern for a NUMERIC measure — grouping, decimals, percent; e.g. "0,0.00", "0.0%". An amount takes its symbol from `currency`, not from a "$" in the pattern. A DATE-valued measure never reads a date pattern: `"YYYY-MM-DD"` renders that arm's default face. A date or datetime value reads `format` as a display style — `short` or `relative`, honoured on both. |
132145
| **currency** | `string` | optional | Display currency code (ISO 4217) |
133146
| **derived** | `{ op: Enum<'ratio' \| 'sum' \| 'difference' \| 'product'>; of: string[] }` | optional | |
147+
| **duration** | `{ from: string; to: string; unit: Enum<'seconds' \| 'minutes' \| 'hours' \| 'days'> }` | optional | Time between two datetime columns of each record (`{ from, to, unit }`), reduced by `aggregate` (avg / min / max / sum). Records with either instant null are excluded. Replaces `field`. |
148+
149+
### Nested Shape: `DatasetMeasure.duration`
150+
151+
| Property | Type | Required | Description |
152+
| :--- | :--- | :--- | :--- |
153+
| **from** | `string` | ✅ | The earlier instant: a datetime field of the dataset's own object, by its bare name. |
154+
| **to** | `string` | ✅ | The later instant: a datetime field of the dataset's own object, by its bare name. |
155+
| **unit** | `Enum<'seconds' \| 'minutes' \| 'hours' \| 'days'>` | ✅ | Unit of the result: the measure answers a number of these (1.5 with "days" is a day and a half). |
156+
157+
158+
---
159+
160+
## DatasetMeasureDuration
161+
162+
### Properties
163+
164+
| Property | Type | Required | Description |
165+
| :--- | :--- | :--- | :--- |
166+
| **from** | `string` | ✅ | The earlier instant: a datetime field of the dataset's own object, by its bare name. |
167+
| **to** | `string` | ✅ | The later instant: a datetime field of the dataset's own object, by its bare name. |
168+
| **unit** | `Enum<'seconds' \| 'minutes' \| 'hours' \| 'days'>` | ✅ | Unit of the result: the measure answers a number of these (1.5 with "days" is a day and a half). |
134169

135170

136171
---

‎docs/audits/2026-07-unknown-key-strictness-ledger.counts/ui.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The `strict` column is the one the campaign schedules against; it counts both th
2121

2222
| Dir | Sites | strict | passthrough | catchall | strip |
2323
|---|---|---|---|---|---|
24-
| `ui/` | 215 | 204 | 4 | 0 | 7 |
24+
| `ui/` | 216 | 205 | 4 | 0 | 7 |
2525

2626
## `ui/` — sites
2727

@@ -38,31 +38,31 @@ classify and is not listed (it becomes reportable the day it grows its first sit
3838
| `chart.zod.ts` | 8 |
3939
| `component.zod.ts` | 81 |
4040
| `dashboard.zod.ts` | 11 |
41-
| `dataset.zod.ts` | 4 |
41+
| `dataset.zod.ts` | 5 |
4242
| `i18n.zod.ts` | 1 |
4343
| `page.zod.ts` | 12 |
4444
| `report.zod.ts` | 3 |
4545
| `responsive.zod.ts` | 1 |
4646
| `sharing.zod.ts` | 1 |
4747
| `view.zod.ts` | 59 |
4848
| `widget.zod.ts` | 1 |
49-
| **total** | **215** |
49+
| **total** | **216** |
5050

5151
## `ui/` — open
5252

5353
Per file, how many of its sites still silently discard unknown keys. The `Class`
5454
column that decides the bucket split is hand-written in the ledger; the arithmetic
5555
over it is here.
5656

57-
**7 strip of 215**, in 4 file(s).
57+
**7 strip of 216**, in 4 file(s).
5858

5959
| File | Strip | Sites |
6060
|---|---|---|
6161
| `action-params.zod.ts` | 1 | 1 |
6262
| `app.zod.ts` | 1 | 19 |
6363
| `view.zod.ts` | 4 | 59 |
6464
| `widget.zod.ts` | 1 | 1 |
65-
| **total** | **7** | **215** |
65+
| **total** | **7** | **216** |
6666

6767
| Bucket | Sites |
6868
|---|---|

‎packages/lint/src/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export {
6060
validateDatasetMeasureAggregates,
6161
MEASURE_AGGREGATE_FIELD_TYPE_REFUSED,
6262
DIMENSION_JSON_STORED_FIELD_REFUSED,
63+
MEASURE_DURATION_FIELD_TYPE_REFUSED,
6364
// [#23235] The dimension leg's group-key verdict on its own, so a consumer
6465
// deriving dataset dimensions (cloud's AI build) asks the door's two
6566
// predicates in the door's order here rather than composing them itself.

‎packages/lint/src/rule-explanations.ts‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2060,7 +2060,8 @@ const MEASURE_AGGREGATE_FIELD_TYPE_REFUSED_EXPLANATION: RuleExplanation = {
20602060
'datetime, time, boolean, toggle.',
20612061
'So the fix is a field of an accepted type, or an aggregate the field\'s declaration accepts. A ' +
20622062
'quantity that must be added up or averaged has to be STORED as a numeric field and aggregated as ' +
2063-
'one.',
2063+
'one. The time BETWEEN two `datetime` fields of a record is not an aggregate over either one: it is ' +
2064+
'a `duration: { from, to, unit }` measure (`measure-duration-field-type-refused`).',
20642065
// Held equal to `VIRTUAL_FIELD_AGGREGATE_REMEDY` (`@objectstack/spec/data`) — the `fix:` line, and
20652066
// the compile leg's remedy, for this pair — by `validate-dataset-measure-aggregates.test.ts`.
20662067
'A `formula` field stores no value, whatever its `returnType` says, so `sum`, `avg`, `min` and `max` ' +
@@ -2104,6 +2105,25 @@ const DIMENSION_JSON_STORED_FIELD_REFUSED_EXPLANATION: RuleExplanation = {
21042105
],
21052106
};
21062107

2108+
const MEASURE_DURATION_FIELD_TYPE_REFUSED_EXPLANATION: RuleExplanation = {
2109+
rule: 'measure-duration-field-type-refused',
2110+
covers: 'which fields a duration measure measures between',
2111+
paragraphs: [
2112+
'A duration measure, `duration: { from, to, unit }` on a dataset measure, aggregates the time from one ' +
2113+
'field to another on the same record: `avg`, `min`, `max` or `sum` of `to` minus `from`, as a number ' +
2114+
'of `unit`s (seconds, minutes, hours or days). The time between two INSTANTS is the same number on ' +
2115+
'every backend, so both fields must be declared `datetime`. A `date` is a calendar day and a `time` ' +
2116+
'a wall clock, neither one an instant; a `text` holding ISO strings or a number holding epoch values ' +
2117+
'is no instant the database knows, and each SQL family would read it its own way.',
2118+
'So the fix is a `datetime` field of the dataset\'s own object in each slot, by its bare name. A ' +
2119+
'record missing either instant takes no part in the aggregate, and a `to` earlier than its `from` ' +
2120+
'counts as a negative duration.',
2121+
'The dataset compile refuses the same column later, with `400 DATASET_INVALID` when a query is built. ' +
2122+
'This rule makes the same refusal where the author is standing. A name the object does not declare ' +
2123+
'is `dataset-field-unknown`\'s finding, and a field with no declared type is not judged.',
2124+
],
2125+
};
2126+
21072127
// ── React page props (`validate-react-page-props.ts`) ───────────────────────
21082128
// Two ids forward `@objectstack/spec`'s own refusal of a chart prop. The
21092129
// verdict quotes it to its head — the key or value, and the schema's rename
@@ -4965,6 +4985,7 @@ export const RULE_EXPLANATIONS: Readonly<Record<string, RuleExplanation>> = Obje
49654985
[VALIDATION_RULE_JSON_SCHEMA_UNKNOWN_FORMAT_EXPLANATION.rule]: VALIDATION_RULE_JSON_SCHEMA_UNKNOWN_FORMAT_EXPLANATION,
49664986
[MEASURE_AGGREGATE_FIELD_TYPE_REFUSED_EXPLANATION.rule]: MEASURE_AGGREGATE_FIELD_TYPE_REFUSED_EXPLANATION,
49674987
[DIMENSION_JSON_STORED_FIELD_REFUSED_EXPLANATION.rule]: DIMENSION_JSON_STORED_FIELD_REFUSED_EXPLANATION,
4988+
[MEASURE_DURATION_FIELD_TYPE_REFUSED_EXPLANATION.rule]: MEASURE_DURATION_FIELD_TYPE_REFUSED_EXPLANATION,
49684989
[REACT_CHART_DRILLDOWN_INVALID_EXPLANATION.rule]: REACT_CHART_DRILLDOWN_INVALID_EXPLANATION,
49694990
[REACT_CHART_AGGREGATE_INVALID_EXPLANATION.rule]: REACT_CHART_AGGREGATE_INVALID_EXPLANATION,
49704991
[REACT_CHART_FIELD_UNPROVISIONED_EXPLANATION.rule]: REACT_CHART_FIELD_UNPROVISIONED_EXPLANATION,

0 commit comments

Comments
 (0)