@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
package/Server/Utils/VM/VMAPI.ts
CHANGED
|
@@ -34,6 +34,20 @@ export default class VMUtil {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/*
|
|
38
|
+
* When we stringified the value ourselves just above, every placeholder in
|
|
39
|
+
* the text we are about to substitute into sits inside a JSON string
|
|
40
|
+
* literal — that is what JSON.stringify did to it. A resolved value
|
|
41
|
+
* carrying a quote or a newline therefore has to be escaped, or the
|
|
42
|
+
* JSON.parse at the bottom of this method fails and the caller silently
|
|
43
|
+
* receives a corrupted string where it asked for an object.
|
|
44
|
+
*
|
|
45
|
+
* This is separate from the caller's isJSON flag, which describes text the
|
|
46
|
+
* caller wrote and where a placeholder may legitimately stand in for a bare
|
|
47
|
+
* JSON value rather than sit inside a string.
|
|
48
|
+
*/
|
|
49
|
+
const shouldEscapeForJSON: boolean = Boolean(isJSON) || didStringify;
|
|
50
|
+
|
|
37
51
|
if (
|
|
38
52
|
typeof valueToReplaceInPlace === "string" &&
|
|
39
53
|
valueToReplaceInPlace.toString().includes("{{") &&
|
|
@@ -45,7 +59,7 @@ export default class VMUtil {
|
|
|
45
59
|
valueToReplaceInPlaceCopy = VMUtil.expandEachLoops(
|
|
46
60
|
storageMap,
|
|
47
61
|
valueToReplaceInPlaceCopy,
|
|
48
|
-
|
|
62
|
+
shouldEscapeForJSON,
|
|
49
63
|
);
|
|
50
64
|
|
|
51
65
|
const variablesInArgument: Array<string> = [];
|
|
@@ -83,11 +97,21 @@ export default class VMUtil {
|
|
|
83
97
|
if (valueToReplaceInPlaceCopy.trim() === "{{" + variable + "}}") {
|
|
84
98
|
valueToReplaceInPlaceCopy = valueToReplaceInPlace;
|
|
85
99
|
} else {
|
|
100
|
+
const replacement: string = shouldEscapeForJSON
|
|
101
|
+
? VMUtil.serializeValueForJSON(valueToReplaceInPlace)
|
|
102
|
+
: `${valueToReplaceInPlace}`;
|
|
103
|
+
|
|
104
|
+
/*
|
|
105
|
+
* Function form, not the string form. String.replace treats $&, $1,
|
|
106
|
+
* $` and $' in the REPLACEMENT as substitution patterns, so a
|
|
107
|
+
* resolved value of "50$" or "a$&b" rewrote itself using the matched
|
|
108
|
+
* text. A function replacement is taken literally.
|
|
109
|
+
*/
|
|
86
110
|
valueToReplaceInPlaceCopy = valueToReplaceInPlaceCopy.replace(
|
|
87
111
|
"{{" + variable + "}}",
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
112
|
+
() => {
|
|
113
|
+
return replacement;
|
|
114
|
+
},
|
|
91
115
|
);
|
|
92
116
|
}
|
|
93
117
|
}
|
|
@@ -294,10 +318,14 @@ export default class VMUtil {
|
|
|
294
318
|
replacement = `${foundValue}`;
|
|
295
319
|
}
|
|
296
320
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
321
|
+
const substitution: string = isJSON
|
|
322
|
+
? VMUtil.serializeValueForJSON(replacement)
|
|
323
|
+
: replacement;
|
|
324
|
+
|
|
325
|
+
// Function form — see the note on the same call in replaceValueInPlace.
|
|
326
|
+
body = body.replace("{{" + variable + "}}", () => {
|
|
327
|
+
return substitution;
|
|
328
|
+
});
|
|
301
329
|
}
|
|
302
330
|
|
|
303
331
|
return body;
|
|
@@ -313,6 +341,15 @@ export default class VMUtil {
|
|
|
313
341
|
value = JSON.stringify(value);
|
|
314
342
|
} else {
|
|
315
343
|
value = value
|
|
344
|
+
/*
|
|
345
|
+
* Backslash first, and only first. It was missing entirely, so a value
|
|
346
|
+
* like a Windows path or a regex ("C:\Users", "\d+") produced an
|
|
347
|
+
* invalid escape sequence in the surrounding document and the
|
|
348
|
+
* JSON.parse that followed threw. Escaping it after the others would
|
|
349
|
+
* instead double-escape the backslashes they just introduced.
|
|
350
|
+
*/
|
|
351
|
+
.split("\\")
|
|
352
|
+
.join("\\\\")
|
|
316
353
|
.split("\t")
|
|
317
354
|
.join("\\t")
|
|
318
355
|
.split("\n")
|
|
@@ -355,6 +392,17 @@ export default class VMUtil {
|
|
|
355
392
|
if (indexString !== "last") {
|
|
356
393
|
index = parseInt(indexString);
|
|
357
394
|
} else {
|
|
395
|
+
/*
|
|
396
|
+
* `current[arrayKey].length` was read unguarded, so `items[last]`
|
|
397
|
+
* against a key that is missing (or is not an array) threw a
|
|
398
|
+
* TypeError out of deepFind and killed the entire run — the one
|
|
399
|
+
* resolution failure in here that was not silent. Fall through to the
|
|
400
|
+
* undefined return that every other miss produces.
|
|
401
|
+
*/
|
|
402
|
+
if (!Array.isArray(current[arrayKey])) {
|
|
403
|
+
return undefined;
|
|
404
|
+
}
|
|
405
|
+
|
|
358
406
|
index = current[arrayKey].length - 1;
|
|
359
407
|
}
|
|
360
408
|
|
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The main entry, not "/extend-expect": the latter no longer ships type
|
|
3
|
+
* declarations, so every jest-dom matcher in this file fails to typecheck and
|
|
4
|
+
* the whole suite is skipped before a single assertion runs.
|
|
5
|
+
*/
|
|
6
|
+
import "@testing-library/jest-dom";
|
|
7
|
+
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
|
8
|
+
import React, { ReactElement } from "react";
|
|
9
|
+
import { afterEach, describe, expect, jest, test } from "@jest/globals";
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* Both previews render a live telemetry viewer that lists logs/spans over the
|
|
13
|
+
* API. Nothing in this file is about the preview, and mounting the real one
|
|
14
|
+
* would only add network to a test that is about clicks.
|
|
15
|
+
*/
|
|
16
|
+
jest.mock(
|
|
17
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Monitor/LogMonitor/LogMonitorPreview",
|
|
18
|
+
() => {
|
|
19
|
+
return {
|
|
20
|
+
__esModule: true,
|
|
21
|
+
default: (): ReactElement => {
|
|
22
|
+
return <div data-testid="logs-preview" />;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
jest.mock(
|
|
29
|
+
"../../../../App/FeatureSet/Dashboard/src/Components/Monitor/TraceMonitor/TraceMonitorPreview",
|
|
30
|
+
() => {
|
|
31
|
+
return {
|
|
32
|
+
__esModule: true,
|
|
33
|
+
default: (): ReactElement => {
|
|
34
|
+
return <div data-testid="spans-preview" />;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
import LogMonitorStepForm from "../../../../App/FeatureSet/Dashboard/src/Components/Form/Monitor/LogMonitor/LogMonitorStepFrom";
|
|
41
|
+
import TraceMonitorStepForm from "../../../../App/FeatureSet/Dashboard/src/Components/Form/Monitor/TraceMonitor/TraceMonitorStepForm";
|
|
42
|
+
import MonitorStepLogMonitor, {
|
|
43
|
+
MonitorStepLogMonitorUtil,
|
|
44
|
+
} from "../../../Types/Monitor/MonitorStepLogMonitor";
|
|
45
|
+
import MonitorStepTraceMonitor, {
|
|
46
|
+
MonitorStepTraceMonitorUtil,
|
|
47
|
+
} from "../../../Types/Monitor/MonitorStepTraceMonitor";
|
|
48
|
+
import Modal from "../../../UI/Components/Modal/Modal";
|
|
49
|
+
import BasicForm from "../../../UI/Components/Forms/BasicForm";
|
|
50
|
+
import FormFieldSchemaType from "../../../UI/Components/Forms/Types/FormFieldSchemaType";
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
* Reported as: "when I try to filter the criteria by attributes it just closes
|
|
54
|
+
* the window". Monitor → Criteria → Edit Monitoring Criteria → Show Advanced
|
|
55
|
+
* Options → Add Filter by Attributes → click the Key box → pick a key off the
|
|
56
|
+
* suggestion list, and the whole Edit Monitor dialog disappears, taking the
|
|
57
|
+
* criteria the user was editing with it.
|
|
58
|
+
*
|
|
59
|
+
* Two separate defects reached that screen, and both are pinned here:
|
|
60
|
+
*
|
|
61
|
+
* 1. The suggestion list is portalled to document.body so the modal's scroll
|
|
62
|
+
* container cannot clip it, and React dispatches synthetic events through
|
|
63
|
+
* the React tree rather than the DOM tree. Picking an option therefore
|
|
64
|
+
* bubbled into Modal's backdrop handler, which decided "outside" by asking
|
|
65
|
+
* whether the panel DOM-contains the target — a portalled option never is.
|
|
66
|
+
* Fixed in Modal (#3133); the mechanism is covered in
|
|
67
|
+
* Common/Tests/UI/Components/ModalPortalDismissal.test.tsx. What was
|
|
68
|
+
* missing was coverage of the surface the user actually reported, which is
|
|
69
|
+
* this one: the criteria editor, where the autocomplete sits at the bottom
|
|
70
|
+
* of a nested form inside a scrolled modal body.
|
|
71
|
+
*
|
|
72
|
+
* 2. Clearing a Number-typed attribute value deleted the array member rather
|
|
73
|
+
* than emptying it, which dropped the row and then crashed the form on the
|
|
74
|
+
* next render. Fixed in Dictionary; the row-level tests are in
|
|
75
|
+
* Common/Tests/UI/Components/DictionaryAttributeFilterRow.test.tsx.
|
|
76
|
+
*
|
|
77
|
+
* The harness runs the real chain from the modal down: Modal → the outer
|
|
78
|
+
* BasicForm → the step form → its own nested BasicForm → FormField → Dictionary
|
|
79
|
+
* → AutocompleteTextInput. Faking any layer of that would lose the thing that
|
|
80
|
+
* made this a bug — the portal boundary between the option and the panel.
|
|
81
|
+
*
|
|
82
|
+
* Every pick fires mousedown *and* click. Modal only dismisses on a press that
|
|
83
|
+
* both starts and ends outside the panel, so a click-only test passes with the
|
|
84
|
+
* bug fully present.
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
const ATTRIBUTE_KEYS: Array<string> = [
|
|
88
|
+
"{OriginalFormat}",
|
|
89
|
+
"Action",
|
|
90
|
+
"ActionId",
|
|
91
|
+
"ActionName",
|
|
92
|
+
"ActionType",
|
|
93
|
+
"AlreadyCancelled",
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
interface Recorder {
|
|
97
|
+
closeCount: number;
|
|
98
|
+
latestLogMonitor: MonitorStepLogMonitor | null;
|
|
99
|
+
latestTraceMonitor: MonitorStepTraceMonitor | null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
type NewRecorderFunction = () => Recorder;
|
|
103
|
+
|
|
104
|
+
const newRecorder: NewRecorderFunction = (): Recorder => {
|
|
105
|
+
return { closeCount: 0, latestLogMonitor: null, latestTraceMonitor: null };
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
* Mirrors the criteria editor: CardModelDetail puts the step form inside a
|
|
110
|
+
* ModelFormModal, so the step form's own BasicForm is nested inside the modal
|
|
111
|
+
* form. That nesting is load-bearing here — it is the shape the user was in.
|
|
112
|
+
*/
|
|
113
|
+
const LogHarness: React.FunctionComponent<{ recorder: Recorder }> = (props: {
|
|
114
|
+
recorder: Recorder;
|
|
115
|
+
}): ReactElement => {
|
|
116
|
+
const [logMonitor, setLogMonitor] = React.useState<MonitorStepLogMonitor>(
|
|
117
|
+
MonitorStepLogMonitorUtil.getDefault(),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<Modal
|
|
122
|
+
title="Edit Monitor"
|
|
123
|
+
onClose={() => {
|
|
124
|
+
props.recorder.closeCount++;
|
|
125
|
+
}}
|
|
126
|
+
onSubmit={() => {}}
|
|
127
|
+
>
|
|
128
|
+
<BasicForm
|
|
129
|
+
id="monitor-form"
|
|
130
|
+
hideSubmitButton={true}
|
|
131
|
+
initialValues={{}}
|
|
132
|
+
onSubmit={() => {}}
|
|
133
|
+
fields={[
|
|
134
|
+
{
|
|
135
|
+
field: { monitorSteps: true },
|
|
136
|
+
title: "Monitor Details",
|
|
137
|
+
fieldType: FormFieldSchemaType.CustomComponent,
|
|
138
|
+
getCustomElement: () => {
|
|
139
|
+
return (
|
|
140
|
+
<LogMonitorStepForm
|
|
141
|
+
monitorStepLogMonitor={logMonitor}
|
|
142
|
+
onMonitorStepLogMonitorChanged={(
|
|
143
|
+
value: MonitorStepLogMonitor,
|
|
144
|
+
) => {
|
|
145
|
+
props.recorder.latestLogMonitor = value;
|
|
146
|
+
setLogMonitor(value);
|
|
147
|
+
}}
|
|
148
|
+
attributeKeys={ATTRIBUTE_KEYS}
|
|
149
|
+
telemetryServices={[]}
|
|
150
|
+
telemetryEntities={[]}
|
|
151
|
+
/>
|
|
152
|
+
);
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
]}
|
|
156
|
+
/>
|
|
157
|
+
</Modal>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const TraceHarness: React.FunctionComponent<{ recorder: Recorder }> = (props: {
|
|
162
|
+
recorder: Recorder;
|
|
163
|
+
}): ReactElement => {
|
|
164
|
+
const [traceMonitor, setTraceMonitor] =
|
|
165
|
+
React.useState<MonitorStepTraceMonitor>(
|
|
166
|
+
MonitorStepTraceMonitorUtil.getDefault(),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<Modal
|
|
171
|
+
title="Edit Monitor"
|
|
172
|
+
onClose={() => {
|
|
173
|
+
props.recorder.closeCount++;
|
|
174
|
+
}}
|
|
175
|
+
onSubmit={() => {}}
|
|
176
|
+
>
|
|
177
|
+
<BasicForm
|
|
178
|
+
id="monitor-form"
|
|
179
|
+
hideSubmitButton={true}
|
|
180
|
+
initialValues={{}}
|
|
181
|
+
onSubmit={() => {}}
|
|
182
|
+
fields={[
|
|
183
|
+
{
|
|
184
|
+
field: { monitorSteps: true },
|
|
185
|
+
title: "Monitor Details",
|
|
186
|
+
fieldType: FormFieldSchemaType.CustomComponent,
|
|
187
|
+
getCustomElement: () => {
|
|
188
|
+
return (
|
|
189
|
+
<TraceMonitorStepForm
|
|
190
|
+
monitorStepTraceMonitor={traceMonitor}
|
|
191
|
+
onMonitorStepTraceMonitorChanged={(
|
|
192
|
+
value: MonitorStepTraceMonitor,
|
|
193
|
+
) => {
|
|
194
|
+
props.recorder.latestTraceMonitor = value;
|
|
195
|
+
setTraceMonitor(value);
|
|
196
|
+
}}
|
|
197
|
+
attributeKeys={ATTRIBUTE_KEYS}
|
|
198
|
+
telemetryServices={[]}
|
|
199
|
+
telemetryEntities={[]}
|
|
200
|
+
/>
|
|
201
|
+
);
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
]}
|
|
205
|
+
/>
|
|
206
|
+
</Modal>
|
|
207
|
+
);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/*
|
|
211
|
+
* AutocompleteTextInput and react-select both report role="combobox". The
|
|
212
|
+
* listbox each autocomplete owns is the only stable way to tell them apart.
|
|
213
|
+
*/
|
|
214
|
+
type AutocompleteInputsFunction = () => Array<HTMLInputElement>;
|
|
215
|
+
|
|
216
|
+
const autocompleteInputs: AutocompleteInputsFunction =
|
|
217
|
+
(): Array<HTMLInputElement> => {
|
|
218
|
+
return Array.from(
|
|
219
|
+
document.querySelectorAll<HTMLInputElement>(
|
|
220
|
+
'input[aria-controls^="autocomplete-suggestions-"]',
|
|
221
|
+
),
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
type KeyInputFunction = () => HTMLInputElement;
|
|
226
|
+
|
|
227
|
+
const keyInput: KeyInputFunction = (): HTMLInputElement => {
|
|
228
|
+
return autocompleteInputs().filter((input: HTMLInputElement) => {
|
|
229
|
+
return input.getAttribute("placeholder") !== "Value";
|
|
230
|
+
})[0]!;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
type ValueInputFunction = () => HTMLInputElement;
|
|
234
|
+
|
|
235
|
+
const valueInput: ValueInputFunction = (): HTMLInputElement => {
|
|
236
|
+
return autocompleteInputs().filter((input: HTMLInputElement) => {
|
|
237
|
+
return input.getAttribute("placeholder") === "Value";
|
|
238
|
+
})[0]!;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// A real pointer press is mousedown-then-click; Modal watches both ends.
|
|
242
|
+
type PressFunction = (element: HTMLElement) => void;
|
|
243
|
+
|
|
244
|
+
const press: PressFunction = (element: HTMLElement): void => {
|
|
245
|
+
fireEvent.mouseDown(element);
|
|
246
|
+
fireEvent.click(element);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/*
|
|
250
|
+
* The layer a user actually presses to dismiss is the one the panel is centred
|
|
251
|
+
* in; the tinted sheet behind it is aria-hidden and inert.
|
|
252
|
+
*/
|
|
253
|
+
type BackdropLayerFunction = () => HTMLElement;
|
|
254
|
+
|
|
255
|
+
const backdropLayer: BackdropLayerFunction = (): HTMLElement => {
|
|
256
|
+
return screen.getByTestId("modal").parentElement!;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// Walks the video: open the advanced section, then add one attribute filter.
|
|
260
|
+
type OpenAttributeFilterFunction = () => void;
|
|
261
|
+
|
|
262
|
+
const openAttributeFilter: OpenAttributeFilterFunction = (): void => {
|
|
263
|
+
const showAdvanced: HTMLElement | null = screen.queryByText(
|
|
264
|
+
"Show Advanced Options",
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
if (showAdvanced) {
|
|
268
|
+
fireEvent.click(showAdvanced);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
fireEvent.click(screen.getByText("Add Filter by Attributes"));
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
type RenderLogHarnessFunction = () => Recorder;
|
|
275
|
+
|
|
276
|
+
const renderLogHarness: RenderLogHarnessFunction = (): Recorder => {
|
|
277
|
+
const recorder: Recorder = newRecorder();
|
|
278
|
+
render(<LogHarness recorder={recorder} />);
|
|
279
|
+
openAttributeFilter();
|
|
280
|
+
return recorder;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
describe("Monitoring criteria — Filter by Attributes", () => {
|
|
284
|
+
afterEach(() => {
|
|
285
|
+
cleanup();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe("the reported bug: picking a key must not close the modal", () => {
|
|
289
|
+
test("the suggestion list is portalled outside the modal panel", () => {
|
|
290
|
+
renderLogHarness();
|
|
291
|
+
|
|
292
|
+
fireEvent.focus(keyInput());
|
|
293
|
+
|
|
294
|
+
/*
|
|
295
|
+
* The precondition that made this a bug at all. If the menu ever stops
|
|
296
|
+
* being portalled, the Modal contract below is no longer what protects
|
|
297
|
+
* this screen — and this test says so before the others start passing
|
|
298
|
+
* for the wrong reason.
|
|
299
|
+
*/
|
|
300
|
+
expect(
|
|
301
|
+
screen.getByTestId("modal").contains(screen.getByText("ActionName")),
|
|
302
|
+
).toBe(false);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test("picking a suggested key leaves the modal open", () => {
|
|
306
|
+
const recorder: Recorder = renderLogHarness();
|
|
307
|
+
|
|
308
|
+
fireEvent.focus(keyInput());
|
|
309
|
+
press(screen.getByText("ActionName"));
|
|
310
|
+
|
|
311
|
+
expect(recorder.closeCount).toBe(0);
|
|
312
|
+
expect(screen.getByTestId("modal")).toBeInTheDocument();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test("picking a suggested key fills the row in", () => {
|
|
316
|
+
renderLogHarness();
|
|
317
|
+
|
|
318
|
+
fireEvent.focus(keyInput());
|
|
319
|
+
press(screen.getByText("ActionName"));
|
|
320
|
+
|
|
321
|
+
expect(keyInput().value).toBe("ActionName");
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("the whole form the user filled in survives the pick", () => {
|
|
325
|
+
const recorder: Recorder = renderLogHarness();
|
|
326
|
+
|
|
327
|
+
fireEvent.focus(keyInput());
|
|
328
|
+
press(screen.getByText("ActionName"));
|
|
329
|
+
|
|
330
|
+
// Everything the modal was showing is still on screen.
|
|
331
|
+
expect(screen.getByText("Filter by Attributes")).toBeInTheDocument();
|
|
332
|
+
expect(screen.getByText("Log Severity")).toBeInTheDocument();
|
|
333
|
+
expect(recorder.closeCount).toBe(0);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("the picked attribute reaches the monitor step", () => {
|
|
337
|
+
const recorder: Recorder = renderLogHarness();
|
|
338
|
+
|
|
339
|
+
fireEvent.focus(keyInput());
|
|
340
|
+
press(screen.getByText("ActionName"));
|
|
341
|
+
fireEvent.change(valueInput(), { target: { value: "checkout" } });
|
|
342
|
+
|
|
343
|
+
expect(recorder.latestLogMonitor?.attributes).toEqual({
|
|
344
|
+
ActionName: "checkout",
|
|
345
|
+
});
|
|
346
|
+
expect(recorder.closeCount).toBe(0);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test("picking a second key on a second row also leaves the modal open", () => {
|
|
350
|
+
const recorder: Recorder = renderLogHarness();
|
|
351
|
+
|
|
352
|
+
fireEvent.focus(keyInput());
|
|
353
|
+
press(screen.getByText("ActionName"));
|
|
354
|
+
|
|
355
|
+
fireEvent.click(screen.getByText("Add Filter by Attributes"));
|
|
356
|
+
|
|
357
|
+
const secondKeyInput: HTMLInputElement = autocompleteInputs().filter(
|
|
358
|
+
(input: HTMLInputElement) => {
|
|
359
|
+
return (
|
|
360
|
+
input.getAttribute("placeholder") !== "Value" && input.value === ""
|
|
361
|
+
);
|
|
362
|
+
},
|
|
363
|
+
)[0]!;
|
|
364
|
+
|
|
365
|
+
fireEvent.focus(secondKeyInput);
|
|
366
|
+
press(screen.getByText("ActionType"));
|
|
367
|
+
|
|
368
|
+
expect(recorder.closeCount).toBe(0);
|
|
369
|
+
expect(recorder.latestLogMonitor?.attributes).toEqual({
|
|
370
|
+
ActionName: "",
|
|
371
|
+
ActionType: "",
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test("the same pick on the Traces criteria form leaves the modal open", () => {
|
|
376
|
+
const recorder: Recorder = newRecorder();
|
|
377
|
+
render(<TraceHarness recorder={recorder} />);
|
|
378
|
+
openAttributeFilter();
|
|
379
|
+
|
|
380
|
+
fireEvent.focus(keyInput());
|
|
381
|
+
press(screen.getByText("ActionName"));
|
|
382
|
+
|
|
383
|
+
expect(recorder.closeCount).toBe(0);
|
|
384
|
+
expect(screen.getByTestId("modal")).toBeInTheDocument();
|
|
385
|
+
expect(keyInput().value).toBe("ActionName");
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe("the rest of the row, from inside the modal", () => {
|
|
390
|
+
test("choosing a key with the keyboard leaves the modal open", () => {
|
|
391
|
+
const recorder: Recorder = renderLogHarness();
|
|
392
|
+
|
|
393
|
+
fireEvent.focus(keyInput());
|
|
394
|
+
fireEvent.keyDown(keyInput(), { key: "ArrowDown" });
|
|
395
|
+
fireEvent.keyDown(keyInput(), { key: "Enter" });
|
|
396
|
+
|
|
397
|
+
expect(recorder.closeCount).toBe(0);
|
|
398
|
+
expect(keyInput().value).toBe("{OriginalFormat}");
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("Enter with nothing highlighted does not close the modal", () => {
|
|
402
|
+
const recorder: Recorder = renderLogHarness();
|
|
403
|
+
|
|
404
|
+
fireEvent.focus(keyInput());
|
|
405
|
+
fireEvent.keyDown(keyInput(), { key: "Enter" });
|
|
406
|
+
|
|
407
|
+
expect(recorder.closeCount).toBe(0);
|
|
408
|
+
expect(screen.getByTestId("modal")).toBeInTheDocument();
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test("Escape closes the suggestion list, not the modal", () => {
|
|
412
|
+
const recorder: Recorder = renderLogHarness();
|
|
413
|
+
|
|
414
|
+
fireEvent.focus(keyInput());
|
|
415
|
+
expect(document.querySelectorAll('[role="listbox"]')).toHaveLength(1);
|
|
416
|
+
|
|
417
|
+
fireEvent.keyDown(keyInput(), { key: "Escape" });
|
|
418
|
+
|
|
419
|
+
expect(document.querySelectorAll('[role="listbox"]')).toHaveLength(0);
|
|
420
|
+
expect(recorder.closeCount).toBe(0);
|
|
421
|
+
expect(screen.getByTestId("modal")).toBeInTheDocument();
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test("a second Escape, with the list closed, does close the modal", () => {
|
|
425
|
+
const recorder: Recorder = renderLogHarness();
|
|
426
|
+
|
|
427
|
+
fireEvent.focus(keyInput());
|
|
428
|
+
fireEvent.keyDown(keyInput(), { key: "Escape" });
|
|
429
|
+
fireEvent.keyDown(document, { key: "Escape" });
|
|
430
|
+
|
|
431
|
+
expect(recorder.closeCount).toBe(1);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test("typing a value keeps the modal open", () => {
|
|
435
|
+
const recorder: Recorder = renderLogHarness();
|
|
436
|
+
|
|
437
|
+
fireEvent.focus(keyInput());
|
|
438
|
+
press(screen.getByText("ActionName"));
|
|
439
|
+
fireEvent.change(valueInput(), { target: { value: "checkout" } });
|
|
440
|
+
|
|
441
|
+
expect(recorder.closeCount).toBe(0);
|
|
442
|
+
expect(valueInput().value).toBe("checkout");
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test("deleting the row keeps the modal open", () => {
|
|
446
|
+
const recorder: Recorder = renderLogHarness();
|
|
447
|
+
|
|
448
|
+
fireEvent.focus(keyInput());
|
|
449
|
+
press(screen.getByText("ActionName"));
|
|
450
|
+
|
|
451
|
+
press(screen.getByTestId("delete-ActionName"));
|
|
452
|
+
|
|
453
|
+
expect(recorder.closeCount).toBe(0);
|
|
454
|
+
expect(autocompleteInputs()).toHaveLength(0);
|
|
455
|
+
expect(screen.getByTestId("modal")).toBeInTheDocument();
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
/*
|
|
460
|
+
* The second defect, seen from the screen it broke. A Number-typed row that
|
|
461
|
+
* had its value cleared used to vanish, and the next "Add Filter by
|
|
462
|
+
* Attributes" click rendered an `undefined` row and threw — which, inside a
|
|
463
|
+
* modal, is another way for the window to just go away.
|
|
464
|
+
*/
|
|
465
|
+
describe("a Number-typed attribute value", () => {
|
|
466
|
+
type SwitchToNumberFunction = () => void;
|
|
467
|
+
|
|
468
|
+
const switchToNumber: SwitchToNumberFunction = (): void => {
|
|
469
|
+
/*
|
|
470
|
+
* The step form has dropdowns of its own above the filter list, so the
|
|
471
|
+
* type select has to be found relative to the row rather than by a
|
|
472
|
+
* position in the whole modal. Within a row the comboboxes run: key
|
|
473
|
+
* autocomplete, operator select, type select, value autocomplete.
|
|
474
|
+
*/
|
|
475
|
+
const row: HTMLElement = keyInput().closest(
|
|
476
|
+
"div.flex.items-start",
|
|
477
|
+
) as HTMLElement;
|
|
478
|
+
const typeSelect: HTMLElement =
|
|
479
|
+
row.querySelectorAll<HTMLElement>('[role="combobox"]')[2]!;
|
|
480
|
+
|
|
481
|
+
fireEvent.keyDown(typeSelect, { key: "ArrowDown" });
|
|
482
|
+
press(screen.getByText("Number"));
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
test("clearing it keeps the row and the modal", () => {
|
|
486
|
+
const recorder: Recorder = renderLogHarness();
|
|
487
|
+
|
|
488
|
+
fireEvent.focus(keyInput());
|
|
489
|
+
press(screen.getByText("ActionId"));
|
|
490
|
+
switchToNumber();
|
|
491
|
+
|
|
492
|
+
const numberInput: HTMLInputElement = document.querySelector(
|
|
493
|
+
'input[type="number"]',
|
|
494
|
+
) as HTMLInputElement;
|
|
495
|
+
fireEvent.change(numberInput, { target: { value: "5" } });
|
|
496
|
+
expect(recorder.latestLogMonitor?.attributes).toEqual({ ActionId: 5 });
|
|
497
|
+
|
|
498
|
+
fireEvent.change(numberInput, { target: { value: "" } });
|
|
499
|
+
|
|
500
|
+
expect(keyInput().value).toBe("ActionId");
|
|
501
|
+
expect(recorder.latestLogMonitor?.attributes).toEqual({});
|
|
502
|
+
expect(recorder.closeCount).toBe(0);
|
|
503
|
+
expect(screen.getByTestId("modal")).toBeInTheDocument();
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
test("adding another row after clearing it does not blow the modal up", () => {
|
|
507
|
+
const recorder: Recorder = renderLogHarness();
|
|
508
|
+
|
|
509
|
+
fireEvent.focus(keyInput());
|
|
510
|
+
press(screen.getByText("ActionId"));
|
|
511
|
+
switchToNumber();
|
|
512
|
+
|
|
513
|
+
const numberInput: HTMLInputElement = document.querySelector(
|
|
514
|
+
'input[type="number"]',
|
|
515
|
+
) as HTMLInputElement;
|
|
516
|
+
fireEvent.change(numberInput, { target: { value: "5" } });
|
|
517
|
+
fireEvent.change(numberInput, { target: { value: "" } });
|
|
518
|
+
|
|
519
|
+
// The click that used to throw on the very next render.
|
|
520
|
+
fireEvent.click(screen.getByText("Add Filter by Attributes"));
|
|
521
|
+
|
|
522
|
+
expect(recorder.closeCount).toBe(0);
|
|
523
|
+
expect(screen.getByTestId("modal")).toBeInTheDocument();
|
|
524
|
+
expect(screen.getByText("Filter by Attributes")).toBeInTheDocument();
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
/*
|
|
529
|
+
* The Modal fix narrows what counts as a backdrop press, so the far more
|
|
530
|
+
* common behaviour next to it has to be pinned just as hard on this screen —
|
|
531
|
+
* a criteria modal nobody can dismiss would be a worse bug than the one it
|
|
532
|
+
* replaced.
|
|
533
|
+
*/
|
|
534
|
+
describe("the modal is still dismissable the ordinary way", () => {
|
|
535
|
+
test("a press on the backdrop closes it", () => {
|
|
536
|
+
const recorder: Recorder = renderLogHarness();
|
|
537
|
+
|
|
538
|
+
press(backdropLayer());
|
|
539
|
+
|
|
540
|
+
expect(recorder.closeCount).toBe(1);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
test("a press on the backdrop still closes it after a key was picked", () => {
|
|
544
|
+
const recorder: Recorder = renderLogHarness();
|
|
545
|
+
|
|
546
|
+
fireEvent.focus(keyInput());
|
|
547
|
+
press(screen.getByText("ActionName"));
|
|
548
|
+
expect(recorder.closeCount).toBe(0);
|
|
549
|
+
|
|
550
|
+
press(backdropLayer());
|
|
551
|
+
|
|
552
|
+
expect(recorder.closeCount).toBe(1);
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
test("the close button still closes it while the suggestion list is open", () => {
|
|
556
|
+
const recorder: Recorder = renderLogHarness();
|
|
557
|
+
|
|
558
|
+
fireEvent.focus(keyInput());
|
|
559
|
+
fireEvent.click(screen.getByTestId("close-button"));
|
|
560
|
+
|
|
561
|
+
expect(recorder.closeCount).toBe(1);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
test("Cancel still closes it", () => {
|
|
565
|
+
const recorder: Recorder = renderLogHarness();
|
|
566
|
+
|
|
567
|
+
fireEvent.focus(keyInput());
|
|
568
|
+
press(screen.getByText("ActionName"));
|
|
569
|
+
fireEvent.click(screen.getByTestId("modal-footer-close-button"));
|
|
570
|
+
|
|
571
|
+
expect(recorder.closeCount).toBe(1);
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
});
|