@oneuptime/common 12.0.34 → 13.0.0

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 (259) hide show
  1. package/Models/DatabaseModels/GlobalConfig.ts +12 -12
  2. package/Models/DatabaseModels/InventoryItem.ts +44 -1
  3. package/Models/DatabaseModels/NetworkDevice.ts +126 -0
  4. package/Server/EnvironmentConfig.ts +47 -16
  5. package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  7. package/Server/Infrastructure/Redis.ts +8 -8
  8. package/Server/Infrastructure/Status.ts +10 -4
  9. package/Server/Services/DatabaseService.ts +65 -13
  10. package/Server/Services/InventoryItemService.ts +31 -1
  11. package/Server/Services/MonitorTemplateService.ts +140 -19
  12. package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +349 -41
  13. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
  14. package/Server/Services/NetworkDeviceService.ts +69 -0
  15. package/Server/Services/UserNotificationRuleService.ts +76 -25
  16. package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
  17. package/Server/Types/Database/QueryUtil.ts +56 -0
  18. package/Server/Types/Markdown.ts +93 -1
  19. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
  20. package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +10 -1
  21. package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
  22. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
  23. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
  24. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +202 -37
  25. package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
  26. package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
  27. package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
  28. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
  29. package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
  30. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
  31. package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
  32. package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
  33. package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
  34. package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
  35. package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
  36. package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
  37. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
  38. package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
  39. package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
  40. package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
  41. package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
  42. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
  43. package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
  44. package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
  45. package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
  46. package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
  47. package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
  48. package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
  49. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
  50. package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
  51. package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
  52. package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
  53. package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
  54. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
  55. package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
  56. package/Tests/Models/InventoryItemStatus.test.ts +147 -0
  57. package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
  58. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
  59. package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
  60. package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
  61. package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
  62. package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
  63. package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
  64. package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
  65. package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
  66. package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
  67. package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
  68. package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
  69. package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
  70. package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
  71. package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
  72. package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
  73. package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
  74. package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
  75. package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
  76. package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
  77. package/Tests/Server/Types/Markdown.test.ts +194 -1
  78. package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
  79. package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
  80. package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
  81. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
  82. package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
  83. package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
  84. package/Tests/Server/Utils/EventLoop.test.ts +359 -0
  85. package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
  86. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
  87. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
  88. package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
  89. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
  90. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
  91. package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
  92. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +224 -10
  93. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
  94. package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
  95. package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
  96. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
  97. package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
  98. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
  99. package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
  100. package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
  101. package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
  102. package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
  103. package/Tests/Types/JSONFunctions.test.ts +40 -0
  104. package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
  105. package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
  106. package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
  107. package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
  108. package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
  109. package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
  110. package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
  111. package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
  112. package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
  113. package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
  114. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
  115. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
  116. package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
  117. package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
  118. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
  119. package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
  120. package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
  121. package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
  122. package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
  123. package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
  124. package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
  125. package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
  126. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  127. package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
  128. package/Types/Dashboard/DashboardVariable.ts +8 -0
  129. package/Types/JSON.ts +4 -0
  130. package/Types/JSONFunctions.ts +16 -0
  131. package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
  132. package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
  133. package/Types/SerializableObjectDictionary.ts +4 -0
  134. package/Types/Telemetry/InventoryLiveness.ts +56 -0
  135. package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
  136. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
  137. package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
  138. package/UI/esbuild-config.js +6 -0
  139. package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
  140. package/Utils/Dashboard/LabelVariable.ts +152 -0
  141. package/Utils/Dashboard/VariableUrlState.ts +27 -5
  142. package/Utils/MetricUnitUtil.ts +34 -0
  143. package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
  144. package/Utils/Monitor/MetricValueFormatter.ts +342 -0
  145. package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
  146. package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
  147. package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
  148. package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
  149. package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
  150. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  151. package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
  152. package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
  153. package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
  154. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  155. package/build/dist/Server/EnvironmentConfig.js +44 -12
  156. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  157. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
  158. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js.map +1 -0
  159. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  160. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  161. package/build/dist/Server/Infrastructure/Redis.js +8 -8
  162. package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
  163. package/build/dist/Server/Infrastructure/Status.js +10 -4
  164. package/build/dist/Server/Infrastructure/Status.js.map +1 -1
  165. package/build/dist/Server/Services/DatabaseService.js +58 -13
  166. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  167. package/build/dist/Server/Services/InventoryItemService.js +28 -1
  168. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  169. package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
  170. package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
  171. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +270 -37
  172. package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
  173. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
  174. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  175. package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
  176. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  177. package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
  178. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  179. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
  180. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
  181. package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
  182. package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
  183. package/build/dist/Server/Types/Markdown.js +75 -0
  184. package/build/dist/Server/Types/Markdown.js.map +1 -1
  185. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
  186. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
  187. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +10 -1
  188. package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
  189. package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
  190. package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
  191. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
  192. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  193. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
  194. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
  195. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +166 -35
  196. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  197. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
  198. package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
  199. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
  200. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
  201. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
  202. package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
  203. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
  204. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  205. package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
  206. package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
  207. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
  208. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  209. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
  210. package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
  211. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
  212. package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
  213. package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
  214. package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
  215. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  216. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  217. package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
  218. package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
  219. package/build/dist/Types/JSON.js +1 -0
  220. package/build/dist/Types/JSON.js.map +1 -1
  221. package/build/dist/Types/JSONFunctions.js +13 -0
  222. package/build/dist/Types/JSONFunctions.js.map +1 -1
  223. package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
  224. package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
  225. package/build/dist/Types/SerializableObjectDictionary.js +4 -0
  226. package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
  227. package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
  228. package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
  229. package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
  230. package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
  231. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
  232. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  233. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
  234. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
  235. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
  236. package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
  237. package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
  238. package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
  239. package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
  240. package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
  241. package/build/dist/Utils/MetricUnitUtil.js +26 -0
  242. package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
  243. package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
  244. package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
  245. package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
  246. package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
  247. package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
  248. package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
  249. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
  250. package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
  251. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
  252. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  253. package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
  254. package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
  255. package/jest.config.json +2 -0
  256. package/package.json +1 -1
  257. package/test-setup.sh +9 -9
  258. package/tsconfig.json +51 -11
  259. package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +0 -399
@@ -1,6 +1,8 @@
1
1
  import DatabaseService from "./DatabaseService";
2
- import Model from "../../Models/DatabaseModels/NetworkDeviceDiscoveryScan";
3
- import { OnCreate, OnUpdate } from "../Types/Database/Hooks";
2
+ import Model, {
3
+ DiscoveredNetworkDevice,
4
+ } from "../../Models/DatabaseModels/NetworkDeviceDiscoveryScan";
5
+ import { OnCreate, OnFind, OnUpdate } from "../Types/Database/Hooks";
4
6
  import CreateBy from "../Types/Database/CreateBy";
5
7
  import UpdateBy from "../Types/Database/UpdateBy";
6
8
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
@@ -20,6 +22,7 @@ import SnmpScanConfigUtil, {
20
22
  } from "../../Utils/NetworkDiscovery/SnmpScanConfigUtil";
21
23
  import { getNextScanAt } from "../../Utils/NetworkDiscovery/RescanIntervalUtil";
22
24
  import ScanModeUtil from "../../Utils/NetworkDiscovery/ScanModeUtil";
25
+ import NetworkDeviceService from "./NetworkDeviceService";
23
26
 
24
27
  /*
25
28
  * The two spellings a many-to-one reference reaches a hook under: the
@@ -175,6 +178,88 @@ export class Service extends DatabaseService<Model> {
175
178
  super(Model);
176
179
  }
177
180
 
181
+ @CaptureSpan()
182
+ protected override async onFindSuccess(
183
+ onFind: OnFind<Model>,
184
+ items: Array<Model>,
185
+ ): Promise<OnFind<Model>> {
186
+ /*
187
+ * Registration is current inventory state, not a fact about the scan.
188
+ * Refresh both directions so old results can be used after a deletion.
189
+ */
190
+ const hostnamesByProject: Map<string, Set<string>> = new Map();
191
+
192
+ for (const scan of items) {
193
+ const projectId: ObjectID | undefined =
194
+ scan.projectId || onFind.findBy.props.tenantId;
195
+
196
+ if (!projectId || !Array.isArray(scan.discoveredDevices)) {
197
+ continue;
198
+ }
199
+
200
+ for (const host of scan.discoveredDevices) {
201
+ const hostname: string = this.getDiscoveredHostname(host);
202
+ if (!hostname) {
203
+ continue;
204
+ }
205
+
206
+ const projectKey: string = projectId.toString();
207
+ if (!hostnamesByProject.has(projectKey)) {
208
+ hostnamesByProject.set(projectKey, new Set());
209
+ }
210
+ hostnamesByProject.get(projectKey)!.add(hostname);
211
+ }
212
+ }
213
+
214
+ const registeredByProject: Map<string, Set<string>> = new Map();
215
+ for (const [projectId, hostnames] of hostnamesByProject) {
216
+ registeredByProject.set(
217
+ projectId,
218
+ await NetworkDeviceService.getRegisteredHostnames({
219
+ projectId: new ObjectID(projectId),
220
+ hostnames: Array.from(hostnames),
221
+ /*
222
+ * The scan read has already passed its access checks. Return only
223
+ * the same existence flag as probe ingest, scoped to this project.
224
+ */
225
+ props: { isRoot: true },
226
+ }),
227
+ );
228
+ }
229
+
230
+ return {
231
+ ...onFind,
232
+ carryForward: items.map((scan: Model): Model => {
233
+ const projectId: ObjectID | undefined =
234
+ scan.projectId || onFind.findBy.props.tenantId;
235
+ const registered: Set<string> | undefined = projectId
236
+ ? registeredByProject.get(projectId.toString())
237
+ : undefined;
238
+
239
+ if (!registered || !Array.isArray(scan.discoveredDevices)) {
240
+ return scan;
241
+ }
242
+
243
+ return Object.assign(new Model(), scan, {
244
+ discoveredDevices: scan.discoveredDevices.map(
245
+ (host: DiscoveredNetworkDevice): DiscoveredNetworkDevice => {
246
+ const hostname: string = this.getDiscoveredHostname(host);
247
+ return hostname
248
+ ? { ...host, isAlreadyRegistered: registered.has(hostname) }
249
+ : host;
250
+ },
251
+ ),
252
+ });
253
+ }),
254
+ };
255
+ }
256
+
257
+ private getDiscoveredHostname(host: DiscoveredNetworkDevice): string {
258
+ return host && typeof host.ipAddress === "string"
259
+ ? host.ipAddress.trim()
260
+ : "";
261
+ }
262
+
178
263
  /*
179
264
  * Validate the scan target at write time, with the same parser the probe
180
265
  * expands it with (Common/Utils/NetworkDiscovery/ScanTargetUtil).
@@ -30,6 +30,7 @@ import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException
30
30
  import ObjectID from "../../Types/ObjectID";
31
31
  import OneUptimeDate from "../../Types/Date";
32
32
  import CidrMatchUtil from "../../Utils/NetworkSite/CidrMatchUtil";
33
+ import { normalizeMac } from "../../Utils/Monitor/EndpointAttachmentUtil";
33
34
  import { SiteAssignmentRuleRunResult } from "../../Types/NetworkAutomation/RuleRunResult";
34
35
  import { NetworkDeviceMonitoringMethodUtil } from "../../Types/NetworkDevice/NetworkDeviceMonitoringMethod";
35
36
  import RelationIdUtil from "../Utils/Database/RelationIdUtil";
@@ -96,6 +97,61 @@ const SITE_KEYS: Array<string> = ["siteId", "site"];
96
97
  * otherwise a no-op rewrite of sysName on every SNMP walk would look like a
97
98
  * change and re-run the rules for the whole fleet every polling cycle.
98
99
  */
100
+ /*
101
+ * The MAC address column, normalised on the way in.
102
+ *
103
+ * Every spelling an operator is likely to paste - AA-BB-CC-DD-EE-FF,
104
+ * aabb.ccdd.eeff, aa:bb:cc:dd:ee:ff, twelve bare hex digits, with or
105
+ * without 0x - is stored as lowercase colon form, because that is the form
106
+ * every endpoint row already holds and the topology builder compares the
107
+ * two as plain strings. Anything that is not twelve hex digits once the
108
+ * separators are gone is refused outright rather than stored to match
109
+ * nothing forever. Blank clears the column, so "delete what I typed"
110
+ * works from a form that cannot post null.
111
+ *
112
+ * Shared by create and update, so it takes the raw payload: a create's
113
+ * data is a model instance and an update's is a QueryDeepPartialEntity, and
114
+ * on the latter a column may hold a SQL-expression function, which is left
115
+ * alone - nothing in the tree writes this column that way, and refusing it
116
+ * would only make a future caller's life harder for no safety gained.
117
+ *
118
+ * Every value that lands this way is the operator's, so the provenance flag
119
+ * is cleared beside it: the ARP pass writes hook-free and never comes
120
+ * through here, and it may only correct a value it wrote itself.
121
+ */
122
+ function normalizeMacAddressOnWrite(data: Record<string, unknown>): void {
123
+ if (!("macAddress" in data)) {
124
+ return;
125
+ }
126
+
127
+ const raw: unknown = data["macAddress"];
128
+
129
+ // TypeORM drops undefined before it builds the SET list; null clears.
130
+ if (raw === undefined || raw === null || typeof raw === "function") {
131
+ return;
132
+ }
133
+
134
+ if (typeof raw !== "string") {
135
+ throw new BadDataException("MAC Address must be text.");
136
+ }
137
+
138
+ if (raw.trim().length === 0) {
139
+ data["macAddress"] = null;
140
+ data["isMacAddressLearned"] = false;
141
+ return;
142
+ }
143
+
144
+ const normalized: string | undefined = normalizeMac(raw);
145
+ if (!normalized) {
146
+ throw new BadDataException(
147
+ "MAC Address must be six pairs of hex digits, for example aa:bb:cc:dd:ee:ff. Dashes, dots and bare hex are accepted too.",
148
+ );
149
+ }
150
+
151
+ data["macAddress"] = normalized;
152
+ data["isMacAddressLearned"] = false;
153
+ }
154
+
99
155
  function normalizeIdentityValue(value: unknown): string {
100
156
  if (value === null || value === undefined) {
101
157
  return "";
@@ -1239,6 +1295,10 @@ export class Service extends DatabaseService<Model> {
1239
1295
  protected override async onBeforeCreate(
1240
1296
  createBy: CreateBy<Model>,
1241
1297
  ): Promise<OnCreate<Model>> {
1298
+ normalizeMacAddressOnWrite(
1299
+ createBy.data as unknown as Record<string, unknown>,
1300
+ );
1301
+
1242
1302
  /*
1243
1303
  * Read both spellings: the dashboard posts the `site` relation, not the
1244
1304
  * `siteId` column, so guarding only `siteId` let a UI-created device
@@ -1523,6 +1583,15 @@ export class Service extends DatabaseService<Model> {
1523
1583
  ): Promise<OnUpdate<Model>> {
1524
1584
  const dataKeys: Array<string> = Object.keys(updateBy.data || {});
1525
1585
 
1586
+ /*
1587
+ * Above the early return, like the id guards below it: a malformed
1588
+ * MAC is refused on every write shape, not only on the ones that
1589
+ * happen to need the snapshot.
1590
+ */
1591
+ normalizeMacAddressOnWrite(
1592
+ updateBy.data as unknown as Record<string, unknown>,
1593
+ );
1594
+
1526
1595
  /*
1527
1596
  * Switching a device to monitor-backed turns polling off with it. The
1528
1597
  * two are one decision, not two: a monitor-backed device's health is
@@ -982,6 +982,16 @@ export class Service extends DatabaseService<Model> {
982
982
  rootCause: true,
983
983
  incidentNumber: true,
984
984
  incidentNumberWithPrefix: true,
985
+ /*
986
+ * AcknowledgeIncident.hbs asks for a Resources Affected row and
987
+ * the generator had nothing to build it from, so the row was
988
+ * silently dropped — the same `{{#if}}` swallow that hid the
989
+ * alert's Root Cause.
990
+ */
991
+ monitors: {
992
+ _id: true,
993
+ name: true,
994
+ },
985
995
  },
986
996
  });
987
997
  }
@@ -1012,6 +1022,20 @@ export class Service extends DatabaseService<Model> {
1012
1022
  },
1013
1023
  alertNumber: true,
1014
1024
  alertNumberWithPrefix: true,
1025
+ /*
1026
+ * AcknowledgeAlert.hbs has always asked for a Root Cause row, but
1027
+ * this select never loaded the column and the template's
1028
+ * `{{#if text}}` guard silently dropped the row — so the one
1029
+ * person being woken up for the alert was the only one who could
1030
+ * not see why it fired. The other three on-call templates already
1031
+ * select it.
1032
+ */
1033
+ rootCause: true,
1034
+ // Same story for the template's Resources Affected row.
1035
+ monitor: {
1036
+ _id: true,
1037
+ name: true,
1038
+ },
1015
1039
  },
1016
1040
  });
1017
1041
  }
@@ -4589,6 +4613,11 @@ export class Service extends DatabaseService<Model> {
4589
4613
  MarkdownContentType.Email,
4590
4614
  ),
4591
4615
  alertSeverity: alert.alertSeverity!.name!,
4616
+ resourcesAffected: alert.monitor?.name || "No resources identified",
4617
+ rootCause: await Markdown.convertToHTML(
4618
+ alert.rootCause || "No root cause identified for this alert",
4619
+ MarkdownContentType.Email,
4620
+ ),
4592
4621
  alertViewLink: (
4593
4622
  await AlertService.getAlertLinkInDashboard(alert.projectId!, alert.id!)
4594
4623
  ).toString(),
@@ -4634,8 +4663,19 @@ export class Service extends DatabaseService<Model> {
4634
4663
  MarkdownContentType.Email,
4635
4664
  ),
4636
4665
  incidentSeverity: incident.incidentSeverity!.name!,
4637
- rootCause:
4666
+ resourcesAffected:
4667
+ (incident.monitors || [])
4668
+ .map((monitor: Monitor): string => {
4669
+ return monitor.name || "";
4670
+ })
4671
+ .filter((name: string): boolean => {
4672
+ return name.length > 0;
4673
+ })
4674
+ .join(", ") || "No resources identified",
4675
+ rootCause: await Markdown.convertToHTML(
4638
4676
  incident.rootCause || "No root cause identified for this incident",
4677
+ MarkdownContentType.Email,
4678
+ ),
4639
4679
  incidentViewLink: (
4640
4680
  await IncidentService.getIncidentLinkInDashboard(
4641
4681
  incident.projectId!,
@@ -4738,7 +4778,9 @@ export class Service extends DatabaseService<Model> {
4738
4778
  if (alerts.length > 0) {
4739
4779
  const alertRows: string[] = [];
4740
4780
  for (const alert of alerts) {
4741
- const alertTitle: string = alert.title || "Untitled Alert";
4781
+ const alertTitle: string = Markdown.escapeHtml(
4782
+ alert.title || "Untitled Alert",
4783
+ );
4742
4784
  const alertNumber: string =
4743
4785
  alert.alertNumberWithPrefix ||
4744
4786
  (alert.alertNumber ? `#${alert.alertNumber}` : "");
@@ -4748,20 +4790,22 @@ export class Service extends DatabaseService<Model> {
4748
4790
  alert.id!,
4749
4791
  )
4750
4792
  ).toString();
4751
- const monitorName: string = alert.monitor?.name || "";
4793
+ const monitorName: string = Markdown.escapeHtml(
4794
+ alert.monitor?.name || "",
4795
+ );
4752
4796
 
4753
4797
  alertRows.push(`
4754
4798
  <tr>
4755
- <td style="padding: 12px 16px; border-bottom: 1px solid #e2e8f0;">
4756
- <table cellpadding="0" cellspacing="0" width="100%">
4799
+ <td style="padding: 16px 20px; border-bottom: 1px solid #e2e8f0; overflow-wrap: anywhere; word-break: break-word;">
4800
+ <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="width: 100%; table-layout: fixed;">
4757
4801
  <tr>
4758
4802
  <td style="vertical-align: middle;">
4759
- <span style="display: inline-block; background-color: #dbeafe; color: #1e40af; font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: 4px; margin-right: 8px;">${alertNumber}</span>
4760
- <a href="${alertLink}" style="color: #2563eb; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; font-weight: 500; text-decoration: none;">${alertTitle}</a>
4761
- ${monitorName ? `<span style="display: block; color: #64748b; font-size: 12px; margin-top: 4px;">Monitor: ${monitorName}</span>` : ""}
4803
+ <span style="display: inline-block; background-color: #f1f5f9; color: #111111; font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: 4px; margin-right: 8px;">${alertNumber}</span>
4804
+ <a href="${alertLink}" style="color: #111111; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 15px; line-height: 24px; font-weight: 600; text-decoration: underline; text-underline-offset: 3px;">${alertTitle}</a>
4805
+ ${monitorName ? `<span style="display: block; color: #64748b; font-size: 13px; line-height: 20px; margin-top: 6px;">Monitor: ${monitorName}</span>` : ""}
4762
4806
  </td>
4763
- <td style="text-align: right; vertical-align: middle;">
4764
- <a href="${alertLink}" style="color: #2563eb; font-size: 12px; text-decoration: none;">View →</a>
4807
+ <td width="52" style="text-align: right; vertical-align: middle; padding-left: 8px;">
4808
+ <a href="${alertLink}" style="color: #111111; font-size: 13px; line-height: 20px; text-decoration: underline;">View →</a>
4765
4809
  </td>
4766
4810
  </tr>
4767
4811
  </table>
@@ -4771,7 +4815,7 @@ export class Service extends DatabaseService<Model> {
4771
4815
  }
4772
4816
  if (alertRows.length > 0) {
4773
4817
  alertsListHtml = `
4774
- <table cellpadding="0" cellspacing="0" width="100%" style="background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); border-radius: 8px; border: 1px solid #e2e8f0; margin: 8px 0 16px 0;">
4818
+ <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="width: 100%; table-layout: fixed; background-color: #f8fafc; border-radius: 12px; border: 1px solid #e2e8f0; margin: 8px 0 16px 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
4775
4819
  <tbody>
4776
4820
  ${alertRows.join("")}
4777
4821
  </tbody>
@@ -4795,9 +4839,11 @@ export class Service extends DatabaseService<Model> {
4795
4839
  ),
4796
4840
  alertEpisodeSeverity: alertEpisode.alertSeverity!.name!,
4797
4841
  resourcesAffected: resourcesAffected,
4798
- rootCause:
4842
+ rootCause: await Markdown.convertToHTML(
4799
4843
  alertEpisode.rootCause ||
4800
- "No root cause identified for this alert episode",
4844
+ "No root cause identified for this alert episode",
4845
+ MarkdownContentType.Email,
4846
+ ),
4801
4847
  alertsList: alertsListHtml,
4802
4848
  alertsCount: alerts.length.toString(),
4803
4849
  alertEpisodeViewLink: (
@@ -4908,7 +4954,9 @@ export class Service extends DatabaseService<Model> {
4908
4954
  if (incidents.length > 0) {
4909
4955
  const incidentRows: string[] = [];
4910
4956
  for (const incident of incidents) {
4911
- const incidentTitle: string = incident.title || "Untitled Incident";
4957
+ const incidentTitle: string = Markdown.escapeHtml(
4958
+ incident.title || "Untitled Incident",
4959
+ );
4912
4960
  const incidentNumber: string =
4913
4961
  incident.incidentNumberWithPrefix ||
4914
4962
  (incident.incidentNumber ? `#${incident.incidentNumber}` : "");
@@ -4918,7 +4966,7 @@ export class Service extends DatabaseService<Model> {
4918
4966
  incident.id!,
4919
4967
  )
4920
4968
  ).toString();
4921
- const monitorName: string =
4969
+ const monitorName: string = Markdown.escapeHtml(
4922
4970
  (incident.monitors || [])
4923
4971
  .map((monitor: Monitor): string => {
4924
4972
  return monitor.name || "";
@@ -4926,20 +4974,21 @@ export class Service extends DatabaseService<Model> {
4926
4974
  .filter((name: string): boolean => {
4927
4975
  return name.length > 0;
4928
4976
  })
4929
- .join(", ") || "";
4977
+ .join(", ") || "",
4978
+ );
4930
4979
 
4931
4980
  incidentRows.push(`
4932
4981
  <tr>
4933
- <td style="padding: 12px 16px; border-bottom: 1px solid #e2e8f0;">
4934
- <table cellpadding="0" cellspacing="0" width="100%">
4982
+ <td style="padding: 16px 20px; border-bottom: 1px solid #e2e8f0; overflow-wrap: anywhere; word-break: break-word;">
4983
+ <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="width: 100%; table-layout: fixed;">
4935
4984
  <tr>
4936
4985
  <td style="vertical-align: middle;">
4937
4986
  <span style="display: inline-block; background-color: #fee2e2; color: #991b1b; font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: 4px; margin-right: 8px;">${incidentNumber}</span>
4938
- <a href="${incidentLink}" style="color: #2563eb; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; font-weight: 500; text-decoration: none;">${incidentTitle}</a>
4939
- ${monitorName ? `<span style="display: block; color: #64748b; font-size: 12px; margin-top: 4px;">Monitor: ${monitorName}</span>` : ""}
4987
+ <a href="${incidentLink}" style="color: #111111; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 15px; line-height: 24px; font-weight: 600; text-decoration: underline; text-underline-offset: 3px;">${incidentTitle}</a>
4988
+ ${monitorName ? `<span style="display: block; color: #64748b; font-size: 13px; line-height: 20px; margin-top: 6px;">Monitor: ${monitorName}</span>` : ""}
4940
4989
  </td>
4941
- <td style="text-align: right; vertical-align: middle;">
4942
- <a href="${incidentLink}" style="color: #2563eb; font-size: 12px; text-decoration: none;">View →</a>
4990
+ <td width="52" style="text-align: right; vertical-align: middle; padding-left: 8px;">
4991
+ <a href="${incidentLink}" style="color: #111111; font-size: 13px; line-height: 20px; text-decoration: underline;">View →</a>
4943
4992
  </td>
4944
4993
  </tr>
4945
4994
  </table>
@@ -4949,7 +4998,7 @@ export class Service extends DatabaseService<Model> {
4949
4998
  }
4950
4999
  if (incidentRows.length > 0) {
4951
5000
  incidentsListHtml = `
4952
- <table cellpadding="0" cellspacing="0" width="100%" style="background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%); border-radius: 8px; border: 1px solid #e2e8f0; margin: 8px 0 16px 0;">
5001
+ <table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="width: 100%; table-layout: fixed; background-color: #f8fafc; border-radius: 12px; border: 1px solid #e2e8f0; margin: 8px 0 16px 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
4953
5002
  <tbody>
4954
5003
  ${incidentRows.join("")}
4955
5004
  </tbody>
@@ -4975,9 +5024,11 @@ export class Service extends DatabaseService<Model> {
4975
5024
  ),
4976
5025
  incidentEpisodeSeverity: incidentEpisode.incidentSeverity!.name!,
4977
5026
  resourcesAffected: resourcesAffected,
4978
- rootCause:
5027
+ rootCause: await Markdown.convertToHTML(
4979
5028
  incidentEpisode.rootCause ||
4980
- "No root cause identified for this incident episode",
5029
+ "No root cause identified for this incident episode",
5030
+ MarkdownContentType.Email,
5031
+ ),
4981
5032
  incidentsList: incidentsListHtml,
4982
5033
  incidentsCount: incidents.length.toString(),
4983
5034
  incidentEpisodeViewLink: (
@@ -1,6 +1,7 @@
1
1
  import DatabaseRequestType from "../../BaseDatabase/DatabaseRequestType";
2
2
  import Query from "../Query";
3
3
  import QueryHelper from "../QueryHelper";
4
+ import QueryUtil from "../QueryUtil";
4
5
  import RelationSelect from "../RelationSelect";
5
6
  import Select from "../Select";
6
7
  import SelectUtil from "../SelectUtil";
@@ -12,8 +13,11 @@ import DatabaseCommonInteractionPropsUtil, {
12
13
  PermissionType,
13
14
  } from "../../../../Types/BaseDatabase/DatabaseCommonInteractionPropsUtil";
14
15
  import ObjectID from "../../../../Types/ObjectID";
16
+ import BadDataException from "../../../../Types/Exception/BadDataException";
15
17
  import Permission, { UserPermission } from "../../../../Types/Permission";
16
18
  import CaptureSpan from "../../../Utils/Telemetry/CaptureSpan";
19
+ import { combineWithPrivacyClause } from "../../../Utils/PrivacyFilterUtil";
20
+ import { FindOperator } from "typeorm";
17
21
 
18
22
  export interface CheckReadPermissionType<TBaseModel extends BaseModel>
19
23
  extends CheckPermissionBaseInterface<TBaseModel> {
@@ -119,13 +123,49 @@ export default class ReadPermission {
119
123
  }
120
124
  }
121
125
 
122
- // now add these to query
123
-
124
126
  const model: TBaseModel = new modelType();
127
+ const accessControlColumn: string | null = model.getAccessControlColumn();
128
+ const manyToManyMeta: ReturnType<
129
+ typeof QueryUtil.getManyToManyRelationMetadata
130
+ > = accessControlColumn
131
+ ? QueryUtil.getManyToManyRelationMetadata(modelType, accessControlColumn)
132
+ : null;
133
+
134
+ if (!manyToManyMeta) {
135
+ throw new BadDataException(
136
+ "Cannot apply read label restrictions without access-control relation metadata.",
137
+ );
138
+ }
125
139
 
126
- (query as any)[model.getAccessControlColumn() as string] = {
127
- _id: QueryHelper.notInOrNull(labelIds),
128
- };
140
+ const idQuery: Query<TBaseModel> = QueryUtil.serializeQuery(modelType, {
141
+ _id: query._id,
142
+ } as Query<TBaseModel>);
143
+ const existingIdFilter: unknown = idQuery._id;
144
+ if (
145
+ existingIdFilter !== undefined &&
146
+ typeof existingIdFilter !== "string" &&
147
+ !(existingIdFilter instanceof FindOperator)
148
+ ) {
149
+ throw new BadDataException(
150
+ "Cannot combine read label restrictions with an unsupported ID filter.",
151
+ );
152
+ }
153
+
154
+ /*
155
+ * Keep the caller's label selection intact. Blocking labels is a separate
156
+ * condition on the owner: it must have none of the blocked label links.
157
+ * Applying that condition to _id also lets grouped dashboard label filters
158
+ * and existing record selectors reach QueryUtil without being replaced.
159
+ * Serialize the ID condition first because QueryUtil does not descend into
160
+ * the database AND operator to convert application query operators later.
161
+ */
162
+ (query as any)._id = combineWithPrivacyClause(
163
+ existingIdFilter,
164
+ QueryHelper.noneEntitiesInManyToMany({
165
+ values: labelIds,
166
+ ...manyToManyMeta,
167
+ }),
168
+ );
129
169
 
130
170
  return { query };
131
171
  }
@@ -7,6 +7,7 @@ import GreaterThanOrEqual from "../../../Types/BaseDatabase/GreaterThanOrEqual";
7
7
  import InBetween from "../../../Types/BaseDatabase/InBetween";
8
8
  import Includes from "../../../Types/BaseDatabase/Includes";
9
9
  import IncludesAll from "../../../Types/BaseDatabase/IncludesAll";
10
+ import IncludesAnyOfGroups from "../../../Types/BaseDatabase/IncludesAnyOfGroups";
10
11
  import IncludesNone from "../../../Types/BaseDatabase/IncludesNone";
11
12
  import StartsWith from "../../../Types/BaseDatabase/StartsWith";
12
13
  import EndsWith from "../../../Types/BaseDatabase/EndsWith";
@@ -24,6 +25,7 @@ import { TableColumnMetadata } from "../../../Types/Database/TableColumn";
24
25
  import TableColumnType from "../../../Types/Database/TableColumnType";
25
26
  import { JSONObject } from "../../../Types/JSON";
26
27
  import ObjectID from "../../../Types/ObjectID";
28
+ import BadDataException from "../../../Types/Exception/BadDataException";
27
29
  import Typeof from "../../../Types/Typeof";
28
30
  import { And, DataSource } from "typeorm";
29
31
  import { FindOperator } from "typeorm/find-options/FindOperator";
@@ -45,6 +47,7 @@ export default class QueryUtil {
45
47
  const model: BaseModel = new modelType();
46
48
 
47
49
  query = query as Query<TBaseModel>;
50
+ const groupedRelationFilters: Array<FindOperator<unknown>> = [];
48
51
 
49
52
  /*
50
53
  * Multi-field text search:
@@ -126,6 +129,37 @@ export default class QueryUtil {
126
129
  const tableColumnMetadata: TableColumnMetadata =
127
130
  model.getTableColumnMetadata(key);
128
131
 
132
+ if (query[key] instanceof IncludesAnyOfGroups) {
133
+ if (tableColumnMetadata?.type !== TableColumnType.EntityArray) {
134
+ throw new BadDataException(
135
+ "IncludesAnyOfGroups requires a many-to-many entity relation.",
136
+ );
137
+ }
138
+
139
+ const relation: {
140
+ joinTableName: string;
141
+ ownerColumnName: string;
142
+ relationColumnName: string;
143
+ } | null = QueryUtil.getManyToManyRelationMetadata(modelType, key);
144
+
145
+ if (!relation) {
146
+ throw new BadDataException(
147
+ "IncludesAnyOfGroups requires available many-to-many relation metadata.",
148
+ );
149
+ }
150
+
151
+ for (const group of (query[key] as IncludesAnyOfGroups).groups) {
152
+ groupedRelationFilters.push(
153
+ QueryHelper.anyOfEntitiesInManyToMany({
154
+ ...relation,
155
+ values: group,
156
+ }) as FindOperator<unknown>,
157
+ );
158
+ }
159
+ delete query[key];
160
+ continue;
161
+ }
162
+
129
163
  if (tableColumnMetadata && query[key] === null) {
130
164
  query[key] = QueryHelper.isNull();
131
165
  } else if (
@@ -634,6 +668,28 @@ export default class QueryUtil {
634
668
  }
635
669
  }
636
670
 
671
+ if (groupedRelationFilters.length > 0) {
672
+ /*
673
+ * Apply these only after scalar operators have been serialized, so an
674
+ * existing id condition survives regardless of the query's key order.
675
+ */
676
+ const existingIdFilter: unknown = query._id;
677
+ if (existingIdFilter !== undefined) {
678
+ if (existingIdFilter instanceof FindOperator) {
679
+ groupedRelationFilters.unshift(existingIdFilter);
680
+ } else if (typeof existingIdFilter === "string") {
681
+ groupedRelationFilters.unshift(
682
+ QueryHelper.equalTo(existingIdFilter) as FindOperator<unknown>,
683
+ );
684
+ } else {
685
+ throw new BadDataException(
686
+ "IncludesAnyOfGroups cannot combine an unsupported id filter.",
687
+ );
688
+ }
689
+ }
690
+ query._id = And(...groupedRelationFilters) as any;
691
+ }
692
+
637
693
  return query;
638
694
  }
639
695