@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
@@ -0,0 +1,316 @@
1
+ /*
2
+ * Starter workflows.
3
+ *
4
+ * An empty canvas is a bad first lesson: the thing a new builder most needs to
5
+ * learn is how one step reads another's output, and nothing on a blank graph
6
+ * teaches it. Each template below is a small, working workflow whose whole
7
+ * point is to show a {{local.components.…}} reference in context.
8
+ *
9
+ * Every template here is deliberately runnable with no external configuration
10
+ * — no Slack token, no API key, no third-party account. Templates that need
11
+ * credentials belong in the docs, not in a one-click "start from this".
12
+ *
13
+ * The graphs are plain data in the same shape the builder saves and the JSON
14
+ * import reads, so a template is created through the ordinary Workflow create
15
+ * path (which denormalizes the trigger onto the row in
16
+ * WorkflowService.onCreateSuccess).
17
+ */
18
+
19
+ import { JSONObject } from "../JSON";
20
+ import ComponentID from "./ComponentID";
21
+ import { ComponentType, NodeType } from "./Component";
22
+
23
+ export interface WorkflowTemplate {
24
+ /** Stable key, used by the picker and by tests. */
25
+ id: string;
26
+ name: string;
27
+ /** One line, shown on the card. */
28
+ description: string;
29
+ /** What the builder should look at once it opens. */
30
+ teaches: string;
31
+ /** Suggested name for the created workflow. */
32
+ workflowName: string;
33
+ workflowDescription: string;
34
+ }
35
+
36
+ interface TemplateNodeSpec {
37
+ componentId: string;
38
+ metadataId: string;
39
+ componentType: ComponentType;
40
+ position: { x: number; y: number };
41
+ args?: JSONObject | undefined;
42
+ }
43
+
44
+ interface TemplateEdgeSpec {
45
+ fromComponentId: string;
46
+ toComponentId: string;
47
+ fromPort: string;
48
+ }
49
+
50
+ interface TemplateGraphSpec {
51
+ nodes: Array<TemplateNodeSpec>;
52
+ edges: Array<TemplateEdgeSpec>;
53
+ }
54
+
55
+ /*
56
+ * Node and edge identifiers are generated per creation rather than baked in,
57
+ * so two workflows made from the same template never share react-flow ids.
58
+ * The component ids (the user-facing "log-1") are stable, because that is what
59
+ * the {{...}} references inside the template point at.
60
+ */
61
+ export type BuildTemplateGraphFunction = (
62
+ spec: TemplateGraphSpec,
63
+ generateId: () => string,
64
+ ) => JSONObject;
65
+
66
+ export const buildTemplateGraph: BuildTemplateGraphFunction = (
67
+ spec: TemplateGraphSpec,
68
+ generateId: () => string,
69
+ ): JSONObject => {
70
+ const nodeIdByComponentId: Record<string, string> = {};
71
+
72
+ const nodes: Array<JSONObject> = spec.nodes.map(
73
+ (node: TemplateNodeSpec): JSONObject => {
74
+ const nodeId: string = generateId();
75
+ nodeIdByComponentId[node.componentId] = nodeId;
76
+
77
+ return {
78
+ id: nodeId,
79
+ type: "node",
80
+ position: node.position,
81
+ data: {
82
+ id: node.componentId,
83
+ nodeType: NodeType.Node,
84
+ componentType: node.componentType,
85
+ metadataId: node.metadataId,
86
+ internalId: generateId(),
87
+ error: "",
88
+ arguments: node.args || {},
89
+ returnValues: {},
90
+ },
91
+ };
92
+ },
93
+ );
94
+
95
+ const edges: Array<JSONObject> = spec.edges.map(
96
+ (edge: TemplateEdgeSpec): JSONObject => {
97
+ return {
98
+ id: generateId(),
99
+ source: nodeIdByComponentId[edge.fromComponentId] as string,
100
+ target: nodeIdByComponentId[edge.toComponentId] as string,
101
+ sourceHandle: edge.fromPort,
102
+ targetHandle: "in",
103
+ };
104
+ },
105
+ );
106
+
107
+ return { nodes: nodes, edges: edges };
108
+ };
109
+
110
+ type TemplateDefinition = WorkflowTemplate & { graph: TemplateGraphSpec };
111
+
112
+ const TEMPLATE_DEFINITIONS: Array<TemplateDefinition> = [
113
+ {
114
+ id: "manual-log",
115
+ name: "Log a message when run by hand",
116
+ description:
117
+ "The smallest working workflow: a manual trigger and a step that writes a line to the run log.",
118
+ teaches: "How to run a workflow and where its output shows up.",
119
+ workflowName: "Log a message",
120
+ workflowDescription:
121
+ "Runs on demand and writes a line to the run log. A good place to start.",
122
+ graph: {
123
+ nodes: [
124
+ {
125
+ componentId: "manual-1",
126
+ metadataId: ComponentID.Manual,
127
+ componentType: ComponentType.Trigger,
128
+ position: { x: 100, y: 100 },
129
+ },
130
+ {
131
+ componentId: "log-1",
132
+ metadataId: ComponentID.Log,
133
+ componentType: ComponentType.Component,
134
+ position: { x: 100, y: 300 },
135
+ args: {
136
+ value: "Hello from OneUptime. This workflow ran.",
137
+ },
138
+ },
139
+ ],
140
+ edges: [
141
+ {
142
+ fromComponentId: "manual-1",
143
+ toComponentId: "log-1",
144
+ fromPort: "success",
145
+ },
146
+ ],
147
+ },
148
+ },
149
+ {
150
+ id: "webhook-echo",
151
+ name: "Echo what a webhook sent",
152
+ description:
153
+ "Takes the body posted to this workflow's webhook and writes it back out to the log.",
154
+ /*
155
+ * The reason this template exists: the log message is a reference, not
156
+ * text, and seeing that in place is the fastest way to understand the
157
+ * whole substitution model.
158
+ */
159
+ teaches:
160
+ "How one step reads another's output, using {{local.components...}}.",
161
+ workflowName: "Echo webhook body",
162
+ workflowDescription:
163
+ "Writes whatever was posted to this workflow's webhook into the run log.",
164
+ graph: {
165
+ nodes: [
166
+ {
167
+ componentId: "webhook-1",
168
+ metadataId: ComponentID.Webhook,
169
+ componentType: ComponentType.Trigger,
170
+ position: { x: 100, y: 100 },
171
+ },
172
+ {
173
+ componentId: "log-1",
174
+ metadataId: ComponentID.Log,
175
+ componentType: ComponentType.Component,
176
+ position: { x: 100, y: 300 },
177
+ args: {
178
+ value:
179
+ "Webhook received: {{local.components.webhook-1.returnValues.request-body}}",
180
+ },
181
+ },
182
+ ],
183
+ edges: [
184
+ {
185
+ fromComponentId: "webhook-1",
186
+ toComponentId: "log-1",
187
+ fromPort: "out",
188
+ },
189
+ ],
190
+ },
191
+ },
192
+ {
193
+ id: "scheduled-check",
194
+ name: "Call an API on a schedule",
195
+ description:
196
+ "Runs every hour, calls a URL, and logs whether it came back OK — with separate paths for success and failure.",
197
+ teaches:
198
+ "How a schedule works, and how a step's Success and Error ports branch.",
199
+ workflowName: "Hourly API check",
200
+ workflowDescription:
201
+ "Calls a URL every hour and logs the outcome. Point the URL at something of yours.",
202
+ graph: {
203
+ nodes: [
204
+ {
205
+ componentId: "schedule-1",
206
+ metadataId: ComponentID.Schedule,
207
+ componentType: ComponentType.Trigger,
208
+ position: { x: 100, y: 100 },
209
+ args: {
210
+ schedule: "0 * * * *",
211
+ },
212
+ },
213
+ {
214
+ componentId: "api-get-1",
215
+ metadataId: ComponentID.ApiGet,
216
+ componentType: ComponentType.Component,
217
+ position: { x: 100, y: 300 },
218
+ args: {
219
+ url: "https://api.github.com/zen",
220
+ },
221
+ },
222
+ {
223
+ componentId: "log-ok",
224
+ metadataId: ComponentID.Log,
225
+ componentType: ComponentType.Component,
226
+ position: { x: -100, y: 500 },
227
+ args: {
228
+ value:
229
+ "Responded {{local.components.api-get-1.returnValues.response-status}}: {{local.components.api-get-1.returnValues.response-body}}",
230
+ },
231
+ },
232
+ {
233
+ componentId: "log-failed",
234
+ metadataId: ComponentID.Log,
235
+ componentType: ComponentType.Component,
236
+ position: { x: 300, y: 500 },
237
+ args: {
238
+ value:
239
+ "Call failed: {{local.components.api-get-1.returnValues.error}}",
240
+ },
241
+ },
242
+ ],
243
+ edges: [
244
+ {
245
+ fromComponentId: "schedule-1",
246
+ toComponentId: "api-get-1",
247
+ fromPort: "execute",
248
+ },
249
+ {
250
+ fromComponentId: "api-get-1",
251
+ toComponentId: "log-ok",
252
+ fromPort: "success",
253
+ },
254
+ {
255
+ fromComponentId: "api-get-1",
256
+ toComponentId: "log-failed",
257
+ fromPort: "error",
258
+ },
259
+ ],
260
+ },
261
+ },
262
+ ];
263
+
264
+ export type GetWorkflowTemplatesFunction = () => Array<WorkflowTemplate>;
265
+
266
+ export const getWorkflowTemplates: GetWorkflowTemplatesFunction =
267
+ (): Array<WorkflowTemplate> => {
268
+ return TEMPLATE_DEFINITIONS.map(
269
+ (definition: TemplateDefinition): WorkflowTemplate => {
270
+ return {
271
+ id: definition.id,
272
+ name: definition.name,
273
+ description: definition.description,
274
+ teaches: definition.teaches,
275
+ workflowName: definition.workflowName,
276
+ workflowDescription: definition.workflowDescription,
277
+ };
278
+ },
279
+ );
280
+ };
281
+
282
+ export type GetTemplateGraphSpecFunction = (
283
+ templateId: string,
284
+ ) => TemplateGraphSpec | null;
285
+
286
+ export const getTemplateGraphSpec: GetTemplateGraphSpecFunction = (
287
+ templateId: string,
288
+ ): TemplateGraphSpec | null => {
289
+ const definition: TemplateDefinition | undefined = TEMPLATE_DEFINITIONS.find(
290
+ (candidate: TemplateDefinition) => {
291
+ return candidate.id === templateId;
292
+ },
293
+ );
294
+
295
+ return definition ? definition.graph : null;
296
+ };
297
+
298
+ export type BuildGraphForTemplateFunction = (
299
+ templateId: string,
300
+ generateId: () => string,
301
+ ) => JSONObject | null;
302
+
303
+ export const buildGraphForTemplate: BuildGraphForTemplateFunction = (
304
+ templateId: string,
305
+ generateId: () => string,
306
+ ): JSONObject | null => {
307
+ const spec: TemplateGraphSpec | null = getTemplateGraphSpec(templateId);
308
+
309
+ if (!spec) {
310
+ return null;
311
+ }
312
+
313
+ return buildTemplateGraph(spec, generateId);
314
+ };
315
+
316
+ export default getWorkflowTemplates;
@@ -0,0 +1,278 @@
1
+ import React, { FunctionComponent, ReactElement, useId, useState } from "react";
2
+ import InBetween from "../../../Types/BaseDatabase/InBetween";
3
+ import OneUptimeDate from "../../../Types/Date";
4
+ import IconProp from "../../../Types/Icon/IconProp";
5
+ import Icon from "../Icon/Icon";
6
+ import Input, { InputType } from "../Input/Input";
7
+ import Modal, { ModalWidth } from "../Modal/Modal";
8
+
9
+ export interface ComponentProps {
10
+ /*
11
+ * Window the editor opens on. Callers seed this with the range currently in
12
+ * effect so switching to "custom" starts from what the user is looking at
13
+ * rather than from an empty form.
14
+ */
15
+ initialValue?: InBetween<Date> | undefined;
16
+ title?: string | undefined;
17
+ description?: string | undefined;
18
+ onClose: () => void;
19
+ onSave: (value: InBetween<Date>) => void;
20
+ }
21
+
22
+ export interface QuickFillOption {
23
+ label: string;
24
+ minutes: number;
25
+ }
26
+
27
+ /*
28
+ * Shortcuts that fill both ends of the form at once, anchored to now. They are
29
+ * a starting point for a hand-tuned window — picking one does not apply the
30
+ * range, so the user can still nudge either end before hitting Apply.
31
+ */
32
+ export const QUICK_FILL_OPTIONS: Array<QuickFillOption> = [
33
+ { label: "Last 1 hour", minutes: 60 },
34
+ { label: "Last 6 hours", minutes: 60 * 6 },
35
+ { label: "Last 12 hours", minutes: 60 * 12 },
36
+ { label: "Last 24 hours", minutes: 60 * 24 },
37
+ { label: "Last 7 days", minutes: 60 * 24 * 7 },
38
+ { label: "Last 30 days", minutes: 60 * 24 * 30 },
39
+ ];
40
+
41
+ export type CustomTimeRangeValidationError = string;
42
+
43
+ /*
44
+ * Kept pure and exported so the rules the Apply button is gated on can be
45
+ * checked directly, and so callers share one definition of "valid window".
46
+ */
47
+ export function getCustomTimeRangeError(
48
+ startDate: Date | null,
49
+ endDate: Date | null,
50
+ ): CustomTimeRangeValidationError {
51
+ if (!startDate) {
52
+ return "Pick a start date and time.";
53
+ }
54
+
55
+ if (!endDate) {
56
+ return "Pick an end date and time.";
57
+ }
58
+
59
+ if (!OneUptimeDate.isAfter(endDate, startDate)) {
60
+ return "The end must be after the start. Adjust either end of the range.";
61
+ }
62
+
63
+ return "";
64
+ }
65
+
66
+ const CustomTimeRangeModal: FunctionComponent<ComponentProps> = (
67
+ props: ComponentProps,
68
+ ): ReactElement => {
69
+ const [startDate, setStartDate] = useState<Date | null>(
70
+ props.initialValue?.startValue || null,
71
+ );
72
+ const [endDate, setEndDate] = useState<Date | null>(
73
+ props.initialValue?.endValue || null,
74
+ );
75
+ // Two pickers can sit on one page, so the field ids cannot be constants.
76
+ const startFieldId: string = useId();
77
+ const endFieldId: string = useId();
78
+
79
+ const error: CustomTimeRangeValidationError = getCustomTimeRangeError(
80
+ startDate,
81
+ endDate,
82
+ );
83
+ const isValid: boolean = error === "";
84
+
85
+ const durationText: string = isValid
86
+ ? OneUptimeDate.secondsToFormattedFriendlyTimeString(
87
+ OneUptimeDate.getDifferenceInSeconds(endDate!, startDate!),
88
+ ).trim()
89
+ : "";
90
+
91
+ const formatDateTime: (date: Date) => string = (date: Date): string => {
92
+ return OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(
93
+ date,
94
+ false,
95
+ true,
96
+ );
97
+ };
98
+
99
+ const onQuickFill: (minutes: number) => void = (minutes: number): void => {
100
+ const end: Date = OneUptimeDate.resetSecondsAndMilliseconds(
101
+ OneUptimeDate.getCurrentDate(),
102
+ );
103
+ setStartDate(OneUptimeDate.addRemoveMinutes(end, -minutes));
104
+ setEndDate(end);
105
+ };
106
+
107
+ /*
108
+ * `Input` reports a cleared date field as an empty string. Anything else is
109
+ * an ISO string the field has already resolved in the user's timezone.
110
+ */
111
+ const toDate: (changedValue: string | Date) => Date | null = (
112
+ changedValue: string | Date,
113
+ ): Date | null => {
114
+ if (!changedValue) {
115
+ return null;
116
+ }
117
+
118
+ return OneUptimeDate.fromString(changedValue as string);
119
+ };
120
+
121
+ const isQuickFillActive: (minutes: number) => boolean = (
122
+ minutes: number,
123
+ ): boolean => {
124
+ if (!startDate || !endDate) {
125
+ return false;
126
+ }
127
+
128
+ return OneUptimeDate.getDifferenceInMinutes(startDate, endDate) === minutes;
129
+ };
130
+
131
+ return (
132
+ <Modal
133
+ title={props.title || "Custom Time Range"}
134
+ description={
135
+ props.description ||
136
+ "Choose the exact start and end date & time of the data you want to see."
137
+ }
138
+ modalWidth={ModalWidth.Medium}
139
+ submitButtonText="Apply"
140
+ closeButtonText="Cancel"
141
+ disableSubmitButton={!isValid}
142
+ onClose={props.onClose}
143
+ onSubmit={() => {
144
+ if (!isValid) {
145
+ return;
146
+ }
147
+
148
+ props.onSave(new InBetween<Date>(startDate!, endDate!));
149
+ }}
150
+ >
151
+ <div data-testid="custom-time-range-modal" className="space-y-5">
152
+ <div>
153
+ <div className="text-xs font-semibold uppercase tracking-wide text-gray-400">
154
+ Quick fill
155
+ </div>
156
+ <div className="mt-2 flex flex-wrap gap-2">
157
+ {QUICK_FILL_OPTIONS.map((option: QuickFillOption) => {
158
+ const isActive: boolean = isQuickFillActive(option.minutes);
159
+
160
+ return (
161
+ <button
162
+ key={option.label}
163
+ type="button"
164
+ aria-pressed={isActive}
165
+ onClick={() => {
166
+ onQuickFill(option.minutes);
167
+ }}
168
+ className={`rounded-full border px-3 py-1 text-xs font-medium transition-colors ${
169
+ isActive
170
+ ? "border-indigo-200 bg-indigo-50 text-indigo-700"
171
+ : "border-gray-200 bg-white text-gray-600 hover:border-gray-300 hover:bg-gray-50"
172
+ }`}
173
+ >
174
+ {option.label}
175
+ </button>
176
+ );
177
+ })}
178
+ </div>
179
+ </div>
180
+
181
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
182
+ <div className="rounded-lg border border-gray-200 bg-gray-50/60 p-3">
183
+ <label
184
+ htmlFor={startFieldId}
185
+ className="flex items-center gap-1.5 text-sm font-medium text-gray-900"
186
+ >
187
+ <Icon
188
+ icon={IconProp.Calendar}
189
+ className="h-4 w-4 text-gray-400"
190
+ />
191
+ From
192
+ </label>
193
+ <Input
194
+ id={startFieldId}
195
+ dataTestId="custom-time-range-start"
196
+ type={InputType.DATETIME_LOCAL}
197
+ showSecondsForDateTime={true}
198
+ placeholder="Start date & time"
199
+ value={startDate || ""}
200
+ onChange={(changedValue: string | Date) => {
201
+ setStartDate(toDate(changedValue));
202
+ }}
203
+ />
204
+ </div>
205
+
206
+ <div className="rounded-lg border border-gray-200 bg-gray-50/60 p-3">
207
+ <label
208
+ htmlFor={endFieldId}
209
+ className="flex items-center gap-1.5 text-sm font-medium text-gray-900"
210
+ >
211
+ <Icon
212
+ icon={IconProp.Calendar}
213
+ className="h-4 w-4 text-gray-400"
214
+ />
215
+ To
216
+ </label>
217
+ <Input
218
+ id={endFieldId}
219
+ dataTestId="custom-time-range-end"
220
+ type={InputType.DATETIME_LOCAL}
221
+ showSecondsForDateTime={true}
222
+ placeholder="End date & time"
223
+ value={endDate || ""}
224
+ onChange={(changedValue: string | Date) => {
225
+ setEndDate(toDate(changedValue));
226
+ }}
227
+ />
228
+ </div>
229
+ </div>
230
+
231
+ {isValid ? (
232
+ <div
233
+ data-testid="custom-time-range-summary"
234
+ className="rounded-lg border border-gray-200 bg-white p-3"
235
+ >
236
+ <div className="flex items-center justify-between text-sm">
237
+ <span className="text-gray-500">From</span>
238
+ <span className="font-medium text-gray-900">
239
+ {formatDateTime(startDate!)}
240
+ </span>
241
+ </div>
242
+ <div className="mt-2 flex items-center justify-between text-sm">
243
+ <span className="text-gray-500">To</span>
244
+ <span className="font-medium text-gray-900">
245
+ {formatDateTime(endDate!)}
246
+ </span>
247
+ </div>
248
+ <div className="mt-2 flex items-center justify-between border-t border-gray-100 pt-2 text-sm">
249
+ <span className="flex items-center gap-1.5 text-gray-500">
250
+ <Icon icon={IconProp.Clock} className="h-3.5 w-3.5" />
251
+ Duration
252
+ </span>
253
+ <span className="font-medium text-gray-900">{durationText}</span>
254
+ </div>
255
+ </div>
256
+ ) : (
257
+ <div
258
+ data-testid="custom-time-range-error"
259
+ role="alert"
260
+ className="flex items-start gap-2 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700"
261
+ >
262
+ <Icon
263
+ icon={IconProp.ErrorSolid}
264
+ className="mt-0.5 h-4 w-4 shrink-0 text-red-500"
265
+ />
266
+ <span>{error}</span>
267
+ </div>
268
+ )}
269
+
270
+ <div className="text-xs text-gray-400">
271
+ Times are shown in {OneUptimeDate.getCurrentTimezoneString()}.
272
+ </div>
273
+ </div>
274
+ </Modal>
275
+ );
276
+ };
277
+
278
+ export default CustomTimeRangeModal;