@altertable/data-app 0.62.0 → 0.64.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/AGENTS.md +4 -34
- package/CONTRIBUTING.md +79 -87
- package/README.md +14 -38
- package/dist/chunks/{contract-ryyf6dme.js → contract-cxr9t12b.js} +2 -5
- package/dist/chunks/contract-cxr9t12b.js.map +10 -0
- package/dist/chunks/contract-mev09s5v.js.map +2 -2
- package/dist/chunks/{contract-xjv197ck.js → contract-mwe7gnmh.js} +8 -8
- package/dist/chunks/{contract-xjv197ck.js.map → contract-mwe7gnmh.js.map} +3 -3
- package/dist/chunks/{contract-3bnrf5pf.js → contract-tf8c3qpv.js} +11 -3
- package/dist/chunks/contract-tf8c3qpv.js.map +11 -0
- package/dist/chunks/{contract-farfe948.js → contract-txjy0en2.js} +31 -31
- package/dist/chunks/contract-txjy0en2.js.map +10 -0
- package/dist/chunks/contract-wz59z8pq.js.map +2 -2
- package/dist/chunks/{contract-4vrw9zk9.js → contract-xtxza1fy.js} +69 -60
- package/dist/chunks/contract-xtxza1fy.js.map +12 -0
- package/dist/chunks/{contract-nt819swq.js → contract-yxbjea23.js} +13 -6
- package/dist/chunks/contract-yxbjea23.js.map +12 -0
- package/dist/chunks/{contract-rrm7s5zp.js → contract-zr3jd7mr.js} +5 -5
- package/dist/chunks/contract-zr3jd7mr.js.map +12 -0
- package/dist/client/index.js +7 -8
- package/dist/client/index.js.map +1 -1
- package/dist/core/appearance.js +4 -2
- package/dist/core/appearance.js.map +1 -1
- package/dist/core/contract.js +2 -2
- package/dist/embed/index.js +9 -11
- package/dist/embed/index.js.map +2 -2
- package/dist/local.js +7 -31
- package/dist/local.js.map +5 -7
- package/dist/react/embed/index.js +2 -3
- package/dist/react/embed/index.js.map +2 -2
- package/dist/react/index.js +5446 -230
- package/dist/react/index.js.map +71 -66
- package/dist/server.js +7 -31
- package/dist/server.js.map +5 -7
- package/dist/types/client/data-client.d.ts +33 -0
- package/dist/types/client/index.d.ts +5 -41
- package/dist/types/client/location.d.ts +4 -6
- package/dist/types/client/navigation.d.ts +2 -1
- package/dist/types/core/appearance.d.ts +12 -1
- package/dist/types/core/bridge.d.ts +2 -8
- package/dist/types/core/config.d.ts +3 -2
- package/dist/types/core/contract.d.ts +9 -56
- package/dist/types/core/format.d.ts +0 -1
- package/dist/types/core/messages.d.ts +5 -30
- package/dist/types/core/navigation.d.ts +11 -0
- package/dist/types/core/operation-types.d.ts +76 -0
- package/dist/types/core/operation.d.ts +2 -8
- package/dist/types/core/variables.d.ts +2 -2
- package/dist/types/embed/host.d.ts +5 -3
- package/dist/types/embed/index.d.ts +0 -1
- package/dist/types/embed/source.d.ts +2 -9
- package/dist/types/react/content.d.ts +3 -3
- package/dist/types/react/hooks.d.ts +65 -64
- package/dist/types/react/index.d.ts +135 -2
- package/dist/types/react/injectStyles.d.ts +7 -0
- package/dist/types/react/shellStyles.d.ts +3 -0
- package/dist/types/react/styles.d.ts +8 -0
- package/dist/types/react/ui/AboutData.d.ts +0 -1
- package/dist/types/react/ui/AppFooter.d.ts +0 -1
- package/dist/types/react/ui/AppHeader.d.ts +0 -1
- package/dist/types/react/ui/AppLayout.d.ts +0 -1
- package/dist/types/react/ui/AppScope.d.ts +0 -1
- package/dist/types/react/ui/AppToolbar.d.ts +0 -1
- package/dist/types/react/ui/Breakdown.d.ts +0 -1
- package/dist/types/react/ui/Button.d.ts +0 -1
- package/dist/types/react/ui/Checkbox.d.ts +0 -1
- package/dist/types/react/ui/Combobox.d.ts +0 -1
- package/dist/types/react/ui/ComparisonVisual.d.ts +0 -1
- package/dist/types/react/ui/ContentSkeleton.d.ts +2 -5
- package/dist/types/react/ui/DataApp.d.ts +2 -2
- package/dist/types/react/ui/DataAppSkeleton.d.ts +0 -1
- package/dist/types/react/ui/DataBoundary.d.ts +0 -1
- package/dist/types/react/ui/DataSection.d.ts +2 -4
- package/dist/types/react/ui/DataTable.d.ts +2 -3
- package/dist/types/react/ui/DataViewToast.d.ts +0 -1
- package/dist/types/react/ui/DataWidget.d.ts +4 -14
- package/dist/types/react/ui/DateRangePicker.d.ts +0 -1
- package/dist/types/react/ui/DateTimeTooltip.d.ts +0 -1
- package/dist/types/react/ui/EmptyState.d.ts +2 -5
- package/dist/types/react/ui/GettingStarted.d.ts +0 -1
- package/dist/types/react/ui/GlossaryDefinition.d.ts +0 -1
- package/dist/types/react/ui/GlossaryExplanation.d.ts +0 -1
- package/dist/types/react/ui/GradientScroll.d.ts +0 -1
- package/dist/types/react/ui/Grid.d.ts +0 -1
- package/dist/types/react/ui/HelpPopover.d.ts +0 -2
- package/dist/types/react/ui/Kbd.d.ts +0 -1
- package/dist/types/react/ui/LiveControl.d.ts +0 -1
- package/dist/types/react/ui/MetricWidget.d.ts +0 -2
- package/dist/types/react/ui/PeriodSummary.d.ts +0 -1
- package/dist/types/react/ui/PresentStory.d.ts +0 -1
- package/dist/types/react/ui/QueryList.d.ts +0 -1
- package/dist/types/react/ui/Ranking.d.ts +0 -1
- package/dist/types/react/ui/RefreshControl.d.ts +0 -1
- package/dist/types/react/ui/RefreshRegion.d.ts +0 -1
- package/dist/types/react/ui/RequestHint.d.ts +0 -1
- package/dist/types/react/ui/SearchField.d.ts +0 -1
- package/dist/types/react/ui/SearchInput.d.ts +0 -1
- package/dist/types/react/ui/SearchMatch.d.ts +0 -1
- package/dist/types/react/ui/SelectableBarChart.d.ts +0 -1
- package/dist/types/react/ui/SelectionMark.d.ts +0 -1
- package/dist/types/react/ui/Sheet.d.ts +0 -1
- package/dist/types/react/ui/Skeleton.d.ts +0 -1
- package/dist/types/react/ui/Stack.d.ts +0 -1
- package/dist/types/react/ui/StatusPanel.d.ts +0 -1
- package/dist/types/react/ui/TableWidget.d.ts +2 -3
- package/dist/types/react/ui/Tabs.d.ts +0 -1
- package/dist/types/react/ui/TextContent.d.ts +4 -0
- package/dist/types/react/ui/TextWidget.d.ts +19 -0
- package/dist/types/react/ui/ThemeSelector.d.ts +0 -1
- package/dist/types/react/ui/Tooltip.d.ts +0 -1
- package/dist/types/react/ui/UpdatedAt.d.ts +0 -1
- package/dist/types/react/ui/VariableBar.d.ts +0 -1
- package/dist/types/react/ui/VisualizationWidget.d.ts +5 -13
- package/dist/types/react/ui/WidgetDisclosure.d.ts +0 -1
- package/dist/types/react/ui/WidgetViewTabs.d.ts +2 -3
- package/dist/types/react/ui/data-identifiers.d.ts +0 -1
- package/dist/types/react/ui/presentation.d.ts +19 -0
- package/dist/types/react/ui/useAppAppearance.d.ts +2 -2
- package/dist/types/react/view-controls.d.ts +1 -1
- package/dist/types/react/view.d.ts +2 -2
- package/dist/worker.js +1 -0
- package/docs/app-authoring.md +61 -44
- package/docs/client.md +11 -19
- package/docs/contract.md +9 -9
- package/docs/embed.md +10 -29
- package/docs/hosted-apps.md +39 -0
- package/docs/local-data-apps.md +14 -0
- package/docs/react-embed.md +10 -19
- package/docs/react.md +108 -134
- package/docs/server-bun.md +4 -6
- package/docs/server.md +2 -2
- package/docs/worker.md +3 -5
- package/examples/starter-data-app/index.tsx +159 -0
- package/package.json +16 -10
- package/dist/chunks/contract-3bnrf5pf.js.map +0 -10
- package/dist/chunks/contract-4vrw9zk9.js.map +0 -12
- package/dist/chunks/contract-8q35dcyh.js +0 -9
- package/dist/chunks/contract-8q35dcyh.js.map +0 -10
- package/dist/chunks/contract-farfe948.js.map +0 -10
- package/dist/chunks/contract-nt819swq.js.map +0 -11
- package/dist/chunks/contract-rrm7s5zp.js.map +0 -12
- package/dist/chunks/contract-ryyf6dme.js.map +0 -10
- package/dist/react/index.css +0 -4628
- package/dist/react.css.d.ts +0 -1
- package/dist/types/react/ui/index.d.ts +0 -131
- package/docs/appearance.md +0 -43
- package/docs/config.md +0 -21
- package/docs/format.md +0 -29
- package/docs/react-styles.md +0 -17
- package/docs/starter-agent-instructions.md +0 -53
package/docs/react-embed.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# React embedding
|
|
2
2
|
|
|
3
|
-
Import
|
|
3
|
+
Import `<DataAppBridge>` from `@altertable/data-app/react/embed`. This entry depends
|
|
4
4
|
on React and the embedding engine, and does not load the app's widgets, React
|
|
5
5
|
Query, or CSS.
|
|
6
6
|
|
|
7
|
-
The bridge owns iframe setup and `postMessage` communication. The consuming
|
|
7
|
+
The bridge owns iframe setup and `postMessage()` communication. The consuming
|
|
8
8
|
frontend or CLI owns its shell: fetching a bundle, subscriptions, layout, loading
|
|
9
9
|
and error UI, and retry controls. Both hosts use the same bridge and transport.
|
|
10
10
|
|
|
@@ -63,7 +63,7 @@ including `className`, `style`, and `hidden`. The bridge controls `src`, `srcDoc
|
|
|
63
63
|
|
|
64
64
|
## Host-owned iframe
|
|
65
65
|
|
|
66
|
-
Use the same
|
|
66
|
+
Use the same `<DataAppBridge>` with `iframe` and `connection` when the host already
|
|
67
67
|
owns a loaded iframe and its security policy:
|
|
68
68
|
|
|
69
69
|
```tsx
|
|
@@ -91,27 +91,18 @@ mode renders nothing and handles delivery only. Use source mode for bundle
|
|
|
91
91
|
loading, sandbox policy, token rotation, and startup timeout. The two prop modes
|
|
92
92
|
are mutually exclusive.
|
|
93
93
|
|
|
94
|
-
##
|
|
94
|
+
## Loading an embedded app
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
`
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
fetching and subscriptions and CLI local-server forwarding remain host concerns.
|
|
101
|
-
|
|
102
|
-
## Loading placeholder
|
|
103
|
-
|
|
104
|
-
For a shared placeholder while building or connecting, render `DataAppSkeleton`
|
|
105
|
-
from `@altertable/data-app/react` in the consuming shell. Import
|
|
106
|
-
`@altertable/data-app/react/styles.css` once in that host's browser entry. See
|
|
107
|
-
[React](react.md#loading-an-embedded-app) for usage. The bridge does not render
|
|
108
|
-
loading UI itself.
|
|
96
|
+
Use `<DataAppSkeleton>` from `/react` while the host builds or starts an app.
|
|
97
|
+
Call `injectDataAppShellStyles()` from `/react` before rendering the placeholder.
|
|
98
|
+
The host owns when to show it and supplies any surrounding header or footer.
|
|
99
|
+
`/react/embed` itself remains independent of UI components and styles.
|
|
109
100
|
|
|
110
101
|
## Parent-owned presentation
|
|
111
102
|
|
|
112
103
|
Pass `presentation={{ surface: 'embedded', theme: resolvedTheme }}`
|
|
113
|
-
to
|
|
104
|
+
to `<DataAppBridge>` in either source or connection mode. Use `'standalone'` when the app should render its own page chrome.
|
|
114
105
|
Prop updates publish trusted state without reloading the iframe or reconnecting
|
|
115
106
|
the session. Resolve system preference in the parent to `'light'` or `'dark'`.
|
|
116
|
-
Inside an embedded surface,
|
|
107
|
+
Inside an embedded surface, `<DataApp>` retains toolbar actions and hides its header
|
|
117
108
|
and footer. See [parent presentation](embed.md#parent-presentation).
|
package/docs/react.md
CHANGED
|
@@ -1,75 +1,26 @@
|
|
|
1
1
|
# React
|
|
2
2
|
|
|
3
3
|
Import hooks, components, and UI helpers from `@altertable/data-app/react`.
|
|
4
|
-
Import `@altertable/data-app/react/styles.css` once in the browser entry.
|
|
5
4
|
React 19.2 or newer and React DOM 19.2 or newer are peer dependencies.
|
|
6
5
|
|
|
6
|
+
## Mount the app
|
|
7
|
+
|
|
7
8
|
`mountDataApp({ config, component })` mounts into `#root`, sets the document
|
|
8
9
|
title and language, attaches navigation to an available iframe transport, and
|
|
9
|
-
installs
|
|
10
|
-
framework, wrap the app in
|
|
11
|
-
|
|
12
|
-
## Find UI by task
|
|
13
|
-
|
|
14
|
-
All of these APIs are exported from `/react`. Each component's stylesheet lives beside its implementation.
|
|
15
|
-
|
|
16
|
-
| Task | Start here | Related APIs |
|
|
17
|
-
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
18
|
-
| Primary request and page shell | [DataApp](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/DataApp.tsx) | AppLayout, AppHeader, AppToolbar, AppFooter, AppScope, ThemeToggle |
|
|
19
|
-
| Initial connection check | [GettingStarted](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/GettingStarted.tsx) | Pair with `connectionCheck()` from `/contract` |
|
|
20
|
-
| Arrange content | [Grid](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/Grid.tsx), [Stack](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/Stack.tsx) | DataWidget |
|
|
21
|
-
| Show a key number | [MetricWidget](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/MetricWidget.tsx) | ComparisonVisual |
|
|
22
|
-
| Show charts and collections | [VisualizationWidget](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/VisualizationWidget.tsx), [TableWidget](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/TableWidget.tsx) | DataTable, Ranking, Breakdown, chartColor |
|
|
23
|
-
| Handle a request's loading, error, and stale data | [DataSection](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/DataSection.tsx) | DataBoundary, DataViewToast, EmptyState, StatusPanel, Skeleton |
|
|
24
|
-
| Show freshness and refresh | [UpdatedAt](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/UpdatedAt.tsx), [AppToolbar](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/AppToolbar.tsx) | RefreshRegion, LiveControl |
|
|
25
|
-
| Bind filters to the URL | [variables](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/variables.ts), [DateRangePicker](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/DateRangePicker.tsx) | Combobox, PeriodSummary, Tabs, useViewTab |
|
|
26
|
-
| Search a loaded collection | [searchItems](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/searchItems.ts), [SearchMatch](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/SearchMatch.tsx) | SearchField |
|
|
27
|
-
| Explain context, glossary, and queries | [AboutData](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/AboutData.tsx), [DataContext](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/data-context.ts) | [GlossaryDefinition](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/GlossaryDefinition.tsx), GlossaryExplanation, [defineDataIdentifiers](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/data-identifiers.tsx) |
|
|
28
|
-
| Present loaded findings | [PresentStory](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/PresentStory.tsx) | StoryFinding |
|
|
29
|
-
| Build custom controls and overlays | [Button](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/Button.tsx), [Sheet](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/Sheet.tsx) | IconButton, Tooltip, HelpPopover, Kbd |
|
|
30
|
-
|
|
31
|
-
## Loading an embedded app
|
|
32
|
-
|
|
33
|
-
`DataAppSkeleton` is a host-side placeholder while an embedded app is building or
|
|
34
|
-
starting. It composes the shared metric, visualization, and data widgets with
|
|
35
|
-
skeleton content: three metric cards, a chart, and table rows. Optional `header`
|
|
36
|
-
and `footer` React nodes let the host supply its own layout; neither renders by
|
|
37
|
-
default.
|
|
38
|
-
It adapts to container width and inherits the package's appearance variables and
|
|
39
|
-
reduced-motion skeleton animation.
|
|
10
|
+
installs `<DataAppProvider>`. When mounting through another
|
|
11
|
+
framework, wrap the app in `<DataAppProvider>` yourself.
|
|
40
12
|
|
|
41
13
|
```tsx
|
|
42
|
-
import {
|
|
43
|
-
import '@altertable/data-app/react/styles.css';
|
|
44
|
-
|
|
45
|
-
<DataAppSkeleton
|
|
46
|
-
aria-label="Loading activity report"
|
|
47
|
-
className="app-loading"
|
|
48
|
-
header={<ReportHeaderSkeleton />}
|
|
49
|
-
footer={<ReportFooterSkeleton />}
|
|
50
|
-
/>;
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
The container announces a loading status; its widget placeholders are hidden from
|
|
54
|
-
assistive technology. Supplied header and footer nodes remain accessible and can
|
|
55
|
-
use the exported `Skeleton` component for their own placeholders. Standard output props, including `style` and `ref`, can be used
|
|
56
|
-
for layout. The consuming shell controls when to display it. The `/react/embed`
|
|
57
|
-
entry remains independent of UI components and the stylesheet.
|
|
58
|
-
|
|
59
|
-
## Bound views and widgets
|
|
14
|
+
import { injectDataAppStyles, mountDataApp } from '@altertable/data-app/react';
|
|
60
15
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
| `defineDataView` | URL variables, operation input, emptiness and the primary date binding. `useView` connects the result and controls to `DataApp`. |
|
|
65
|
-
| `DataApp` | Header, variable bar, refresh state, stale-result notice and dimming, default inspection empty states. |
|
|
66
|
-
| `view.content` | One loading/ready layout. `result.select` never evaluates loading data; `result.metric` binds comparisons to the displayed input. |
|
|
67
|
-
| `context.metric` | Label, numeric format, glossary evidence and optional direction of improvement. |
|
|
68
|
-
| `WidgetViewTabs` | Valid, unique selection IDs and a required empty state per tab. |
|
|
69
|
-
|
|
70
|
-
SQL and business definitions belong to the app. Hosted adapters authorize every request; local development can use the CLI proxy. SQL disclosure also requires server permission.
|
|
16
|
+
injectDataAppStyles();
|
|
17
|
+
mountDataApp({ config, component: App });
|
|
18
|
+
```
|
|
71
19
|
|
|
72
|
-
|
|
20
|
+
Importing the package does not inject styles. For server rendering, call the
|
|
21
|
+
injector on the client before mounting or hydrating. With a restrictive CSP,
|
|
22
|
+
pass a permitted nonce; the first call sets it for the document. No CSS loader
|
|
23
|
+
or separate stylesheet asset is needed.
|
|
73
24
|
|
|
74
25
|
## Bind a view
|
|
75
26
|
|
|
@@ -86,7 +37,7 @@ import {
|
|
|
86
37
|
} from '@altertable/data-app/react';
|
|
87
38
|
import type { operations } from '#app/operations.ts';
|
|
88
39
|
import { calendar } from '#app/contracts.ts';
|
|
89
|
-
import { dataContext,
|
|
40
|
+
import { dataContext, trackedIdentities } from '#app/data-context.tsx';
|
|
90
41
|
import config from '#config';
|
|
91
42
|
|
|
92
43
|
const period = dateRangeVariable({
|
|
@@ -112,7 +63,7 @@ const featureEvidence = dataContext.evidence({
|
|
|
112
63
|
const content = activityView.content(result => (
|
|
113
64
|
<Grid columns={2}>
|
|
114
65
|
<MetricWidget
|
|
115
|
-
metric={
|
|
66
|
+
metric={trackedIdentities}
|
|
116
67
|
reading={result.metric(data => ({
|
|
117
68
|
current: data.count,
|
|
118
69
|
previous: data.previousCount,
|
|
@@ -145,33 +96,62 @@ function App() {
|
|
|
145
96
|
|
|
146
97
|
The `date` binding identifies the controlling variable and extracts its range from the operation input. Nested inputs use, for example, `input: (input) => input.period`. The runtime rejects mappings that silently change the selected range or comparison. Non-date views supply `describeInput`; date views can override it when other inputs also need describing.
|
|
147
98
|
|
|
148
|
-
|
|
99
|
+
Share the [date range contract](contract.md#shared-date-ranges) between the
|
|
100
|
+
operation parser and the view.
|
|
149
101
|
|
|
150
|
-
|
|
151
|
-
import { defineDateRangeContract } from '@altertable/data-app/contract';
|
|
152
|
-
export const calendar = defineDateRangeContract({
|
|
153
|
-
minDate: '2026-01-01',
|
|
154
|
-
maxRangeDays: 90,
|
|
155
|
-
timeZone: 'UTC',
|
|
156
|
-
});
|
|
157
|
-
// Server operation: input: calendar.parseRequest
|
|
158
|
-
```
|
|
102
|
+
`useView()` generates controls for date, text and fixed-option select variables; custom controls use `result.variables.bind(name)`. `input` chooses which variables reach the operation, so local search can stay local. Hooks belong in the enclosing component.
|
|
159
103
|
|
|
160
|
-
|
|
104
|
+
For large tables, use query-backed pagination with a stable sort and total
|
|
105
|
+
count. Client pagination and search cover only the rows already returned.
|
|
161
106
|
|
|
162
|
-
|
|
107
|
+
Bound `<VisualizationWidget>` and `<TableWidget>` components require `evidence` from `context.evidence(...)`. A bound `<MetricWidget>` gets evidence from its metric definition. Evidence must name at least one glossary entry or query. Static widgets may omit it.
|
|
163
108
|
|
|
164
|
-
|
|
109
|
+
`<MetricWidget>` and `<ComparisonVisual>` both accept the same `metric` and `reading`. The comparison is enabled by the displayed result's range. The definition supplies formatting and evidence; a reading cannot override those or provide a second value. `favorableDirection` is optional; changes are neutral until the author defines whether up or down is favorable.
|
|
165
110
|
|
|
166
|
-
|
|
111
|
+
Use the [app helpers](#reuse-app-helpers) for metric formats and values in tables,
|
|
112
|
+
charts, and custom views.
|
|
167
113
|
|
|
168
|
-
`defineDataContent` remains available for manually managed requests. Its optional `{ date: (input) => rangeRequest }` binds comparison readings.
|
|
114
|
+
`defineDataContent()` remains available for manually managed requests. Its optional `{ date: (input) => rangeRequest }` binds comparison readings. `<DataSection>` handles independent requests. Low-level widgets, tabs and layout components remain available for custom interfaces.
|
|
169
115
|
|
|
170
|
-
##
|
|
116
|
+
## Narrative text
|
|
117
|
+
|
|
118
|
+
Use `<TextContent>` for prose within a page or custom layout, and `<TextWidget>`
|
|
119
|
+
when the explanation belongs in a titled panel alongside other widgets.
|
|
120
|
+
|
|
121
|
+
Give text a purpose: frame the question, explain how to interpret a comparison,
|
|
122
|
+
qualify a finding, or suggest what to explore next. Choose the content for the
|
|
123
|
+
reader's question and the decisions the exploration supports.
|
|
124
|
+
|
|
125
|
+
For data-dependent text, use `reading={result.select((data, input) => ...)}` and
|
|
126
|
+
provide `evidence`. Derive both the explanation and its scope from those displayed
|
|
127
|
+
values so it stays consistent with the visualizations while filters change.
|
|
128
|
+
Static instructions can use ordinary children; local filters should feed the same
|
|
129
|
+
filtered data to the text and its related visualization.
|
|
130
|
+
|
|
131
|
+
## Reuse app helpers
|
|
132
|
+
|
|
133
|
+
Use the shared helpers for common app tasks. Follow the entry points below to
|
|
134
|
+
find their exports, types, and usage constraints.
|
|
135
|
+
|
|
136
|
+
| Task | Entry point |
|
|
137
|
+
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
138
|
+
| Format numbers, counts, percentages, currency, date ranges, and plural labels (`pluralize`) | [Format helpers](https://github.com/altertable-ai/data-app/blob/main/src/core/format.ts) |
|
|
139
|
+
| Choose chart colors | [Chart colors](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/chartColor.ts) |
|
|
140
|
+
| Search items and highlight matches | [Search helpers](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/searchItems.ts) |
|
|
141
|
+
| Read and synchronize URL query state | [URL state helpers](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/search.ts) |
|
|
142
|
+
| Render timestamps, freshness, table shares, periods, and metric comparisons | [React exports](https://github.com/altertable-ai/data-app/blob/main/src/react/index.ts) |
|
|
143
|
+
| Configure appearance and theme preferences | [Appearance helpers](https://github.com/altertable-ai/data-app/blob/main/src/core/appearance.ts) |
|
|
144
|
+
| Build the app's scoped document title | [Configuration helpers](https://github.com/altertable-ai/data-app/blob/main/src/core/config.ts) |
|
|
145
|
+
|
|
146
|
+
## Register source identifiers
|
|
147
|
+
|
|
148
|
+
Use `defineDataIdentifiers()` from `/react` to register exact catalog, schema,
|
|
149
|
+
table, and field names. Use `<DataIdentifier>` in descriptions and glossary
|
|
150
|
+
entries to render those source references consistently.
|
|
171
151
|
|
|
172
152
|
```tsx
|
|
173
|
-
|
|
174
|
-
|
|
153
|
+
import { defineDataIdentifiers } from '@altertable/data-app/react';
|
|
154
|
+
|
|
175
155
|
const identifiers = defineDataIdentifiers({
|
|
176
156
|
tables: {
|
|
177
157
|
events: {
|
|
@@ -183,6 +163,20 @@ const identifiers = defineDataIdentifiers({
|
|
|
183
163
|
columns: { identity: { table: 'events', name: 'identity_uuid' } },
|
|
184
164
|
});
|
|
185
165
|
const { DataIdentifier } = identifiers;
|
|
166
|
+
|
|
167
|
+
<DataIdentifier id="tables.events" />;
|
|
168
|
+
<DataIdentifier id="columns.events.identity" />;
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Pass `identifiers.definitions` to `createDataContext()` so the context carries the
|
|
172
|
+
same source registry. Source identifiers name physical data; glossary entries
|
|
173
|
+
explain its business meaning, and query evidence records how it was queried.
|
|
174
|
+
|
|
175
|
+
## Bind evidence
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
const queries = defineQueryNames({ activity: 'feature-activity' });
|
|
179
|
+
// In the server operation: queryNames: queries
|
|
186
180
|
const context = createDataContext(queries)({
|
|
187
181
|
identifiers: identifiers.definitions,
|
|
188
182
|
description: (
|
|
@@ -207,7 +201,7 @@ const evidence = context.evidence({
|
|
|
207
201
|
glossaryIds: ['identities'],
|
|
208
202
|
queryNames: [queries.activity],
|
|
209
203
|
});
|
|
210
|
-
const
|
|
204
|
+
const trackedIdentities = context.metric({
|
|
211
205
|
id: 'actions',
|
|
212
206
|
glossaryId: 'identities',
|
|
213
207
|
label: 'Tracked identities',
|
|
@@ -221,67 +215,27 @@ const finding = context.finding({
|
|
|
221
215
|
});
|
|
222
216
|
```
|
|
223
217
|
|
|
224
|
-
Import `defineQueryNames` from `/contract` and the context/identifier factories from `/react`. Use the same registry in `defineOperation({ queryNames: queries, ... })`. Unknown glossary/query references fail type checks and registry validation; the server also validates returned query names.
|
|
218
|
+
Import `defineQueryNames()` from `/contract` and the context/identifier factories from `/react`. Use the same registry in `defineOperation({ queryNames: queries, ... })`. Unknown glossary/query references fail type checks and registry validation; the server also validates returned query names.
|
|
225
219
|
|
|
226
|
-
##
|
|
220
|
+
## Time views and field filters
|
|
227
221
|
|
|
228
|
-
|
|
229
|
-
- Numeric metrics use `value={count} format={{ kind: "count" }}`. Custom formatted JSX or strings use `content={...}` instead of `value`.
|
|
230
|
-
- Supply `empty` to secondary `DataSection` requests or pass a bound `useView` result. A primary `DataApp` accepts it either from `useView` or as an explicit prop.
|
|
231
|
-
- For alternate views of the same bound result, pass `views={[{ id, label, render }]}` and `viewLabel` to `VisualizationWidget`. Its required `isEmpty` and `empty` apply to the whole result; the widget owns selection. Use `WidgetViewTabs` directly only when views have independent empty states.
|
|
232
|
-
- Variable URL keys cannot use `view`, `about`, `tab`, `present`, or `step`.
|
|
233
|
-
|
|
234
|
-
## Time views and dimension filters
|
|
235
|
-
|
|
236
|
-
`createDataHooks(client).defineTimeView` owns the `period` variable, calendar
|
|
222
|
+
`createDataHooks(client).defineTimeView()` owns the `period` variable, calendar
|
|
237
223
|
controls, and displayed-period label. Declare `time: { contract, defaultValue }`,
|
|
238
224
|
an operation, `isEmpty`, and `empty`. With no additional variables, its default
|
|
239
225
|
input is the calendar request. With additional variables, it is `{ period, ...variables }`.
|
|
240
226
|
Supply an `input` mapper for a different operation shape and `bindings` to extract
|
|
241
|
-
nested period or
|
|
227
|
+
nested period or field-filter inputs. Mappings must preserve the selected values.
|
|
242
228
|
|
|
243
|
-
`dimensionFilter` requires exactly one option source: fixed `options` or a `facet`.
|
|
244
|
-
Use `defineFacetFilter` to bind a facet operation and its typed input. The generated
|
|
245
|
-
|
|
229
|
+
`dimensionFilter()` from `/contract` requires exactly one option source: fixed `options` or a `facet`.
|
|
230
|
+
Use `defineFacetFilter()` to bind a facet operation and its typed input. The generated
|
|
231
|
+
`<DimensionPicker>` preserves cached options during refresh and failure, offers
|
|
246
232
|
missing values separately, and retains selected values absent from a result with
|
|
247
|
-
zero counts.
|
|
248
|
-
|
|
249
|
-
## Findings and inspection
|
|
250
|
-
|
|
251
|
-
`DataWidget` composes a body, toolbar feedback, and footer. Widgets and their
|
|
252
|
-
inspection sheets render the same visual and controls. Keep interactive state
|
|
253
|
-
above both mounts when authoring custom children.
|
|
254
|
-
|
|
255
|
-
Widget headings open inspection when evidence is available; a disclosure arrow
|
|
256
|
-
appears beside the heading on hover or keyboard focus. The top-end toolbar is
|
|
257
|
-
reserved for actions and request status. Empty widgets omit their footer, charts
|
|
258
|
-
omit interaction instructions without data, and table pagination appears only
|
|
259
|
-
when there is more than one page. Local `DataBoundary` and `DataSection` inline
|
|
260
|
-
notices sit above retained section content; use widget status for widget feedback
|
|
261
|
-
and page controls or notices for page feedback.
|
|
262
|
-
|
|
263
|
-
Combobox search text aligns with option labels. Empty search inputs blur on
|
|
264
|
-
Escape; a second Escape dismisses an open picker. Picker failures show a centered
|
|
265
|
-
message with the retry action below it.
|
|
233
|
+
zero counts. `<SelectableBarChart>` can share controlled selection with the picker.
|
|
266
234
|
|
|
267
|
-
|
|
268
|
-
during refresh or failure. Return one to four `StoryFinding` values with unique
|
|
269
|
-
IDs and registered evidence. `PresentStory` presents those findings directly;
|
|
270
|
-
`context.finding` validates their evidence against the context registry.
|
|
235
|
+
## Preserve displayed results
|
|
271
236
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
- `DataWidget` replaces the internal `DataPanel` shell and `StorySection` layout.
|
|
275
|
-
- `PresentStory` replaces `PlayStory`; provide `findings` with explicit `evidence`.
|
|
276
|
-
- `context.finding` replaces `context.storyStep`.
|
|
277
|
-
- Table pagination is enabled by default; use `pagination={false}` for complete tables.
|
|
278
|
-
|
|
279
|
-
## Component gallery
|
|
280
|
-
|
|
281
|
-
Contributors can preview `/gallery` on the browser fixture server with
|
|
282
|
-
`bun browser-tests/server.ts`. The gallery covers control, widget, request,
|
|
283
|
-
inspection, and narrow-layout defaults. `bun run test:browser` verifies desktop
|
|
284
|
-
and phone interactions. Fixtures are excluded from the published package.
|
|
237
|
+
The callback in `view.content()` receives the displayed result and its original
|
|
238
|
+
input during refreshes and failures. Use that input when labeling the data.
|
|
285
239
|
|
|
286
240
|
Previous data and evidence are retained only when the input changes within the
|
|
287
241
|
same operation. Switching to another operation shows its own cached response or
|
|
@@ -292,8 +246,28 @@ from the same client share queries; separate clients do not share results, stale
|
|
|
292
246
|
data, or cancellation even under one provider. Keep client instances stable
|
|
293
247
|
across renders to preserve their cache.
|
|
294
248
|
|
|
249
|
+
## Findings and inspection
|
|
250
|
+
|
|
251
|
+
`<DataWidget>` composes a body, toolbar feedback, and footer. Widgets and their
|
|
252
|
+
inspection sheets render the same visual and controls. Keep interactive state
|
|
253
|
+
above both mounts when authoring custom children.
|
|
254
|
+
|
|
295
255
|
When a trusted parent supplies [parent presentation](embed.md#parent-presentation),
|
|
296
|
-
|
|
256
|
+
`<DataApp>` follows its resolved theme and suppresses local theme controls,
|
|
297
257
|
including in presentations. On an embedded surface (`surface: 'embedded'`),
|
|
298
258
|
only toolbar actions remain above the app body; the title, scope, description, and
|
|
299
259
|
footer are omitted. Variables and request states remain available.
|
|
260
|
+
|
|
261
|
+
## Present data with stories
|
|
262
|
+
|
|
263
|
+
Stories turn the exploration's findings into a presentation. Set the `story` prop on `<DataApp>`
|
|
264
|
+
to enable **Present story** in the toolbar; use `<PresentStory>` directly for a
|
|
265
|
+
custom shell. Start from the [data app starter](../examples/starter-data-app/index.tsx).
|
|
266
|
+
|
|
267
|
+
Return one to four findings with a headline, a visual, and registered source
|
|
268
|
+
evidence. Use `context.finding()` to bind the evidence.
|
|
269
|
+
|
|
270
|
+
The callback receives the displayed data and its original input, including
|
|
271
|
+
during refresh or failure. Derive the story from that snapshot so it agrees with
|
|
272
|
+
the visible exploration. Initial loading, empty results, and initial errors have
|
|
273
|
+
no story to present.
|
package/docs/server-bun.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# Local Bun server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use this Bun server for local data apps. Start from the CLI scaffold or
|
|
4
|
+
[local data app starter](https://github.com/altertable-ai/data-app/tree/main/examples/starter-local-data-app).
|
|
5
|
+
|
|
6
|
+
Import `serveLocalApp()` and `localLakehouse()` from
|
|
4
7
|
`@altertable/data-app/server/bun`. This entry requires Bun; install `@types/bun`
|
|
5
8
|
when typechecking a Bun app.
|
|
6
9
|
|
|
@@ -24,8 +27,3 @@ Missing credentials fail the query. Keep these variables out of browser code.
|
|
|
24
27
|
Local serving permits SQL disclosure and does not authenticate hosted viewers.
|
|
25
28
|
Use [the portable server handler](server.md) with per-request authorization when
|
|
26
29
|
hosting an app for other people.
|
|
27
|
-
|
|
28
|
-
The local adapter validates NDJSON metadata, optional string or typed column
|
|
29
|
-
headers, and array rows before returning a result. Query IDs must be strings.
|
|
30
|
-
Rows must match the column count when a header is present; headerless array rows
|
|
31
|
-
remain supported with empty column metadata. Empty results remain valid.
|
package/docs/server.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Server
|
|
2
2
|
|
|
3
|
-
Import `createDataHandler` and the `RequestAccess` type from
|
|
3
|
+
Import `createDataHandler()` and the `RequestAccess` type from
|
|
4
4
|
`@altertable/data-app/server`. The handler accepts a Web `Request` and returns a
|
|
5
5
|
`Promise<Response>`. This entry can be imported in Node and Bun without loading
|
|
6
6
|
React or the Bun local-development adapter.
|
|
@@ -21,7 +21,7 @@ export const handleDataRequest = createDataHandler(
|
|
|
21
21
|
);
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
The app supplies `authenticate` and `lakehouseFor`, then routes `/api/data/*`
|
|
24
|
+
The app supplies `authenticate()` and `lakehouseFor()`, then routes `/api/data/*`
|
|
25
25
|
requests to the handler. Authorize every viewer and operation, and scope the
|
|
26
26
|
returned lakehouse to the viewer's permitted data. Origin and Fetch Metadata
|
|
27
27
|
checks reject cross-site browser requests; they do not authenticate viewers.
|
package/docs/worker.md
CHANGED
|
@@ -41,10 +41,8 @@ return 403. Missing or invalid origin configuration returns 503; an absent
|
|
|
41
41
|
selection requires an exact default origin. Referrers and messages never supply
|
|
42
42
|
trust.
|
|
43
43
|
|
|
44
|
-
The
|
|
45
|
-
|
|
46
|
-
`no-referrer`, and `nosniff`. App scripts load through the authenticated
|
|
47
|
-
`altertable:data-app` bridge and retain opaque host state. Apps own styling and
|
|
44
|
+
The Worker enforces a restrictive CSP with configured `frame-ancestors`,
|
|
45
|
+
`no-referrer`, and `nosniff`. App scripts load through the authenticated bridge. Apps own styling and
|
|
48
46
|
navigation. Bundle apps execute their operation registry in the browser and send
|
|
49
47
|
SQL through the [host query route](embed.md#sql-query-route); the Worker does not
|
|
50
48
|
resolve operation names or execute queries. Data requests require backend authorization.
|
|
@@ -53,5 +51,5 @@ If Terraform reads a file, copy the resolved `/worker` asset unchanged to that
|
|
|
53
51
|
file during deployment preparation. Configure `DOMAIN_NAME`, `PARENT_ORIGINS`,
|
|
54
52
|
domains, routes, and deployment settings in Terraform. Upgrade the host package
|
|
55
53
|
and Worker together for protocol changes.
|
|
56
|
-
Custom hosts that bundle their own bootstrap can use `startDataAppBootstrap` from
|
|
54
|
+
Custom hosts that bundle their own bootstrap can use `startDataAppBootstrap()` from
|
|
57
55
|
[/embed](embed.md#trusted-bootstrap).
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { createDataClient } from '@altertable/data-app/client';
|
|
2
|
+
import type { DataAppConfig } from '@altertable/data-app/config';
|
|
3
|
+
import {
|
|
4
|
+
defineOperation,
|
|
5
|
+
defineQueryNames,
|
|
6
|
+
parseCount,
|
|
7
|
+
} from '@altertable/data-app/contract';
|
|
8
|
+
import {
|
|
9
|
+
createDataContext,
|
|
10
|
+
createDataHooks,
|
|
11
|
+
DataApp,
|
|
12
|
+
injectDataAppStyles,
|
|
13
|
+
mountDataApp,
|
|
14
|
+
MetricWidget,
|
|
15
|
+
textVariable,
|
|
16
|
+
} from '@altertable/data-app/react';
|
|
17
|
+
|
|
18
|
+
const queryNames = defineQueryNames({
|
|
19
|
+
sampleCountsByGroup: 'sample-counts-by-group',
|
|
20
|
+
});
|
|
21
|
+
function parseSampleCountFilter(value: unknown) {
|
|
22
|
+
if (
|
|
23
|
+
!value ||
|
|
24
|
+
typeof value !== 'object' ||
|
|
25
|
+
!('groupName' in value) ||
|
|
26
|
+
typeof value.groupName !== 'string' ||
|
|
27
|
+
value.groupName.length > 40
|
|
28
|
+
)
|
|
29
|
+
throw new Error('Expected a group name of at most 40 characters.');
|
|
30
|
+
return { groupName: value.groupName };
|
|
31
|
+
}
|
|
32
|
+
function parseSampleCounts(
|
|
33
|
+
value: unknown
|
|
34
|
+
): { groupName: string; sampleCount: number }[] {
|
|
35
|
+
if (!Array.isArray(value))
|
|
36
|
+
throw new Error('Expected sample counts by group.');
|
|
37
|
+
return value.map(row => {
|
|
38
|
+
if (!row || typeof row !== 'object' || typeof row.groupName !== 'string')
|
|
39
|
+
throw new Error('Expected a group name.');
|
|
40
|
+
return {
|
|
41
|
+
groupName: row.groupName,
|
|
42
|
+
sampleCount: parseCount(row.sampleCount),
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const operations = {
|
|
47
|
+
sampleCountsByGroup: defineOperation({
|
|
48
|
+
queryNames,
|
|
49
|
+
input: parseSampleCountFilter,
|
|
50
|
+
output: parseSampleCounts,
|
|
51
|
+
checks: [
|
|
52
|
+
{ groupName: '' },
|
|
53
|
+
{ groupName: 'Alpha' },
|
|
54
|
+
{ groupName: 'missing' },
|
|
55
|
+
],
|
|
56
|
+
policy: { maxQueryRows: 10, maxDurationMs: 15000, exposeSql: true },
|
|
57
|
+
async run({ query }, { groupName }) {
|
|
58
|
+
// Portable sample data, not a production table. Escape the validated SQL literal.
|
|
59
|
+
const escapedGroupName = groupName.replaceAll("'", "''");
|
|
60
|
+
const queryResult = await query(
|
|
61
|
+
queryNames.sampleCountsByGroup,
|
|
62
|
+
`
|
|
63
|
+
WITH sample_counts(group_name, sample_count) AS (VALUES ('Alpha', 3), ('Beta', 0))
|
|
64
|
+
SELECT group_name, sample_count FROM sample_counts
|
|
65
|
+
WHERE '${escapedGroupName}' = '' OR group_name = '${escapedGroupName}'
|
|
66
|
+
ORDER BY group_name LIMIT 10`
|
|
67
|
+
);
|
|
68
|
+
return parseSampleCounts(
|
|
69
|
+
queryResult.rows.map(([groupName, sampleCount]) => ({
|
|
70
|
+
groupName,
|
|
71
|
+
sampleCount,
|
|
72
|
+
}))
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
}),
|
|
76
|
+
};
|
|
77
|
+
const appConfig: DataAppConfig = {
|
|
78
|
+
title: 'Sample counts',
|
|
79
|
+
scope: { organization: 'demo', environment: 'sample' },
|
|
80
|
+
appearance: { theme: 'system' },
|
|
81
|
+
};
|
|
82
|
+
const sampleDataContext = createDataContext(queryNames)({
|
|
83
|
+
description:
|
|
84
|
+
'Two SQL VALUES rows demonstrate the host query path. Replace them with inspected source data before publishing findings.',
|
|
85
|
+
glossary: {
|
|
86
|
+
sampleCount: {
|
|
87
|
+
term: 'Sample count',
|
|
88
|
+
definition: 'A fixture value: Alpha is 3 and Beta is a measured zero.',
|
|
89
|
+
queryNames: [queryNames.sampleCountsByGroup],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
const { defineDataView, useView } = createDataHooks(
|
|
94
|
+
createDataClient({ operations })
|
|
95
|
+
);
|
|
96
|
+
const sampleCountsView = defineDataView({
|
|
97
|
+
operation: 'sampleCountsByGroup',
|
|
98
|
+
variables: {
|
|
99
|
+
groupName: textVariable({ key: 'group', label: 'Group', defaultValue: '' }),
|
|
100
|
+
},
|
|
101
|
+
input: ({ groupName }) => ({ groupName }),
|
|
102
|
+
describeInput: ({ groupName }) =>
|
|
103
|
+
groupName ? `group ${groupName}` : 'all groups',
|
|
104
|
+
isEmpty: sampleCounts => sampleCounts.length === 0,
|
|
105
|
+
empty: {
|
|
106
|
+
title: 'No matching groups',
|
|
107
|
+
description: 'Try Alpha, Beta, or clear the group filter.',
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
function App() {
|
|
111
|
+
const sampleCountsRequest = useView(sampleCountsView);
|
|
112
|
+
return (
|
|
113
|
+
<DataApp
|
|
114
|
+
config={appConfig}
|
|
115
|
+
dataContext={sampleDataContext}
|
|
116
|
+
request={sampleCountsRequest}
|
|
117
|
+
story={
|
|
118
|
+
sampleCountsRequest.snapshot?.data.length
|
|
119
|
+
? ({ data: sampleCounts, input: displayedInput }) =>
|
|
120
|
+
sampleCounts.map(({ groupName, sampleCount }) =>
|
|
121
|
+
sampleDataContext.finding({
|
|
122
|
+
id: `sample-count-${groupName}`,
|
|
123
|
+
headline: `${groupName} has ${sampleCount} samples`,
|
|
124
|
+
context: `Demonstration values for ${displayedInput.groupName || 'all groups'}.`,
|
|
125
|
+
visual: (
|
|
126
|
+
<MetricWidget
|
|
127
|
+
label="Sample count"
|
|
128
|
+
value={sampleCount}
|
|
129
|
+
format={{ kind: 'count' }}
|
|
130
|
+
/>
|
|
131
|
+
),
|
|
132
|
+
visualKind: 'metric',
|
|
133
|
+
evidence: {
|
|
134
|
+
id: `sample-count-${groupName}`,
|
|
135
|
+
glossaryIds: ['sampleCount'],
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
)
|
|
139
|
+
: undefined
|
|
140
|
+
}
|
|
141
|
+
>
|
|
142
|
+
{(sampleCounts, displayedInput) => (
|
|
143
|
+
<section aria-label="Sample results">
|
|
144
|
+
<h2>Sample counts</h2>
|
|
145
|
+
<p>Showing {displayedInput.groupName || 'all groups'}</p>
|
|
146
|
+
<ul>
|
|
147
|
+
{sampleCounts.map(({ groupName, sampleCount }) => (
|
|
148
|
+
<li key={groupName}>
|
|
149
|
+
{groupName}: {sampleCount}
|
|
150
|
+
</li>
|
|
151
|
+
))}
|
|
152
|
+
</ul>
|
|
153
|
+
</section>
|
|
154
|
+
)}
|
|
155
|
+
</DataApp>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
injectDataAppStyles();
|
|
159
|
+
mountDataApp({ config: appConfig, component: App });
|