@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,284 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
3
|
+
import React, { ReactElement } from "react";
|
|
4
|
+
import {
|
|
5
|
+
afterEach,
|
|
6
|
+
beforeEach,
|
|
7
|
+
describe,
|
|
8
|
+
expect,
|
|
9
|
+
jest,
|
|
10
|
+
test,
|
|
11
|
+
} from "@jest/globals";
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* UserElement can only show an email the caller actually asked the API for,
|
|
15
|
+
* and an avatar it was given an id for. Two families of call sites failed one
|
|
16
|
+
* of those and would have kept rendering a bare name after the component
|
|
17
|
+
* change:
|
|
18
|
+
*
|
|
19
|
+
* - the notification-log tables passed `selectMoreFields={{user: {name}}}`.
|
|
20
|
+
* BaseModelTable's getSelectFields ASSIGNS selectMoreFields over the
|
|
21
|
+
* select the columns built, rather than merging into it, so the narrower
|
|
22
|
+
* one won and only the name was ever fetched.
|
|
23
|
+
* - the team compliance table hand-builds its user object and left out the
|
|
24
|
+
* id, so the avatar route could not be built at all.
|
|
25
|
+
*
|
|
26
|
+
* Both are invisible in a screenshot of a seeded dev project (short names, a
|
|
27
|
+
* default avatar) and silent in every other suite, so they are pinned here.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const BLANK_PROFILE_PIC: string = "/blank-profile.svg";
|
|
31
|
+
|
|
32
|
+
jest.mock("../../../UI/Images/users/blank-profile.svg", () => {
|
|
33
|
+
return BLANK_PROFILE_PIC;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
jest.mock("../../../UI/Utils/Permission", () => {
|
|
37
|
+
return {
|
|
38
|
+
__esModule: true,
|
|
39
|
+
default: {
|
|
40
|
+
getAllPermissions: () => {
|
|
41
|
+
return [];
|
|
42
|
+
},
|
|
43
|
+
getProjectPermissions: () => {
|
|
44
|
+
return [];
|
|
45
|
+
},
|
|
46
|
+
getGlobalPermissions: () => {
|
|
47
|
+
return [];
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/*
|
|
54
|
+
* The real ModelTable would drag in the facet bar, URL state and the pager.
|
|
55
|
+
* What matters here is the select it was handed and what its User column does
|
|
56
|
+
* with a row, so it is replaced by a recorder.
|
|
57
|
+
*/
|
|
58
|
+
type CapturedTableProps = {
|
|
59
|
+
selectMoreFields?: Record<string, unknown> | undefined;
|
|
60
|
+
columns: Array<{
|
|
61
|
+
title?: string | undefined;
|
|
62
|
+
field?: Record<string, unknown> | undefined;
|
|
63
|
+
getElement?: ((item: Record<string, unknown>) => ReactElement) | undefined;
|
|
64
|
+
}>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
let capturedTableProps: CapturedTableProps | null = null;
|
|
68
|
+
|
|
69
|
+
jest.mock("../../../UI/Components/ModelTable/ModelTable", () => {
|
|
70
|
+
return {
|
|
71
|
+
__esModule: true,
|
|
72
|
+
default: (props: CapturedTableProps) => {
|
|
73
|
+
capturedTableProps = props;
|
|
74
|
+
return null;
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
import CallLogsTable from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationLogs/CallLogsTable";
|
|
80
|
+
import EmailLogsTable from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationLogs/EmailLogsTable";
|
|
81
|
+
import PushLogsTable from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationLogs/PushLogsTable";
|
|
82
|
+
import SmsLogsTable from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationLogs/SmsLogsTable";
|
|
83
|
+
import TelegramLogsTable from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationLogs/TelegramLogsTable";
|
|
84
|
+
import WebhookLogsTable from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationLogs/WebhookLogsTable";
|
|
85
|
+
import WhatsAppLogsTable from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationLogs/WhatsAppLogsTable";
|
|
86
|
+
import WorkspaceLogsTable from "../../../../App/FeatureSet/Dashboard/src/Components/NotificationLogs/WorkspaceLogsTable";
|
|
87
|
+
import TeamComplianceStatusTable from "../../../../App/FeatureSet/Dashboard/src/Components/Team/TeamComplianceStatusTable";
|
|
88
|
+
import API from "../../../UI/Utils/API/API";
|
|
89
|
+
import ModelAPI from "../../../UI/Utils/ModelAPI/ModelAPI";
|
|
90
|
+
import ProjectUtil from "../../../UI/Utils/Project";
|
|
91
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
92
|
+
|
|
93
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
94
|
+
"00000000-0000-4000-8000-000000000001",
|
|
95
|
+
);
|
|
96
|
+
const TEAM_ID: ObjectID = new ObjectID("00000000-0000-4000-8000-000000000002");
|
|
97
|
+
const USER_ID: string = "00000000-0000-4000-8000-000000000003";
|
|
98
|
+
|
|
99
|
+
const NOTIFICATION_LOG_TABLES: Array<{
|
|
100
|
+
name: string;
|
|
101
|
+
Component: React.FunctionComponent<any>;
|
|
102
|
+
}> = [
|
|
103
|
+
{ name: "CallLogsTable", Component: CallLogsTable },
|
|
104
|
+
{ name: "EmailLogsTable", Component: EmailLogsTable },
|
|
105
|
+
{ name: "PushLogsTable", Component: PushLogsTable },
|
|
106
|
+
{ name: "SmsLogsTable", Component: SmsLogsTable },
|
|
107
|
+
{ name: "TelegramLogsTable", Component: TelegramLogsTable },
|
|
108
|
+
{ name: "WebhookLogsTable", Component: WebhookLogsTable },
|
|
109
|
+
{ name: "WhatsAppLogsTable", Component: WhatsAppLogsTable },
|
|
110
|
+
{ name: "WorkspaceLogsTable", Component: WorkspaceLogsTable },
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
type UserColumnFunction = () => CapturedTableProps["columns"][0];
|
|
114
|
+
|
|
115
|
+
const userColumn: UserColumnFunction = (): CapturedTableProps["columns"][0] => {
|
|
116
|
+
const column: CapturedTableProps["columns"][0] | undefined =
|
|
117
|
+
capturedTableProps?.columns.find(
|
|
118
|
+
(candidate: CapturedTableProps["columns"][0]) => {
|
|
119
|
+
return candidate.title === "User";
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (!column) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`No "User" column. Columns: ${(capturedTableProps?.columns || [])
|
|
126
|
+
.map((candidate: CapturedTableProps["columns"][0]) => {
|
|
127
|
+
return candidate.title;
|
|
128
|
+
})
|
|
129
|
+
.join(", ")}`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return column;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
describe("notification log tables", () => {
|
|
137
|
+
beforeEach(() => {
|
|
138
|
+
capturedTableProps = null;
|
|
139
|
+
jest.spyOn(ProjectUtil, "getCurrentProjectId").mockReturnValue(PROJECT_ID);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
afterEach(() => {
|
|
143
|
+
jest.restoreAllMocks();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe.each(NOTIFICATION_LOG_TABLES)(
|
|
147
|
+
"$name",
|
|
148
|
+
({ Component }: { Component: React.FunctionComponent<any> }) => {
|
|
149
|
+
beforeEach(() => {
|
|
150
|
+
render(<Component />);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
/*
|
|
154
|
+
* selectMoreFields overwrites the column's user select rather than
|
|
155
|
+
* merging with it. Narrowing it back to { name } silently drops the
|
|
156
|
+
* email off the wire and the row goes back to a bare name, with the
|
|
157
|
+
* column definition still (misleadingly) asking for the email.
|
|
158
|
+
*/
|
|
159
|
+
test("asks the API for the email, not just the name", () => {
|
|
160
|
+
expect(capturedTableProps!.selectMoreFields!["user"]).toEqual(
|
|
161
|
+
expect.objectContaining({ email: true }),
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("asks the API for the profile picture too", () => {
|
|
166
|
+
expect(capturedTableProps!.selectMoreFields!["user"]).toEqual(
|
|
167
|
+
expect.objectContaining({ profilePictureId: true }),
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
/*
|
|
172
|
+
* The two selects have to agree: whichever one wins, the column's
|
|
173
|
+
* getElement must have the fields it renders.
|
|
174
|
+
*/
|
|
175
|
+
test("selects the same user fields in the column and in selectMoreFields", () => {
|
|
176
|
+
expect(capturedTableProps!.selectMoreFields!["user"]).toEqual(
|
|
177
|
+
userColumn().field!["user"],
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("renders the name, the email and the avatar for a log row", () => {
|
|
182
|
+
render(
|
|
183
|
+
userColumn().getElement!({
|
|
184
|
+
user: {
|
|
185
|
+
_id: USER_ID,
|
|
186
|
+
name: "Jane Doe",
|
|
187
|
+
email: "jane@acme.com",
|
|
188
|
+
},
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
expect(screen.getByText("Jane Doe")).toBeInTheDocument();
|
|
193
|
+
expect(screen.getByTestId("user-email")).toHaveTextContent(
|
|
194
|
+
"jane@acme.com",
|
|
195
|
+
);
|
|
196
|
+
expect(screen.getAllByRole("img")[0]).toHaveAttribute(
|
|
197
|
+
"src",
|
|
198
|
+
`/api/user/profile-picture/${USER_ID}`,
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("still renders a dash for a log row with no user", () => {
|
|
203
|
+
render(userColumn().getElement!({}));
|
|
204
|
+
|
|
205
|
+
expect(screen.getByText("-")).toBeInTheDocument();
|
|
206
|
+
});
|
|
207
|
+
},
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe("team compliance status table", () => {
|
|
212
|
+
const complianceResponse: {
|
|
213
|
+
teamId: string;
|
|
214
|
+
teamName: string;
|
|
215
|
+
complianceSettings: Array<{ ruleType: string; enabled: boolean }>;
|
|
216
|
+
userComplianceStatuses: Array<{
|
|
217
|
+
userId: string;
|
|
218
|
+
userName: string;
|
|
219
|
+
userEmail: string;
|
|
220
|
+
isCompliant: boolean;
|
|
221
|
+
nonCompliantRules: Array<{ ruleType: string; reason: string }>;
|
|
222
|
+
}>;
|
|
223
|
+
} = {
|
|
224
|
+
teamId: TEAM_ID.toString(),
|
|
225
|
+
teamName: "On-Call",
|
|
226
|
+
complianceSettings: [
|
|
227
|
+
{ ruleType: "HasNotificationEmailMethod", enabled: true },
|
|
228
|
+
],
|
|
229
|
+
userComplianceStatuses: [
|
|
230
|
+
{
|
|
231
|
+
userId: USER_ID,
|
|
232
|
+
userName: "Jane Doe",
|
|
233
|
+
userEmail: "jane@acme.com",
|
|
234
|
+
isCompliant: false,
|
|
235
|
+
nonCompliantRules: [
|
|
236
|
+
{
|
|
237
|
+
ruleType: "HasNotificationEmailMethod",
|
|
238
|
+
reason: "No email notification method",
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
beforeEach(async () => {
|
|
246
|
+
jest.spyOn(ModelAPI, "getCommonHeaders").mockReturnValue({});
|
|
247
|
+
jest
|
|
248
|
+
.spyOn(API, "get")
|
|
249
|
+
.mockResolvedValue({ data: complianceResponse } as never);
|
|
250
|
+
|
|
251
|
+
render(<TeamComplianceStatusTable teamId={TEAM_ID} />);
|
|
252
|
+
|
|
253
|
+
await waitFor(() => {
|
|
254
|
+
expect(screen.getByText("Jane Doe")).toBeInTheDocument();
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
afterEach(() => {
|
|
259
|
+
jest.restoreAllMocks();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("shows the member's email beside their name", () => {
|
|
263
|
+
expect(screen.getByTestId("user-email")).toHaveTextContent("jane@acme.com");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
/*
|
|
267
|
+
* The row is hand-built from the compliance payload. Without the id the
|
|
268
|
+
* avatar route cannot be built and every member falls back to the blank
|
|
269
|
+
* picture, which reads as "nobody has a photo" rather than as a bug.
|
|
270
|
+
*/
|
|
271
|
+
test("builds the avatar from the member's own id", () => {
|
|
272
|
+
expect(screen.getAllByRole("img")[0]).toHaveAttribute(
|
|
273
|
+
"src",
|
|
274
|
+
`/api/user/profile-picture/${USER_ID}`,
|
|
275
|
+
);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("still shows why the member is non-compliant", () => {
|
|
279
|
+
expect(screen.getByText("Non-Compliant")).toBeInTheDocument();
|
|
280
|
+
expect(
|
|
281
|
+
screen.getByText(/No email notification method/),
|
|
282
|
+
).toBeInTheDocument();
|
|
283
|
+
});
|
|
284
|
+
});
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import CommonModel from "../../../Models/AnalyticsModels/AnalyticsBaseModel/CommonModel";
|
|
2
|
+
import AnalyticsTableColumn from "../../../Types/AnalyticsDatabase/TableColumn";
|
|
3
|
+
import TableColumnType from "../../../Types/AnalyticsDatabase/TableColumnType";
|
|
4
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
5
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* Unit tests for CommonModel — the base every analytics (ClickHouse) model
|
|
9
|
+
* extends. It owns the column-aware value coercion (setColumnValue) and the
|
|
10
|
+
* JSON (de)serialization that the analytics query/ingest layer relies on.
|
|
11
|
+
*
|
|
12
|
+
* Nothing imports CommonModel directly in the existing test suite, so its
|
|
13
|
+
* per-type string coercion and its static/instance JSON helpers were
|
|
14
|
+
* uncovered. The logic is pure: it reads column metadata off the instance,
|
|
15
|
+
* so no ClickHouse connection is required.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// Build a column of a given type. Only key + type matter for these tests.
|
|
19
|
+
const column: (key: string, type: TableColumnType) => AnalyticsTableColumn = (
|
|
20
|
+
key: string,
|
|
21
|
+
type: TableColumnType,
|
|
22
|
+
): AnalyticsTableColumn => {
|
|
23
|
+
return new AnalyticsTableColumn({
|
|
24
|
+
key,
|
|
25
|
+
title: key,
|
|
26
|
+
description: `${key} column`,
|
|
27
|
+
required: false,
|
|
28
|
+
type,
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const COLUMNS: Array<AnalyticsTableColumn> = [
|
|
33
|
+
column("name", TableColumnType.Text),
|
|
34
|
+
column("resourceId", TableColumnType.ObjectID),
|
|
35
|
+
column("startsAt", TableColumnType.Date),
|
|
36
|
+
column("observedAt", TableColumnType.DateTime64),
|
|
37
|
+
column("attributes", TableColumnType.JSON),
|
|
38
|
+
column("tags", TableColumnType.JSONArray),
|
|
39
|
+
column("count", TableColumnType.Number),
|
|
40
|
+
column("ratio", TableColumnType.Decimal),
|
|
41
|
+
column("bigCount", TableColumnType.LongNumber),
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
* A concrete model with a no-arg constructor so the static new type() paths
|
|
46
|
+
* (fromJSON / fromJSONArray) work.
|
|
47
|
+
*/
|
|
48
|
+
class TestModel extends CommonModel {
|
|
49
|
+
public constructor() {
|
|
50
|
+
super({ tableColumns: COLUMNS });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const newModel: () => TestModel = (): TestModel => {
|
|
55
|
+
return new TestModel();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
describe("CommonModel.getTableColumn", () => {
|
|
59
|
+
test("returns the column when it exists", () => {
|
|
60
|
+
const model: TestModel = newModel();
|
|
61
|
+
const col: AnalyticsTableColumn | null = model.getTableColumn("name");
|
|
62
|
+
expect(col).not.toBeNull();
|
|
63
|
+
expect(col!.key).toBe("name");
|
|
64
|
+
expect(col!.type).toBe(TableColumnType.Text);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("returns null for an unknown column", () => {
|
|
68
|
+
expect(newModel().getTableColumn("doesNotExist")).toBeNull();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("getTableColumns returns the full column set", () => {
|
|
72
|
+
expect(newModel().getTableColumns()).toHaveLength(COLUMNS.length);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("CommonModel.setColumnValue coercion by column type", () => {
|
|
77
|
+
test("stores a plain text value unchanged", () => {
|
|
78
|
+
const model: TestModel = newModel();
|
|
79
|
+
model.setColumnValue("name", "hello");
|
|
80
|
+
expect(model.getColumnValue("name")).toBe("hello");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("wraps a string into an ObjectID for an ObjectID column", () => {
|
|
84
|
+
const model: TestModel = newModel();
|
|
85
|
+
const id: ObjectID = ObjectID.generate();
|
|
86
|
+
model.setColumnValue("resourceId", id.toString());
|
|
87
|
+
const stored: unknown = model.getColumnValue("resourceId");
|
|
88
|
+
expect(stored).toBeInstanceOf(ObjectID);
|
|
89
|
+
expect((stored as ObjectID).toString()).toBe(id.toString());
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("parses a string into a Date for Date and DateTime64 columns", () => {
|
|
93
|
+
const model: TestModel = newModel();
|
|
94
|
+
model.setColumnValue("startsAt", "2023-01-15T12:30:00.000Z");
|
|
95
|
+
model.setColumnValue("observedAt", "2023-06-01T00:00:00.000Z");
|
|
96
|
+
expect(model.getColumnValue("startsAt")).toBeInstanceOf(Date);
|
|
97
|
+
expect(model.getColumnValue("observedAt")).toBeInstanceOf(Date);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("parses a JSON string for a JSON column", () => {
|
|
101
|
+
const model: TestModel = newModel();
|
|
102
|
+
model.setColumnValue("attributes", '{"env":"prod","n":5}');
|
|
103
|
+
expect(model.getColumnValue("attributes")).toEqual({
|
|
104
|
+
env: "prod",
|
|
105
|
+
n: 5,
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("falls back to an empty object when the JSON string is invalid", () => {
|
|
110
|
+
const model: TestModel = newModel();
|
|
111
|
+
model.setColumnValue("attributes", "not json {");
|
|
112
|
+
expect(model.getColumnValue("attributes")).toEqual({});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("parses a JSON array string for a JSONArray column", () => {
|
|
116
|
+
const model: TestModel = newModel();
|
|
117
|
+
model.setColumnValue("tags", "[1, 2, 3]");
|
|
118
|
+
expect(model.getColumnValue("tags")).toEqual([1, 2, 3]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("falls back to an empty array when the JSONArray string is invalid", () => {
|
|
122
|
+
const model: TestModel = newModel();
|
|
123
|
+
model.setColumnValue("tags", "definitely not an array");
|
|
124
|
+
expect(model.getColumnValue("tags")).toEqual([]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("parses a string into an integer for a Number column", () => {
|
|
128
|
+
const model: TestModel = newModel();
|
|
129
|
+
model.setColumnValue("count", "42");
|
|
130
|
+
expect(model.getColumnValue("count")).toBe(42);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("parses a string into a float for a Decimal column", () => {
|
|
134
|
+
const model: TestModel = newModel();
|
|
135
|
+
model.setColumnValue("ratio", "3.14");
|
|
136
|
+
expect(model.getColumnValue("ratio")).toBe(3.14);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("parses a string into an integer for a LongNumber column", () => {
|
|
140
|
+
const model: TestModel = newModel();
|
|
141
|
+
model.setColumnValue("bigCount", "1000");
|
|
142
|
+
expect(model.getColumnValue("bigCount")).toBe(1000);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("leaves an already-numeric value on a Number column unchanged", () => {
|
|
146
|
+
const model: TestModel = newModel();
|
|
147
|
+
model.setColumnValue("count", 7);
|
|
148
|
+
expect(model.getColumnValue("count")).toBe(7);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("throws BadDataException when setting an unknown column", () => {
|
|
152
|
+
const model: TestModel = newModel();
|
|
153
|
+
expect(() => {
|
|
154
|
+
model.setColumnValue("nope", "x");
|
|
155
|
+
}).toThrow(BadDataException);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("CommonModel.getColumnValue", () => {
|
|
160
|
+
test("returns undefined for an unknown column", () => {
|
|
161
|
+
expect(newModel().getColumnValue("nope")).toBeUndefined();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("returns undefined for a known-but-unset column", () => {
|
|
165
|
+
expect(newModel().getColumnValue("name")).toBeUndefined();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("CommonModel.fromJSON (instance) and toJSON", () => {
|
|
170
|
+
test("fromJSON populates every provided column with coercion", () => {
|
|
171
|
+
const model: CommonModel = newModel().fromJSON({
|
|
172
|
+
name: "widget",
|
|
173
|
+
count: "10",
|
|
174
|
+
ratio: "2.5",
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
expect(model.getColumnValue("name")).toBe("widget");
|
|
178
|
+
expect(model.getColumnValue("count")).toBe(10);
|
|
179
|
+
expect(model.getColumnValue("ratio")).toBe(2.5);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("toJSON emits set columns and omits unset ones", () => {
|
|
183
|
+
const model: TestModel = newModel();
|
|
184
|
+
model.setColumnValue("name", "widget");
|
|
185
|
+
model.setColumnValue("count", 3);
|
|
186
|
+
|
|
187
|
+
const json: Record<string, unknown> = model.toJSON();
|
|
188
|
+
|
|
189
|
+
expect(json["name"]).toBe("widget");
|
|
190
|
+
expect(json["count"]).toBe(3);
|
|
191
|
+
// Unset columns serialize to undefined, which JSONFunctions.serialize drops.
|
|
192
|
+
expect(Object.prototype.hasOwnProperty.call(json, "ratio")).toBe(false);
|
|
193
|
+
expect(json["ratio"]).toBeUndefined();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("toJSON recursively serializes a nested CommonModel value", () => {
|
|
197
|
+
const parent: TestModel = newModel();
|
|
198
|
+
const child: TestModel = newModel();
|
|
199
|
+
child.setColumnValue("name", "child");
|
|
200
|
+
|
|
201
|
+
/*
|
|
202
|
+
* A non-string value on a column is stored as-is, so a nested model
|
|
203
|
+
* survives to toJSON where it is recursively serialized.
|
|
204
|
+
*/
|
|
205
|
+
parent.setColumnValue("attributes", child as unknown as never);
|
|
206
|
+
|
|
207
|
+
const json: Record<string, unknown> = parent.toJSON();
|
|
208
|
+
expect((json["attributes"] as Record<string, unknown>)["name"]).toBe(
|
|
209
|
+
"child",
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe("CommonModel static fromJSON / toJSON", () => {
|
|
215
|
+
test("builds a model instance from a plain JSON object", () => {
|
|
216
|
+
const model: CommonModel = CommonModel.fromJSON(
|
|
217
|
+
{ name: "from-json" },
|
|
218
|
+
TestModel,
|
|
219
|
+
) as CommonModel;
|
|
220
|
+
|
|
221
|
+
expect(model).toBeInstanceOf(TestModel);
|
|
222
|
+
expect(model.getColumnValue("name")).toBe("from-json");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("returns an already-constructed model unchanged", () => {
|
|
226
|
+
const existing: TestModel = newModel();
|
|
227
|
+
existing.setColumnValue("name", "existing");
|
|
228
|
+
|
|
229
|
+
const result: CommonModel = CommonModel.fromJSON(
|
|
230
|
+
existing,
|
|
231
|
+
TestModel,
|
|
232
|
+
) as CommonModel;
|
|
233
|
+
|
|
234
|
+
expect(result).toBe(existing);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("builds an array of models, passing through existing instances", () => {
|
|
238
|
+
const existing: TestModel = newModel();
|
|
239
|
+
existing.setColumnValue("name", "kept");
|
|
240
|
+
|
|
241
|
+
const result: Array<CommonModel> = CommonModel.fromJSON(
|
|
242
|
+
[{ name: "a" }, existing, { name: "b" }] as never,
|
|
243
|
+
TestModel,
|
|
244
|
+
) as Array<CommonModel>;
|
|
245
|
+
|
|
246
|
+
expect(result).toHaveLength(3);
|
|
247
|
+
expect(result[0]!.getColumnValue("name")).toBe("a");
|
|
248
|
+
expect(result[1]).toBe(existing);
|
|
249
|
+
expect(result[2]!.getColumnValue("name")).toBe("b");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("static toJSON delegates to the instance toJSON", () => {
|
|
253
|
+
const model: TestModel = newModel();
|
|
254
|
+
model.setColumnValue("name", "delegated");
|
|
255
|
+
|
|
256
|
+
const json: Record<string, unknown> = CommonModel.toJSON(model, TestModel);
|
|
257
|
+
expect(json["name"]).toBe("delegated");
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe("CommonModel static fromJSONArray / toJSONArray", () => {
|
|
262
|
+
test("fromJSONArray builds instances and passes through existing ones", () => {
|
|
263
|
+
const existing: TestModel = newModel();
|
|
264
|
+
existing.setColumnValue("name", "kept");
|
|
265
|
+
|
|
266
|
+
const models: Array<CommonModel> = CommonModel.fromJSONArray(
|
|
267
|
+
[{ name: "first" }, existing],
|
|
268
|
+
TestModel,
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
expect(models).toHaveLength(2);
|
|
272
|
+
expect(models[0]!.getColumnValue("name")).toBe("first");
|
|
273
|
+
expect(models[1]).toBe(existing);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("toJSONArray round-trips a list of models to JSON objects", () => {
|
|
277
|
+
const a: TestModel = newModel();
|
|
278
|
+
a.setColumnValue("name", "a");
|
|
279
|
+
const b: TestModel = newModel();
|
|
280
|
+
b.setColumnValue("name", "b");
|
|
281
|
+
|
|
282
|
+
const jsonArray: Array<Record<string, unknown>> = CommonModel.toJSONArray(
|
|
283
|
+
[a, b],
|
|
284
|
+
TestModel,
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
expect(jsonArray).toHaveLength(2);
|
|
288
|
+
expect(jsonArray[0]!["name"]).toBe("a");
|
|
289
|
+
expect(jsonArray[1]!["name"]).toBe("b");
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("fromJSONArray then toJSONArray preserves scalar values", () => {
|
|
293
|
+
const input: Array<Record<string, unknown>> = [
|
|
294
|
+
{ name: "x", count: 1 },
|
|
295
|
+
{ name: "y", count: 2 },
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
const models: Array<CommonModel> = CommonModel.fromJSONArray(
|
|
299
|
+
input as never,
|
|
300
|
+
TestModel,
|
|
301
|
+
);
|
|
302
|
+
const output: Array<Record<string, unknown>> = CommonModel.toJSONArray(
|
|
303
|
+
models,
|
|
304
|
+
TestModel,
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
expect(output[0]!["name"]).toBe("x");
|
|
308
|
+
expect(output[0]!["count"]).toBe(1);
|
|
309
|
+
expect(output[1]!["name"]).toBe("y");
|
|
310
|
+
expect(output[1]!["count"]).toBe(2);
|
|
311
|
+
});
|
|
312
|
+
});
|