@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.
Files changed (251) hide show
  1. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +32 -5
  2. package/Models/DatabaseModels/NetworkDevice.ts +64 -0
  3. package/Models/DatabaseModels/StatusPage.ts +59 -0
  4. package/Models/DatabaseModels/StatusPageOidc.ts +2 -0
  5. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -2
  6. package/Server/API/AIChatAPI.ts +55 -56
  7. package/Server/API/CommonAPI.ts +89 -0
  8. package/Server/API/MicrosoftTeamsAPI.ts +20 -9
  9. package/Server/API/SlackAPI.ts +13 -10
  10. package/Server/API/StatusPageAPI.ts +2197 -2128
  11. package/Server/API/TelemetryAPI.ts +72 -3
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.ts +30 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.ts +47 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  15. package/Server/Middleware/UserAuthorization.ts +11 -2
  16. package/Server/Services/ApiKeyPermissionService.ts +116 -2
  17. package/Server/Services/DatabaseService.ts +17 -6
  18. package/Server/Services/LogAggregationService.ts +34 -0
  19. package/Server/Services/LogService.ts +21 -0
  20. package/Server/Services/NetworkSiteService.ts +12 -0
  21. package/Server/Services/SpanService.ts +21 -0
  22. package/Server/Services/TraceAggregationService.ts +15 -0
  23. package/Server/Services/WorkspaceNotificationRuleService.ts +172 -220
  24. package/Server/Types/AnalyticsDatabase/ModelPermission.ts +9 -0
  25. package/Server/Types/Database/Permissions/AccessControlPermission.ts +47 -16
  26. package/Server/Types/Workflow/Components/Conditions/IfElse.ts +9 -0
  27. package/Server/Types/Workflow/Components/JavaScript.ts +9 -0
  28. package/Server/Utils/APIKey/AccessPermission.ts +16 -40
  29. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +144 -0
  30. package/Server/Utils/LogRedaction.ts +576 -0
  31. package/Server/Utils/Logger.ts +123 -46
  32. package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +94 -23
  33. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +12 -0
  34. package/Server/Utils/Monitor/MonitorResource.ts +29 -9
  35. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +28 -7
  36. package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +56 -10
  37. package/Server/Utils/StartServer.ts +30 -9
  38. package/Server/Utils/StatusPageSearchEngineIndexing.ts +33 -0
  39. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +441 -0
  40. package/Server/Utils/VM/VMRunner.ts +693 -874
  41. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +488 -46
  42. package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +300 -0
  43. package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +130 -0
  44. package/Tests/Models/DatabaseModels/DatabaseBaseModelToJSONObjectCache.test.ts +180 -0
  45. package/Tests/Models/StatusPageEnableSearchEngineIndexing.test.ts +157 -0
  46. package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +23 -1
  47. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +785 -0
  48. package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +21 -8
  49. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +290 -0
  50. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +7 -1
  51. package/Tests/Server/API/StatusPageOverviewCache.test.ts +474 -0
  52. package/Tests/Server/API/StatusPageSeoSearchEngineIndexing.test.ts +189 -0
  53. package/Tests/Server/Infrastructure/InMemoryTTLCache.test.ts +158 -0
  54. package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +9 -60
  55. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +24 -15
  56. package/Tests/Server/Middleware/UserAuthorization.test.ts +79 -0
  57. package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +332 -0
  58. package/Tests/Server/Services/ApiKeyPermissionService.test.ts +347 -0
  59. package/Tests/Server/Services/DatabaseServiceUpdateDebugLogging.test.ts +282 -0
  60. package/Tests/Server/Services/NetworkSiteService.test.ts +56 -2
  61. package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +267 -0
  62. package/Tests/Server/Services/WorkspaceNotificationRuleTestRuleChannels.test.ts +702 -0
  63. package/Tests/Server/Types/Database/Permissions/AccessControlPermission.test.ts +165 -0
  64. package/Tests/Server/Types/Workflow/Components/CustomCodeScriptError.test.ts +121 -0
  65. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +48 -36
  66. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +213 -0
  67. package/Tests/Server/Utils/LogRedaction.test.ts +415 -0
  68. package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +245 -0
  69. package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +374 -4
  70. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorJavascriptExpression.test.ts +92 -0
  71. package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +455 -0
  72. package/Tests/Server/Utils/Monitor/NetworkDeviceReachabilityRoundTrip.test.ts +369 -0
  73. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +86 -8
  74. package/Tests/Server/Utils/PrivacyFilterUtil.test.ts +64 -0
  75. package/Tests/Server/Utils/SessionReplay/SessionReplayGateCacheStore.test.ts +207 -0
  76. package/Tests/Server/Utils/StartServerBodyVerify.test.ts +335 -0
  77. package/Tests/Server/Utils/StatusPageSearchEngineIndexing.test.ts +60 -0
  78. package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +435 -0
  79. package/Tests/Server/Utils/VM/VMRunnerIsolation.test.ts +385 -0
  80. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +3 -0
  81. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +607 -62
  82. package/Tests/Server/Utils/Workspace/MicrosoftTeamsInstalledTeamIdSpace.test.ts +778 -0
  83. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +44 -10
  84. package/Tests/Types/API/HostnameFromAuthority.test.ts +164 -0
  85. package/Tests/Types/Database/AccessControl/ColumnAccessControlCache.test.ts +344 -0
  86. package/Tests/Types/Database/DatabasePropertyFindOperator.test.ts +518 -0
  87. package/Tests/Types/Database/TableColumnMetadataCache.test.ts +286 -0
  88. package/Tests/Types/Monitor/MonitorStep.test.ts +100 -0
  89. package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +360 -0
  90. package/Tests/Types/StatusPage/SearchEngineIndexing.test.ts +86 -0
  91. package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +254 -0
  92. package/Tests/UI/Components/CardSelect.test.tsx +1021 -0
  93. package/Tests/UI/Components/MasterPage.test.tsx +36 -1
  94. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +66 -1
  95. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +115 -6
  96. package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +610 -0
  97. package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +114 -45
  98. package/Types/API/Hostname.ts +79 -0
  99. package/Types/Database/AccessControl/ColumnAccessControl.ts +33 -11
  100. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +33 -11
  101. package/Types/Database/DatabaseProperty.ts +38 -0
  102. package/Types/Database/TableColumn.ts +34 -7
  103. package/Types/Events/Recurring.ts +16 -1
  104. package/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse.ts +14 -2
  105. package/Types/Monitor/MonitorStep.ts +13 -1
  106. package/Types/Monitor/MonitorType.ts +345 -40
  107. package/Types/Monitor/SSLMonitor/SslMonitorResponse.ts +43 -0
  108. package/Types/StatusPage/SearchEngineIndexing.ts +61 -0
  109. package/Types/Telemetry/ResourceEntityFacet.ts +211 -0
  110. package/UI/Components/CardSelect/CardSelect.tsx +714 -105
  111. package/UI/Components/EmptyState/EmptyState.tsx +8 -1
  112. package/UI/Components/Footer/Footer.tsx +32 -6
  113. package/UI/Components/Forms/Fields/FormField.tsx +3 -0
  114. package/UI/Components/Forms/Types/Field.ts +8 -0
  115. package/UI/Components/Loader/PageLoader.tsx +29 -8
  116. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +26 -4
  117. package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +17 -1
  118. package/UI/Components/MasterPage/MasterPage.tsx +24 -11
  119. package/Utils/Monitor/MonitorMetricType.ts +16 -1
  120. package/Utils/Monitor/NetworkTopologyUtil.ts +60 -8
  121. package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +262 -0
  122. package/Utils/NetworkSite/SiteStatusRollupUtil.ts +41 -24
  123. package/Utils/Telemetry/CrossSignalScope.ts +75 -4
  124. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +20 -4
  125. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  126. package/build/dist/Models/DatabaseModels/NetworkDevice.js +66 -0
  127. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  128. package/build/dist/Models/DatabaseModels/StatusPage.js +60 -0
  129. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  130. package/build/dist/Models/DatabaseModels/StatusPageOidc.js +2 -0
  131. package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
  132. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  133. package/build/dist/Server/API/AIChatAPI.js +48 -35
  134. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  135. package/build/dist/Server/API/CommonAPI.js +59 -0
  136. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  137. package/build/dist/Server/API/MicrosoftTeamsAPI.js +20 -6
  138. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  139. package/build/dist/Server/API/SlackAPI.js +13 -10
  140. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  141. package/build/dist/Server/API/StatusPageAPI.js +745 -673
  142. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  143. package/build/dist/Server/API/TelemetryAPI.js +34 -3
  144. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  145. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js +23 -0
  146. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js.map +1 -0
  147. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js +34 -0
  148. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js.map +1 -0
  149. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  150. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  151. package/build/dist/Server/Middleware/UserAuthorization.js +6 -2
  152. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  153. package/build/dist/Server/Services/ApiKeyPermissionService.js +90 -1
  154. package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
  155. package/build/dist/Server/Services/DatabaseService.js +15 -6
  156. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  157. package/build/dist/Server/Services/LogAggregationService.js +2 -0
  158. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  159. package/build/dist/Server/Services/LogService.js +16 -0
  160. package/build/dist/Server/Services/LogService.js.map +1 -1
  161. package/build/dist/Server/Services/NetworkSiteService.js +12 -0
  162. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
  163. package/build/dist/Server/Services/SpanService.js +16 -0
  164. package/build/dist/Server/Services/SpanService.js.map +1 -1
  165. package/build/dist/Server/Services/TraceAggregationService.js +2 -0
  166. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  167. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +137 -164
  168. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  169. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +9 -0
  170. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
  171. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js +21 -13
  172. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js.map +1 -1
  173. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +8 -0
  174. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
  175. package/build/dist/Server/Types/Workflow/Components/JavaScript.js +8 -0
  176. package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
  177. package/build/dist/Server/Utils/APIKey/AccessPermission.js +9 -32
  178. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  179. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +87 -0
  180. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  181. package/build/dist/Server/Utils/LogRedaction.js +449 -0
  182. package/build/dist/Server/Utils/LogRedaction.js.map +1 -0
  183. package/build/dist/Server/Utils/Logger.js +98 -45
  184. package/build/dist/Server/Utils/Logger.js.map +1 -1
  185. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +65 -13
  186. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
  187. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +12 -1
  188. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  189. package/build/dist/Server/Utils/Monitor/MonitorResource.js +20 -3
  190. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  191. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +27 -7
  192. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  193. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +29 -10
  194. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -1
  195. package/build/dist/Server/Utils/StartServer.js +10 -7
  196. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  197. package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js +24 -0
  198. package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js.map +1 -0
  199. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +311 -0
  200. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -0
  201. package/build/dist/Server/Utils/VM/VMRunner.js +402 -525
  202. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  203. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +366 -37
  204. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  205. package/build/dist/Types/API/Hostname.js +63 -0
  206. package/build/dist/Types/API/Hostname.js.map +1 -1
  207. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +20 -6
  208. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  209. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +20 -6
  210. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  211. package/build/dist/Types/Database/DatabaseProperty.js +38 -0
  212. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  213. package/build/dist/Types/Database/TableColumn.js +24 -6
  214. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  215. package/build/dist/Types/Events/Recurring.js +13 -1
  216. package/build/dist/Types/Events/Recurring.js.map +1 -1
  217. package/build/dist/Types/Monitor/MonitorStep.js +9 -1
  218. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  219. package/build/dist/Types/Monitor/MonitorType.js +330 -31
  220. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  221. package/build/dist/Types/StatusPage/SearchEngineIndexing.js +55 -0
  222. package/build/dist/Types/StatusPage/SearchEngineIndexing.js.map +1 -0
  223. package/build/dist/Types/Telemetry/ResourceEntityFacet.js +156 -0
  224. package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -0
  225. package/build/dist/UI/Components/CardSelect/CardSelect.js +360 -40
  226. package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
  227. package/build/dist/UI/Components/EmptyState/EmptyState.js +1 -1
  228. package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
  229. package/build/dist/UI/Components/Footer/Footer.js +7 -4
  230. package/build/dist/UI/Components/Footer/Footer.js.map +1 -1
  231. package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
  232. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  233. package/build/dist/UI/Components/Loader/PageLoader.js +15 -5
  234. package/build/dist/UI/Components/Loader/PageLoader.js.map +1 -1
  235. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +16 -3
  236. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  237. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js +13 -1
  238. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
  239. package/build/dist/UI/Components/MasterPage/MasterPage.js +10 -2
  240. package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
  241. package/build/dist/Utils/Monitor/MonitorMetricType.js +13 -1
  242. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  243. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +34 -7
  244. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  245. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +177 -0
  246. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -0
  247. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +23 -27
  248. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
  249. package/build/dist/Utils/Telemetry/CrossSignalScope.js +44 -4
  250. package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
  251. package/package.json +1 -1
@@ -0,0 +1,435 @@
1
+ import "../../TestingUtils/Init";
2
+ import ObjectID from "../../../../Types/ObjectID";
3
+ import {
4
+ SQL,
5
+ Statement,
6
+ } from "../../../../Server/Utils/AnalyticsDatabase/Statement";
7
+ import TableColumnType from "../../../../Types/AnalyticsDatabase/TableColumnType";
8
+ import {
9
+ keyForHost,
10
+ keyForKubernetesCluster,
11
+ } from "../../../../Utils/Telemetry/EntityKey";
12
+
13
+ /*
14
+ * The four resource tables are mocked at module level: the unit under test
15
+ * is the id -> identifier -> entity-key translation, not Postgres. Each
16
+ * mock records the query it was handed so the tests can assert the lookup
17
+ * is project-scoped (a cluster id from another tenant must not resolve).
18
+ */
19
+ const hostFindBy: jest.Mock = jest.fn();
20
+ const dockerHostFindBy: jest.Mock = jest.fn();
21
+ const podmanHostFindBy: jest.Mock = jest.fn();
22
+ const kubernetesClusterFindBy: jest.Mock = jest.fn();
23
+
24
+ jest.mock("../../../../Server/Services/HostService", () => {
25
+ return { __esModule: true, default: { findBy: hostFindBy } };
26
+ });
27
+ jest.mock("../../../../Server/Services/DockerHostService", () => {
28
+ return { __esModule: true, default: { findBy: dockerHostFindBy } };
29
+ });
30
+ jest.mock("../../../../Server/Services/PodmanHostService", () => {
31
+ return { __esModule: true, default: { findBy: podmanHostFindBy } };
32
+ });
33
+ jest.mock("../../../../Server/Services/KubernetesClusterService", () => {
34
+ return { __esModule: true, default: { findBy: kubernetesClusterFindBy } };
35
+ });
36
+
37
+ import ResourceEntityFilter, {
38
+ ResourceEntityScope,
39
+ appendResourceScopeFilters,
40
+ } from "../../../../Server/Utils/Telemetry/ResourceEntityFilter";
41
+
42
+ const PROJECT_ID: ObjectID = new ObjectID(
43
+ "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
44
+ );
45
+ const CLUSTER_ID: string = "8c0f2f1e-2e4f-4a8c-9a1a-2f5b6c7d8e9f";
46
+ const OTHER_CLUSTER_ID: string = "1a2b3c4d-5e6f-4071-8293-a4b5c6d7e8f9";
47
+ const HOST_ID: string = "5f4e3d2c-1b0a-4998-8776-655443322110";
48
+
49
+ describe("ResourceEntityFilter", () => {
50
+ beforeEach(() => {
51
+ hostFindBy.mockReset();
52
+ dockerHostFindBy.mockReset();
53
+ podmanHostFindBy.mockReset();
54
+ kubernetesClusterFindBy.mockReset();
55
+
56
+ hostFindBy.mockResolvedValue([]);
57
+ dockerHostFindBy.mockResolvedValue([]);
58
+ podmanHostFindBy.mockResolvedValue([]);
59
+ kubernetesClusterFindBy.mockResolvedValue([]);
60
+ });
61
+
62
+ describe("resolveScopes", () => {
63
+ test("resolves a Kubernetes cluster id to the entity key ingest stamped", async () => {
64
+ kubernetesClusterFindBy.mockResolvedValue([
65
+ { clusterIdentifier: "prod-eu" },
66
+ ]);
67
+
68
+ const scopes: Array<ResourceEntityScope> =
69
+ await ResourceEntityFilter.resolveScopes({
70
+ projectId: PROJECT_ID,
71
+ selections: { kubernetesClusterId: [CLUSTER_ID] },
72
+ });
73
+
74
+ expect(scopes).toHaveLength(1);
75
+ /*
76
+ * The key MUST be the one InventoryItem's k8s.cluster resolver
77
+ * computes at ingest — a read-side key that does not byte-match finds
78
+ * nothing, which is the failure this whole change exists to fix.
79
+ */
80
+ expect(scopes[0]!.entityKeys).toEqual([
81
+ keyForKubernetesCluster(PROJECT_ID.toString(), "prod-eu"),
82
+ ]);
83
+ // The id branch stays so agent-ingested cluster telemetry still matches.
84
+ expect(scopes[0]!.entityIds).toEqual([CLUSTER_ID]);
85
+ // And the pre-entityKeys fallback mirrors the cluster detail page.
86
+ expect(scopes[0]!.attributeKey).toBe("resource.k8s.cluster.name");
87
+ expect(scopes[0]!.attributeValues).toEqual(["prod-eu"]);
88
+ });
89
+
90
+ test("scopes the identifier lookup to the requesting project", async () => {
91
+ kubernetesClusterFindBy.mockResolvedValue([
92
+ { clusterIdentifier: "prod-eu" },
93
+ ]);
94
+
95
+ await ResourceEntityFilter.resolveScopes({
96
+ projectId: PROJECT_ID,
97
+ selections: { kubernetesClusterId: [CLUSTER_ID] },
98
+ });
99
+
100
+ const call: Record<string, any> = kubernetesClusterFindBy.mock
101
+ .calls[0]![0] as Record<string, any>;
102
+
103
+ expect(call["query"]["projectId"]).toBe(PROJECT_ID);
104
+ expect(call["query"]["_id"].values.map(String)).toEqual([CLUSTER_ID]);
105
+ });
106
+
107
+ test("host / docker host / podman host all key on the Host entity (host.name)", async () => {
108
+ hostFindBy.mockResolvedValue([{ hostIdentifier: "web-1" }]);
109
+ dockerHostFindBy.mockResolvedValue([{ hostIdentifier: "web-1" }]);
110
+ podmanHostFindBy.mockResolvedValue([{ hostIdentifier: "web-1" }]);
111
+
112
+ for (const facetKey of ["hostId", "dockerHostId", "podmanHostId"]) {
113
+ const scopes: Array<ResourceEntityScope> =
114
+ await ResourceEntityFilter.resolveScopes({
115
+ projectId: PROJECT_ID,
116
+ selections: { [facetKey]: [HOST_ID] },
117
+ });
118
+
119
+ expect(scopes[0]!.entityKeys).toEqual([
120
+ keyForHost(PROJECT_ID.toString(), "web-1"),
121
+ ]);
122
+ expect(scopes[0]!.attributeKey).toBe("resource.host.name");
123
+ }
124
+ });
125
+
126
+ test("emits one scope per facet so two facets intersect rather than union", async () => {
127
+ kubernetesClusterFindBy.mockResolvedValue([
128
+ { clusterIdentifier: "prod-eu" },
129
+ ]);
130
+ hostFindBy.mockResolvedValue([{ hostIdentifier: "web-1" }]);
131
+
132
+ const scopes: Array<ResourceEntityScope> =
133
+ await ResourceEntityFilter.resolveScopes({
134
+ projectId: PROJECT_ID,
135
+ selections: {
136
+ kubernetesClusterId: [CLUSTER_ID],
137
+ hostId: [HOST_ID],
138
+ },
139
+ });
140
+
141
+ expect(scopes).toHaveLength(2);
142
+ });
143
+
144
+ test("multiple values inside one facet stay in one scope (they OR)", async () => {
145
+ kubernetesClusterFindBy.mockResolvedValue([
146
+ { clusterIdentifier: "prod-eu" },
147
+ { clusterIdentifier: "prod-us" },
148
+ ]);
149
+
150
+ const scopes: Array<ResourceEntityScope> =
151
+ await ResourceEntityFilter.resolveScopes({
152
+ projectId: PROJECT_ID,
153
+ selections: {
154
+ kubernetesClusterId: [CLUSTER_ID, OTHER_CLUSTER_ID],
155
+ },
156
+ });
157
+
158
+ expect(scopes).toHaveLength(1);
159
+ expect(scopes[0]!.entityKeys).toEqual([
160
+ keyForKubernetesCluster(PROJECT_ID.toString(), "prod-eu"),
161
+ keyForKubernetesCluster(PROJECT_ID.toString(), "prod-us"),
162
+ ]);
163
+ });
164
+
165
+ test("an id that no longer resolves keeps the primaryEntityId branch", async () => {
166
+ kubernetesClusterFindBy.mockResolvedValue([]);
167
+
168
+ const scopes: Array<ResourceEntityScope> =
169
+ await ResourceEntityFilter.resolveScopes({
170
+ projectId: PROJECT_ID,
171
+ selections: { kubernetesClusterId: [CLUSTER_ID] },
172
+ });
173
+
174
+ /*
175
+ * Degrading to the id-only predicate reproduces the old behavior for
176
+ * that facet. Dropping the scope instead would silently WIDEN the
177
+ * result set to every row in the window.
178
+ */
179
+ expect(scopes[0]!.entityIds).toEqual([CLUSTER_ID]);
180
+ expect(scopes[0]!.entityKeys).toEqual([]);
181
+ expect(scopes[0]!.attributeValues).toBeUndefined();
182
+ });
183
+
184
+ test("a Postgres failure degrades to the id branch instead of throwing", async () => {
185
+ kubernetesClusterFindBy.mockRejectedValue(
186
+ new Error("connection refused") as never,
187
+ );
188
+
189
+ const scopes: Array<ResourceEntityScope> =
190
+ await ResourceEntityFilter.resolveScopes({
191
+ projectId: PROJECT_ID,
192
+ selections: { kubernetesClusterId: [CLUSTER_ID] },
193
+ });
194
+
195
+ expect(scopes[0]!.entityIds).toEqual([CLUSTER_ID]);
196
+ expect(scopes[0]!.entityKeys).toEqual([]);
197
+ });
198
+
199
+ test("rows with a blank identifier contribute no key", async () => {
200
+ kubernetesClusterFindBy.mockResolvedValue([{ clusterIdentifier: " " }]);
201
+
202
+ const scopes: Array<ResourceEntityScope> =
203
+ await ResourceEntityFilter.resolveScopes({
204
+ projectId: PROJECT_ID,
205
+ selections: { kubernetesClusterId: [CLUSTER_ID] },
206
+ });
207
+
208
+ expect(scopes[0]!.entityKeys).toEqual([]);
209
+ });
210
+
211
+ test("no selection means no scope and no Postgres round trip", async () => {
212
+ const scopes: Array<ResourceEntityScope> =
213
+ await ResourceEntityFilter.resolveScopes({
214
+ projectId: PROJECT_ID,
215
+ selections: {},
216
+ });
217
+
218
+ expect(scopes).toEqual([]);
219
+ expect(kubernetesClusterFindBy).not.toHaveBeenCalled();
220
+ });
221
+
222
+ test("an empty id list for a facet is not a filter", async () => {
223
+ const scopes: Array<ResourceEntityScope> =
224
+ await ResourceEntityFilter.resolveScopes({
225
+ projectId: PROJECT_ID,
226
+ selections: { kubernetesClusterId: [] },
227
+ });
228
+
229
+ expect(scopes).toEqual([]);
230
+ });
231
+ });
232
+
233
+ describe("rewriteAnalyticsQuery", () => {
234
+ test("replaces the client's ids with resolved scopes", async () => {
235
+ kubernetesClusterFindBy.mockResolvedValue([
236
+ { clusterIdentifier: "prod-eu" },
237
+ ]);
238
+
239
+ const query: Record<string, unknown> = {
240
+ resourceFilters: { kubernetesClusterId: [CLUSTER_ID] },
241
+ };
242
+
243
+ await ResourceEntityFilter.rewriteAnalyticsQuery({
244
+ query,
245
+ projectId: PROJECT_ID,
246
+ });
247
+
248
+ expect(query["resourceFilters"]).toBeUndefined();
249
+
250
+ const scopes: Array<ResourceEntityScope> = query[
251
+ "resourceEntityScopes"
252
+ ] as Array<ResourceEntityScope>;
253
+
254
+ expect(scopes).toHaveLength(1);
255
+ expect(scopes[0]!.entityKeys).toEqual([
256
+ keyForKubernetesCluster(PROJECT_ID.toString(), "prod-eu"),
257
+ ]);
258
+ });
259
+
260
+ test("leaves the rest of the query untouched", async () => {
261
+ kubernetesClusterFindBy.mockResolvedValue([
262
+ { clusterIdentifier: "prod-eu" },
263
+ ]);
264
+
265
+ const query: Record<string, unknown> = {
266
+ severityText: "Error",
267
+ resourceFilters: { kubernetesClusterId: [CLUSTER_ID] },
268
+ };
269
+
270
+ await ResourceEntityFilter.rewriteAnalyticsQuery({
271
+ query,
272
+ projectId: PROJECT_ID,
273
+ });
274
+
275
+ expect(query["severityText"]).toBe("Error");
276
+ });
277
+
278
+ test("discards a hand-crafted resourceEntityScopes so only the resolver writes it", async () => {
279
+ const query: Record<string, unknown> = {
280
+ resourceEntityScopes: [
281
+ { entityIds: [], entityKeys: ["deadbeefdeadbeef"] },
282
+ ],
283
+ };
284
+
285
+ await ResourceEntityFilter.rewriteAnalyticsQuery({
286
+ query,
287
+ projectId: PROJECT_ID,
288
+ });
289
+
290
+ expect(query["resourceEntityScopes"]).toBeUndefined();
291
+ });
292
+
293
+ test("a query with no resource filter is left alone", async () => {
294
+ const query: Record<string, unknown> = { severityText: "Error" };
295
+
296
+ await ResourceEntityFilter.rewriteAnalyticsQuery({
297
+ query,
298
+ projectId: PROJECT_ID,
299
+ });
300
+
301
+ expect(query).toEqual({ severityText: "Error" });
302
+ expect(kubernetesClusterFindBy).not.toHaveBeenCalled();
303
+ });
304
+
305
+ test("without a tenant the scope keeps its ids and drops the entity keys", async () => {
306
+ const query: Record<string, unknown> = {
307
+ resourceFilters: { kubernetesClusterId: [CLUSTER_ID] },
308
+ };
309
+
310
+ await ResourceEntityFilter.rewriteAnalyticsQuery({ query });
311
+
312
+ const scopes: Array<ResourceEntityScope> = query[
313
+ "resourceEntityScopes"
314
+ ] as Array<ResourceEntityScope>;
315
+
316
+ expect(scopes).toEqual([{ entityIds: [CLUSTER_ID], entityKeys: [] }]);
317
+ expect(kubernetesClusterFindBy).not.toHaveBeenCalled();
318
+ });
319
+
320
+ test("a malformed filter carries no constraint at all", async () => {
321
+ const query: Record<string, unknown> = {
322
+ resourceFilters: { kubernetesClusterId: ["not-an-id"] },
323
+ };
324
+
325
+ await ResourceEntityFilter.rewriteAnalyticsQuery({
326
+ query,
327
+ projectId: PROJECT_ID,
328
+ });
329
+
330
+ expect(query["resourceEntityScopes"]).toBeUndefined();
331
+ expect(query["resourceFilters"]).toBeUndefined();
332
+ });
333
+
334
+ test("an absent query is a no-op", async () => {
335
+ await expect(
336
+ ResourceEntityFilter.rewriteAnalyticsQuery({
337
+ query: undefined,
338
+ projectId: PROJECT_ID,
339
+ }),
340
+ ).resolves.toBeUndefined();
341
+ });
342
+ });
343
+
344
+ describe("appendResourceScopeFilters", () => {
345
+ test("emits one OR group per scope, every branch parameter-bound", () => {
346
+ const statement: Statement = new Statement();
347
+
348
+ appendResourceScopeFilters(statement, [
349
+ {
350
+ entityIds: [CLUSTER_ID],
351
+ entityKeys: ["210dac24142f1baa"],
352
+ attributeKey: "resource.k8s.cluster.name",
353
+ attributeValues: ["prod-eu"],
354
+ },
355
+ ]);
356
+
357
+ /*
358
+ * Statement.query trims the fragment's leading separator when it is
359
+ * the whole statement; in the aggregation services it is appended to
360
+ * a WHERE that already has predicates (covered below).
361
+ */
362
+ expect(statement.query).toBe(
363
+ "AND (primaryEntityId IN ({p0:Array(String)}) OR hasAny(entityKeys, {p1:Array(String)}) OR attributes[{p2:String}] IN ({p3:Array(String)}))",
364
+ );
365
+ expect(statement.query_params).toStrictEqual({
366
+ p0: [CLUSTER_ID],
367
+ p1: ["210dac24142f1baa"],
368
+ p2: "resource.k8s.cluster.name",
369
+ p3: ["prod-eu"],
370
+ });
371
+ });
372
+
373
+ test("two scopes AND with each other", () => {
374
+ const statement: Statement = new Statement();
375
+
376
+ appendResourceScopeFilters(statement, [
377
+ { entityIds: [CLUSTER_ID], entityKeys: [] },
378
+ { entityIds: [HOST_ID], entityKeys: [] },
379
+ ]);
380
+
381
+ expect(statement.query).toBe(
382
+ "AND (primaryEntityId IN ({p0:Array(String)})) AND (primaryEntityId IN ({p1:Array(String)}))",
383
+ );
384
+ });
385
+
386
+ test("a scope with nothing to match on appends no predicate", () => {
387
+ const statement: Statement = new Statement();
388
+
389
+ appendResourceScopeFilters(statement, [
390
+ { entityIds: [], entityKeys: [], attributeValues: [] },
391
+ ]);
392
+
393
+ expect(statement.query).toBe("");
394
+ });
395
+
396
+ test("separates cleanly from the predicates already in the statement", () => {
397
+ const statement: Statement = SQL`WHERE projectId = ${{
398
+ type: TableColumnType.ObjectID,
399
+ value: PROJECT_ID,
400
+ }}`;
401
+
402
+ appendResourceScopeFilters(statement, [
403
+ { entityIds: [CLUSTER_ID], entityKeys: [] },
404
+ ]);
405
+
406
+ expect(statement.query).toBe(
407
+ "WHERE projectId = {p0:String} AND (primaryEntityId IN ({p1:Array(String)}))",
408
+ );
409
+ });
410
+
411
+ test("an absent scope list appends nothing", () => {
412
+ const statement: Statement = new Statement();
413
+
414
+ appendResourceScopeFilters(statement, undefined);
415
+
416
+ expect(statement.query).toBe("");
417
+ });
418
+
419
+ test("the attribute fallback needs both a key and values", () => {
420
+ const statement: Statement = new Statement();
421
+
422
+ appendResourceScopeFilters(statement, [
423
+ {
424
+ entityIds: [],
425
+ entityKeys: ["210dac24142f1baa"],
426
+ attributeValues: ["prod-eu"],
427
+ },
428
+ ]);
429
+
430
+ expect(statement.query).toBe(
431
+ "AND (hasAny(entityKeys, {p0:Array(String)}))",
432
+ );
433
+ });
434
+ });
435
+ });