@malloy-publisher/sdk 0.0.80 → 0.0.81
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/components/Model/Model.d.ts +3 -1
- package/dist/components/Package/index.d.ts +1 -0
- package/dist/components/Project/index.d.ts +1 -0
- package/dist/components/Workbook/BrowserWorkbookStorage.d.ts +11 -0
- package/dist/components/{MutableNotebook → Workbook}/EditableMalloyCell.d.ts +2 -2
- package/dist/components/{MutableNotebook → Workbook}/MutableCell.d.ts +3 -3
- package/dist/components/Workbook/Workbook.d.ts +9 -0
- package/dist/components/Workbook/WorkbookList.d.ts +7 -0
- package/dist/components/Workbook/WorkbookManager.d.ts +87 -0
- package/dist/components/Workbook/WorkbookStorage.d.ts +18 -0
- package/dist/components/Workbook/WorkbookStorageProvider.d.ts +12 -0
- package/dist/components/Workbook/index.d.ts +7 -0
- package/dist/components/index.d.ts +3 -1
- package/dist/index.cjs.js +62 -62
- package/dist/index.es.js +6466 -6337
- package/package.json +3 -2
- package/src/components/AnalyzePackageButton.tsx +121 -24
- package/src/components/Model/Model.tsx +7 -1
- package/src/components/Package/index.ts +1 -0
- package/src/components/Project/index.ts +1 -0
- package/src/components/Workbook/BrowserWorkbookStorage.ts +100 -0
- package/src/components/{MutableNotebook → Workbook}/EditableMalloyCell.tsx +2 -2
- package/src/components/{MutableNotebook → Workbook}/MutableCell.tsx +3 -3
- package/src/components/{MutableNotebook/MutableNotebook.tsx → Workbook/Workbook.tsx} +81 -57
- package/src/components/Workbook/WorkbookList.tsx +111 -0
- package/src/components/Workbook/WorkbookManager.ts +230 -0
- package/src/components/Workbook/WorkbookStorage.ts +54 -0
- package/src/components/Workbook/WorkbookStorageProvider.tsx +37 -0
- package/src/components/Workbook/index.ts +7 -0
- package/src/components/index.ts +3 -1
- package/dist/components/MutableNotebook/BrowserNotebookStorage.d.ts +0 -9
- package/dist/components/MutableNotebook/MutableNotebook.d.ts +0 -8
- package/dist/components/MutableNotebook/MutableNotebookList.d.ts +0 -6
- package/dist/components/MutableNotebook/NotebookStorage.d.ts +0 -11
- package/dist/components/MutableNotebook/NotebookStorageProvider.d.ts +0 -14
- package/dist/components/MutableNotebook/index.d.ts +0 -5
- package/dist/components/NotebookManager.d.ts +0 -86
- package/src/components/MutableNotebook/BrowserNotebookStorage.ts +0 -58
- package/src/components/MutableNotebook/MutableNotebookList.tsx +0 -69
- package/src/components/MutableNotebook/NotebookStorage.ts +0 -27
- package/src/components/MutableNotebook/NotebookStorageProvider.tsx +0 -43
- package/src/components/MutableNotebook/index.ts +0 -8
- package/src/components/NotebookManager.ts +0 -225
- /package/dist/components/{MutableNotebook → Workbook}/ModelPicker.d.ts +0 -0
- /package/src/components/{MutableNotebook → Workbook}/ModelPicker.tsx +0 -0
|
@@ -20,21 +20,22 @@ import React from "react";
|
|
|
20
20
|
import { Configuration, ModelsApi } from "../../client";
|
|
21
21
|
import { useRouterClickHandler } from "../click_helper";
|
|
22
22
|
import { SourceAndPath } from "../Model/SourcesExplorer";
|
|
23
|
-
import {
|
|
23
|
+
import { WorkbookManager } from "./WorkbookManager";
|
|
24
24
|
import { usePackage } from "../Package";
|
|
25
25
|
import { useServer } from "../ServerProvider";
|
|
26
26
|
import { StyledCard, StyledCardContent, StyledCardMedia } from "../styles";
|
|
27
27
|
import { MutableCell } from "./MutableCell";
|
|
28
|
-
import {
|
|
28
|
+
import { useWorkbookStorage } from "./WorkbookStorageProvider";
|
|
29
29
|
|
|
30
30
|
import * as Malloy from "@malloydata/malloy-interfaces";
|
|
31
31
|
import { ModelPicker } from "./ModelPicker";
|
|
32
32
|
import { getAxiosConfig } from "../../hooks";
|
|
33
|
+
import { WorkbookLocator } from "./WorkbookStorage";
|
|
33
34
|
|
|
34
35
|
const modelsApi = new ModelsApi(new Configuration());
|
|
35
36
|
|
|
36
|
-
interface
|
|
37
|
-
|
|
37
|
+
interface WorkbookProps {
|
|
38
|
+
workbookPath?: WorkbookLocator;
|
|
38
39
|
expandCodeCells?: boolean;
|
|
39
40
|
expandEmbeddings?: boolean;
|
|
40
41
|
hideEmbeddingIcons?: boolean;
|
|
@@ -45,28 +46,32 @@ interface PathToSources {
|
|
|
45
46
|
sourceInfos: Malloy.SourceInfo[];
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
export default function
|
|
49
|
-
|
|
49
|
+
export default function Workbook({
|
|
50
|
+
workbookPath,
|
|
50
51
|
expandCodeCells,
|
|
51
52
|
expandEmbeddings,
|
|
52
53
|
hideEmbeddingIcons,
|
|
53
|
-
}:
|
|
54
|
+
}: WorkbookProps) {
|
|
54
55
|
const navigate = useRouterClickHandler();
|
|
55
|
-
const
|
|
56
|
+
const packageContext = usePackage();
|
|
57
|
+
const { projectName, packageName, versionId } = packageContext;
|
|
56
58
|
const { server, getAccessToken } = useServer();
|
|
57
|
-
const {
|
|
59
|
+
const { workbookStorage } = useWorkbookStorage();
|
|
60
|
+
const [lastError, setLastError] = React.useState<string | undefined>(
|
|
61
|
+
undefined,
|
|
62
|
+
);
|
|
58
63
|
if (!projectName || !packageName) {
|
|
59
64
|
throw new Error(
|
|
60
65
|
"Project and package must be provided via PubliserPackageProvider",
|
|
61
66
|
);
|
|
62
67
|
}
|
|
63
|
-
if (!
|
|
68
|
+
if (!workbookStorage) {
|
|
64
69
|
throw new Error(
|
|
65
|
-
"
|
|
70
|
+
"Workbook storage be provided via WorkbookStorageProvider",
|
|
66
71
|
);
|
|
67
72
|
}
|
|
68
|
-
const [
|
|
69
|
-
|
|
73
|
+
const [workbookData, setWorkbookData] = React.useState<
|
|
74
|
+
WorkbookManager | undefined
|
|
70
75
|
>();
|
|
71
76
|
const [editingMalloyIndex, setEditingMalloyIndex] = React.useState<
|
|
72
77
|
number | undefined
|
|
@@ -87,11 +92,11 @@ export default function MutableNotebook({
|
|
|
87
92
|
setMenuIndex(null);
|
|
88
93
|
};
|
|
89
94
|
const handleAddCell = (isMarkdown: boolean, index: number) => {
|
|
90
|
-
|
|
95
|
+
workbookData.insertCell(index, {
|
|
91
96
|
isMarkdown,
|
|
92
97
|
value: "",
|
|
93
98
|
});
|
|
94
|
-
|
|
99
|
+
saveWorkbook();
|
|
95
100
|
if (isMarkdown) {
|
|
96
101
|
setEditingMarkdownIndex(index);
|
|
97
102
|
} else {
|
|
@@ -105,9 +110,16 @@ export default function MutableNotebook({
|
|
|
105
110
|
setDeleteDialogOpen(true);
|
|
106
111
|
};
|
|
107
112
|
|
|
108
|
-
const handleDeleteConfirm = (event?: React.MouseEvent) => {
|
|
109
|
-
if (
|
|
110
|
-
|
|
113
|
+
const handleDeleteConfirm = async (event?: React.MouseEvent) => {
|
|
114
|
+
if (workbookPath && workbookStorage && packageContext) {
|
|
115
|
+
await workbookStorage
|
|
116
|
+
.deleteWorkbook(packageContext, workbookPath)
|
|
117
|
+
.then(() => {
|
|
118
|
+
setLastError(undefined);
|
|
119
|
+
})
|
|
120
|
+
.catch((error) => {
|
|
121
|
+
setLastError(`Error deleting workbook: ${error.message}`);
|
|
122
|
+
});
|
|
111
123
|
}
|
|
112
124
|
setDeleteDialogOpen(false);
|
|
113
125
|
navigate(`/${projectName}/${packageName}`, event);
|
|
@@ -117,12 +129,17 @@ export default function MutableNotebook({
|
|
|
117
129
|
setDeleteDialogOpen(false);
|
|
118
130
|
};
|
|
119
131
|
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
132
|
+
const saveWorkbook = React.useCallback(async () => {
|
|
133
|
+
try {
|
|
134
|
+
setWorkbookData(await workbookData.saveWorkbook());
|
|
135
|
+
setLastError(undefined);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
setLastError(`Error saving workbook: ${error.message}`);
|
|
138
|
+
}
|
|
139
|
+
}, [workbookData]);
|
|
123
140
|
React.useEffect(() => {
|
|
124
141
|
// Load SourceInfos from selected models and sync PathsToSources
|
|
125
|
-
if (!
|
|
142
|
+
if (!workbookData) {
|
|
126
143
|
return;
|
|
127
144
|
}
|
|
128
145
|
|
|
@@ -136,7 +153,7 @@ export default function MutableNotebook({
|
|
|
136
153
|
const newSourceAndPaths = [];
|
|
137
154
|
const promises = [];
|
|
138
155
|
|
|
139
|
-
for (const model of
|
|
156
|
+
for (const model of workbookData.getModels()) {
|
|
140
157
|
if (!modelPathToSourceInfo.has(model)) {
|
|
141
158
|
console.log("Fetching model from Publisher", model);
|
|
142
159
|
promises.push(
|
|
@@ -173,9 +190,9 @@ export default function MutableNotebook({
|
|
|
173
190
|
|
|
174
191
|
fetchModels();
|
|
175
192
|
}, [
|
|
176
|
-
//
|
|
193
|
+
// Work this cannot depend on sourceAndPaths because it will cause an infinite loop.
|
|
177
194
|
getAccessToken,
|
|
178
|
-
|
|
195
|
+
workbookData,
|
|
179
196
|
packageName,
|
|
180
197
|
projectName,
|
|
181
198
|
server,
|
|
@@ -183,19 +200,19 @@ export default function MutableNotebook({
|
|
|
183
200
|
]);
|
|
184
201
|
|
|
185
202
|
React.useEffect(() => {
|
|
186
|
-
if (!
|
|
203
|
+
if (!workbookPath) {
|
|
187
204
|
return;
|
|
188
205
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
)
|
|
195
|
-
);
|
|
196
|
-
}, [
|
|
206
|
+
WorkbookManager.loadWorkbook(
|
|
207
|
+
workbookStorage,
|
|
208
|
+
packageContext,
|
|
209
|
+
workbookPath,
|
|
210
|
+
).then((workbookData) => {
|
|
211
|
+
setWorkbookData(workbookData);
|
|
212
|
+
});
|
|
213
|
+
}, [workbookPath, workbookStorage, packageContext]);
|
|
197
214
|
|
|
198
|
-
if (!
|
|
215
|
+
if (!workbookData) {
|
|
199
216
|
return <div>Loading...</div>;
|
|
200
217
|
}
|
|
201
218
|
const getSourceList = (sourceAndPaths: PathToSources[]): SourceAndPath[] => {
|
|
@@ -246,14 +263,21 @@ export default function MutableNotebook({
|
|
|
246
263
|
flex: 2,
|
|
247
264
|
}}
|
|
248
265
|
>
|
|
249
|
-
{plusButton(false,
|
|
250
|
-
{plusButton(true,
|
|
266
|
+
{plusButton(false, workbookData.getCells().length)}
|
|
267
|
+
{plusButton(true, workbookData.getCells().length)}
|
|
251
268
|
</Box>
|
|
252
269
|
);
|
|
253
270
|
|
|
254
271
|
return (
|
|
255
272
|
<StyledCard variant="outlined">
|
|
256
273
|
<StyledCardContent>
|
|
274
|
+
{lastError && (
|
|
275
|
+
<Box sx={{ mb: 2 }}>
|
|
276
|
+
<Typography color="error" variant="body2">
|
|
277
|
+
{lastError}
|
|
278
|
+
</Typography>
|
|
279
|
+
</Box>
|
|
280
|
+
)}
|
|
257
281
|
<Stack
|
|
258
282
|
sx={{
|
|
259
283
|
flexDirection: "row",
|
|
@@ -281,7 +305,7 @@ export default function MutableNotebook({
|
|
|
281
305
|
ml: 1,
|
|
282
306
|
}}
|
|
283
307
|
>
|
|
284
|
-
{`${projectName} > ${packageName} > ${
|
|
308
|
+
{`${projectName} > ${packageName} > ${workbookPath.path}`}
|
|
285
309
|
</Typography>
|
|
286
310
|
</Stack>
|
|
287
311
|
<Stack sx={{ display: "flex", flexDirection: "row", gap: 1 }}>
|
|
@@ -293,7 +317,7 @@ export default function MutableNotebook({
|
|
|
293
317
|
mb: 1,
|
|
294
318
|
}}
|
|
295
319
|
>
|
|
296
|
-
<ExportMalloyButton
|
|
320
|
+
<ExportMalloyButton workbookData={workbookData} />
|
|
297
321
|
</Box>
|
|
298
322
|
<Box
|
|
299
323
|
sx={{
|
|
@@ -314,12 +338,12 @@ export default function MutableNotebook({
|
|
|
314
338
|
open={deleteDialogOpen}
|
|
315
339
|
onClose={handleDeleteCancel}
|
|
316
340
|
>
|
|
317
|
-
<DialogTitle>Delete
|
|
341
|
+
<DialogTitle>Delete Workbook</DialogTitle>
|
|
318
342
|
<DialogContent>
|
|
319
343
|
<DialogContentText>
|
|
320
|
-
Are you sure you want to delete the
|
|
344
|
+
Are you sure you want to delete the workbook
|
|
321
345
|
"
|
|
322
|
-
{
|
|
346
|
+
{workbookPath.path}"? This action cannot be
|
|
323
347
|
undone.
|
|
324
348
|
</DialogContentText>
|
|
325
349
|
</DialogContent>
|
|
@@ -355,10 +379,10 @@ export default function MutableNotebook({
|
|
|
355
379
|
>
|
|
356
380
|
<Box sx={{ flex: 1 }}>
|
|
357
381
|
<ModelPicker
|
|
358
|
-
initialSelectedModels={
|
|
382
|
+
initialSelectedModels={workbookData.getModels()}
|
|
359
383
|
onModelChange={(models) => {
|
|
360
|
-
|
|
361
|
-
|
|
384
|
+
setWorkbookData(workbookData.setModels(models));
|
|
385
|
+
saveWorkbook();
|
|
362
386
|
}}
|
|
363
387
|
/>
|
|
364
388
|
</Box>
|
|
@@ -367,7 +391,7 @@ export default function MutableNotebook({
|
|
|
367
391
|
</StyledCardContent>
|
|
368
392
|
<StyledCardMedia>
|
|
369
393
|
<Stack>
|
|
370
|
-
{
|
|
394
|
+
{workbookData.getCells().length === 0 && (
|
|
371
395
|
<>
|
|
372
396
|
<Typography
|
|
373
397
|
sx={{
|
|
@@ -387,12 +411,12 @@ export default function MutableNotebook({
|
|
|
387
411
|
</Typography>
|
|
388
412
|
</>
|
|
389
413
|
)}
|
|
390
|
-
{
|
|
414
|
+
{workbookData.getCells().map((cell, index) => (
|
|
391
415
|
<React.Fragment
|
|
392
|
-
key={`${index}-${
|
|
416
|
+
key={`${index}-${workbookData.getCells().length}`}
|
|
393
417
|
>
|
|
394
418
|
<MutableCell
|
|
395
|
-
key={`${index}-${cell.isMarkdown}-${
|
|
419
|
+
key={`${index}-${cell.isMarkdown}-${workbookPath}-${projectName}-${packageName}`}
|
|
396
420
|
cell={cell}
|
|
397
421
|
addButtonCallback={(isMarkdown) =>
|
|
398
422
|
plusButton(isMarkdown, index)
|
|
@@ -404,12 +428,12 @@ export default function MutableNotebook({
|
|
|
404
428
|
editingMarkdown={editingMarkdownIndex === index}
|
|
405
429
|
editingMalloy={editingMalloyIndex === index}
|
|
406
430
|
onDelete={() => {
|
|
407
|
-
|
|
408
|
-
|
|
431
|
+
setWorkbookData(workbookData.deleteCell(index));
|
|
432
|
+
saveWorkbook();
|
|
409
433
|
}}
|
|
410
434
|
onCellChange={(cell) => {
|
|
411
|
-
|
|
412
|
-
|
|
435
|
+
setWorkbookData(workbookData.setCell(index, cell));
|
|
436
|
+
saveWorkbook();
|
|
413
437
|
}}
|
|
414
438
|
onEdit={() => {
|
|
415
439
|
if (cell.isMarkdown) {
|
|
@@ -459,14 +483,14 @@ export default function MutableNotebook({
|
|
|
459
483
|
}
|
|
460
484
|
|
|
461
485
|
function ExportMalloyButton({
|
|
462
|
-
|
|
486
|
+
workbookData,
|
|
463
487
|
}: {
|
|
464
|
-
|
|
488
|
+
workbookData: WorkbookManager;
|
|
465
489
|
}) {
|
|
466
490
|
const [copied, setCopied] = React.useState(false);
|
|
467
491
|
const handleExport = async () => {
|
|
468
|
-
if (!
|
|
469
|
-
const malloy =
|
|
492
|
+
if (!workbookData) return;
|
|
493
|
+
const malloy = workbookData.toMalloyWorkbook();
|
|
470
494
|
try {
|
|
471
495
|
await navigator.clipboard.writeText(malloy);
|
|
472
496
|
setCopied(true);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Box,
|
|
3
|
+
Divider,
|
|
4
|
+
List,
|
|
5
|
+
ListItem,
|
|
6
|
+
ListItemText,
|
|
7
|
+
Typography,
|
|
8
|
+
} from "@mui/material";
|
|
9
|
+
import React from "react";
|
|
10
|
+
import { useWorkbookStorage } from "./WorkbookStorageProvider";
|
|
11
|
+
import { usePackage } from "../Package";
|
|
12
|
+
import { WorkbookLocator } from "./WorkbookStorage";
|
|
13
|
+
|
|
14
|
+
interface WorkbookListProps {
|
|
15
|
+
onWorkbookClick: (
|
|
16
|
+
workbook: WorkbookLocator,
|
|
17
|
+
event: React.MouseEvent,
|
|
18
|
+
) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function WorkbookList({ onWorkbookClick }: WorkbookListProps) {
|
|
22
|
+
const { workbookStorage } = useWorkbookStorage();
|
|
23
|
+
const packageContext = usePackage();
|
|
24
|
+
const [workbooks, setWorkbooks] = React.useState<WorkbookLocator[]>([]);
|
|
25
|
+
const [lastError, setLastError] = React.useState<string | undefined>(
|
|
26
|
+
undefined,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
React.useEffect(() => {
|
|
30
|
+
if (workbookStorage) {
|
|
31
|
+
workbookStorage
|
|
32
|
+
.listWorkspaces(packageContext, false)
|
|
33
|
+
.then((workspaces) => {
|
|
34
|
+
const allWorkbooks: WorkbookLocator[] = [];
|
|
35
|
+
Promise.all(
|
|
36
|
+
workspaces.map(async (workspace) => {
|
|
37
|
+
await workbookStorage
|
|
38
|
+
.listWorkbooks(workspace, packageContext)
|
|
39
|
+
.then((newWorkbooks) => {
|
|
40
|
+
allWorkbooks.push(...newWorkbooks);
|
|
41
|
+
})
|
|
42
|
+
.catch((error) => {
|
|
43
|
+
setLastError(
|
|
44
|
+
`Error listing workbooks: ${error.message}`,
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
}),
|
|
48
|
+
).then(() => {
|
|
49
|
+
setWorkbooks(allWorkbooks);
|
|
50
|
+
setLastError(undefined);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}, [workbookStorage, packageContext]);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<>
|
|
58
|
+
{lastError && (
|
|
59
|
+
<Box sx={{ mb: 2 }}>
|
|
60
|
+
<Typography color="error" variant="body2">
|
|
61
|
+
{lastError}
|
|
62
|
+
</Typography>
|
|
63
|
+
</Box>
|
|
64
|
+
)}
|
|
65
|
+
<Divider />
|
|
66
|
+
<Box
|
|
67
|
+
sx={{
|
|
68
|
+
maxHeight: "300px",
|
|
69
|
+
overflow: "auto",
|
|
70
|
+
"&::-webkit-scrollbar": {
|
|
71
|
+
width: "8px",
|
|
72
|
+
},
|
|
73
|
+
"&::-webkit-scrollbar-track": {
|
|
74
|
+
background: "transparent",
|
|
75
|
+
},
|
|
76
|
+
"&::-webkit-scrollbar-thumb": {
|
|
77
|
+
background: "rgba(0,0,0,0.2)",
|
|
78
|
+
borderRadius: "4px",
|
|
79
|
+
},
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<List dense>
|
|
83
|
+
{workbooks.length === 0 && (
|
|
84
|
+
<ListItem>
|
|
85
|
+
<ListItemText
|
|
86
|
+
primary="No workbooks found."
|
|
87
|
+
sx={{ textAlign: "center" }}
|
|
88
|
+
/>
|
|
89
|
+
</ListItem>
|
|
90
|
+
)}
|
|
91
|
+
{workbooks.map((workbook) => (
|
|
92
|
+
<ListItem
|
|
93
|
+
key={workbook.path}
|
|
94
|
+
onClick={(event: React.MouseEvent) =>
|
|
95
|
+
onWorkbookClick(workbook, event)
|
|
96
|
+
}
|
|
97
|
+
sx={{
|
|
98
|
+
cursor: "pointer",
|
|
99
|
+
"&:hover": {
|
|
100
|
+
backgroundColor: "action.hover",
|
|
101
|
+
},
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
<ListItemText primary={workbook.path} />
|
|
105
|
+
</ListItem>
|
|
106
|
+
))}
|
|
107
|
+
</List>
|
|
108
|
+
</Box>
|
|
109
|
+
</>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { PackageContextProps } from "../Package";
|
|
2
|
+
import type { WorkbookLocator, WorkbookStorage } from "./WorkbookStorage";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Interface representing the data structure of a Mutable Workbook
|
|
6
|
+
* @interface WorkbookData
|
|
7
|
+
* @property {string[]} models - Array of model paths used in the workbook
|
|
8
|
+
* @property {WorkbookCellValue[]} cells - Array of cells in the workbook
|
|
9
|
+
* @property {WorkbookLocator} workbookPath - Path to the workbook file (relative to project/package)
|
|
10
|
+
*/
|
|
11
|
+
export interface WorkbookData {
|
|
12
|
+
models: string[];
|
|
13
|
+
cells: WorkbookCellValue[];
|
|
14
|
+
workbookPath: WorkbookLocator;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Interface representing a cell in the workbook
|
|
19
|
+
* @interface WorkbookCellValue
|
|
20
|
+
* @property {boolean} isMarkdown - Whether the cell is a markdown cell
|
|
21
|
+
* @property {string} [value] - The content of the cell
|
|
22
|
+
* @property {string} [result] - The result of executing the cell
|
|
23
|
+
* @property {string} [modelPath] - modelPath associated with the query in the cell
|
|
24
|
+
* @property {string} [sourceName] - Name of the source associated with the cell
|
|
25
|
+
* @property {string} [queryInfo] - Information about the query in the cell
|
|
26
|
+
*/
|
|
27
|
+
export interface WorkbookCellValue {
|
|
28
|
+
isMarkdown: boolean;
|
|
29
|
+
value?: string;
|
|
30
|
+
result?: string;
|
|
31
|
+
modelPath?: string;
|
|
32
|
+
sourceName?: string;
|
|
33
|
+
queryInfo?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Class for managing workbook operations
|
|
38
|
+
* @class WorkbookManager
|
|
39
|
+
*/
|
|
40
|
+
export class WorkbookManager {
|
|
41
|
+
private isSaved: boolean;
|
|
42
|
+
private workbookStorage: WorkbookStorage;
|
|
43
|
+
private packageContext: PackageContextProps;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Creates a new WorkbookManager instance
|
|
47
|
+
* @param {WorkbookStorage} workbookStorage - Storage implementation
|
|
48
|
+
* @param {PackageContextProps} packageContext - Package context for storage
|
|
49
|
+
* @param {WorkbookData} workbookData - Initial workbook data
|
|
50
|
+
*/
|
|
51
|
+
constructor(
|
|
52
|
+
workbookStorage: WorkbookStorage,
|
|
53
|
+
packageContext: PackageContextProps,
|
|
54
|
+
private workbookData: WorkbookData,
|
|
55
|
+
) {
|
|
56
|
+
this.workbookStorage = workbookStorage;
|
|
57
|
+
this.packageContext = packageContext;
|
|
58
|
+
if (this.workbookData) {
|
|
59
|
+
this.isSaved = true;
|
|
60
|
+
} else {
|
|
61
|
+
this.workbookData = {
|
|
62
|
+
models: [],
|
|
63
|
+
cells: [],
|
|
64
|
+
workbookPath: undefined,
|
|
65
|
+
};
|
|
66
|
+
this.isSaved = false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Gets the current workbook data
|
|
72
|
+
* @returns {WorkbookData} The current workbook data
|
|
73
|
+
*/
|
|
74
|
+
getWorkbookData(): WorkbookData {
|
|
75
|
+
return this.workbookData;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Gets the current workbook path
|
|
80
|
+
* @returns {string} The path to the workbook
|
|
81
|
+
*/
|
|
82
|
+
getWorkbookPath(): WorkbookLocator {
|
|
83
|
+
return this.workbookData.workbookPath;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Renames the workbook and updates storage
|
|
88
|
+
* @param {string} workbookPath - New path for the workbook
|
|
89
|
+
* @returns {WorkbookManager} The updated WorkbookManager instance
|
|
90
|
+
*/
|
|
91
|
+
async renameWorkbook(workbookPath: string): Promise<WorkbookManager> {
|
|
92
|
+
if (this.workbookData.workbookPath.path !== workbookPath) {
|
|
93
|
+
try {
|
|
94
|
+
await this.workbookStorage.moveWorkbook(
|
|
95
|
+
this.packageContext,
|
|
96
|
+
this.workbookData.workbookPath,
|
|
97
|
+
{
|
|
98
|
+
path: workbookPath,
|
|
99
|
+
workspace: this.workbookData.workbookPath.workspace,
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
} catch {
|
|
103
|
+
// ignore if not found
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
this.workbookData.workbookPath.path = workbookPath;
|
|
107
|
+
this.isSaved = false;
|
|
108
|
+
return await this.saveWorkbook();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
getCells(): WorkbookCellValue[] {
|
|
112
|
+
return this.workbookData.cells;
|
|
113
|
+
}
|
|
114
|
+
deleteCell(index: number): WorkbookManager {
|
|
115
|
+
this.workbookData.cells = [
|
|
116
|
+
...this.workbookData.cells.slice(0, index),
|
|
117
|
+
...this.workbookData.cells.slice(index + 1),
|
|
118
|
+
];
|
|
119
|
+
this.isSaved = false;
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
insertCell(index: number, cell: WorkbookCellValue): WorkbookManager {
|
|
123
|
+
this.workbookData.cells = [
|
|
124
|
+
...this.workbookData.cells.slice(0, index),
|
|
125
|
+
cell,
|
|
126
|
+
...this.workbookData.cells.slice(index),
|
|
127
|
+
];
|
|
128
|
+
this.isSaved = false;
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
setCell(index: number, cell: WorkbookCellValue): WorkbookManager {
|
|
132
|
+
this.workbookData.cells[index] = cell;
|
|
133
|
+
this.isSaved = false;
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
setModels(models: string[]): WorkbookManager {
|
|
137
|
+
this.workbookData.models = models;
|
|
138
|
+
this.isSaved = false;
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
141
|
+
getModels(): string[] {
|
|
142
|
+
return this.workbookData.models;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
updateWorkbookData(workbookData: WorkbookData): WorkbookManager {
|
|
146
|
+
this.workbookData = workbookData;
|
|
147
|
+
this.isSaved = false;
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async saveWorkbook(): Promise<WorkbookManager> {
|
|
152
|
+
if (!this.isSaved) {
|
|
153
|
+
if (!this.workbookData.workbookPath) {
|
|
154
|
+
throw new Error("Workbook path is not set");
|
|
155
|
+
}
|
|
156
|
+
await this.workbookStorage.saveWorkbook(
|
|
157
|
+
this.packageContext,
|
|
158
|
+
this.workbookData.workbookPath,
|
|
159
|
+
JSON.stringify(this.workbookData),
|
|
160
|
+
);
|
|
161
|
+
this.isSaved = true;
|
|
162
|
+
}
|
|
163
|
+
return new WorkbookManager(
|
|
164
|
+
this.workbookStorage,
|
|
165
|
+
this.packageContext,
|
|
166
|
+
this.workbookData,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Converts the workbook data to a Malloy workbook string.
|
|
172
|
+
* @returns {string} The Malloy workbook string
|
|
173
|
+
*/
|
|
174
|
+
toMalloyWorkbook(): string {
|
|
175
|
+
return this.workbookData.cells
|
|
176
|
+
.map((cell) => {
|
|
177
|
+
if (cell.isMarkdown) {
|
|
178
|
+
return ">>>markdown\n" + cell.value;
|
|
179
|
+
} else {
|
|
180
|
+
return (
|
|
181
|
+
">>>malloy\n" +
|
|
182
|
+
`import {${cell.sourceName}}" from '${cell.modelPath}'"\n` +
|
|
183
|
+
cell.value +
|
|
184
|
+
"\n"
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
.join("\n");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static newWorkbook(
|
|
192
|
+
workbookStorage: WorkbookStorage,
|
|
193
|
+
packageContext: PackageContextProps,
|
|
194
|
+
): WorkbookManager {
|
|
195
|
+
return new WorkbookManager(workbookStorage, packageContext, undefined);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Creates a new workbook manager by loading from local storage.
|
|
200
|
+
* Returns an empty instance if the workbook is not found.
|
|
201
|
+
* @param workbookStorage - The storage implementation
|
|
202
|
+
* @param userContext - The user context for storage
|
|
203
|
+
* @param workbookPath - The path to the workbook file (relative to project/package)
|
|
204
|
+
*/
|
|
205
|
+
static async loadWorkbook(
|
|
206
|
+
workbookStorage: WorkbookStorage,
|
|
207
|
+
packageContext: PackageContextProps,
|
|
208
|
+
workbookPath: WorkbookLocator,
|
|
209
|
+
): Promise<WorkbookManager> {
|
|
210
|
+
let workbookData: WorkbookData | undefined = undefined;
|
|
211
|
+
console.log("loadWorkbook", workbookPath);
|
|
212
|
+
try {
|
|
213
|
+
const saved = await workbookStorage.getWorkbook(
|
|
214
|
+
packageContext,
|
|
215
|
+
workbookPath,
|
|
216
|
+
);
|
|
217
|
+
if (saved) {
|
|
218
|
+
workbookData = JSON.parse(saved);
|
|
219
|
+
}
|
|
220
|
+
} catch {
|
|
221
|
+
// Not found, create a new workbook
|
|
222
|
+
workbookData = {
|
|
223
|
+
models: [],
|
|
224
|
+
cells: [],
|
|
225
|
+
workbookPath: workbookPath,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return new WorkbookManager(workbookStorage, packageContext, workbookData);
|
|
229
|
+
}
|
|
230
|
+
}
|