@aglyn/plugins-workflows 1.0.0-beta.199 → 1.0.0-beta.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.
@@ -14,7 +14,6 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */ 'use client';
17
- import { _ as _extends } from "@swc/helpers/_/_extends";
18
17
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
18
  import { actionRunResult, actionRunSummary, actionTriggerLabel } from "@aglyn/aglyn/app-utils/activity-presenter";
20
19
  import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot-context";
@@ -22,31 +21,17 @@ import { HOST_EVENT_TYPES } from "@aglyn/aglyn/app-utils/workflows";
22
21
  import { CardDisplay } from "@aglyn/shared-ui-jsx";
23
22
  import ListFilterChips from "@aglyn/shared-ui-jsx/components/list-filter-chips.component";
24
23
  import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
24
+ import ListQueryNotices, { listQueryRefusals } from "@aglyn/shared-ui-jsx/components/list-query-notices.component";
25
25
  import { ListTable } from "@aglyn/shared-ui-jsx/components/list-table.component";
26
- import { inMemoryListField } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
27
- import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
28
- import { useListRowsFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-rows-filter";
29
- import { Alert, Chip, Stack, Tooltip, Typography } from "@mui/material";
30
- import { collection, limit, orderBy, query, where } from "firebase/firestore";
31
- import { useEffect, useMemo, useState } from "react";
32
- import { useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
26
+ import { listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
27
+ import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
28
+ import { Chip, Stack, Tooltip, Typography } from "@mui/material";
29
+ import { collection } from "firebase/firestore";
30
+ import { useMemo } from "react";
31
+ import { useFirestore } from "@aglyn/tenant-feature-instance";
32
+ import { useListQuery } from "@aglyn/tenant-feature-instance/hooks/use-list-query";
33
33
  import { pluginDocsHelp } from "@aglyn/aglyn";
34
- /**
35
- * Activity rows read before the run filter.
36
- *
37
- * A CEILING rather than a page, and the difference is the client-side filter
38
- * below. `activity` holds publishes, media saves and member changes as well as
39
- * runs, and only a document carrying a run verdict is a row here — so a
40
- * server page of ten activity entries might contain two runs, one, or none,
41
- * and paging the QUERY would hand the reader pages of wildly different
42
- * heights with no way to tell a short page from the end of the history.
43
- *
44
- * The filter cannot move to the server either: `actionRunResult` falls back to
45
- * reading the prose `action` for entries written before AGL-2171, which have
46
- * no `result` field, and `where('result','in',…)` excludes exactly those. That
47
- * is the same field-presence trap as ordering on an optional field, and it
48
- * would silently drop every historic run.
49
- */ const WINDOW = 200;
34
+ import { RUN_HISTORY_HEADERS, RUN_HISTORY_QUERY, runHistoryBase } from "../model/run-history.js";
50
35
  const RESULT_COLOR = {
51
36
  succeeded: 'success',
52
37
  failed: 'error',
@@ -57,24 +42,7 @@ const RESULT_LABEL = {
57
42
  failed: 'Failed',
58
43
  skipped: 'Skipped'
59
44
  };
60
- /*
61
- * What the run grid's Filters panel offers. The card holds its whole window,
62
- * so the panel and the search answer over every run it read. Trigger and
63
- * Result are picked from the event list and the three verdicts; what
64
- * happened is typed.
65
- */ const RUN_FILTER_FIELDS = [
66
- inMemoryListField('createdAtMs', 'date'),
67
- inMemoryListField('trigger', 'select'),
68
- inMemoryListField('result', 'select'),
69
- inMemoryListField('summary', 'text')
70
- ];
71
- const RUN_FILTER_HEADERS = {
72
- createdAtMs: 'Time',
73
- trigger: 'Trigger',
74
- result: 'Result',
75
- summary: 'What happened'
76
- };
77
- const RUN_FILTER_OPTIONS = {
45
+ /** Trigger and Result are picked: the event list and the three verdicts. */ const RUN_FILTER_OPTIONS = {
78
46
  trigger: HOST_EVENT_TYPES.map((event)=>({
79
47
  value: event,
80
48
  label: actionTriggerLabel(event)
@@ -84,10 +52,7 @@ const RUN_FILTER_OPTIONS = {
84
52
  label: RESULT_LABEL[result]
85
53
  }))
86
54
  };
87
- /** What the quick search reads on a run row. */ const RUN_SEARCH_FIELDS = [
88
- 'summary',
89
- 'triggerLabel'
90
- ];
55
+ const RUN_SELECT_FIELDS = Object.keys(RUN_FILTER_OPTIONS);
91
56
  /**
92
57
  * The run-history table `/product/workflows` advertises (AGL-2171):
93
58
  * `Time | Trigger | Result | What happened`.
@@ -99,12 +64,14 @@ const RUN_FILTER_OPTIONS = {
99
64
  * `Action ran on formSubmission — My automation` where the mockup shows
100
65
  * four columns.
101
66
  *
102
- * `actionRunResult` returning `undefined` is the filter: the activity
103
- * collection holds far more than runs, and a publish has no verdict. An
104
- * entry that is not a run simply does not appear, rather than appearing
105
- * under a `Succeeded` that means nothing for it.
67
+ * EVERY FILTER IS ON THE QUERY (AGL-3321). The history is one Firestore
68
+ * query over `hosts/{hostId}/activity` — this automation's entries, runs
69
+ * only, newest first — with each Filters-panel clause and the search word
70
+ * as another predicate on it (`RUN_HISTORY_QUERY`), and it pages by that
71
+ * query. A filtered page is a page of matches from the whole history, not
72
+ * the matches among entries already read; what one query cannot hold is
73
+ * said above the grid and not applied.
106
74
  */ export function HostRunHistoryCard(props) {
107
- var _ref;
108
75
  const { hostId, orgId, targetId, targetType, targetName = '', header = 'Run history', help = pluginDocsHelp('buildAWorkflow', {
109
76
  anchor: '#4-save-and-test',
110
77
  excerpt: 'Every run of this automation, including the ones a trigger ' + 'condition skipped — which is the answer to "why did it not fire?".'
@@ -123,66 +90,45 @@ const RUN_FILTER_OPTIONS = {
123
90
  targetType,
124
91
  targetName
125
92
  ]);
126
- /*
127
- * The window is NARROWED BY THE SERVER, not by the client (AGL-2292).
128
- *
129
- * A bare `limit(200)` with no ordering is not "the most recent 200 runs" —
130
- * Firestore answers an unordered limit in `__name__` order, so it is an
131
- * arbitrary slice of the host's whole activity feed. `activity` also holds
132
- * publishes, media saves and member changes, and only entries carrying a
133
- * run result survive the filter below, so a busy site could fill all 200
134
- * places with rows this card discards and report "No runs yet" for a
135
- * workflow that had run all day.
136
- *
137
- * `target.id` equality is what makes the read proportional to the card:
138
- * it asks for this workflow's rows rather than the site's. Both shapes
139
- * order by `createdAt` descending, so the ceiling keeps the NEWEST entries;
140
- * an equality with no ordering would keep an arbitrary `__name__` slice of
141
- * the automation's history once it outgrew the window. The targeted shape
142
- * is served by the `activity` (`target.id` ↑, `createdAt` ↓) composite the
143
- * console's organization activity route already uses.
144
- */ const { data: entries } = useFirestoreCollection(()=>{
145
- if (!hostId) return null;
146
- const base = collection(firestore, 'hosts', hostId, 'activity');
147
- /*
148
- * `WINDOW + 1` is a PROBE, not an off-by-one (AGL-2501).
149
- *
150
- * The card used to say "showing what we read" by saying nothing at all.
151
- * Reading one document more than the ceiling turns "there is older
152
- * history than this" into a fact for the price of a single read;
153
- * comparing `length === WINDOW` cannot, because it is wrong in both
154
- * directions at exactly the count that equals the ceiling. The probe row
155
- * is dropped below and never rendered.
156
- */ return targetId ? query(base, where('target.id', '==', targetId), orderBy('createdAt', 'desc'), limit(WINDOW + 1)) : query(base, orderBy('createdAt', 'desc'), limit(WINDOW + 1));
157
- }, // `targetId` belongs here: it now shapes the query, so a card that
158
- // switched workflows without it would go on showing the first one's runs.
159
- [
93
+ const gridFilter = useListGridFilter({
94
+ selectFields: RUN_SELECT_FIELDS
95
+ });
96
+ const activity = useMemo(()=>hostId ? collection(firestore, 'hosts', hostId, 'activity') : null, [
160
97
  firestore,
161
- hostId,
162
- targetId
163
- ], {
98
+ hostId
99
+ ]);
100
+ /*
101
+ * `target.id` equality keeps the read proportional to the card — this
102
+ * automation's rows, not the site's — and `result` keeps it to runs.
103
+ * Both, and every clause, are served by the `activity` composites the
104
+ * index file carries for `RUN_HISTORY_QUERY` beneath `createdAt` DESC.
105
+ * A different workflow is a different history, and a new plan a new
106
+ * query: the pager starts over on either.
107
+ */ const { rows: entries, status, hasMore, page, setPage, pageSize, setPageSize, plan } = useListQuery({
108
+ collection: activity,
109
+ declaration: RUN_HISTORY_QUERY,
110
+ request: {
111
+ clauses: gridFilter.clauses,
112
+ search: gridFilter.searchWords,
113
+ base: runHistoryBase(targetId, gridFilter.clauses)
114
+ },
115
+ deps: [
116
+ firestore,
117
+ hostId,
118
+ targetId
119
+ ],
164
120
  idField: '$id'
165
121
  });
166
- /** The activity ceiling bit — there are older entries than were read. */ const truncated = ((_ref = entries == null ? void 0 : entries.length) != null ? _ref : 0) > WINDOW;
167
- const runs = useMemo(()=>[
168
- ...entries != null ? entries : []
169
- ].slice(0, WINDOW).filter((entry)=>{
170
- var _entry_target;
171
- return !targetId || ((_entry_target = entry.target) == null ? void 0 : _entry_target.id) === targetId;
172
- }).map((entry)=>({
173
- entry,
174
- result: actionRunResult(entry)
175
- })).filter((row)=>Boolean(row.result)).sort((a, b)=>{
176
- var _ref, _ref1;
177
- var _b_entry_createdAt, _a_entry_createdAt;
178
- return ((_ref = (_b_entry_createdAt = b.entry.createdAt) == null ? void 0 : _b_entry_createdAt.seconds) != null ? _ref : 0) - ((_ref1 = (_a_entry_createdAt = a.entry.createdAt) == null ? void 0 : _a_entry_createdAt.seconds) != null ? _ref1 : 0);
179
- }).map(({ entry, result })=>{
180
- var _entry_trigger;
122
+ const filtering = gridFilter.clauses.length > 0 || gridFilter.searchWords.length > 0;
123
+ const runs = useMemo(()=>entries.map((entry)=>{
124
+ var _actionRunResult, _entry_trigger;
181
125
  var _entry_createdAt;
182
126
  return {
183
127
  $id: String(entry.$id),
184
128
  entry,
185
- result,
129
+ // Every entry the query returns carries a verdict; the presenter
130
+ // still reads it, so a row renders the way the feed names it.
131
+ result: (_actionRunResult = actionRunResult(entry)) != null ? _actionRunResult : 'succeeded',
186
132
  trigger: String((_entry_trigger = entry.trigger) != null ? _entry_trigger : ''),
187
133
  // `formSubmission` → `Form submitted`.
188
134
  triggerLabel: actionTriggerLabel(entry.trigger),
@@ -190,46 +136,16 @@ const RUN_FILTER_OPTIONS = {
190
136
  createdAtMs: typeof ((_entry_createdAt = entry.createdAt) == null ? void 0 : _entry_createdAt.seconds) === 'number' ? entry.createdAt.seconds * 1000 : null
191
137
  };
192
138
  }), [
193
- entries,
194
- targetId
139
+ entries
195
140
  ]);
196
- const listFilter = useListRowsFilter({
197
- rows: runs,
198
- fields: RUN_FILTER_FIELDS,
199
- options: RUN_FILTER_OPTIONS,
200
- headers: RUN_FILTER_HEADERS,
201
- search: RUN_SEARCH_FIELDS
202
- });
203
- const matched = listFilter.rows;
204
- /*
205
- * The page is a SLICE, because the rows are already in hand.
206
- *
207
- * Every run in the window has been read and paid for by the query above, so
208
- * a second query per page would buy nothing and cost a read. What the card
209
- * lacked was not a cheaper read but a control: it sliced the newest
210
- * twenty-five off the window and rendered them in one wall, so run
211
- * twenty-six was read, discarded, and unreachable — on the surface a reader
212
- * opens precisely to ask "why did it not fire the time before last?".
213
- */ const [page, setPage] = useState(0);
214
- const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT);
215
- // A different workflow is a different history: page three of the last one
216
- // is not a position in this one, and an out-of-range page renders empty
217
- // with no explanation, which reads as the runs having gone.
218
- // A narrowed list starts again at its first page, for the same reason.
219
- const searchKey = listFilter.gridFilter.searchWords.join(' ');
220
- useEffect(()=>setPage(0), [
221
- targetId,
222
- hostId,
223
- listFilter.gridFilter.clauses,
224
- searchKey
225
- ]);
226
- const shown = useMemo(()=>matched.slice(page * pageSize, page * pageSize + pageSize), [
227
- matched,
228
- page,
229
- pageSize
141
+ const refused = useMemo(()=>listQueryRefusals(plan.refused, {
142
+ fields: RUN_HISTORY_QUERY.fields,
143
+ headers: RUN_HISTORY_HEADERS,
144
+ options: RUN_FILTER_OPTIONS
145
+ }), [
146
+ plan
230
147
  ]);
231
- const { filterColumns } = listFilter;
232
- const columns = useMemo(()=>filterColumns([
148
+ const columns = useMemo(()=>listFilterGridColumns([
233
149
  {
234
150
  field: 'createdAtMs',
235
151
  headerName: 'Time',
@@ -292,8 +208,7 @@ const RUN_FILTER_OPTIONS = {
292
208
  ]
293
209
  })
294
210
  }
295
- ]), [
296
- filterColumns,
211
+ ], RUN_HISTORY_QUERY.fields, RUN_FILTER_OPTIONS, RUN_HISTORY_HEADERS), [
297
212
  RunZone,
298
213
  zoneTarget,
299
214
  hostId,
@@ -305,41 +220,49 @@ const RUN_FILTER_OPTIONS = {
305
220
  contentGutterX: true,
306
221
  contentGutterY: true,
307
222
  contentBordered: "all",
308
- children: runs.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
223
+ children: !filtering && page === 0 && runs.length === 0 && status === 'success' ? /*#__PURE__*/ _jsx(Typography, {
309
224
  variant: "body2",
310
225
  color: "text.secondary",
311
226
  children: 'No runs yet — every run of this automation is logged here, ' + 'including the ones a condition skipped.'
312
227
  }) : /*#__PURE__*/ _jsxs(Stack, {
313
228
  spacing: 1.5,
314
229
  children: [
315
- /*#__PURE__*/ _jsx(ListFilterChips, _extends({}, listFilter.chipsProps)),
316
- /*#__PURE__*/ _jsx(ListTable, _extends({
230
+ /*#__PURE__*/ _jsx(ListFilterChips, {
231
+ fields: RUN_HISTORY_QUERY.fields,
232
+ headers: RUN_HISTORY_HEADERS,
233
+ clauses: gridFilter.clauses,
234
+ onChange: gridFilter.setClauses,
235
+ options: RUN_FILTER_OPTIONS
236
+ }),
237
+ /*#__PURE__*/ _jsx(ListQueryNotices, {
238
+ refused: refused,
239
+ notices: plan.notices
240
+ }),
241
+ /*#__PURE__*/ _jsx(ListTable, {
317
242
  "aria-label": "Run history",
318
- rows: shown,
319
- columns: columns
320
- }, listFilter.gridProps, {
243
+ rows: runs,
244
+ columns: columns,
245
+ filterMode: "server",
246
+ filterModel: gridFilter.filterModel,
247
+ onFilterModelChange: gridFilter.onFilterModelChange,
248
+ quickFilter: true,
249
+ // Newest first is the query's one order.
250
+ sortingMode: "server",
251
+ disableColumnSorting: true,
321
252
  // A failed run carries what other plugins add under its summary.
322
253
  getRowHeight: ()=>'auto',
323
- // `ListPagination` below pages the matches.
254
+ // `ListPagination` below pages the query.
324
255
  hideFooter: true,
325
256
  noRowsLabel: "No runs match these filters"
326
- })),
257
+ }),
327
258
  /*#__PURE__*/ _jsx(ListPagination, {
328
259
  page: page,
329
260
  pageSize: pageSize,
330
- rowCount: shown.length,
331
- // The runs in the window that match, which is a number this card
332
- // genuinely holds. What it does not know is how many runs are OLDER
333
- // than the window, and the notice below says so rather than letting
334
- // the count line imply a total it cannot see.
335
- count: matched.length,
261
+ rowCount: runs.length,
262
+ hasMore: hasMore,
336
263
  onPageChange: setPage,
337
264
  onPageSizeChange: setPageSize
338
- }),
339
- truncated ? /*#__PURE__*/ _jsx(Alert, {
340
- severity: "info",
341
- children: `Runs found in the ${WINDOW} most recent activity entries for ` + (targetId ? 'this automation' : 'this site') + '. Older runs than that are recorded and are not listed here.'
342
- }) : null
265
+ })
343
266
  ]
344
267
  })
345
268
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/host-run-history-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n actionRunResult,\n actionRunSummary,\n actionTriggerLabel,\n} from '@aglyn/aglyn/app-utils/activity-presenter'\nimport { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\nimport { HOST_EVENT_TYPES } from '@aglyn/aglyn/app-utils/workflows'\nimport type { ConsoleAutomationTarget } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { CardDisplay, type HelpTipContent } from '@aglyn/shared-ui-jsx'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { inMemoryListField } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useListRowsFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-rows-filter'\nimport { Alert, Chip, Stack, Tooltip, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { collection, limit, orderBy, query, where } from 'firebase/firestore'\nimport { useEffect, useMemo, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n} from '@aglyn/tenant-feature-instance'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\n\nexport interface HostRunHistoryCardProps {\n hostId: string\n /** The org the page names, for what other plugins add to a failed run. */\n orgId?: string\n /** Show only runs of this action/workflow. */\n targetId?: string\n /**\n * What `targetId` names, and its name. A run row records its automation's id\n * but not whether it is an action or a workflow, so the card that opened the\n * history says; without it, a failed run offers nothing beside its summary.\n */\n targetType?: ConsoleAutomationTarget['type']\n targetName?: string\n header?: string\n /** Overrides the default help affordance on the card header. */\n help?: HelpTipContent\n}\n\n/**\n * Activity rows read before the run filter.\n *\n * A CEILING rather than a page, and the difference is the client-side filter\n * below. `activity` holds publishes, media saves and member changes as well as\n * runs, and only a document carrying a run verdict is a row here — so a\n * server page of ten activity entries might contain two runs, one, or none,\n * and paging the QUERY would hand the reader pages of wildly different\n * heights with no way to tell a short page from the end of the history.\n *\n * The filter cannot move to the server either: `actionRunResult` falls back to\n * reading the prose `action` for entries written before AGL-2171, which have\n * no `result` field, and `where('result','in',…)` excludes exactly those. That\n * is the same field-presence trap as ordering on an optional field, and it\n * would silently drop every historic run.\n */\nconst WINDOW = 200\n\nconst RESULT_COLOR = {\n succeeded: 'success',\n failed: 'error',\n skipped: 'warning',\n} as const\n\nconst RESULT_LABEL = {\n succeeded: 'Succeeded',\n failed: 'Failed',\n skipped: 'Skipped',\n} as const\n\ntype RunResult = keyof typeof RESULT_LABEL\n\n/** One run as the grid holds it: the entry, and what it is shown and filtered by. */\ninterface RunRow {\n $id: string\n entry: any\n result: RunResult\n trigger: string\n triggerLabel: string\n summary: string\n createdAtMs: number | null\n}\n\n/*\n * What the run grid's Filters panel offers. The card holds its whole window,\n * so the panel and the search answer over every run it read. Trigger and\n * Result are picked from the event list and the three verdicts; what\n * happened is typed.\n */\nconst RUN_FILTER_FIELDS = [\n inMemoryListField('createdAtMs', 'date'),\n inMemoryListField('trigger', 'select'),\n inMemoryListField('result', 'select'),\n inMemoryListField('summary', 'text'),\n]\nconst RUN_FILTER_HEADERS: Readonly<Record<string, string>> = {\n createdAtMs: 'Time',\n trigger: 'Trigger',\n result: 'Result',\n summary: 'What happened',\n}\nconst RUN_FILTER_OPTIONS = {\n trigger: HOST_EVENT_TYPES.map((event) => ({\n value: event,\n label: actionTriggerLabel(event),\n })),\n result: (Object.keys(RESULT_LABEL) as RunResult[]).map((result) => ({\n value: result,\n label: RESULT_LABEL[result],\n })),\n}\n/** What the quick search reads on a run row. */\nconst RUN_SEARCH_FIELDS = ['summary', 'triggerLabel'] as const\n\n/**\n * The run-history table `/product/workflows` advertises (AGL-2171):\n * `Time | Trigger | Result | What happened`.\n *\n * A separate component from `HostActivityCard` on purpose. That card is\n * the site's general feed — publishes, media saves, member changes — and\n * a run is a different record with different columns. Pointing the Runs\n * dialog at the general feed is what produced a list of\n * `Action ran on formSubmission — My automation` where the mockup shows\n * four columns.\n *\n * `actionRunResult` returning `undefined` is the filter: the activity\n * collection holds far more than runs, and a publish has no verdict. An\n * entry that is not a run simply does not appear, rather than appearing\n * under a `Succeeded` that means nothing for it.\n */\nexport function HostRunHistoryCard(props: HostRunHistoryCardProps) {\n const {\n hostId,\n orgId,\n targetId,\n targetType,\n targetName = '',\n header = 'Run history',\n help = pluginDocsHelp('buildAWorkflow', {\n anchor: '#4-save-and-test',\n excerpt:\n 'Every run of this automation, including the ones a trigger ' +\n 'condition skipped — which is the answer to \"why did it not fire?\".',\n }),\n } = props\n const firestore = useFirestore()\n /**\n * The shell's zone renderer (AGL-2919), for what other plugins add to a\n * failed run; `null` outside the console shell.\n */\n const RunZone = useConsoleWidgetSlot()\n /** The automation a failed run belongs to, as the `automationRun` zone names it. */\n const zoneTarget = useMemo<ConsoleAutomationTarget | null>(\n () => (targetId && targetType ? { type: targetType, id: targetId, name: targetName } : null),\n [targetId, targetType, targetName],\n )\n /*\n * The window is NARROWED BY THE SERVER, not by the client (AGL-2292).\n *\n * A bare `limit(200)` with no ordering is not \"the most recent 200 runs\" —\n * Firestore answers an unordered limit in `__name__` order, so it is an\n * arbitrary slice of the host's whole activity feed. `activity` also holds\n * publishes, media saves and member changes, and only entries carrying a\n * run result survive the filter below, so a busy site could fill all 200\n * places with rows this card discards and report \"No runs yet\" for a\n * workflow that had run all day.\n *\n * `target.id` equality is what makes the read proportional to the card:\n * it asks for this workflow's rows rather than the site's. Both shapes\n * order by `createdAt` descending, so the ceiling keeps the NEWEST entries;\n * an equality with no ordering would keep an arbitrary `__name__` slice of\n * the automation's history once it outgrew the window. The targeted shape\n * is served by the `activity` (`target.id` ↑, `createdAt` ↓) composite the\n * console's organization activity route already uses.\n */\n const { data: entries } = useFirestoreCollection<any>(\n () => {\n if (!hostId) return null\n const base = collection(firestore, 'hosts', hostId, 'activity')\n /*\n * `WINDOW + 1` is a PROBE, not an off-by-one (AGL-2501).\n *\n * The card used to say \"showing what we read\" by saying nothing at all.\n * Reading one document more than the ceiling turns \"there is older\n * history than this\" into a fact for the price of a single read;\n * comparing `length === WINDOW` cannot, because it is wrong in both\n * directions at exactly the count that equals the ceiling. The probe row\n * is dropped below and never rendered.\n */\n return targetId\n ? query(\n base,\n where('target.id', '==', targetId),\n orderBy('createdAt', 'desc'),\n limit(WINDOW + 1),\n )\n : query(base, orderBy('createdAt', 'desc'), limit(WINDOW + 1))\n },\n // `targetId` belongs here: it now shapes the query, so a card that\n // switched workflows without it would go on showing the first one's runs.\n [firestore, hostId, targetId],\n { idField: '$id' },\n )\n /** The activity ceiling bit — there are older entries than were read. */\n const truncated = (entries?.length ?? 0) > WINDOW\n\n const runs = useMemo(\n () =>\n [...(entries ?? [])]\n .slice(0, WINDOW)\n .filter((entry) => !targetId || entry.target?.id === targetId)\n .map((entry) => ({ entry, result: actionRunResult(entry) }))\n .filter(\n (row): row is { entry: any; result: RunResult } =>\n Boolean(row.result),\n )\n .sort(\n (a, b) =>\n (b.entry.createdAt?.seconds ?? 0) -\n (a.entry.createdAt?.seconds ?? 0),\n )\n .map(\n ({ entry, result }): RunRow => ({\n $id: String(entry.$id),\n entry,\n result,\n trigger: String(entry.trigger ?? ''),\n // `formSubmission` → `Form submitted`.\n triggerLabel: actionTriggerLabel(entry.trigger),\n summary: actionRunSummary(entry),\n createdAtMs:\n typeof entry.createdAt?.seconds === 'number'\n ? entry.createdAt.seconds * 1000\n : null,\n }),\n ),\n [entries, targetId],\n )\n const listFilter = useListRowsFilter({\n rows: runs,\n fields: RUN_FILTER_FIELDS,\n options: RUN_FILTER_OPTIONS,\n headers: RUN_FILTER_HEADERS,\n search: RUN_SEARCH_FIELDS,\n })\n const matched = listFilter.rows\n\n /*\n * The page is a SLICE, because the rows are already in hand.\n *\n * Every run in the window has been read and paid for by the query above, so\n * a second query per page would buy nothing and cost a read. What the card\n * lacked was not a cheaper read but a control: it sliced the newest\n * twenty-five off the window and rendered them in one wall, so run\n * twenty-six was read, discarded, and unreachable — on the surface a reader\n * opens precisely to ask \"why did it not fire the time before last?\".\n */\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n // A different workflow is a different history: page three of the last one\n // is not a position in this one, and an out-of-range page renders empty\n // with no explanation, which reads as the runs having gone.\n // A narrowed list starts again at its first page, for the same reason.\n const searchKey = listFilter.gridFilter.searchWords.join(' ')\n useEffect(\n () => setPage(0),\n [targetId, hostId, listFilter.gridFilter.clauses, searchKey],\n )\n const shown = useMemo(\n () => matched.slice(page * pageSize, page * pageSize + pageSize),\n [matched, page, pageSize],\n )\n\n const { filterColumns } = listFilter\n const columns = useMemo(\n () =>\n filterColumns([\n {\n field: 'createdAtMs',\n headerName: 'Time',\n width: 120,\n renderCell: ({ row }: { row: RunRow }) => {\n const at = row.entry.createdAt?.toDate?.()\n return (\n <Tooltip title={at ? at.toLocaleString() : ''}>\n <span>{at ? at.toLocaleTimeString() : '--'}</span>\n </Tooltip>\n )\n },\n },\n {\n field: 'trigger',\n headerName: 'Trigger',\n width: 180,\n renderCell: ({ row }: { row: RunRow }) => row.triggerLabel,\n },\n {\n field: 'result',\n headerName: 'Result',\n width: 120,\n renderCell: ({ row }: { row: RunRow }) => (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={RESULT_COLOR[row.result]}\n label={RESULT_LABEL[row.result]}\n />\n ),\n },\n {\n field: 'summary',\n headerName: 'What happened',\n flex: 1,\n minWidth: 220,\n renderCell: ({ row }: { row: RunRow }) => (\n <Stack sx={{ minWidth: 0, py: 0.75 }}>\n <Typography variant=\"body2\">{row.summary}</Typography>\n {row.entry.durationMs != null ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${row.entry.durationMs}ms`}\n </Typography>\n ) : null}\n {/*\n What other plugins add to a failed run (AGL-2919): the\n `automationRun` zone, through the shell's gated slot.\n */}\n {RunZone && zoneTarget && row.result === 'failed' ? (\n <RunZone\n slot=\"automationRun\"\n hostId={hostId}\n orgId={orgId}\n target={zoneTarget}\n runId={row.$id}\n />\n ) : null}\n </Stack>\n ),\n },\n ] as GridColDef[]),\n [filterColumns, RunZone, zoneTarget, hostId, orgId],\n )\n\n return (\n <CardDisplay\n header={header}\n help={help}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n {runs.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No runs yet — every run of this automation is logged here, ' +\n 'including the ones a condition skipped.'}\n </Typography>\n ) : (\n <Stack spacing={1.5}>\n <ListFilterChips {...listFilter.chipsProps} />\n <ListTable\n aria-label=\"Run history\"\n rows={shown}\n columns={columns}\n {...listFilter.gridProps}\n // A failed run carries what other plugins add under its summary.\n getRowHeight={() => 'auto'}\n // `ListPagination` below pages the matches.\n hideFooter\n noRowsLabel=\"No runs match these filters\"\n />\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={shown.length}\n // The runs in the window that match, which is a number this card\n // genuinely holds. What it does not know is how many runs are OLDER\n // than the window, and the notice below says so rather than letting\n // the count line imply a total it cannot see.\n count={matched.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n {truncated ? (\n <Alert severity=\"info\">\n {`Runs found in the ${WINDOW} most recent activity entries for ` +\n (targetId ? 'this automation' : 'this site') +\n '. Older runs than that are recorded and are not listed here.'}\n </Alert>\n ) : null}\n </Stack>\n )}\n </CardDisplay>\n )\n}\nHostRunHistoryCard.displayName = 'HostRunHistoryCard'\n\nexport default HostRunHistoryCard\n"],"names":["actionRunResult","actionRunSummary","actionTriggerLabel","useConsoleWidgetSlot","HOST_EVENT_TYPES","CardDisplay","ListFilterChips","ListPagination","ListTable","inMemoryListField","TABLE_PAGE_SIZE_DEFAULT","useListRowsFilter","Alert","Chip","Stack","Tooltip","Typography","collection","limit","orderBy","query","where","useEffect","useMemo","useState","useFirestore","useFirestoreCollection","pluginDocsHelp","WINDOW","RESULT_COLOR","succeeded","failed","skipped","RESULT_LABEL","RUN_FILTER_FIELDS","RUN_FILTER_HEADERS","createdAtMs","trigger","result","summary","RUN_FILTER_OPTIONS","map","event","value","label","Object","keys","RUN_SEARCH_FIELDS","HostRunHistoryCard","props","hostId","orgId","targetId","targetType","targetName","header","help","anchor","excerpt","firestore","RunZone","zoneTarget","type","id","name","data","entries","base","idField","truncated","length","runs","slice","filter","entry","target","row","Boolean","sort","a","b","createdAt","seconds","$id","String","triggerLabel","listFilter","rows","fields","options","headers","search","matched","page","setPage","pageSize","setPageSize","searchKey","gridFilter","searchWords","join","clauses","shown","filterColumns","columns","field","headerName","width","renderCell","at","toDate","title","toLocaleString","span","toLocaleTimeString","size","variant","color","flex","minWidth","sx","py","durationMs","slot","runId","contentGutterX","contentGutterY","contentBordered","spacing","chipsProps","aria-label","gridProps","getRowHeight","hideFooter","noRowsLabel","rowCount","count","onPageChange","onPageSizeChange","severity","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/workflows/src/lib/components/host-run-history-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n actionRunResult,\n actionRunSummary,\n actionTriggerLabel,\n} from '@aglyn/aglyn/app-utils/activity-presenter'\nimport { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\nimport { HOST_EVENT_TYPES } from '@aglyn/aglyn/app-utils/workflows'\nimport type { ConsoleAutomationTarget } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { CardDisplay, type HelpTipContent } from '@aglyn/shared-ui-jsx'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { ListTable } from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { listFilterGridColumns } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport { Chip, Stack, Tooltip, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { collection } from 'firebase/firestore'\nimport { useMemo } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { useListQuery } from '@aglyn/tenant-feature-instance/hooks/use-list-query'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport {\n RUN_HISTORY_HEADERS,\n RUN_HISTORY_QUERY,\n runHistoryBase,\n} from '../model/run-history'\n\nexport interface HostRunHistoryCardProps {\n hostId: string\n /** The org the page names, for what other plugins add to a failed run. */\n orgId?: string\n /** Show only runs of this action/workflow. */\n targetId?: string\n /**\n * What `targetId` names, and its name. A run row records its automation's id\n * but not whether it is an action or a workflow, so the card that opened the\n * history says; without it, a failed run offers nothing beside its summary.\n */\n targetType?: ConsoleAutomationTarget['type']\n targetName?: string\n header?: string\n /** Overrides the default help affordance on the card header. */\n help?: HelpTipContent\n}\n\nconst RESULT_COLOR = {\n succeeded: 'success',\n failed: 'error',\n skipped: 'warning',\n} as const\n\nconst RESULT_LABEL = {\n succeeded: 'Succeeded',\n failed: 'Failed',\n skipped: 'Skipped',\n} as const\n\ntype RunResult = keyof typeof RESULT_LABEL\n\n/** One run as the grid holds it: the entry, and what it is shown by. */\ninterface RunRow {\n $id: string\n entry: any\n result: RunResult\n trigger: string\n triggerLabel: string\n summary: string\n createdAtMs: number | null\n}\n\n/** Trigger and Result are picked: the event list and the three verdicts. */\nconst RUN_FILTER_OPTIONS = {\n trigger: HOST_EVENT_TYPES.map((event) => ({\n value: event,\n label: actionTriggerLabel(event),\n })),\n result: (Object.keys(RESULT_LABEL) as RunResult[]).map((result) => ({\n value: result,\n label: RESULT_LABEL[result],\n })),\n}\nconst RUN_SELECT_FIELDS = Object.keys(RUN_FILTER_OPTIONS)\n\n/**\n * The run-history table `/product/workflows` advertises (AGL-2171):\n * `Time | Trigger | Result | What happened`.\n *\n * A separate component from `HostActivityCard` on purpose. That card is\n * the site's general feed — publishes, media saves, member changes — and\n * a run is a different record with different columns. Pointing the Runs\n * dialog at the general feed is what produced a list of\n * `Action ran on formSubmission — My automation` where the mockup shows\n * four columns.\n *\n * EVERY FILTER IS ON THE QUERY (AGL-3321). The history is one Firestore\n * query over `hosts/{hostId}/activity` — this automation's entries, runs\n * only, newest first — with each Filters-panel clause and the search word\n * as another predicate on it (`RUN_HISTORY_QUERY`), and it pages by that\n * query. A filtered page is a page of matches from the whole history, not\n * the matches among entries already read; what one query cannot hold is\n * said above the grid and not applied.\n */\nexport function HostRunHistoryCard(props: HostRunHistoryCardProps) {\n const {\n hostId,\n orgId,\n targetId,\n targetType,\n targetName = '',\n header = 'Run history',\n help = pluginDocsHelp('buildAWorkflow', {\n anchor: '#4-save-and-test',\n excerpt:\n 'Every run of this automation, including the ones a trigger ' +\n 'condition skipped — which is the answer to \"why did it not fire?\".',\n }),\n } = props\n const firestore = useFirestore()\n /**\n * The shell's zone renderer (AGL-2919), for what other plugins add to a\n * failed run; `null` outside the console shell.\n */\n const RunZone = useConsoleWidgetSlot()\n /** The automation a failed run belongs to, as the `automationRun` zone names it. */\n const zoneTarget = useMemo<ConsoleAutomationTarget | null>(\n () =>\n targetId && targetType\n ? { type: targetType, id: targetId, name: targetName }\n : null,\n [targetId, targetType, targetName],\n )\n const gridFilter = useListGridFilter({ selectFields: RUN_SELECT_FIELDS })\n const activity = useMemo(\n () => (hostId ? collection(firestore, 'hosts', hostId, 'activity') : null),\n [firestore, hostId],\n )\n /*\n * `target.id` equality keeps the read proportional to the card — this\n * automation's rows, not the site's — and `result` keeps it to runs.\n * Both, and every clause, are served by the `activity` composites the\n * index file carries for `RUN_HISTORY_QUERY` beneath `createdAt` DESC.\n * A different workflow is a different history, and a new plan a new\n * query: the pager starts over on either.\n */\n const {\n rows: entries,\n status,\n hasMore,\n page,\n setPage,\n pageSize,\n setPageSize,\n plan,\n } = useListQuery<any>({\n collection: activity,\n declaration: RUN_HISTORY_QUERY,\n request: {\n clauses: gridFilter.clauses,\n search: gridFilter.searchWords,\n base: runHistoryBase(targetId, gridFilter.clauses),\n },\n deps: [firestore, hostId, targetId],\n idField: '$id',\n })\n const filtering =\n gridFilter.clauses.length > 0 || gridFilter.searchWords.length > 0\n\n const runs = useMemo(\n () =>\n entries.map((entry): RunRow => ({\n $id: String(entry.$id),\n entry,\n // Every entry the query returns carries a verdict; the presenter\n // still reads it, so a row renders the way the feed names it.\n result: actionRunResult(entry) ?? 'succeeded',\n trigger: String(entry.trigger ?? ''),\n // `formSubmission` → `Form submitted`.\n triggerLabel: actionTriggerLabel(entry.trigger),\n summary: actionRunSummary(entry),\n createdAtMs:\n typeof entry.createdAt?.seconds === 'number'\n ? entry.createdAt.seconds * 1000\n : null,\n })),\n [entries],\n )\n const refused = useMemo(\n () =>\n listQueryRefusals(plan.refused, {\n fields: RUN_HISTORY_QUERY.fields,\n headers: RUN_HISTORY_HEADERS,\n options: RUN_FILTER_OPTIONS,\n }),\n [plan],\n )\n\n const columns = useMemo(\n () =>\n listFilterGridColumns(\n [\n {\n field: 'createdAtMs',\n headerName: 'Time',\n width: 120,\n renderCell: ({ row }: { row: RunRow }) => {\n const at = row.entry.createdAt?.toDate?.()\n return (\n <Tooltip title={at ? at.toLocaleString() : ''}>\n <span>{at ? at.toLocaleTimeString() : '--'}</span>\n </Tooltip>\n )\n },\n },\n {\n field: 'trigger',\n headerName: 'Trigger',\n width: 180,\n renderCell: ({ row }: { row: RunRow }) => row.triggerLabel,\n },\n {\n field: 'result',\n headerName: 'Result',\n width: 120,\n renderCell: ({ row }: { row: RunRow }) => (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={RESULT_COLOR[row.result]}\n label={RESULT_LABEL[row.result]}\n />\n ),\n },\n {\n field: 'summary',\n headerName: 'What happened',\n flex: 1,\n minWidth: 220,\n renderCell: ({ row }: { row: RunRow }) => (\n <Stack sx={{ minWidth: 0, py: 0.75 }}>\n <Typography variant=\"body2\">{row.summary}</Typography>\n {row.entry.durationMs != null ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${row.entry.durationMs}ms`}\n </Typography>\n ) : null}\n {/*\n What other plugins add to a failed run (AGL-2919): the\n `automationRun` zone, through the shell's gated slot.\n */}\n {RunZone && zoneTarget && row.result === 'failed' ? (\n <RunZone\n slot=\"automationRun\"\n hostId={hostId}\n orgId={orgId}\n target={zoneTarget}\n runId={row.$id}\n />\n ) : null}\n </Stack>\n ),\n },\n ] as GridColDef[],\n RUN_HISTORY_QUERY.fields,\n RUN_FILTER_OPTIONS,\n RUN_HISTORY_HEADERS,\n ),\n [RunZone, zoneTarget, hostId, orgId],\n )\n\n return (\n <CardDisplay\n header={header}\n help={help}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n {!filtering && page === 0 && runs.length === 0 && status === 'success' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No runs yet — every run of this automation is logged here, ' +\n 'including the ones a condition skipped.'}\n </Typography>\n ) : (\n <Stack spacing={1.5}>\n <ListFilterChips\n fields={RUN_HISTORY_QUERY.fields}\n headers={RUN_HISTORY_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={RUN_FILTER_OPTIONS}\n />\n <ListQueryNotices refused={refused} notices={plan.notices} />\n <ListTable\n aria-label=\"Run history\"\n rows={runs}\n columns={columns}\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n // Newest first is the query's one order.\n sortingMode=\"server\"\n disableColumnSorting\n // A failed run carries what other plugins add under its summary.\n getRowHeight={() => 'auto'}\n // `ListPagination` below pages the query.\n hideFooter\n noRowsLabel=\"No runs match these filters\"\n />\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={runs.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </Stack>\n )}\n </CardDisplay>\n )\n}\nHostRunHistoryCard.displayName = 'HostRunHistoryCard'\n\nexport default HostRunHistoryCard\n"],"names":["actionRunResult","actionRunSummary","actionTriggerLabel","useConsoleWidgetSlot","HOST_EVENT_TYPES","CardDisplay","ListFilterChips","ListPagination","ListQueryNotices","listQueryRefusals","ListTable","listFilterGridColumns","useListGridFilter","Chip","Stack","Tooltip","Typography","collection","useMemo","useFirestore","useListQuery","pluginDocsHelp","RUN_HISTORY_HEADERS","RUN_HISTORY_QUERY","runHistoryBase","RESULT_COLOR","succeeded","failed","skipped","RESULT_LABEL","RUN_FILTER_OPTIONS","trigger","map","event","value","label","result","Object","keys","RUN_SELECT_FIELDS","HostRunHistoryCard","props","hostId","orgId","targetId","targetType","targetName","header","help","anchor","excerpt","firestore","RunZone","zoneTarget","type","id","name","gridFilter","selectFields","activity","rows","entries","status","hasMore","page","setPage","pageSize","setPageSize","plan","declaration","request","clauses","search","searchWords","base","deps","idField","filtering","length","runs","entry","$id","String","triggerLabel","summary","createdAtMs","createdAt","seconds","refused","fields","headers","options","columns","field","headerName","width","renderCell","row","at","toDate","title","toLocaleString","span","toLocaleTimeString","size","variant","color","flex","minWidth","sx","py","durationMs","slot","target","runId","contentGutterX","contentGutterY","contentBordered","spacing","onChange","setClauses","notices","aria-label","filterMode","filterModel","onFilterModelChange","quickFilter","sortingMode","disableColumnSorting","getRowHeight","hideFooter","noRowsLabel","rowCount","onPageChange","onPageSizeChange","displayName"],"mappings":"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"}
@@ -918,7 +918,8 @@ import { WORKFLOW_USAGE_ZONE } from "./workflow-zones.js";
918
918
  /*#__PURE__*/ _jsxs(Dialog, {
919
919
  open: Boolean(runsFor),
920
920
  onClose: ()=>setRunsFor(null),
921
- maxWidth: "sm",
921
+ // md: the history's four columns fit without the table scrolling.
922
+ maxWidth: "md",
922
923
  fullWidth: true,
923
924
  children: [
924
925
  /*#__PURE__*/ _jsx(DialogTitle, {