@malloy-publisher/sdk 0.0.199 → 0.0.201

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.
Files changed (35) hide show
  1. package/dist/ServerProvider-BSyxB6sf.cjs.js +1 -0
  2. package/dist/{ServerProvider-BuM1usxf.es.js → ServerProvider-DSnbMlP3.es.js} +330 -470
  3. package/dist/client/api.d.ts +21 -121
  4. package/dist/client/index.cjs.js +1 -1
  5. package/dist/client/index.es.js +1 -1
  6. package/dist/components/Connections/EditConnectionDialog.d.ts +2 -1
  7. package/dist/components/given/GivenInput.d.ts +16 -0
  8. package/dist/components/given/GivensPanel.d.ts +18 -0
  9. package/dist/components/given/index.d.ts +2 -0
  10. package/dist/{core-DfcpQGVP.es.js → core-B3IQNPBD.es.js} +1 -1
  11. package/dist/{core-yDgxkpo0.cjs.js → core-GkjltsUf.cjs.js} +1 -1
  12. package/dist/hooks/index.d.ts +2 -0
  13. package/dist/hooks/useGivensForm.d.ts +33 -0
  14. package/dist/hooks/useModelGivens.d.ts +10 -0
  15. package/dist/index-BpoC5QHF.cjs.js +229 -0
  16. package/dist/{index-Y4ooZDYA.es.js → index-uW-ZBpF2.es.js} +26102 -25626
  17. package/dist/index.cjs.js +1 -1
  18. package/dist/index.es.js +28 -26
  19. package/package.json +1 -1
  20. package/src/components/Connections/DeleteConnectionDialog.tsx +10 -3
  21. package/src/components/Connections/EditConnectionDialog.tsx +11 -3
  22. package/src/components/Environment/Connections.tsx +388 -0
  23. package/src/components/Environment/Environment.tsx +7 -2
  24. package/src/components/Notebook/Notebook.tsx +81 -23
  25. package/src/components/Package/Package.tsx +0 -7
  26. package/src/components/given/GivenInput.tsx +190 -0
  27. package/src/components/given/GivensPanel.tsx +85 -0
  28. package/src/components/given/index.ts +2 -0
  29. package/src/hooks/index.ts +8 -0
  30. package/src/hooks/useGivensForm.ts +110 -0
  31. package/src/hooks/useModelGivens.ts +26 -0
  32. package/dist/ServerProvider-C_Mnvmgc.cjs.js +0 -1
  33. package/dist/index-CMA8U4-B.cjs.js +0 -228
  34. package/src/components/Package/Connections.tsx +0 -373
  35. /package/dist/components/{Package → Environment}/Connections.d.ts +0 -0
package/dist/index.cjs.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-CMA8U4-B.cjs.js"),o=require("./ServerProvider-C_Mnvmgc.cjs.js");exports.AnalyzePackageButton=e.AnalyzePackageButton;exports.BrowserWorkbookStorage=e.BrowserWorkbookStorage;exports.ConnectionExplorer=e.ConnectionExplorer;exports.DimensionFilter=e.DimensionFilter;exports.EmbeddedQueryResult=e.EmbeddedQueryResult;exports.Environment=e.Environment;exports.Home=e.Home;exports.Loading=e.Loading;exports.Model=e.Model;exports.ModelExplorer=e.ModelExplorer;exports.ModelExplorerDialog=e.ModelExplorerDialog;exports.Notebook=e.Notebook;exports.Package=e.Package;exports.Packages=e.Packages;exports.QueryResult=e.QueryResult;exports.RenderedResult=e.RenderedResult;exports.ResultContainer=e.ResultContainer;exports.SourceExplorerComponent=e.SourceExplorerComponent;exports.SourcesExplorer=e.SourcesExplorer;exports.Workbook=e.Workbook;exports.WorkbookList=e.WorkbookList;exports.WorkbookManager=e.WorkbookManager;exports.WorkbookStorageProvider=e.WorkbookStorageProvider;exports.createEmbeddedQueryResult=e.createEmbeddedQueryResult;exports.encodeResourceUri=e.encodeResourceUri;exports.extractDimensionSpecs=e.extractDimensionSpecs;exports.extractSourceFromQuery=e.extractSourceFromQuery;exports.generateFilterClause=e.generateFilterClause;exports.getDimensionKey=e.getDimensionKey;exports.getJoinedSources=e.getJoinedSources;exports.injectWhereClause=e.injectWhereClause;exports.makeDimensionKey=e.makeDimensionKey;exports.parseAllSourceInfos=e.parseAllSourceInfos;exports.parseDimensionFilterAnnotation=e.parseDimensionFilterAnnotation;exports.parseNotebookFilterAnnotation=e.parseNotebookFilterAnnotation;exports.parseResourceUri=e.parseResourceUri;exports.useDimensionFilters=e.useDimensionFilters;exports.useDimensionFiltersFromSpec=e.useDimensionFiltersFromSpec;exports.useDimensionFiltersQuery=e.useDimensionFiltersQuery;exports.useDimensionalFilterRangeData=e.useDimensionalFilterRangeData;exports.useModelData=e.useModelData;exports.useRawQueryData=e.useRawQueryData;exports.useRouterClickHandler=e.useRouterClickHandler;exports.ServerProvider=o.ServerProvider;exports.useServer=o.useServer;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-BpoC5QHF.cjs.js"),o=require("./ServerProvider-BSyxB6sf.cjs.js");exports.AnalyzePackageButton=e.AnalyzePackageButton;exports.BrowserWorkbookStorage=e.BrowserWorkbookStorage;exports.ConnectionExplorer=e.ConnectionExplorer;exports.DimensionFilter=e.DimensionFilter;exports.EmbeddedQueryResult=e.EmbeddedQueryResult;exports.Environment=e.Environment;exports.Home=e.Home;exports.Loading=e.Loading;exports.Model=e.Model;exports.ModelExplorer=e.ModelExplorer;exports.ModelExplorerDialog=e.ModelExplorerDialog;exports.Notebook=e.Notebook;exports.Package=e.Package;exports.Packages=e.Packages;exports.QueryResult=e.QueryResult;exports.RenderedResult=e.RenderedResult;exports.ResultContainer=e.ResultContainer;exports.SourceExplorerComponent=e.SourceExplorerComponent;exports.SourcesExplorer=e.SourcesExplorer;exports.Workbook=e.Workbook;exports.WorkbookList=e.WorkbookList;exports.WorkbookManager=e.WorkbookManager;exports.WorkbookStorageProvider=e.WorkbookStorageProvider;exports.createEmbeddedQueryResult=e.createEmbeddedQueryResult;exports.encodeResourceUri=e.encodeResourceUri;exports.extractDimensionSpecs=e.extractDimensionSpecs;exports.extractSourceFromQuery=e.extractSourceFromQuery;exports.generateFilterClause=e.generateFilterClause;exports.getDimensionKey=e.getDimensionKey;exports.getJoinedSources=e.getJoinedSources;exports.injectWhereClause=e.injectWhereClause;exports.makeDimensionKey=e.makeDimensionKey;exports.parseAllSourceInfos=e.parseAllSourceInfos;exports.parseDimensionFilterAnnotation=e.parseDimensionFilterAnnotation;exports.parseNotebookFilterAnnotation=e.parseNotebookFilterAnnotation;exports.parseResourceUri=e.parseResourceUri;exports.useDimensionFilters=e.useDimensionFilters;exports.useDimensionFiltersFromSpec=e.useDimensionFiltersFromSpec;exports.useDimensionFiltersQuery=e.useDimensionFiltersQuery;exports.useDimensionalFilterRangeData=e.useDimensionalFilterRangeData;exports.useGivensForm=e.useGivensForm;exports.useModelData=e.useModelData;exports.useModelGivens=e.useModelGivens;exports.useRawQueryData=e.useRawQueryData;exports.useRouterClickHandler=e.useRouterClickHandler;exports.ServerProvider=o.ServerProvider;exports.useServer=o.useServer;
package/dist/index.es.js CHANGED
@@ -1,14 +1,14 @@
1
- import { A as a, I as o, C as r, D as n, x as t, E as i, H as l, L as u, M as m, o as c, q as d, N as k, v as p, P as D, Q as g, R as S, B as R, r as F, S as x, W as y, F as b, J as E, G as C, y as Q, Z as W, g as v, i as M, j as P, V as A, k as f, l as B, X as H, p as K, m as L, n as N, Y as U, K as j, O as w, T as I, U as J, t as h, u as q, e as z } from "./index-Y4ooZDYA.es.js";
2
- import { S as O, u as T } from "./ServerProvider-BuM1usxf.es.js";
1
+ import { A as a, I as o, C as r, D as n, x as i, E as t, H as u, L as l, M as m, o as c, q as d, N as k, v as p, P as D, Q as g, R as S, B as F, r as R, S as x, W as y, F as b, J as E, G as v, y as C, $ as M, g as Q, i as W, j as P, V as A, k as f, l as B, X as G, p as H, m as K, n as L, _ as N, K as U, O as j, T as w, U as I, Z as J, t as h, Y as q, u as z, e as O } from "./index-uW-ZBpF2.es.js";
2
+ import { S as V, u as X } from "./ServerProvider-DSnbMlP3.es.js";
3
3
  export {
4
4
  a as AnalyzePackageButton,
5
5
  o as BrowserWorkbookStorage,
6
6
  r as ConnectionExplorer,
7
7
  n as DimensionFilter,
8
- t as EmbeddedQueryResult,
9
- i as Environment,
10
- l as Home,
11
- u as Loading,
8
+ i as EmbeddedQueryResult,
9
+ t as Environment,
10
+ u as Home,
11
+ l as Loading,
12
12
  m as Model,
13
13
  c as ModelExplorer,
14
14
  d as ModelExplorerDialog,
@@ -17,33 +17,35 @@ export {
17
17
  D as Packages,
18
18
  g as QueryResult,
19
19
  S as RenderedResult,
20
- R as ResultContainer,
21
- O as ServerProvider,
22
- F as SourceExplorerComponent,
20
+ F as ResultContainer,
21
+ V as ServerProvider,
22
+ R as SourceExplorerComponent,
23
23
  x as SourcesExplorer,
24
24
  y as Workbook,
25
25
  b as WorkbookList,
26
26
  E as WorkbookManager,
27
- C as WorkbookStorageProvider,
28
- Q as createEmbeddedQueryResult,
29
- W as encodeResourceUri,
30
- v as extractDimensionSpecs,
31
- M as extractSourceFromQuery,
27
+ v as WorkbookStorageProvider,
28
+ C as createEmbeddedQueryResult,
29
+ M as encodeResourceUri,
30
+ Q as extractDimensionSpecs,
31
+ W as extractSourceFromQuery,
32
32
  P as generateFilterClause,
33
33
  A as getDimensionKey,
34
34
  f as getJoinedSources,
35
35
  B as injectWhereClause,
36
- H as makeDimensionKey,
37
- K as parseAllSourceInfos,
38
- L as parseDimensionFilterAnnotation,
39
- N as parseNotebookFilterAnnotation,
40
- U as parseResourceUri,
41
- j as useDimensionFilters,
42
- w as useDimensionFiltersFromSpec,
43
- I as useDimensionFiltersQuery,
44
- J as useDimensionalFilterRangeData,
36
+ G as makeDimensionKey,
37
+ H as parseAllSourceInfos,
38
+ K as parseDimensionFilterAnnotation,
39
+ L as parseNotebookFilterAnnotation,
40
+ N as parseResourceUri,
41
+ U as useDimensionFilters,
42
+ j as useDimensionFiltersFromSpec,
43
+ w as useDimensionFiltersQuery,
44
+ I as useDimensionalFilterRangeData,
45
+ J as useGivensForm,
45
46
  h as useModelData,
46
- q as useRawQueryData,
47
- z as useRouterClickHandler,
48
- T as useServer
47
+ q as useModelGivens,
48
+ z as useRawQueryData,
49
+ O as useRouterClickHandler,
50
+ X as useServer
49
51
  };
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.199",
4
+ "version": "0.0.201",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs.js",
7
7
  "module": "dist/index.es.js",
@@ -7,6 +7,9 @@ import DialogActions from "@mui/material/DialogActions";
7
7
  import DialogContent from "@mui/material/DialogContent";
8
8
  import DialogTitle from "@mui/material/DialogTitle";
9
9
  import IconButton from "@mui/material/IconButton";
10
+ import ListItemIcon from "@mui/material/ListItemIcon";
11
+ import ListItemText from "@mui/material/ListItemText";
12
+ import MenuItem from "@mui/material/MenuItem";
10
13
  import Typography from "@mui/material/Typography";
11
14
  import React, { useState } from "react";
12
15
  import { Connection } from "../../client";
@@ -34,15 +37,19 @@ export default function DeleteConnectionDialog({
34
37
 
35
38
  return (
36
39
  <React.Fragment>
37
- <IconButton
40
+ <MenuItem
38
41
  aria-label={`Delete connection ${connection?.name ?? ""}`.trim()}
39
42
  onClick={(event) => {
40
43
  event.stopPropagation();
41
44
  handleClickOpen();
42
45
  }}
46
+ sx={{ color: "error.main" }}
43
47
  >
44
- <Delete />
45
- </IconButton>
48
+ <ListItemIcon sx={{ color: "inherit" }}>
49
+ <Delete fontSize="small" />
50
+ </ListItemIcon>
51
+ <ListItemText>Delete</ListItemText>
52
+ </MenuItem>
46
53
 
47
54
  <Dialog
48
55
  onClose={handleClose}
@@ -8,6 +8,8 @@ import DialogContent from "@mui/material/DialogContent";
8
8
  import DialogContentText from "@mui/material/DialogContentText";
9
9
  import DialogTitle from "@mui/material/DialogTitle";
10
10
  import IconButton from "@mui/material/IconButton";
11
+ import ListItemIcon from "@mui/material/ListItemIcon";
12
+ import ListItemText from "@mui/material/ListItemText";
11
13
  import TextField from "@mui/material/TextField";
12
14
  import Typography from "@mui/material/Typography";
13
15
  import React, { useState } from "react";
@@ -34,6 +36,7 @@ type EditConnectionDialogProps = {
34
36
  connection: Connection;
35
37
  onSubmit: (connection: Connection) => Promise<unknown>;
36
38
  isSubmitting: boolean;
39
+ onCloseDialog?: () => void;
37
40
  };
38
41
 
39
42
  function initAttachedDatabases(connection: Connection) {
@@ -56,6 +59,7 @@ export default function EditConnectionDialog({
56
59
  connection,
57
60
  onSubmit,
58
61
  isSubmitting,
62
+ onCloseDialog,
59
63
  }: EditConnectionDialogProps) {
60
64
  const [open, setOpen] = useState(false);
61
65
  const [type, setType] = useState<Connection["type"]>(connection.type);
@@ -83,6 +87,7 @@ export default function EditConnectionDialog({
83
87
 
84
88
  const handleClose = () => {
85
89
  setOpen(false);
90
+ onCloseDialog?.();
86
91
  };
87
92
 
88
93
  const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
@@ -446,7 +451,7 @@ export default function EditConnectionDialog({
446
451
 
447
452
  return (
448
453
  <React.Fragment>
449
- <IconButton
454
+ <MenuItem
450
455
  aria-label={`Edit connection ${connection?.name ?? ""}`.trim()}
451
456
  onClick={(event) => {
452
457
  event.preventDefault();
@@ -454,8 +459,11 @@ export default function EditConnectionDialog({
454
459
  handleClickOpen();
455
460
  }}
456
461
  >
457
- <Edit />
458
- </IconButton>
462
+ <ListItemIcon>
463
+ <Edit fontSize="small" />
464
+ </ListItemIcon>
465
+ <ListItemText>Edit</ListItemText>
466
+ </MenuItem>
459
467
  <Dialog open={open} onClose={handleClose}>
460
468
  <DialogTitle
461
469
  onClick={(event) => {
@@ -0,0 +1,388 @@
1
+ import { MoreVert } from "@mui/icons-material";
2
+ import StorageOutlinedIcon from "@mui/icons-material/StorageOutlined";
3
+ import {
4
+ Box,
5
+ Card,
6
+ CardContent,
7
+ Dialog,
8
+ DialogContent,
9
+ DialogTitle,
10
+ Grid,
11
+ IconButton,
12
+ Menu,
13
+ Snackbar,
14
+ Typography,
15
+ } from "@mui/material";
16
+ import { useQueryClient } from "@tanstack/react-query";
17
+ import { useState } from "react";
18
+ import { Connection as ApiConnection } from "../../client/api";
19
+ import {
20
+ useMutationWithApiError,
21
+ useQueryWithApiError,
22
+ } from "../../hooks/useQueryWithApiError";
23
+ import { encodeResourceUri, parseResourceUri } from "../../utils/formatting";
24
+ import { ApiErrorDisplay } from "../ApiErrorDisplay";
25
+ import AddConnectionDialog from "../Connections/AddConnectionDialog";
26
+ import DeleteConnectionDialog from "../Connections/DeleteConnectionDialog";
27
+ import EditConnectionDialog from "../Connections/EditConnectionDialog";
28
+ import { useServer } from "../ServerProvider";
29
+ import ConnectionExplorer from "./ConnectionExplorer";
30
+
31
+ const CONNECTION_TYPE_LABELS: Record<string, string> = {
32
+ bigquery: "BigQuery",
33
+ snowflake: "Snowflake",
34
+ postgres: "PostgreSQL",
35
+ mysql: "MySQL",
36
+ trino: "Trino",
37
+ databricks: "Databricks",
38
+ duckdb: "DuckDB",
39
+ ducklake: "DuckLake",
40
+ s3: "S3",
41
+ gcs: "GCS",
42
+ azure: "Azure",
43
+ };
44
+
45
+ function typeLabel(type: string | undefined): string {
46
+ if (!type) return "";
47
+ return CONNECTION_TYPE_LABELS[type] ?? type;
48
+ }
49
+
50
+ type ConnectionsProps = {
51
+ resourceUri: string;
52
+ };
53
+
54
+ export default function Connections({ resourceUri }: ConnectionsProps) {
55
+ const { apiClients, mutable } = useServer();
56
+ const queryClient = useQueryClient();
57
+ const { environmentName } = parseResourceUri(resourceUri);
58
+ const [notificationMessage, setNotificationMessage] = useState("");
59
+ const [selectedConnection, setSelectedConnection] = useState<string | null>(
60
+ null,
61
+ );
62
+ const selectedConnectionResourceUri = encodeResourceUri({
63
+ environmentName,
64
+ connectionName: selectedConnection,
65
+ });
66
+
67
+ const { data, isSuccess, isError, error } = useQueryWithApiError({
68
+ queryKey: ["connections", environmentName],
69
+ queryFn: () => apiClients.connections.listConnections(environmentName),
70
+ });
71
+
72
+ const handleCloseDialog = () => {
73
+ setSelectedConnection(null);
74
+ };
75
+
76
+ const addConnection = useMutationWithApiError({
77
+ mutationFn: (payload: ApiConnection) => {
78
+ return apiClients.environments.updateEnvironment(environmentName, {
79
+ name: environmentName,
80
+ connections: [...data!.data, payload],
81
+ });
82
+ },
83
+ onSuccess() {
84
+ setNotificationMessage("Connection added successfully");
85
+ queryClient.invalidateQueries({
86
+ queryKey: ["connections", environmentName],
87
+ });
88
+ },
89
+ onError(error) {
90
+ setNotificationMessage(error.message);
91
+ },
92
+ });
93
+
94
+ const updateConnection = useMutationWithApiError({
95
+ mutationFn: (payload: ApiConnection) => {
96
+ return apiClients.environments.updateEnvironment(environmentName, {
97
+ name: environmentName,
98
+ connections: data!.data.map((conn) =>
99
+ conn.name === payload.name ? payload : conn,
100
+ ),
101
+ });
102
+ },
103
+ onSuccess(_data, variables) {
104
+ setNotificationMessage(
105
+ `Connection ${variables.name} updated successfully`,
106
+ );
107
+ queryClient.invalidateQueries({
108
+ queryKey: ["connections", environmentName],
109
+ });
110
+ },
111
+ onError(error) {
112
+ setNotificationMessage(error.message);
113
+ },
114
+ });
115
+
116
+ const deleteConnection = useMutationWithApiError({
117
+ mutationFn: (payload: ApiConnection) => {
118
+ return apiClients.environments.updateEnvironment(environmentName, {
119
+ name: environmentName,
120
+ connections: data!.data.filter(
121
+ (conn) => conn.name !== payload.name,
122
+ ),
123
+ });
124
+ },
125
+ onSuccess(_data, variables) {
126
+ setNotificationMessage(
127
+ `Connection ${variables.name} deleted successfully`,
128
+ );
129
+ queryClient.invalidateQueries({
130
+ queryKey: ["connections", environmentName],
131
+ });
132
+ },
133
+ onError(error) {
134
+ setNotificationMessage(error.message);
135
+ },
136
+ });
137
+
138
+ const connections = isSuccess
139
+ ? [...data.data].sort((a, b) =>
140
+ (a.name ?? "").localeCompare(b.name ?? ""),
141
+ )
142
+ : [];
143
+
144
+ const isMutating =
145
+ addConnection.isPending ||
146
+ updateConnection.isPending ||
147
+ deleteConnection.isPending;
148
+
149
+ return (
150
+ <Box>
151
+ <Box
152
+ sx={{
153
+ display: "flex",
154
+ alignItems: "flex-start",
155
+ justifyContent: "space-between",
156
+ mb: 3,
157
+ }}
158
+ >
159
+ <Box>
160
+ <Typography
161
+ variant="h6"
162
+ sx={{ fontWeight: 600, letterSpacing: "-0.025em" }}
163
+ >
164
+ Connections
165
+ </Typography>
166
+ <Typography variant="body2" color="text.secondary">
167
+ Database connections available to packages in this environment
168
+ </Typography>
169
+ </Box>
170
+ {mutable && isSuccess && (
171
+ <AddConnectionDialog
172
+ onSubmit={(payload) => addConnection.mutateAsync(payload)}
173
+ isSubmitting={addConnection.isPending}
174
+ />
175
+ )}
176
+ </Box>
177
+
178
+ {!isSuccess && !isError && (
179
+ <Typography variant="body2" color="text.secondary">
180
+ Fetching Connections...
181
+ </Typography>
182
+ )}
183
+ {isError && (
184
+ <ApiErrorDisplay
185
+ error={error}
186
+ context={`${environmentName} > Connections`}
187
+ />
188
+ )}
189
+ {isSuccess && connections.length === 0 && (
190
+ <Typography variant="body2" color="text.secondary">
191
+ No connections yet.
192
+ </Typography>
193
+ )}
194
+ {isSuccess && connections.length > 0 && (
195
+ <Grid container spacing={2}>
196
+ {connections.map((conn) => (
197
+ <Grid size={{ xs: 12, sm: 6, md: 4 }} key={conn.name}>
198
+ <ConnectionCard
199
+ connection={conn}
200
+ mutable={mutable}
201
+ isMutating={isMutating}
202
+ onOpenExplorer={() =>
203
+ setSelectedConnection(conn.name ?? null)
204
+ }
205
+ onEdit={(payload) =>
206
+ updateConnection.mutateAsync(payload)
207
+ }
208
+ onDelete={(payload) => {
209
+ if (!conn.resource) {
210
+ deleteConnection.mutateAsync(payload);
211
+ } else {
212
+ setNotificationMessage(
213
+ "Cannot delete this connection",
214
+ );
215
+ }
216
+ }}
217
+ />
218
+ </Grid>
219
+ ))}
220
+ </Grid>
221
+ )}
222
+
223
+ <Snackbar
224
+ open={notificationMessage !== ""}
225
+ autoHideDuration={6000}
226
+ onClose={() => setNotificationMessage("")}
227
+ message={notificationMessage}
228
+ />
229
+
230
+ <Dialog
231
+ open={selectedConnection !== null}
232
+ onClose={handleCloseDialog}
233
+ maxWidth="lg"
234
+ fullWidth
235
+ >
236
+ <DialogTitle>
237
+ Connection Explorer: {selectedConnection}
238
+ <IconButton
239
+ aria-label="close"
240
+ onClick={handleCloseDialog}
241
+ sx={{ position: "absolute", right: 8, top: 8 }}
242
+ >
243
+ <Box
244
+ sx={{
245
+ width: 24,
246
+ height: 24,
247
+ display: "flex",
248
+ alignItems: "center",
249
+ justifyContent: "center",
250
+ }}
251
+ >
252
+ X
253
+ </Box>
254
+ </IconButton>
255
+ </DialogTitle>
256
+ <DialogContent>
257
+ {selectedConnection && (
258
+ <ConnectionExplorer
259
+ resourceUri={selectedConnectionResourceUri}
260
+ connectionName={selectedConnection}
261
+ connection={data?.data?.find(
262
+ (c) => c.name === selectedConnection,
263
+ )}
264
+ />
265
+ )}
266
+ </DialogContent>
267
+ </Dialog>
268
+ </Box>
269
+ );
270
+ }
271
+
272
+ type ConnectionCardProps = {
273
+ connection: ApiConnection;
274
+ mutable: boolean;
275
+ isMutating: boolean;
276
+ onOpenExplorer: () => void;
277
+ onEdit: (connection: ApiConnection) => Promise<unknown>;
278
+ onDelete: (connection: ApiConnection) => Promise<unknown> | void;
279
+ };
280
+
281
+ function ConnectionCard({
282
+ connection,
283
+ mutable,
284
+ isMutating,
285
+ onOpenExplorer,
286
+ onEdit,
287
+ onDelete,
288
+ }: ConnectionCardProps) {
289
+ const [menuAnchorEl, setMenuAnchorEl] = useState<null | HTMLElement>(null);
290
+ const menuOpen = Boolean(menuAnchorEl);
291
+
292
+ const handleMenuClick = (event: React.MouseEvent<HTMLElement>) => {
293
+ event.stopPropagation();
294
+ setMenuAnchorEl(event.currentTarget);
295
+ };
296
+
297
+ const handleMenuClose = () => {
298
+ setMenuAnchorEl(null);
299
+ };
300
+
301
+ return (
302
+ <Card
303
+ variant="outlined"
304
+ onClick={onOpenExplorer}
305
+ sx={{
306
+ height: "100%",
307
+ cursor: "pointer",
308
+ borderRadius: 3,
309
+ borderColor: "divider",
310
+ boxShadow: "none",
311
+ transition: "all 0.2s ease-in-out",
312
+ "&:hover": { boxShadow: 2, borderColor: "primary.main" },
313
+ }}
314
+ >
315
+ <CardContent sx={{ p: 2.5, "&:last-child": { pb: 2.5 } }}>
316
+ <Box sx={{ display: "flex", alignItems: "flex-start", gap: 1.5 }}>
317
+ <Box
318
+ sx={{
319
+ width: 36,
320
+ height: 36,
321
+ borderRadius: 1.5,
322
+ bgcolor: "warning.light",
323
+ display: "flex",
324
+ alignItems: "center",
325
+ justifyContent: "center",
326
+ flexShrink: 0,
327
+ color: "warning.main",
328
+ }}
329
+ >
330
+ <StorageOutlinedIcon sx={{ fontSize: 20 }} />
331
+ </Box>
332
+ <Box sx={{ flex: 1, minWidth: 0 }}>
333
+ <Typography
334
+ variant="subtitle1"
335
+ component="h6"
336
+ noWrap
337
+ sx={{ fontWeight: 600, mb: 0.5 }}
338
+ >
339
+ {connection.name}
340
+ </Typography>
341
+ <Typography variant="body2" color="text.secondary" noWrap>
342
+ {typeLabel(connection.type)}
343
+ </Typography>
344
+ </Box>
345
+ {mutable && (
346
+ <>
347
+ <IconButton
348
+ size="small"
349
+ onClick={handleMenuClick}
350
+ aria-label={`Connection actions for ${connection.name ?? ""}`.trim()}
351
+ sx={{ flexShrink: 0, mt: -0.5, mr: -0.5 }}
352
+ >
353
+ <MoreVert fontSize="small" />
354
+ </IconButton>
355
+ <Menu
356
+ anchorEl={menuAnchorEl}
357
+ open={menuOpen}
358
+ onClose={handleMenuClose}
359
+ onClick={(e) => e.stopPropagation()}
360
+ anchorOrigin={{
361
+ vertical: "bottom",
362
+ horizontal: "right",
363
+ }}
364
+ transformOrigin={{
365
+ vertical: "top",
366
+ horizontal: "right",
367
+ }}
368
+ >
369
+ <EditConnectionDialog
370
+ connection={connection}
371
+ onSubmit={onEdit}
372
+ isSubmitting={isMutating}
373
+ onCloseDialog={handleMenuClose}
374
+ />
375
+ <DeleteConnectionDialog
376
+ connection={connection}
377
+ onCloseDialog={handleMenuClose}
378
+ isMutating={isMutating}
379
+ onDelete={() => onDelete(connection)}
380
+ />
381
+ </Menu>
382
+ </>
383
+ )}
384
+ </Box>
385
+ </CardContent>
386
+ </Card>
387
+ );
388
+ }
@@ -4,6 +4,7 @@ import { parseResourceUri } from "../../utils/formatting";
4
4
  import { useServer } from "../ServerProvider";
5
5
  import About from "./About";
6
6
  import AddPackageDialog from "./AddPackageDialog";
7
+ import Connections from "./Connections";
7
8
  import Packages from "./Packages";
8
9
 
9
10
  interface EnvironmentProps {
@@ -36,8 +37,8 @@ export default function Environment({
36
37
  {environmentName}
37
38
  </Typography>
38
39
  <Typography variant="body2" color="text.secondary">
39
- Manage packages in this environment. Open a package to explore
40
- its models, notebooks, and connections.
40
+ Manage packages and database connections in this environment.
41
+ Open a package to explore its models and notebooks.
41
42
  </Typography>
42
43
  </Box>
43
44
 
@@ -67,6 +68,10 @@ export default function Environment({
67
68
  />
68
69
  </Box>
69
70
 
71
+ <Box sx={{ mb: 5 }}>
72
+ <Connections resourceUri={resourceUri} />
73
+ </Box>
74
+
70
75
  <About resourceUri={resourceUri} />
71
76
  </Container>
72
77
  );