@malloy-publisher/sdk 0.0.127 → 0.0.129

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.127",
4
+ "version": "0.0.129",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs.js",
7
7
  "module": "dist/index.es.js",
@@ -22,13 +22,20 @@ import { useModelData } from "./useModelData";
22
22
  interface ModelProps {
23
23
  onChange?: (query: QueryExplorerResult) => void;
24
24
  resourceUri: string;
25
+ runOnDemand?: boolean;
26
+ maxResultSize?: number;
25
27
  }
26
28
 
27
29
  // Note: For this to properly render outside of publisher,
28
30
  // you must explicitly import the styles from the package:
29
31
  // import "@malloy-publisher/sdk/malloy-explorer.css";
30
32
 
31
- export default function Model({ onChange, resourceUri }: ModelProps) {
33
+ export default function Model({
34
+ onChange,
35
+ resourceUri,
36
+ runOnDemand = false,
37
+ maxResultSize = 0,
38
+ }: ModelProps) {
32
39
  const { modelPath } = parseResourceUri(resourceUri);
33
40
  const { data, isError, isLoading, error } = useModelData(resourceUri);
34
41
  const [dialogOpen, setDialogOpen] = React.useState(false);
@@ -184,6 +191,8 @@ export default function Model({ onChange, resourceUri }: ModelProps) {
184
191
  queryName={query.name}
185
192
  annotations={query.annotations}
186
193
  resourceUri={resourceUri}
194
+ runOnDemand={runOnDemand}
195
+ maxResultSize={maxResultSize}
187
196
  />
188
197
  ))}
189
198
  </Stack>
@@ -1,5 +1,6 @@
1
+ import PlayArrowIcon from "@mui/icons-material/PlayArrow";
1
2
  import SearchIcon from "@mui/icons-material/Search";
2
- import { Box, IconButton, Typography } from "@mui/material";
3
+ import { Box, Button, IconButton, Typography } from "@mui/material";
3
4
  import React, { useEffect } from "react";
4
5
  import { useQueryWithApiError } from "../../hooks/useQueryWithApiError";
5
6
  import { parseResourceUri } from "../../utils/formatting";
@@ -15,16 +16,21 @@ interface ModelCellProps {
15
16
  noView?: boolean;
16
17
  annotations?: string[];
17
18
  resourceUri: string;
19
+ runOnDemand?: boolean;
20
+ maxResultSize?: number;
18
21
  }
19
22
 
20
23
  export function ModelCell({
21
24
  queryName,
22
25
  annotations,
23
26
  resourceUri,
27
+ runOnDemand = false,
28
+ maxResultSize = 0,
24
29
  }: ModelCellProps) {
25
30
  const [highlightedAnnotations, setHighlightedAnnotations] =
26
31
  React.useState<string>();
27
32
  const [resultsDialogOpen, setResultsDialogOpen] = React.useState(false);
33
+ const [hasRun, setHasRun] = React.useState(false);
28
34
 
29
35
  const { packageName, projectName, versionId, modelPath } =
30
36
  parseResourceUri(resourceUri);
@@ -48,7 +54,7 @@ export function ModelCell({
48
54
  versionId: versionId,
49
55
  },
50
56
  ),
51
- enabled: true, // Always execute
57
+ enabled: runOnDemand ? hasRun : true, // Execute on demand or always
52
58
  });
53
59
 
54
60
  useEffect(() => {
@@ -126,19 +132,55 @@ export function ModelCell({
126
132
  position: "relative",
127
133
  }}
128
134
  >
129
- {isLoading && (
135
+ {runOnDemand && !hasRun && (
136
+ <Box
137
+ sx={{
138
+ padding: "40px 20px",
139
+ textAlign: "center",
140
+ display: "flex",
141
+ flexDirection: "column",
142
+ alignItems: "center",
143
+ gap: "16px",
144
+ }}
145
+ >
146
+ <Typography variant="body2" color="text.secondary">
147
+ Click Run to execute the query
148
+ </Typography>
149
+ <Button
150
+ variant="contained"
151
+ startIcon={<PlayArrowIcon />}
152
+ onClick={() => setHasRun(true)}
153
+ sx={{
154
+ backgroundColor: "#1976d2",
155
+ "&:hover": {
156
+ backgroundColor: "#1565c0",
157
+ },
158
+ textTransform: "none",
159
+ fontSize: "14px",
160
+ fontWeight: 600,
161
+ padding: "8px 24px",
162
+ }}
163
+ >
164
+ Run Query
165
+ </Button>
166
+ </Box>
167
+ )}
168
+ {(!runOnDemand || hasRun) && isLoading && (
130
169
  <Box sx={{ padding: "20px", textAlign: "center" }}>
131
170
  <Typography>Loading results...</Typography>
132
171
  </Box>
133
172
  )}
134
- {isSuccess && queryData?.data?.result && (
135
- <ResultContainer
136
- result={queryData.data.result}
137
- minHeight={300}
138
- maxHeight={600}
139
- hideToggle={false}
140
- />
141
- )}
173
+ {(!runOnDemand || hasRun) &&
174
+ isSuccess &&
175
+ queryData?.data?.result && (
176
+ <ResultContainer
177
+ result={queryData.data.result}
178
+ minHeight={300}
179
+ maxHeight={600}
180
+ hideToggle={false}
181
+ maxResultSize={maxResultSize}
182
+ />
183
+ )}
142
184
  </CleanMetricCard>
143
185
 
144
186
  {/* Results Dialog */}
@@ -4,18 +4,22 @@ import { CompiledNotebook } from "../../client";
4
4
  import { useQueryWithApiError } from "../../hooks/useQueryWithApiError";
5
5
  import { ApiErrorDisplay } from "../ApiErrorDisplay";
6
6
 
7
+ import { parseResourceUri } from "../../utils/formatting";
7
8
  import { Loading } from "../Loading";
9
+ import { useServer } from "../ServerProvider";
8
10
  import { CleanNotebookContainer, CleanNotebookSection } from "../styles";
9
11
  import { NotebookCell } from "./NotebookCell";
10
- import { parseResourceUri } from "../../utils/formatting";
11
- import { useServer } from "../ServerProvider";
12
12
 
13
13
  interface NotebookProps {
14
14
  resourceUri: string;
15
+ maxResultSize?: number;
15
16
  }
16
17
 
17
18
  // Requires PackageProvider
18
- export default function Notebook({ resourceUri }: NotebookProps) {
19
+ export default function Notebook({
20
+ resourceUri,
21
+ maxResultSize = 0,
22
+ }: NotebookProps) {
19
23
  const { apiClients } = useServer();
20
24
  const {
21
25
  projectName,
@@ -55,6 +59,7 @@ export default function Notebook({ resourceUri }: NotebookProps) {
55
59
  key={index}
56
60
  index={index}
57
61
  resourceUri={resourceUri}
62
+ maxResultSize={maxResultSize}
58
63
  />
59
64
  ))}
60
65
  {isError && error.status === 404 && (
@@ -32,6 +32,7 @@ interface NotebookCellProps {
32
32
  hideEmbeddingIcon?: boolean;
33
33
  resourceUri: string;
34
34
  index: number;
35
+ maxResultSize?: number;
35
36
  }
36
37
 
37
38
  export function NotebookCell({
@@ -40,6 +41,7 @@ export function NotebookCell({
40
41
  hideEmbeddingIcon,
41
42
  resourceUri,
42
43
  index,
44
+ maxResultSize,
43
45
  }: NotebookCellProps) {
44
46
  const [codeDialogOpen, setCodeDialogOpen] = React.useState<boolean>(false);
45
47
  const [embeddingDialogOpen, setEmbeddingDialogOpen] =
@@ -420,6 +422,7 @@ export function NotebookCell({
420
422
  result={cell.result}
421
423
  minHeight={300}
422
424
  maxHeight={1000}
425
+ maxResultSize={maxResultSize}
423
426
  />
424
427
  </Box>
425
428
 
@@ -36,7 +36,7 @@ type ConnectionProps = {
36
36
  connection: ApiConnection;
37
37
  onClick: () => void;
38
38
  onEdit: (connection: ApiConnection) => Promise<unknown>;
39
- onDelete: (connection: ApiConnection) => Promise<unknown>;
39
+ onDelete: (connection: ApiConnection) => Promise<unknown> | void;
40
40
  isMutating: boolean;
41
41
  mutable: boolean;
42
42
  };
@@ -274,9 +274,19 @@ export default function Connections({ resourceUri }: ConnectionsProps) {
274
274
  onEdit={(payload) =>
275
275
  updateConnection.mutateAsync(payload)
276
276
  }
277
- onDelete={(payload) =>
278
- deleteConnection.mutateAsync(payload)
279
- }
277
+ onDelete={(payload) => {
278
+ if (
279
+ !selectedConnectionResourceUri.startsWith(
280
+ "publisher:",
281
+ )
282
+ ) {
283
+ deleteConnection.mutateAsync(payload);
284
+ } else {
285
+ setNotificationMessage(
286
+ "Cannot delete this connection (publisher: resource)",
287
+ );
288
+ }
289
+ }}
280
290
  isMutating={
281
291
  updateConnection.isPending ||
282
292
  deleteConnection.isPending
@@ -1,5 +1,5 @@
1
- import { ExpandLess, ExpandMore } from "@mui/icons-material";
2
- import { Box, IconButton } from "@mui/material";
1
+ import { ExpandLess, ExpandMore, Warning } from "@mui/icons-material";
2
+ import { Box, Button, IconButton, Typography } from "@mui/material";
3
3
  import {
4
4
  lazy,
5
5
  Suspense,
@@ -17,6 +17,10 @@ interface ResultContainerProps {
17
17
  minHeight: number;
18
18
  maxHeight: number;
19
19
  hideToggle?: boolean;
20
+ // if Results are larger than this size, show a warning and a button to proceed
21
+ // this is to prevent performance issues with large results.
22
+ // the default is 0, which means no warning will be shown.
23
+ maxResultSize?: number;
20
24
  }
21
25
 
22
26
  // ResultContainer is a component that renders a result, with a toggle button to expand/collapse the result.
@@ -28,6 +32,7 @@ export default function ResultContainer({
28
32
  minHeight,
29
33
  maxHeight,
30
34
  hideToggle = false,
35
+ maxResultSize = 0,
31
36
  }: ResultContainerProps) {
32
37
  const [isExpanded, setIsExpanded] = useState(false);
33
38
  const [contentHeight, setContentHeight] = useState<number>(0);
@@ -36,6 +41,7 @@ export default function ResultContainer({
36
41
  const containerRef = useRef<HTMLDivElement>(null);
37
42
  const [explicitHeight, setExplicitHeight] = useState<number>(undefined);
38
43
  const [isFillElement, setIsFillElement] = useState(false);
44
+ const [userAcknowledged, setUserAcknowledged] = useState(false);
39
45
  const handleToggle = useCallback(() => {
40
46
  const wasExpanded = isExpanded;
41
47
  setIsExpanded(!isExpanded);
@@ -85,6 +91,42 @@ export default function ResultContainer({
85
91
  if (!result) {
86
92
  return null;
87
93
  }
94
+
95
+ // Check if result exceeds max size and user hasn't acknowledged yet
96
+ const exceedsMaxSize = maxResultSize > 0 && result.length > maxResultSize;
97
+ if (exceedsMaxSize && !userAcknowledged) {
98
+ return (
99
+ <Box
100
+ sx={{
101
+ minHeight: `${minHeight}px`,
102
+ display: "flex",
103
+ flexDirection: "column",
104
+ alignItems: "center",
105
+ justifyContent: "center",
106
+ gap: 2,
107
+ p: 4,
108
+ backgroundColor: "#fafafa",
109
+ border: "1px solid",
110
+ borderColor: "#e0e0e0",
111
+ borderRadius: 1,
112
+ }}
113
+ >
114
+ <Warning sx={{ fontSize: 48, color: "#757575" }} />
115
+ <Typography variant="h6" color="text.secondary" align="center">
116
+ Processing large results may cause browser performance issues.
117
+ Proceed?
118
+ </Typography>
119
+ <Button
120
+ variant="contained"
121
+ color="primary"
122
+ onClick={() => setUserAcknowledged(true)}
123
+ >
124
+ Proceed
125
+ </Button>
126
+ </Box>
127
+ );
128
+ }
129
+
88
130
  const loading = <Loading text="Loading..." centered={true} size={32} />;
89
131
  const renderedHeight = isFillElement
90
132
  ? isExpanded