@burdenoff/fluidgrids-fe-sdk 2026.1115.8 → 2026.1115.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AiStudioPage-BoxsR2hy.js → AiStudioPage-CZTaVeQE.js} +4 -4
- package/dist/{DataSinkDashboardPage-Ccg_SZmm.js → DataSinkDashboardPage-CPq6-jZM.js} +1 -1
- package/dist/{ExecutionDetailPage-DwadwaR0.js → ExecutionDetailPage-d29WPJDN.js} +7 -6
- package/dist/ExecutionSettingsFields-DzKJDOjU.js +1003 -0
- package/dist/{ExecutionsListPage-D18xVwH7.js → ExecutionsListPage-xQthHGAJ.js} +5 -23
- package/dist/{FluidGridsProvider-xFYPchhw.js → FluidGridsProvider-BRC0WlAa.js} +247 -3044
- package/dist/{HistoryPage-DjvD1-YA.js → HistoryPage-Drdk9j_Q.js} +1 -1
- package/dist/{InsightsPage-eHkVOiJC.js → InsightsPage-mYWZvyGR.js} +1 -1
- package/dist/{PaidTemplateConfirm-C4hmka25.js → PaidTemplateConfirm-Dog5moZa.js} +1 -1
- package/dist/{agent-form-D0uLvzx4.js → RunLogAppendedDocument-D1_BfMZa.js} +1 -82
- package/dist/{SettingsPage-B1DZRkIr.js → SettingsPage-DFnqjkpT.js} +236 -891
- package/dist/{TemplateCard-CCz5NW6t.js → TemplateCard-D2zt-kXW.js} +3 -3
- package/dist/{TemplatePreview-Kd2FKykc.js → TemplatePreview-BgdkcCF-.js} +4 -4
- package/dist/{TemplatesPage-DvRDWMtn.js → TemplatesPage-t7rGU-BP.js} +6 -6
- package/dist/{WorkflowBuilder-CrLctwxF.js → WorkflowBuilder-BS9kVcgU.js} +3 -3
- package/dist/{WorkflowBuilder-44buoEQ3.js → WorkflowBuilder-CtTM79lP.js} +6 -5
- package/dist/{WorkflowSearchRedirect-guNkbQSU.js → WorkflowSearchRedirect-Bqdxr833.js} +1 -1
- package/dist/WorkflowSettingsDrawer-BiqTf_tV.js +191 -0
- package/dist/WorkflowSettingsDrawer-MVU1BKfY.d.ts +18 -0
- package/dist/{WorkflowsList-Dqqk_Y-X.js → WorkflowsList-DjKePEwo.js} +2032 -514
- package/dist/agent-form-Bfvm32j1.js +83 -0
- package/dist/{agentTools-B_Po22sZ.js → agentTools-DrVlHNeP.js} +1 -1
- package/dist/{canvasNoteTools-DljlLrdC.js → canvasNoteTools-Bo2QldkD.js} +1 -1
- package/dist/executions.js +2 -2
- package/dist/{flow-builder-Dkf2pKoQ.js → flow-builder-CWfNAFd8.js} +69 -14
- package/dist/history.js +1 -1
- package/dist/{index-BFzvT8JS.d.ts → index-C2ehY2Ld.d.ts} +18 -10
- package/dist/index.d.ts +3 -3
- package/dist/index.js +38 -35
- package/dist/{node-detail-view-BxPBWA_k.js → node-detail-view-D_N3kLtA.js} +5 -5
- package/dist/{node-editor-tab-body-CfcsbpTx.js → node-editor-tab-body-BQ9JZa72.js} +2 -2
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +2 -2
- package/dist/{useAgentDefinitions-BCLCgtQt.js → operations-qq_bbgXP.js} +3 -223
- package/dist/{popover-DJ91SCN_.js → popover-DZdTN21a.js} +3 -3
- package/dist/{quick-config-panel-DnO8kCH6.js → quick-config-panel-BCUY4tQy.js} +3 -3
- package/dist/relative-time-Dxt8ZIIu.js +55 -0
- package/dist/{schedule-panel-D2nCNyJR.js → schedule-panel-B6FrpX9d.js} +6 -5
- package/dist/settings.d.ts +2 -2
- package/dist/settings.js +3 -2
- package/dist/{sidebar-node-editor-DuE6t-hj.js → sidebar-node-editor-CWrmlFlZ.js} +3 -3
- package/dist/templates.js +2 -2
- package/dist/useAgentDefinitions-DX5GvEIp.js +223 -0
- package/dist/{useDurableParity-BO--Wn7k.js → useDurableParity-BtWk4d-i.js} +1 -1
- package/dist/{useExecutions-DG6XbEsa.js → useExecutions-C9pVKexC.js} +2 -1
- package/dist/{useGlobalStoreClient-CY3q5xdv.js → useGlobalStoreClient-C2P50Gj5.js} +1 -1
- package/dist/{useObservability-CC8fdMQ-.js → useObservability-DsHX86Xl.js} +1 -1
- package/dist/{useRunHistory-DIFumHVe.js → useRunHistory-DUNLNOKA.js} +1 -1
- package/dist/{useWorkflowCodeExport-CcGfzyh6.js → useWorkflowCodeExport-DSbfcehl.js} +12 -2
- package/dist/{useWorkflows-B7ZkOKhh.js → useWorkflows-B13Cp8tk.js} +90 -10
- package/dist/workflow-builder.js +4 -4
- package/dist/{workflow-description-DIp27SKz.js → workflow-description-F3inwvof.js} +1 -1
- package/dist/workflow-pages.d.ts +2 -2
- package/dist/workflow-pages.js +13 -13
- package/dist/workflow-save-state-DUE9lNwQ.js +2813 -0
- package/dist/workflows-list.js +1 -1
- package/package.json +3 -3
- package/dist/SettingsPage-DMHQFj5-.d.ts +0 -9
|
@@ -0,0 +1,1003 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { o as throwIfResultError } from "./errors-xPZ-IbQd.js";
|
|
3
|
+
import { n as getFluidGridsClient } from "./client-Ctl4jmY3.js";
|
|
4
|
+
import { r as useTr } from "./TranslationBridge-Da-TPgQm.js";
|
|
5
|
+
import { c as findSaveConflict } from "./workflow-save-state-DUE9lNwQ.js";
|
|
6
|
+
import { At as useUpdateWorkflowDefinitionMutation, Ft as useWorkflowDefinitionQuery, Rt as useWorkflowDefinitionsQuery } from "./operations-qq_bbgXP.js";
|
|
7
|
+
import { s as useFluidGridsPermission, t as FLUIDGRIDS_PERMISSIONS } from "./constants-65QV61nQ.js";
|
|
8
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
9
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
import { gql } from "@apollo/client";
|
|
11
|
+
import { Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
|
|
13
|
+
// src/generated/documents/wspace/UpdateWorkflowNotificationSettingsDocument.ts
|
|
14
|
+
const UpdateWorkflowNotificationSettingsDocument = {
|
|
15
|
+
"kind": "Document",
|
|
16
|
+
"definitions": [{
|
|
17
|
+
"kind": "OperationDefinition",
|
|
18
|
+
"operation": "mutation",
|
|
19
|
+
"name": {
|
|
20
|
+
"kind": "Name",
|
|
21
|
+
"value": "UpdateWorkflowNotificationSettings"
|
|
22
|
+
},
|
|
23
|
+
"variableDefinitions": [{
|
|
24
|
+
"kind": "VariableDefinition",
|
|
25
|
+
"variable": {
|
|
26
|
+
"kind": "Variable",
|
|
27
|
+
"name": {
|
|
28
|
+
"kind": "Name",
|
|
29
|
+
"value": "input"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"type": {
|
|
33
|
+
"kind": "NonNullType",
|
|
34
|
+
"type": {
|
|
35
|
+
"kind": "NamedType",
|
|
36
|
+
"name": {
|
|
37
|
+
"kind": "Name",
|
|
38
|
+
"value": "UpdateWorkflowNotificationSettingsInput"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"directives": []
|
|
43
|
+
}],
|
|
44
|
+
"directives": [],
|
|
45
|
+
"selectionSet": {
|
|
46
|
+
"kind": "SelectionSet",
|
|
47
|
+
"selections": [{
|
|
48
|
+
"kind": "Field",
|
|
49
|
+
"name": {
|
|
50
|
+
"kind": "Name",
|
|
51
|
+
"value": "updateWorkflowNotificationSettings"
|
|
52
|
+
},
|
|
53
|
+
"arguments": [{
|
|
54
|
+
"kind": "Argument",
|
|
55
|
+
"name": {
|
|
56
|
+
"kind": "Name",
|
|
57
|
+
"value": "input"
|
|
58
|
+
},
|
|
59
|
+
"value": {
|
|
60
|
+
"kind": "Variable",
|
|
61
|
+
"name": {
|
|
62
|
+
"kind": "Name",
|
|
63
|
+
"value": "input"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}],
|
|
67
|
+
"directives": [],
|
|
68
|
+
"selectionSet": {
|
|
69
|
+
"kind": "SelectionSet",
|
|
70
|
+
"selections": [
|
|
71
|
+
{
|
|
72
|
+
"kind": "Field",
|
|
73
|
+
"name": {
|
|
74
|
+
"kind": "Name",
|
|
75
|
+
"value": "workflowKey"
|
|
76
|
+
},
|
|
77
|
+
"arguments": [],
|
|
78
|
+
"directives": []
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"kind": "Field",
|
|
82
|
+
"name": {
|
|
83
|
+
"kind": "Name",
|
|
84
|
+
"value": "notifyOnFailure"
|
|
85
|
+
},
|
|
86
|
+
"arguments": [],
|
|
87
|
+
"directives": []
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"kind": "Field",
|
|
91
|
+
"name": {
|
|
92
|
+
"kind": "Name",
|
|
93
|
+
"value": "notifyOnSuccess"
|
|
94
|
+
},
|
|
95
|
+
"arguments": [],
|
|
96
|
+
"directives": []
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"kind": "Field",
|
|
100
|
+
"name": {
|
|
101
|
+
"kind": "Name",
|
|
102
|
+
"value": "notifyOnApprovalRequest"
|
|
103
|
+
},
|
|
104
|
+
"arguments": [],
|
|
105
|
+
"directives": []
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"kind": "Field",
|
|
109
|
+
"name": {
|
|
110
|
+
"kind": "Name",
|
|
111
|
+
"value": "notifyOnTriggerDisabled"
|
|
112
|
+
},
|
|
113
|
+
"arguments": [],
|
|
114
|
+
"directives": []
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"kind": "Field",
|
|
118
|
+
"name": {
|
|
119
|
+
"kind": "Name",
|
|
120
|
+
"value": "pushEnabled"
|
|
121
|
+
},
|
|
122
|
+
"arguments": [],
|
|
123
|
+
"directives": []
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"kind": "Field",
|
|
127
|
+
"name": {
|
|
128
|
+
"kind": "Name",
|
|
129
|
+
"value": "throttleMinutes"
|
|
130
|
+
},
|
|
131
|
+
"arguments": [],
|
|
132
|
+
"directives": []
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"kind": "Field",
|
|
136
|
+
"name": {
|
|
137
|
+
"kind": "Name",
|
|
138
|
+
"value": "customized"
|
|
139
|
+
},
|
|
140
|
+
"arguments": [],
|
|
141
|
+
"directives": []
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"kind": "Field",
|
|
145
|
+
"name": {
|
|
146
|
+
"kind": "Name",
|
|
147
|
+
"value": "updatedAt"
|
|
148
|
+
},
|
|
149
|
+
"arguments": [],
|
|
150
|
+
"directives": []
|
|
151
|
+
}
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
}]
|
|
155
|
+
}
|
|
156
|
+
}]
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// src/generated/documents/wspace/WorkflowNotificationSettingsDocument.ts
|
|
160
|
+
const WorkflowNotificationSettingsDocument = {
|
|
161
|
+
"kind": "Document",
|
|
162
|
+
"definitions": [{
|
|
163
|
+
"kind": "OperationDefinition",
|
|
164
|
+
"operation": "query",
|
|
165
|
+
"name": {
|
|
166
|
+
"kind": "Name",
|
|
167
|
+
"value": "WorkflowNotificationSettings"
|
|
168
|
+
},
|
|
169
|
+
"variableDefinitions": [{
|
|
170
|
+
"kind": "VariableDefinition",
|
|
171
|
+
"variable": {
|
|
172
|
+
"kind": "Variable",
|
|
173
|
+
"name": {
|
|
174
|
+
"kind": "Name",
|
|
175
|
+
"value": "workflowKey"
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
"type": {
|
|
179
|
+
"kind": "NonNullType",
|
|
180
|
+
"type": {
|
|
181
|
+
"kind": "NamedType",
|
|
182
|
+
"name": {
|
|
183
|
+
"kind": "Name",
|
|
184
|
+
"value": "String"
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
"directives": []
|
|
189
|
+
}],
|
|
190
|
+
"directives": [],
|
|
191
|
+
"selectionSet": {
|
|
192
|
+
"kind": "SelectionSet",
|
|
193
|
+
"selections": [{
|
|
194
|
+
"kind": "Field",
|
|
195
|
+
"name": {
|
|
196
|
+
"kind": "Name",
|
|
197
|
+
"value": "workflowNotificationSettings"
|
|
198
|
+
},
|
|
199
|
+
"arguments": [{
|
|
200
|
+
"kind": "Argument",
|
|
201
|
+
"name": {
|
|
202
|
+
"kind": "Name",
|
|
203
|
+
"value": "workflowKey"
|
|
204
|
+
},
|
|
205
|
+
"value": {
|
|
206
|
+
"kind": "Variable",
|
|
207
|
+
"name": {
|
|
208
|
+
"kind": "Name",
|
|
209
|
+
"value": "workflowKey"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}],
|
|
213
|
+
"directives": [],
|
|
214
|
+
"selectionSet": {
|
|
215
|
+
"kind": "SelectionSet",
|
|
216
|
+
"selections": [
|
|
217
|
+
{
|
|
218
|
+
"kind": "Field",
|
|
219
|
+
"name": {
|
|
220
|
+
"kind": "Name",
|
|
221
|
+
"value": "workflowKey"
|
|
222
|
+
},
|
|
223
|
+
"arguments": [],
|
|
224
|
+
"directives": []
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"kind": "Field",
|
|
228
|
+
"name": {
|
|
229
|
+
"kind": "Name",
|
|
230
|
+
"value": "notifyOnFailure"
|
|
231
|
+
},
|
|
232
|
+
"arguments": [],
|
|
233
|
+
"directives": []
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"kind": "Field",
|
|
237
|
+
"name": {
|
|
238
|
+
"kind": "Name",
|
|
239
|
+
"value": "notifyOnSuccess"
|
|
240
|
+
},
|
|
241
|
+
"arguments": [],
|
|
242
|
+
"directives": []
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"kind": "Field",
|
|
246
|
+
"name": {
|
|
247
|
+
"kind": "Name",
|
|
248
|
+
"value": "notifyOnApprovalRequest"
|
|
249
|
+
},
|
|
250
|
+
"arguments": [],
|
|
251
|
+
"directives": []
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"kind": "Field",
|
|
255
|
+
"name": {
|
|
256
|
+
"kind": "Name",
|
|
257
|
+
"value": "notifyOnTriggerDisabled"
|
|
258
|
+
},
|
|
259
|
+
"arguments": [],
|
|
260
|
+
"directives": []
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"kind": "Field",
|
|
264
|
+
"name": {
|
|
265
|
+
"kind": "Name",
|
|
266
|
+
"value": "pushEnabled"
|
|
267
|
+
},
|
|
268
|
+
"arguments": [],
|
|
269
|
+
"directives": []
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"kind": "Field",
|
|
273
|
+
"name": {
|
|
274
|
+
"kind": "Name",
|
|
275
|
+
"value": "throttleMinutes"
|
|
276
|
+
},
|
|
277
|
+
"arguments": [],
|
|
278
|
+
"directives": []
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
"kind": "Field",
|
|
282
|
+
"name": {
|
|
283
|
+
"kind": "Name",
|
|
284
|
+
"value": "customized"
|
|
285
|
+
},
|
|
286
|
+
"arguments": [],
|
|
287
|
+
"directives": []
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"kind": "Field",
|
|
291
|
+
"name": {
|
|
292
|
+
"kind": "Name",
|
|
293
|
+
"value": "updatedAt"
|
|
294
|
+
},
|
|
295
|
+
"arguments": [],
|
|
296
|
+
"directives": []
|
|
297
|
+
}
|
|
298
|
+
]
|
|
299
|
+
}
|
|
300
|
+
}]
|
|
301
|
+
}
|
|
302
|
+
}]
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// src/modules/settings/hooks/useWorkflowNotificationSettings.ts
|
|
306
|
+
/**
|
|
307
|
+
* A workflow's notification preferences (G08) for Settings → Notifications
|
|
308
|
+
* (BOFF-9974): which events notify the workflow's person in the app's bell
|
|
309
|
+
* and, unless switched off, by push to their devices, and how often.
|
|
310
|
+
*
|
|
311
|
+
* Read with `workflowNotificationSettings` (the defaults when nobody saved
|
|
312
|
+
* any) and saved with `updateWorkflowNotificationSettings`, a partial update.
|
|
313
|
+
*/
|
|
314
|
+
/** The throttle window the service accepts, in minutes (0 sends every one). */
|
|
315
|
+
const NOTIFICATION_THROTTLE_RANGE = {
|
|
316
|
+
min: 0,
|
|
317
|
+
max: 1440
|
|
318
|
+
};
|
|
319
|
+
async function fetchWorkflowNotificationSettings(workflowKey) {
|
|
320
|
+
const result = await getFluidGridsClient().query({
|
|
321
|
+
query: WorkflowNotificationSettingsDocument,
|
|
322
|
+
variables: { workflowKey },
|
|
323
|
+
fetchPolicy: "network-only"
|
|
324
|
+
});
|
|
325
|
+
throwIfResultError(result);
|
|
326
|
+
return result.data?.workflowNotificationSettings ?? null;
|
|
327
|
+
}
|
|
328
|
+
async function saveWorkflowNotificationSettings(workflowKey, changes) {
|
|
329
|
+
const result = await getFluidGridsClient().mutate({
|
|
330
|
+
mutation: UpdateWorkflowNotificationSettingsDocument,
|
|
331
|
+
variables: { input: {
|
|
332
|
+
workflowKey,
|
|
333
|
+
...changes
|
|
334
|
+
} }
|
|
335
|
+
});
|
|
336
|
+
throwIfResultError(result);
|
|
337
|
+
const saved = result.data?.updateWorkflowNotificationSettings;
|
|
338
|
+
if (!saved) throw new Error("The notification settings were not saved.");
|
|
339
|
+
return saved;
|
|
340
|
+
}
|
|
341
|
+
function useWorkflowNotificationSettings(workflowKey) {
|
|
342
|
+
const [loaded, setLoaded] = useState(null);
|
|
343
|
+
useEffect(() => {
|
|
344
|
+
if (!workflowKey) return;
|
|
345
|
+
let cancelled = false;
|
|
346
|
+
fetchWorkflowNotificationSettings(workflowKey).then((settings) => {
|
|
347
|
+
if (!cancelled) setLoaded({
|
|
348
|
+
workflowKey,
|
|
349
|
+
settings,
|
|
350
|
+
error: null
|
|
351
|
+
});
|
|
352
|
+
}, (err) => {
|
|
353
|
+
if (!cancelled) setLoaded({
|
|
354
|
+
workflowKey,
|
|
355
|
+
settings: null,
|
|
356
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
return () => {
|
|
360
|
+
cancelled = true;
|
|
361
|
+
};
|
|
362
|
+
}, [workflowKey]);
|
|
363
|
+
const save = useCallback(async (changes) => {
|
|
364
|
+
if (!workflowKey) throw new Error("No workflow selected.");
|
|
365
|
+
const saved = await saveWorkflowNotificationSettings(workflowKey, changes);
|
|
366
|
+
setLoaded({
|
|
367
|
+
workflowKey,
|
|
368
|
+
settings: saved,
|
|
369
|
+
error: null
|
|
370
|
+
});
|
|
371
|
+
return saved;
|
|
372
|
+
}, [workflowKey]);
|
|
373
|
+
const current = loaded?.workflowKey === workflowKey ? loaded : null;
|
|
374
|
+
return {
|
|
375
|
+
settings: current?.settings ?? null,
|
|
376
|
+
loading: !!workflowKey && current === null,
|
|
377
|
+
error: current?.error ?? null,
|
|
378
|
+
save
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/modules/settings/utils/error-trigger.ts
|
|
383
|
+
/**
|
|
384
|
+
* Error Workflow setting (BOFF-7514): the chosen workflow starts at its Error
|
|
385
|
+
* Trigger node, so one without that node never starts. Mirrors the backend's
|
|
386
|
+
* `findErrorTriggerNodeId` in wspace-workflow-svc.
|
|
387
|
+
*/
|
|
388
|
+
const ERROR_TRIGGER_NODE_TYPE = "internal:error-trigger";
|
|
389
|
+
function asRecord(value) {
|
|
390
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
391
|
+
}
|
|
392
|
+
/** Reads `data.nodes`, falling back to the legacy top-level `nodes`. */
|
|
393
|
+
function hasErrorTriggerNode(definitionJson) {
|
|
394
|
+
const definition = asRecord(definitionJson);
|
|
395
|
+
const dataNodes = asRecord(definition?.data)?.nodes;
|
|
396
|
+
const nodes = Array.isArray(dataNodes) ? dataNodes : definition?.nodes;
|
|
397
|
+
return Array.isArray(nodes) && nodes.some((node) => asRecord(node)?.type === "internal:error-trigger");
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/modules/settings/utils/run-data.ts
|
|
401
|
+
function toSavingChoice(value) {
|
|
402
|
+
if (value === void 0) return "default";
|
|
403
|
+
return value ? "save" : "skip";
|
|
404
|
+
}
|
|
405
|
+
function fromSavingChoice(choice) {
|
|
406
|
+
if (choice === "save") return true;
|
|
407
|
+
if (choice === "skip") return false;
|
|
408
|
+
}
|
|
409
|
+
/** Limits the backend applies to a workflow's redacted fields (run-data-privacy.ts, BOFF-9752). */
|
|
410
|
+
const MAX_REDACT_FIELDS = 50;
|
|
411
|
+
const MAX_REDACT_FIELD_LENGTH = 200;
|
|
412
|
+
const MAX_REDACT_PATH_SEGMENTS = 16;
|
|
413
|
+
/**
|
|
414
|
+
* Read the "Redacted fields" input: entries separated by commas or new lines, each a field name
|
|
415
|
+
* or a dotted path. Entries the backend would ignore (empty segments, too long, too deep) and
|
|
416
|
+
* repeats are dropped, and the list is capped. An empty list means "none" (undefined).
|
|
417
|
+
*/
|
|
418
|
+
function parseRedactFields(text) {
|
|
419
|
+
const seen = /* @__PURE__ */ new Set();
|
|
420
|
+
const fields = [];
|
|
421
|
+
for (const raw of text.split(/[,\n]/)) {
|
|
422
|
+
const segments = raw.trim().split(".").map((segment) => segment.trim());
|
|
423
|
+
const field = segments.join(".");
|
|
424
|
+
if (field.length === 0 || field.length > MAX_REDACT_FIELD_LENGTH || segments.length > MAX_REDACT_PATH_SEGMENTS || segments.some((segment) => segment.length === 0)) continue;
|
|
425
|
+
const key = field.toLowerCase();
|
|
426
|
+
if (seen.has(key)) continue;
|
|
427
|
+
seen.add(key);
|
|
428
|
+
fields.push(field);
|
|
429
|
+
if (fields.length >= 50) break;
|
|
430
|
+
}
|
|
431
|
+
return fields.length > 0 ? fields : void 0;
|
|
432
|
+
}
|
|
433
|
+
/** Show a saved list in the "Redacted fields" input. */
|
|
434
|
+
function formatRedactFields(fields) {
|
|
435
|
+
return (fields ?? []).join(", ");
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Drop the keys left unset, and the whole object when nothing is set, so a workflow that follows
|
|
439
|
+
* its workspace saves no override at all.
|
|
440
|
+
*/
|
|
441
|
+
function compactOverrides(value) {
|
|
442
|
+
if (!value) return void 0;
|
|
443
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0);
|
|
444
|
+
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
445
|
+
}
|
|
446
|
+
/** Bounds the backend accepts (wspace-workflow-svc run-data-lifecycle.service). */
|
|
447
|
+
const RETENTION_DAYS_RANGE = {
|
|
448
|
+
min: 1,
|
|
449
|
+
max: 3650
|
|
450
|
+
};
|
|
451
|
+
const RETENTION_MAX_RUNS_RANGE = {
|
|
452
|
+
min: 1,
|
|
453
|
+
max: 1e6
|
|
454
|
+
};
|
|
455
|
+
/**
|
|
456
|
+
* An empty input means "no override"; anything else is read whole (so `1e3` is 1000, not 1),
|
|
457
|
+
* truncated to a whole number and clamped to its range.
|
|
458
|
+
*/
|
|
459
|
+
function parseBoundedInt(value, range) {
|
|
460
|
+
if (value.trim() === "") return void 0;
|
|
461
|
+
const parsed = Number(value);
|
|
462
|
+
if (!Number.isFinite(parsed)) return void 0;
|
|
463
|
+
return Math.min(range.max, Math.max(range.min, Math.trunc(parsed)));
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// src/modules/settings/utils/workflow-settings-persistence.ts
|
|
467
|
+
/** The nested fields the Settings page edits; any other nested key is kept as saved. */
|
|
468
|
+
const DATA_SAVING_KEYS = [
|
|
469
|
+
"saveSuccessExecutions",
|
|
470
|
+
"saveFailedExecutions",
|
|
471
|
+
"saveManualExecutions"
|
|
472
|
+
];
|
|
473
|
+
const RETENTION_KEYS = ["maxAgeDays", "maxRuns"];
|
|
474
|
+
const DATA_PRIVACY_KEYS = ["doNotStoreRunData", "redactFields"];
|
|
475
|
+
const BUDGET_KEYS = [
|
|
476
|
+
"maxSteps",
|
|
477
|
+
"maxWallTimeSec",
|
|
478
|
+
"maxTokens",
|
|
479
|
+
"maxCostMicroUsd"
|
|
480
|
+
];
|
|
481
|
+
/**
|
|
482
|
+
* Merge the edited settings into the saved config without mutating it.
|
|
483
|
+
*
|
|
484
|
+
* Keys the form does not know (engine-only flags, fields added later) are kept. For the nested
|
|
485
|
+
* run-data objects the form is authoritative over the fields it edits: a field it leaves unset was
|
|
486
|
+
* cleared to "follow the workspace" (BOFF-7514), so it must not come back from the saved config.
|
|
487
|
+
*/
|
|
488
|
+
function mergeWorkflowExecutionSettings(existing, next) {
|
|
489
|
+
const merged = {
|
|
490
|
+
...existing,
|
|
491
|
+
...next
|
|
492
|
+
};
|
|
493
|
+
for (const [key, editable] of [
|
|
494
|
+
["dataSaving", DATA_SAVING_KEYS],
|
|
495
|
+
["retention", RETENTION_KEYS],
|
|
496
|
+
["dataPrivacy", DATA_PRIVACY_KEYS],
|
|
497
|
+
["budget", BUDGET_KEYS]
|
|
498
|
+
]) {
|
|
499
|
+
const nested = mergeNested(existing[key], next[key], editable);
|
|
500
|
+
if (nested) merged[key] = nested;
|
|
501
|
+
else delete merged[key];
|
|
502
|
+
}
|
|
503
|
+
return merged;
|
|
504
|
+
}
|
|
505
|
+
function mergeNested(existing, next, editable) {
|
|
506
|
+
const editableKeys = new Set(editable);
|
|
507
|
+
const kept = Object.fromEntries(Object.entries(isRecord(existing) ? existing : {}).filter(([key]) => !editableKeys.has(key)));
|
|
508
|
+
const result = Object.fromEntries(Object.entries({
|
|
509
|
+
...kept,
|
|
510
|
+
...next ?? {}
|
|
511
|
+
}).filter(([, value]) => value !== void 0));
|
|
512
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
513
|
+
}
|
|
514
|
+
function isRecord(value) {
|
|
515
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/modules/settings/hooks/useRunDataSettings.ts
|
|
519
|
+
/**
|
|
520
|
+
* The workspace's run-data settings (BOFF-7514): which finished runs keep their data and how long
|
|
521
|
+
* finished runs are kept, for every workflow that does not override them in its own Settings.
|
|
522
|
+
*/
|
|
523
|
+
const FIELDS = `
|
|
524
|
+
saveSuccessfulRuns
|
|
525
|
+
saveFailedRuns
|
|
526
|
+
saveManualRuns
|
|
527
|
+
retentionDays
|
|
528
|
+
retentionMaxRunsPerWorkflow
|
|
529
|
+
customized
|
|
530
|
+
updatedAt
|
|
531
|
+
`;
|
|
532
|
+
const RUN_DATA_SETTINGS_QUERY = gql`
|
|
533
|
+
query WorkflowExecutionSettings {
|
|
534
|
+
workflowExecutionSettings {
|
|
535
|
+
${FIELDS}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
`;
|
|
539
|
+
const UPDATE_RUN_DATA_SETTINGS = gql`
|
|
540
|
+
mutation UpdateWorkflowExecutionSettings($input: UpdateWorkflowExecutionSettingsInput!) {
|
|
541
|
+
updateWorkflowExecutionSettings(input: $input) {
|
|
542
|
+
${FIELDS}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
`;
|
|
546
|
+
function useRunDataSettings() {
|
|
547
|
+
const [settings, setSettings] = useState(null);
|
|
548
|
+
const [loading, setLoading] = useState(true);
|
|
549
|
+
const [error, setError] = useState(null);
|
|
550
|
+
useEffect(() => {
|
|
551
|
+
let cancelled = false;
|
|
552
|
+
getFluidGridsClient().query({
|
|
553
|
+
query: RUN_DATA_SETTINGS_QUERY,
|
|
554
|
+
fetchPolicy: "network-only"
|
|
555
|
+
}).then((result) => {
|
|
556
|
+
throwIfResultError(result);
|
|
557
|
+
if (!cancelled) setSettings(result.data?.workflowExecutionSettings ?? null);
|
|
558
|
+
}).catch((err) => {
|
|
559
|
+
if (!cancelled) setError(err instanceof Error ? err : new Error(String(err)));
|
|
560
|
+
}).finally(() => {
|
|
561
|
+
if (!cancelled) setLoading(false);
|
|
562
|
+
});
|
|
563
|
+
return () => {
|
|
564
|
+
cancelled = true;
|
|
565
|
+
};
|
|
566
|
+
}, []);
|
|
567
|
+
return {
|
|
568
|
+
settings,
|
|
569
|
+
loading,
|
|
570
|
+
error,
|
|
571
|
+
save: useCallback(async (changes) => {
|
|
572
|
+
const result = await getFluidGridsClient().mutate({
|
|
573
|
+
mutation: UPDATE_RUN_DATA_SETTINGS,
|
|
574
|
+
variables: { input: changes }
|
|
575
|
+
});
|
|
576
|
+
throwIfResultError(result);
|
|
577
|
+
const saved = result.data?.updateWorkflowExecutionSettings;
|
|
578
|
+
if (!saved) throw new Error("No settings returned");
|
|
579
|
+
setSettings(saved);
|
|
580
|
+
return saved;
|
|
581
|
+
}, [])
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// src/modules/settings/hooks/useWorkflowExecutionSettingsForm.ts
|
|
586
|
+
/**
|
|
587
|
+
* One workflow's execution settings as an editable form (BOFF-7512): the
|
|
588
|
+
* values in `definitionJson.config`, the lists the form's pickers need, and a
|
|
589
|
+
* save that never overwrites a canvas someone changed meanwhile.
|
|
590
|
+
*
|
|
591
|
+
* Shared by Settings › Execution and the Navigation v2 settings drawer
|
|
592
|
+
* (BOFF-11843), so both read, check and save exactly the same way.
|
|
593
|
+
*/
|
|
594
|
+
/** What "On workflow error" means when nobody chose (the engine's default). */
|
|
595
|
+
const DEFAULT_ON_ERROR = "stop";
|
|
596
|
+
/**
|
|
597
|
+
* The stored definition with the form's settings applied to its `config`.
|
|
598
|
+
* Only `config` changes; every other part of the definition is sent back as
|
|
599
|
+
* it was read.
|
|
600
|
+
*/
|
|
601
|
+
function definitionWithSettings(definitionJson, form) {
|
|
602
|
+
const definition = definitionJson ?? {};
|
|
603
|
+
return {
|
|
604
|
+
...definition,
|
|
605
|
+
config: mergeWorkflowExecutionSettings(definition.config ?? {}, form)
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
/** Whether saving `form` would change the stored `config`. */
|
|
609
|
+
function settingsChanged(persisted, form) {
|
|
610
|
+
const before = mergeWorkflowExecutionSettings(persisted, persisted);
|
|
611
|
+
const after = mergeWorkflowExecutionSettings(persisted, form);
|
|
612
|
+
return JSON.stringify(sortKeys(before)) !== JSON.stringify(sortKeys(after));
|
|
613
|
+
}
|
|
614
|
+
function sortKeys(value) {
|
|
615
|
+
if (Array.isArray(value)) return value.map(sortKeys);
|
|
616
|
+
if (typeof value !== "object" || value === null) return value;
|
|
617
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortKeys(value[key])]));
|
|
618
|
+
}
|
|
619
|
+
function useWorkflowExecutionSettingsForm(workflowKey) {
|
|
620
|
+
const tr = useTr();
|
|
621
|
+
const { data, loading, refetch } = useWorkflowDefinitionQuery({
|
|
622
|
+
variables: { workflowKey },
|
|
623
|
+
skip: !workflowKey,
|
|
624
|
+
fetchPolicy: "cache-and-network"
|
|
625
|
+
});
|
|
626
|
+
const [updateWorkflowDefinition, { loading: saving }] = useUpdateWorkflowDefinitionMutation();
|
|
627
|
+
const { allowed: canEdit } = useFluidGridsPermission(FLUIDGRIDS_PERMISSIONS.WORKFLOW_UPDATE);
|
|
628
|
+
const locked = loading || saving || !canEdit || !data?.workflowDefinition;
|
|
629
|
+
const { data: otherWorkflowsData, loading: otherWorkflowsLoading } = useWorkflowDefinitionsQuery({
|
|
630
|
+
variables: { input: {
|
|
631
|
+
latestOnly: true,
|
|
632
|
+
limit: 100
|
|
633
|
+
} },
|
|
634
|
+
skip: !workflowKey
|
|
635
|
+
});
|
|
636
|
+
const errorWorkflowOptions = useMemo(() => (otherWorkflowsData?.workflowDefinitions?.items ?? []).filter((w) => w.workflowKey !== workflowKey), [otherWorkflowsData, workflowKey]);
|
|
637
|
+
const workflow = data?.workflowDefinition;
|
|
638
|
+
const persistedConfig = useMemo(() => {
|
|
639
|
+
return (workflow?.definitionJson)?.config ?? {};
|
|
640
|
+
}, [workflow?.definitionJson]);
|
|
641
|
+
const [form, setForm] = useState(persistedConfig);
|
|
642
|
+
const [redactFieldsText, setRedactFieldsText] = useState(() => formatRedactFields(persistedConfig.dataPrivacy?.redactFields));
|
|
643
|
+
useEffect(() => {
|
|
644
|
+
setForm(persistedConfig);
|
|
645
|
+
setRedactFieldsText(formatRedactFields(persistedConfig.dataPrivacy?.redactFields));
|
|
646
|
+
}, [persistedConfig]);
|
|
647
|
+
const runData = useRunDataSettings();
|
|
648
|
+
const selectedErrorWorkflowKey = form.errorWorkflowKey;
|
|
649
|
+
const { data: errorWorkflowData } = useWorkflowDefinitionQuery({
|
|
650
|
+
variables: { workflowKey: selectedErrorWorkflowKey ?? "" },
|
|
651
|
+
skip: !selectedErrorWorkflowKey
|
|
652
|
+
});
|
|
653
|
+
const selectedErrorWorkflow = errorWorkflowData?.workflowDefinition;
|
|
654
|
+
const errorWorkflowLacksTrigger = Boolean(selectedErrorWorkflowKey && selectedErrorWorkflow && !hasErrorTriggerNode(selectedErrorWorkflow.definitionJson));
|
|
655
|
+
/**
|
|
656
|
+
* Saves the form into the workflow's `config`. Resolves once saved, throws
|
|
657
|
+
* the service's refusal otherwise, and throws when the workflow could not be
|
|
658
|
+
* read, so a caller never reports a save that did not happen. A conflict (the workflow changed since
|
|
659
|
+
* it was read) is retried once on the latest version: settings only touch
|
|
660
|
+
* `config`, so they apply to it unchanged.
|
|
661
|
+
*/
|
|
662
|
+
const save = useCallback(async () => {
|
|
663
|
+
if (!workflow?.id) throw new Error(tr("fluidgrids.settings.execution.workflowNotFound", "This workflow couldn't be found. It may have been deleted, or you may no longer have access to it."));
|
|
664
|
+
const send = (stored) => updateWorkflowDefinition({ variables: {
|
|
665
|
+
id: stored.id,
|
|
666
|
+
input: {
|
|
667
|
+
definitionJson: definitionWithSettings(stored.definitionJson, form),
|
|
668
|
+
...typeof stored.revision === "number" ? { expectedRevision: stored.revision } : {}
|
|
669
|
+
}
|
|
670
|
+
} });
|
|
671
|
+
try {
|
|
672
|
+
await send(workflow);
|
|
673
|
+
} catch (err) {
|
|
674
|
+
if (!findSaveConflict(err)) throw err;
|
|
675
|
+
const latest = (await refetch()).data?.workflowDefinition;
|
|
676
|
+
if (!latest?.id) throw err;
|
|
677
|
+
await send(latest);
|
|
678
|
+
}
|
|
679
|
+
refetch();
|
|
680
|
+
}, [
|
|
681
|
+
workflow,
|
|
682
|
+
form,
|
|
683
|
+
updateWorkflowDefinition,
|
|
684
|
+
refetch,
|
|
685
|
+
tr
|
|
686
|
+
]);
|
|
687
|
+
return {
|
|
688
|
+
workflow,
|
|
689
|
+
loading,
|
|
690
|
+
saving,
|
|
691
|
+
canEdit,
|
|
692
|
+
locked,
|
|
693
|
+
form,
|
|
694
|
+
setForm,
|
|
695
|
+
persistedConfig,
|
|
696
|
+
dirty: settingsChanged(persistedConfig, form),
|
|
697
|
+
redactFieldsText,
|
|
698
|
+
setRedactFieldsText,
|
|
699
|
+
runData,
|
|
700
|
+
errorWorkflowOptions,
|
|
701
|
+
otherWorkflowsLoading,
|
|
702
|
+
errorWorkflowLacksTrigger,
|
|
703
|
+
save
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// src/modules/settings/utils/optional-positive-int.ts
|
|
708
|
+
/**
|
|
709
|
+
* Number inputs whose empty value means "no limit" — Max parallel executions
|
|
710
|
+
* (BOFF-7647) and Timeout in seconds (BOFF-7514). Empty is also what an unset
|
|
711
|
+
* value means to the engine; anything else is clamped to a whole number of at
|
|
712
|
+
* least 1.
|
|
713
|
+
*/
|
|
714
|
+
function parseOptionalPositiveInt(value) {
|
|
715
|
+
if (value.trim() === "") return void 0;
|
|
716
|
+
const parsed = Number(value);
|
|
717
|
+
return Number.isFinite(parsed) ? Math.max(1, Math.trunc(parsed)) : void 0;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// src/modules/settings/components/SettingRow.tsx
|
|
721
|
+
/**
|
|
722
|
+
* One labelled setting: label and description on the left, its control on the right.
|
|
723
|
+
* `stacked` puts the control under the label at every width, for narrow panels such
|
|
724
|
+
* as the workflow settings drawer (BOFF-11843).
|
|
725
|
+
*/
|
|
726
|
+
function SettingRow({ label, description, stacked = false, children }) {
|
|
727
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
728
|
+
className: stacked ? "flex flex-col gap-2 py-3" : "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 py-4",
|
|
729
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
730
|
+
className: "flex-1 min-w-0",
|
|
731
|
+
children: [/* @__PURE__ */ jsx(Label, {
|
|
732
|
+
className: "text-sm font-medium text-text-primary",
|
|
733
|
+
children: label
|
|
734
|
+
}), description && /* @__PURE__ */ jsx("p", {
|
|
735
|
+
className: "text-xs text-text-secondary mt-0.5",
|
|
736
|
+
children: description
|
|
737
|
+
})]
|
|
738
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
739
|
+
className: "shrink-0",
|
|
740
|
+
children
|
|
741
|
+
})]
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// src/modules/settings/components/ExecutionSettingsFields.tsx
|
|
746
|
+
/**
|
|
747
|
+
* The per-workflow execution settings rows (BOFF-7512, BOFF-7514), shared by
|
|
748
|
+
* Settings › Execution and the Navigation v2 settings drawer (BOFF-11843).
|
|
749
|
+
* Each row reads and writes the form of `useWorkflowExecutionSettingsForm`.
|
|
750
|
+
*/
|
|
751
|
+
/** The "On workflow error" choices, in the order they are offered. */
|
|
752
|
+
const ON_ERROR_CHOICES = [
|
|
753
|
+
"stop",
|
|
754
|
+
"continue",
|
|
755
|
+
"retry"
|
|
756
|
+
];
|
|
757
|
+
function isOnErrorChoice(value) {
|
|
758
|
+
return ON_ERROR_CHOICES.includes(value);
|
|
759
|
+
}
|
|
760
|
+
function TimeoutSetting({ ctl, stacked }) {
|
|
761
|
+
const tr = useTr();
|
|
762
|
+
const label = tr("fluidgrids.settings.execution.timeoutLabel", "Timeout");
|
|
763
|
+
return /* @__PURE__ */ jsx(SettingRow, {
|
|
764
|
+
stacked,
|
|
765
|
+
label,
|
|
766
|
+
description: tr("fluidgrids.settings.execution.defaultTimeoutDescription", "Stop a run that keeps running longer than this. Time a run spends paused (a Wait over a minute, an approval, a person's answer) does not count. Leave empty for no limit."),
|
|
767
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
768
|
+
className: "flex items-center gap-2",
|
|
769
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
770
|
+
type: "number",
|
|
771
|
+
min: 1,
|
|
772
|
+
value: ctl.form.timeout ?? "",
|
|
773
|
+
placeholder: tr("fluidgrids.settings.execution.timeoutPlaceholder", "No timeout"),
|
|
774
|
+
"aria-label": label,
|
|
775
|
+
"data-testid": "execution-settings-timeout",
|
|
776
|
+
onChange: (e) => ctl.setForm((f) => ({
|
|
777
|
+
...f,
|
|
778
|
+
timeout: parseOptionalPositiveInt(e.target.value)
|
|
779
|
+
})),
|
|
780
|
+
disabled: ctl.locked,
|
|
781
|
+
className: "w-[100px] text-right"
|
|
782
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
783
|
+
className: "text-xs text-text-secondary",
|
|
784
|
+
children: tr("fluidgrids.settings.execution.defaultTimeoutUnit", "seconds")
|
|
785
|
+
})]
|
|
786
|
+
})
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
function MaxParallelSetting({ ctl, stacked }) {
|
|
790
|
+
const tr = useTr();
|
|
791
|
+
const label = tr("fluidgrids.settings.execution.maxParallelLabel", "Max parallel executions");
|
|
792
|
+
return /* @__PURE__ */ jsx(SettingRow, {
|
|
793
|
+
stacked,
|
|
794
|
+
label,
|
|
795
|
+
description: tr("fluidgrids.settings.execution.maxParallelDescription", "How many production runs of this workflow may run at once. Extra runs wait in a queue and start in order; manual test runs are never held back. Leave empty for no limit."),
|
|
796
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
797
|
+
type: "number",
|
|
798
|
+
min: 1,
|
|
799
|
+
value: ctl.form.maxParallelism ?? "",
|
|
800
|
+
placeholder: tr("fluidgrids.settings.execution.maxParallelPlaceholder", "No limit"),
|
|
801
|
+
"aria-label": label,
|
|
802
|
+
"data-testid": "execution-settings-max-parallel",
|
|
803
|
+
onChange: (e) => ctl.setForm((f) => ({
|
|
804
|
+
...f,
|
|
805
|
+
maxParallelism: parseOptionalPositiveInt(e.target.value)
|
|
806
|
+
})),
|
|
807
|
+
disabled: ctl.locked,
|
|
808
|
+
className: "w-[100px] text-right"
|
|
809
|
+
})
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* "On workflow error": Stop, Continue or Retry. An fe-libs Select (it was a
|
|
814
|
+
* native `<select>`, which ignored the theme in its open list).
|
|
815
|
+
*/
|
|
816
|
+
function OnErrorSetting({ ctl, stacked }) {
|
|
817
|
+
const tr = useTr();
|
|
818
|
+
const label = tr("fluidgrids.settings.execution.onErrorLabel", "On workflow error");
|
|
819
|
+
const choiceLabel = {
|
|
820
|
+
stop: tr("fluidgrids.settings.execution.onErrorStop", "Stop"),
|
|
821
|
+
continue: tr("fluidgrids.settings.execution.onErrorContinue", "Continue"),
|
|
822
|
+
retry: tr("fluidgrids.settings.execution.onErrorRetry", "Retry")
|
|
823
|
+
};
|
|
824
|
+
return /* @__PURE__ */ jsx(SettingRow, {
|
|
825
|
+
stacked,
|
|
826
|
+
label,
|
|
827
|
+
description: tr("fluidgrids.settings.execution.onErrorDescription", "What to do when this workflow's execution fails"),
|
|
828
|
+
children: /* @__PURE__ */ jsxs(Select, {
|
|
829
|
+
value: ctl.form.onError ?? "stop",
|
|
830
|
+
onValueChange: (value) => {
|
|
831
|
+
if (!isOnErrorChoice(value)) return;
|
|
832
|
+
ctl.setForm((f) => ({
|
|
833
|
+
...f,
|
|
834
|
+
onError: value
|
|
835
|
+
}));
|
|
836
|
+
},
|
|
837
|
+
disabled: ctl.locked,
|
|
838
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
839
|
+
"aria-label": label,
|
|
840
|
+
"data-testid": "execution-settings-on-error",
|
|
841
|
+
className: "w-[160px] bg-surface border-border-default",
|
|
842
|
+
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
843
|
+
}), /* @__PURE__ */ jsx(SelectContent, { children: ON_ERROR_CHOICES.map((choice) => /* @__PURE__ */ jsx(SelectItem, {
|
|
844
|
+
value: choice,
|
|
845
|
+
children: choiceLabel[choice]
|
|
846
|
+
}, choice)) })]
|
|
847
|
+
})
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
const NO_ERROR_WORKFLOW = "__none__";
|
|
851
|
+
function ErrorWorkflowSetting({ ctl, stacked }) {
|
|
852
|
+
const tr = useTr();
|
|
853
|
+
const label = tr("fluidgrids.settings.execution.errorWorkflowLabel", "Error workflow");
|
|
854
|
+
return /* @__PURE__ */ jsx(SettingRow, {
|
|
855
|
+
stacked,
|
|
856
|
+
label,
|
|
857
|
+
description: tr("fluidgrids.settings.execution.errorWorkflowDescription", "Runs when a production run of this workflow fails, starting at that workflow's Error Trigger (optional)"),
|
|
858
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
859
|
+
className: stacked ? "flex flex-col gap-1" : "flex flex-col items-end gap-1",
|
|
860
|
+
children: [/* @__PURE__ */ jsxs(Select, {
|
|
861
|
+
value: ctl.form.errorWorkflowKey ?? NO_ERROR_WORKFLOW,
|
|
862
|
+
onValueChange: (val) => ctl.setForm((f) => ({
|
|
863
|
+
...f,
|
|
864
|
+
errorWorkflowKey: val === NO_ERROR_WORKFLOW ? void 0 : val
|
|
865
|
+
})),
|
|
866
|
+
disabled: ctl.locked || ctl.otherWorkflowsLoading,
|
|
867
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
868
|
+
"aria-label": label,
|
|
869
|
+
"data-testid": "execution-settings-error-workflow",
|
|
870
|
+
className: "w-[220px] bg-surface border-border-default",
|
|
871
|
+
children: /* @__PURE__ */ jsx(SelectValue, { placeholder: tr("fluidgrids.settings.execution.errorWorkflowPlaceholder", "None") })
|
|
872
|
+
}), /* @__PURE__ */ jsxs(SelectContent, { children: [/* @__PURE__ */ jsx(SelectItem, {
|
|
873
|
+
value: NO_ERROR_WORKFLOW,
|
|
874
|
+
children: tr("fluidgrids.settings.execution.errorWorkflowNone", "None")
|
|
875
|
+
}), ctl.errorWorkflowOptions.map((w) => /* @__PURE__ */ jsx(SelectItem, {
|
|
876
|
+
value: w.workflowKey,
|
|
877
|
+
children: w.name
|
|
878
|
+
}, w.workflowKey))] })]
|
|
879
|
+
}), ctl.errorWorkflowLacksTrigger && /* @__PURE__ */ jsx("p", {
|
|
880
|
+
className: stacked ? "max-w-[260px] text-xs text-status-warning-text" : "max-w-[260px] text-right text-xs text-status-warning-text",
|
|
881
|
+
"data-testid": "error-workflow-no-trigger",
|
|
882
|
+
children: tr("fluidgrids.settings.execution.errorWorkflowNoTrigger", "This workflow has no Error Trigger, so it will not start. Add an Error Trigger node to it.")
|
|
883
|
+
})]
|
|
884
|
+
})
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
const SAVING_ROWS = [
|
|
888
|
+
{
|
|
889
|
+
key: "saveSuccessExecutions",
|
|
890
|
+
workspaceKey: "saveSuccessfulRuns",
|
|
891
|
+
labelKey: "fluidgrids.settings.execution.saveSuccessLabel",
|
|
892
|
+
label: "Keep data of successful runs",
|
|
893
|
+
testId: "execution-settings-keep-success"
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
key: "saveFailedExecutions",
|
|
897
|
+
workspaceKey: "saveFailedRuns",
|
|
898
|
+
labelKey: "fluidgrids.settings.execution.saveFailedLabel",
|
|
899
|
+
label: "Keep data of failed runs",
|
|
900
|
+
testId: "execution-settings-keep-failed"
|
|
901
|
+
},
|
|
902
|
+
{
|
|
903
|
+
key: "saveManualExecutions",
|
|
904
|
+
workspaceKey: "saveManualRuns",
|
|
905
|
+
labelKey: "fluidgrids.settings.execution.saveManualLabel",
|
|
906
|
+
label: "Keep data of test runs",
|
|
907
|
+
testId: "execution-settings-keep-test"
|
|
908
|
+
}
|
|
909
|
+
];
|
|
910
|
+
/** Keep data of successful / failed / test runs: workspace default, keep, or don't. */
|
|
911
|
+
function RunDataSavingSettings({ ctl, stacked }) {
|
|
912
|
+
const tr = useTr();
|
|
913
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: SAVING_ROWS.map((row) => {
|
|
914
|
+
const inherited = ctl.runData.settings?.[row.workspaceKey];
|
|
915
|
+
return /* @__PURE__ */ jsx(SettingRow, {
|
|
916
|
+
stacked,
|
|
917
|
+
label: tr(row.labelKey, row.label),
|
|
918
|
+
children: /* @__PURE__ */ jsxs(Select, {
|
|
919
|
+
value: toSavingChoice(ctl.form.dataSaving?.[row.key]),
|
|
920
|
+
onValueChange: (choice) => ctl.setForm((f) => ({
|
|
921
|
+
...f,
|
|
922
|
+
dataSaving: compactOverrides({
|
|
923
|
+
...f.dataSaving,
|
|
924
|
+
[row.key]: fromSavingChoice(choice)
|
|
925
|
+
})
|
|
926
|
+
})),
|
|
927
|
+
disabled: ctl.locked,
|
|
928
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
929
|
+
"aria-label": tr(row.labelKey, row.label),
|
|
930
|
+
"data-testid": row.testId,
|
|
931
|
+
className: "w-[220px] bg-surface border-border-default",
|
|
932
|
+
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
933
|
+
}), /* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
934
|
+
/* @__PURE__ */ jsx(SelectItem, {
|
|
935
|
+
value: "default",
|
|
936
|
+
children: inherited === void 0 ? tr("fluidgrids.settings.execution.savingDefault", "Workspace default") : inherited ? tr("fluidgrids.settings.execution.savingDefaultOn", "Workspace default (keep)") : tr("fluidgrids.settings.execution.savingDefaultOff", "Workspace default (don't keep)")
|
|
937
|
+
}),
|
|
938
|
+
/* @__PURE__ */ jsx(SelectItem, {
|
|
939
|
+
value: "save",
|
|
940
|
+
children: tr("fluidgrids.settings.execution.savingKeep", "Keep")
|
|
941
|
+
}),
|
|
942
|
+
/* @__PURE__ */ jsx(SelectItem, {
|
|
943
|
+
value: "skip",
|
|
944
|
+
children: tr("fluidgrids.settings.execution.savingDontKeep", "Don't keep")
|
|
945
|
+
})
|
|
946
|
+
] })]
|
|
947
|
+
})
|
|
948
|
+
}, row.key);
|
|
949
|
+
}) });
|
|
950
|
+
}
|
|
951
|
+
/** Delete runs after (days) and Runs kept: empty follows the workspace. */
|
|
952
|
+
function RetentionSettings({ ctl, stacked }) {
|
|
953
|
+
const tr = useTr();
|
|
954
|
+
const daysLabel = tr("fluidgrids.settings.execution.retentionDaysLabel", "Delete runs after (days)");
|
|
955
|
+
const runsLabel = tr("fluidgrids.settings.execution.maxRunsLabel", "Runs kept");
|
|
956
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(SettingRow, {
|
|
957
|
+
stacked,
|
|
958
|
+
label: daysLabel,
|
|
959
|
+
description: tr("fluidgrids.settings.execution.retentionDaysDescription", "Leave empty to follow the workspace's Run data settings."),
|
|
960
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
961
|
+
type: "number",
|
|
962
|
+
min: RETENTION_DAYS_RANGE.min,
|
|
963
|
+
max: RETENTION_DAYS_RANGE.max,
|
|
964
|
+
"aria-label": daysLabel,
|
|
965
|
+
"data-testid": "execution-settings-retention-days",
|
|
966
|
+
value: ctl.form.retention?.maxAgeDays ?? "",
|
|
967
|
+
placeholder: ctl.runData.settings ? String(ctl.runData.settings.retentionDays) : "",
|
|
968
|
+
onChange: (e) => ctl.setForm((f) => ({
|
|
969
|
+
...f,
|
|
970
|
+
retention: compactOverrides({
|
|
971
|
+
...f.retention,
|
|
972
|
+
maxAgeDays: parseBoundedInt(e.target.value, RETENTION_DAYS_RANGE)
|
|
973
|
+
})
|
|
974
|
+
})),
|
|
975
|
+
disabled: ctl.locked,
|
|
976
|
+
className: "w-[100px] text-right"
|
|
977
|
+
})
|
|
978
|
+
}), /* @__PURE__ */ jsx(SettingRow, {
|
|
979
|
+
stacked,
|
|
980
|
+
label: runsLabel,
|
|
981
|
+
description: tr("fluidgrids.settings.execution.maxRunsDescription", "Leave empty to follow the workspace's Run data settings."),
|
|
982
|
+
children: /* @__PURE__ */ jsx(Input, {
|
|
983
|
+
type: "number",
|
|
984
|
+
min: RETENTION_MAX_RUNS_RANGE.min,
|
|
985
|
+
max: RETENTION_MAX_RUNS_RANGE.max,
|
|
986
|
+
"aria-label": runsLabel,
|
|
987
|
+
"data-testid": "execution-settings-max-runs",
|
|
988
|
+
value: ctl.form.retention?.maxRuns ?? "",
|
|
989
|
+
placeholder: ctl.runData.settings ? String(ctl.runData.settings.retentionMaxRunsPerWorkflow) : "",
|
|
990
|
+
onChange: (e) => ctl.setForm((f) => ({
|
|
991
|
+
...f,
|
|
992
|
+
retention: compactOverrides({
|
|
993
|
+
...f.retention,
|
|
994
|
+
maxRuns: parseBoundedInt(e.target.value, RETENTION_MAX_RUNS_RANGE)
|
|
995
|
+
})
|
|
996
|
+
})),
|
|
997
|
+
disabled: ctl.locked,
|
|
998
|
+
className: "w-[120px] text-right"
|
|
999
|
+
})
|
|
1000
|
+
})] });
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
export { WorkflowNotificationSettingsDocument as _, RunDataSavingSettings as a, useWorkflowExecutionSettingsForm as c, RETENTION_MAX_RUNS_RANGE as d, compactOverrides as f, useWorkflowNotificationSettings as g, NOTIFICATION_THROTTLE_RANGE as h, RetentionSettings as i, useRunDataSettings as l, parseRedactFields as m, MaxParallelSetting as n, TimeoutSetting as o, parseBoundedInt as p, OnErrorSetting as r, SettingRow as s, ErrorWorkflowSetting as t, RETENTION_DAYS_RANGE as u, UpdateWorkflowNotificationSettingsDocument as v };
|