@oneuptime/common 12.0.9 → 12.0.11
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/NetworkDevice.ts +44 -0
- package/Models/DatabaseModels/NetworkDeviceLink.ts +116 -0
- package/Models/DatabaseModels/RunnerJob.ts +17 -2
- package/Server/API/LlmProviderAPI.ts +8 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.ts +52 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +240 -0
- package/Server/Services/NetworkDeviceLinkService.ts +338 -2
- package/Server/Services/NetworkDeviceService.ts +265 -12
- package/Server/Services/RunnerJobService.ts +24 -1
- package/Server/Types/Billing/MeteredPlan/AllMeteredPlans.ts +19 -5
- package/Server/Types/Database/JSONColumnQuery.ts +42 -4
- package/Server/Utils/LLM/LLMService.ts +108 -19
- package/Server/Utils/Monitor/MonitorAlert.ts +22 -115
- package/Server/Utils/Monitor/MonitorIncident.ts +7 -337
- package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +27 -2
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +32 -0
- package/Server/Utils/Monitor/SeriesResourceLinker.ts +469 -0
- package/Server/Utils/SSRFProtection.ts +5 -4
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +186 -38
- package/Tests/App/AdminDashboard/AdminUsersBulkAddToProject.test.tsx +955 -0
- package/Tests/App/AdminDashboard/BulkAddUsersToProject.test.ts +778 -0
- package/Tests/App/AdminDashboard/BulkAddUsersToProjectModal.test.tsx +1051 -0
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +420 -112
- package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +81 -6
- package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +543 -0
- package/Tests/App/Dashboard/PayAsYouGoFreePlan.test.ts +125 -0
- package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +612 -0
- package/Tests/App/Dashboard/SloWidgetChartRendering.test.tsx +536 -0
- package/Tests/App/Dashboard/UserElementEmail.test.tsx +447 -0
- package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +284 -0
- package/Tests/Models/AnalyticsModels/CommonModel.test.ts +312 -0
- package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +250 -0
- package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +46 -18
- package/Tests/Server/Services/NetworkDeviceLabelRuleRun.test.ts +618 -0
- package/Tests/Server/Services/NetworkDeviceLinkParentValidation.test.ts +1043 -0
- package/Tests/Server/Services/NetworkSiteAssignmentRuleRun.test.ts +706 -0
- package/Tests/Server/Services/RunnerJobEnqueue.test.ts +412 -0
- package/Tests/Server/Types/Billing/PayAsYouGoMeteredPricing.test.ts +125 -0
- package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +508 -0
- package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +2 -2
- package/Tests/Server/Utils/AI/LLMServiceRetryPolicy.test.ts +450 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +556 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +547 -0
- package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +699 -0
- package/Tests/Server/Utils/SeriesResourceLabels.test.ts +55 -0
- package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +668 -0
- package/Tests/Types/API/URLMalformedTolerance.test.ts +301 -0
- package/Tests/Types/API/URLTelScheme.test.ts +261 -0
- package/Tests/Types/API/URLUnsupportedScheme.test.ts +148 -0
- package/Tests/Types/Billing/PayAsYouGoPricing.test.ts +64 -0
- package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +107 -0
- package/Tests/Types/Monitor/MonitorType.test.ts +62 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +184 -0
- package/Tests/Types/Runbook/RunbookStepType.test.ts +124 -0
- package/Tests/Types/Workflow/BaseModelComponents.test.ts +17 -0
- package/Tests/UI/Components/Button.test.tsx +279 -0
- package/Tests/UI/Components/Charts/ChartXAxisDataKey.test.tsx +256 -0
- package/Tests/UI/Components/Charts/DataPointUtil.test.ts +323 -0
- package/Tests/UI/Components/DictionaryValuePlaceholderAndSuggestions.test.tsx +595 -0
- package/Tests/UI/Components/Icon.test.tsx +57 -0
- package/Tests/UI/Components/List.test.tsx +20 -3
- package/Tests/UI/Components/LogsSavedViewsDropdown.test.tsx +304 -0
- package/Tests/UI/Components/LogsViewerClearSavedView.test.tsx +179 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +349 -1
- package/Tests/UI/Components/Pagination.test.tsx +721 -146
- package/Tests/UI/Components/PaginationIntegration.test.tsx +386 -0
- package/Tests/UI/Components/PaginationUtil.test.ts +417 -0
- package/Tests/UI/Components/TelemetrySavedViewsDropdown.test.tsx +343 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnControl.test.ts +289 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnOperators.test.ts +271 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRow.test.ts +119 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.test.ts +968 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +766 -0
- package/Tests/UI/Utils/RunNetworkRule.test.ts +320 -0
- package/Tests/Utils/APIRetryPolicy.test.ts +823 -0
- package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +502 -0
- package/Tests/Utils/Monitor/NetworkTopologyDeclaredHierarchy.test.ts +759 -0
- package/Tests/Utils/NetworkAutomation/RuleRunSummary.test.ts +286 -0
- package/Tests/Utils/Schema/BaseSchema.test.ts +651 -0
- package/Types/API/Protocol.ts +9 -0
- package/Types/API/URL.ts +286 -20
- package/Types/Billing/PayAsYouGoPricing.ts +47 -0
- package/Types/CustomField/CustomFieldType.ts +28 -0
- package/Types/Monitor/MonitorType.ts +11 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +121 -0
- package/Types/Runbook/RunbookStepType.ts +23 -0
- package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +7 -0
- package/UI/Components/Button/Button.tsx +14 -22
- package/UI/Components/Charts/Area/AreaChart.tsx +4 -2
- package/UI/Components/Charts/Bar/BarChart.tsx +4 -2
- package/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.ts +13 -0
- package/UI/Components/Charts/Line/LineChart.tsx +4 -2
- package/UI/Components/Charts/Types/XAxis/XAxis.ts +6 -0
- package/UI/Components/Charts/Utils/DataPoint.ts +15 -12
- package/UI/Components/Dictionary/Dictionary.tsx +4 -39
- package/UI/Components/Icon/Icon.tsx +1 -1
- package/UI/Components/LogsViewer/LogsViewer.tsx +5 -1
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +8 -0
- package/UI/Components/LogsViewer/components/LogsPagination.tsx +29 -91
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +2 -0
- package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +66 -1
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +55 -0
- package/UI/Components/Pagination/Pagination.tsx +373 -328
- package/UI/Components/Pagination/PaginationUtil.ts +263 -0
- package/UI/Components/Pagination/Screenshots/README.md +15 -0
- package/UI/Components/Pagination/Screenshots/pagination-before-after.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-go-to-page-modal.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-long-list.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-mobile.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-telemetry.png +0 -0
- package/UI/Components/TelemetryViewer/components/SavedViewsDropdown.tsx +66 -1
- package/UI/Components/TelemetryViewer/components/TelemetryPagination.tsx +38 -92
- package/UI/Components/TextArea/TextArea.tsx +3 -0
- package/UI/Components/Workflow/ArgumentsForm.tsx +18 -1
- package/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.tsx +177 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.tsx +243 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnControl.ts +255 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.tsx +178 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnOperators.ts +206 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnRow.ts +157 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.ts +517 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnValueInput.tsx +482 -0
- package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +131 -0
- package/UI/Components/Workflow/ColumnEditor/ModelRecordForm.tsx +163 -0
- package/UI/Components/Workflow/ModelColumnEditor.tsx +284 -100
- package/UI/Utils/NetworkAutomation/RunNetworkRule.ts +122 -0
- package/Utils/API.ts +243 -36
- package/Utils/Monitor/NetworkDeviceRoleUtil.ts +45 -0
- package/Utils/Monitor/NetworkTopologyUtil.ts +42 -2
- package/Utils/NetworkAutomation/RuleRunSummary.ts +147 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +45 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +115 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RunnerJob.js +17 -2
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
- package/build/dist/Server/API/LlmProviderAPI.js +9 -1
- package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js +33 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js +202 -0
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLinkService.js +253 -1
- package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +223 -12
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/RunnerJobService.js +20 -2
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js +12 -5
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js.map +1 -1
- package/build/dist/Server/Types/Database/JSONColumnQuery.js +40 -4
- package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +78 -22
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -98
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +7 -254
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +14 -2
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +27 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +285 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -0
- package/build/dist/Server/Utils/SSRFProtection.js +5 -4
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +128 -35
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
- package/build/dist/Types/API/Protocol.js +9 -0
- package/build/dist/Types/API/Protocol.js.map +1 -1
- package/build/dist/Types/API/URL.js +241 -18
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/Billing/PayAsYouGoPricing.js +41 -0
- package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldType.js +26 -0
- package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +10 -0
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +39 -0
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookStepType.js +21 -0
- package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
- package/build/dist/UI/Components/Button/Button.js +10 -16
- package/build/dist/UI/Components/Button/Button.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +2 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +2 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js +12 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +2 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js +17 -10
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/Dictionary.js +4 -18
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +1 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +3 -3
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +5 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js +14 -44
- package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +20 -2
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +42 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +174 -199
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/UI/Components/Pagination/PaginationUtil.js +160 -0
- package/build/dist/UI/Components/Pagination/PaginationUtil.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js +20 -2
- package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js +23 -47
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js.map +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js +14 -2
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js +86 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js +119 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js +206 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js +57 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js +150 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js +67 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js +309 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js +204 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +66 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js +83 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +187 -68
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -1
- package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js +75 -0
- package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js.map +1 -0
- package/build/dist/Utils/API.js +175 -34
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +37 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +25 -3
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js +78 -0
- package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,766 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* What the builder actually sees.
|
|
3
|
+
*
|
|
4
|
+
* The complaint this editor exists to answer was that it asked for things the
|
|
5
|
+
* model schema had already said: the column's name, typed as free text into a
|
|
6
|
+
* box labelled "Key", and the value's type, picked from a dropdown offering
|
|
7
|
+
* Text / Number / Boolean. The first two suites below are about those two
|
|
8
|
+
* questions being gone.
|
|
9
|
+
*
|
|
10
|
+
* The third is about the invariant that guards every saved workflow: this
|
|
11
|
+
* component is mounted every time someone opens a step's settings, so it must
|
|
12
|
+
* not report a change until a change is actually made.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import React from "react";
|
|
16
|
+
|
|
17
|
+
jest.mock("../../../../UI/Utils/API/API", () => {
|
|
18
|
+
return {
|
|
19
|
+
__esModule: true,
|
|
20
|
+
default: {
|
|
21
|
+
get: jest.fn(),
|
|
22
|
+
getFriendlyMessage: jest.fn((error: unknown) => {
|
|
23
|
+
return String(error);
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* Monaco cannot run in jsdom. A textarea that echoes the value is enough to
|
|
31
|
+
* assert what the JSON view was handed and to type into it.
|
|
32
|
+
*/
|
|
33
|
+
jest.mock("@monaco-editor/react", () => {
|
|
34
|
+
return {
|
|
35
|
+
__esModule: true,
|
|
36
|
+
loader: { config: jest.fn() },
|
|
37
|
+
default: (editorProps: {
|
|
38
|
+
value?: string | undefined;
|
|
39
|
+
onChange?: ((value: string | undefined) => void) | undefined;
|
|
40
|
+
}) => {
|
|
41
|
+
return (
|
|
42
|
+
<textarea
|
|
43
|
+
data-testid="monaco"
|
|
44
|
+
value={editorProps.value || ""}
|
|
45
|
+
onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
46
|
+
editorProps.onChange?.(event.target.value);
|
|
47
|
+
}}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
import HTTPErrorResponse from "../../../../Types/API/HTTPErrorResponse";
|
|
55
|
+
import HTTPResponse from "../../../../Types/API/HTTPResponse";
|
|
56
|
+
import TableColumnType from "../../../../Types/Database/TableColumnType";
|
|
57
|
+
import { JSONObject } from "../../../../Types/JSON";
|
|
58
|
+
import ModelColumnEditor, {
|
|
59
|
+
ModelColumnEditorMode,
|
|
60
|
+
RecordIntent,
|
|
61
|
+
} from "../../../../UI/Components/Workflow/ModelColumnEditor";
|
|
62
|
+
import { ModelSchemaColumn } from "../../../../UI/Components/Workflow/ModelSchema";
|
|
63
|
+
import API from "../../../../UI/Utils/API/API";
|
|
64
|
+
import getJestMockFunction, { MockFunction } from "../../../MockType";
|
|
65
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
66
|
+
import "@testing-library/jest-dom";
|
|
67
|
+
import {
|
|
68
|
+
RenderResult,
|
|
69
|
+
fireEvent,
|
|
70
|
+
render,
|
|
71
|
+
waitFor,
|
|
72
|
+
} from "@testing-library/react";
|
|
73
|
+
|
|
74
|
+
type MakeColumnFunction = (
|
|
75
|
+
overrides: Partial<ModelSchemaColumn> & { id: string },
|
|
76
|
+
) => ModelSchemaColumn;
|
|
77
|
+
|
|
78
|
+
const makeColumn: MakeColumnFunction = (
|
|
79
|
+
overrides: Partial<ModelSchemaColumn> & { id: string },
|
|
80
|
+
): ModelSchemaColumn => {
|
|
81
|
+
return {
|
|
82
|
+
title: overrides.id,
|
|
83
|
+
type: TableColumnType.ShortText,
|
|
84
|
+
isRelation: false,
|
|
85
|
+
...overrides,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const COLUMNS: Array<ModelSchemaColumn> = [
|
|
90
|
+
makeColumn({
|
|
91
|
+
id: "_id",
|
|
92
|
+
title: "ID",
|
|
93
|
+
type: TableColumnType.ObjectID,
|
|
94
|
+
description: "The unique ID of this Incident State.",
|
|
95
|
+
}),
|
|
96
|
+
makeColumn({
|
|
97
|
+
id: "name",
|
|
98
|
+
title: "Name",
|
|
99
|
+
type: TableColumnType.Name,
|
|
100
|
+
required: true,
|
|
101
|
+
description: "The name of this Incident State.",
|
|
102
|
+
}),
|
|
103
|
+
makeColumn({
|
|
104
|
+
id: "color",
|
|
105
|
+
title: "Color",
|
|
106
|
+
type: TableColumnType.Color,
|
|
107
|
+
required: true,
|
|
108
|
+
description: "The color shown against this state.",
|
|
109
|
+
}),
|
|
110
|
+
makeColumn({
|
|
111
|
+
id: "order",
|
|
112
|
+
title: "Order",
|
|
113
|
+
type: TableColumnType.Number,
|
|
114
|
+
description: "Where this state sits in the list.",
|
|
115
|
+
}),
|
|
116
|
+
makeColumn({
|
|
117
|
+
id: "isResolvedState",
|
|
118
|
+
title: "Is Resolved State",
|
|
119
|
+
type: TableColumnType.Boolean,
|
|
120
|
+
description: "Whether reaching this state resolves the incident.",
|
|
121
|
+
}),
|
|
122
|
+
makeColumn({
|
|
123
|
+
id: "createdAt",
|
|
124
|
+
title: "Created At",
|
|
125
|
+
type: TableColumnType.Date,
|
|
126
|
+
description: "When this row was created.",
|
|
127
|
+
}),
|
|
128
|
+
makeColumn({
|
|
129
|
+
id: "projectId",
|
|
130
|
+
title: "Project ID",
|
|
131
|
+
type: TableColumnType.ObjectID,
|
|
132
|
+
isTenantColumn: true,
|
|
133
|
+
}),
|
|
134
|
+
makeColumn({
|
|
135
|
+
id: "monitorSteps",
|
|
136
|
+
title: "Monitor Steps",
|
|
137
|
+
type: TableColumnType.MonitorSteps,
|
|
138
|
+
}),
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
type MockApiGetFunction = () => MockFunction;
|
|
142
|
+
|
|
143
|
+
const apiGet: MockApiGetFunction = (): MockFunction => {
|
|
144
|
+
return API.get as unknown as MockFunction;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
type ResolveSchemaFunction = (columns?: Array<ModelSchemaColumn>) => void;
|
|
148
|
+
|
|
149
|
+
const resolveSchemaWith: ResolveSchemaFunction = (
|
|
150
|
+
columns?: Array<ModelSchemaColumn>,
|
|
151
|
+
): void => {
|
|
152
|
+
apiGet().mockResolvedValue(
|
|
153
|
+
new HTTPResponse<JSONObject>(
|
|
154
|
+
200,
|
|
155
|
+
{ columns: columns || COLUMNS } as unknown as JSONObject,
|
|
156
|
+
{},
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
type RenderEditorProps = {
|
|
162
|
+
mode?: ModelColumnEditorMode | undefined;
|
|
163
|
+
initialValue?: string | undefined;
|
|
164
|
+
recordIntent?: RecordIntent | undefined;
|
|
165
|
+
onChange?: MockFunction | undefined;
|
|
166
|
+
valueSuggestions?: Array<string> | undefined;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
type RenderEditorFunction = (props?: RenderEditorProps) => RenderResult;
|
|
170
|
+
|
|
171
|
+
const renderEditor: RenderEditorFunction = (
|
|
172
|
+
props?: RenderEditorProps,
|
|
173
|
+
): RenderResult => {
|
|
174
|
+
return render(
|
|
175
|
+
<ModelColumnEditor
|
|
176
|
+
tableName="IncidentState"
|
|
177
|
+
mode={props?.mode || ModelColumnEditorMode.Record}
|
|
178
|
+
initialValue={props?.initialValue}
|
|
179
|
+
recordIntent={props?.recordIntent}
|
|
180
|
+
valueSuggestions={props?.valueSuggestions}
|
|
181
|
+
onChange={
|
|
182
|
+
(props?.onChange as unknown as (value: string) => void) ||
|
|
183
|
+
((): void => {})
|
|
184
|
+
}
|
|
185
|
+
/>,
|
|
186
|
+
);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
beforeEach(() => {
|
|
190
|
+
resolveSchemaWith();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
afterEach(() => {
|
|
194
|
+
apiGet().mockReset();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe("the schema is used instead of being asked for", () => {
|
|
198
|
+
test("fields are labelled with the model's own titles and descriptions", async () => {
|
|
199
|
+
const { findByText, getByText }: RenderResult = renderEditor();
|
|
200
|
+
|
|
201
|
+
expect(await findByText("Name")).toBeInTheDocument();
|
|
202
|
+
expect(getByText("Color")).toBeInTheDocument();
|
|
203
|
+
expect(getByText("The name of this Incident State.")).toBeInTheDocument();
|
|
204
|
+
expect(
|
|
205
|
+
getByText("The color shown against this state."),
|
|
206
|
+
).toBeInTheDocument();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("the column name is shown, not asked for", async () => {
|
|
210
|
+
const { findByText, queryByPlaceholderText }: RenderResult = renderEditor();
|
|
211
|
+
|
|
212
|
+
await findByText("Name");
|
|
213
|
+
|
|
214
|
+
/*
|
|
215
|
+
* "Column" was the placeholder of the free-text key box. Its absence is the
|
|
216
|
+
* complaint being answered.
|
|
217
|
+
*/
|
|
218
|
+
expect(queryByPlaceholderText("Column")).toBeNull();
|
|
219
|
+
expect(queryByPlaceholderText("Key")).toBeNull();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("nothing anywhere asks what type the value is", async () => {
|
|
223
|
+
const { findByText, queryByText }: RenderResult = renderEditor();
|
|
224
|
+
|
|
225
|
+
await findByText("Name");
|
|
226
|
+
|
|
227
|
+
/*
|
|
228
|
+
* The old Type dropdown offered these three against every column, next to a
|
|
229
|
+
* schema that already knew. If any of them is on screen it is back.
|
|
230
|
+
*/
|
|
231
|
+
expect(queryByText("Type")).toBeNull();
|
|
232
|
+
expect(queryByText("Number")).toBeNull();
|
|
233
|
+
expect(queryByText("Boolean")).toBeNull();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("each field says what kind of value it wants, from the schema", async () => {
|
|
237
|
+
const { findByText, getByText }: RenderResult = renderEditor({
|
|
238
|
+
initialValue: '{"order":1,"isResolvedState":true,"createdAt":null}',
|
|
239
|
+
recordIntent: RecordIntent.Update,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
expect(await findByText("Order")).toBeInTheDocument();
|
|
243
|
+
expect(getByText("Number")).toBeInTheDocument();
|
|
244
|
+
expect(getByText("True or false")).toBeInTheDocument();
|
|
245
|
+
expect(getByText("Date and time")).toBeInTheDocument();
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
describe("the control comes from the column type", () => {
|
|
250
|
+
test("a number column gets a number input", async () => {
|
|
251
|
+
const { findByTestId }: RenderResult = renderEditor({
|
|
252
|
+
initialValue: '{"order":3}',
|
|
253
|
+
recordIntent: RecordIntent.Update,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const input: HTMLElement = await findByTestId("model-column-value-order");
|
|
257
|
+
|
|
258
|
+
expect(input).toHaveAttribute("type", "number");
|
|
259
|
+
|
|
260
|
+
/*
|
|
261
|
+
* The shared Input writes its value into the DOM from an effect rather than
|
|
262
|
+
* from a value prop, so this settles a render after the element appears.
|
|
263
|
+
*/
|
|
264
|
+
await waitFor(() => {
|
|
265
|
+
expect(input).toHaveValue(3);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("a date column gets a date-and-time input", async () => {
|
|
270
|
+
const { findByTestId }: RenderResult = renderEditor({
|
|
271
|
+
initialValue: '{"createdAt":"2026-08-15T09:30:00.000Z"}',
|
|
272
|
+
recordIntent: RecordIntent.Update,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
expect(await findByTestId("model-column-value-createdAt")).toHaveAttribute(
|
|
276
|
+
"type",
|
|
277
|
+
"datetime-local",
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test('a boolean column gets three states, so "not set" can be said', async () => {
|
|
282
|
+
const { findByText, getByText }: RenderResult = renderEditor({
|
|
283
|
+
initialValue: '{"isResolvedState":true}',
|
|
284
|
+
recordIntent: RecordIntent.Update,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
expect(await findByText("True")).toBeInTheDocument();
|
|
288
|
+
expect(getByText("False")).toBeInTheDocument();
|
|
289
|
+
expect(getByText("Not set")).toBeInTheDocument();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("a column no row can hold is named rather than silently missing", async () => {
|
|
293
|
+
const { findByText }: RenderResult = renderEditor();
|
|
294
|
+
|
|
295
|
+
expect(
|
|
296
|
+
await findByText(/Monitor Steps.*can only be set with Edit as JSON/),
|
|
297
|
+
).toBeInTheDocument();
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe("opening a step does not rewrite what it does", () => {
|
|
302
|
+
test("nothing is reported while the schema is still loading", async () => {
|
|
303
|
+
const onChange: MockFunction = getJestMockFunction();
|
|
304
|
+
|
|
305
|
+
renderEditor({ onChange: onChange });
|
|
306
|
+
|
|
307
|
+
await waitFor(() => {
|
|
308
|
+
expect(apiGet()).toHaveBeenCalled();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("nothing is reported after the schema resolves and rows are seeded", async () => {
|
|
315
|
+
const onChange: MockFunction = getJestMockFunction();
|
|
316
|
+
|
|
317
|
+
const { findByText }: RenderResult = renderEditor({ onChange: onChange });
|
|
318
|
+
|
|
319
|
+
await findByText("Name");
|
|
320
|
+
|
|
321
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test("nothing is reported when a stored value is loaded", async () => {
|
|
325
|
+
const onChange: MockFunction = getJestMockFunction();
|
|
326
|
+
|
|
327
|
+
const { findByText }: RenderResult = renderEditor({
|
|
328
|
+
initialValue: '{"name":"Acknowledged","color":"#ffffff"}',
|
|
329
|
+
onChange: onChange,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
await findByText("Name");
|
|
333
|
+
|
|
334
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("nothing is reported by switching to JSON and back", async () => {
|
|
338
|
+
const onChange: MockFunction = getJestMockFunction();
|
|
339
|
+
|
|
340
|
+
const { findByTestId, getByTestId }: RenderResult = renderEditor({
|
|
341
|
+
initialValue: '{"name":"Acknowledged"}',
|
|
342
|
+
onChange: onChange,
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
fireEvent.click(await findByTestId("model-column-json-toggle"));
|
|
346
|
+
fireEvent.click(getByTestId("model-column-json-toggle"));
|
|
347
|
+
|
|
348
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("editing a value is reported, with the exact JSON that will be stored", async () => {
|
|
352
|
+
const onChange: MockFunction = getJestMockFunction();
|
|
353
|
+
|
|
354
|
+
const { findByTestId }: RenderResult = renderEditor({
|
|
355
|
+
initialValue: '{"name":"Acknowledged"}',
|
|
356
|
+
recordIntent: RecordIntent.Update,
|
|
357
|
+
onChange: onChange,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
fireEvent.change(await findByTestId("model-column-value-name"), {
|
|
361
|
+
target: { value: "Resolved" },
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
expect(onChange).toHaveBeenCalledWith('{"name":"Resolved"}');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test("a number is stored as a number, not as the text that was typed", async () => {
|
|
368
|
+
const onChange: MockFunction = getJestMockFunction();
|
|
369
|
+
|
|
370
|
+
const { findByTestId }: RenderResult = renderEditor({
|
|
371
|
+
initialValue: '{"order":1}',
|
|
372
|
+
recordIntent: RecordIntent.Update,
|
|
373
|
+
onChange: onChange,
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
fireEvent.change(await findByTestId("model-column-value-order"), {
|
|
377
|
+
target: { value: "7" },
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
expect(onChange).toHaveBeenCalledWith('{"order":7}');
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test("a boolean is stored as a boolean", async () => {
|
|
384
|
+
const onChange: MockFunction = getJestMockFunction();
|
|
385
|
+
|
|
386
|
+
const { findByText }: RenderResult = renderEditor({
|
|
387
|
+
initialValue: '{"isResolvedState":true}',
|
|
388
|
+
recordIntent: RecordIntent.Update,
|
|
389
|
+
onChange: onChange,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
fireEvent.click(await findByText("False"));
|
|
393
|
+
|
|
394
|
+
expect(onChange).toHaveBeenCalledWith('{"isResolvedState":false}');
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
describe("a create opens on the fields it needs; an update does not", () => {
|
|
399
|
+
test("a create opens with a row for every column it must be given", async () => {
|
|
400
|
+
const { findByText, getByText }: RenderResult = renderEditor({
|
|
401
|
+
recordIntent: RecordIntent.Create,
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
expect(await findByText("Name")).toBeInTheDocument();
|
|
405
|
+
expect(getByText("Color")).toBeInTheDocument();
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
test("and says how many of them are still empty", async () => {
|
|
409
|
+
const { findByText }: RenderResult = renderEditor({
|
|
410
|
+
recordIntent: RecordIntent.Create,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
expect(
|
|
414
|
+
await findByText(/2 required fields still empty/),
|
|
415
|
+
).toBeInTheDocument();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test("an update opens on nothing, because it legitimately writes one column", async () => {
|
|
419
|
+
const { findByText, queryByText }: RenderResult = renderEditor({
|
|
420
|
+
recordIntent: RecordIntent.Update,
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
expect(await findByText("No fields set yet")).toBeInTheDocument();
|
|
424
|
+
expect(queryByText("Color")).toBeNull();
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
test("the project column is never seeded — the workflow stamps it itself", async () => {
|
|
428
|
+
const { findByText, queryByText }: RenderResult = renderEditor({
|
|
429
|
+
recordIntent: RecordIntent.Create,
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
await findByText("Name");
|
|
433
|
+
|
|
434
|
+
expect(queryByText("Project ID")).toBeNull();
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
describe("editing as JSON", () => {
|
|
439
|
+
test("the JSON view is offered and shows what is stored", async () => {
|
|
440
|
+
const { findByTestId, getByTestId }: RenderResult = renderEditor({
|
|
441
|
+
initialValue: '{"name":"Acknowledged"}',
|
|
442
|
+
recordIntent: RecordIntent.Update,
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
fireEvent.click(await findByTestId("model-column-json-toggle"));
|
|
446
|
+
|
|
447
|
+
expect(getByTestId("monaco")).toHaveValue('{"name":"Acknowledged"}');
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test("a key added in the JSON view is there as a field on the way back", async () => {
|
|
451
|
+
/*
|
|
452
|
+
* The rows used to be seeded once and never re-read, so this edit was
|
|
453
|
+
* silently discarded and the next keystroke in any row wrote the pre-edit
|
|
454
|
+
* content back over it.
|
|
455
|
+
*/
|
|
456
|
+
const { findByTestId, getByTestId, getByText }: RenderResult = renderEditor(
|
|
457
|
+
{
|
|
458
|
+
initialValue: '{"name":"Acknowledged"}',
|
|
459
|
+
recordIntent: RecordIntent.Update,
|
|
460
|
+
},
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
fireEvent.click(await findByTestId("model-column-json-toggle"));
|
|
464
|
+
fireEvent.change(getByTestId("monaco"), {
|
|
465
|
+
target: { value: '{"name":"Acknowledged","color":"#ffffff"}' },
|
|
466
|
+
});
|
|
467
|
+
fireEvent.click(getByTestId("model-column-json-toggle"));
|
|
468
|
+
|
|
469
|
+
expect(getByText("Color")).toBeInTheDocument();
|
|
470
|
+
await waitFor(() => {
|
|
471
|
+
expect(getByTestId("model-column-value-color")).toHaveValue("#ffffff");
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
test("an edit rows cannot show refuses the trip back rather than losing it", async () => {
|
|
476
|
+
const { findByTestId, getByTestId, getByText }: RenderResult = renderEditor(
|
|
477
|
+
{
|
|
478
|
+
initialValue: '{"name":"Acknowledged"}',
|
|
479
|
+
recordIntent: RecordIntent.Update,
|
|
480
|
+
},
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
fireEvent.click(await findByTestId("model-column-json-toggle"));
|
|
484
|
+
fireEvent.change(getByTestId("monaco"), {
|
|
485
|
+
target: { value: '{"name":{"nested":"value"}}' },
|
|
486
|
+
});
|
|
487
|
+
fireEvent.click(getByTestId("model-column-json-toggle"));
|
|
488
|
+
|
|
489
|
+
// Still on JSON, with the edit intact and a reason given.
|
|
490
|
+
expect(getByTestId("monaco")).toHaveValue('{"name":{"nested":"value"}}');
|
|
491
|
+
expect(getByText(/can't go back to fields/)).toBeInTheDocument();
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
test("a value rows cannot show at all opens on JSON with the reason on screen", async () => {
|
|
495
|
+
const { findByText, queryByTestId }: RenderResult = renderEditor({
|
|
496
|
+
initialValue: '{"labels":["a","b"]}',
|
|
497
|
+
recordIntent: RecordIntent.Update,
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
expect(await findByText(/Editing as JSON, because/)).toBeInTheDocument();
|
|
501
|
+
expect(queryByTestId("model-column-json-toggle")).toBeNull();
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
describe("values the rows keep faithfully", () => {
|
|
506
|
+
test("a column this model does not describe keeps its row and is flagged", async () => {
|
|
507
|
+
const { findByText, getByDisplayValue }: RenderResult = renderEditor({
|
|
508
|
+
initialValue: '{"id":"00000000-0000-0000-0000-000000000000"}',
|
|
509
|
+
recordIntent: RecordIntent.Update,
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// The "id" versus "_id" mistake has to stay visible, and fixable.
|
|
513
|
+
expect(await findByText("Not a column on this model")).toBeInTheDocument();
|
|
514
|
+
expect(getByDisplayValue("id")).toBeInTheDocument();
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test("a stored value whose type disagrees with the column is left alone", async () => {
|
|
518
|
+
const { findByText }: RenderResult = renderEditor({
|
|
519
|
+
initialValue: '{"order":"3"}',
|
|
520
|
+
recordIntent: RecordIntent.Update,
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
expect(
|
|
524
|
+
await findByText(/Kept exactly as it was saved/),
|
|
525
|
+
).toBeInTheDocument();
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
test("a reference is shown as a reference on a numeric column", async () => {
|
|
529
|
+
const { findByDisplayValue }: RenderResult = renderEditor({
|
|
530
|
+
initialValue: '{"order":"{{local.variables.position}}"}',
|
|
531
|
+
recordIntent: RecordIntent.Update,
|
|
532
|
+
valueSuggestions: ["{{local.variables.position}}"],
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
expect(
|
|
536
|
+
await findByDisplayValue("{{local.variables.position}}"),
|
|
537
|
+
).toBeInTheDocument();
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
describe("query mode", () => {
|
|
542
|
+
test("conditions get a real column picker and a comparison, not a type", async () => {
|
|
543
|
+
const { findByText, queryByText }: RenderResult = renderEditor({
|
|
544
|
+
mode: ModelColumnEditorMode.Query,
|
|
545
|
+
initialValue: '{"name":"Acknowledged"}',
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
expect(await findByText("Column")).toBeInTheDocument();
|
|
549
|
+
expect(await findByText("Comparison")).toBeInTheDocument();
|
|
550
|
+
expect(queryByText("Type")).toBeNull();
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
test("an empty query says what that means, which on a delete matters", async () => {
|
|
554
|
+
const { findByText, getByText }: RenderResult = renderEditor({
|
|
555
|
+
mode: ModelColumnEditorMode.Query,
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
expect(await findByText("No conditions yet")).toBeInTheDocument();
|
|
559
|
+
expect(
|
|
560
|
+
getByText("With no conditions this matches every record in the project."),
|
|
561
|
+
).toBeInTheDocument();
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
test("a query is never seeded with required fields — it is not a record", async () => {
|
|
565
|
+
const { findByText }: RenderResult = renderEditor({
|
|
566
|
+
mode: ModelColumnEditorMode.Query,
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
expect(await findByText("No conditions yet")).toBeInTheDocument();
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
describe("when the schema cannot be loaded", () => {
|
|
574
|
+
test("the failure is shown and the value stays editable", async () => {
|
|
575
|
+
apiGet().mockResolvedValue(
|
|
576
|
+
new HTTPErrorResponse(500, { message: "boom" }, {}),
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
const { findByText, getByTestId }: RenderResult = renderEditor({
|
|
580
|
+
initialValue: '{"name":"Acknowledged"}',
|
|
581
|
+
recordIntent: RecordIntent.Update,
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
expect(
|
|
585
|
+
await findByText(/Couldn't load this model's columns/),
|
|
586
|
+
).toBeInTheDocument();
|
|
587
|
+
|
|
588
|
+
// The escape hatch is still there, which is the whole point of keeping it.
|
|
589
|
+
expect(getByTestId("model-column-json-toggle")).toBeInTheDocument();
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
test("a stored field still gets a row, with its key editable", async () => {
|
|
593
|
+
apiGet().mockResolvedValue(
|
|
594
|
+
new HTTPErrorResponse(500, { message: "boom" }, {}),
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
const { findByDisplayValue }: RenderResult = renderEditor({
|
|
598
|
+
initialValue: '{"name":"Acknowledged"}',
|
|
599
|
+
recordIntent: RecordIntent.Update,
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
expect(await findByDisplayValue("name")).toBeInTheDocument();
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
describe("an edit reaches the workflow, whatever the cell was holding", () => {
|
|
607
|
+
test("typing over a value kept raw stores what was typed", async () => {
|
|
608
|
+
/*
|
|
609
|
+
* The row for a stored value the column's own control cannot hold re-emits
|
|
610
|
+
* that value verbatim until it is edited. Reporting the mode change and the
|
|
611
|
+
* text change separately made the edit permanently impossible: both reports
|
|
612
|
+
* were built from the same render's row, so the second undid the first and
|
|
613
|
+
* the stored value won on every keystroke, while the box showed the new one.
|
|
614
|
+
*/
|
|
615
|
+
const onChange: MockFunction = getJestMockFunction();
|
|
616
|
+
|
|
617
|
+
const { findByTestId }: RenderResult = renderEditor({
|
|
618
|
+
initialValue: '{"order":"3"}',
|
|
619
|
+
recordIntent: RecordIntent.Update,
|
|
620
|
+
onChange: onChange,
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
fireEvent.change(await findByTestId("model-column-value-order"), {
|
|
624
|
+
target: { value: "7" },
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
628
|
+
expect(onChange).toHaveBeenLastCalledWith('{"order":7}');
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
test("typing into a field stored as null stores what was typed", async () => {
|
|
632
|
+
const onChange: MockFunction = getJestMockFunction();
|
|
633
|
+
|
|
634
|
+
const { findByTestId }: RenderResult = renderEditor({
|
|
635
|
+
initialValue: '{"name":null}',
|
|
636
|
+
recordIntent: RecordIntent.Update,
|
|
637
|
+
onChange: onChange,
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
fireEvent.change(await findByTestId("model-column-value-name"), {
|
|
641
|
+
target: { value: "Acknowledged" },
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
expect(onChange).toHaveBeenLastCalledWith('{"name":"Acknowledged"}');
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
test("typing {{ turns the cell into a reference there and then", async () => {
|
|
648
|
+
const { findByTestId, getByTestId }: RenderResult = renderEditor({
|
|
649
|
+
initialValue: '{"name":"Acknowledged"}',
|
|
650
|
+
recordIntent: RecordIntent.Update,
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
fireEvent.change(await findByTestId("model-column-value-name"), {
|
|
654
|
+
target: { value: "{{local.variables.stateName}}" },
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
expect(
|
|
658
|
+
getByTestId("model-column-value-name-reference-toggle"),
|
|
659
|
+
).toHaveAttribute("aria-pressed", "true");
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
test("and the reference is what gets stored", async () => {
|
|
663
|
+
const onChange: MockFunction = getJestMockFunction();
|
|
664
|
+
|
|
665
|
+
const { findByTestId }: RenderResult = renderEditor({
|
|
666
|
+
initialValue: '{"name":"Acknowledged"}',
|
|
667
|
+
recordIntent: RecordIntent.Update,
|
|
668
|
+
onChange: onChange,
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
fireEvent.change(await findByTestId("model-column-value-name"), {
|
|
672
|
+
target: { value: "{{local.variables.stateName}}" },
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
expect(onChange).toHaveBeenLastCalledWith(
|
|
676
|
+
'{"name":"{{local.variables.stateName}}"}',
|
|
677
|
+
);
|
|
678
|
+
});
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
describe("typing a column name does not throw the row away", () => {
|
|
682
|
+
test("the key box keeps focus while it is being typed in", async () => {
|
|
683
|
+
/*
|
|
684
|
+
* The row used to be keyed on the column name it edits, so every keystroke
|
|
685
|
+
* gave it a new identity, unmounted it, and detached the focused input.
|
|
686
|
+
*/
|
|
687
|
+
const { findByDisplayValue }: RenderResult = renderEditor({
|
|
688
|
+
initialValue: '{"id":"abc"}',
|
|
689
|
+
recordIntent: RecordIntent.Update,
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
const keyInput: HTMLElement = await findByDisplayValue("id");
|
|
693
|
+
keyInput.focus();
|
|
694
|
+
|
|
695
|
+
expect(document.activeElement).toBe(keyInput);
|
|
696
|
+
|
|
697
|
+
fireEvent.change(keyInput, { target: { value: "_i" } });
|
|
698
|
+
|
|
699
|
+
expect(document.body.contains(keyInput)).toBe(true);
|
|
700
|
+
expect(document.activeElement).toBe(keyInput);
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
test("correcting the column name clears the warning about it", async () => {
|
|
704
|
+
const { findByText, findByDisplayValue, queryByText }: RenderResult =
|
|
705
|
+
renderEditor({
|
|
706
|
+
initialValue: '{"id":"abc"}',
|
|
707
|
+
recordIntent: RecordIntent.Update,
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
expect(
|
|
711
|
+
await findByText(/"id" isn't a known column on this model/),
|
|
712
|
+
).toBeInTheDocument();
|
|
713
|
+
|
|
714
|
+
fireEvent.change(await findByDisplayValue("id"), {
|
|
715
|
+
target: { value: "_id" },
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
expect(queryByText(/isn't a known column/)).toBeNull();
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
describe("the JSON view does not lose what the editor opened with", () => {
|
|
723
|
+
test("a create's required rows survive the trip through JSON", async () => {
|
|
724
|
+
/*
|
|
725
|
+
* The rows were seeded only on the way in, so the first look at the JSON
|
|
726
|
+
* left a create with no fields on screen and nothing saying which ones it
|
|
727
|
+
* needs.
|
|
728
|
+
*/
|
|
729
|
+
const { findByTestId, getByTestId, getByText }: RenderResult = renderEditor(
|
|
730
|
+
{
|
|
731
|
+
recordIntent: RecordIntent.Create,
|
|
732
|
+
},
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
fireEvent.click(await findByTestId("model-column-json-toggle"));
|
|
736
|
+
fireEvent.click(getByTestId("model-column-json-toggle"));
|
|
737
|
+
|
|
738
|
+
expect(getByText("Name")).toBeInTheDocument();
|
|
739
|
+
expect(getByText("Color")).toBeInTheDocument();
|
|
740
|
+
});
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
describe("query mode keeps showing what a condition is really about", () => {
|
|
744
|
+
test("a condition on a column the picker cannot offer still names it", async () => {
|
|
745
|
+
/*
|
|
746
|
+
* projectId is a real column that the picker deliberately withholds - the
|
|
747
|
+
* workflow stamps it itself. A condition stored against it was rendering as
|
|
748
|
+
* an empty "Column" placeholder over a filter that was really there.
|
|
749
|
+
*/
|
|
750
|
+
const { findByText }: RenderResult = renderEditor({
|
|
751
|
+
mode: ModelColumnEditorMode.Query,
|
|
752
|
+
initialValue: '{"projectId":"abc"}',
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
expect(await findByText("Project ID")).toBeInTheDocument();
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
test("conditions that take no value are still counted as set", async () => {
|
|
759
|
+
const { findByText }: RenderResult = renderEditor({
|
|
760
|
+
mode: ModelColumnEditorMode.Query,
|
|
761
|
+
initialValue: '{"createdAt":{"_type":"IsNull","value":null}}',
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
expect(await findByText(/1 condition set/)).toBeInTheDocument();
|
|
765
|
+
});
|
|
766
|
+
});
|