@databricks/appkit 0.58.0 → 0.60.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/dist/appkit/package.js +1 -1
- package/dist/plugins/agents/agents.d.ts.map +1 -1
- package/dist/plugins/agents/agents.js +117 -80
- package/dist/plugins/agents/agents.js.map +1 -1
- package/dist/plugins/agents/manifest.js +10 -0
- package/dist/plugins/agents/mlflow.js +123 -0
- package/dist/plugins/agents/mlflow.js.map +1 -0
- package/dist/plugins/agents/schemas.js +4 -2
- package/dist/plugins/agents/schemas.js.map +1 -1
- package/dist/plugins/analytics/analytics.d.ts.map +1 -1
- package/dist/plugins/analytics/analytics.js +9 -3
- package/dist/plugins/analytics/analytics.js.map +1 -1
- package/dist/plugins/analytics/metric.js +2 -1
- package/dist/plugins/analytics/mv/cache.js +2 -0
- package/dist/plugins/analytics/mv/cache.js.map +1 -1
- package/dist/plugins/analytics/mv/constants.js +40 -27
- package/dist/plugins/analytics/mv/constants.js.map +1 -1
- package/dist/plugins/analytics/mv/formatters.js +18 -2
- package/dist/plugins/analytics/mv/formatters.js.map +1 -1
- package/dist/plugins/analytics/mv/index.js +2 -1
- package/dist/plugins/analytics/mv/metadata.js +52 -10
- package/dist/plugins/analytics/mv/metadata.js.map +1 -1
- package/dist/plugins/analytics/mv/schemas.js +31 -1
- package/dist/plugins/analytics/mv/schemas.js.map +1 -1
- package/dist/plugins/analytics/types.d.ts +6 -5
- package/dist/plugins/analytics/types.d.ts.map +1 -1
- package/dist/plugins/analytics/types.js.map +1 -1
- package/dist/shared/src/schemas/manifest.d.ts +4 -4
- package/dist/shared/src/schemas/metric-metadata-bundle.js +24 -0
- package/dist/shared/src/schemas/metric-metadata-bundle.js.map +1 -0
- package/dist/shared/src/schemas/metric-source.js +1 -1
- package/dist/type-generator/index.js +10 -6
- package/dist/type-generator/index.js.map +1 -1
- package/dist/type-generator/mv-registry/render-types.js +35 -52
- package/dist/type-generator/mv-registry/render-types.js.map +1 -1
- package/dist/type-generator/vite-plugin.js +0 -1
- package/dist/type-generator/vite-plugin.js.map +1 -1
- package/docs/development/type-generation.md +7 -6
- package/docs/plugins/analytics.md +287 -12
- package/package.json +2 -1
- package/sbom.cdx.json +1 -1
|
@@ -111,6 +111,7 @@ Content-Type: application/json
|
|
|
111
111
|
"timeGrain": "month",
|
|
112
112
|
"timeDimension": "order_date",
|
|
113
113
|
"filter": { "member": "region", "operator": "in", "values": ["EMEA", "APAC"] },
|
|
114
|
+
"orderBy": [{ "field": "revenue", "direction": "DESC" }],
|
|
114
115
|
"limit": 100
|
|
115
116
|
}
|
|
116
117
|
|
|
@@ -118,15 +119,16 @@ Content-Type: application/json
|
|
|
118
119
|
|
|
119
120
|
`:key` is a metric key from `definitions.json`. The body fields:
|
|
120
121
|
|
|
121
|
-
| Field | Type | Required | Description
|
|
122
|
-
| --------------- | ---------- | -------- |
|
|
123
|
-
| `measures` | `string[]` | yes | Measures to aggregate. At least 1, at most 50. Each becomes `MEASURE(<name>) AS <name>`.
|
|
124
|
-
| `dimensions` | `string[]` | no | Dimensions to group by (max 20). Selected verbatim and grouped via `GROUP BY ALL`.
|
|
125
|
-
| `filter` | object | no | Structured predicate tree translated into a parameterized `WHERE` clause (see [Filters](#filters)).
|
|
126
|
-
| `timeGrain` | `string` | no | Bucket a time dimension via `date_trunc('<grain>', …)` — e.g. `day`, `month`. Requires `timeDimension`.
|
|
127
|
-
| `timeDimension` | `string` | no | The single dimension `timeGrain` buckets. Must be one of `dimensions`. Required whenever `timeGrain` is set.
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
122
|
+
| Field | Type | Required | Description |
|
|
123
|
+
| --------------- | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
124
|
+
| `measures` | `string[]` | yes | Measures to aggregate. At least 1, at most 50. Each becomes `MEASURE(<name>) AS <name>`. |
|
|
125
|
+
| `dimensions` | `string[]` | no | Dimensions to group by (max 20). Selected verbatim and grouped via `GROUP BY ALL`. |
|
|
126
|
+
| `filter` | object | no | Structured predicate tree translated into a parameterized `WHERE` clause (see [Filters](#filters)). |
|
|
127
|
+
| `timeGrain` | `string` | no | Bucket a time dimension via `date_trunc('<grain>', …)` — e.g. `day`, `month`. Requires `timeDimension`. |
|
|
128
|
+
| `timeDimension` | `string` | no | The single dimension `timeGrain` buckets. Must be one of `dimensions`. Required whenever `timeGrain` is set. |
|
|
129
|
+
| `orderBy` | array | no | Array of `{field, direction}` sort keys (max 20). `field` must be a selected measure or dimension. `direction` is `"ASC"` (default, omitted from SQL) or `"DESC"`. Order measures by their SELECT alias. |
|
|
130
|
+
| `limit` | `number` | no | Positive integer row cap (max 100000). |
|
|
131
|
+
| `format` | `string` | no | `JSON_ARRAY` (default). `JSON` is accepted as a deprecated alias for it; Arrow formats (`ARROW`, `ARROW_STREAM`) are rejected on this route. |
|
|
130
132
|
|
|
131
133
|
Measures and dimensions must be unique across both lists — a name cannot repeat, nor appear as both a measure and a dimension.
|
|
132
134
|
|
|
@@ -140,12 +142,30 @@ SELECT MEASURE(`arr`) AS `arr`, MEASURE(`revenue`) AS `revenue`,
|
|
|
140
142
|
FROM `catalog`.`schema`.`revenue_metrics`
|
|
141
143
|
WHERE `region` IN (:f_0, :f_1)
|
|
142
144
|
GROUP BY ALL
|
|
145
|
+
ORDER BY `revenue` DESC, `order_date`, `region`
|
|
143
146
|
LIMIT 100
|
|
144
147
|
|
|
145
148
|
```
|
|
146
149
|
|
|
147
150
|
The metric view's FQN and every measure/dimension identifier are backtick-quoted; filter values are bound as parameters (`:f_0`, `:f_1`, …), never interpolated into the SQL string.
|
|
148
151
|
|
|
152
|
+
### Deterministic results with `limit`[](#deterministic-results-with-limit "Direct link to deterministic-results-with-limit")
|
|
153
|
+
|
|
154
|
+
When `limit` is set, the route automatically appends all grouped dimensions to the `ORDER BY` clause as tie-breakers (unless they are already named in `orderBy`). Under `GROUP BY ALL`, the full dimension tuple is unique per row, so ordering by all dimensions produces a **TOTAL order** — every run returns the same rows, not an arbitrary sample.
|
|
155
|
+
|
|
156
|
+
This matters because `LIMIT` without `ORDER BY` is a row *sample*, not "the top n": Spark returns whichever rows it produced first, which varies with partitioning, parallelism and cache state. A card built on such a request can show a different number run to run with nothing erroring. The tie-breakers close that gap — over unchanged data, the same request now returns the same rows.
|
|
157
|
+
|
|
158
|
+
If you want **top-N by a measure**, order that measure explicitly and provide `limit`:
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{ "orderBy": [{ "field": "revenue", "direction": "DESC" }], "limit": 100 }
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The route adds the remaining dimensions (`order_date`, `region` in the example above) after your explicit entry, so the result is stable across runs.
|
|
166
|
+
|
|
167
|
+
**Important:** order measures by their **SELECT alias**. Spark rejects `ORDER BY MEASURE(\`revenue\`)`with`METRIC\_VIEW\_INVALID\_MEASURE\_FUNCTION\_INPUT`. The generated SQL aliases every measure (e.g. `MEASURE(\`revenue\`) AS \`revenue\``), so always reference the alias — in this case, just `"revenue"\`.
|
|
168
|
+
|
|
149
169
|
### Filters[](#filters "Direct link to Filters")
|
|
150
170
|
|
|
151
171
|
`filter` is a recursive tree. A leaf is a single predicate:
|
|
@@ -279,7 +299,7 @@ If the configured SQL warehouse is `STOPPED` or `STARTING` when a query is reque
|
|
|
279
299
|
2. Poll the warehouse state and stream `warehouse_status` events over SSE until it reaches `RUNNING`.
|
|
280
300
|
3. Execute the SQL statement.
|
|
281
301
|
|
|
282
|
-
This means a cold start no longer freezes the UI on a stalled spinner.
|
|
302
|
+
This means a cold start no longer freezes the UI on a stalled spinner. Both `useAnalyticsQuery` and `useMetricView` expose the latest status for their current request through `warehouseStatus`; render it to give users feedback:
|
|
283
303
|
|
|
284
304
|
```tsx
|
|
285
305
|
import { useAnalyticsQuery } from "@databricks/appkit-ui/react";
|
|
@@ -300,7 +320,7 @@ function SpendTable() {
|
|
|
300
320
|
|
|
301
321
|
```
|
|
302
322
|
|
|
303
|
-
`warehouseStatus`
|
|
323
|
+
For both hooks, `warehouseStatus` resets to `null` when a request starts and remains there until the first status event arrives. After the server has observed the warehouse `RUNNING` once, subsequent requests within \~30s skip the readiness check entirely and `warehouseStatus` stays `null`, so the steady-state hot path isn't taxed any extra round-trips.
|
|
304
324
|
|
|
305
325
|
If the warehouse is `DELETED`/`DELETING` or fails to reach `RUNNING` within the configured timeout, the route emits an `error` event (surfaced via the `error` field).
|
|
306
326
|
|
|
@@ -327,7 +347,7 @@ export function AppShell({ children }) {
|
|
|
327
347
|
|
|
328
348
|
```
|
|
329
349
|
|
|
330
|
-
`useAnalyticsQuery`
|
|
350
|
+
`useAnalyticsQuery` and `useMetricView` register themselves with the nearest provider, so no per-chart wiring is needed. The indicator renders only the `<Toaster />` mount point while every resource is healthy; it pops a single sticky toast — `toast.loading` for cold starts, `toast.error` for unrecoverable states — keyed by the worst kind, and dismisses it when they all settle. Because the same provider is shared across resource kinds (warehouse, lakebase, model serving, …), a single indicator covers every plugin.
|
|
331
351
|
|
|
332
352
|
If you already render your own `<Toaster />` for unrelated app toasts, drop the indicator and call `useResourceStatusToaster()` instead so resource-status toasts share that single Toaster:
|
|
333
353
|
|
|
@@ -492,3 +512,258 @@ const { data } = useAnalyticsQuery("users", params);
|
|
|
492
512
|
const { data } = useAnalyticsQuery("users", { status: sql.string("active") });
|
|
493
513
|
|
|
494
514
|
```
|
|
515
|
+
|
|
516
|
+
### useMetricView[](#usemetricview "Direct link to useMetricView")
|
|
517
|
+
|
|
518
|
+
React hook that measures a [metric view](#metric-views) over SSE — the client twin of `POST /api/analytics/metric/:key`. Instead of writing SQL, you pass the measures, dimensions, and filter as a structured request; the hook streams back rows with typed column names plus per-column display metadata.
|
|
519
|
+
|
|
520
|
+
```ts
|
|
521
|
+
import { useMetricView } from "@databricks/appkit-ui/react";
|
|
522
|
+
|
|
523
|
+
const { data, loading, error, errorCode, metadata, warehouseStatus } =
|
|
524
|
+
useMetricView("revenue", {
|
|
525
|
+
measures: ["arr", "mrr"],
|
|
526
|
+
dimensions: ["created_at"],
|
|
527
|
+
timeGrain: "month",
|
|
528
|
+
timeDimension: "created_at",
|
|
529
|
+
orderBy: [{ field: "created_at", direction: "ASC" }],
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
When `"revenue"` is a key in the generated `MetricRegistry` (see [Metric-view types](./docs/development/type-generation.md#metric-view-types)), the measure/dimension names, the allowed `timeGrain` values, and the selected row keys are all inferred — passing an unknown measure is a type error. JSON\_ARRAY preserves SQL scalar cells as strings and allows SQL NULL for every column, so `data` is typed as `Array<{ arr: string | null; mrr: string | null; created_at: string | null }> | null`; use `metadata[col].type` when intentionally parsing a value.
|
|
535
|
+
|
|
536
|
+
Time-series queries should explicitly order their selected time dimension ascending, as above. SQL result order is otherwise unspecified; chart helpers may normalize chronological data defensively, but consumers should not rely on that for query ordering.
|
|
537
|
+
|
|
538
|
+
**Options:**
|
|
539
|
+
|
|
540
|
+
| Option | Type | Required | Description |
|
|
541
|
+
| --------------- | ----------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
542
|
+
| `measures` | `string[]` | yes | Measures to aggregate. Inferred from `MetricRegistry[key].measureKeys` for a known key. |
|
|
543
|
+
| `dimensions` | `string[]` | no | Dimensions to group by. Inferred from `measureKeys` / `dimensionKeys`. |
|
|
544
|
+
| `filter` | `MetricFilter` | no | Recursive predicate tree (same grammar as the route — see [Filters](#filters)). |
|
|
545
|
+
| `timeGrain` | `string` | no | Bucket a time dimension (`day`, `month`, …). Requires `timeDimension`. Inferred `timeGrains`. |
|
|
546
|
+
| `timeDimension` | `string` | no | The single dimension `timeGrain` buckets. Must be one of `dimensions`. |
|
|
547
|
+
| `orderBy` | `{field, direction?}[]` | no | Sort keys. `field` is narrowed to the measures/dimensions this call selected, so ordering by an unselected column is a type error. See [Deterministic results with `limit`](#deterministic-results-with-limit). |
|
|
548
|
+
| `limit` | `number` | no | Positive integer row cap. |
|
|
549
|
+
| `autoStart` | `boolean` | no | Start the metric query automatically. Defaults to `true`; set to `false` to defer it until the option becomes `true`. |
|
|
550
|
+
|
|
551
|
+
**Return type:**
|
|
552
|
+
|
|
553
|
+
```ts
|
|
554
|
+
{
|
|
555
|
+
data: T | null; // selected row keys with JSON_ARRAY string | null values
|
|
556
|
+
loading: boolean; // true while the metric query is executing
|
|
557
|
+
error: string | null; // sanitized human-readable message, or null on success
|
|
558
|
+
errorCode: string | null; // stable upstream code (branch on this, not the message)
|
|
559
|
+
metadata: Record<string, MetricViewColumnDisplay> | undefined; // per-column display metadata (see below)
|
|
560
|
+
warehouseStatus: WarehouseStatus | null; // latest readiness status for the current request
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
Like `useAnalyticsQuery`, the option object is serialized (`JSON.stringify`) internally, so object/array literals passed fresh each render do **not** trigger a refetch as long as they serialize to the same string — you do **not** need to `useMemo` the options. (This is same-serialization, not deep structural equality: reordering keys within `filter` changes the string and does re-query. Hoisting `measures`/`dimensions` to module scope or memoizing is still fine, and keeps the arrays type-narrowed to their literal tuple.)
|
|
566
|
+
|
|
567
|
+
`metadata` is the per-column display metadata for **only the columns you queried**, scoped and carried in the SSE `result` payload. It is `undefined` when the server resolved no metadata (the metric key is unknown, or types have not been generated) — so always treat it as optional.
|
|
568
|
+
|
|
569
|
+
### Metadata[](#metadata "Direct link to Metadata")
|
|
570
|
+
|
|
571
|
+
The metric route stamps per-column display metadata (`display_name`, `format`, `type`, `description`) onto each `result` message. This metadata is **build-generated** by the metric-view type generator, which writes it to `config/metric-views/metadata.generated.json` beside your hand-authored `definitions.json`.
|
|
572
|
+
|
|
573
|
+
**No wiring required.** The plugin discovers the bundle the same way it discovers `definitions.json`, so `analytics({})` is enough:
|
|
574
|
+
|
|
575
|
+
```ts
|
|
576
|
+
// server/index.ts
|
|
577
|
+
import { analytics, createApp, server } from "@databricks/appkit";
|
|
578
|
+
|
|
579
|
+
createApp({
|
|
580
|
+
plugins: [
|
|
581
|
+
server(),
|
|
582
|
+
analytics({}),
|
|
583
|
+
// …
|
|
584
|
+
],
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
Commit `metadata.generated.json` alongside your generated types — it is the runtime half of the same generation pass, and the route reads it from disk at request time.
|
|
590
|
+
|
|
591
|
+
This is **pure response decoration**: the metadata never enters the cache key and never changes the SQL. Every metric `result` message carries a `metadata` field scoped to the requested columns; when no bundle is present the message is byte-identical to a plain `/query` result and the hook's `metadata` is `undefined`. A missing or malformed bundle degrades to unlabeled columns and logs a warning — it never fails the query. Because the metadata rides on the payload, the client never has to import the generated file or hardcode a format string — it is **payload-carried and client-agnostic**.
|
|
592
|
+
|
|
593
|
+
To bypass the file entirely — an app that builds its metadata some other way, or pins it deliberately — pass `analytics({ metricViewsMetadata })`. An explicit value always wins over the discovered bundle.
|
|
594
|
+
|
|
595
|
+
### Format utilities[](#format-utilities "Direct link to Format utilities")
|
|
596
|
+
|
|
597
|
+
`@databricks/appkit-ui/js` ships small, pure, tree-shakeable formatters that turn raw values + the metadata above into display strings. They take the format spec (or `MetricViewColumnDisplay`) as **arguments** — no React, no chart-library coupling — so they work in tables, tooltips, and chart configs alike.
|
|
598
|
+
|
|
599
|
+
| Function | Purpose |
|
|
600
|
+
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
601
|
+
| `formatValue(value, format?)` | Format a raw value with a UC/spreadsheet format spec (`"$#,##0.00"`, `"#,##0"`, `"0.0%"`). No spec → sensible default. |
|
|
602
|
+
| `formatLabel(name, columnMeta?)` | Human label for a column: prefers `columnMeta.display_name`, else humanizes the raw name. |
|
|
603
|
+
| `toD3Format(format?)` | Split a UC format into a [d3-format](https://d3js.org/d3-format) `specifier` and literal currency `prefix`. |
|
|
604
|
+
|
|
605
|
+
The golden rule: **source the format from `metadata`, never hand-type it.** When `metadata` is `undefined`, `metadata?.[col]?.format` is `undefined` and `formatValue` degrades gracefully to a default:
|
|
606
|
+
|
|
607
|
+
```tsx
|
|
608
|
+
import { formatLabel, formatValue } from "@databricks/appkit-ui/js";
|
|
609
|
+
import { useMetricView } from "@databricks/appkit-ui/react";
|
|
610
|
+
|
|
611
|
+
function RevenueTable() {
|
|
612
|
+
const { data, metadata } = useMetricView("revenue", {
|
|
613
|
+
measures: ["arr", "mrr"],
|
|
614
|
+
dimensions: ["created_at"],
|
|
615
|
+
timeGrain: "month",
|
|
616
|
+
timeDimension: "created_at",
|
|
617
|
+
orderBy: [{ field: "created_at", direction: "ASC" }],
|
|
618
|
+
});
|
|
619
|
+
const columns = ["created_at", "arr", "mrr"] as const;
|
|
620
|
+
|
|
621
|
+
return (
|
|
622
|
+
<table>
|
|
623
|
+
<thead>
|
|
624
|
+
<tr>
|
|
625
|
+
{columns.map((col) => (
|
|
626
|
+
// Header text from display_name (or a humanized fallback).
|
|
627
|
+
<th key={col}>{formatLabel(col, metadata?.[col])}</th>
|
|
628
|
+
))}
|
|
629
|
+
</tr>
|
|
630
|
+
</thead>
|
|
631
|
+
<tbody>
|
|
632
|
+
{data?.map((row, i) => (
|
|
633
|
+
<tr key={i}>
|
|
634
|
+
{columns.map((col) => (
|
|
635
|
+
// Format string comes from metadata, never hand-typed.
|
|
636
|
+
<td key={col}>{formatValue(row[col], metadata?.[col]?.format)}</td>
|
|
637
|
+
))}
|
|
638
|
+
</tr>
|
|
639
|
+
))}
|
|
640
|
+
</tbody>
|
|
641
|
+
</table>
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
#### Feeding the format into charts[](#feeding-the-format-into-charts "Direct link to Feeding the format into charts")
|
|
648
|
+
|
|
649
|
+
Because `metadata[col].format` is just a string on the payload, the same spec drives axis ticks and tooltips in any chart library.
|
|
650
|
+
|
|
651
|
+
**AppKit charts** — pass a `valueFormatter` to the built-in chart. The second argument is the measure field, so one callback can select the catalog format for each series. The chart applies it to its built-in value axis and per-series tooltips without replacing the internal ECharts `yAxis` or `tooltip` defaults:
|
|
652
|
+
|
|
653
|
+
```tsx
|
|
654
|
+
import { formatValue } from "@databricks/appkit-ui/js";
|
|
655
|
+
import { LineChart, useMetricView } from "@databricks/appkit-ui/react";
|
|
656
|
+
|
|
657
|
+
function RevenueChart() {
|
|
658
|
+
const { data, metadata } = useMetricView("revenue", {
|
|
659
|
+
measures: ["arr", "mrr"],
|
|
660
|
+
dimensions: ["created_at"],
|
|
661
|
+
timeGrain: "month",
|
|
662
|
+
timeDimension: "created_at",
|
|
663
|
+
orderBy: [{ field: "created_at", direction: "ASC" }],
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
if (!data) return null;
|
|
667
|
+
|
|
668
|
+
return (
|
|
669
|
+
<LineChart
|
|
670
|
+
data={data}
|
|
671
|
+
xKey="created_at"
|
|
672
|
+
yKey={["arr", "mrr"]}
|
|
673
|
+
valueFormatter={(value, field) =>
|
|
674
|
+
formatValue(value, metadata?.[field]?.format)
|
|
675
|
+
}
|
|
676
|
+
/>
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
When multiple series share one value axis, its ticks use the first `yKey`; each tooltip uses the matching series field.
|
|
683
|
+
|
|
684
|
+
The `selected` prop adds declarative emphasis only to bar, pie, and donut charts. Line, area, scatter, heatmap, and radar charts ignore it because category-selection semantics are not defined for those chart types.
|
|
685
|
+
|
|
686
|
+
**[Plotly](https://plotly.com/javascript/)** — pass the numeric specifier as `tickformat` and the literal currency symbol as `tickprefix`. Keeping them separate is necessary because d3's `$` marker is locale-driven and cannot represent arbitrary symbols:
|
|
687
|
+
|
|
688
|
+
```tsx
|
|
689
|
+
import Plot from "react-plotly.js";
|
|
690
|
+
import { toD3Format } from "@databricks/appkit-ui/js";
|
|
691
|
+
import { useMetricView } from "@databricks/appkit-ui/react";
|
|
692
|
+
|
|
693
|
+
function RevenuePlot() {
|
|
694
|
+
const { data, metadata } = useMetricView("revenue", {
|
|
695
|
+
measures: ["arr"],
|
|
696
|
+
dimensions: ["created_at"],
|
|
697
|
+
timeGrain: "month",
|
|
698
|
+
timeDimension: "created_at",
|
|
699
|
+
orderBy: [{ field: "created_at", direction: "ASC" }],
|
|
700
|
+
});
|
|
701
|
+
const arrFormat = toD3Format(metadata?.arr?.format);
|
|
702
|
+
// "€#,##0.00" → { specifier: ",.2f", prefix: "€" }
|
|
703
|
+
|
|
704
|
+
return (
|
|
705
|
+
<Plot
|
|
706
|
+
data={[
|
|
707
|
+
{
|
|
708
|
+
type: "scatter",
|
|
709
|
+
mode: "lines+markers",
|
|
710
|
+
x: data?.map((r) => r.created_at) ?? [],
|
|
711
|
+
y: data?.map((r) => r.arr) ?? [],
|
|
712
|
+
name: metadata?.arr?.display_name ?? "arr",
|
|
713
|
+
},
|
|
714
|
+
]}
|
|
715
|
+
layout={{
|
|
716
|
+
yaxis: {
|
|
717
|
+
tickformat: arrFormat?.specifier,
|
|
718
|
+
tickprefix: arrFormat?.prefix,
|
|
719
|
+
},
|
|
720
|
+
hoverlabel: { namelength: -1 },
|
|
721
|
+
}}
|
|
722
|
+
/>
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
**[ECharts](https://echarts.apache.org/)** — use the format spec inside `axisLabel.formatter` / `tooltip.formatter` via `formatValue`:
|
|
729
|
+
|
|
730
|
+
```tsx
|
|
731
|
+
import ReactECharts from "echarts-for-react";
|
|
732
|
+
import { formatLabel, formatValue } from "@databricks/appkit-ui/js";
|
|
733
|
+
import { useMetricView } from "@databricks/appkit-ui/react";
|
|
734
|
+
|
|
735
|
+
function RevenueECharts() {
|
|
736
|
+
const { data, metadata } = useMetricView("revenue", {
|
|
737
|
+
measures: ["arr"],
|
|
738
|
+
dimensions: ["created_at"],
|
|
739
|
+
timeGrain: "month",
|
|
740
|
+
timeDimension: "created_at",
|
|
741
|
+
orderBy: [{ field: "created_at", direction: "ASC" }],
|
|
742
|
+
});
|
|
743
|
+
const arrFormat = metadata?.arr?.format;
|
|
744
|
+
|
|
745
|
+
const option = {
|
|
746
|
+
xAxis: { type: "category", data: data?.map((r) => r.created_at) ?? [] },
|
|
747
|
+
yAxis: {
|
|
748
|
+
type: "value",
|
|
749
|
+
axisLabel: { formatter: (v: number) => formatValue(v, arrFormat) },
|
|
750
|
+
},
|
|
751
|
+
tooltip: {
|
|
752
|
+
trigger: "axis",
|
|
753
|
+
valueFormatter: (v: number) => formatValue(v, arrFormat),
|
|
754
|
+
},
|
|
755
|
+
series: [
|
|
756
|
+
{
|
|
757
|
+
name: formatLabel("arr", metadata?.arr),
|
|
758
|
+
type: "line",
|
|
759
|
+
data: data?.map((r) => r.arr) ?? [],
|
|
760
|
+
},
|
|
761
|
+
],
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
return <ReactECharts option={option} />;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
In both cases the format string originates from the server-injected `metadata` and is never written into the component — swapping the YAML `format` attribute on the metric view re-flows every axis, tooltip, and table cell without a client change.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@databricks/appkit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.60.0",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"bin": {
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"get-port": "7.2.0",
|
|
74
74
|
"js-yaml": "4.2.0",
|
|
75
75
|
"magic-string": "0.30.21",
|
|
76
|
+
"mlflow-tracing": "0.1.3",
|
|
76
77
|
"obug": "2.1.1",
|
|
77
78
|
"pg": "8.18.0",
|
|
78
79
|
"picocolors": "1.1.1",
|