@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,241 @@
|
|
|
1
|
+
import Validation from "../../../../UI/Components/Forms/Validation";
|
|
2
|
+
import Field from "../../../../UI/Components/Forms/Types/Field";
|
|
3
|
+
import FormFieldSchemaType from "../../../../UI/Components/Forms/Types/FormFieldSchemaType";
|
|
4
|
+
import FormValues from "../../../../UI/Components/Forms/Types/FormValues";
|
|
5
|
+
import Dictionary from "../../../../Types/Dictionary";
|
|
6
|
+
import { JSONObject, JSONValue } from "../../../../Types/JSON";
|
|
7
|
+
import { describe, expect, test } from "@jest/globals";
|
|
8
|
+
|
|
9
|
+
interface TestEntity extends JSONObject {
|
|
10
|
+
config?: JSONValue;
|
|
11
|
+
name?: JSONValue;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type MakeFieldFunction = (
|
|
15
|
+
overrides?: Partial<Field<TestEntity>>,
|
|
16
|
+
) => Field<TestEntity>;
|
|
17
|
+
|
|
18
|
+
const makeJSONField: MakeFieldFunction = (
|
|
19
|
+
overrides?: Partial<Field<TestEntity>>,
|
|
20
|
+
): Field<TestEntity> => {
|
|
21
|
+
return {
|
|
22
|
+
title: "Config",
|
|
23
|
+
name: "config",
|
|
24
|
+
field: { config: true },
|
|
25
|
+
fieldType: FormFieldSchemaType.JSON,
|
|
26
|
+
...overrides,
|
|
27
|
+
} as Field<TestEntity>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type ValidateValueFunction = (
|
|
31
|
+
value: unknown,
|
|
32
|
+
field?: Field<TestEntity> | undefined,
|
|
33
|
+
) => string | null;
|
|
34
|
+
|
|
35
|
+
const validateValue: ValidateValueFunction = (
|
|
36
|
+
value: unknown,
|
|
37
|
+
field?: Field<TestEntity> | undefined,
|
|
38
|
+
): string | null => {
|
|
39
|
+
return Validation.validateJSONSyntax(
|
|
40
|
+
value as never,
|
|
41
|
+
field || makeJSONField(),
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
describe("Validation.validateJSONSyntax — only acts on JSON fields", () => {
|
|
46
|
+
test("ignores every other field type", () => {
|
|
47
|
+
const textField: Field<TestEntity> = makeJSONField({
|
|
48
|
+
fieldType: FormFieldSchemaType.Text,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(validateValue("definitely not json", textField)).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("names the field in the message", () => {
|
|
55
|
+
const message: string | null = validateValue('{"a":1,}');
|
|
56
|
+
|
|
57
|
+
expect(message).toMatch(/^Config is not valid JSON\./);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("Validation.validateJSONSyntax — catches the real mistakes", () => {
|
|
62
|
+
test("a trailing comma", () => {
|
|
63
|
+
expect(validateValue('{"a": 1,}')).toBeTruthy();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("an unclosed brace", () => {
|
|
67
|
+
expect(validateValue('{"a": 1')).toBeTruthy();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("unquoted keys and single quotes", () => {
|
|
71
|
+
expect(validateValue("{a: 1}")).toBeTruthy();
|
|
72
|
+
expect(validateValue("{'a': 1}")).toBeTruthy();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("plain text in a JSON box", () => {
|
|
76
|
+
expect(validateValue("hello")).toBeTruthy();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("Validation.validateJSONSyntax — never fires on a working value", () => {
|
|
81
|
+
test("well-formed JSON", () => {
|
|
82
|
+
expect(validateValue('{"a": 1}')).toBeNull();
|
|
83
|
+
expect(validateValue("[1,2,3]")).toBeNull();
|
|
84
|
+
expect(validateValue('"a string"')).toBeNull();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
/*
|
|
88
|
+
* The values that made a naive JSON.parse check unshippable. Each is a real
|
|
89
|
+
* thing a workflow author writes.
|
|
90
|
+
*/
|
|
91
|
+
test("a whole-field template", () => {
|
|
92
|
+
expect(
|
|
93
|
+
validateValue(
|
|
94
|
+
"{{local.components.api-get-1.returnValues.response-body}}",
|
|
95
|
+
),
|
|
96
|
+
).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("a template standing in for a bare value", () => {
|
|
100
|
+
expect(validateValue('{"retries": {{local.variables.count}}}')).toBeNull();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("a template inside a string", () => {
|
|
104
|
+
expect(
|
|
105
|
+
validateValue('{"auth": "Bearer {{local.variables.token}}"}'),
|
|
106
|
+
).toBeNull();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("a loop, whose shape is only known at run time", () => {
|
|
110
|
+
expect(
|
|
111
|
+
validateValue(
|
|
112
|
+
'[{{#each local.components.a.returnValues.items}}{"id":"{{this}}"},{{/each}}]',
|
|
113
|
+
),
|
|
114
|
+
).toBeNull();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
/*
|
|
118
|
+
* The seven model-backed forms elsewhere in the product bind real arrays and
|
|
119
|
+
* objects to a JSON field. Those must never be judged as text — this is the
|
|
120
|
+
* regression the raw-value plumbing exists to prevent.
|
|
121
|
+
*/
|
|
122
|
+
test("an already-parsed object", () => {
|
|
123
|
+
expect(validateValue({ a: 1 })).toBeNull();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("an already-parsed array", () => {
|
|
127
|
+
expect(validateValue(["https://a.example.com"])).toBeNull();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("empty, so the required-field message stands instead", () => {
|
|
131
|
+
expect(validateValue("")).toBeNull();
|
|
132
|
+
expect(validateValue(" ")).toBeNull();
|
|
133
|
+
expect(validateValue(undefined)).toBeNull();
|
|
134
|
+
expect(validateValue(null)).toBeNull();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("Validation.validateJSONSyntax — allowJSON5", () => {
|
|
139
|
+
/*
|
|
140
|
+
* Some workflow argument types are read back with JSONFunctions.parse, which
|
|
141
|
+
* is JSON5. Judging those by JSON's rules would report a mistake in a
|
|
142
|
+
* workflow that runs perfectly well.
|
|
143
|
+
*/
|
|
144
|
+
const looseField: Field<TestEntity> = makeJSONField({ allowJSON5: true });
|
|
145
|
+
|
|
146
|
+
test("accepts unquoted keys, single quotes and a trailing comma", () => {
|
|
147
|
+
expect(validateValue("{a: 1}", looseField)).toBeNull();
|
|
148
|
+
expect(validateValue("{'a': 1}", looseField)).toBeNull();
|
|
149
|
+
expect(validateValue('{"a": 1,}', looseField)).toBeNull();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("still rejects text that is not parseable at all", () => {
|
|
153
|
+
expect(validateValue("{oops", looseField)).toBeTruthy();
|
|
154
|
+
expect(validateValue("hello", looseField)).toBeTruthy();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("is off by default, so JSON fields stay strict", () => {
|
|
158
|
+
expect(validateValue('{"a": 1,}')).toBeTruthy();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe("Validation.validate — end to end through the form validator", () => {
|
|
163
|
+
type RunValidateFunction = (
|
|
164
|
+
values: FormValues<TestEntity>,
|
|
165
|
+
fields: Array<Field<TestEntity>>,
|
|
166
|
+
) => Dictionary<string>;
|
|
167
|
+
|
|
168
|
+
const runValidate: RunValidateFunction = (
|
|
169
|
+
values: FormValues<TestEntity>,
|
|
170
|
+
fields: Array<Field<TestEntity>>,
|
|
171
|
+
): Dictionary<string> => {
|
|
172
|
+
return Validation.validate<TestEntity>({
|
|
173
|
+
formFields: fields,
|
|
174
|
+
values: values,
|
|
175
|
+
onValidate: undefined,
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
test("surfaces a JSON error against the field name", () => {
|
|
180
|
+
const errors: Dictionary<string> = runValidate(
|
|
181
|
+
{ config: '{"a":1,}' } as FormValues<TestEntity>,
|
|
182
|
+
[makeJSONField()],
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
expect(errors["config"]).toMatch(/not valid JSON/);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("says required, not malformed, when the box is empty", () => {
|
|
189
|
+
const errors: Dictionary<string> = runValidate(
|
|
190
|
+
{ config: "" } as FormValues<TestEntity>,
|
|
191
|
+
[makeJSONField({ required: true })],
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
expect(errors["config"]).toMatch(/required/i);
|
|
195
|
+
expect(errors["config"]).not.toMatch(/not valid JSON/);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("does not flag an object bound to a JSON field", () => {
|
|
199
|
+
const errors: Dictionary<string> = runValidate(
|
|
200
|
+
{ config: { nested: { deep: true } } } as FormValues<TestEntity>,
|
|
201
|
+
[makeJSONField()],
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
expect(errors["config"]).toBeUndefined();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("does not flag an array bound to a JSON field", () => {
|
|
208
|
+
const errors: Dictionary<string> = runValidate(
|
|
209
|
+
{
|
|
210
|
+
config: ["https://a.example.com", "https://b.example.com"],
|
|
211
|
+
} as FormValues<TestEntity>,
|
|
212
|
+
[makeJSONField()],
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
expect(errors["config"]).toBeUndefined();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("leaves a hidden field alone", () => {
|
|
219
|
+
const errors: Dictionary<string> = runValidate(
|
|
220
|
+
{ config: "{oops" } as FormValues<TestEntity>,
|
|
221
|
+
[
|
|
222
|
+
makeJSONField({
|
|
223
|
+
showIf: (): boolean => {
|
|
224
|
+
return false;
|
|
225
|
+
},
|
|
226
|
+
}),
|
|
227
|
+
],
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
expect(errors["config"]).toBeUndefined();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("reports nothing for a valid form", () => {
|
|
234
|
+
const errors: Dictionary<string> = runValidate(
|
|
235
|
+
{ config: '{"a": 1}' } as FormValues<TestEntity>,
|
|
236
|
+
[makeJSONField()],
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
expect(Object.keys(errors)).toHaveLength(0);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
@@ -3,6 +3,7 @@ import LogTimeRangePicker, {
|
|
|
3
3
|
} from "../../../UI/Components/LogsViewer/components/LogTimeRangePicker";
|
|
4
4
|
import RangeStartAndEndDateTime from "../../../Types/Time/RangeStartAndEndDateTime";
|
|
5
5
|
import TimeRange from "../../../Types/Time/TimeRange";
|
|
6
|
+
import InBetween from "../../../Types/BaseDatabase/InBetween";
|
|
6
7
|
import "@testing-library/jest-dom";
|
|
7
8
|
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
8
9
|
import React from "react";
|
|
@@ -135,4 +136,45 @@ describe("LogTimeRangePicker", () => {
|
|
|
135
136
|
dropdown.parentElement?.classList.contains("relative") ?? false,
|
|
136
137
|
).toBe(true);
|
|
137
138
|
});
|
|
139
|
+
|
|
140
|
+
test("keeps the date fields out of the narrow dropdown panel", () => {
|
|
141
|
+
const dropdown: HTMLElement = openDropdown({
|
|
142
|
+
range: TimeRange.CUSTOM,
|
|
143
|
+
startAndEndDate: new InBetween<Date>(
|
|
144
|
+
new Date("2024-01-01T00:00:00.000Z"),
|
|
145
|
+
new Date("2024-01-02T00:00:00.000Z"),
|
|
146
|
+
),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(dropdown.querySelectorAll("input").length).toBe(0);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("opens the custom range modal from the dropdown", () => {
|
|
153
|
+
openDropdown({ range: TimeRange.PAST_ONE_HOUR });
|
|
154
|
+
|
|
155
|
+
fireEvent.click(screen.getByTestId("log-time-range-picker-custom-option"));
|
|
156
|
+
|
|
157
|
+
expect(screen.getByTestId("custom-time-range-modal")).toBeInTheDocument();
|
|
158
|
+
expect(screen.queryByTestId(DROPDOWN_TEST_ID)).toBeNull();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("applies a custom range picked in the modal", () => {
|
|
162
|
+
const emitted: Array<RangeStartAndEndDateTime> = [];
|
|
163
|
+
render(
|
|
164
|
+
<LogTimeRangePicker
|
|
165
|
+
value={{ range: TimeRange.PAST_ONE_HOUR }}
|
|
166
|
+
onChange={(value: RangeStartAndEndDateTime) => {
|
|
167
|
+
emitted.push(value);
|
|
168
|
+
}}
|
|
169
|
+
/>,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
fireEvent.click(screen.getByTestId(BUTTON_TEST_ID));
|
|
173
|
+
fireEvent.click(screen.getByTestId("log-time-range-picker-custom-option"));
|
|
174
|
+
fireEvent.click(screen.getByText("Apply"));
|
|
175
|
+
|
|
176
|
+
expect(emitted).toHaveLength(1);
|
|
177
|
+
expect(emitted[0]!.range).toBe(TimeRange.CUSTOM);
|
|
178
|
+
expect(emitted[0]!.startAndEndDate).toBeInstanceOf(InBetween);
|
|
179
|
+
});
|
|
138
180
|
});
|
|
@@ -75,9 +75,27 @@ type PressFunction = (element: HTMLElement) => void;
|
|
|
75
75
|
|
|
76
76
|
const press: PressFunction = (element: HTMLElement): void => {
|
|
77
77
|
fireEvent.mouseDown(element);
|
|
78
|
+
fireEvent.mouseUp(element);
|
|
78
79
|
fireEvent.click(element);
|
|
79
80
|
};
|
|
80
81
|
|
|
82
|
+
/*
|
|
83
|
+
* A press that begins on one element and ends on another. The browser reports
|
|
84
|
+
* the click against the nearest common ancestor of the two, which for anything
|
|
85
|
+
* inside the dialog is the backdrop layer itself — so the click alone cannot
|
|
86
|
+
* tell a drag from a dismissal, and the modal has to watch both ends.
|
|
87
|
+
*/
|
|
88
|
+
type DragPressFunction = (from: HTMLElement, to: HTMLElement) => void;
|
|
89
|
+
|
|
90
|
+
const dragPress: DragPressFunction = (
|
|
91
|
+
from: HTMLElement,
|
|
92
|
+
to: HTMLElement,
|
|
93
|
+
): void => {
|
|
94
|
+
fireEvent.mouseDown(from);
|
|
95
|
+
fireEvent.mouseUp(to);
|
|
96
|
+
fireEvent.click(getBackdropLayer().parentElement!);
|
|
97
|
+
};
|
|
98
|
+
|
|
81
99
|
interface PortalHarness {
|
|
82
100
|
onClose: () => void;
|
|
83
101
|
}
|
|
@@ -472,6 +490,78 @@ describe("Modal dismissal and portalled dropdown menus", () => {
|
|
|
472
490
|
expect(harness.onClose).toHaveBeenCalledTimes(1);
|
|
473
491
|
});
|
|
474
492
|
|
|
493
|
+
test("a press that starts on the backdrop and ends on the panel is a drag, not a dismissal", () => {
|
|
494
|
+
const harness: PortalHarness = renderInModal(<div>Modal content</div>);
|
|
495
|
+
|
|
496
|
+
/*
|
|
497
|
+
* Pressing outside and releasing inside — reaching for the backdrop,
|
|
498
|
+
* changing your mind, letting go over the form. The click lands on the
|
|
499
|
+
* backdrop layer either way, so before the modal tracked the release this
|
|
500
|
+
* discarded whatever had been typed.
|
|
501
|
+
*/
|
|
502
|
+
dragPress(getBackdropLayer(), screen.getByText("Modal content"));
|
|
503
|
+
|
|
504
|
+
expect(harness.onClose).not.toHaveBeenCalled();
|
|
505
|
+
expect(screen.getByTestId("modal")).toBeInTheDocument();
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
test("a press that starts on the panel and ends on the backdrop is a drag too", () => {
|
|
509
|
+
const harness: PortalHarness = renderInModal(<div>Modal content</div>);
|
|
510
|
+
|
|
511
|
+
// Selecting text in the body and releasing past the panel edge.
|
|
512
|
+
dragPress(screen.getByText("Modal content"), getBackdropLayer());
|
|
513
|
+
|
|
514
|
+
expect(harness.onClose).not.toHaveBeenCalled();
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test("a release on a portalled menu does not dismiss the dialog either", () => {
|
|
518
|
+
const harness: PortalHarness = renderInModal(
|
|
519
|
+
<div>
|
|
520
|
+
{createPortal(
|
|
521
|
+
<button type="button" data-testid="portalled-option">
|
|
522
|
+
pick me
|
|
523
|
+
</button>,
|
|
524
|
+
document.body,
|
|
525
|
+
)}
|
|
526
|
+
</div>,
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
dragPress(getBackdropLayer(), screen.getByTestId("portalled-option"));
|
|
530
|
+
|
|
531
|
+
expect(harness.onClose).not.toHaveBeenCalled();
|
|
532
|
+
expect(screen.getByTestId("modal")).toBeInTheDocument();
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
test("dismissing an open picker leaves the modal behind it standing", () => {
|
|
536
|
+
const harness: PortalHarness = renderInModal(
|
|
537
|
+
<ColorPicker
|
|
538
|
+
dataTestId="color-value"
|
|
539
|
+
placeholder="Pick a color"
|
|
540
|
+
onChange={jest.fn()}
|
|
541
|
+
/>,
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
fireEvent.click(screen.getByTestId("color-value"));
|
|
545
|
+
expect(screen.getByTestId("color-picker-popup")).toBeInTheDocument();
|
|
546
|
+
|
|
547
|
+
/*
|
|
548
|
+
* The popup covers a good part of the backdrop, so "click off it to put
|
|
549
|
+
* it away" is the obvious gesture — and one press used to close the
|
|
550
|
+
* picker and the form behind it together. Dismissal belongs to the
|
|
551
|
+
* topmost layer that is open.
|
|
552
|
+
*/
|
|
553
|
+
press(getBackdropLayer());
|
|
554
|
+
|
|
555
|
+
expect(screen.queryByTestId("color-picker-popup")).toBeNull();
|
|
556
|
+
expect(harness.onClose).not.toHaveBeenCalled();
|
|
557
|
+
expect(screen.getByTestId("modal")).toBeInTheDocument();
|
|
558
|
+
|
|
559
|
+
// With the picker gone, the very next backdrop press dismisses as usual.
|
|
560
|
+
press(getBackdropLayer());
|
|
561
|
+
|
|
562
|
+
expect(harness.onClose).toHaveBeenCalledTimes(1);
|
|
563
|
+
});
|
|
564
|
+
|
|
475
565
|
test("the close button still closes a modal holding a portalled menu", () => {
|
|
476
566
|
const harness: PortalHarness = renderInModal(
|
|
477
567
|
<AutocompleteTextInput
|
|
@@ -371,7 +371,12 @@ describe("TelemetryTimeRangePicker", () => {
|
|
|
371
371
|
expect(optionLabels).toEqual(expectedLabels);
|
|
372
372
|
});
|
|
373
373
|
|
|
374
|
-
test("
|
|
374
|
+
test("keeps the date fields out of the narrow dropdown panel", () => {
|
|
375
|
+
/*
|
|
376
|
+
* Regression: the custom editor used to render two `datetime-local`
|
|
377
|
+
* fields inside the 288px panel. It now lives in a modal, so the panel
|
|
378
|
+
* itself stays a plain list of options.
|
|
379
|
+
*/
|
|
375
380
|
renderPicker({
|
|
376
381
|
range: TimeRange.CUSTOM,
|
|
377
382
|
startAndEndDate: new InBetween<Date>(
|
|
@@ -382,14 +387,11 @@ describe("TelemetryTimeRangePicker", () => {
|
|
|
382
387
|
|
|
383
388
|
const dropdown: HTMLElement = openDropdown();
|
|
384
389
|
|
|
385
|
-
// The custom editor lives in the same panel, so alignment still applies.
|
|
386
390
|
expect(hasClass(dropdown, "left-0")).toBe(true);
|
|
387
|
-
expect(dropdown.querySelectorAll("input").length).
|
|
391
|
+
expect(dropdown.querySelectorAll("input").length).toBe(0);
|
|
388
392
|
});
|
|
389
393
|
|
|
390
|
-
test("
|
|
391
|
-
stubTriggerRect(1310, 1430);
|
|
392
|
-
|
|
394
|
+
test("marks the custom entry as selected while a custom range is active", () => {
|
|
393
395
|
renderPicker({
|
|
394
396
|
range: TimeRange.CUSTOM,
|
|
395
397
|
startAndEndDate: new InBetween<Date>(
|
|
@@ -398,7 +400,47 @@ describe("TelemetryTimeRangePicker", () => {
|
|
|
398
400
|
),
|
|
399
401
|
});
|
|
400
402
|
|
|
401
|
-
|
|
403
|
+
openDropdown();
|
|
404
|
+
|
|
405
|
+
expect(
|
|
406
|
+
hasClass(
|
|
407
|
+
screen.getByTestId("telemetry-time-range-picker-custom-option"),
|
|
408
|
+
"text-indigo-700",
|
|
409
|
+
),
|
|
410
|
+
).toBe(true);
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test("opens the custom range modal from the dropdown", () => {
|
|
414
|
+
renderPicker({ range: TimeRange.PAST_ONE_HOUR });
|
|
415
|
+
openDropdown();
|
|
416
|
+
|
|
417
|
+
fireEvent.click(
|
|
418
|
+
screen.getByTestId("telemetry-time-range-picker-custom-option"),
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
expect(screen.getByTestId("custom-time-range-modal")).toBeInTheDocument();
|
|
422
|
+
// The dropdown gets out of the way once the modal is up.
|
|
423
|
+
expect(screen.queryByTestId(DROPDOWN_TEST_ID)).toBeNull();
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test("applies a custom range picked in the modal", () => {
|
|
427
|
+
const emitted: Array<RangeStartAndEndDateTime> = [];
|
|
428
|
+
renderPicker(
|
|
429
|
+
{ range: TimeRange.PAST_ONE_HOUR },
|
|
430
|
+
(value: RangeStartAndEndDateTime) => {
|
|
431
|
+
emitted.push(value);
|
|
432
|
+
},
|
|
433
|
+
);
|
|
434
|
+
openDropdown();
|
|
435
|
+
|
|
436
|
+
fireEvent.click(
|
|
437
|
+
screen.getByTestId("telemetry-time-range-picker-custom-option"),
|
|
438
|
+
);
|
|
439
|
+
fireEvent.click(screen.getByText("Apply"));
|
|
440
|
+
|
|
441
|
+
expect(emitted).toHaveLength(1);
|
|
442
|
+
expect(emitted[0]!.range).toBe(TimeRange.CUSTOM);
|
|
443
|
+
expect(emitted[0]!.startAndEndDate).toBeInstanceOf(InBetween);
|
|
402
444
|
});
|
|
403
445
|
});
|
|
404
446
|
});
|