@oneuptime/common 12.0.7 → 12.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/Models/DatabaseModels/TelemetryEntity.ts +45 -0
  2. package/Models/DatabaseModels/TelemetryEntityRelationship.ts +26 -0
  3. package/Models/DatabaseModels/WorkflowLog.ts +38 -0
  4. package/Server/API/UserAPI.ts +16 -1
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.ts +41 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.ts +17 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  8. package/Server/Middleware/MasterAdminAuthorization.ts +11 -6
  9. package/Server/Services/TelemetryEntityRelationshipService.ts +3 -0
  10. package/Server/Services/TelemetryEntityService.ts +114 -1
  11. package/Server/Types/Workflow/Components/API/Utils.ts +44 -1
  12. package/Server/Types/Workflow/TriggerCode.ts +12 -0
  13. package/Server/Types/Workflow/Workflow.ts +5 -0
  14. package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +689 -0
  15. package/Server/Utils/VM/VMAPI.ts +56 -8
  16. package/Tests/App/Dashboard/MonitorCriteriaAttributeFilter.test.tsx +574 -0
  17. package/Tests/Server/API/UserProjectsAPI.test.ts +478 -4
  18. package/Tests/Server/Services/TelemetryEntityManualCreate.test.ts +248 -0
  19. package/Tests/Server/Types/Workflow/Components/ApiComponentHeaders.test.ts +192 -0
  20. package/Tests/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.test.ts +152 -0
  21. package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +322 -0
  22. package/Tests/Server/Utils/VM/VMAPISubstitution.test.ts +243 -0
  23. package/Tests/Types/SerializableObjectDictionaryImportCycle.test.ts +197 -0
  24. package/Tests/Types/Telemetry/EntityTypeGroups.test.ts +140 -0
  25. package/Tests/Types/Workflow/StepTrace.test.ts +235 -0
  26. package/Tests/Types/Workflow/TemplateSyntax.test.ts +379 -0
  27. package/Tests/Types/Workflow/Templates.test.ts +403 -0
  28. package/Tests/UI/Components/CustomTimeRangeModal.test.tsx +459 -0
  29. package/Tests/UI/Components/DictionaryAttributeFilterRow.test.tsx +477 -0
  30. package/Tests/UI/Components/Forms/AnchoredFieldPopupKeyboard.test.tsx +382 -0
  31. package/Tests/UI/Components/Forms/ColorPickerPicking.test.tsx +569 -0
  32. package/Tests/UI/Components/Forms/ValidationJSON.test.ts +241 -0
  33. package/Tests/UI/Components/LogTimeRangePicker.test.tsx +42 -0
  34. package/Tests/UI/Components/ModalPortalDismissal.test.tsx +90 -0
  35. package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +49 -7
  36. package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +325 -0
  37. package/Tests/UI/Components/Workflow/GraphLint.test.ts +821 -0
  38. package/Tests/UI/Components/Workflow/ModelColumnEditor.test.ts +287 -0
  39. package/Tests/UI/Components/Workflow/ModelColumnEditorServerContract.test.ts +185 -0
  40. package/Tests/UI/Components/Workflow/ModelSchema.test.ts +174 -0
  41. package/Tests/UI/Components/Workflow/RunStatusWatcher.test.ts +152 -0
  42. package/Tests/UI/Components/Workflow/Utils.test.ts +192 -0
  43. package/Tests/UI/Monitor/MonitorStepCriteriaView.test.tsx +432 -0
  44. package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +514 -0
  45. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +240 -0
  46. package/Tests/Utils/Schema/AnalyticsModelSchema.test.ts +469 -0
  47. package/Tests/Utils/Schema/ModelSchema.test.ts +268 -0
  48. package/Tests/Utils/Telemetry/EntityKeyNonTelemetry.test.ts +193 -0
  49. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +31 -0
  50. package/Types/SerializableObjectDictionary.ts +133 -39
  51. package/Types/Telemetry/EntitySource.ts +40 -0
  52. package/Types/Telemetry/EntityType.ts +27 -0
  53. package/Types/Telemetry/EntityTypeGroups.ts +65 -0
  54. package/Types/Workflow/Component.ts +29 -0
  55. package/Types/Workflow/Components/API.ts +35 -0
  56. package/Types/Workflow/Components/BaseModel.ts +7 -3
  57. package/Types/Workflow/StepTrace.ts +181 -0
  58. package/Types/Workflow/TemplateSyntax.ts +499 -0
  59. package/Types/Workflow/Templates.ts +316 -0
  60. package/UI/Components/Date/CustomTimeRangeModal.tsx +278 -0
  61. package/UI/Components/Date/TimeRangePickerDropdown.tsx +250 -0
  62. package/UI/Components/Dictionary/Dictionary.tsx +23 -6
  63. package/UI/Components/Forms/Fields/ColorPicker.tsx +66 -10
  64. package/UI/Components/Forms/Fields/IconPicker.tsx +48 -10
  65. package/UI/Components/Forms/Types/Field.ts +7 -0
  66. package/UI/Components/Forms/Validation.ts +63 -1
  67. package/UI/Components/Input/Input.tsx +31 -3
  68. package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +11 -216
  69. package/UI/Components/Modal/Modal.tsx +37 -4
  70. package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +11 -210
  71. package/UI/Components/Workflow/ArgumentsForm.tsx +194 -7
  72. package/UI/Components/Workflow/Component.tsx +28 -26
  73. package/UI/Components/Workflow/ComponentSettingsModal.tsx +69 -8
  74. package/UI/Components/Workflow/GraphLint.ts +591 -0
  75. package/UI/Components/Workflow/ModelColumnEditor.tsx +396 -0
  76. package/UI/Components/Workflow/ModelSchema.ts +196 -0
  77. package/UI/Components/Workflow/RunForm.tsx +41 -7
  78. package/UI/Components/Workflow/RunStatusWatcher.ts +122 -0
  79. package/UI/Components/Workflow/StepTraceViewer.tsx +186 -0
  80. package/UI/Components/Workflow/Utils.ts +93 -1
  81. package/UI/Components/Workflow/Workflow.tsx +77 -4
  82. package/UI/Types/LayeredDismissal.ts +31 -0
  83. package/UI/Types/UseAnchoredFieldPopup.ts +99 -0
  84. package/Utils/Monitor/NetworkDeviceRoleUtil.ts +533 -0
  85. package/Utils/Monitor/NetworkTopologyUtil.ts +45 -7
  86. package/Utils/Telemetry/EntityKey.ts +66 -0
  87. package/build/dist/Models/DatabaseModels/TelemetryEntity.js +47 -0
  88. package/build/dist/Models/DatabaseModels/TelemetryEntity.js.map +1 -1
  89. package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js +27 -0
  90. package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js.map +1 -1
  91. package/build/dist/Models/DatabaseModels/WorkflowLog.js +39 -0
  92. package/build/dist/Models/DatabaseModels/WorkflowLog.js.map +1 -1
  93. package/build/dist/Server/API/UserAPI.js +16 -1
  94. package/build/dist/Server/API/UserAPI.js.map +1 -1
  95. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.js +20 -0
  96. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.js.map +1 -0
  97. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.js +12 -0
  98. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.js.map +1 -0
  99. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  100. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  101. package/build/dist/Server/Middleware/MasterAdminAuthorization.js +11 -6
  102. package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
  103. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +3 -0
  104. package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
  105. package/build/dist/Server/Services/TelemetryEntityService.js +91 -1
  106. package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
  107. package/build/dist/Server/Types/Workflow/Components/API/Utils.js +28 -0
  108. package/build/dist/Server/Types/Workflow/Components/API/Utils.js.map +1 -1
  109. package/build/dist/Server/Types/Workflow/TriggerCode.js.map +1 -1
  110. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +507 -0
  111. package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -0
  112. package/build/dist/Server/Utils/VM/VMAPI.js +51 -4
  113. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  114. package/build/dist/Types/SerializableObjectDictionary.js +133 -39
  115. package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
  116. package/build/dist/Types/Telemetry/EntitySource.js +39 -0
  117. package/build/dist/Types/Telemetry/EntitySource.js.map +1 -0
  118. package/build/dist/Types/Telemetry/EntityType.js +27 -0
  119. package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
  120. package/build/dist/Types/Telemetry/EntityTypeGroups.js +57 -0
  121. package/build/dist/Types/Telemetry/EntityTypeGroups.js.map +1 -0
  122. package/build/dist/Types/Workflow/Component.js +21 -0
  123. package/build/dist/Types/Workflow/Component.js.map +1 -1
  124. package/build/dist/Types/Workflow/Components/API.js +35 -0
  125. package/build/dist/Types/Workflow/Components/API.js.map +1 -1
  126. package/build/dist/Types/Workflow/Components/BaseModel.js +7 -3
  127. package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
  128. package/build/dist/Types/Workflow/StepTrace.js +104 -0
  129. package/build/dist/Types/Workflow/StepTrace.js.map +1 -0
  130. package/build/dist/Types/Workflow/TemplateSyntax.js +326 -0
  131. package/build/dist/Types/Workflow/TemplateSyntax.js.map +1 -0
  132. package/build/dist/Types/Workflow/Templates.js +218 -0
  133. package/build/dist/Types/Workflow/Templates.js.map +1 -0
  134. package/build/dist/UI/Components/Date/CustomTimeRangeModal.js +126 -0
  135. package/build/dist/UI/Components/Date/CustomTimeRangeModal.js.map +1 -0
  136. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +123 -0
  137. package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -0
  138. package/build/dist/UI/Components/Dictionary/Dictionary.js +22 -6
  139. package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
  140. package/build/dist/UI/Components/Forms/Fields/ColorPicker.js +52 -13
  141. package/build/dist/UI/Components/Forms/Fields/ColorPicker.js.map +1 -1
  142. package/build/dist/UI/Components/Forms/Fields/IconPicker.js +28 -12
  143. package/build/dist/UI/Components/Forms/Fields/IconPicker.js.map +1 -1
  144. package/build/dist/UI/Components/Forms/Validation.js +44 -0
  145. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  146. package/build/dist/UI/Components/Input/Input.js +12 -4
  147. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  148. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +5 -108
  149. package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
  150. package/build/dist/UI/Components/Modal/Modal.js +31 -5
  151. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  152. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +5 -105
  153. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
  154. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +140 -11
  155. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  156. package/build/dist/UI/Components/Workflow/Component.js +20 -19
  157. package/build/dist/UI/Components/Workflow/Component.js.map +1 -1
  158. package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js +31 -3
  159. package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js.map +1 -1
  160. package/build/dist/UI/Components/Workflow/GraphLint.js +396 -0
  161. package/build/dist/UI/Components/Workflow/GraphLint.js.map +1 -0
  162. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +225 -0
  163. package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -0
  164. package/build/dist/UI/Components/Workflow/ModelSchema.js +118 -0
  165. package/build/dist/UI/Components/Workflow/ModelSchema.js.map +1 -0
  166. package/build/dist/UI/Components/Workflow/RunForm.js +22 -6
  167. package/build/dist/UI/Components/Workflow/RunForm.js.map +1 -1
  168. package/build/dist/UI/Components/Workflow/RunStatusWatcher.js +76 -0
  169. package/build/dist/UI/Components/Workflow/RunStatusWatcher.js.map +1 -0
  170. package/build/dist/UI/Components/Workflow/StepTraceViewer.js +76 -0
  171. package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -0
  172. package/build/dist/UI/Components/Workflow/Utils.js +73 -1
  173. package/build/dist/UI/Components/Workflow/Utils.js.map +1 -1
  174. package/build/dist/UI/Components/Workflow/Workflow.js +51 -6
  175. package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
  176. package/build/dist/UI/Types/LayeredDismissal.js +21 -0
  177. package/build/dist/UI/Types/LayeredDismissal.js.map +1 -0
  178. package/build/dist/UI/Types/UseAnchoredFieldPopup.js +74 -1
  179. package/build/dist/UI/Types/UseAnchoredFieldPopup.js.map +1 -1
  180. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +386 -0
  181. package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -0
  182. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +35 -6
  183. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  184. package/build/dist/Utils/Telemetry/EntityKey.js +52 -0
  185. package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
  186. package/package.json +1 -1
@@ -6,8 +6,12 @@ import { CustomElementProps } from "../Forms/Types/Field";
6
6
  import FormValues from "../Forms/Types/FormValues";
7
7
  import ComponentValuePickerModal from "./ComponentValuePickerModal";
8
8
  import CronScheduleField from "./CronScheduleField";
9
+ import ModelColumnEditor, { ModelColumnEditorMode } from "./ModelColumnEditor";
9
10
  import ModelFieldPicker from "./ModelFieldPicker";
10
- import { componentInputTypeToFormFieldType } from "./Utils";
11
+ import {
12
+ componentInputTypeToFormFieldType,
13
+ parseStringDictionaryValue,
14
+ } from "./Utils";
11
15
  import VariableModal from "./VariableModal";
12
16
  import Dictionary from "../../../Types/Dictionary";
13
17
  import { JSONObject } from "../../../Types/JSON";
@@ -16,6 +20,7 @@ import {
16
20
  Argument,
17
21
  ComponentInputType,
18
22
  NodeDataProp,
23
+ isJSON5ToleratedInputType,
19
24
  } from "../../../Types/Workflow/Component";
20
25
  import { DropdownOption } from "../Dropdown/Dropdown";
21
26
  import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
@@ -53,6 +58,96 @@ const ArgumentsForm: FunctionComponent<ComponentProps> = (
53
58
 
54
59
  const [selectedArgId, setSelectedArgId] = useState<string>("");
55
60
 
61
+ /*
62
+ * Arguments flagged isAdvanced are collapsed behind a disclosure, so the
63
+ * settings panel opens on the two or three fields that actually decide what
64
+ * the step does rather than on every knob it has. A required argument is
65
+ * never collapsed no matter how it is flagged: BasicForm skips validation
66
+ * for a field hidden by showIf, so hiding a required one would let an
67
+ * incomplete step save cleanly.
68
+ */
69
+ const collapsibleAdvancedArguments: Array<Argument> = (
70
+ component.metadata.arguments || []
71
+ ).filter((arg: Argument) => {
72
+ return Boolean(arg.isAdvanced) && !arg.required;
73
+ });
74
+
75
+ const hasValueForArgument: (arg: Argument) => boolean = (
76
+ arg: Argument,
77
+ ): boolean => {
78
+ const value: unknown = component.arguments
79
+ ? component.arguments[arg.id]
80
+ : undefined;
81
+
82
+ if (value === undefined || value === null) {
83
+ return false;
84
+ }
85
+
86
+ if (typeof value === "string") {
87
+ return value.trim() !== "";
88
+ }
89
+
90
+ if (typeof value === "object") {
91
+ return Object.keys(value as JSONObject).length > 0;
92
+ }
93
+
94
+ return true;
95
+ };
96
+
97
+ /*
98
+ * Open on load when something down there is already set, so an existing
99
+ * configuration is never hidden from the person who comes back to read it.
100
+ */
101
+ const [showAdvanced, setShowAdvanced] = useState<boolean>(
102
+ collapsibleAdvancedArguments.some(hasValueForArgument),
103
+ );
104
+
105
+ const isCollapsibleAdvanced: (arg: Argument) => boolean = (
106
+ arg: Argument,
107
+ ): boolean => {
108
+ return collapsibleAdvancedArguments.some((advancedArg: Argument) => {
109
+ return advancedArg.id === arg.id;
110
+ });
111
+ };
112
+
113
+ /*
114
+ * StringDictionary arguments render as key/value rows, and the row editor
115
+ * needs a real object. Every workflow written before that change stored a
116
+ * JSON string, so parse those on the way into the form. Anything
117
+ * parseStringDictionaryValue declines stays a string and keeps the JSON
118
+ * editor (see componentInputTypeToFormFieldType), so the two always agree.
119
+ */
120
+ const formInitialValues: JSONObject = { ...(component.arguments || {}) };
121
+
122
+ for (const arg of component.metadata.arguments || []) {
123
+ if (arg.type !== ComponentInputType.StringDictionary) {
124
+ continue;
125
+ }
126
+
127
+ const parsed: JSONObject | null = parseStringDictionaryValue(
128
+ formInitialValues[arg.id],
129
+ );
130
+
131
+ if (parsed !== null) {
132
+ formInitialValues[arg.id] = parsed;
133
+ }
134
+ }
135
+
136
+ /*
137
+ * Everyday settings first, collapsible ones last, each group keeping its
138
+ * declared order. Without this an advanced argument declared in the middle
139
+ * would pop into the middle of the form when the disclosure opens.
140
+ */
141
+ const orderedArguments: Array<Argument> = [
142
+ ...(component.metadata.arguments || []).filter((arg: Argument) => {
143
+ return !isCollapsibleAdvanced(arg);
144
+ }),
145
+ ...collapsibleAdvancedArguments,
146
+ ];
147
+
148
+ const firstAdvancedArgumentIndex: number =
149
+ orderedArguments.length - collapsibleAdvancedArguments.length;
150
+
56
151
  /*
57
152
  * Workflows in the current project, used to populate dropdowns for any
58
153
  * argument of type WorkflowSelect (e.g. the "Workflow" field on the
@@ -163,7 +258,7 @@ const ArgumentsForm: FunctionComponent<ComponentProps> = (
163
258
  hideSubmitButton={true}
164
259
  ref={formRef}
165
260
  initialValues={{
166
- ...(component.arguments || {}),
261
+ ...formInitialValues,
167
262
  }}
168
263
  onChange={(values: FormValues<JSONObject>) => {
169
264
  setComponent({
@@ -175,16 +270,23 @@ const ArgumentsForm: FunctionComponent<ComponentProps> = (
175
270
  });
176
271
  }}
177
272
  onFormValidationErrorChanged={(hasError: boolean) => {
178
- if (hasFormValidationErrors["id"] !== hasError) {
273
+ /*
274
+ * Keyed "arguments", not "id": the Identifier field in the
275
+ * settings modal reports into the same dictionary under "id",
276
+ * so both forms were overwriting each other. Typing in the
277
+ * Identifier box cleared an outstanding argument error and
278
+ * re-enabled Save on a component that was still invalid.
279
+ */
280
+ if (hasFormValidationErrors["arguments"] !== hasError) {
179
281
  setHasFormValidationErrors({
180
282
  ...hasFormValidationErrors,
181
- id: hasError,
283
+ arguments: hasError,
182
284
  });
183
285
  }
184
286
  }}
185
287
  fields={
186
288
  component.metadata.arguments &&
187
- component.metadata.arguments.map((arg: Argument) => {
289
+ orderedArguments.map((arg: Argument, argIndex: number) => {
188
290
  const isWorkflowSelect: boolean =
189
291
  arg.type === ComponentInputType.WorkflowSelect;
190
292
 
@@ -207,6 +309,25 @@ const ArgumentsForm: FunctionComponent<ComponentProps> = (
207
309
  const useCronPicker: boolean =
208
310
  arg.type === ComponentInputType.CronTab;
209
311
 
312
+ /*
313
+ * Query arguments (which records does this act on) and
314
+ * BaseModel arguments (which values does it write) are both
315
+ * keyed on the model's columns, so both get the row editor
316
+ * backed by the model schema. Same tableName requirement as
317
+ * the field picker.
318
+ *
319
+ * BaseModelArray is deliberately not included: it holds a
320
+ * list of records, which rows cannot represent.
321
+ */
322
+ const columnEditorMode: ModelColumnEditorMode | undefined =
323
+ !component.metadata.tableName
324
+ ? undefined
325
+ : arg.type === ComponentInputType.Query
326
+ ? ModelColumnEditorMode.Query
327
+ : arg.type === ComponentInputType.BaseModel
328
+ ? ModelColumnEditorMode.Record
329
+ : undefined;
330
+
210
331
  let baseField: {
211
332
  fieldType: import("../Forms/Types/FormFieldSchemaType").default;
212
333
  dropdownOptions?: Array<DropdownOption> | undefined;
@@ -216,7 +337,29 @@ const ArgumentsForm: FunctionComponent<ComponentProps> = (
216
337
  ) => ReactElement | undefined;
217
338
  };
218
339
 
219
- if (useFieldPicker) {
340
+ if (columnEditorMode) {
341
+ baseField = {
342
+ fieldType: FormFieldSchemaType.CustomComponent,
343
+ getCustomElement: (
344
+ _values: FormValues<JSONObject>,
345
+ customProps: CustomElementProps,
346
+ ): ReactElement => {
347
+ return (
348
+ <ModelColumnEditor
349
+ tableName={component.metadata.tableName as string}
350
+ mode={columnEditorMode}
351
+ initialValue={customProps.initialValue}
352
+ onChange={(value: string) => {
353
+ void customProps.onChange?.(value);
354
+ }}
355
+ placeholder={customProps.placeholder}
356
+ error={customProps.error}
357
+ tabIndex={customProps.tabIndex}
358
+ />
359
+ );
360
+ },
361
+ };
362
+ } else if (useFieldPicker) {
220
363
  baseField = {
221
364
  fieldType: FormFieldSchemaType.CustomComponent,
222
365
  getCustomElement: (
@@ -285,10 +428,29 @@ const ArgumentsForm: FunctionComponent<ComponentProps> = (
285
428
  * and a schedule can't reference component return values).
286
429
  */
287
430
  const showVariableFooter: boolean =
288
- !isWorkflowSelect && !useFieldPicker && !useCronPicker;
431
+ !isWorkflowSelect &&
432
+ !useFieldPicker &&
433
+ !useCronPicker &&
434
+ !columnEditorMode;
435
+
436
+ const isAdvanced: boolean = isCollapsibleAdvanced(arg);
289
437
 
290
438
  return {
291
439
  title: `${arg.name}`,
440
+ /*
441
+ * The heading sits on the first advanced field, so the
442
+ * disclosure reads as a labelled section rather than as a
443
+ * run of extra inputs appearing out of nowhere.
444
+ */
445
+ sectionTitle:
446
+ isAdvanced && argIndex === firstAdvancedArgumentIndex
447
+ ? "Advanced"
448
+ : undefined,
449
+ showIf: isAdvanced
450
+ ? (): boolean => {
451
+ return showAdvanced;
452
+ }
453
+ : undefined,
292
454
  footerElement: showVariableFooter ? (
293
455
  <div className="text-gray-500">
294
456
  <p className="text-sm">
@@ -323,12 +485,37 @@ const ArgumentsForm: FunctionComponent<ComponentProps> = (
323
485
  },
324
486
  required: arg.required,
325
487
  placeholder: arg.placeholder,
488
+ /*
489
+ * Some argument types are read back with JSON5 rather than
490
+ * JSON, so the check has to be no stricter than the parser
491
+ * that will actually see the value.
492
+ */
493
+ allowJSON5: isJSON5ToleratedInputType(arg.type),
326
494
  ...baseField,
327
495
  };
328
496
  })
329
497
  }
330
498
  />
331
499
  )}
500
+
501
+ {collapsibleAdvancedArguments.length > 0 && (
502
+ <div className="mt-3">
503
+ <button
504
+ type="button"
505
+ aria-expanded={showAdvanced}
506
+ className="text-sm underline text-blue-500 hover:text-blue-600 cursor-pointer"
507
+ onClick={() => {
508
+ setShowAdvanced(!showAdvanced);
509
+ }}
510
+ >
511
+ {showAdvanced
512
+ ? "Hide advanced settings"
513
+ : `Show ${collapsibleAdvancedArguments.length} advanced setting${
514
+ collapsibleAdvancedArguments.length === 1 ? "" : "s"
515
+ }`}
516
+ </button>
517
+ </div>
518
+ )}
332
519
  </div>
333
520
  {showVariableModal && (
334
521
  <VariableModal
@@ -372,35 +372,37 @@ const Node: FunctionComponent<ComponentProps> = (props: ComponentProps) => {
372
372
  </div>
373
373
  )}
374
374
 
375
- {/* Error indicator */}
375
+ {/* Error indicator — hover it to read what is actually wrong. */}
376
376
  {!props.data.isPreview && hasError && (
377
- <div
378
- style={{
379
- position: "absolute",
380
- top: "8px",
381
- right: "8px",
382
- zIndex: 10,
383
- width: "22px",
384
- height: "22px",
385
- borderRadius: "50%",
386
- backgroundColor:
387
- "color-mix(in srgb, #ef4444 15%, var(--ou-surface-primary, #ffffff))",
388
- display: "flex",
389
- alignItems: "center",
390
- justifyContent: "center",
391
- border: "1px solid rgb(248 113 113 / 45%)",
392
- }}
393
- >
394
- <Icon
395
- icon={IconProp.Alert}
377
+ <Tooltip text={props.data.error}>
378
+ <div
396
379
  style={{
397
- color: "#ef4444",
398
- width: "0.75rem",
399
- height: "0.75rem",
380
+ position: "absolute",
381
+ top: "8px",
382
+ right: "8px",
383
+ zIndex: 10,
384
+ width: "22px",
385
+ height: "22px",
386
+ borderRadius: "50%",
387
+ backgroundColor:
388
+ "color-mix(in srgb, #ef4444 15%, var(--ou-surface-primary, #ffffff))",
389
+ display: "flex",
390
+ alignItems: "center",
391
+ justifyContent: "center",
392
+ border: "1px solid rgb(248 113 113 / 45%)",
400
393
  }}
401
- thick={ThickProp.Thick}
402
- />
403
- </div>
394
+ >
395
+ <Icon
396
+ icon={IconProp.Alert}
397
+ style={{
398
+ color: "#ef4444",
399
+ width: "0.75rem",
400
+ height: "0.75rem",
401
+ }}
402
+ thick={ThickProp.Thick}
403
+ />
404
+ </div>
405
+ </Tooltip>
404
406
  )}
405
407
 
406
408
  {/* Header bar with gradient */}
@@ -22,6 +22,11 @@ export interface ComponentProps {
22
22
  onClose: () => void;
23
23
  onSave: (component: NodeDataProp) => void;
24
24
  onDelete: (component: NodeDataProp) => void;
25
+ /**
26
+ * Run this one step now. Absent for triggers, which have nothing to run on
27
+ * their own.
28
+ */
29
+ onRunStep?: ((component: NodeDataProp) => void) | undefined;
25
30
  component: NodeDataProp;
26
31
  graphComponents: Array<NodeDataProp>;
27
32
  workflowId: ObjectID;
@@ -67,6 +72,8 @@ const ComponentSettingsModal: FunctionComponent<ComponentProps> = (
67
72
  >({});
68
73
  const [showDeleteConfirmation, setShowDeleteConfirmation] =
69
74
  useState<boolean>(false);
75
+ const [showRunStepConfirmation, setShowRunStepConfirmation] =
76
+ useState<boolean>(false);
70
77
 
71
78
  const settingsSection: ReactElement = (
72
79
  <SectionCard icon={IconProp.Settings} title="Settings">
@@ -188,17 +195,71 @@ const ComponentSettingsModal: FunctionComponent<ComponentProps> = (
188
195
  modalWidth={ModalWidth.Large}
189
196
  disableSubmitButton={hasErrors}
190
197
  leftFooterElement={
191
- <Button
192
- title="Delete"
193
- icon={IconProp.Trash}
194
- buttonStyle={ButtonStyleType.DANGER_OUTLINE}
195
- onClick={() => {
196
- setShowDeleteConfirmation(true);
197
- }}
198
- />
198
+ /*
199
+ * Say why Save is off. Form errors are only rendered under a field
200
+ * once it has been touched, so a component opened with a setting that
201
+ * was already invalid — a stored JSON value that does not parse, say —
202
+ * otherwise presents a dead button and no explanation anywhere.
203
+ */
204
+ hasErrors ? (
205
+ <div className="flex items-center gap-3">
206
+ <Button
207
+ title="Delete"
208
+ icon={IconProp.Trash}
209
+ buttonStyle={ButtonStyleType.DANGER_OUTLINE}
210
+ onClick={() => {
211
+ setShowDeleteConfirmation(true);
212
+ }}
213
+ />
214
+ <span className="text-sm text-red-600">
215
+ Some settings need fixing before this can be saved.
216
+ </span>
217
+ </div>
218
+ ) : (
219
+ <div className="flex items-center gap-3">
220
+ <Button
221
+ title="Delete"
222
+ icon={IconProp.Trash}
223
+ buttonStyle={ButtonStyleType.DANGER_OUTLINE}
224
+ onClick={() => {
225
+ setShowDeleteConfirmation(true);
226
+ }}
227
+ />
228
+ {props.onRunStep && (
229
+ <Button
230
+ title="Run just this step"
231
+ icon={IconProp.Play}
232
+ buttonStyle={ButtonStyleType.OUTLINE}
233
+ onClick={() => {
234
+ setShowRunStepConfirmation(true);
235
+ }}
236
+ />
237
+ )}
238
+ </div>
239
+ )
199
240
  }
200
241
  >
201
242
  <>
243
+ {showRunStepConfirmation && (
244
+ <ConfirmModal
245
+ title={`Run this step now?`}
246
+ /*
247
+ * Worded as what it is. There is no dry run: the step executes for
248
+ * real, and anything it sends or changes is not undone afterwards.
249
+ */
250
+ description={`This runs "${component.metadata.title}" for real, on its own. Anything it sends, writes or deletes actually happens. Values it reads from other steps will be empty, because nothing else runs.`}
251
+ onClose={() => {
252
+ setShowRunStepConfirmation(false);
253
+ }}
254
+ submitButtonText="Run this step"
255
+ onSubmit={() => {
256
+ setShowRunStepConfirmation(false);
257
+ props.onRunStep?.(component);
258
+ }}
259
+ submitButtonType={ButtonStyleType.NORMAL}
260
+ />
261
+ )}
262
+
202
263
  {showDeleteConfirmation && (
203
264
  <ConfirmModal
204
265
  title={`Delete ${component.metadata.componentType}`}