@malloy-publisher/sdk 0.0.228 → 0.0.229

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 (62) hide show
  1. package/dist/{ServerProvider-BnQo3hhW.es.js → ServerProvider-BdQK_q3C.es.js} +780 -715
  2. package/dist/ServerProvider-n4SO1thp.cjs.js +1 -0
  3. package/dist/client/api.d.ts +79 -1
  4. package/dist/client/index.cjs.js +1 -1
  5. package/dist/client/index.es.js +1 -1
  6. package/dist/components/Materializations/CreateEnvMaterializationDialog.d.ts +14 -0
  7. package/dist/components/Materializations/EnvironmentMaterializations.d.ts +7 -0
  8. package/dist/components/Materializations/MaterializationDetailDialog.d.ts +2 -1
  9. package/dist/components/Materializations/MaterializationRunsList.d.ts +5 -1
  10. package/dist/components/Materializations/ScheduleCard.d.ts +20 -0
  11. package/dist/components/Materializations/SectionLabel.d.ts +10 -0
  12. package/dist/components/Materializations/SetScheduleDialog.d.ts +11 -0
  13. package/dist/components/Materializations/TriggerChip.d.ts +10 -0
  14. package/dist/components/Materializations/cron.d.ts +23 -0
  15. package/dist/components/Materializations/index.d.ts +1 -0
  16. package/dist/components/Materializations/utils.d.ts +9 -0
  17. package/dist/components/Notebook/Notebook.d.ts +6 -1
  18. package/dist/components/Notebook/NotebookCell.d.ts +3 -1
  19. package/dist/components/RenderedResult/ResultContainer.d.ts +3 -1
  20. package/dist/components/RenderedResult/renderLogs.d.ts +6 -0
  21. package/dist/components/ResultsDialog.d.ts +3 -1
  22. package/dist/constants/docLinks.d.ts +6 -0
  23. package/dist/{core-DYlbkyc-.cjs.js → core-BJUrppec.cjs.js} +1 -1
  24. package/dist/{core-CEDZMHV1.es.js → core-Coi3caGs.es.js} +1 -1
  25. package/dist/index-DWcuaQOW.cjs.js +309 -0
  26. package/dist/{index-DC7gIcJX.es.js → index-mbDUet0T.es.js} +34625 -25301
  27. package/dist/index.cjs.js +1 -1
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.es.js +54 -52
  30. package/dist/utils/queryClient.d.ts +5 -0
  31. package/package.json +3 -1
  32. package/src/components/Environment/AddPackageDialog.tsx +2 -1
  33. package/src/components/Environment/Environment.tsx +8 -0
  34. package/src/components/Home/Home.tsx +5 -4
  35. package/src/components/Materializations/CreateEnvMaterializationDialog.tsx +176 -0
  36. package/src/components/Materializations/EnvironmentMaterializations.tsx +264 -0
  37. package/src/components/Materializations/ManifestView.tsx +2 -3
  38. package/src/components/Materializations/MaterializationDetailDialog.tsx +121 -95
  39. package/src/components/Materializations/MaterializationRunsList.tsx +37 -0
  40. package/src/components/Materializations/Materializations.tsx +86 -1
  41. package/src/components/Materializations/ScheduleCard.tsx +228 -0
  42. package/src/components/Materializations/SectionLabel.tsx +27 -0
  43. package/src/components/Materializations/SetScheduleDialog.tsx +159 -0
  44. package/src/components/Materializations/TriggerChip.tsx +41 -0
  45. package/src/components/Materializations/cron.ts +121 -0
  46. package/src/components/Materializations/index.ts +1 -0
  47. package/src/components/Materializations/utils.ts +14 -0
  48. package/src/components/Model/ModelCell.tsx +4 -0
  49. package/src/components/Notebook/Notebook.tsx +14 -1
  50. package/src/components/Notebook/NotebookCell.tsx +103 -2
  51. package/src/components/Package/Package.tsx +14 -3
  52. package/src/components/QueryResult/QueryResult.tsx +7 -1
  53. package/src/components/RenderedResult/RenderedResult.tsx +153 -51
  54. package/src/components/RenderedResult/ResultContainer.tsx +40 -1
  55. package/src/components/RenderedResult/renderLogs.spec.ts +55 -0
  56. package/src/components/RenderedResult/renderLogs.ts +40 -0
  57. package/src/components/ResultsDialog.tsx +4 -0
  58. package/src/constants/docLinks.ts +17 -0
  59. package/src/index.ts +1 -0
  60. package/src/utils/queryClient.ts +14 -0
  61. package/dist/ServerProvider-CCKhmHeT.cjs.js +0 -1
  62. package/dist/index-DO6Y8XP_.cjs.js +0 -307
@@ -0,0 +1,264 @@
1
+ import { Box, Button, Snackbar, Stack, Typography } from "@mui/material";
2
+ import { useQueryClient } from "@tanstack/react-query";
3
+ import React, { useState } from "react";
4
+ import { Materialization, MaterializationActionActionEnum } from "../../client";
5
+ import {
6
+ useMutationWithApiError,
7
+ useQueryWithApiError,
8
+ } from "../../hooks/useQueryWithApiError";
9
+ import { parseResourceUri } from "../../utils/formatting";
10
+ import { ApiErrorDisplay } from "../ApiErrorDisplay";
11
+ import { Loading } from "../Loading";
12
+ import { useServer } from "../ServerProvider";
13
+ import CreateEnvMaterializationDialog from "./CreateEnvMaterializationDialog";
14
+ import MaterializationDetailDialog from "./MaterializationDetailDialog";
15
+ import MaterializationRunsList from "./MaterializationRunsList";
16
+ import { isActiveStatus } from "./utils";
17
+
18
+ const MATERIALIZATION_POLL_MS = 3000;
19
+ const PAGE_SIZE = 10;
20
+
21
+ interface EnvironmentMaterializationsProps {
22
+ resourceUri: string;
23
+ onClickPackageFile?: (to: string, event?: React.MouseEvent) => void;
24
+ }
25
+
26
+ export default function EnvironmentMaterializations({
27
+ resourceUri,
28
+ onClickPackageFile,
29
+ }: EnvironmentMaterializationsProps) {
30
+ const { apiClients, mutable } = useServer();
31
+ const queryClient = useQueryClient();
32
+ const { environmentName } = parseResourceUri(resourceUri);
33
+ const [notificationMessage, setNotificationMessage] = useState("");
34
+ const [selectedId, setSelectedId] = useState<string | null>(null);
35
+ // Show the most recent runs and page in more on demand — a busy environment
36
+ // (e.g. a frequent schedule) accumulates runs without bound, so never render
37
+ // the full history at once.
38
+ const [limit, setLimit] = useState(PAGE_SIZE);
39
+
40
+ const onClick =
41
+ onClickPackageFile ??
42
+ ((to: string) => {
43
+ window.location.href = to;
44
+ });
45
+
46
+ const listQuery = useQueryWithApiError({
47
+ queryKey: ["env-materializations", environmentName, limit],
48
+ queryFn: () =>
49
+ apiClients.materializations.listEnvironmentMaterializations(
50
+ environmentName,
51
+ limit,
52
+ ),
53
+ // "Show more" grows `limit` (part of the key), so keep the current rows on
54
+ // screen while the larger page loads instead of flashing back to the
55
+ // loading state.
56
+ placeholderData: (previousData) => previousData,
57
+ refetchInterval: (query) => {
58
+ const payload = query.state.data as
59
+ | { data?: Materialization[] }
60
+ | undefined;
61
+ const rows = payload?.data ?? [];
62
+ return rows.some((row) => isActiveStatus(row.status))
63
+ ? MATERIALIZATION_POLL_MS
64
+ : false;
65
+ },
66
+ });
67
+
68
+ const packagesQuery = useQueryWithApiError({
69
+ queryKey: ["packages", environmentName],
70
+ queryFn: () => apiClients.packages.listPackages(environmentName),
71
+ });
72
+
73
+ const invalidateList = () =>
74
+ queryClient.invalidateQueries({
75
+ queryKey: ["env-materializations", environmentName],
76
+ });
77
+
78
+ // Fire one create per selected package. Each package builds independently, so
79
+ // a failure on one (e.g. an already-active materialization) does not block the
80
+ // others — report an aggregate outcome.
81
+ const createMaterializations = useMutationWithApiError({
82
+ mutationFn: async (opts: {
83
+ packageNames: string[];
84
+ forceRefresh: boolean;
85
+ }) => {
86
+ const results = await Promise.allSettled(
87
+ opts.packageNames.map((pkg) =>
88
+ apiClients.materializations.createMaterialization(
89
+ environmentName,
90
+ pkg,
91
+ { forceRefresh: opts.forceRefresh },
92
+ ),
93
+ ),
94
+ );
95
+ const failed = results.filter((r) => r.status === "rejected").length;
96
+ return { total: opts.packageNames.length, failed };
97
+ },
98
+ onSuccess(result) {
99
+ const started = result.total - result.failed;
100
+ setNotificationMessage(
101
+ result.failed === 0
102
+ ? `Started ${started} materialization${started === 1 ? "" : "s"}`
103
+ : `Started ${started} of ${result.total} (${result.failed} could not start — a build may already be active)`,
104
+ );
105
+ invalidateList();
106
+ },
107
+ onError(error) {
108
+ setNotificationMessage(error.message);
109
+ invalidateList();
110
+ },
111
+ });
112
+
113
+ const stopMaterialization = useMutationWithApiError({
114
+ mutationFn: (materialization: Materialization) =>
115
+ apiClients.materializations.materializationAction(
116
+ environmentName,
117
+ materialization.packageName as string,
118
+ materialization.id as string,
119
+ MaterializationActionActionEnum.Stop,
120
+ ),
121
+ onSuccess() {
122
+ setNotificationMessage("Materialization stopped");
123
+ invalidateList();
124
+ },
125
+ onError(error) {
126
+ setNotificationMessage(error.message);
127
+ },
128
+ });
129
+
130
+ const deleteMaterialization = useMutationWithApiError({
131
+ mutationFn: ({
132
+ materialization,
133
+ dropTables,
134
+ }: {
135
+ materialization: Materialization;
136
+ dropTables: boolean;
137
+ }) =>
138
+ apiClients.materializations.deleteMaterialization(
139
+ environmentName,
140
+ materialization.packageName as string,
141
+ materialization.id as string,
142
+ dropTables,
143
+ ),
144
+ onSuccess() {
145
+ setNotificationMessage("Materialization deleted");
146
+ invalidateList();
147
+ },
148
+ onError(error) {
149
+ setNotificationMessage(error.message);
150
+ },
151
+ });
152
+
153
+ const materializations = (listQuery.data?.data ?? []) as Materialization[];
154
+ const packages = (packagesQuery.data?.data ?? []) as { name: string }[];
155
+ const selected =
156
+ materializations.find((row) => row.id === selectedId) ?? null;
157
+ const isMutating =
158
+ createMaterializations.isPending ||
159
+ stopMaterialization.isPending ||
160
+ deleteMaterialization.isPending;
161
+ // A full page implies there may be older runs beyond the current window.
162
+ const hasMore = materializations.length >= limit;
163
+
164
+ return (
165
+ <Box>
166
+ <Box
167
+ sx={{
168
+ display: "flex",
169
+ alignItems: "flex-start",
170
+ justifyContent: "space-between",
171
+ mb: 3,
172
+ }}
173
+ >
174
+ <Box>
175
+ <Typography
176
+ variant="h6"
177
+ sx={{ fontWeight: 600, letterSpacing: "-0.025em" }}
178
+ >
179
+ Materializations
180
+ </Typography>
181
+ <Typography variant="body2" color="text.secondary">
182
+ All materialization builds across the packages in this
183
+ environment
184
+ </Typography>
185
+ </Box>
186
+ {mutable && (
187
+ <CreateEnvMaterializationDialog
188
+ packages={packages}
189
+ onSubmit={(opts) => createMaterializations.mutateAsync(opts)}
190
+ isSubmitting={createMaterializations.isPending}
191
+ />
192
+ )}
193
+ </Box>
194
+
195
+ {listQuery.isError && (
196
+ <ApiErrorDisplay
197
+ error={listQuery.error}
198
+ context={`${environmentName} > Materializations`}
199
+ />
200
+ )}
201
+ {!listQuery.isSuccess && !listQuery.isError && (
202
+ <Loading text="Loading materializations..." />
203
+ )}
204
+ {listQuery.isSuccess && (
205
+ <MaterializationRunsList
206
+ materializations={materializations}
207
+ mutable={mutable}
208
+ isMutating={isMutating}
209
+ showPackage
210
+ onClickPackage={(packageName) =>
211
+ onClick(`/${environmentName}/${packageName}/materializations`)
212
+ }
213
+ onStop={(materialization) =>
214
+ stopMaterialization.mutate(materialization)
215
+ }
216
+ onDelete={(materialization, dropTables) =>
217
+ deleteMaterialization.mutate({ materialization, dropTables })
218
+ }
219
+ onViewDetails={(materialization) =>
220
+ setSelectedId(materialization.id ?? null)
221
+ }
222
+ />
223
+ )}
224
+ {listQuery.isSuccess && materializations.length > 0 && (
225
+ <Stack
226
+ direction="row"
227
+ alignItems="center"
228
+ justifyContent="center"
229
+ spacing={2}
230
+ sx={{ mt: 2 }}
231
+ >
232
+ <Typography variant="caption" color="text.secondary">
233
+ {hasMore
234
+ ? `Showing ${materializations.length} most recent`
235
+ : `${materializations.length} materialization${materializations.length === 1 ? "" : "s"}`}
236
+ </Typography>
237
+ {hasMore && (
238
+ <Button
239
+ size="small"
240
+ onClick={() => setLimit((l) => l + PAGE_SIZE)}
241
+ disabled={listQuery.isFetching}
242
+ >
243
+ Show more
244
+ </Button>
245
+ )}
246
+ </Stack>
247
+ )}
248
+
249
+ <MaterializationDetailDialog
250
+ materialization={selected}
251
+ buildPlan={null}
252
+ showBuildPlan={false}
253
+ onClose={() => setSelectedId(null)}
254
+ />
255
+
256
+ <Snackbar
257
+ open={notificationMessage !== ""}
258
+ autoHideDuration={6000}
259
+ onClose={() => setNotificationMessage("")}
260
+ message={notificationMessage}
261
+ />
262
+ </Box>
263
+ );
264
+ }
@@ -10,6 +10,7 @@ import {
10
10
  } from "@mui/material";
11
11
  import { ManifestEntry } from "../../client";
12
12
  import { MONO_FONT_FAMILY } from "../styles";
13
+ import SectionLabel from "./SectionLabel";
13
14
  import { formatTimestamp } from "./utils";
14
15
 
15
16
  type ManifestViewProps = {
@@ -31,9 +32,7 @@ export default function ManifestView({ entries, builtAt }: ManifestViewProps) {
31
32
  alignItems="baseline"
32
33
  justifyContent="space-between"
33
34
  >
34
- <Typography variant="subtitle2" gutterBottom>
35
- Build manifest
36
- </Typography>
35
+ <SectionLabel>Build manifest</SectionLabel>
37
36
  {builtAt && (
38
37
  <Typography variant="caption" color="text.secondary">
39
38
  Built {formatTimestamp(builtAt)}
@@ -17,9 +17,12 @@ import {
17
17
  import { BuildPlan, Materialization } from "../../client";
18
18
  import { MONO_FONT_FAMILY } from "../styles";
19
19
  import ManifestView from "./ManifestView";
20
+ import SectionLabel from "./SectionLabel";
21
+ import TriggerChip from "./TriggerChip";
20
22
  import {
21
23
  formatDuration,
22
24
  formatTimestamp,
25
+ isActiveStatus,
23
26
  parseMetadata,
24
27
  statusColor,
25
28
  statusLabel,
@@ -29,13 +32,22 @@ type MaterializationDetailDialogProps = {
29
32
  materialization: Materialization | null;
30
33
  // The compiled package's current build plan (Package.buildPlan), shown for
31
34
  // context. It is a property of the package version, not the historical run.
35
+ // Note it is null both in the environment-scoped view and for a package with
36
+ // no persist sources, so it cannot itself gate the section — see
37
+ // `showBuildPlan`.
32
38
  buildPlan: BuildPlan | null;
39
+ // Whether to render the Build plan section. The package view shows it (with a
40
+ // "no persist sources" empty state when the plan is empty); the
41
+ // environment-scoped view, which spans packages and has no single plan, omits
42
+ // it entirely.
43
+ showBuildPlan?: boolean;
33
44
  onClose: () => void;
34
45
  };
35
46
 
36
47
  export default function MaterializationDetailDialog({
37
48
  materialization,
38
49
  buildPlan,
50
+ showBuildPlan = true,
39
51
  onClose,
40
52
  }: MaterializationDetailDialogProps) {
41
53
  const meta = materialization ? parseMetadata(materialization) : {};
@@ -51,20 +63,41 @@ export default function MaterializationDetailDialog({
51
63
  >
52
64
  {materialization && (
53
65
  <>
54
- <DialogTitle sx={{ pr: 6 }} id="materialization-detail-title">
55
- <Stack direction="row" alignItems="center" spacing={1.5}>
66
+ <DialogTitle
67
+ sx={{ pr: 6, pb: 1.5 }}
68
+ id="materialization-detail-title"
69
+ >
70
+ <Stack
71
+ direction="row"
72
+ alignItems="center"
73
+ spacing={1}
74
+ sx={{ mb: 0.75 }}
75
+ >
56
76
  <Chip
57
77
  size="small"
58
78
  label={statusLabel(materialization.status)}
59
79
  color={statusColor(materialization.status)}
80
+ variant={
81
+ isActiveStatus(materialization.status)
82
+ ? "filled"
83
+ : "outlined"
84
+ }
60
85
  />
61
- <Typography
62
- variant="subtitle1"
63
- sx={{ fontFamily: MONO_FONT_FAMILY }}
64
- >
65
- {materialization.id}
66
- </Typography>
86
+ <TriggerChip meta={meta} />
67
87
  </Stack>
88
+ <Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
89
+ {materialization.packageName ?? "Materialization"}
90
+ </Typography>
91
+ <Typography
92
+ variant="caption"
93
+ color="text.secondary"
94
+ sx={{
95
+ fontFamily: MONO_FONT_FAMILY,
96
+ wordBreak: "break-all",
97
+ }}
98
+ >
99
+ {materialization.id}
100
+ </Typography>
68
101
  <IconButton
69
102
  aria-label="close"
70
103
  onClick={onClose}
@@ -76,21 +109,16 @@ export default function MaterializationDetailDialog({
76
109
  <DialogContent dividers>
77
110
  <Box
78
111
  sx={{
112
+ bgcolor: "action.hover",
113
+ borderRadius: 2,
114
+ p: 2,
115
+ mb: 3,
79
116
  display: "grid",
80
117
  gridTemplateColumns:
81
- "repeat(auto-fit, minmax(150px, 1fr))",
118
+ "repeat(auto-fit, minmax(140px, 1fr))",
82
119
  gap: 2,
83
- mb: 3,
84
120
  }}
85
121
  >
86
- <DetailField
87
- label="Package"
88
- value={materialization.packageName ?? "—"}
89
- />
90
- <DetailField
91
- label="Force refresh"
92
- value={meta.forceRefresh ? "Yes" : "No"}
93
- />
94
122
  <DetailField
95
123
  label="Started"
96
124
  value={formatTimestamp(
@@ -110,32 +138,30 @@ export default function MaterializationDetailDialog({
110
138
  )}
111
139
  />
112
140
  <DetailField
113
- label="Sources built"
114
- value={`${meta.sourcesBuilt ?? 0}`}
115
- />
116
- <DetailField
117
- label="Sources reused"
118
- value={`${meta.sourcesReused ?? 0}`}
119
- />
120
- <DetailField
121
- label="Created"
122
- value={formatTimestamp(materialization.createdAt)}
141
+ label="Sources"
142
+ value={`${meta.sourcesBuilt ?? 0} built · ${meta.sourcesReused ?? 0} reused`}
123
143
  />
124
144
  <DetailField
125
- label="Updated"
126
- value={formatTimestamp(materialization.updatedAt)}
145
+ label="Force refresh"
146
+ value={meta.forceRefresh ? "Yes" : "No"}
127
147
  />
128
148
  </Box>
129
149
 
130
150
  {materialization.error && (
131
- <Box sx={{ mb: 3 }}>
132
- <Typography
133
- variant="subtitle2"
134
- color="error"
135
- gutterBottom
136
- >
137
- Error
138
- </Typography>
151
+ <Box
152
+ sx={{
153
+ borderRadius: 2,
154
+ p: 2,
155
+ mb: 3,
156
+ border: "1px solid",
157
+ borderColor: "error.main",
158
+ }}
159
+ >
160
+ <SectionLabel>
161
+ <Box component="span" sx={{ color: "error.main" }}>
162
+ Error
163
+ </Box>
164
+ </SectionLabel>
139
165
  <Typography
140
166
  variant="body2"
141
167
  sx={{
@@ -148,64 +174,64 @@ export default function MaterializationDetailDialog({
148
174
  </Box>
149
175
  )}
150
176
 
151
- <Box sx={{ mb: 3 }}>
152
- <Typography variant="subtitle2" gutterBottom>
153
- Build plan
154
- </Typography>
155
- {planSources.length === 0 ? (
156
- <Typography
157
- variant="body2"
158
- color="text.secondary"
159
- sx={{ fontStyle: "italic" }}
160
- >
161
- This package has no persist sources.
162
- </Typography>
163
- ) : (
164
- <Table size="small">
165
- <TableHead>
166
- <TableRow>
167
- <TableCell>Source</TableCell>
168
- <TableCell>Connection</TableCell>
169
- <TableCell>Dialect</TableCell>
170
- <TableCell align="right">Columns</TableCell>
171
- <TableCell>Source Entity ID</TableCell>
172
- </TableRow>
173
- </TableHead>
174
- <TableBody>
175
- {planSources.map((source) => (
176
- <TableRow key={source.sourceID}>
177
- <TableCell
178
- sx={{ fontFamily: MONO_FONT_FAMILY }}
179
- >
180
- {source.name}
181
- </TableCell>
182
- <TableCell
183
- sx={{ fontFamily: MONO_FONT_FAMILY }}
184
- >
185
- {source.connectionName}
186
- </TableCell>
187
- <TableCell>
188
- {source.dialect ?? "-"}
189
- </TableCell>
190
- <TableCell align="right">
191
- {source.columns?.length ?? 0}
192
- </TableCell>
193
- <TableCell
194
- sx={{
195
- fontFamily: MONO_FONT_FAMILY,
196
- fontSize: "0.75rem",
197
- wordBreak: "break-all",
198
- maxWidth: 220,
199
- }}
200
- >
201
- {source.sourceEntityId}
202
- </TableCell>
177
+ {showBuildPlan && (
178
+ <Box sx={{ mb: 3 }}>
179
+ <SectionLabel>Build plan</SectionLabel>
180
+ {planSources.length === 0 ? (
181
+ <Typography
182
+ variant="body2"
183
+ color="text.secondary"
184
+ sx={{ fontStyle: "italic" }}
185
+ >
186
+ This package has no persist sources.
187
+ </Typography>
188
+ ) : (
189
+ <Table size="small">
190
+ <TableHead>
191
+ <TableRow>
192
+ <TableCell>Source</TableCell>
193
+ <TableCell>Connection</TableCell>
194
+ <TableCell>Dialect</TableCell>
195
+ <TableCell align="right">Columns</TableCell>
196
+ <TableCell>Source Entity ID</TableCell>
203
197
  </TableRow>
204
- ))}
205
- </TableBody>
206
- </Table>
207
- )}
208
- </Box>
198
+ </TableHead>
199
+ <TableBody>
200
+ {planSources.map((source) => (
201
+ <TableRow key={source.sourceID}>
202
+ <TableCell
203
+ sx={{ fontFamily: MONO_FONT_FAMILY }}
204
+ >
205
+ {source.name}
206
+ </TableCell>
207
+ <TableCell
208
+ sx={{ fontFamily: MONO_FONT_FAMILY }}
209
+ >
210
+ {source.connectionName}
211
+ </TableCell>
212
+ <TableCell>
213
+ {source.dialect ?? "-"}
214
+ </TableCell>
215
+ <TableCell align="right">
216
+ {source.columns?.length ?? 0}
217
+ </TableCell>
218
+ <TableCell
219
+ sx={{
220
+ fontFamily: MONO_FONT_FAMILY,
221
+ fontSize: "0.75rem",
222
+ wordBreak: "break-all",
223
+ maxWidth: 220,
224
+ }}
225
+ >
226
+ {source.sourceEntityId}
227
+ </TableCell>
228
+ </TableRow>
229
+ ))}
230
+ </TableBody>
231
+ </Table>
232
+ )}
233
+ </Box>
234
+ )}
209
235
 
210
236
  <ManifestView
211
237
  entries={materialization.manifest?.entries}
@@ -5,6 +5,7 @@ import {
5
5
  Box,
6
6
  Chip,
7
7
  IconButton,
8
+ Link,
8
9
  ListItemIcon,
9
10
  ListItemText,
10
11
  Menu,
@@ -20,6 +21,7 @@ import {
20
21
  import { useState } from "react";
21
22
  import { Materialization } from "../../client";
22
23
  import DeleteMaterializationDialog from "./DeleteMaterializationDialog";
24
+ import TriggerChip from "./TriggerChip";
23
25
  import {
24
26
  formatDuration,
25
27
  formatRelativeTime,
@@ -34,6 +36,10 @@ type MaterializationRunsListProps = {
34
36
  materializations: Materialization[];
35
37
  mutable: boolean;
36
38
  isMutating: boolean;
39
+ /** Show a Package column — for the environment-scoped view spanning packages. */
40
+ showPackage?: boolean;
41
+ /** Navigate to a package (used by the Package column links). */
42
+ onClickPackage?: (packageName: string) => void;
37
43
  onStop: (materialization: Materialization) => void;
38
44
  onDelete: (materialization: Materialization, dropTables: boolean) => void;
39
45
  onViewDetails: (materialization: Materialization) => void;
@@ -43,6 +49,8 @@ export default function MaterializationRunsList({
43
49
  materializations,
44
50
  mutable,
45
51
  isMutating,
52
+ showPackage = false,
53
+ onClickPackage,
46
54
  onStop,
47
55
  onDelete,
48
56
  onViewDetails,
@@ -63,7 +71,9 @@ export default function MaterializationRunsList({
63
71
  <Table size="small">
64
72
  <TableHead>
65
73
  <TableRow>
74
+ {showPackage && <TableCell>Package</TableCell>}
66
75
  <TableCell>Status</TableCell>
76
+ <TableCell>Trigger</TableCell>
67
77
  <TableCell>Started</TableCell>
68
78
  <TableCell>Duration</TableCell>
69
79
  <TableCell>Sources</TableCell>
@@ -77,6 +87,8 @@ export default function MaterializationRunsList({
77
87
  materialization={materialization}
78
88
  mutable={mutable}
79
89
  isMutating={isMutating}
90
+ showPackage={showPackage}
91
+ onClickPackage={onClickPackage}
80
92
  onStop={onStop}
81
93
  onDelete={onDelete}
82
94
  onViewDetails={onViewDetails}
@@ -91,6 +103,8 @@ function MaterializationRow({
91
103
  materialization,
92
104
  mutable,
93
105
  isMutating,
106
+ showPackage,
107
+ onClickPackage,
94
108
  onStop,
95
109
  onDelete,
96
110
  onViewDetails,
@@ -98,6 +112,8 @@ function MaterializationRow({
98
112
  materialization: Materialization;
99
113
  mutable: boolean;
100
114
  isMutating: boolean;
115
+ showPackage?: boolean;
116
+ onClickPackage?: (packageName: string) => void;
101
117
  onStop: (materialization: Materialization) => void;
102
118
  onDelete: (materialization: Materialization, dropTables: boolean) => void;
103
119
  onViewDetails: (materialization: Materialization) => void;
@@ -138,6 +154,24 @@ function MaterializationRow({
138
154
  tabIndex={0}
139
155
  aria-label={`View materialization ${materialization.id ?? ""} details`.trim()}
140
156
  >
157
+ {showPackage && (
158
+ <TableCell onClick={(event) => event.stopPropagation()}>
159
+ {onClickPackage ? (
160
+ <Link
161
+ component="button"
162
+ underline="hover"
163
+ onClick={() =>
164
+ onClickPackage(materialization.packageName ?? "")
165
+ }
166
+ sx={{ fontWeight: 500 }}
167
+ >
168
+ {materialization.packageName}
169
+ </Link>
170
+ ) : (
171
+ materialization.packageName
172
+ )}
173
+ </TableCell>
174
+ )}
141
175
  <TableCell>
142
176
  <Box sx={{ display: "flex", alignItems: "center", gap: 0.5 }}>
143
177
  <Chip
@@ -153,6 +187,9 @@ function MaterializationRow({
153
187
  )}
154
188
  </Box>
155
189
  </TableCell>
190
+ <TableCell>
191
+ <TriggerChip meta={meta} />
192
+ </TableCell>
156
193
  <TableCell>
157
194
  {formatRelativeTime(
158
195
  materialization.startedAt ?? materialization.createdAt,