@oneuptime/common 12.0.11 → 12.0.13
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/DatabaseBaseModel/DatabaseBaseModel.ts +32 -5
- package/Models/DatabaseModels/NetworkDevice.ts +64 -0
- package/Models/DatabaseModels/StatusPage.ts +59 -0
- package/Models/DatabaseModels/StatusPageOidc.ts +2 -0
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -2
- package/Server/API/AIChatAPI.ts +55 -56
- package/Server/API/CommonAPI.ts +89 -0
- package/Server/API/MicrosoftTeamsAPI.ts +20 -9
- package/Server/API/SlackAPI.ts +13 -10
- package/Server/API/StatusPageAPI.ts +2197 -2128
- package/Server/API/TelemetryAPI.ts +72 -3
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.ts +30 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.ts +47 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/UserAuthorization.ts +11 -2
- package/Server/Services/ApiKeyPermissionService.ts +116 -2
- package/Server/Services/DatabaseService.ts +17 -6
- package/Server/Services/LogAggregationService.ts +34 -0
- package/Server/Services/LogService.ts +21 -0
- package/Server/Services/NetworkSiteService.ts +12 -0
- package/Server/Services/SpanService.ts +21 -0
- package/Server/Services/TraceAggregationService.ts +15 -0
- package/Server/Services/WorkspaceNotificationRuleService.ts +172 -220
- package/Server/Types/AnalyticsDatabase/ModelPermission.ts +9 -0
- package/Server/Types/Database/Permissions/AccessControlPermission.ts +47 -16
- package/Server/Types/Workflow/Components/Conditions/IfElse.ts +9 -0
- package/Server/Types/Workflow/Components/JavaScript.ts +9 -0
- package/Server/Utils/APIKey/AccessPermission.ts +16 -40
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +144 -0
- package/Server/Utils/LogRedaction.ts +576 -0
- package/Server/Utils/Logger.ts +123 -46
- package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +94 -23
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +12 -0
- package/Server/Utils/Monitor/MonitorResource.ts +29 -9
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +28 -7
- package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +56 -10
- package/Server/Utils/StartServer.ts +30 -9
- package/Server/Utils/StatusPageSearchEngineIndexing.ts +33 -0
- package/Server/Utils/Telemetry/ResourceEntityFilter.ts +441 -0
- package/Server/Utils/VM/VMRunner.ts +693 -874
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +488 -46
- package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +300 -0
- package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +130 -0
- package/Tests/Models/DatabaseModels/DatabaseBaseModelToJSONObjectCache.test.ts +180 -0
- package/Tests/Models/StatusPageEnableSearchEngineIndexing.test.ts +157 -0
- package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +23 -1
- package/Tests/Server/API/AIChatRouteAuthorization.test.ts +785 -0
- package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +21 -8
- package/Tests/Server/API/CommonAPIAuthGuard.test.ts +290 -0
- package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +7 -1
- package/Tests/Server/API/StatusPageOverviewCache.test.ts +474 -0
- package/Tests/Server/API/StatusPageSeoSearchEngineIndexing.test.ts +189 -0
- package/Tests/Server/Infrastructure/InMemoryTTLCache.test.ts +158 -0
- package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +9 -60
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +24 -15
- package/Tests/Server/Middleware/UserAuthorization.test.ts +79 -0
- package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +332 -0
- package/Tests/Server/Services/ApiKeyPermissionService.test.ts +347 -0
- package/Tests/Server/Services/DatabaseServiceUpdateDebugLogging.test.ts +282 -0
- package/Tests/Server/Services/NetworkSiteService.test.ts +56 -2
- package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +267 -0
- package/Tests/Server/Services/WorkspaceNotificationRuleTestRuleChannels.test.ts +702 -0
- package/Tests/Server/Types/Database/Permissions/AccessControlPermission.test.ts +165 -0
- package/Tests/Server/Types/Workflow/Components/CustomCodeScriptError.test.ts +121 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +48 -36
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +213 -0
- package/Tests/Server/Utils/LogRedaction.test.ts +415 -0
- package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +245 -0
- package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +374 -4
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorJavascriptExpression.test.ts +92 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +455 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceReachabilityRoundTrip.test.ts +369 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +86 -8
- package/Tests/Server/Utils/PrivacyFilterUtil.test.ts +64 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayGateCacheStore.test.ts +207 -0
- package/Tests/Server/Utils/StartServerBodyVerify.test.ts +335 -0
- package/Tests/Server/Utils/StatusPageSearchEngineIndexing.test.ts +60 -0
- package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +435 -0
- package/Tests/Server/Utils/VM/VMRunnerIsolation.test.ts +385 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +3 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +607 -62
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsInstalledTeamIdSpace.test.ts +778 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +44 -10
- package/Tests/Types/API/HostnameFromAuthority.test.ts +164 -0
- package/Tests/Types/Database/AccessControl/ColumnAccessControlCache.test.ts +344 -0
- package/Tests/Types/Database/DatabasePropertyFindOperator.test.ts +518 -0
- package/Tests/Types/Database/TableColumnMetadataCache.test.ts +286 -0
- package/Tests/Types/Monitor/MonitorStep.test.ts +100 -0
- package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +360 -0
- package/Tests/Types/StatusPage/SearchEngineIndexing.test.ts +86 -0
- package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +254 -0
- package/Tests/UI/Components/CardSelect.test.tsx +1021 -0
- package/Tests/UI/Components/MasterPage.test.tsx +36 -1
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +66 -1
- package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +115 -6
- package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +610 -0
- package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +114 -45
- package/Types/API/Hostname.ts +79 -0
- package/Types/Database/AccessControl/ColumnAccessControl.ts +33 -11
- package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +33 -11
- package/Types/Database/DatabaseProperty.ts +38 -0
- package/Types/Database/TableColumn.ts +34 -7
- package/Types/Events/Recurring.ts +16 -1
- package/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse.ts +14 -2
- package/Types/Monitor/MonitorStep.ts +13 -1
- package/Types/Monitor/MonitorType.ts +345 -40
- package/Types/Monitor/SSLMonitor/SslMonitorResponse.ts +43 -0
- package/Types/StatusPage/SearchEngineIndexing.ts +61 -0
- package/Types/Telemetry/ResourceEntityFacet.ts +211 -0
- package/UI/Components/CardSelect/CardSelect.tsx +714 -105
- package/UI/Components/EmptyState/EmptyState.tsx +8 -1
- package/UI/Components/Footer/Footer.tsx +32 -6
- package/UI/Components/Forms/Fields/FormField.tsx +3 -0
- package/UI/Components/Forms/Types/Field.ts +8 -0
- package/UI/Components/Loader/PageLoader.tsx +29 -8
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +26 -4
- package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +17 -1
- package/UI/Components/MasterPage/MasterPage.tsx +24 -11
- package/Utils/Monitor/MonitorMetricType.ts +16 -1
- package/Utils/Monitor/NetworkTopologyUtil.ts +60 -8
- package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +262 -0
- package/Utils/NetworkSite/SiteStatusRollupUtil.ts +41 -24
- package/Utils/Telemetry/CrossSignalScope.ts +75 -4
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +20 -4
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +66 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +60 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js +2 -0
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +48 -35
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/CommonAPI.js +59 -0
- package/build/dist/Server/API/CommonAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +20 -6
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +13 -10
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +745 -673
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +34 -3
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +6 -2
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/ApiKeyPermissionService.js +90 -1
- package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +15 -6
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +2 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/LogService.js +16 -0
- package/build/dist/Server/Services/LogService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteService.js +12 -0
- package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
- package/build/dist/Server/Services/SpanService.js +16 -0
- package/build/dist/Server/Services/SpanService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +2 -0
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +137 -164
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +9 -0
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js +21 -13
- package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +8 -0
- package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/JavaScript.js +8 -0
- package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +9 -32
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +87 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/LogRedaction.js +449 -0
- package/build/dist/Server/Utils/LogRedaction.js.map +1 -0
- package/build/dist/Server/Utils/Logger.js +98 -45
- package/build/dist/Server/Utils/Logger.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +65 -13
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +12 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +20 -3
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +27 -7
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +29 -10
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +10 -7
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js +24 -0
- package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +311 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -0
- package/build/dist/Server/Utils/VM/VMRunner.js +402 -525
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +366 -37
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/API/Hostname.js +63 -0
- package/build/dist/Types/API/Hostname.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +20 -6
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +20 -6
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
- package/build/dist/Types/Database/DatabaseProperty.js +38 -0
- package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
- package/build/dist/Types/Database/TableColumn.js +24 -6
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/Events/Recurring.js +13 -1
- package/build/dist/Types/Events/Recurring.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +9 -1
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +330 -31
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/StatusPage/SearchEngineIndexing.js +55 -0
- package/build/dist/Types/StatusPage/SearchEngineIndexing.js.map +1 -0
- package/build/dist/Types/Telemetry/ResourceEntityFacet.js +156 -0
- package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -0
- package/build/dist/UI/Components/CardSelect/CardSelect.js +360 -40
- package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
- package/build/dist/UI/Components/EmptyState/EmptyState.js +1 -1
- package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
- package/build/dist/UI/Components/Footer/Footer.js +7 -4
- package/build/dist/UI/Components/Footer/Footer.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Loader/PageLoader.js +15 -5
- package/build/dist/UI/Components/Loader/PageLoader.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +16 -3
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js +13 -1
- package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js +10 -2
- package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +13 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +34 -7
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +177 -0
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -0
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +23 -27
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
- package/build/dist/Utils/Telemetry/CrossSignalScope.js +44 -4
- package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* PasswordHash carries a pre-existing TS5.9 diagnostic that fails any suite
|
|
3
|
+
* whose runtime require graph reaches it (DatabaseService, the base class of
|
|
4
|
+
* every concrete service, imports it). Nothing password-related is under
|
|
5
|
+
* test here, so the module is replaced WITH A FACTORY — an automock would
|
|
6
|
+
* still require (and type-check) the real file.
|
|
7
|
+
*/
|
|
8
|
+
jest.mock("../../../Server/Utils/PasswordHash", () => {
|
|
9
|
+
return {
|
|
10
|
+
__esModule: true,
|
|
11
|
+
default: {
|
|
12
|
+
hash: jest.fn(),
|
|
13
|
+
verify: jest.fn(),
|
|
14
|
+
generateSalt: jest.fn(),
|
|
15
|
+
needsUpgrade: jest.fn(),
|
|
16
|
+
applyPepper: jest.fn(),
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
import NetworkDeviceDiscoveryScanService from "../../../Server/Services/NetworkDeviceDiscoveryScanService";
|
|
22
|
+
import ModelPermission from "../../../Server/Types/Database/Permissions/Index";
|
|
23
|
+
import ConfigLogLevel from "../../../Server/Types/ConfigLogLevel";
|
|
24
|
+
import UpdateBy from "../../../Server/Types/Database/UpdateBy";
|
|
25
|
+
import logger from "../../../Server/Utils/Logger";
|
|
26
|
+
import NetworkDeviceDiscoveryScan from "../../../Models/DatabaseModels/NetworkDeviceDiscoveryScan";
|
|
27
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
28
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
29
|
+
import getJestMockFunction, { MockFunction } from "../../MockType";
|
|
30
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
* Regression tests for the log-level gate on _updateBy's per-row debug
|
|
34
|
+
* logging.
|
|
35
|
+
*
|
|
36
|
+
* _updateBy used to build a pretty-printed JSON.stringify(updatedItem,
|
|
37
|
+
* null, 2) for EVERY matched row of EVERY update, unconditionally — and
|
|
38
|
+
* logger.debug then discarded it at the default INFO level. That was pure
|
|
39
|
+
* per-row CPU and allocation fleet-wide, and it also meant a payload whose
|
|
40
|
+
* serialization throws (e.g. a circular structure) failed the whole update
|
|
41
|
+
* even though nothing would ever be logged.
|
|
42
|
+
*
|
|
43
|
+
* The fix hoists a single logger.getLogLevel() check above the per-item loop
|
|
44
|
+
* and only enters the debug-logging block (stringify + LogAttributes
|
|
45
|
+
* allocation) when the level is DEBUG. These tests pin:
|
|
46
|
+
*
|
|
47
|
+
* - at INFO: the block is skipped entirely (no debug calls, no stringify,
|
|
48
|
+
* one level read for the whole update rather than per row), and the
|
|
49
|
+
* write itself is untouched;
|
|
50
|
+
* - at DEBUG: logger.debug receives the byte-identical pretty-printed
|
|
51
|
+
* payload it always did;
|
|
52
|
+
* - the one blessed semantic delta: a payload whose serialization throws
|
|
53
|
+
* now only throws at DEBUG level.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
type ScanUpdateData = UpdateBy<NetworkDeviceDiscoveryScan>["data"];
|
|
57
|
+
|
|
58
|
+
describe("DatabaseService._updateBy — per-row debug logging is gated on the log level", () => {
|
|
59
|
+
let saveMock: MockFunction;
|
|
60
|
+
let updateMock: MockFunction;
|
|
61
|
+
let debugSpy: MockFunction;
|
|
62
|
+
let getLogLevelSpy: MockFunction;
|
|
63
|
+
|
|
64
|
+
const mockFoundItems: (ids: Array<ObjectID>) => void = (
|
|
65
|
+
ids: Array<ObjectID>,
|
|
66
|
+
): void => {
|
|
67
|
+
const items: Array<NetworkDeviceDiscoveryScan> = ids.map((id: ObjectID) => {
|
|
68
|
+
const item: NetworkDeviceDiscoveryScan = new NetworkDeviceDiscoveryScan();
|
|
69
|
+
item._id = id.toString();
|
|
70
|
+
return item;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
jest
|
|
74
|
+
.spyOn(NetworkDeviceDiscoveryScanService as any, "_findBy")
|
|
75
|
+
.mockResolvedValue(items as never);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const setLogLevel: (level: ConfigLogLevel) => void = (
|
|
79
|
+
level: ConfigLogLevel,
|
|
80
|
+
): void => {
|
|
81
|
+
getLogLevelSpy = jest
|
|
82
|
+
.spyOn(logger, "getLogLevel")
|
|
83
|
+
.mockReturnValue(level) as unknown as MockFunction;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
jest.restoreAllMocks();
|
|
88
|
+
jest.clearAllMocks();
|
|
89
|
+
|
|
90
|
+
debugSpy = jest.spyOn(logger, "debug").mockImplementation(() => {
|
|
91
|
+
return undefined;
|
|
92
|
+
}) as unknown as MockFunction;
|
|
93
|
+
|
|
94
|
+
saveMock = getJestMockFunction();
|
|
95
|
+
saveMock.mockImplementation((item: unknown) => {
|
|
96
|
+
return Promise.resolve(item);
|
|
97
|
+
});
|
|
98
|
+
updateMock = getJestMockFunction();
|
|
99
|
+
updateMock.mockImplementation(() => {
|
|
100
|
+
return Promise.resolve({ affected: 1 });
|
|
101
|
+
});
|
|
102
|
+
jest
|
|
103
|
+
.spyOn(NetworkDeviceDiscoveryScanService, "getRepository")
|
|
104
|
+
.mockReturnValue({ save: saveMock, update: updateMock } as never);
|
|
105
|
+
|
|
106
|
+
// The permission layer needs a DB and is not what these tests exercise.
|
|
107
|
+
jest
|
|
108
|
+
.spyOn(ModelPermission, "checkUpdatePermissionByModel")
|
|
109
|
+
.mockResolvedValue(undefined as never);
|
|
110
|
+
jest
|
|
111
|
+
.spyOn(ModelPermission, "checkUpdateQueryPermissions")
|
|
112
|
+
.mockImplementation(((
|
|
113
|
+
_modelType: unknown,
|
|
114
|
+
query: unknown,
|
|
115
|
+
): Promise<unknown> => {
|
|
116
|
+
return Promise.resolve(query);
|
|
117
|
+
}) as never);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
afterEach(() => {
|
|
121
|
+
/*
|
|
122
|
+
* Restores logger.getLogLevel / logger.debug so other suites see the
|
|
123
|
+
* real log level again.
|
|
124
|
+
*/
|
|
125
|
+
jest.restoreAllMocks();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("at INFO, an update whose payload JSON.stringify would throw on still completes", async () => {
|
|
129
|
+
setLogLevel(ConfigLogLevel.INFO);
|
|
130
|
+
|
|
131
|
+
const scanId: ObjectID = ObjectID.generate();
|
|
132
|
+
mockFoundItems([scanId]);
|
|
133
|
+
|
|
134
|
+
const circular: JSONObject = { name: "loop" };
|
|
135
|
+
circular["self"] = circular;
|
|
136
|
+
expect(() => {
|
|
137
|
+
return JSON.stringify(circular);
|
|
138
|
+
}).toThrow();
|
|
139
|
+
|
|
140
|
+
const updatedCount: number =
|
|
141
|
+
await NetworkDeviceDiscoveryScanService.updateOneById({
|
|
142
|
+
id: scanId,
|
|
143
|
+
data: { status: circular } as unknown as ScanUpdateData,
|
|
144
|
+
props: { isRoot: true },
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(updatedCount).toBe(1);
|
|
148
|
+
expect(updateMock).toHaveBeenCalledTimes(1);
|
|
149
|
+
expect(saveMock).not.toHaveBeenCalled();
|
|
150
|
+
|
|
151
|
+
/*
|
|
152
|
+
* The write payload is untouched: the very same object reference lands
|
|
153
|
+
* in the SET, never a serialized copy.
|
|
154
|
+
*/
|
|
155
|
+
const whereClause: JSONObject = updateMock.mock.calls[0]![0] as JSONObject;
|
|
156
|
+
const setPayload: JSONObject = updateMock.mock.calls[0]![1] as JSONObject;
|
|
157
|
+
expect(whereClause["_id"]).toBe(scanId.toString());
|
|
158
|
+
expect(setPayload["status"]).toBe(circular);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("at INFO, logger.debug is never called for any matched row", async () => {
|
|
162
|
+
setLogLevel(ConfigLogLevel.INFO);
|
|
163
|
+
|
|
164
|
+
const scanIds: Array<ObjectID> = [
|
|
165
|
+
ObjectID.generate(),
|
|
166
|
+
ObjectID.generate(),
|
|
167
|
+
ObjectID.generate(),
|
|
168
|
+
];
|
|
169
|
+
mockFoundItems(scanIds);
|
|
170
|
+
|
|
171
|
+
const updatedCount: number =
|
|
172
|
+
await NetworkDeviceDiscoveryScanService.updateBy({
|
|
173
|
+
query: {},
|
|
174
|
+
data: { status: "In Progress" } as ScanUpdateData,
|
|
175
|
+
limit: scanIds.length,
|
|
176
|
+
skip: 0,
|
|
177
|
+
props: { isRoot: true },
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// On the old code this was two debug calls per row (six here).
|
|
181
|
+
expect(debugSpy).not.toHaveBeenCalled();
|
|
182
|
+
|
|
183
|
+
// The write itself is unchanged.
|
|
184
|
+
expect(updatedCount).toBe(scanIds.length);
|
|
185
|
+
expect(updateMock).toHaveBeenCalledTimes(scanIds.length);
|
|
186
|
+
for (let i: number = 0; i < scanIds.length; i++) {
|
|
187
|
+
const whereClause: JSONObject = updateMock.mock.calls[
|
|
188
|
+
i
|
|
189
|
+
]![0] as JSONObject;
|
|
190
|
+
const setPayload: JSONObject = updateMock.mock.calls[i]![1] as JSONObject;
|
|
191
|
+
expect(whereClause["_id"]).toBe(scanIds[i]!.toString());
|
|
192
|
+
expect(setPayload["status"]).toBe("In Progress");
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("the level check is hoisted: one getLogLevel read for the whole update, not one per row", async () => {
|
|
197
|
+
setLogLevel(ConfigLogLevel.INFO);
|
|
198
|
+
|
|
199
|
+
mockFoundItems([
|
|
200
|
+
ObjectID.generate(),
|
|
201
|
+
ObjectID.generate(),
|
|
202
|
+
ObjectID.generate(),
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
await NetworkDeviceDiscoveryScanService.updateBy({
|
|
206
|
+
query: {},
|
|
207
|
+
data: { status: "In Progress" } as ScanUpdateData,
|
|
208
|
+
limit: 3,
|
|
209
|
+
skip: 0,
|
|
210
|
+
props: { isRoot: true },
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(getLogLevelSpy).toHaveBeenCalledTimes(1);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("at DEBUG, logger.debug receives the exact pretty-printed payload per row, as before", async () => {
|
|
217
|
+
setLogLevel(ConfigLogLevel.DEBUG);
|
|
218
|
+
|
|
219
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
220
|
+
const scanIds: Array<ObjectID> = [ObjectID.generate(), ObjectID.generate()];
|
|
221
|
+
mockFoundItems(scanIds);
|
|
222
|
+
|
|
223
|
+
await NetworkDeviceDiscoveryScanService.updateBy({
|
|
224
|
+
query: {},
|
|
225
|
+
data: { status: "In Progress" } as ScanUpdateData,
|
|
226
|
+
limit: scanIds.length,
|
|
227
|
+
skip: 0,
|
|
228
|
+
props: { isRoot: true, tenantId: projectId },
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
/*
|
|
232
|
+
* Two debug calls per matched row: the "Updated Item" marker, then the
|
|
233
|
+
* pretty-printed payload.
|
|
234
|
+
*/
|
|
235
|
+
expect(debugSpy).toHaveBeenCalledTimes(scanIds.length * 2);
|
|
236
|
+
|
|
237
|
+
for (let i: number = 0; i < scanIds.length; i++) {
|
|
238
|
+
const expectedPayload: string = JSON.stringify(
|
|
239
|
+
{ status: "In Progress", _id: scanIds[i]!.toString() },
|
|
240
|
+
null,
|
|
241
|
+
2,
|
|
242
|
+
);
|
|
243
|
+
/*
|
|
244
|
+
* Guard the guard: the expectation itself must be the multi-line
|
|
245
|
+
* pretty-printed form, not a compact one.
|
|
246
|
+
*/
|
|
247
|
+
expect(expectedPayload).toContain("\n");
|
|
248
|
+
|
|
249
|
+
expect(debugSpy.mock.calls[i * 2]![0]).toBe("Updated Item");
|
|
250
|
+
expect(debugSpy.mock.calls[i * 2]![1]).toEqual({
|
|
251
|
+
projectId: projectId.toString(),
|
|
252
|
+
});
|
|
253
|
+
expect(debugSpy.mock.calls[i * 2 + 1]![0]).toBe(expectedPayload);
|
|
254
|
+
expect(debugSpy.mock.calls[i * 2 + 1]![1]).toEqual({
|
|
255
|
+
projectId: projectId.toString(),
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
expect(updateMock).toHaveBeenCalledTimes(scanIds.length);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("at DEBUG, a payload whose serialization throws still fails the update (blessed delta)", async () => {
|
|
263
|
+
setLogLevel(ConfigLogLevel.DEBUG);
|
|
264
|
+
|
|
265
|
+
const scanId: ObjectID = ObjectID.generate();
|
|
266
|
+
mockFoundItems([scanId]);
|
|
267
|
+
|
|
268
|
+
const circular: JSONObject = { name: "loop" };
|
|
269
|
+
circular["self"] = circular;
|
|
270
|
+
|
|
271
|
+
await expect(
|
|
272
|
+
NetworkDeviceDiscoveryScanService.updateOneById({
|
|
273
|
+
id: scanId,
|
|
274
|
+
data: { status: circular } as unknown as ScanUpdateData,
|
|
275
|
+
props: { isRoot: true },
|
|
276
|
+
}),
|
|
277
|
+
).rejects.toThrow();
|
|
278
|
+
|
|
279
|
+
expect(updateMock).not.toHaveBeenCalled();
|
|
280
|
+
expect(saveMock).not.toHaveBeenCalled();
|
|
281
|
+
});
|
|
282
|
+
});
|
|
@@ -201,14 +201,17 @@ describe("NetworkSiteService.recomputeRollupForSite", () => {
|
|
|
201
201
|
expect(spies.timelineCreate).not.toHaveBeenCalled();
|
|
202
202
|
});
|
|
203
203
|
|
|
204
|
-
it("uses the
|
|
204
|
+
it("uses the SNMP fallback for devices without a stamped status", async () => {
|
|
205
205
|
const spies: RollupSpies = setupRollup({
|
|
206
206
|
site: fakeSite({ currentMonitorStatusId: OPERATIONAL_STATUS_ID }),
|
|
207
207
|
devices: [
|
|
208
208
|
{
|
|
209
209
|
id: DEVICE_ID,
|
|
210
|
-
// Unmonitored and last
|
|
210
|
+
// Unmonitored, and its last poll could not reach it.
|
|
211
|
+
isReachable: false,
|
|
212
|
+
lastPolledAt: new Date(Date.now() - 60 * 1000),
|
|
211
213
|
lastSeenAt: new Date(Date.now() - 60 * 60 * 1000),
|
|
214
|
+
pollingIntervalInMinutes: 5,
|
|
212
215
|
},
|
|
213
216
|
] as unknown as Array<NetworkDevice>,
|
|
214
217
|
});
|
|
@@ -221,6 +224,57 @@ describe("NetworkSiteService.recomputeRollupForSite", () => {
|
|
|
221
224
|
);
|
|
222
225
|
});
|
|
223
226
|
|
|
227
|
+
/*
|
|
228
|
+
* Issue #3220 at the site card. A probe behind on a large fleet leaves
|
|
229
|
+
* every device's last successful poll well outside the old fixed
|
|
230
|
+
* 15-minute freshness window, and the site above them went red even
|
|
231
|
+
* though each one had answered. The rollup asks about the last poll's
|
|
232
|
+
* OUTCOME now, so it does not.
|
|
233
|
+
*/
|
|
234
|
+
it("issue #3220: devices answering 21 minutes ago keep the site operational", async () => {
|
|
235
|
+
const spies: RollupSpies = setupRollup({
|
|
236
|
+
site: fakeSite({ currentMonitorStatusId: OFFLINE_STATUS_ID }),
|
|
237
|
+
devices: [
|
|
238
|
+
{
|
|
239
|
+
id: DEVICE_ID,
|
|
240
|
+
isReachable: true,
|
|
241
|
+
lastPolledAt: new Date(Date.now() - 21 * 60 * 1000),
|
|
242
|
+
lastSeenAt: new Date(Date.now() - 21 * 60 * 1000),
|
|
243
|
+
pollingIntervalInMinutes: 5,
|
|
244
|
+
},
|
|
245
|
+
] as unknown as Array<NetworkDevice>,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
await NetworkSiteService.recomputeRollupForSite(SITE_ID);
|
|
249
|
+
|
|
250
|
+
const updateArgs: any = spies.updateColumns.mock.calls[0]![0];
|
|
251
|
+
expect(updateArgs.data.currentMonitorStatusId.toString()).toBe(
|
|
252
|
+
OPERATIONAL_STATUS_ID.toString(),
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
/*
|
|
257
|
+
* The rollup reads four columns and the query has to fetch all four. A
|
|
258
|
+
* missing `isReachable` here compiles, runs, and silently drops the whole
|
|
259
|
+
* subtree back onto the legacy freshness path.
|
|
260
|
+
*/
|
|
261
|
+
it("selects every column the reachability rule reads", async () => {
|
|
262
|
+
const spies: RollupSpies = setupRollup({
|
|
263
|
+
site: fakeSite({ currentMonitorStatusId: OPERATIONAL_STATUS_ID }),
|
|
264
|
+
devices: [],
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
await NetworkSiteService.recomputeRollupForSite(SITE_ID);
|
|
268
|
+
|
|
269
|
+
const select: Record<string, unknown> =
|
|
270
|
+
spies.deviceFindBy.mock.calls[0]![0].select;
|
|
271
|
+
|
|
272
|
+
expect(select["isReachable"]).toBe(true);
|
|
273
|
+
expect(select["lastPolledAt"]).toBe(true);
|
|
274
|
+
expect(select["lastSeenAt"]).toBe(true);
|
|
275
|
+
expect(select["pollingIntervalInMinutes"]).toBe(true);
|
|
276
|
+
});
|
|
277
|
+
|
|
224
278
|
it("does nothing when the site does not exist", async () => {
|
|
225
279
|
const spies: RollupSpies = setupRollup({ site: null, devices: [] });
|
|
226
280
|
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The identifier lookup is stubbed: these tests are about what the SQL and
|
|
3
|
+
* the rewritten query look like, and leaving it live would make them depend
|
|
4
|
+
* on a reachable Postgres.
|
|
5
|
+
*/
|
|
6
|
+
const kubernetesClusterFindBy: jest.Mock = jest.fn();
|
|
7
|
+
|
|
8
|
+
jest.mock("../../../Server/Services/KubernetesClusterService", () => {
|
|
9
|
+
return { __esModule: true, default: { findBy: kubernetesClusterFindBy } };
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
import LogAggregationService from "../../../Server/Services/LogAggregationService";
|
|
13
|
+
import TraceAggregationService from "../../../Server/Services/TraceAggregationService";
|
|
14
|
+
import LogService from "../../../Server/Services/LogService";
|
|
15
|
+
import SpanService from "../../../Server/Services/SpanService";
|
|
16
|
+
import { Results } from "../../../Server/Services/AnalyticsDatabaseService";
|
|
17
|
+
import { Statement } from "../../../Server/Utils/AnalyticsDatabase/Statement";
|
|
18
|
+
import { ResourceEntityScope } from "../../../Server/Utils/Telemetry/ResourceEntityFilter";
|
|
19
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
20
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
21
|
+
import { keyForKubernetesCluster } from "../../../Utils/Telemetry/EntityKey";
|
|
22
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* End of the read path for issue #3216. Selecting a Kubernetes cluster used to
|
|
26
|
+
* compile to `primaryEntityId = '<clusterId>'`, and OTLP telemetry that
|
|
27
|
+
* carries a `service.name` is primary-keyed on its SERVICE — the cluster only
|
|
28
|
+
* appears in `entityKeys`. So the predicate matched zero rows, and pairing the
|
|
29
|
+
* cluster with a service made results reappear because both ids shared one
|
|
30
|
+
* `IN (...)` list.
|
|
31
|
+
*
|
|
32
|
+
* These tests pin the two surfaces that have to agree on what a resource facet
|
|
33
|
+
* selects: the aggregation statements behind the histogram / facets / analytics
|
|
34
|
+
* panels, and the list query the rows themselves come from. A chart that
|
|
35
|
+
* disagrees with its list is its own bug.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
39
|
+
const startTime: Date = new Date("2026-08-14T11:00:00.000Z");
|
|
40
|
+
const endTime: Date = new Date("2026-08-14T12:00:00.000Z");
|
|
41
|
+
|
|
42
|
+
const clusterId: string = ObjectID.generate().toString();
|
|
43
|
+
const serviceId: string = ObjectID.generate().toString();
|
|
44
|
+
const clusterEntityKey: string = keyForKubernetesCluster(
|
|
45
|
+
projectId.toString(),
|
|
46
|
+
"prod-eu",
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const clusterScope: ResourceEntityScope = {
|
|
50
|
+
entityIds: [clusterId],
|
|
51
|
+
entityKeys: [clusterEntityKey],
|
|
52
|
+
attributeKey: "resource.k8s.cluster.name",
|
|
53
|
+
attributeValues: ["prod-eu"],
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
* Capture the statements a service builds instead of running them. The
|
|
58
|
+
* assertions are about the SQL shape (which branches, how they are combined),
|
|
59
|
+
* which is what decides whether a row matches.
|
|
60
|
+
*/
|
|
61
|
+
function captureStatements(
|
|
62
|
+
service: { executeQuery: (statement: Statement) => Promise<Results> },
|
|
63
|
+
rows: Array<JSONObject> = [],
|
|
64
|
+
): Array<Statement> {
|
|
65
|
+
const captured: Array<Statement> = [];
|
|
66
|
+
|
|
67
|
+
jest.spyOn(service as never, "executeQuery").mockImplementation(((
|
|
68
|
+
statement: Statement,
|
|
69
|
+
): Promise<Results> => {
|
|
70
|
+
captured.push(statement);
|
|
71
|
+
|
|
72
|
+
return Promise.resolve({
|
|
73
|
+
json: (): Promise<unknown> => {
|
|
74
|
+
return Promise.resolve({ data: rows });
|
|
75
|
+
},
|
|
76
|
+
} as unknown as Results);
|
|
77
|
+
}) as never);
|
|
78
|
+
|
|
79
|
+
return captured;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
describe("resource facet filters reach the telemetry read path", () => {
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
kubernetesClusterFindBy.mockReset();
|
|
85
|
+
kubernetesClusterFindBy.mockResolvedValue([
|
|
86
|
+
{ clusterIdentifier: "prod-eu" },
|
|
87
|
+
]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
afterEach(() => {
|
|
91
|
+
jest.restoreAllMocks();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("LogAggregationService", () => {
|
|
95
|
+
test("the histogram matches a cluster by entity key, not only by primaryEntityId", async () => {
|
|
96
|
+
const captured: Array<Statement> = captureStatements(LogService);
|
|
97
|
+
|
|
98
|
+
await LogAggregationService.getHistogram({
|
|
99
|
+
projectId,
|
|
100
|
+
startTime,
|
|
101
|
+
endTime,
|
|
102
|
+
bucketSizeInMinutes: 5,
|
|
103
|
+
resourceScopes: [clusterScope],
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const query: string = captured[0]!.query;
|
|
107
|
+
|
|
108
|
+
expect(query).toContain("primaryEntityId IN");
|
|
109
|
+
expect(query).toContain("hasAny(entityKeys,");
|
|
110
|
+
expect(query).toContain("attributes[");
|
|
111
|
+
expect(Object.values(captured[0]!.query_params)).toContainEqual([
|
|
112
|
+
clusterEntityKey,
|
|
113
|
+
]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("a cluster and a service are separate predicates, so they intersect", async () => {
|
|
117
|
+
const captured: Array<Statement> = captureStatements(LogService);
|
|
118
|
+
|
|
119
|
+
await LogAggregationService.getHistogram({
|
|
120
|
+
projectId,
|
|
121
|
+
startTime,
|
|
122
|
+
endTime,
|
|
123
|
+
bucketSizeInMinutes: 5,
|
|
124
|
+
serviceIds: [new ObjectID(serviceId)],
|
|
125
|
+
resourceScopes: [clusterScope],
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const query: string = captured[0]!.query;
|
|
129
|
+
|
|
130
|
+
/*
|
|
131
|
+
* Two AND-ed predicates. Before the fix both ids went into ONE
|
|
132
|
+
* `primaryEntityId IN (...)`, which OR-ed them — that is why adding a
|
|
133
|
+
* service "fixed" the empty result while dropping the cluster.
|
|
134
|
+
*/
|
|
135
|
+
expect(query).toContain("AND primaryEntityId IN");
|
|
136
|
+
expect(query).toContain("AND (primaryEntityId IN");
|
|
137
|
+
expect(query).toContain("hasAny(entityKeys,");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("facet value queries carry the same scope as the histogram", async () => {
|
|
141
|
+
const captured: Array<Statement> = captureStatements(LogService);
|
|
142
|
+
|
|
143
|
+
await LogAggregationService.getFacetValues({
|
|
144
|
+
projectId,
|
|
145
|
+
startTime,
|
|
146
|
+
endTime,
|
|
147
|
+
facetKey: "severityText",
|
|
148
|
+
resourceScopes: [clusterScope],
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
expect(captured[0]!.query).toContain("hasAny(entityKeys,");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("no resource scope leaves the statement exactly as it was", async () => {
|
|
155
|
+
const captured: Array<Statement> = captureStatements(LogService);
|
|
156
|
+
|
|
157
|
+
await LogAggregationService.getHistogram({
|
|
158
|
+
projectId,
|
|
159
|
+
startTime,
|
|
160
|
+
endTime,
|
|
161
|
+
bucketSizeInMinutes: 5,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(captured[0]!.query).not.toContain("hasAny(entityKeys,");
|
|
165
|
+
expect(captured[0]!.query).not.toContain("AND (primaryEntityId IN");
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("TraceAggregationService", () => {
|
|
170
|
+
test("the span histogram matches a cluster by entity key too", async () => {
|
|
171
|
+
const captured: Array<Statement> = captureStatements(SpanService);
|
|
172
|
+
|
|
173
|
+
await TraceAggregationService.getHistogram({
|
|
174
|
+
projectId,
|
|
175
|
+
startTime,
|
|
176
|
+
endTime,
|
|
177
|
+
bucketSizeInMinutes: 5,
|
|
178
|
+
resourceScopes: [clusterScope],
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const query: string = captured[0]!.query;
|
|
182
|
+
|
|
183
|
+
expect(query).toContain("primaryEntityId IN");
|
|
184
|
+
expect(query).toContain("hasAny(entityKeys,");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("no resource scope leaves the span statement unchanged", async () => {
|
|
188
|
+
const captured: Array<Statement> = captureStatements(SpanService);
|
|
189
|
+
|
|
190
|
+
await TraceAggregationService.getHistogram({
|
|
191
|
+
projectId,
|
|
192
|
+
startTime,
|
|
193
|
+
endTime,
|
|
194
|
+
bucketSizeInMinutes: 5,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
expect(captured[0]!.query).not.toContain("hasAny(entityKeys,");
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("list queries", () => {
|
|
202
|
+
/*
|
|
203
|
+
* The list runs through the generic analytics endpoint, so the ids the
|
|
204
|
+
* explorer sends have to be resolved in the service's own onBeforeFind
|
|
205
|
+
* hook — that is the last point where Postgres is still reachable.
|
|
206
|
+
*/
|
|
207
|
+
type FindByShape = {
|
|
208
|
+
query: Record<string, unknown>;
|
|
209
|
+
props: { tenantId: ObjectID };
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
test("LogService resolves the explorer's resource ids before compiling", async () => {
|
|
213
|
+
const findBy: FindByShape = {
|
|
214
|
+
query: { resourceFilters: { kubernetesClusterId: [clusterId] } },
|
|
215
|
+
props: { tenantId: projectId },
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
await (
|
|
219
|
+
LogService as unknown as {
|
|
220
|
+
onBeforeFind: (input: FindByShape) => Promise<unknown>;
|
|
221
|
+
}
|
|
222
|
+
).onBeforeFind(findBy);
|
|
223
|
+
|
|
224
|
+
expect(findBy.query["resourceFilters"]).toBeUndefined();
|
|
225
|
+
expect(findBy.query["resourceEntityScopes"]).toBeDefined();
|
|
226
|
+
|
|
227
|
+
const scopes: Array<ResourceEntityScope> = findBy.query[
|
|
228
|
+
"resourceEntityScopes"
|
|
229
|
+
] as Array<ResourceEntityScope>;
|
|
230
|
+
|
|
231
|
+
expect(scopes[0]!.entityIds).toEqual([clusterId]);
|
|
232
|
+
// Resolved through Postgres to the key ingest stamped on the rows.
|
|
233
|
+
expect(scopes[0]!.entityKeys).toEqual([clusterEntityKey]);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("SpanService resolves them the same way", async () => {
|
|
237
|
+
const findBy: FindByShape = {
|
|
238
|
+
query: { resourceFilters: { kubernetesClusterId: [clusterId] } },
|
|
239
|
+
props: { tenantId: projectId },
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
await (
|
|
243
|
+
SpanService as unknown as {
|
|
244
|
+
onBeforeFind: (input: FindByShape) => Promise<unknown>;
|
|
245
|
+
}
|
|
246
|
+
).onBeforeFind(findBy);
|
|
247
|
+
|
|
248
|
+
expect(findBy.query["resourceFilters"]).toBeUndefined();
|
|
249
|
+
expect(findBy.query["resourceEntityScopes"]).toBeDefined();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("a query without resource filters is handed through untouched", async () => {
|
|
253
|
+
const findBy: FindByShape = {
|
|
254
|
+
query: { severityText: "Error" },
|
|
255
|
+
props: { tenantId: projectId },
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
await (
|
|
259
|
+
LogService as unknown as {
|
|
260
|
+
onBeforeFind: (input: FindByShape) => Promise<unknown>;
|
|
261
|
+
}
|
|
262
|
+
).onBeforeFind(findBy);
|
|
263
|
+
|
|
264
|
+
expect(findBy.query).toEqual({ severityText: "Error" });
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
});
|