@malloy-publisher/sdk 0.0.30 → 0.0.31

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@malloy-publisher/sdk",
3
3
  "description": "Malloy Publisher SDK",
4
- "version": "0.0.30",
4
+ "version": "0.0.31",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs.js",
7
7
  "module": "dist/index.es.js",
@@ -34,19 +34,19 @@
34
34
  "@emotion/react": "^11.14.0",
35
35
  "@emotion/styled": "^11.14.0",
36
36
  "@malloydata/malloy-explorer": "^0.0.285-dev250530165648",
37
- "@malloydata/malloy-query-builder": "^0.0.285",
38
37
  "@malloydata/malloy-interfaces": "^0.0.285",
38
+ "@malloydata/malloy-query-builder": "^0.0.285",
39
39
  "@malloydata/render": "^0.0.285",
40
- "@mui/icons-material": "^7.0.2",
41
- "@mui/material": "^7.0.2",
42
- "@mui/system": "^7.0.2",
40
+ "@mui/icons-material": "^7.1.1",
41
+ "@mui/material": "^7.1.1",
42
+ "@mui/system": "^7.1.1",
43
43
  "@mui/x-tree-view": "^7.16.0",
44
44
  "@react-spring/web": "^10.0.1",
45
45
  "@tanstack/react-query": "^5.59.16",
46
46
  "@uiw/react-md-editor": "^4.0.6",
47
47
  "axios": "^1.7.7",
48
- "markdown-to-jsx": "^7.5.0",
49
- "react-router-dom": "^6.26.1"
48
+ "markdown-to-jsx": "^7.7.6",
49
+ "react-router-dom": "^7.6.2"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@openapitools/openapi-generator-cli": "^2.13.5",
@@ -1,27 +1,28 @@
1
- import React, { useEffect } from "react";
1
+ import ContentCopyIcon from "@mui/icons-material/ContentCopy";
2
+ import LinkOutlinedIcon from "@mui/icons-material/LinkOutlined";
2
3
  import {
3
4
  Box,
4
- Typography,
5
- Stack,
6
5
  CardActions,
7
- Tooltip,
8
- IconButton,
9
6
  Collapse,
10
7
  Divider,
11
- Tabs,
8
+ IconButton,
9
+ Stack,
12
10
  Tab,
11
+ Tabs,
12
+ Tooltip,
13
+ Typography,
13
14
  } from "@mui/material";
14
15
  import { QueryClient, useQuery } from "@tanstack/react-query";
15
- import LinkOutlinedIcon from "@mui/icons-material/LinkOutlined";
16
- import ContentCopyIcon from "@mui/icons-material/ContentCopy";
16
+ import React, { useEffect } from "react";
17
17
  import { Configuration, ModelsApi } from "../../client";
18
- import { ModelCell } from "./ModelCell";
19
- import { StyledCard, StyledCardContent, StyledCardMedia } from "../styles";
20
18
  import { highlight } from "../highlighter";
19
+ import { StyledCard, StyledCardContent, StyledCardMedia } from "../styles";
20
+ import { ModelCell } from "./ModelCell";
21
21
 
22
22
  import "@malloydata/malloy-explorer/styles.css";
23
23
  import { usePublisherPackage } from "../Package/PublisherPackageProvider";
24
24
  import { SourceExplorerComponent } from "./SourcesExplorer";
25
+ import NamedQueries from "./NamedQueries";
25
26
  const modelsApi = new ModelsApi(new Configuration());
26
27
 
27
28
  const queryClient = new QueryClient();
@@ -136,28 +137,32 @@ export default function Model({
136
137
  })}
137
138
  </Tabs>
138
139
  )}
139
- <CardActions
140
- sx={{
141
- padding: "0px 10px 0px 10px",
142
- mb: "auto",
143
- mt: "auto",
144
- }}
145
- >
146
- <Tooltip
147
- title={
148
- embeddingExpanded ? "Hide Embedding" : "View Embedding"
149
- }
140
+ {!hideEmbeddingIcons && (
141
+ <CardActions
142
+ sx={{
143
+ padding: "0px 10px 0px 10px",
144
+ mb: "auto",
145
+ mt: "auto",
146
+ }}
150
147
  >
151
- <IconButton
152
- size="small"
153
- onClick={() => {
154
- setEmbeddingExpanded(!embeddingExpanded);
155
- }}
148
+ <Tooltip
149
+ title={
150
+ embeddingExpanded
151
+ ? "Hide Embedding"
152
+ : "View Embedding"
153
+ }
156
154
  >
157
- <LinkOutlinedIcon />
158
- </IconButton>
159
- </Tooltip>
160
- </CardActions>
155
+ <IconButton
156
+ size="small"
157
+ onClick={() => {
158
+ setEmbeddingExpanded(!embeddingExpanded);
159
+ }}
160
+ >
161
+ <LinkOutlinedIcon />
162
+ </IconButton>
163
+ </Tooltip>
164
+ </CardActions>
165
+ )}
161
166
  </Stack>
162
167
  <Collapse in={embeddingExpanded} timeout="auto" unmountOnExit>
163
168
  <Divider />
@@ -0,0 +1,156 @@
1
+ import {
2
+ Typography,
3
+ Accordion,
4
+ AccordionSummary,
5
+ AccordionDetails,
6
+ } from "@mui/material";
7
+ import { Box } from "@mui/system";
8
+ import { Query, QueryresultsApi } from "../../client/api";
9
+ import { StyledCard, StyledCardContent } from "../styles";
10
+
11
+ import { QueryClient, useMutation } from "@tanstack/react-query";
12
+ import { Configuration } from "../../client";
13
+ import { usePublisherPackage } from "../Package";
14
+ import React from "react";
15
+ import ResultContainer from "../RenderedResult/ResultContainer";
16
+
17
+ const queryResultsApi = new QueryresultsApi(new Configuration());
18
+ const queryClient = new QueryClient();
19
+
20
+ interface NamedQueryProps {
21
+ modelPath: string;
22
+ namedQueries: Array<Query>;
23
+ }
24
+
25
+ export default function NamedQueries({
26
+ namedQueries,
27
+ modelPath,
28
+ }: NamedQueryProps) {
29
+ const { server, projectName, packageName, versionId, accessToken } =
30
+ usePublisherPackage();
31
+ const [namedQueryResults, setNamedQueryResults] = React.useState<
32
+ Record<string, string>
33
+ >({});
34
+ const [expandedAccordions, setExpandedAccordions] = React.useState<
35
+ Record<string, boolean>
36
+ >({});
37
+
38
+ const mutation = useMutation(
39
+ {
40
+ mutationFn: ({ query }: { query: Query }) => {
41
+ const val = queryResultsApi.executeQuery(
42
+ projectName,
43
+ packageName,
44
+ modelPath,
45
+ undefined,
46
+ undefined,
47
+ query.name,
48
+ versionId,
49
+ {
50
+ baseURL: server,
51
+ withCredentials: !accessToken,
52
+ headers: {
53
+ Authorization: accessToken && `Bearer ${accessToken}`,
54
+ },
55
+ },
56
+ );
57
+ return val;
58
+ },
59
+ onSuccess: (data, { query }: { query: Query }) => {
60
+ if (data) {
61
+ setNamedQueryResults((prev) => ({
62
+ ...prev,
63
+ [query.name]: data.data.result,
64
+ }));
65
+ }
66
+ },
67
+ },
68
+ queryClient,
69
+ );
70
+
71
+ const handleAccordionChange =
72
+ (query: Query, queryKey: string) =>
73
+ (event: React.SyntheticEvent, isExpanded: boolean) => {
74
+ setExpandedAccordions((prev) => ({
75
+ ...prev,
76
+ [queryKey]: isExpanded,
77
+ }));
78
+
79
+ // Trigger mutation only if expanding and we haven't executed this query before
80
+ if (isExpanded && !namedQueryResults[query.name]) {
81
+ mutation.mutate({ query });
82
+ }
83
+ };
84
+
85
+ if (!namedQueries) {
86
+ return <div> Loading Named Queries</div>;
87
+ }
88
+ if (namedQueries.length == 0) {
89
+ return <div> No Named Queries</div>;
90
+ }
91
+
92
+ return (
93
+ <StyledCard variant="outlined">
94
+ <StyledCardContent>
95
+ <Typography variant="subtitle1">Named Queries</Typography>
96
+ </StyledCardContent>
97
+ <Box>
98
+ {namedQueries.map((query: any, idx: number) => {
99
+ const queryKey = query.name || `query-${idx}`;
100
+ return (
101
+ <Accordion
102
+ key={queryKey}
103
+ expanded={expandedAccordions[queryKey] || false}
104
+ onChange={handleAccordionChange(query, queryKey)}
105
+ >
106
+ <AccordionSummary>
107
+ <Typography variant="body1">
108
+ {query.name || `Query ${idx + 1}`}
109
+ </Typography>
110
+ </AccordionSummary>
111
+ <AccordionDetails>
112
+ {mutation.isPending && expandedAccordions[queryKey] && (
113
+ <div
114
+ style={{
115
+ marginTop: "10px",
116
+ fontStyle: "italic",
117
+ }}
118
+ >
119
+ Executing query...
120
+ </div>
121
+ )}
122
+ <ResultContainer
123
+ result={namedQueryResults[query.name]}
124
+ minHeight={300}
125
+ maxHeight={900}
126
+ />
127
+ {Array.isArray(query.annotations) &&
128
+ query.annotations.length > 0 && (
129
+ <Box sx={{ mt: 1 }}>
130
+ <Typography
131
+ variant="caption"
132
+ color="text.secondary"
133
+ >
134
+ Annotations:
135
+ </Typography>
136
+ <ul style={{ margin: 0, paddingLeft: 16 }}>
137
+ {query.annotations.map(
138
+ (annotation: string, aidx: number) => (
139
+ <li key={aidx}>
140
+ <Typography variant="caption">
141
+ {annotation}
142
+ </Typography>
143
+ </li>
144
+ ),
145
+ )}
146
+ </ul>
147
+ </Box>
148
+ )}
149
+ </AccordionDetails>
150
+ </Accordion>
151
+ );
152
+ })}
153
+ </Box>
154
+ </StyledCard>
155
+ );
156
+ }
@@ -87,8 +87,8 @@ export function SourcesExplorer({
87
87
  const [selectedTab, setSelectedTab] = React.useState(
88
88
  existingSourceName
89
89
  ? sourceAndPaths.findIndex(
90
- (entry) => entry.sourceInfo.name === existingSourceName,
91
- )
90
+ (entry) => entry.sourceInfo.name === existingSourceName,
91
+ )
92
92
  : 0,
93
93
  );
94
94
 
@@ -291,7 +291,7 @@ export function SourceExplorerComponent({
291
291
  >
292
292
  <ResizableCollapsiblePanel
293
293
  isInitiallyExpanded={true}
294
- initialWidth={280}
294
+ initialWidth={180}
295
295
  minWidth={180}
296
296
  icon="database"
297
297
  title={sourceAndPath.sourceInfo.name}
@@ -304,7 +304,7 @@ export function SourceExplorerComponent({
304
304
  </ResizableCollapsiblePanel>
305
305
  <ResizableCollapsiblePanel
306
306
  isInitiallyExpanded={true}
307
- initialWidth={360}
307
+ initialWidth={280}
308
308
  minWidth={280}
309
309
  icon="filterSliders"
310
310
  title="Query"
@@ -324,16 +324,16 @@ export function SourceExplorerComponent({
324
324
  submittedQuery={
325
325
  query?.malloyQuery
326
326
  ? {
327
- executionState: mutation.isPending
328
- ? "running"
329
- : "finished",
330
- response: {
331
- result: query.malloyResult,
332
- },
333
- query: query.malloyQuery,
334
- queryResolutionStartMillis: Date.now(),
335
- onCancel: mutation.reset,
336
- }
327
+ executionState: mutation.isPending
328
+ ? "running"
329
+ : "finished",
330
+ response: {
331
+ result: query.malloyResult,
332
+ },
333
+ query: query.malloyQuery,
334
+ queryResolutionStartMillis: Date.now(),
335
+ onCancel: mutation.reset,
336
+ }
337
337
  : undefined
338
338
  }
339
339
  options={{ showRawQuery: true }}
@@ -311,7 +311,7 @@ export function MutableCell({
311
311
  }
312
312
 
313
313
  function useDebounce<T>(callback: (value: T) => void, delay: number = 2000) {
314
- const timeoutRef = React.useRef<NodeJS.Timeout>();
314
+ const timeoutRef = React.useRef<ReturnType<typeof setTimeout>>();
315
315
 
316
316
  return React.useCallback(
317
317
  (value: T) => {
@@ -100,28 +100,32 @@ export default function Notebook({
100
100
  <Typography variant="overline" fontWeight="bold">
101
101
  Notebook
102
102
  </Typography>
103
- <CardActions
104
- sx={{
105
- padding: "0px 10px 0px 10px",
106
- mb: "auto",
107
- mt: "auto",
108
- }}
109
- >
110
- <Tooltip
111
- title={
112
- embeddingExpanded ? "Hide Embedding" : "View Embedding"
113
- }
103
+ {!hideEmbeddingIcons && (
104
+ <CardActions
105
+ sx={{
106
+ padding: "0px 10px 0px 10px",
107
+ mb: "auto",
108
+ mt: "auto",
109
+ }}
114
110
  >
115
- <IconButton
116
- size="small"
117
- onClick={() => {
118
- setEmbeddingExpanded(!embeddingExpanded);
119
- }}
111
+ <Tooltip
112
+ title={
113
+ embeddingExpanded
114
+ ? "Hide Embedding"
115
+ : "View Embedding"
116
+ }
120
117
  >
121
- <LinkOutlinedIcon />
122
- </IconButton>
123
- </Tooltip>
124
- </CardActions>
118
+ <IconButton
119
+ size="small"
120
+ onClick={() => {
121
+ setEmbeddingExpanded(!embeddingExpanded);
122
+ }}
123
+ >
124
+ <LinkOutlinedIcon />
125
+ </IconButton>
126
+ </Tooltip>
127
+ </CardActions>
128
+ )}
125
129
  </Stack>
126
130
  <Collapse in={embeddingExpanded} timeout="auto" unmountOnExit>
127
131
  <Divider />
@@ -4,13 +4,13 @@ import ContentCopyIcon from "@mui/icons-material/ContentCopy";
4
4
  import LinkOutlinedIcon from "@mui/icons-material/LinkOutlined";
5
5
  import {
6
6
  CardActions,
7
- CardContent,
8
7
  Collapse,
9
8
  Divider,
10
9
  IconButton,
11
10
  Stack,
12
11
  Tooltip,
13
12
  Typography,
13
+ Box,
14
14
  } from "@mui/material";
15
15
  import Markdown from "markdown-to-jsx";
16
16
  import React, { Suspense, lazy, useEffect } from "react";
@@ -18,8 +18,7 @@ import { NotebookCell as ClientNotebookCell } from "../../client";
18
18
  import { highlight } from "../highlighter";
19
19
  import { SourcesExplorer } from "../Model";
20
20
  import { StyledCard, StyledCardContent } from "../styles";
21
-
22
- const RenderedResult = lazy(() => import("../RenderedResult/RenderedResult"));
21
+ import ResultContainer from "../RenderedResult/ResultContainer";
23
22
 
24
23
  interface NotebookCellProps {
25
24
  cell: ClientNotebookCell;
@@ -72,7 +71,7 @@ export function NotebookCell({
72
71
  </StyledCard>
73
72
  )) ||
74
73
  (cell.type === "code" && (
75
- <StyledCard variant="outlined">
74
+ <StyledCard variant="outlined" sx={{ height: "auto" }}>
76
75
  {(!hideCodeCellIcon || (!hideEmbeddingIcon && cell.result)) && (
77
76
  <Stack sx={{ flexDirection: "row", justifyContent: "right" }}>
78
77
  <CardActions
@@ -82,6 +81,31 @@ export function NotebookCell({
82
81
  mt: "auto",
83
82
  }}
84
83
  >
84
+ {cell.newSources && cell.newSources.length > 0 && (
85
+ <Tooltip title="Explore Data Sources">
86
+ <IconButton
87
+ size="small"
88
+ onClick={() => {
89
+ setSourcesExpanded(!sourcesExpanded);
90
+ setEmbeddingExpanded(false);
91
+ setCodeExpanded(false);
92
+ }}
93
+ >
94
+ <svg
95
+ width="24"
96
+ height="24"
97
+ viewBox="0 0 24 24"
98
+ fill="none"
99
+ xmlns="http://www.w3.org/2000/svg"
100
+ >
101
+ <path
102
+ d="M3 3h18v18H3V3zm2 2v14h14V5H5zm2 2h10v2H7V7zm0 4h10v2H7v-2zm0 4h10v2H7v-2z"
103
+ fill="currentColor"
104
+ />
105
+ </svg>
106
+ </IconButton>
107
+ </Tooltip>
108
+ )}
85
109
  {!hideCodeCellIcon && (
86
110
  <Tooltip
87
111
  title={codeExpanded ? "Hide Code" : "View Code"}
@@ -114,31 +138,6 @@ export function NotebookCell({
114
138
  </IconButton>
115
139
  </Tooltip>
116
140
  )}
117
- {cell.newSources && cell.newSources.length > 0 && (
118
- <Tooltip title="Explore Data Sources">
119
- <IconButton
120
- size="small"
121
- onClick={() => {
122
- setSourcesExpanded(!sourcesExpanded);
123
- setEmbeddingExpanded(false);
124
- setCodeExpanded(false);
125
- }}
126
- >
127
- <svg
128
- width="24"
129
- height="24"
130
- viewBox="0 0 24 24"
131
- fill="none"
132
- xmlns="http://www.w3.org/2000/svg"
133
- >
134
- <path
135
- d="M3 3h18v18H3V3zm2 2v14h14V5H5zm2 2h10v2H7V7zm0 4h10v2H7v-2zm0 4h10v2H7v-2z"
136
- fill="currentColor"
137
- />
138
- </svg>
139
- </IconButton>
140
- </Tooltip>
141
- )}
142
141
  </CardActions>
143
142
  </Stack>
144
143
  )}
@@ -212,18 +211,11 @@ export function NotebookCell({
212
211
  </Collapse>
213
212
  {cell.result && !sourcesExpanded && (
214
213
  <>
215
- <Divider sx={{ mb: "10px" }} />
216
- <CardContent
217
- sx={{
218
- minHeight: "100px",
219
- maxHeight: "800px",
220
- overflow: "auto",
221
- }}
222
- >
223
- <Suspense fallback="Loading malloy...">
224
- <RenderedResult result={cell.result} />
225
- </Suspense>
226
- </CardContent>
214
+ <ResultContainer
215
+ result={cell.result}
216
+ minHeight={200}
217
+ maxHeight={800}
218
+ />
227
219
  </>
228
220
  )}
229
221
  </StyledCard>
@@ -106,6 +106,7 @@ export default function Package({
106
106
  <Notebook
107
107
  notebookPath={README_NOTEBOOK}
108
108
  expandCodeCells={true}
109
+ hideEmbeddingIcons={true}
109
110
  />
110
111
  </Grid>
111
112
  </Grid>
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable react/prop-types */
2
2
  import type { MalloyRenderProps } from "@malloydata/render";
3
3
  import "@malloydata/render/webcomponent";
4
- import React, { useEffect, useRef } from "react";
4
+ import React, { useEffect, useRef, useCallback } from "react";
5
5
 
6
6
  type MalloyRenderElement = HTMLElement & MalloyRenderProps;
7
7
 
@@ -19,14 +19,54 @@ declare global {
19
19
 
20
20
  interface RenderedResultProps {
21
21
  result: string;
22
+ onSizeChange?: (height: number) => void;
22
23
  }
23
24
 
24
- export default function RenderedResult({ result }: RenderedResultProps) {
25
+ export default function RenderedResult({
26
+ result,
27
+ onSizeChange,
28
+ }: RenderedResultProps) {
25
29
  const ref = useRef<MalloyRenderElement>(null);
30
+
26
31
  useEffect(() => {
27
32
  if (ref.current) {
28
33
  ref.current.malloyResult = JSON.parse(result);
29
34
  }
30
35
  }, [result]);
36
+
37
+ // Set up size measurement using scrollHeight instead of ResizeObserver
38
+ useEffect(() => {
39
+ if (!ref.current || !onSizeChange) return;
40
+
41
+ const element = ref.current;
42
+
43
+ // Function to measure and report size
44
+ const measureSize = () => {
45
+ if (element) {
46
+ const height = element.offsetHeight;
47
+ if (height > 0) {
48
+ onSizeChange(height);
49
+ }
50
+ }
51
+ };
52
+
53
+ // Initial measurement after a brief delay to let content render
54
+ const timeoutId = setTimeout(measureSize, 100);
55
+
56
+ // Also measure when the malloy result changes
57
+ const observer = new MutationObserver(measureSize);
58
+ observer.observe(element, {
59
+ childList: true,
60
+ subtree: true,
61
+ attributes: true,
62
+ });
63
+
64
+ // Cleanup
65
+ return () => {
66
+ clearTimeout(timeoutId);
67
+ observer.disconnect();
68
+ };
69
+ }, [onSizeChange, result]);
70
+
31
71
  return <malloy-render ref={ref} />;
32
72
  }