@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,325 @@
1
+ /** @timezone UTC */
2
+ import TimeRangePickerDropdown, {
3
+ CUSTOM_RANGE_OPTION_LABEL,
4
+ TIME_RANGE_PRESET_OPTIONS,
5
+ getTimeRangeButtonLabel,
6
+ } from "../../../UI/Components/Date/TimeRangePickerDropdown";
7
+ import InBetween from "../../../Types/BaseDatabase/InBetween";
8
+ import OneUptimeDate from "../../../Types/Date";
9
+ import RangeStartAndEndDateTime from "../../../Types/Time/RangeStartAndEndDateTime";
10
+ import TimeRange from "../../../Types/Time/TimeRange";
11
+ import "@testing-library/jest-dom";
12
+ import { cleanup, fireEvent, render, screen } from "@testing-library/react";
13
+ import React from "react";
14
+ import { afterEach, describe, expect, test } from "@jest/globals";
15
+
16
+ const PREFIX: string = "test-range-picker";
17
+ const DROPDOWN_WIDTH_IN_PX: number = 288;
18
+
19
+ interface Harness {
20
+ emitted: Array<RangeStartAndEndDateTime>;
21
+ }
22
+
23
+ function renderPicker(value: RangeStartAndEndDateTime): Harness {
24
+ const harness: Harness = { emitted: [] };
25
+
26
+ render(
27
+ <TimeRangePickerDropdown
28
+ value={value}
29
+ onChange={(next: RangeStartAndEndDateTime) => {
30
+ harness.emitted.push(next);
31
+ }}
32
+ dataTestIdPrefix={PREFIX}
33
+ dropdownWidthInPx={DROPDOWN_WIDTH_IN_PX}
34
+ />,
35
+ );
36
+
37
+ return harness;
38
+ }
39
+
40
+ function openDropdown(): void {
41
+ fireEvent.click(screen.getByTestId(`${PREFIX}-button`));
42
+ }
43
+
44
+ function openCustomModal(): void {
45
+ openDropdown();
46
+ fireEvent.click(screen.getByTestId(`${PREFIX}-custom-option`));
47
+ }
48
+
49
+ function getStartInput(): HTMLInputElement {
50
+ return screen.getByTestId("custom-time-range-start") as HTMLInputElement;
51
+ }
52
+
53
+ function getEndInput(): HTMLInputElement {
54
+ return screen.getByTestId("custom-time-range-end") as HTMLInputElement;
55
+ }
56
+
57
+ /*
58
+ * jsdom normalises a `datetime-local` value to minute precision, so compare
59
+ * the instant a field names rather than the string it reports.
60
+ */
61
+ function getFieldDate(input: HTMLInputElement): Date {
62
+ return OneUptimeDate.fromDateTimeLocalString(input.value);
63
+ }
64
+
65
+ function minutesBetweenFields(): number {
66
+ return OneUptimeDate.getDifferenceInMinutes(
67
+ getFieldDate(getStartInput()),
68
+ getFieldDate(getEndInput()),
69
+ );
70
+ }
71
+
72
+ afterEach(() => {
73
+ cleanup();
74
+ });
75
+
76
+ describe("getTimeRangeButtonLabel", () => {
77
+ test("uses the preset label for a preset range", () => {
78
+ expect(getTimeRangeButtonLabel({ range: TimeRange.PAST_THIRTY_MINS })).toBe(
79
+ "Past 30 Minutes",
80
+ );
81
+ });
82
+
83
+ test("falls back to the raw range when there is no preset label", () => {
84
+ expect(getTimeRangeButtonLabel({ range: TimeRange.CUSTOM })).toBe(
85
+ TimeRange.CUSTOM,
86
+ );
87
+ });
88
+
89
+ test("summarises a custom range as a start–end pair", () => {
90
+ const label: string = getTimeRangeButtonLabel({
91
+ range: TimeRange.CUSTOM,
92
+ startAndEndDate: new InBetween<Date>(
93
+ new Date("2024-03-01T10:00:00.000Z"),
94
+ new Date("2024-03-02T13:30:00.000Z"),
95
+ ),
96
+ });
97
+
98
+ expect(label).toBe("Mar 1, 10:00 – Mar 2, 13:30");
99
+ });
100
+ });
101
+
102
+ describe("TimeRangePickerDropdown", () => {
103
+ describe("test ids", () => {
104
+ test("namespaces every test id with the given prefix", () => {
105
+ renderPicker({ range: TimeRange.PAST_ONE_HOUR });
106
+
107
+ expect(screen.getByTestId(`${PREFIX}-button`)).toBeInTheDocument();
108
+
109
+ openDropdown();
110
+
111
+ expect(screen.getByTestId(`${PREFIX}-dropdown`)).toBeInTheDocument();
112
+ expect(screen.getByTestId(`${PREFIX}-custom-option`)).toBeInTheDocument();
113
+ });
114
+ });
115
+
116
+ describe("presets", () => {
117
+ test("lists every preset plus the custom entry", () => {
118
+ renderPicker({ range: TimeRange.PAST_ONE_HOUR });
119
+ openDropdown();
120
+
121
+ const labels: Array<string> = Array.from(
122
+ screen.getByTestId(`${PREFIX}-dropdown`).querySelectorAll("button"),
123
+ ).map((button: HTMLButtonElement): string => {
124
+ return (button.textContent || "").trim();
125
+ });
126
+
127
+ expect(labels).toEqual([
128
+ ...TIME_RANGE_PRESET_OPTIONS.map(
129
+ (option: { label: string }): string => {
130
+ return option.label;
131
+ },
132
+ ),
133
+ CUSTOM_RANGE_OPTION_LABEL,
134
+ ]);
135
+ });
136
+
137
+ test("emits a preset without any absolute window attached", () => {
138
+ const harness: Harness = renderPicker({ range: TimeRange.PAST_ONE_HOUR });
139
+ openDropdown();
140
+
141
+ fireEvent.click(screen.getByText("Past 2 Days"));
142
+
143
+ expect(harness.emitted).toEqual([{ range: TimeRange.PAST_TWO_DAYS }]);
144
+ });
145
+
146
+ test("closes the dropdown after picking a preset", () => {
147
+ renderPicker({ range: TimeRange.PAST_ONE_HOUR });
148
+ openDropdown();
149
+
150
+ fireEvent.click(screen.getByText("Past 2 Days"));
151
+
152
+ expect(screen.queryByTestId(`${PREFIX}-dropdown`)).toBeNull();
153
+ });
154
+
155
+ test("switching back to a preset from a custom range drops the window", () => {
156
+ const harness: Harness = renderPicker({
157
+ range: TimeRange.CUSTOM,
158
+ startAndEndDate: new InBetween<Date>(
159
+ new Date("2024-03-01T10:00:00.000Z"),
160
+ new Date("2024-03-01T13:00:00.000Z"),
161
+ ),
162
+ });
163
+ openDropdown();
164
+
165
+ fireEvent.click(screen.getByText("Past 1 Hour"));
166
+
167
+ expect(harness.emitted).toEqual([{ range: TimeRange.PAST_ONE_HOUR }]);
168
+ });
169
+ });
170
+
171
+ describe("custom range modal", () => {
172
+ test("is not mounted until the custom entry is clicked", () => {
173
+ renderPicker({ range: TimeRange.PAST_ONE_HOUR });
174
+ openDropdown();
175
+
176
+ expect(screen.queryByTestId("custom-time-range-modal")).toBeNull();
177
+ });
178
+
179
+ test("opens on the custom entry and closes the dropdown behind it", () => {
180
+ renderPicker({ range: TimeRange.PAST_ONE_HOUR });
181
+ openCustomModal();
182
+
183
+ expect(screen.getByTestId("custom-time-range-modal")).toBeInTheDocument();
184
+ expect(screen.queryByTestId(`${PREFIX}-dropdown`)).toBeNull();
185
+ });
186
+
187
+ test("seeds from the absolute bounds of the selected preset", () => {
188
+ renderPicker({ range: TimeRange.PAST_THREE_HOURS });
189
+ openCustomModal();
190
+
191
+ expect(minutesBetweenFields()).toBe(180);
192
+ });
193
+
194
+ test("seeds from the active custom range when there is one", () => {
195
+ const start: Date = new Date("2024-03-01T10:00:00.000Z");
196
+ const end: Date = new Date("2024-03-01T13:00:00.000Z");
197
+
198
+ renderPicker({
199
+ range: TimeRange.CUSTOM,
200
+ startAndEndDate: new InBetween<Date>(start, end),
201
+ });
202
+ openCustomModal();
203
+
204
+ expect(getFieldDate(getStartInput()).getTime()).toBe(start.getTime());
205
+ expect(getFieldDate(getEndInput()).getTime()).toBe(end.getTime());
206
+ });
207
+
208
+ test("falls back to the preset bounds when a custom range has no window", () => {
209
+ renderPicker({ range: TimeRange.CUSTOM });
210
+ openCustomModal();
211
+
212
+ expect(getStartInput().value).not.toBe("");
213
+ expect(getEndInput().value).not.toBe("");
214
+ });
215
+
216
+ test("emits a custom range when the modal is applied", () => {
217
+ const harness: Harness = renderPicker({ range: TimeRange.PAST_ONE_HOUR });
218
+ openCustomModal();
219
+
220
+ fireEvent.change(getStartInput(), {
221
+ target: { value: "2024-03-01T08:00:00" },
222
+ });
223
+ fireEvent.change(getEndInput(), {
224
+ target: { value: "2024-03-01T09:30:00" },
225
+ });
226
+ fireEvent.click(screen.getByText("Apply"));
227
+
228
+ expect(harness.emitted).toHaveLength(1);
229
+ expect(harness.emitted[0]!.range).toBe(TimeRange.CUSTOM);
230
+ expect(
231
+ OneUptimeDate.toDateTimeLocalString(
232
+ harness.emitted[0]!.startAndEndDate!.startValue,
233
+ ),
234
+ ).toBe("2024-03-01T08:00:00");
235
+ expect(
236
+ OneUptimeDate.toDateTimeLocalString(
237
+ harness.emitted[0]!.startAndEndDate!.endValue,
238
+ ),
239
+ ).toBe("2024-03-01T09:30:00");
240
+ });
241
+
242
+ test("unmounts the modal once a range is applied", () => {
243
+ renderPicker({ range: TimeRange.PAST_ONE_HOUR });
244
+ openCustomModal();
245
+
246
+ fireEvent.click(screen.getByText("Apply"));
247
+
248
+ expect(screen.queryByTestId("custom-time-range-modal")).toBeNull();
249
+ });
250
+
251
+ test("emits nothing when the modal is cancelled", () => {
252
+ const harness: Harness = renderPicker({ range: TimeRange.PAST_ONE_HOUR });
253
+ openCustomModal();
254
+
255
+ fireEvent.change(getStartInput(), {
256
+ target: { value: "2024-03-01T08:00:00" },
257
+ });
258
+ fireEvent.click(screen.getByText("Cancel"));
259
+
260
+ expect(harness.emitted).toHaveLength(0);
261
+ expect(screen.queryByTestId("custom-time-range-modal")).toBeNull();
262
+ });
263
+
264
+ test("discards abandoned edits when reopened", () => {
265
+ renderPicker({
266
+ range: TimeRange.CUSTOM,
267
+ startAndEndDate: new InBetween<Date>(
268
+ new Date("2024-03-01T10:00:00.000Z"),
269
+ new Date("2024-03-01T13:00:00.000Z"),
270
+ ),
271
+ });
272
+
273
+ openCustomModal();
274
+ fireEvent.change(getStartInput(), {
275
+ target: { value: "2024-03-01T01:00:00" },
276
+ });
277
+ fireEvent.click(screen.getByText("Cancel"));
278
+
279
+ openCustomModal();
280
+
281
+ expect(getFieldDate(getStartInput()).getTime()).toBe(
282
+ new Date("2024-03-01T10:00:00.000Z").getTime(),
283
+ );
284
+ });
285
+
286
+ test("re-seeds from the preset selected since it was last opened", () => {
287
+ renderPicker({ range: TimeRange.PAST_THREE_HOURS });
288
+
289
+ openCustomModal();
290
+ expect(minutesBetweenFields()).toBe(180);
291
+ fireEvent.click(screen.getByText("Cancel"));
292
+
293
+ cleanup();
294
+ renderPicker({ range: TimeRange.PAST_ONE_DAY });
295
+ openCustomModal();
296
+
297
+ expect(minutesBetweenFields()).toBe(1440);
298
+ });
299
+ });
300
+
301
+ describe("click outside", () => {
302
+ test("closes the dropdown on an outside mousedown", () => {
303
+ renderPicker({ range: TimeRange.PAST_ONE_HOUR });
304
+ openDropdown();
305
+
306
+ fireEvent.mouseDown(document.body);
307
+
308
+ expect(screen.queryByTestId(`${PREFIX}-dropdown`)).toBeNull();
309
+ });
310
+
311
+ test("leaves the custom modal alone on an outside mousedown", () => {
312
+ /*
313
+ * The native date picker a `datetime-local` field opens renders outside
314
+ * this component. While the editor lived in the dropdown, clicking a day
315
+ * in that popup tore the whole panel down mid-edit.
316
+ */
317
+ renderPicker({ range: TimeRange.PAST_ONE_HOUR });
318
+ openCustomModal();
319
+
320
+ fireEvent.mouseDown(document.body);
321
+
322
+ expect(screen.getByTestId("custom-time-range-modal")).toBeInTheDocument();
323
+ });
324
+ });
325
+ });