@malloy-publisher/sdk 0.0.140 → 0.0.142
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1350 -17
- package/dist/index.cjs.js +27 -27
- package/dist/index.es.js +914 -920
- package/package.json +1 -1
- package/src/components/Notebook/Notebook.tsx +2 -2
- package/src/components/RenderedResult/ResultContainer.tsx +1 -1
- package/src/components/filter/DimensionFilter.tsx +33 -28
package/README.md
CHANGED
|
@@ -1,24 +1,584 @@
|
|
|
1
1
|
# Malloy Publisher SDK
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The Publisher SDK (`@malloy-publisher/sdk`) is a comprehensive React component library for building data applications that interact with Publisher's REST API. It provides everything you need to browse semantic models, execute queries, visualize results, and build interactive data experiences.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Installation](#installation)
|
|
8
|
+
2. [Quick Start](#quick-start)
|
|
9
|
+
3. [Core Concepts](#core-concepts)
|
|
10
|
+
4. [ServerProvider](#serverprovider)
|
|
11
|
+
5. [Page Components](#page-components)
|
|
12
|
+
6. [Query & Results Components](#query--results-components)
|
|
13
|
+
7. [Dimensional Filters](#dimensional-filters)
|
|
14
|
+
8. [Hooks](#hooks)
|
|
15
|
+
9. [Utilities](#utilities)
|
|
16
|
+
10. [Workbook Storage](#workbook-storage)
|
|
17
|
+
11. [Styling](#styling)
|
|
18
|
+
12. [Building a Custom Data App](#building-a-custom-data-app)
|
|
19
|
+
13. [API Reference](#api-reference)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Using bun
|
|
27
|
+
bun add @malloy-publisher/sdk
|
|
28
|
+
|
|
29
|
+
# Using npm
|
|
30
|
+
npm install @malloy-publisher/sdk
|
|
31
|
+
|
|
32
|
+
# Using yarn
|
|
33
|
+
yarn add @malloy-publisher/sdk
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
### Basic Setup
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { ServerProvider, Home } from "@malloy-publisher/sdk";
|
|
44
|
+
import "@malloy-publisher/sdk/styles.css";
|
|
45
|
+
|
|
46
|
+
function App() {
|
|
47
|
+
return (
|
|
48
|
+
<ServerProvider baseURL="http://localhost:4000/api/v0">
|
|
49
|
+
<Home onClickProject={(path) => console.log("Navigate to:", path)} />
|
|
50
|
+
</ServerProvider>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### With React Router
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import {
|
|
59
|
+
BrowserRouter,
|
|
60
|
+
Routes,
|
|
61
|
+
Route,
|
|
62
|
+
useNavigate,
|
|
63
|
+
useParams,
|
|
64
|
+
} from "react-router-dom";
|
|
65
|
+
import {
|
|
66
|
+
ServerProvider,
|
|
67
|
+
Home,
|
|
68
|
+
Project,
|
|
69
|
+
Package,
|
|
70
|
+
Model,
|
|
71
|
+
Notebook,
|
|
72
|
+
encodeResourceUri,
|
|
73
|
+
useRouterClickHandler,
|
|
74
|
+
} from "@malloy-publisher/sdk";
|
|
75
|
+
import "@malloy-publisher/sdk/styles.css";
|
|
76
|
+
|
|
77
|
+
function App() {
|
|
78
|
+
return (
|
|
79
|
+
<ServerProvider>
|
|
80
|
+
<BrowserRouter>
|
|
81
|
+
<Routes>
|
|
82
|
+
<Route path="/" element={<HomePage />} />
|
|
83
|
+
<Route path="/:projectName" element={<ProjectPage />} />
|
|
84
|
+
<Route
|
|
85
|
+
path="/:projectName/:packageName"
|
|
86
|
+
element={<PackagePage />}
|
|
87
|
+
/>
|
|
88
|
+
<Route
|
|
89
|
+
path="/:projectName/:packageName/*"
|
|
90
|
+
element={<ModelPage />}
|
|
91
|
+
/>
|
|
92
|
+
</Routes>
|
|
93
|
+
</BrowserRouter>
|
|
94
|
+
</ServerProvider>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function HomePage() {
|
|
99
|
+
const navigate = useRouterClickHandler();
|
|
100
|
+
return <Home onClickProject={navigate} />;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function ProjectPage() {
|
|
104
|
+
const navigate = useRouterClickHandler();
|
|
105
|
+
const { projectName } = useParams();
|
|
106
|
+
const resourceUri = encodeResourceUri({ projectName });
|
|
107
|
+
return <Project onSelectPackage={navigate} resourceUri={resourceUri} />;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function PackagePage() {
|
|
111
|
+
const navigate = useRouterClickHandler();
|
|
112
|
+
const { projectName, packageName } = useParams();
|
|
113
|
+
const resourceUri = encodeResourceUri({ projectName, packageName });
|
|
114
|
+
return <Package onClickPackageFile={navigate} resourceUri={resourceUri} />;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function ModelPage() {
|
|
118
|
+
const params = useParams();
|
|
119
|
+
const modelPath = params["*"];
|
|
120
|
+
const resourceUri = encodeResourceUri({
|
|
121
|
+
projectName: params.projectName,
|
|
122
|
+
packageName: params.packageName,
|
|
123
|
+
modelPath,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (modelPath?.endsWith(".malloy")) {
|
|
127
|
+
return <Model resourceUri={resourceUri} />;
|
|
128
|
+
}
|
|
129
|
+
if (modelPath?.endsWith(".malloynb")) {
|
|
130
|
+
return <Notebook resourceUri={resourceUri} />;
|
|
131
|
+
}
|
|
132
|
+
return <div>Unknown file type</div>;
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Core Concepts
|
|
139
|
+
|
|
140
|
+
### Resource URIs
|
|
141
|
+
|
|
142
|
+
The SDK uses a standardized URI format to identify resources:
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
publisher://projects/{projectName}/packages/{packageName}/models/{modelPath}?versionId={version}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Examples:
|
|
149
|
+
|
|
150
|
+
- Project: `publisher://projects/my-project`
|
|
151
|
+
- Package: `publisher://projects/my-project/packages/analytics`
|
|
152
|
+
- Model: `publisher://projects/my-project/packages/analytics/models/orders.malloy`
|
|
153
|
+
|
|
154
|
+
Use the `encodeResourceUri()` and `parseResourceUri()` utilities to work with these URIs.
|
|
155
|
+
|
|
156
|
+
### Component Hierarchy
|
|
157
|
+
|
|
158
|
+
The SDK components follow a natural hierarchy:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
ServerProvider (required wrapper)
|
|
162
|
+
├── Home (list all projects)
|
|
163
|
+
│ └── Project (show packages in a project)
|
|
164
|
+
│ └── Package (show models, notebooks, connections)
|
|
165
|
+
│ ├── Model (visual query builder + named queries)
|
|
166
|
+
│ └── Notebook (read-only notebook viewer)
|
|
167
|
+
└── Workbook (interactive analysis workbook)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Navigation Pattern
|
|
171
|
+
|
|
172
|
+
Components accept callback functions for navigation rather than handling routing directly. This allows you to integrate with any routing solution:
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
// With React Router
|
|
176
|
+
const navigate = useRouterClickHandler();
|
|
177
|
+
<Home onClickProject={navigate} />
|
|
178
|
+
|
|
179
|
+
// Custom navigation
|
|
180
|
+
<Home onClickProject={(path) => window.location.href = path} />
|
|
181
|
+
|
|
182
|
+
// SPA with history
|
|
183
|
+
<Home onClickProject={(path) => history.push(path)} />
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## ServerProvider
|
|
189
|
+
|
|
190
|
+
The `ServerProvider` is the required context provider that wraps your application. It initializes API clients and passes auth headers (if required by the backend server).
|
|
191
|
+
|
|
192
|
+
### Props
|
|
193
|
+
|
|
194
|
+
| Prop | Type | Default | Description |
|
|
195
|
+
| ---------------- | ----------------------- | ------------- | -------------------------------------------------------------------- |
|
|
196
|
+
| `baseURL` | `string` | Auto-detected | Base URL of the Publisher API (e.g., `http://localhost:4000/api/v0`) |
|
|
197
|
+
| `getAccessToken` | `() => Promise<string>` | `undefined` | Async function returning auth token |
|
|
198
|
+
| `mutable` | `boolean` | `true` | Enable/disable project/package management UI |
|
|
199
|
+
|
|
200
|
+
### Basic Usage
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
<ServerProvider>{/* Your app */}</ServerProvider>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### With Authentication
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
async function getAccessToken() {
|
|
210
|
+
const response = await fetch("/auth/token");
|
|
211
|
+
const { token } = await response.json();
|
|
212
|
+
return `Bearer ${token}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
<ServerProvider getAccessToken={getAccessToken}>
|
|
216
|
+
{/* Your app */}
|
|
217
|
+
</ServerProvider>;
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Read-Only Mode
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
// Disable add/edit/delete UI for production deployments
|
|
224
|
+
<ServerProvider mutable={false}>{/* Your app */}</ServerProvider>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Custom Server URL
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
<ServerProvider baseURL="https://publisher.example.com/api/v0">
|
|
231
|
+
{/* Your app */}
|
|
232
|
+
</ServerProvider>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Page Components
|
|
238
|
+
|
|
239
|
+
### Home
|
|
240
|
+
|
|
241
|
+
Displays a landing page with feature cards and a list of all available projects.
|
|
242
|
+
|
|
243
|
+
```tsx
|
|
244
|
+
import { Home } from "@malloy-publisher/sdk";
|
|
245
|
+
|
|
246
|
+
interface HomeProps {
|
|
247
|
+
onClickProject?: (path: string, event?: React.MouseEvent) => void;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Usage
|
|
251
|
+
<Home
|
|
252
|
+
onClickProject={(path, event) => {
|
|
253
|
+
// path is like "/my-project/"
|
|
254
|
+
navigate(path);
|
|
255
|
+
}}
|
|
256
|
+
/>;
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Features:**
|
|
260
|
+
|
|
261
|
+
- Hero section with Publisher branding
|
|
262
|
+
- Feature cards (Ad Hoc Analysis, Notebook Dashboards, AI Agents)
|
|
263
|
+
- Project listing with descriptions
|
|
264
|
+
- Add/Edit/Delete project dialogs (when `mutable=true`)
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
### Project
|
|
269
|
+
|
|
270
|
+
Shows all packages within a project.
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
import { Project, encodeResourceUri } from "@malloy-publisher/sdk";
|
|
274
|
+
|
|
275
|
+
interface ProjectProps {
|
|
276
|
+
onSelectPackage: (path: string, event?: React.MouseEvent) => void;
|
|
277
|
+
resourceUri: string;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Usage
|
|
281
|
+
const resourceUri = encodeResourceUri({ projectName: "my-project" });
|
|
282
|
+
|
|
283
|
+
<Project
|
|
284
|
+
onSelectPackage={(path) => navigate(path)}
|
|
285
|
+
resourceUri={resourceUri}
|
|
286
|
+
/>;
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Features:**
|
|
290
|
+
|
|
291
|
+
- Package listing with version info
|
|
292
|
+
- Add/Edit/Delete package dialogs (when `mutable=true`)
|
|
293
|
+
- Project README display
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
### Package
|
|
298
|
+
|
|
299
|
+
Displays package details including models, notebooks, databases, and connections.
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
import { Package, encodeResourceUri } from "@malloy-publisher/sdk";
|
|
303
|
+
|
|
304
|
+
interface PackageProps {
|
|
305
|
+
onClickPackageFile?: (path: string, event?: React.MouseEvent) => void;
|
|
306
|
+
resourceUri: string;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Usage
|
|
310
|
+
const resourceUri = encodeResourceUri({
|
|
311
|
+
projectName: "my-project",
|
|
312
|
+
packageName: "analytics",
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
<Package
|
|
316
|
+
onClickPackageFile={(path) => navigate(path)}
|
|
317
|
+
resourceUri={resourceUri}
|
|
318
|
+
/>;
|
|
10
319
|
```
|
|
11
320
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
321
|
+
**Features:**
|
|
322
|
+
|
|
323
|
+
- Models list (`.malloy` files)
|
|
324
|
+
- Notebooks list (`.malloynb` files)
|
|
325
|
+
- Embedded databases (`.parquet` files)
|
|
326
|
+
- Connection configuration
|
|
327
|
+
- Package README
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
### Model
|
|
332
|
+
|
|
333
|
+
The visual query builder and model explorer. This is the primary component for ad-hoc data analysis.
|
|
334
|
+
|
|
335
|
+
```tsx
|
|
336
|
+
import { Model, encodeResourceUri } from "@malloy-publisher/sdk";
|
|
337
|
+
|
|
338
|
+
interface ModelProps {
|
|
339
|
+
resourceUri: string;
|
|
340
|
+
onChange?: (query: QueryExplorerResult) => void;
|
|
341
|
+
runOnDemand?: boolean; // Default: false
|
|
342
|
+
maxResultSize?: number; // Default: 0 (no limit)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
interface QueryExplorerResult {
|
|
346
|
+
query: string | undefined;
|
|
347
|
+
malloyQuery: Malloy.Query | string | undefined;
|
|
348
|
+
malloyResult: Malloy.Result | undefined;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Usage
|
|
352
|
+
const resourceUri = encodeResourceUri({
|
|
353
|
+
projectName: "my-project",
|
|
354
|
+
packageName: "analytics",
|
|
355
|
+
modelPath: "models/orders.malloy",
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
<Model
|
|
359
|
+
resourceUri={resourceUri}
|
|
360
|
+
runOnDemand={true}
|
|
361
|
+
maxResultSize={512 * 1024}
|
|
362
|
+
onChange={(result) => {
|
|
363
|
+
console.log("Query:", result.query);
|
|
364
|
+
console.log("Result:", result.malloyResult);
|
|
365
|
+
}}
|
|
366
|
+
/>;
|
|
20
367
|
```
|
|
21
368
|
|
|
369
|
+
**Features:**
|
|
370
|
+
|
|
371
|
+
- Source selector (dropdown for models with multiple sources)
|
|
372
|
+
- Visual query builder (Malloy Explorer integration)
|
|
373
|
+
- Named queries display
|
|
374
|
+
- Full-screen dialog mode
|
|
375
|
+
- Copy link to current view
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
### ModelExplorer
|
|
380
|
+
|
|
381
|
+
A lower-level component for embedding the query builder without the full Model chrome.
|
|
382
|
+
|
|
383
|
+
```tsx
|
|
384
|
+
import {
|
|
385
|
+
ModelExplorer,
|
|
386
|
+
useModelData,
|
|
387
|
+
encodeResourceUri,
|
|
388
|
+
} from "@malloy-publisher/sdk";
|
|
389
|
+
|
|
390
|
+
interface ModelExplorerProps {
|
|
391
|
+
data?: CompiledModel; // Pre-loaded model data
|
|
392
|
+
onChange?: (query: QueryExplorerResult) => void;
|
|
393
|
+
existingQuery?: QueryExplorerResult; // Initialize with existing query
|
|
394
|
+
initialSelectedSourceIndex?: number; // Default: 0
|
|
395
|
+
onSourceChange?: (index: number) => void;
|
|
396
|
+
resourceUri: string;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Usage with automatic data loading
|
|
400
|
+
<ModelExplorer
|
|
401
|
+
resourceUri={resourceUri}
|
|
402
|
+
onChange={(result) => console.log(result)}
|
|
403
|
+
/>;
|
|
404
|
+
|
|
405
|
+
// Usage with pre-loaded data
|
|
406
|
+
const { data } = useModelData(resourceUri);
|
|
407
|
+
|
|
408
|
+
<ModelExplorer
|
|
409
|
+
data={data}
|
|
410
|
+
resourceUri={resourceUri}
|
|
411
|
+
onChange={(result) => console.log(result)}
|
|
412
|
+
/>;
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
### Notebook
|
|
418
|
+
|
|
419
|
+
Read-only notebook viewer that executes cells and displays results.
|
|
420
|
+
|
|
421
|
+
```tsx
|
|
422
|
+
import { Notebook, encodeResourceUri } from "@malloy-publisher/sdk";
|
|
423
|
+
|
|
424
|
+
interface NotebookProps {
|
|
425
|
+
resourceUri: string;
|
|
426
|
+
maxResultSize?: number; // Default: 0 (no limit)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Usage
|
|
430
|
+
const resourceUri = encodeResourceUri({
|
|
431
|
+
projectName: "my-project",
|
|
432
|
+
packageName: "analytics",
|
|
433
|
+
modelPath: "notebooks/sales-dashboard.malloynb",
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
<Notebook resourceUri={resourceUri} maxResultSize={1024 * 1024} />;
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**Features:**
|
|
440
|
+
|
|
441
|
+
- Sequential cell execution
|
|
442
|
+
- Markdown rendering
|
|
443
|
+
- Code cell execution with results
|
|
444
|
+
- Error handling per cell
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
### Workbook
|
|
449
|
+
|
|
450
|
+
Interactive workbook editor for creating and saving custom analyses.
|
|
451
|
+
|
|
452
|
+
```tsx
|
|
453
|
+
import {
|
|
454
|
+
Workbook,
|
|
455
|
+
WorkbookStorageProvider,
|
|
456
|
+
BrowserWorkbookStorage,
|
|
457
|
+
encodeResourceUri,
|
|
458
|
+
} from "@malloy-publisher/sdk";
|
|
459
|
+
|
|
460
|
+
interface WorkbookProps {
|
|
461
|
+
workbookPath?: WorkbookLocator; // { path: string, workspace: string }
|
|
462
|
+
resourceUri: string;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Usage
|
|
466
|
+
const workbookStorage = new BrowserWorkbookStorage();
|
|
467
|
+
const resourceUri = encodeResourceUri({
|
|
468
|
+
projectName: "my-project",
|
|
469
|
+
packageName: "analytics",
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
<WorkbookStorageProvider workbookStorage={workbookStorage}>
|
|
473
|
+
<Workbook
|
|
474
|
+
workbookPath={{ path: "my-analysis", workspace: "Local" }}
|
|
475
|
+
resourceUri={resourceUri}
|
|
476
|
+
/>
|
|
477
|
+
</WorkbookStorageProvider>;
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Features:**
|
|
481
|
+
|
|
482
|
+
- Add/remove Markdown and Malloy cells
|
|
483
|
+
- Model picker for source selection
|
|
484
|
+
- Auto-save to storage backend
|
|
485
|
+
- Export to Malloy format
|
|
486
|
+
- Delete workbook
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## Query & Results Components
|
|
491
|
+
|
|
492
|
+
### QueryResult
|
|
493
|
+
|
|
494
|
+
Executes a query and displays the visualization.
|
|
495
|
+
|
|
496
|
+
```tsx
|
|
497
|
+
import { QueryResult, encodeResourceUri } from "@malloy-publisher/sdk";
|
|
498
|
+
|
|
499
|
+
interface QueryResultProps {
|
|
500
|
+
query?: string; // Raw Malloy query
|
|
501
|
+
sourceName?: string; // Source name for named query
|
|
502
|
+
queryName?: string; // Named query to execute
|
|
503
|
+
resourceUri?: string; // Resource URI for model
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Execute a named query
|
|
507
|
+
<QueryResult
|
|
508
|
+
sourceName="orders"
|
|
509
|
+
queryName="by_region"
|
|
510
|
+
resourceUri={encodeResourceUri({
|
|
511
|
+
projectName: "my-project",
|
|
512
|
+
packageName: "analytics",
|
|
513
|
+
modelPath: "models/orders.malloy",
|
|
514
|
+
})}
|
|
515
|
+
/>
|
|
516
|
+
|
|
517
|
+
// Execute a raw query
|
|
518
|
+
<QueryResult
|
|
519
|
+
query="run: orders -> { group_by: status; aggregate: order_count }"
|
|
520
|
+
resourceUri={encodeResourceUri({
|
|
521
|
+
projectName: "my-project",
|
|
522
|
+
packageName: "analytics",
|
|
523
|
+
modelPath: "models/orders.malloy",
|
|
524
|
+
})}
|
|
525
|
+
/>
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
### RenderedResult
|
|
531
|
+
|
|
532
|
+
Low-level component for rendering Malloy result JSON as a visualization.
|
|
533
|
+
|
|
534
|
+
```tsx
|
|
535
|
+
import RenderedResult from "@malloy-publisher/sdk";
|
|
536
|
+
|
|
537
|
+
interface RenderedResultProps {
|
|
538
|
+
result: string; // JSON result string
|
|
539
|
+
height?: number; // Fixed height in pixels
|
|
540
|
+
onSizeChange?: (height: number) => void; // Callback when size changes
|
|
541
|
+
onDrill?: (element: unknown) => void; // Drill-down callback
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Usage (result is the JSON string from query execution)
|
|
545
|
+
<RenderedResult
|
|
546
|
+
result={queryResultJson}
|
|
547
|
+
onDrill={(element) => {
|
|
548
|
+
console.log("Drilled into:", element);
|
|
549
|
+
}}
|
|
550
|
+
/>;
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
### EmbeddedQueryResult
|
|
556
|
+
|
|
557
|
+
Helper for embedding query results as serialized JSON (useful for storage/transfer).
|
|
558
|
+
|
|
559
|
+
```tsx
|
|
560
|
+
import {
|
|
561
|
+
EmbeddedQueryResult,
|
|
562
|
+
createEmbeddedQueryResult,
|
|
563
|
+
} from "@malloy-publisher/sdk";
|
|
564
|
+
|
|
565
|
+
// Create embedded query config
|
|
566
|
+
const embedded = createEmbeddedQueryResult({
|
|
567
|
+
queryName: "by_region",
|
|
568
|
+
sourceName: "orders",
|
|
569
|
+
resourceUri: encodeResourceUri({
|
|
570
|
+
projectName: "my-project",
|
|
571
|
+
packageName: "analytics",
|
|
572
|
+
modelPath: "models/orders.malloy",
|
|
573
|
+
}),
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// Later, render it
|
|
577
|
+
<EmbeddedQueryResult embeddedQueryResult={embedded} />;
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
22
582
|
## Dimensional Filters
|
|
23
583
|
|
|
24
584
|
The SDK supports interactive dimensional filtering for notebooks and embedded data apps. Filters are configured through annotations in Malloy source files and notebooks.
|
|
@@ -95,9 +655,9 @@ If no search function is supplied, the filter is ignored.
|
|
|
95
655
|
For custom data apps, use the SDK's React hooks:
|
|
96
656
|
|
|
97
657
|
```tsx
|
|
98
|
-
import {
|
|
658
|
+
import {
|
|
99
659
|
useDimensionFiltersFromSpec,
|
|
100
|
-
DimensionFiltersConfig
|
|
660
|
+
DimensionFiltersConfig
|
|
101
661
|
} from '@malloy-publisher/sdk';
|
|
102
662
|
|
|
103
663
|
const config: DimensionFiltersConfig = {
|
|
@@ -138,4 +698,777 @@ Filters support different match types depending on the filter type:
|
|
|
138
698
|
| `Greater Than` / `Less Than` | Comparison | MinMax |
|
|
139
699
|
| `Between` | Range (inclusive) | MinMax, DateMinMax |
|
|
140
700
|
| `After` / `Before` | Date comparison | DateMinMax |
|
|
141
|
-
| `Concept Search` | Semantic similarity | Retrieval |
|
|
701
|
+
| `Concept Search` | Semantic similarity | Retrieval |
|
|
702
|
+
|
|
703
|
+
---
|
|
704
|
+
|
|
705
|
+
## Hooks
|
|
706
|
+
|
|
707
|
+
### useServer
|
|
708
|
+
|
|
709
|
+
Access the server context (API clients, configuration).
|
|
710
|
+
|
|
711
|
+
```tsx
|
|
712
|
+
import { useServer } from "@malloy-publisher/sdk";
|
|
713
|
+
|
|
714
|
+
function MyComponent() {
|
|
715
|
+
const {
|
|
716
|
+
server, // Base URL string
|
|
717
|
+
apiClients, // API client instances
|
|
718
|
+
mutable, // Whether mutations are allowed
|
|
719
|
+
getAccessToken, // Auth token function
|
|
720
|
+
} = useServer();
|
|
721
|
+
|
|
722
|
+
// Use API clients directly
|
|
723
|
+
const projects = await apiClients.projects.listProjects();
|
|
724
|
+
const model = await apiClients.models.getModel(
|
|
725
|
+
projectName,
|
|
726
|
+
packageName,
|
|
727
|
+
modelPath,
|
|
728
|
+
versionId,
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
### API Clients Available
|
|
734
|
+
|
|
735
|
+
```typescript
|
|
736
|
+
interface ApiClients {
|
|
737
|
+
models: ModelsApi; // Get/execute models
|
|
738
|
+
projects: ProjectsApi; // CRUD projects
|
|
739
|
+
packages: PackagesApi; // CRUD packages
|
|
740
|
+
notebooks: NotebooksApi; // Get/execute notebooks
|
|
741
|
+
connections: ConnectionsApi; // CRUD connections
|
|
742
|
+
databases: DatabasesApi; // Access embedded databases
|
|
743
|
+
watchMode: WatchModeApi; // File watching for dev
|
|
744
|
+
}
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
### useQueryWithApiError
|
|
750
|
+
|
|
751
|
+
React Query wrapper with standardized error handling.
|
|
752
|
+
|
|
753
|
+
```tsx
|
|
754
|
+
import { useQueryWithApiError } from "@malloy-publisher/sdk";
|
|
755
|
+
|
|
756
|
+
function MyComponent() {
|
|
757
|
+
const { data, isLoading, isError, error } = useQueryWithApiError({
|
|
758
|
+
queryKey: ["my-data", someParam],
|
|
759
|
+
queryFn: async () => {
|
|
760
|
+
const response = await apiClients.projects.listProjects();
|
|
761
|
+
return response.data;
|
|
762
|
+
},
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
if (isLoading) return <Loading />;
|
|
766
|
+
if (isError) return <ApiErrorDisplay error={error} context="Loading data" />;
|
|
767
|
+
return <div>{JSON.stringify(data)}</div>;
|
|
768
|
+
}
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
**Features:**
|
|
772
|
+
|
|
773
|
+
- Automatic server-based cache key namespacing
|
|
774
|
+
- Standardized axios error transformation
|
|
775
|
+
- No automatic retries (explicit control)
|
|
776
|
+
|
|
777
|
+
---
|
|
778
|
+
|
|
779
|
+
### useMutationWithApiError
|
|
780
|
+
|
|
781
|
+
Mutation wrapper with standardized error handling.
|
|
782
|
+
|
|
783
|
+
```tsx
|
|
784
|
+
import { useMutationWithApiError } from "@malloy-publisher/sdk";
|
|
785
|
+
|
|
786
|
+
function MyComponent() {
|
|
787
|
+
const mutation = useMutationWithApiError({
|
|
788
|
+
mutationFn: async (newProject) => {
|
|
789
|
+
const response = await apiClients.projects.createProject(newProject);
|
|
790
|
+
return response.data;
|
|
791
|
+
},
|
|
792
|
+
onSuccess: () => {
|
|
793
|
+
queryClient.invalidateQueries(["projects"]);
|
|
794
|
+
},
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
return (
|
|
798
|
+
<button onClick={() => mutation.mutate({ name: "new-project" })}>
|
|
799
|
+
Create Project
|
|
800
|
+
</button>
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
---
|
|
806
|
+
|
|
807
|
+
### useModelData
|
|
808
|
+
|
|
809
|
+
Fetch compiled model data for a resource URI.
|
|
810
|
+
|
|
811
|
+
```tsx
|
|
812
|
+
import { useModelData } from "@malloy-publisher/sdk";
|
|
813
|
+
|
|
814
|
+
function MyComponent({ resourceUri }) {
|
|
815
|
+
const {
|
|
816
|
+
data, // CompiledModel
|
|
817
|
+
isLoading,
|
|
818
|
+
isError,
|
|
819
|
+
error,
|
|
820
|
+
} = useModelData(resourceUri);
|
|
821
|
+
|
|
822
|
+
if (isLoading) return <Loading text="Loading model..." />;
|
|
823
|
+
if (isError) return <ApiErrorDisplay error={error} />;
|
|
824
|
+
|
|
825
|
+
// Access model data
|
|
826
|
+
console.log("Sources:", data.sourceInfos);
|
|
827
|
+
console.log("Queries:", data.queries);
|
|
828
|
+
}
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
---
|
|
832
|
+
|
|
833
|
+
### useRawQueryData
|
|
834
|
+
|
|
835
|
+
Execute a query and get raw data (array of rows) instead of visualization.
|
|
836
|
+
|
|
837
|
+
```tsx
|
|
838
|
+
import { useRawQueryData } from "@malloy-publisher/sdk";
|
|
839
|
+
|
|
840
|
+
function MyComponent({ resourceUri }) {
|
|
841
|
+
const {
|
|
842
|
+
data, // Array of row objects
|
|
843
|
+
isLoading,
|
|
844
|
+
isSuccess,
|
|
845
|
+
isError,
|
|
846
|
+
error,
|
|
847
|
+
} = useRawQueryData({
|
|
848
|
+
resourceUri,
|
|
849
|
+
modelPath: "models/orders.malloy",
|
|
850
|
+
queryName: "by_region",
|
|
851
|
+
sourceName: "orders",
|
|
852
|
+
enabled: true,
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
if (isSuccess) {
|
|
856
|
+
// data is an array of row objects
|
|
857
|
+
data.forEach((row) => {
|
|
858
|
+
console.log(row.region, row.total_sales);
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
---
|
|
865
|
+
|
|
866
|
+
### useRouterClickHandler
|
|
867
|
+
|
|
868
|
+
Smart navigation hook that supports modifier keys (Cmd/Ctrl+click for new tab).
|
|
869
|
+
|
|
870
|
+
```tsx
|
|
871
|
+
import { useRouterClickHandler } from "@malloy-publisher/sdk";
|
|
872
|
+
|
|
873
|
+
function MyComponent() {
|
|
874
|
+
const navigate = useRouterClickHandler();
|
|
875
|
+
|
|
876
|
+
return (
|
|
877
|
+
<button onClick={(e) => navigate("/projects/analytics", e)}>
|
|
878
|
+
Go to Analytics
|
|
879
|
+
</button>
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
**Behavior:**
|
|
885
|
+
|
|
886
|
+
- Normal click: In-app navigation
|
|
887
|
+
- Cmd/Ctrl+click: Open in new tab
|
|
888
|
+
- Middle-click: Open in new tab
|
|
889
|
+
- Shift+click: Open in new window
|
|
890
|
+
|
|
891
|
+
---
|
|
892
|
+
|
|
893
|
+
## Utilities
|
|
894
|
+
|
|
895
|
+
### encodeResourceUri
|
|
896
|
+
|
|
897
|
+
Create a resource URI from components.
|
|
898
|
+
|
|
899
|
+
```tsx
|
|
900
|
+
import { encodeResourceUri } from "@malloy-publisher/sdk";
|
|
901
|
+
|
|
902
|
+
// Project only
|
|
903
|
+
const projectUri = encodeResourceUri({
|
|
904
|
+
projectName: "my-project",
|
|
905
|
+
});
|
|
906
|
+
// Result: "publisher://projects/my-project"
|
|
907
|
+
|
|
908
|
+
// Package
|
|
909
|
+
const packageUri = encodeResourceUri({
|
|
910
|
+
projectName: "my-project",
|
|
911
|
+
packageName: "analytics",
|
|
912
|
+
});
|
|
913
|
+
// Result: "publisher://projects/my-project/packages/analytics"
|
|
914
|
+
|
|
915
|
+
// Model with version
|
|
916
|
+
const modelUri = encodeResourceUri({
|
|
917
|
+
projectName: "my-project",
|
|
918
|
+
packageName: "analytics",
|
|
919
|
+
modelPath: "models/orders.malloy",
|
|
920
|
+
versionId: "abc123",
|
|
921
|
+
});
|
|
922
|
+
// Result: "publisher://projects/my-project/packages/analytics/models/models/orders.malloy?versionId=abc123"
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
---
|
|
926
|
+
|
|
927
|
+
### parseResourceUri
|
|
928
|
+
|
|
929
|
+
Parse a resource URI back to components.
|
|
930
|
+
|
|
931
|
+
```tsx
|
|
932
|
+
import { parseResourceUri } from "@malloy-publisher/sdk";
|
|
933
|
+
|
|
934
|
+
const uri =
|
|
935
|
+
"publisher://projects/my-project/packages/analytics/models/orders.malloy?versionId=abc123";
|
|
936
|
+
const parsed = parseResourceUri(uri);
|
|
937
|
+
|
|
938
|
+
// Result:
|
|
939
|
+
// {
|
|
940
|
+
// projectName: "my-project",
|
|
941
|
+
// packageName: "analytics",
|
|
942
|
+
// modelPath: "orders.malloy",
|
|
943
|
+
// versionId: "abc123"
|
|
944
|
+
// }
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
---
|
|
948
|
+
|
|
949
|
+
### ParsedResource Type
|
|
950
|
+
|
|
951
|
+
```typescript
|
|
952
|
+
type ParsedResource = {
|
|
953
|
+
projectName: string;
|
|
954
|
+
packageName?: string;
|
|
955
|
+
connectionName?: string;
|
|
956
|
+
versionId?: string;
|
|
957
|
+
modelPath?: string;
|
|
958
|
+
};
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
---
|
|
962
|
+
|
|
963
|
+
## Workbook Storage
|
|
964
|
+
|
|
965
|
+
Workbooks are interactive analysis documents that can be saved and loaded. The SDK provides a storage abstraction that you can implement for different backends.
|
|
966
|
+
|
|
967
|
+
### WorkbookStorage Interface
|
|
968
|
+
|
|
969
|
+
```typescript
|
|
970
|
+
interface Workspace {
|
|
971
|
+
name: string;
|
|
972
|
+
writeable: boolean;
|
|
973
|
+
description: string;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
interface WorkbookLocator {
|
|
977
|
+
path: string;
|
|
978
|
+
workspace: string;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
interface WorkbookStorage {
|
|
982
|
+
listWorkspaces(writeableOnly: boolean): Promise<Workspace[]>;
|
|
983
|
+
listWorkbooks(workspace: Workspace): Promise<WorkbookLocator[]>;
|
|
984
|
+
getWorkbook(path: WorkbookLocator): Promise<string>;
|
|
985
|
+
deleteWorkbook(path: WorkbookLocator): Promise<void>;
|
|
986
|
+
saveWorkbook(path: WorkbookLocator, workbook: string): Promise<void>;
|
|
987
|
+
moveWorkbook(from: WorkbookLocator, to: WorkbookLocator): Promise<void>;
|
|
988
|
+
}
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
---
|
|
992
|
+
|
|
993
|
+
### BrowserWorkbookStorage
|
|
994
|
+
|
|
995
|
+
Built-in implementation using browser localStorage.
|
|
996
|
+
|
|
997
|
+
```tsx
|
|
998
|
+
import {
|
|
999
|
+
BrowserWorkbookStorage,
|
|
1000
|
+
WorkbookStorageProvider,
|
|
1001
|
+
} from "@malloy-publisher/sdk";
|
|
1002
|
+
|
|
1003
|
+
const storage = new BrowserWorkbookStorage();
|
|
1004
|
+
|
|
1005
|
+
<WorkbookStorageProvider workbookStorage={storage}>
|
|
1006
|
+
<App />
|
|
1007
|
+
</WorkbookStorageProvider>;
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
---
|
|
1011
|
+
|
|
1012
|
+
### Custom Storage Implementation
|
|
1013
|
+
|
|
1014
|
+
```tsx
|
|
1015
|
+
class S3WorkbookStorage implements WorkbookStorage {
|
|
1016
|
+
private s3Client: S3Client;
|
|
1017
|
+
private bucket: string;
|
|
1018
|
+
|
|
1019
|
+
constructor(s3Client: S3Client, bucket: string) {
|
|
1020
|
+
this.s3Client = s3Client;
|
|
1021
|
+
this.bucket = bucket;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
async listWorkspaces(writeableOnly: boolean): Promise<Workspace[]> {
|
|
1025
|
+
return [
|
|
1026
|
+
{
|
|
1027
|
+
name: this.bucket,
|
|
1028
|
+
writeable: true,
|
|
1029
|
+
description: "S3 bucket storage",
|
|
1030
|
+
},
|
|
1031
|
+
];
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
async listWorkbooks(workspace: Workspace): Promise<WorkbookLocator[]> {
|
|
1035
|
+
const objects = await this.s3Client.listObjects(
|
|
1036
|
+
this.bucket,
|
|
1037
|
+
"workbooks/",
|
|
1038
|
+
);
|
|
1039
|
+
return objects.map((obj) => ({
|
|
1040
|
+
path: obj.key,
|
|
1041
|
+
workspace: workspace.name,
|
|
1042
|
+
}));
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
async getWorkbook(path: WorkbookLocator): Promise<string> {
|
|
1046
|
+
const data = await this.s3Client.getObject(this.bucket, path.path);
|
|
1047
|
+
return data.toString();
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
async saveWorkbook(path: WorkbookLocator, workbook: string): Promise<void> {
|
|
1051
|
+
await this.s3Client.putObject(this.bucket, path.path, workbook);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
async deleteWorkbook(path: WorkbookLocator): Promise<void> {
|
|
1055
|
+
await this.s3Client.deleteObject(this.bucket, path.path);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
async moveWorkbook(
|
|
1059
|
+
from: WorkbookLocator,
|
|
1060
|
+
to: WorkbookLocator,
|
|
1061
|
+
): Promise<void> {
|
|
1062
|
+
const content = await this.getWorkbook(from);
|
|
1063
|
+
await this.saveWorkbook(to, content);
|
|
1064
|
+
await this.deleteWorkbook(from);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// Usage
|
|
1069
|
+
const storage = new S3WorkbookStorage(s3Client, "my-workbooks-bucket");
|
|
1070
|
+
|
|
1071
|
+
<WorkbookStorageProvider workbookStorage={storage}>
|
|
1072
|
+
<App />
|
|
1073
|
+
</WorkbookStorageProvider>;
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
---
|
|
1077
|
+
|
|
1078
|
+
### WorkbookStorageProvider
|
|
1079
|
+
|
|
1080
|
+
Context provider for workbook storage.
|
|
1081
|
+
|
|
1082
|
+
```tsx
|
|
1083
|
+
import {
|
|
1084
|
+
WorkbookStorageProvider,
|
|
1085
|
+
useWorkbookStorage,
|
|
1086
|
+
} from "@malloy-publisher/sdk";
|
|
1087
|
+
|
|
1088
|
+
// Provider setup
|
|
1089
|
+
<WorkbookStorageProvider workbookStorage={myStorage}>
|
|
1090
|
+
<App />
|
|
1091
|
+
</WorkbookStorageProvider>;
|
|
1092
|
+
|
|
1093
|
+
// Access in components
|
|
1094
|
+
function MyComponent() {
|
|
1095
|
+
const { workbookStorage } = useWorkbookStorage();
|
|
1096
|
+
|
|
1097
|
+
const workbooks = await workbookStorage.listWorkbooks({
|
|
1098
|
+
name: "Local",
|
|
1099
|
+
writeable: true,
|
|
1100
|
+
description: "",
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
```
|
|
1104
|
+
|
|
1105
|
+
---
|
|
1106
|
+
|
|
1107
|
+
## Styling
|
|
1108
|
+
|
|
1109
|
+
### Required CSS
|
|
1110
|
+
|
|
1111
|
+
Import the SDK styles in your app entry point:
|
|
1112
|
+
|
|
1113
|
+
```tsx
|
|
1114
|
+
// Main SDK styles (required)
|
|
1115
|
+
import "@malloy-publisher/sdk/styles.css";
|
|
1116
|
+
|
|
1117
|
+
// If using Model/ModelExplorer outside of Publisher
|
|
1118
|
+
import "@malloy-publisher/sdk/malloy-explorer.css";
|
|
1119
|
+
|
|
1120
|
+
// If using Workbook markdown editor
|
|
1121
|
+
import "@malloy-publisher/sdk/markdown-editor.css";
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
---
|
|
1125
|
+
|
|
1126
|
+
### Material-UI Theme
|
|
1127
|
+
|
|
1128
|
+
The SDK uses Material-UI (MUI) v7. You can customize the theme:
|
|
1129
|
+
|
|
1130
|
+
```tsx
|
|
1131
|
+
import { createTheme, ThemeProvider, CssBaseline } from "@mui/material";
|
|
1132
|
+
import { ServerProvider } from "@malloy-publisher/sdk";
|
|
1133
|
+
|
|
1134
|
+
const theme = createTheme({
|
|
1135
|
+
palette: {
|
|
1136
|
+
primary: {
|
|
1137
|
+
main: "#14b3cb", // Malloy teal
|
|
1138
|
+
},
|
|
1139
|
+
secondary: {
|
|
1140
|
+
main: "#fbbb04", // Malloy yellow
|
|
1141
|
+
},
|
|
1142
|
+
},
|
|
1143
|
+
typography: {
|
|
1144
|
+
fontFamily: '"Inter", "Roboto", sans-serif',
|
|
1145
|
+
},
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
function App() {
|
|
1149
|
+
return (
|
|
1150
|
+
<ServerProvider>
|
|
1151
|
+
<ThemeProvider theme={theme}>
|
|
1152
|
+
<CssBaseline />
|
|
1153
|
+
{/* Your app */}
|
|
1154
|
+
</ThemeProvider>
|
|
1155
|
+
</ServerProvider>
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
---
|
|
1161
|
+
|
|
1162
|
+
### Styled Components
|
|
1163
|
+
|
|
1164
|
+
The SDK exports several pre-styled components for consistent UI:
|
|
1165
|
+
|
|
1166
|
+
```tsx
|
|
1167
|
+
import {
|
|
1168
|
+
StyledCard,
|
|
1169
|
+
StyledCardContent,
|
|
1170
|
+
StyledCardMedia,
|
|
1171
|
+
PackageCard,
|
|
1172
|
+
PackageCardContent,
|
|
1173
|
+
PackageSectionTitle,
|
|
1174
|
+
CleanNotebookContainer,
|
|
1175
|
+
CleanNotebookSection,
|
|
1176
|
+
} from "@malloy-publisher/sdk";
|
|
1177
|
+
```
|
|
1178
|
+
|
|
1179
|
+
---
|
|
1180
|
+
|
|
1181
|
+
## Building a Custom Data App
|
|
1182
|
+
|
|
1183
|
+
### Example: Dashboard with Multiple Visualizations
|
|
1184
|
+
|
|
1185
|
+
```tsx
|
|
1186
|
+
import {
|
|
1187
|
+
ServerProvider,
|
|
1188
|
+
QueryResult,
|
|
1189
|
+
useModelData,
|
|
1190
|
+
encodeResourceUri,
|
|
1191
|
+
ApiErrorDisplay,
|
|
1192
|
+
Loading,
|
|
1193
|
+
} from "@malloy-publisher/sdk";
|
|
1194
|
+
import "@malloy-publisher/sdk/styles.css";
|
|
1195
|
+
import { Grid, Typography, Paper } from "@mui/material";
|
|
1196
|
+
|
|
1197
|
+
function Dashboard() {
|
|
1198
|
+
const resourceUri = encodeResourceUri({
|
|
1199
|
+
projectName: "my-project",
|
|
1200
|
+
packageName: "analytics",
|
|
1201
|
+
modelPath: "models/sales.malloy",
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
const { data, isLoading, isError, error } = useModelData(resourceUri);
|
|
1205
|
+
|
|
1206
|
+
if (isLoading) return <Loading text="Loading dashboard..." />;
|
|
1207
|
+
if (isError) return <ApiErrorDisplay error={error} context="Dashboard" />;
|
|
1208
|
+
|
|
1209
|
+
return (
|
|
1210
|
+
<Grid container spacing={3}>
|
|
1211
|
+
<Grid item xs={12}>
|
|
1212
|
+
<Typography variant="h4">Sales Dashboard</Typography>
|
|
1213
|
+
</Grid>
|
|
1214
|
+
|
|
1215
|
+
<Grid item xs={12} md={6}>
|
|
1216
|
+
<Paper sx={{ p: 2 }}>
|
|
1217
|
+
<Typography variant="h6">Sales by Region</Typography>
|
|
1218
|
+
<QueryResult
|
|
1219
|
+
sourceName="orders"
|
|
1220
|
+
queryName="by_region"
|
|
1221
|
+
resourceUri={resourceUri}
|
|
1222
|
+
/>
|
|
1223
|
+
</Paper>
|
|
1224
|
+
</Grid>
|
|
1225
|
+
|
|
1226
|
+
<Grid item xs={12} md={6}>
|
|
1227
|
+
<Paper sx={{ p: 2 }}>
|
|
1228
|
+
<Typography variant="h6">Monthly Trends</Typography>
|
|
1229
|
+
<QueryResult
|
|
1230
|
+
sourceName="orders"
|
|
1231
|
+
queryName="monthly_trends"
|
|
1232
|
+
resourceUri={resourceUri}
|
|
1233
|
+
/>
|
|
1234
|
+
</Paper>
|
|
1235
|
+
</Grid>
|
|
1236
|
+
|
|
1237
|
+
<Grid item xs={12}>
|
|
1238
|
+
<Paper sx={{ p: 2 }}>
|
|
1239
|
+
<Typography variant="h6">Custom Query</Typography>
|
|
1240
|
+
<QueryResult
|
|
1241
|
+
query="run: orders -> {
|
|
1242
|
+
group_by: product_category
|
|
1243
|
+
aggregate:
|
|
1244
|
+
total_revenue is sum(revenue)
|
|
1245
|
+
avg_order_value is avg(order_value)
|
|
1246
|
+
}"
|
|
1247
|
+
resourceUri={resourceUri}
|
|
1248
|
+
/>
|
|
1249
|
+
</Paper>
|
|
1250
|
+
</Grid>
|
|
1251
|
+
</Grid>
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
function App() {
|
|
1256
|
+
return (
|
|
1257
|
+
<ServerProvider baseURL="http://localhost:4000/api/v0">
|
|
1258
|
+
<Dashboard />
|
|
1259
|
+
</ServerProvider>
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1264
|
+
---
|
|
1265
|
+
|
|
1266
|
+
### Example: Data Table with Raw Query Data
|
|
1267
|
+
|
|
1268
|
+
```tsx
|
|
1269
|
+
import {
|
|
1270
|
+
ServerProvider,
|
|
1271
|
+
useRawQueryData,
|
|
1272
|
+
encodeResourceUri,
|
|
1273
|
+
Loading,
|
|
1274
|
+
ApiErrorDisplay,
|
|
1275
|
+
} from "@malloy-publisher/sdk";
|
|
1276
|
+
import { DataGrid } from "@mui/x-data-grid";
|
|
1277
|
+
|
|
1278
|
+
function DataTable() {
|
|
1279
|
+
const resourceUri = encodeResourceUri({
|
|
1280
|
+
projectName: "my-project",
|
|
1281
|
+
packageName: "analytics",
|
|
1282
|
+
modelPath: "models/customers.malloy",
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
const { data, isLoading, isError, error } = useRawQueryData({
|
|
1286
|
+
resourceUri,
|
|
1287
|
+
modelPath: "models/customers.malloy",
|
|
1288
|
+
sourceName: "customers",
|
|
1289
|
+
queryName: "all_customers",
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
if (isLoading) return <Loading text="Loading data..." />;
|
|
1293
|
+
if (isError) return <ApiErrorDisplay error={error} />;
|
|
1294
|
+
|
|
1295
|
+
const columns =
|
|
1296
|
+
data.length > 0
|
|
1297
|
+
? Object.keys(data[0]).map((key) => ({
|
|
1298
|
+
field: key,
|
|
1299
|
+
headerName: key,
|
|
1300
|
+
width: 150,
|
|
1301
|
+
}))
|
|
1302
|
+
: [];
|
|
1303
|
+
|
|
1304
|
+
return (
|
|
1305
|
+
<DataGrid
|
|
1306
|
+
rows={data.map((row, i) => ({ id: i, ...row }))}
|
|
1307
|
+
columns={columns}
|
|
1308
|
+
pageSize={10}
|
|
1309
|
+
autoHeight
|
|
1310
|
+
/>
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
```
|
|
1314
|
+
|
|
1315
|
+
---
|
|
1316
|
+
|
|
1317
|
+
### Example: Interactive Model Explorer
|
|
1318
|
+
|
|
1319
|
+
```tsx
|
|
1320
|
+
import {
|
|
1321
|
+
ServerProvider,
|
|
1322
|
+
ModelExplorer,
|
|
1323
|
+
encodeResourceUri,
|
|
1324
|
+
} from "@malloy-publisher/sdk";
|
|
1325
|
+
import "@malloy-publisher/sdk/styles.css";
|
|
1326
|
+
import "@malloy-publisher/sdk/malloy-explorer.css";
|
|
1327
|
+
import { useState } from "react";
|
|
1328
|
+
|
|
1329
|
+
function Explorer() {
|
|
1330
|
+
const [selectedQuery, setSelectedQuery] = useState(null);
|
|
1331
|
+
|
|
1332
|
+
const resourceUri = encodeResourceUri({
|
|
1333
|
+
projectName: "my-project",
|
|
1334
|
+
packageName: "analytics",
|
|
1335
|
+
modelPath: "models/orders.malloy",
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
return (
|
|
1339
|
+
<div style={{ display: "flex", gap: "20px" }}>
|
|
1340
|
+
<div style={{ flex: 1 }}>
|
|
1341
|
+
<h2>Build Your Query</h2>
|
|
1342
|
+
<ModelExplorer
|
|
1343
|
+
resourceUri={resourceUri}
|
|
1344
|
+
onChange={(result) => {
|
|
1345
|
+
setSelectedQuery(result);
|
|
1346
|
+
console.log("Generated Query:", result.query);
|
|
1347
|
+
}}
|
|
1348
|
+
/>
|
|
1349
|
+
</div>
|
|
1350
|
+
|
|
1351
|
+
{selectedQuery && (
|
|
1352
|
+
<div style={{ flex: 1 }}>
|
|
1353
|
+
<h2>Query Preview</h2>
|
|
1354
|
+
<pre>{selectedQuery.query}</pre>
|
|
1355
|
+
</div>
|
|
1356
|
+
)}
|
|
1357
|
+
</div>
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
```
|
|
1361
|
+
|
|
1362
|
+
---
|
|
1363
|
+
|
|
1364
|
+
### Example: Lightweight Client-Only Setup
|
|
1365
|
+
|
|
1366
|
+
For minimal bundle size when you only need API access:
|
|
1367
|
+
|
|
1368
|
+
```tsx
|
|
1369
|
+
// Use the client entry point
|
|
1370
|
+
import { ServerProvider, useServer } from "@malloy-publisher/sdk/client";
|
|
1371
|
+
|
|
1372
|
+
function MyApp() {
|
|
1373
|
+
return (
|
|
1374
|
+
<ServerProvider baseURL="http://localhost:4000/api/v0">
|
|
1375
|
+
<ProjectList />
|
|
1376
|
+
</ServerProvider>
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
function ProjectList() {
|
|
1381
|
+
const { apiClients } = useServer();
|
|
1382
|
+
const [projects, setProjects] = useState([]);
|
|
1383
|
+
|
|
1384
|
+
useEffect(() => {
|
|
1385
|
+
apiClients.projects
|
|
1386
|
+
.listProjects()
|
|
1387
|
+
.then((response) => setProjects(response.data));
|
|
1388
|
+
}, []);
|
|
1389
|
+
|
|
1390
|
+
return (
|
|
1391
|
+
<ul>
|
|
1392
|
+
{projects.map((p) => (
|
|
1393
|
+
<li key={p.name}>{p.name}</li>
|
|
1394
|
+
))}
|
|
1395
|
+
</ul>
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
```
|
|
1399
|
+
|
|
1400
|
+
---
|
|
1401
|
+
|
|
1402
|
+
## API Reference
|
|
1403
|
+
|
|
1404
|
+
### Exported Components
|
|
1405
|
+
|
|
1406
|
+
| Component | Description |
|
|
1407
|
+
| ------------------------- | ----------------------------------------------- |
|
|
1408
|
+
| `ServerProvider` | Required context provider for API access |
|
|
1409
|
+
| `Home` | Project listing landing page |
|
|
1410
|
+
| `Project` | Package listing for a project |
|
|
1411
|
+
| `Package` | Package detail (models, notebooks, connections) |
|
|
1412
|
+
| `Model` | Full model explorer with visual query builder |
|
|
1413
|
+
| `ModelExplorer` | Lower-level query builder component |
|
|
1414
|
+
| `ModelExplorerDialog` | Model explorer in a modal dialog |
|
|
1415
|
+
| `Notebook` | Read-only notebook viewer |
|
|
1416
|
+
| `Workbook` | Interactive workbook editor |
|
|
1417
|
+
| `WorkbookList` | List workbooks from storage |
|
|
1418
|
+
| `WorkbookManager` | Workbook state management class |
|
|
1419
|
+
| `WorkbookStorageProvider` | Context for workbook storage |
|
|
1420
|
+
| `QueryResult` | Execute and display query |
|
|
1421
|
+
| `RenderedResult` | Render Malloy result JSON |
|
|
1422
|
+
| `EmbeddedQueryResult` | Render serialized query config |
|
|
1423
|
+
| `Loading` | Loading spinner with text |
|
|
1424
|
+
| `ApiErrorDisplay` | Error display component |
|
|
1425
|
+
| `AnalyzePackageButton` | Create/manage workbooks |
|
|
1426
|
+
| `SourcesExplorer` | Source schema browser |
|
|
1427
|
+
| `ConnectionExplorer` | Connection management UI |
|
|
1428
|
+
|
|
1429
|
+
### Exported Hooks
|
|
1430
|
+
|
|
1431
|
+
| Hook | Description |
|
|
1432
|
+
| ------------------------------- | ----------------------------------- |
|
|
1433
|
+
| `useServer` | Access ServerProvider context |
|
|
1434
|
+
| `useQueryWithApiError` | React Query with error handling |
|
|
1435
|
+
| `useMutationWithApiError` | Mutations with error handling |
|
|
1436
|
+
| `useModelData` | Fetch compiled model |
|
|
1437
|
+
| `useRawQueryData` | Execute query, get raw data |
|
|
1438
|
+
| `useRouterClickHandler` | Smart navigation with modifier keys |
|
|
1439
|
+
| `useWorkbookStorage` | Access workbook storage context |
|
|
1440
|
+
| `useDimensionFiltersFromSpec` | Programmatic dimensional filtering |
|
|
1441
|
+
|
|
1442
|
+
### Exported Utilities
|
|
1443
|
+
|
|
1444
|
+
| Utility | Description |
|
|
1445
|
+
| --------------------------- | ----------------------------------- |
|
|
1446
|
+
| `encodeResourceUri` | Create resource URI from components |
|
|
1447
|
+
| `parseResourceUri` | Parse resource URI to components |
|
|
1448
|
+
| `createEmbeddedQueryResult` | Serialize query config |
|
|
1449
|
+
| `BrowserWorkbookStorage` | localStorage-based workbook storage |
|
|
1450
|
+
| `globalQueryClient` | Shared React Query client |
|
|
1451
|
+
|
|
1452
|
+
### Exported Types
|
|
1453
|
+
|
|
1454
|
+
| Type | Description |
|
|
1455
|
+
| ------------------------ | ------------------------------------ |
|
|
1456
|
+
| `ParsedResource` | Parsed resource URI components |
|
|
1457
|
+
| `ServerContextValue` | Server context interface |
|
|
1458
|
+
| `ServerProviderProps` | ServerProvider props |
|
|
1459
|
+
| `QueryExplorerResult` | Query builder result |
|
|
1460
|
+
| `SourceAndPath` | Source info with model path |
|
|
1461
|
+
| `WorkbookStorage` | Workbook storage interface |
|
|
1462
|
+
| `WorkbookLocator` | Workbook path + workspace |
|
|
1463
|
+
| `Workspace` | Workspace metadata |
|
|
1464
|
+
| `ApiError` | Standardized API error |
|
|
1465
|
+
| `ModelExplorerProps` | ModelExplorer props |
|
|
1466
|
+
| `DimensionFiltersConfig` | Dimensional filter configuration |
|
|
1467
|
+
|
|
1468
|
+
---
|
|
1469
|
+
|
|
1470
|
+
## Additional Resources
|
|
1471
|
+
|
|
1472
|
+
- [Publisher GitHub Repository](https://github.com/malloydata/publisher)
|
|
1473
|
+
- [Malloy Language Reference](https://docs.malloydata.dev/)
|
|
1474
|
+
- [Malloy Slack Community](https://join.slack.com/t/malloy-community/shared_invite/zt-1kgfwgi5g-CrsdaRqs81QY67QW0~t_uw)
|