@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.
- package/Models/DatabaseModels/TelemetryEntity.ts +45 -0
- package/Models/DatabaseModels/TelemetryEntityRelationship.ts +26 -0
- package/Models/DatabaseModels/WorkflowLog.ts +38 -0
- package/Server/API/UserAPI.ts +16 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.ts +41 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/MasterAdminAuthorization.ts +11 -6
- package/Server/Services/TelemetryEntityRelationshipService.ts +3 -0
- package/Server/Services/TelemetryEntityService.ts +114 -1
- package/Server/Types/Workflow/Components/API/Utils.ts +44 -1
- package/Server/Types/Workflow/TriggerCode.ts +12 -0
- package/Server/Types/Workflow/Workflow.ts +5 -0
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +689 -0
- package/Server/Utils/VM/VMAPI.ts +56 -8
- package/Tests/App/Dashboard/MonitorCriteriaAttributeFilter.test.tsx +574 -0
- package/Tests/Server/API/UserProjectsAPI.test.ts +478 -4
- package/Tests/Server/Services/TelemetryEntityManualCreate.test.ts +248 -0
- package/Tests/Server/Types/Workflow/Components/ApiComponentHeaders.test.ts +192 -0
- package/Tests/Server/Utils/AnalyticsDatabase/QuerySettingsHelper.test.ts +152 -0
- package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +322 -0
- package/Tests/Server/Utils/VM/VMAPISubstitution.test.ts +243 -0
- package/Tests/Types/SerializableObjectDictionaryImportCycle.test.ts +197 -0
- package/Tests/Types/Telemetry/EntityTypeGroups.test.ts +140 -0
- package/Tests/Types/Workflow/StepTrace.test.ts +235 -0
- package/Tests/Types/Workflow/TemplateSyntax.test.ts +379 -0
- package/Tests/Types/Workflow/Templates.test.ts +403 -0
- package/Tests/UI/Components/CustomTimeRangeModal.test.tsx +459 -0
- package/Tests/UI/Components/DictionaryAttributeFilterRow.test.tsx +477 -0
- package/Tests/UI/Components/Forms/AnchoredFieldPopupKeyboard.test.tsx +382 -0
- package/Tests/UI/Components/Forms/ColorPickerPicking.test.tsx +569 -0
- package/Tests/UI/Components/Forms/ValidationJSON.test.ts +241 -0
- package/Tests/UI/Components/LogTimeRangePicker.test.tsx +42 -0
- package/Tests/UI/Components/ModalPortalDismissal.test.tsx +90 -0
- package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +49 -7
- package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +325 -0
- package/Tests/UI/Components/Workflow/GraphLint.test.ts +821 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditor.test.ts +287 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditorServerContract.test.ts +185 -0
- package/Tests/UI/Components/Workflow/ModelSchema.test.ts +174 -0
- package/Tests/UI/Components/Workflow/RunStatusWatcher.test.ts +152 -0
- package/Tests/UI/Components/Workflow/Utils.test.ts +192 -0
- package/Tests/UI/Monitor/MonitorStepCriteriaView.test.tsx +432 -0
- package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +514 -0
- package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +240 -0
- package/Tests/Utils/Schema/AnalyticsModelSchema.test.ts +469 -0
- package/Tests/Utils/Schema/ModelSchema.test.ts +268 -0
- package/Tests/Utils/Telemetry/EntityKeyNonTelemetry.test.ts +193 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +31 -0
- package/Types/SerializableObjectDictionary.ts +133 -39
- package/Types/Telemetry/EntitySource.ts +40 -0
- package/Types/Telemetry/EntityType.ts +27 -0
- package/Types/Telemetry/EntityTypeGroups.ts +65 -0
- package/Types/Workflow/Component.ts +29 -0
- package/Types/Workflow/Components/API.ts +35 -0
- package/Types/Workflow/Components/BaseModel.ts +7 -3
- package/Types/Workflow/StepTrace.ts +181 -0
- package/Types/Workflow/TemplateSyntax.ts +499 -0
- package/Types/Workflow/Templates.ts +316 -0
- package/UI/Components/Date/CustomTimeRangeModal.tsx +278 -0
- package/UI/Components/Date/TimeRangePickerDropdown.tsx +250 -0
- package/UI/Components/Dictionary/Dictionary.tsx +23 -6
- package/UI/Components/Forms/Fields/ColorPicker.tsx +66 -10
- package/UI/Components/Forms/Fields/IconPicker.tsx +48 -10
- package/UI/Components/Forms/Types/Field.ts +7 -0
- package/UI/Components/Forms/Validation.ts +63 -1
- package/UI/Components/Input/Input.tsx +31 -3
- package/UI/Components/LogsViewer/components/LogTimeRangePicker.tsx +11 -216
- package/UI/Components/Modal/Modal.tsx +37 -4
- package/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.tsx +11 -210
- package/UI/Components/Workflow/ArgumentsForm.tsx +194 -7
- package/UI/Components/Workflow/Component.tsx +28 -26
- package/UI/Components/Workflow/ComponentSettingsModal.tsx +69 -8
- package/UI/Components/Workflow/GraphLint.ts +591 -0
- package/UI/Components/Workflow/ModelColumnEditor.tsx +396 -0
- package/UI/Components/Workflow/ModelSchema.ts +196 -0
- package/UI/Components/Workflow/RunForm.tsx +41 -7
- package/UI/Components/Workflow/RunStatusWatcher.ts +122 -0
- package/UI/Components/Workflow/StepTraceViewer.tsx +186 -0
- package/UI/Components/Workflow/Utils.ts +93 -1
- package/UI/Components/Workflow/Workflow.tsx +77 -4
- package/UI/Types/LayeredDismissal.ts +31 -0
- package/UI/Types/UseAnchoredFieldPopup.ts +99 -0
- package/Utils/Monitor/NetworkDeviceRoleUtil.ts +533 -0
- package/Utils/Monitor/NetworkTopologyUtil.ts +45 -7
- package/Utils/Telemetry/EntityKey.ts +66 -0
- package/build/dist/Models/DatabaseModels/TelemetryEntity.js +47 -0
- package/build/dist/Models/DatabaseModels/TelemetryEntity.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js +27 -0
- package/build/dist/Models/DatabaseModels/TelemetryEntityRelationship.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowLog.js +39 -0
- package/build/dist/Models/DatabaseModels/WorkflowLog.js.map +1 -1
- package/build/dist/Server/API/UserAPI.js +16 -1
- package/build/dist/Server/API/UserAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786551733814-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786559879134-AddWorkflowLogStepTrace.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js +11 -6
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +3 -0
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryEntityService.js +91 -1
- package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Utils.js +28 -0
- package/build/dist/Server/Types/Workflow/Components/API/Utils.js.map +1 -1
- package/build/dist/Server/Types/Workflow/TriggerCode.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +507 -0
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -0
- package/build/dist/Server/Utils/VM/VMAPI.js +51 -4
- package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +133 -39
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/EntitySource.js +39 -0
- package/build/dist/Types/Telemetry/EntitySource.js.map +1 -0
- package/build/dist/Types/Telemetry/EntityType.js +27 -0
- package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
- package/build/dist/Types/Telemetry/EntityTypeGroups.js +57 -0
- package/build/dist/Types/Telemetry/EntityTypeGroups.js.map +1 -0
- package/build/dist/Types/Workflow/Component.js +21 -0
- package/build/dist/Types/Workflow/Component.js.map +1 -1
- package/build/dist/Types/Workflow/Components/API.js +35 -0
- package/build/dist/Types/Workflow/Components/API.js.map +1 -1
- package/build/dist/Types/Workflow/Components/BaseModel.js +7 -3
- package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
- package/build/dist/Types/Workflow/StepTrace.js +104 -0
- package/build/dist/Types/Workflow/StepTrace.js.map +1 -0
- package/build/dist/Types/Workflow/TemplateSyntax.js +326 -0
- package/build/dist/Types/Workflow/TemplateSyntax.js.map +1 -0
- package/build/dist/Types/Workflow/Templates.js +218 -0
- package/build/dist/Types/Workflow/Templates.js.map +1 -0
- package/build/dist/UI/Components/Date/CustomTimeRangeModal.js +126 -0
- package/build/dist/UI/Components/Date/CustomTimeRangeModal.js.map +1 -0
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +123 -0
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -0
- package/build/dist/UI/Components/Dictionary/Dictionary.js +22 -6
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js +52 -13
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js +28 -12
- package/build/dist/UI/Components/Forms/Fields/IconPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +44 -0
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +12 -4
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js +5 -108
- package/build/dist/UI/Components/LogsViewer/components/LogTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +31 -5
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js +5 -105
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryTimeRangePicker.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js +140 -11
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Component.js +20 -19
- package/build/dist/UI/Components/Workflow/Component.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js +31 -3
- package/build/dist/UI/Components/Workflow/ComponentSettingsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/GraphLint.js +396 -0
- package/build/dist/UI/Components/Workflow/GraphLint.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +225 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelSchema.js +118 -0
- package/build/dist/UI/Components/Workflow/ModelSchema.js.map +1 -0
- package/build/dist/UI/Components/Workflow/RunForm.js +22 -6
- package/build/dist/UI/Components/Workflow/RunForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/RunStatusWatcher.js +76 -0
- package/build/dist/UI/Components/Workflow/RunStatusWatcher.js.map +1 -0
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js +76 -0
- package/build/dist/UI/Components/Workflow/StepTraceViewer.js.map +1 -0
- package/build/dist/UI/Components/Workflow/Utils.js +73 -1
- package/build/dist/UI/Components/Workflow/Utils.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Workflow.js +51 -6
- package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
- package/build/dist/UI/Types/LayeredDismissal.js +21 -0
- package/build/dist/UI/Types/LayeredDismissal.js.map +1 -0
- package/build/dist/UI/Types/UseAnchoredFieldPopup.js +74 -1
- package/build/dist/UI/Types/UseAnchoredFieldPopup.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +386 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +35 -6
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/Telemetry/EntityKey.js +52 -0
- package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/** @timezone UTC */
|
|
2
|
+
import CustomTimeRangeModal, {
|
|
3
|
+
QUICK_FILL_OPTIONS,
|
|
4
|
+
getCustomTimeRangeError,
|
|
5
|
+
} from "../../../UI/Components/Date/CustomTimeRangeModal";
|
|
6
|
+
import InBetween from "../../../Types/BaseDatabase/InBetween";
|
|
7
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
8
|
+
import "@testing-library/jest-dom";
|
|
9
|
+
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
10
|
+
import React from "react";
|
|
11
|
+
import { afterEach, describe, expect, jest, test } from "@jest/globals";
|
|
12
|
+
|
|
13
|
+
const START: Date = new Date("2024-03-01T10:00:00.000Z");
|
|
14
|
+
const END: Date = new Date("2024-03-01T13:00:00.000Z");
|
|
15
|
+
|
|
16
|
+
interface RenderResult {
|
|
17
|
+
saved: Array<InBetween<Date>>;
|
|
18
|
+
closes: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function renderModalWith(
|
|
22
|
+
initialValue: InBetween<Date> | undefined,
|
|
23
|
+
): RenderResult {
|
|
24
|
+
const result: RenderResult = { saved: [], closes: 0 };
|
|
25
|
+
|
|
26
|
+
render(
|
|
27
|
+
<CustomTimeRangeModal
|
|
28
|
+
initialValue={initialValue}
|
|
29
|
+
onClose={() => {
|
|
30
|
+
result.closes++;
|
|
31
|
+
}}
|
|
32
|
+
onSave={(value: InBetween<Date>) => {
|
|
33
|
+
result.saved.push(value);
|
|
34
|
+
}}
|
|
35
|
+
/>,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function renderModal(
|
|
42
|
+
initialValue: InBetween<Date> = new InBetween<Date>(START, END),
|
|
43
|
+
): RenderResult {
|
|
44
|
+
return renderModalWith(initialValue);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getStartInput(): HTMLInputElement {
|
|
48
|
+
return screen.getByTestId("custom-time-range-start") as HTMLInputElement;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getEndInput(): HTMLInputElement {
|
|
52
|
+
return screen.getByTestId("custom-time-range-end") as HTMLInputElement;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/*
|
|
56
|
+
* jsdom normalises a `datetime-local` value to minute precision, so the raw
|
|
57
|
+
* string a field reports back is not comparable to what the component wrote
|
|
58
|
+
* into it. Reading the instant it names is, and these tests use whole minutes.
|
|
59
|
+
*/
|
|
60
|
+
function getFieldDate(input: HTMLInputElement): Date {
|
|
61
|
+
return OneUptimeDate.fromDateTimeLocalString(input.value);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getApplyButton(): HTMLButtonElement {
|
|
65
|
+
return screen.getByText("Apply").closest("button") as HTMLButtonElement;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function typeInto(input: HTMLInputElement, value: string): void {
|
|
69
|
+
fireEvent.change(input, { target: { value: value } });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
afterEach(() => {
|
|
73
|
+
cleanup();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("getCustomTimeRangeError", () => {
|
|
77
|
+
test("asks for a start when there is none", () => {
|
|
78
|
+
expect(getCustomTimeRangeError(null, END)).toBe(
|
|
79
|
+
"Pick a start date and time.",
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("asks for an end when there is none", () => {
|
|
84
|
+
expect(getCustomTimeRangeError(START, null)).toBe(
|
|
85
|
+
"Pick an end date and time.",
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("asks for a start first when both ends are missing", () => {
|
|
90
|
+
expect(getCustomTimeRangeError(null, null)).toBe(
|
|
91
|
+
"Pick a start date and time.",
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("rejects an inverted range", () => {
|
|
96
|
+
expect(getCustomTimeRangeError(END, START)).toBe(
|
|
97
|
+
"The end must be after the start. Adjust either end of the range.",
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("rejects a zero-length range", () => {
|
|
102
|
+
expect(getCustomTimeRangeError(START, new Date(START))).not.toBe("");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("accepts a range whose end is after its start", () => {
|
|
106
|
+
expect(getCustomTimeRangeError(START, END)).toBe("");
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("CustomTimeRangeModal", () => {
|
|
111
|
+
describe("rendering", () => {
|
|
112
|
+
test("renders inside a modal dialog", () => {
|
|
113
|
+
renderModal();
|
|
114
|
+
|
|
115
|
+
expect(screen.getByTestId("modal")).toBeInTheDocument();
|
|
116
|
+
expect(screen.getByTestId("custom-time-range-modal")).toBeInTheDocument();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("uses the default title and description", () => {
|
|
120
|
+
renderModal();
|
|
121
|
+
|
|
122
|
+
expect(screen.getByTestId("modal-title").textContent).toBe(
|
|
123
|
+
"Custom Time Range",
|
|
124
|
+
);
|
|
125
|
+
expect(screen.getByTestId("modal-description").textContent).toContain(
|
|
126
|
+
"exact start and end date",
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("lets the caller override the title and description", () => {
|
|
131
|
+
render(
|
|
132
|
+
<CustomTimeRangeModal
|
|
133
|
+
initialValue={new InBetween<Date>(START, END)}
|
|
134
|
+
title="Pick log window"
|
|
135
|
+
description="Only logs in this window are shown."
|
|
136
|
+
onClose={jest.fn()}
|
|
137
|
+
onSave={jest.fn()}
|
|
138
|
+
/>,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(screen.getByTestId("modal-title").textContent).toBe(
|
|
142
|
+
"Pick log window",
|
|
143
|
+
);
|
|
144
|
+
expect(screen.getByTestId("modal-description").textContent).toBe(
|
|
145
|
+
"Only logs in this window are shown.",
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("seeds both fields from the initial value", () => {
|
|
150
|
+
renderModal();
|
|
151
|
+
|
|
152
|
+
expect(getFieldDate(getStartInput()).getTime()).toBe(START.getTime());
|
|
153
|
+
expect(getFieldDate(getEndInput()).getTime()).toBe(END.getTime());
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("renders both fields with seconds enabled", () => {
|
|
157
|
+
renderModal();
|
|
158
|
+
|
|
159
|
+
expect(getStartInput().getAttribute("step")).toBe("1");
|
|
160
|
+
expect(getEndInput().getAttribute("step")).toBe("1");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("uses datetime-local fields so the browser supplies date and time", () => {
|
|
164
|
+
renderModal();
|
|
165
|
+
|
|
166
|
+
expect(getStartInput().getAttribute("type")).toBe("datetime-local");
|
|
167
|
+
expect(getEndInput().getAttribute("type")).toBe("datetime-local");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("labels both fields", () => {
|
|
171
|
+
renderModal();
|
|
172
|
+
|
|
173
|
+
expect(getStartInput()).toBe(screen.getByLabelText("From"));
|
|
174
|
+
expect(getEndInput()).toBe(screen.getByLabelText("To"));
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("starts empty when no initial value is given", () => {
|
|
178
|
+
renderModalWith(undefined);
|
|
179
|
+
|
|
180
|
+
expect(getStartInput().value).toBe("");
|
|
181
|
+
expect(getEndInput().value).toBe("");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("names the timezone the times are shown in", () => {
|
|
185
|
+
renderModal();
|
|
186
|
+
|
|
187
|
+
expect(
|
|
188
|
+
screen.getByText(
|
|
189
|
+
`Times are shown in ${OneUptimeDate.getCurrentTimezoneString()}.`,
|
|
190
|
+
),
|
|
191
|
+
).toBeInTheDocument();
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe("summary", () => {
|
|
196
|
+
test("summarises a valid window", () => {
|
|
197
|
+
renderModal();
|
|
198
|
+
|
|
199
|
+
const summary: HTMLElement = screen.getByTestId(
|
|
200
|
+
"custom-time-range-summary",
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
expect(summary.textContent).toContain(
|
|
204
|
+
OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(
|
|
205
|
+
START,
|
|
206
|
+
false,
|
|
207
|
+
true,
|
|
208
|
+
),
|
|
209
|
+
);
|
|
210
|
+
expect(summary.textContent).toContain(
|
|
211
|
+
OneUptimeDate.getDateAsUserFriendlyLocalFormattedString(
|
|
212
|
+
END,
|
|
213
|
+
false,
|
|
214
|
+
true,
|
|
215
|
+
),
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("shows the duration of the selected window", () => {
|
|
220
|
+
renderModal();
|
|
221
|
+
|
|
222
|
+
expect(
|
|
223
|
+
screen.getByTestId("custom-time-range-summary").textContent,
|
|
224
|
+
).toContain("3 hours");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("replaces the summary with an error when the window is invalid", () => {
|
|
228
|
+
renderModal(new InBetween<Date>(END, START));
|
|
229
|
+
|
|
230
|
+
expect(screen.queryByTestId("custom-time-range-summary")).toBeNull();
|
|
231
|
+
expect(screen.getByTestId("custom-time-range-error")).toBeInTheDocument();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("announces the error to assistive technology", () => {
|
|
235
|
+
renderModal(new InBetween<Date>(END, START));
|
|
236
|
+
|
|
237
|
+
expect(
|
|
238
|
+
screen.getByTestId("custom-time-range-error").getAttribute("role"),
|
|
239
|
+
).toBe("alert");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("recomputes the summary as the user edits a field", () => {
|
|
243
|
+
renderModal();
|
|
244
|
+
|
|
245
|
+
typeInto(getEndInput(), "2024-03-01T11:00:00");
|
|
246
|
+
|
|
247
|
+
expect(
|
|
248
|
+
screen.getByTestId("custom-time-range-summary").textContent,
|
|
249
|
+
).toContain("1 hour");
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe("quick fill", () => {
|
|
254
|
+
test("renders every quick fill option", () => {
|
|
255
|
+
renderModal();
|
|
256
|
+
|
|
257
|
+
for (const option of QUICK_FILL_OPTIONS) {
|
|
258
|
+
expect(screen.getByText(option.label)).toBeInTheDocument();
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("fills both ends of the range from a quick fill", () => {
|
|
263
|
+
renderModal();
|
|
264
|
+
|
|
265
|
+
fireEvent.click(screen.getByText("Last 24 hours"));
|
|
266
|
+
|
|
267
|
+
const start: Date = OneUptimeDate.fromDateTimeLocalString(
|
|
268
|
+
getStartInput().value,
|
|
269
|
+
);
|
|
270
|
+
const end: Date = OneUptimeDate.fromDateTimeLocalString(
|
|
271
|
+
getEndInput().value,
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
expect(OneUptimeDate.getDifferenceInMinutes(start, end)).toBe(60 * 24);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("does not apply the range on its own", () => {
|
|
278
|
+
const result: RenderResult = renderModal();
|
|
279
|
+
|
|
280
|
+
fireEvent.click(screen.getByText("Last 7 days"));
|
|
281
|
+
|
|
282
|
+
expect(result.saved).toHaveLength(0);
|
|
283
|
+
expect(result.closes).toBe(0);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("marks the matching quick fill as pressed", () => {
|
|
287
|
+
renderModal();
|
|
288
|
+
|
|
289
|
+
fireEvent.click(screen.getByText("Last 6 hours"));
|
|
290
|
+
|
|
291
|
+
expect(
|
|
292
|
+
screen.getByText("Last 6 hours").getAttribute("aria-pressed"),
|
|
293
|
+
).toBe("true");
|
|
294
|
+
expect(screen.getByText("Last 1 hour").getAttribute("aria-pressed")).toBe(
|
|
295
|
+
"false",
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("leaves every quick fill unpressed for a window none of them match", () => {
|
|
300
|
+
// The seeded window is three hours, which is not an offered shortcut.
|
|
301
|
+
renderModal();
|
|
302
|
+
|
|
303
|
+
for (const option of QUICK_FILL_OPTIONS) {
|
|
304
|
+
expect(
|
|
305
|
+
screen.getByText(option.label).getAttribute("aria-pressed"),
|
|
306
|
+
).toBe("false");
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("recovers from an invalid window", () => {
|
|
311
|
+
renderModal(new InBetween<Date>(END, START));
|
|
312
|
+
|
|
313
|
+
expect(getApplyButton().hasAttribute("disabled")).toBe(true);
|
|
314
|
+
|
|
315
|
+
fireEvent.click(screen.getByText("Last 1 hour"));
|
|
316
|
+
|
|
317
|
+
expect(getApplyButton().hasAttribute("disabled")).toBe(false);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe("editing", () => {
|
|
322
|
+
test("keeps edits local until Apply is pressed", () => {
|
|
323
|
+
const result: RenderResult = renderModal();
|
|
324
|
+
|
|
325
|
+
typeInto(getStartInput(), "2024-03-01T09:00:00");
|
|
326
|
+
typeInto(getEndInput(), "2024-03-01T12:00:00");
|
|
327
|
+
|
|
328
|
+
/*
|
|
329
|
+
* The old inline editor pushed every keystroke straight at the parent,
|
|
330
|
+
* which re-queried the backend for half-typed dates.
|
|
331
|
+
*/
|
|
332
|
+
expect(result.saved).toHaveLength(0);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("applies the edited window", () => {
|
|
336
|
+
const result: RenderResult = renderModal();
|
|
337
|
+
|
|
338
|
+
typeInto(getStartInput(), "2024-03-01T09:30:00");
|
|
339
|
+
fireEvent.click(getApplyButton());
|
|
340
|
+
|
|
341
|
+
expect(result.saved).toHaveLength(1);
|
|
342
|
+
expect(
|
|
343
|
+
OneUptimeDate.toDateTimeLocalString(result.saved[0]!.startValue),
|
|
344
|
+
).toBe("2024-03-01T09:30:00");
|
|
345
|
+
expect(
|
|
346
|
+
OneUptimeDate.toDateTimeLocalString(result.saved[0]!.endValue),
|
|
347
|
+
).toBe(OneUptimeDate.toDateTimeLocalString(END));
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("hands back an InBetween of dates", () => {
|
|
351
|
+
const result: RenderResult = renderModal();
|
|
352
|
+
|
|
353
|
+
fireEvent.click(getApplyButton());
|
|
354
|
+
|
|
355
|
+
expect(result.saved[0]).toBeInstanceOf(InBetween);
|
|
356
|
+
expect(result.saved[0]!.startValue).toBeInstanceOf(Date);
|
|
357
|
+
expect(result.saved[0]!.endValue).toBeInstanceOf(Date);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test("treats a cleared field as missing", () => {
|
|
361
|
+
renderModal();
|
|
362
|
+
|
|
363
|
+
typeInto(getStartInput(), "");
|
|
364
|
+
|
|
365
|
+
expect(screen.getByTestId("custom-time-range-error").textContent).toBe(
|
|
366
|
+
"Pick a start date and time.",
|
|
367
|
+
);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("re-enables Apply once a cleared field is filled back in", () => {
|
|
371
|
+
renderModal();
|
|
372
|
+
|
|
373
|
+
typeInto(getEndInput(), "");
|
|
374
|
+
expect(getApplyButton().hasAttribute("disabled")).toBe(true);
|
|
375
|
+
|
|
376
|
+
typeInto(getEndInput(), "2024-03-01T18:00:00");
|
|
377
|
+
expect(getApplyButton().hasAttribute("disabled")).toBe(false);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("resolves typed wall-clock times in the configured timezone", () => {
|
|
381
|
+
const result: RenderResult = renderModal();
|
|
382
|
+
|
|
383
|
+
typeInto(getStartInput(), "2024-03-01T09:15:00");
|
|
384
|
+
typeInto(getEndInput(), "2024-03-01T09:45:00");
|
|
385
|
+
fireEvent.click(getApplyButton());
|
|
386
|
+
|
|
387
|
+
expect(result.saved[0]!.startValue.getTime()).toBe(
|
|
388
|
+
OneUptimeDate.fromDateTimeLocalString("2024-03-01T09:15:00").getTime(),
|
|
389
|
+
);
|
|
390
|
+
expect(result.saved[0]!.endValue.getTime()).toBe(
|
|
391
|
+
OneUptimeDate.fromDateTimeLocalString("2024-03-01T09:45:00").getTime(),
|
|
392
|
+
);
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
describe("apply and cancel", () => {
|
|
397
|
+
test("disables Apply while the range is inverted", () => {
|
|
398
|
+
renderModal();
|
|
399
|
+
|
|
400
|
+
typeInto(getEndInput(), "2024-03-01T08:00:00");
|
|
401
|
+
|
|
402
|
+
expect(getApplyButton().hasAttribute("disabled")).toBe(true);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test("does not save an invalid range even if Apply is invoked", () => {
|
|
406
|
+
const result: RenderResult = renderModal(new InBetween<Date>(END, START));
|
|
407
|
+
|
|
408
|
+
fireEvent.click(getApplyButton());
|
|
409
|
+
|
|
410
|
+
expect(result.saved).toHaveLength(0);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test("enables Apply for a valid seeded range", () => {
|
|
414
|
+
renderModal();
|
|
415
|
+
|
|
416
|
+
expect(getApplyButton().hasAttribute("disabled")).toBe(false);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test("closes without saving when Cancel is pressed", () => {
|
|
420
|
+
const result: RenderResult = renderModal();
|
|
421
|
+
|
|
422
|
+
fireEvent.click(screen.getByText("Cancel"));
|
|
423
|
+
|
|
424
|
+
expect(result.closes).toBe(1);
|
|
425
|
+
expect(result.saved).toHaveLength(0);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test("closes without saving when the close button is pressed", () => {
|
|
429
|
+
const result: RenderResult = renderModal();
|
|
430
|
+
|
|
431
|
+
fireEvent.click(screen.getByTestId("close-button"));
|
|
432
|
+
|
|
433
|
+
expect(result.closes).toBe(1);
|
|
434
|
+
expect(result.saved).toHaveLength(0);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test("closes without saving on Escape", () => {
|
|
438
|
+
const result: RenderResult = renderModal();
|
|
439
|
+
|
|
440
|
+
fireEvent.keyDown(document, { key: "Escape" });
|
|
441
|
+
|
|
442
|
+
expect(result.closes).toBe(1);
|
|
443
|
+
expect(result.saved).toHaveLength(0);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test("does not close itself when the range is applied", () => {
|
|
447
|
+
/*
|
|
448
|
+
* Teardown is the caller's job — it owns the mount, and closing here as
|
|
449
|
+
* well would fire onClose alongside onSave.
|
|
450
|
+
*/
|
|
451
|
+
const result: RenderResult = renderModal();
|
|
452
|
+
|
|
453
|
+
fireEvent.click(getApplyButton());
|
|
454
|
+
|
|
455
|
+
expect(result.saved).toHaveLength(1);
|
|
456
|
+
expect(result.closes).toBe(0);
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
});
|