@oneuptime/common 14.0.6 → 14.0.8

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 (556) hide show
  1. package/Models/DatabaseModels/Incident.ts +267 -1
  2. package/Models/DatabaseModels/IncidentCustomField.ts +202 -0
  3. package/Models/DatabaseModels/IncidentEpisode.ts +30 -0
  4. package/Models/DatabaseModels/IncidentTemplate.ts +126 -1
  5. package/Models/DatabaseModels/InventoryItem.ts +23 -12
  6. package/Models/DatabaseModels/StatusPage.ts +55 -0
  7. package/Models/DatabaseModels/StatusPageSubscriber.ts +128 -0
  8. package/Server/API/IncidentAPI.ts +162 -0
  9. package/Server/API/StatusPageAPI.ts +487 -171
  10. package/Server/API/StatusPageSubscriberAPI.ts +63 -15
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.ts +217 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.ts +49 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.ts +108 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.ts +33 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.ts +51 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.ts +33 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.ts +168 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.ts +44 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +16 -0
  20. package/Server/Infrastructure/Semaphore.ts +10 -0
  21. package/Server/Middleware/IdentityRateLimit.ts +108 -3
  22. package/Server/Middleware/SubscriberNotificationPreviewRateLimit.ts +237 -0
  23. package/Server/Middleware/SubscriberNotificationTestSendRateLimit.ts +304 -0
  24. package/Server/Services/AlertService.ts +32 -36
  25. package/Server/Services/DatabaseService.ts +93 -10
  26. package/Server/Services/IncidentAlertService.ts +609 -2
  27. package/Server/Services/IncidentCustomFieldService.ts +276 -1
  28. package/Server/Services/IncidentEpisodePublicNoteService.ts +28 -0
  29. package/Server/Services/IncidentEpisodeService.ts +15 -0
  30. package/Server/Services/IncidentEpisodeStateTimelineService.ts +27 -1
  31. package/Server/Services/IncidentPublicNoteService.ts +30 -1
  32. package/Server/Services/IncidentService.ts +1663 -103
  33. package/Server/Services/IncidentStateTimelineService.ts +13 -0
  34. package/Server/Services/IncidentTemplateService.ts +109 -0
  35. package/Server/Services/InventoryItemService.ts +38 -0
  36. package/Server/Services/IoTDeviceCredentialService.ts +13 -4
  37. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +30 -1
  38. package/Server/Services/ScheduledMaintenanceService.ts +118 -64
  39. package/Server/Services/SmsService.ts +10 -0
  40. package/Server/Services/StatusPageService.ts +65 -8
  41. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +299 -37
  42. package/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.ts +129 -0
  43. package/Server/Services/StatusPageSubscriberService.ts +1161 -64
  44. package/Server/Services/UserNotificationRuleService.ts +55 -53
  45. package/Server/Services/WorkspaceNotificationSummaryService.ts +38 -20
  46. package/Server/Types/Markdown.ts +153 -6
  47. package/Server/Types/Workflow/Components/Email.ts +61 -2
  48. package/Server/Utils/AffectedResources/LinkedAffectedResources.ts +576 -0
  49. package/Server/Utils/Alert/AlertStateChangeAuthorization.ts +129 -0
  50. package/Server/Utils/CustomField/CustomFieldRename.ts +378 -0
  51. package/Server/Utils/DataSource/EgressGuard.ts +66 -21
  52. package/Server/Utils/Inventory/InventoryOverviewAggregation.ts +111 -0
  53. package/Server/Utils/Mail/PinnedSmtpSocket.ts +130 -0
  54. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +10 -0
  55. package/Server/Utils/Response.ts +31 -0
  56. package/Server/Utils/SSRFProtection.ts +76 -16
  57. package/Server/Utils/StatusPage/IncidentStatusPageScope.ts +948 -0
  58. package/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.ts +656 -0
  59. package/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.ts +805 -0
  60. package/Server/Utils/StatusPage/StatusPageExclusion.ts +18 -0
  61. package/Server/Utils/StatusPage/StatusPageReadAccess.ts +125 -0
  62. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.ts +151 -0
  63. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.ts +70 -0
  64. package/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.ts +421 -0
  65. package/Server/Utils/StatusPage/SubscriberNotificationClaim.ts +107 -0
  66. package/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.ts +1049 -0
  67. package/Server/Utils/StatusPage/SubscriberNotificationFanOut.ts +180 -0
  68. package/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.ts +839 -0
  69. package/Server/Utils/StatusPage/SubscriberNotificationResendAccess.ts +393 -0
  70. package/Server/Utils/StatusPage/SubscriberNotificationRunLimit.ts +246 -0
  71. package/Server/Utils/StatusPage/SubscriberNotificationTiming.ts +133 -0
  72. package/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.ts +215 -0
  73. package/Server/Utils/StatusPageResource.ts +51 -20
  74. package/Server/Utils/Topology/TopologyConcurrencyLimiter.ts +229 -0
  75. package/Server/Utils/Topology/TopologyQueries.ts +1350 -0
  76. package/Server/Utils/Topology/TopologyRequest.ts +463 -0
  77. package/Server/Utils/Topology/TopologyResponseCache.ts +183 -0
  78. package/Server/Utils/Topology/TopologySql.ts +1024 -0
  79. package/Server/Utils/VM/VMAPI.ts +1 -0
  80. package/Server/Utils/VM/VMRunner.ts +10 -0
  81. package/Server/Utils/Workflow/OAuth2TokenClient.ts +10 -0
  82. package/Server/Views/Partials/SensitiveUrlToken.ejs +8 -5
  83. package/Tests/App/Accounts/RegisterVerifyEmail.test.tsx +748 -0
  84. package/Tests/App/Accounts/ResendVerificationEmail.test.tsx +1222 -0
  85. package/Tests/App/Accounts/VerificationEmailResendUtils.test.ts +797 -0
  86. package/Tests/App/Accounts/VerifyEmailPending.test.tsx +538 -0
  87. package/Tests/App/Accounts/VerifyEmailResend.test.tsx +779 -0
  88. package/Tests/App/Dashboard/AcknowledgeAlertsOnDeclare.test.ts +369 -0
  89. package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +1 -1
  90. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +510 -123
  91. package/Tests/App/Dashboard/AlertsTableAffectedResources.test.tsx +490 -0
  92. package/Tests/App/Dashboard/ChangeAlertStateDeclareIncident.test.tsx +886 -0
  93. package/Tests/App/Dashboard/CreateReviewStepRoleSummary.test.tsx +1250 -0
  94. package/Tests/App/Dashboard/CreateReviewStepSelectionSummary.test.tsx +738 -0
  95. package/Tests/App/Dashboard/CustomFieldsSettingsIncidentOptions.test.tsx +357 -0
  96. package/Tests/App/Dashboard/DeclareIncidentFromAlert.test.ts +417 -0
  97. package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +1948 -229
  98. package/Tests/App/Dashboard/EntityDetailPanelLifecycle.test.tsx +493 -0
  99. package/Tests/App/Dashboard/EntityDetailPanelNavigation.test.tsx +439 -0
  100. package/Tests/App/Dashboard/EventNotes.test.tsx +776 -0
  101. package/Tests/App/Dashboard/EventNotesPages.test.tsx +119 -0
  102. package/Tests/App/Dashboard/EventNotesUtil.test.ts +116 -17
  103. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +534 -6
  104. package/Tests/App/Dashboard/EventStatusPanel.test.tsx +798 -2
  105. package/Tests/App/Dashboard/FetchStateAndSeverityElements.test.tsx +669 -0
  106. package/Tests/App/Dashboard/FetchUsers.test.tsx +230 -0
  107. package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +91 -1
  108. package/Tests/App/Dashboard/IncidentCreateAcknowledgeAlerts.test.tsx +1611 -0
  109. package/Tests/App/Dashboard/IncidentCreateDetailsStep.test.tsx +643 -0
  110. package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +8 -1
  111. package/Tests/App/Dashboard/IncidentCreateStatusPageScope.test.tsx +601 -0
  112. package/Tests/App/Dashboard/IncidentCustomFieldTemplateVariables.test.tsx +386 -0
  113. package/Tests/App/Dashboard/IncidentNoteTemplatePlaceholders.test.tsx +124 -0
  114. package/Tests/App/Dashboard/IncidentNoteTemplateVariablesLoader.test.ts +305 -0
  115. package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +112 -0
  116. package/Tests/App/Dashboard/IncidentSettingsRenotifyOnPublish.test.tsx +674 -0
  117. package/Tests/App/Dashboard/IncidentSettingsStatusPageScope.test.tsx +1051 -0
  118. package/Tests/App/Dashboard/IncidentTemplateCustomFields.test.tsx +384 -0
  119. package/Tests/App/Dashboard/IncidentTemplateStatusPageScopeCard.test.tsx +215 -0
  120. package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +1365 -57
  121. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +964 -23
  122. package/Tests/App/Dashboard/InventoryRelationships.test.tsx +885 -0
  123. package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +113 -1
  124. package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +201 -15
  125. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +508 -36
  126. package/Tests/App/Dashboard/SubscriberAudienceSummary.test.tsx +512 -0
  127. package/Tests/App/Dashboard/SubscriberNotificationPreviewModal.test.tsx +837 -0
  128. package/Tests/App/Dashboard/SubscriberNotificationStatusLabel.test.tsx +88 -0
  129. package/Tests/App/Dashboard/SubscriberNotificationStatusResend.test.tsx +473 -0
  130. package/Tests/App/Dashboard/SubscriberTemplateLivePreview.test.tsx +196 -0
  131. package/Tests/App/Dashboard/TeamAddedSubscribersUnsubscribedNotice.test.tsx +248 -0
  132. package/Tests/App/Dashboard/TopologyDataLifecycle.test.tsx +583 -0
  133. package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +982 -218
  134. package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +818 -114
  135. package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +64 -3
  136. package/Tests/App/StatusPage/StatusPageUnsubscribePage.test.tsx +412 -0
  137. package/Tests/Models/IncidentCustomFieldApiSchemaContract.test.ts +217 -0
  138. package/Tests/Models/IncidentCustomFieldCreateSettingsColumns.test.ts +252 -0
  139. package/Tests/Models/StatusPageSubscriberUnsubscribeColumns.test.ts +218 -0
  140. package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +7 -0
  141. package/Tests/Server/API/IncidentSubscriberAudienceAPI.test.ts +1408 -0
  142. package/Tests/Server/API/StatusPageIncidentScope.test.ts +1868 -0
  143. package/Tests/Server/API/StatusPageIncidentScopePostgres.test.ts +790 -0
  144. package/Tests/Server/API/StatusPageManageSubscriptionRecipient.test.ts +10 -1
  145. package/Tests/Server/API/StatusPageManageSubscriptionTemplateVariables.test.ts +206 -18
  146. package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +14 -2
  147. package/Tests/Server/API/StatusPageSubscriberCreateResponse.test.ts +217 -0
  148. package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +103 -2
  149. package/Tests/Server/API/StatusPageUnsubscribeAPI.test.ts +396 -0
  150. package/Tests/Server/Infrastructure/Postgres/AddIncidentStatusPageScopeMigration.test.ts +395 -0
  151. package/Tests/Server/Infrastructure/Postgres/AddIncidentTemplateStatusPageScopeFlagMigration.test.ts +150 -0
  152. package/Tests/Server/Infrastructure/Postgres/AddSubscriberNotificationClaimedAtMigration.test.ts +156 -0
  153. package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +90 -21
  154. package/Tests/Server/Infrastructure/Postgres/SkipHiddenIncidentCreatedNotificationsMigration.test.ts +410 -0
  155. package/Tests/Server/Middleware/IdentityRateLimit.test.ts +551 -1
  156. package/Tests/Server/Middleware/SubscriberNotificationPreviewRateLimit.test.ts +238 -0
  157. package/Tests/Server/Middleware/SubscriberNotificationTestSendRateLimit.test.ts +245 -0
  158. package/Tests/Server/Services/AddStatusPageSubscriberUnsubscribeTokenMigration.test.ts +152 -0
  159. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +221 -36
  160. package/Tests/Server/Services/DatabaseServiceCompareAndSetColumns.test.ts +191 -0
  161. package/Tests/Server/Services/DomainService.test.ts +525 -0
  162. package/Tests/Server/Services/IncidentAlertAcknowledgeOnDeclare.test.ts +2630 -0
  163. package/Tests/Server/Services/IncidentAlertPostgres.test.ts +1625 -1
  164. package/Tests/Server/Services/IncidentCreatedRenotifyOnPublish.test.ts +681 -0
  165. package/Tests/Server/Services/IncidentCreatedResend.test.ts +775 -0
  166. package/Tests/Server/Services/IncidentCustomFieldRenamePostgres.test.ts +713 -0
  167. package/Tests/Server/Services/IncidentCustomFieldService.test.ts +751 -0
  168. package/Tests/Server/Services/IncidentCustomFieldValueValidation.test.ts +556 -0
  169. package/Tests/Server/Services/IncidentDeclareAcknowledgeAlerts.test.ts +2189 -0
  170. package/Tests/Server/Services/IncidentStatusPageScope.test.ts +2971 -0
  171. package/Tests/Server/Services/IncidentStatusPageScopeFeed.test.ts +131 -0
  172. package/Tests/Server/Services/IncidentStatusPageScopePostgres.test.ts +625 -0
  173. package/Tests/Server/Services/IncidentTemplateCustomFieldsCopy.test.ts +311 -0
  174. package/Tests/Server/Services/IncidentTemplateStatusPageScope.test.ts +363 -0
  175. package/Tests/Server/Services/InventoryItemServiceOverviewCounts.test.ts +345 -0
  176. package/Tests/Server/Services/InventoryOverviewPostgres.test.ts +254 -0
  177. package/Tests/Server/Services/IoTDeviceCredentialService.test.ts +819 -0
  178. package/Tests/Server/Services/LinkedAffectedResourcesCreatedFeed.test.ts +420 -0
  179. package/Tests/Server/Services/PublicNoteEditFeedEventType.test.ts +213 -0
  180. package/Tests/Server/Services/PublicNoteResendAccess.test.ts +994 -0
  181. package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +289 -9
  182. package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +92 -36
  183. package/Tests/Server/Services/SmsServiceFailIfNotSent.test.ts +67 -0
  184. package/Tests/Server/Services/StatusPageReportIncidentCounts.test.ts +610 -0
  185. package/Tests/Server/Services/StatusPageSubscriberAudienceCounts.test.ts +201 -0
  186. package/Tests/Server/Services/StatusPageSubscriberAudienceCountsPostgres.test.ts +271 -0
  187. package/Tests/Server/Services/StatusPageSubscriberConfirmationTemplateEscaping.test.ts +200 -0
  188. package/Tests/Server/Services/StatusPageSubscriberNotificationLogRedaction.test.ts +54 -0
  189. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateCompile.test.ts +37 -0
  190. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateIncidentRecordAccess.test.ts +772 -0
  191. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookup.test.ts +506 -0
  192. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookupPostgres.test.ts +261 -0
  193. package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +21 -5
  194. package/Tests/Server/Services/StatusPageSubscriberUnsubscribe.test.ts +1618 -0
  195. package/Tests/Server/Services/StatusPageSubscriberUnsubscribeLinkSenders.test.ts +373 -0
  196. package/Tests/Server/Services/StatusPageSubscriberUnsubscribePostgres.test.ts +388 -0
  197. package/Tests/Server/Services/SubscriberNotificationInFlightRequeue.test.ts +500 -0
  198. package/Tests/Server/Types/MarkdownEmailLinks.test.ts +320 -0
  199. package/Tests/Server/Types/Workflow/Components/Email.test.ts +491 -1
  200. package/Tests/Server/Types/Workflow/Components/EmailSmtpPinning.test.ts +398 -0
  201. package/Tests/Server/Utils/AffectedResources/LinkedAffectedResources.test.ts +858 -0
  202. package/Tests/Server/Utils/Alert/AlertStateChangeAuthorization.test.ts +1172 -0
  203. package/Tests/Server/Utils/CustomField/CustomFieldRename.test.ts +586 -0
  204. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +246 -10
  205. package/Tests/Server/Utils/DataSource/EgressGuardDeploymentConfig.test.ts +226 -0
  206. package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +131 -0
  207. package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +4 -0
  208. package/Tests/Server/Utils/EgressPolicyEnvironment.ts +45 -0
  209. package/Tests/Server/Utils/Mail/PinnedSmtpSocket.test.ts +409 -0
  210. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +23 -0
  211. package/Tests/Server/Utils/SSRFProtection.test.ts +4 -0
  212. package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +4 -0
  213. package/Tests/Server/Utils/SSRFProtectionPrivateNetwork.test.ts +4 -0
  214. package/Tests/Server/Utils/SSRFProtectionResolutionDetail.test.ts +314 -0
  215. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScope.test.ts +1699 -0
  216. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScopeQueriesPostgres.test.ts +508 -0
  217. package/Tests/Server/Utils/StatusPage/IncidentSubscriberAudienceCallerPermission.test.ts +246 -0
  218. package/Tests/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.test.ts +1348 -0
  219. package/Tests/Server/Utils/StatusPage/StatusPageReadAccess.test.ts +221 -0
  220. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.test.ts +132 -0
  221. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.test.ts +155 -0
  222. package/Tests/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.test.ts +708 -0
  223. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaim.test.ts +255 -0
  224. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaimPostgres.test.ts +528 -0
  225. package/Tests/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.test.ts +1238 -0
  226. package/Tests/Server/Utils/StatusPage/SubscriberNotificationFanOut.test.ts +472 -0
  227. package/Tests/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.test.ts +1168 -0
  228. package/Tests/Server/Utils/StatusPage/SubscriberNotificationRunLimit.test.ts +275 -0
  229. package/Tests/Server/Utils/StatusPage/SubscriberNotificationTiming.test.ts +131 -0
  230. package/Tests/Server/Utils/StatusPageResource.test.ts +98 -0
  231. package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigration.test.ts +386 -0
  232. package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigrationPostgres.test.ts +430 -0
  233. package/Tests/Server/Utils/Topology/TopologyActivityParityPostgres.test.ts +279 -0
  234. package/Tests/Server/Utils/Topology/TopologyConcurrencyLimiter.test.ts +534 -0
  235. package/Tests/Server/Utils/Topology/TopologyEstateGenerator.ts +624 -0
  236. package/Tests/Server/Utils/Topology/TopologyJsonResponse.test.ts +90 -0
  237. package/Tests/Server/Utils/Topology/TopologyQueries.test.ts +1634 -0
  238. package/Tests/Server/Utils/Topology/TopologyQueriesDifferentialPostgres.test.ts +683 -0
  239. package/Tests/Server/Utils/Topology/TopologyQueriesPostgres.test.ts +2624 -0
  240. package/Tests/Server/Utils/Topology/TopologyReference.ts +1018 -0
  241. package/Tests/Server/Utils/Topology/TopologyRequest.test.ts +643 -0
  242. package/Tests/Server/Utils/Topology/TopologyResponseCache.test.ts +305 -0
  243. package/Tests/Server/Utils/Topology/TopologySql.test.ts +973 -0
  244. package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +17 -0
  245. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +75 -0
  246. package/Tests/Types/CustomField/CustomFieldSavedViews.test.ts +247 -0
  247. package/Tests/Types/CustomField/CustomFieldTemplateValues.test.ts +186 -0
  248. package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +48 -0
  249. package/Tests/Types/CustomField/CustomFieldValueFormat.test.ts +215 -0
  250. package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +343 -0
  251. package/Tests/Types/CustomField/CustomFieldVariableKey.test.ts +364 -0
  252. package/Tests/Types/SafeHtml.test.ts +102 -0
  253. package/Tests/Types/StatusPage/IncidentCreatedRenotify.test.ts +419 -0
  254. package/Tests/Types/StatusPage/IncidentCreatedResend.test.ts +118 -0
  255. package/Tests/Types/StatusPage/IncidentScopeAddedPagesNotification.test.ts +558 -0
  256. package/Tests/Types/StatusPage/IncidentSubscriberAudience.test.ts +284 -0
  257. package/Tests/Types/StatusPage/StatusPageSubscriberUnsubscribe.test.ts +341 -0
  258. package/Tests/Types/StatusPage/SubscriberNotificationPreview.test.ts +196 -0
  259. package/Tests/Types/StatusPage/SubscriberNotificationResend.test.ts +201 -0
  260. package/Tests/Types/StatusPage/SubscriberNotificationTemplateCompiler.test.ts +277 -0
  261. package/Tests/Types/StatusPage/SubscriberNotificationTemplatePreview.test.ts +179 -0
  262. package/Tests/Types/StatusPage/SubscriberNotificationTemplateVariables.test.ts +324 -0
  263. package/Tests/Types/StatusPage/SubscriberUpdateNotification.test.ts +60 -13
  264. package/Tests/Types/Topology/TopologyTypeRules.test.ts +176 -0
  265. package/Tests/UI/Components/CustomFields/CustomFieldFormFields.test.ts +432 -0
  266. package/Tests/UI/Components/CustomFields/CustomFieldModelFormFields.test.tsx +685 -0
  267. package/Tests/UI/Components/CustomFields/CustomFieldsDetailOrder.test.tsx +469 -0
  268. package/Tests/UI/Components/Forms/ModelFormCustomFieldInputs.test.tsx +325 -0
  269. package/Tests/UI/Components/Forms/ModelFormDropdownOptionsRefused.test.tsx +358 -0
  270. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +22 -0
  271. package/Tests/UI/Monitor/NetworkPathView.test.tsx +289 -0
  272. package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +302 -0
  273. package/Tests/Utils/Incident/IncidentNoteTemplateVariables.test.ts +623 -0
  274. package/Tests/Utils/Markdown/MarkdownEscape.test.ts +89 -0
  275. package/Tests/Utils/ProbeNetworkFailureUtil.test.ts +992 -0
  276. package/Tests/Utils/RequestFailedDetails.test.ts +575 -0
  277. package/Types/CustomField/CustomFieldOrder.ts +55 -0
  278. package/Types/CustomField/CustomFieldSavedViews.ts +225 -0
  279. package/Types/CustomField/CustomFieldTemplateMerge.ts +71 -0
  280. package/Types/CustomField/CustomFieldType.ts +14 -0
  281. package/Types/CustomField/CustomFieldValueFormat.ts +161 -0
  282. package/Types/CustomField/CustomFieldValueValidator.ts +488 -0
  283. package/Types/CustomField/CustomFieldVariableKey.ts +211 -0
  284. package/Types/Incident/IncidentAlertLink.ts +19 -0
  285. package/Types/SafeHtml.ts +93 -0
  286. package/Types/StatusPage/IncidentCreatedRenotify.ts +224 -0
  287. package/Types/StatusPage/IncidentCreatedResend.ts +108 -0
  288. package/Types/StatusPage/IncidentScopeAddedPagesNotification.ts +343 -0
  289. package/Types/StatusPage/IncidentSubscriberAudience.ts +332 -0
  290. package/Types/StatusPage/StatusPageSubscriberUnsubscribe.ts +405 -0
  291. package/Types/StatusPage/SubscriberNotificationInterruption.ts +32 -0
  292. package/Types/StatusPage/SubscriberNotificationPreview.ts +322 -0
  293. package/Types/StatusPage/SubscriberNotificationResend.ts +132 -0
  294. package/Types/StatusPage/SubscriberNotificationTemplateCompiler.ts +180 -0
  295. package/Types/StatusPage/SubscriberNotificationTemplatePreview.ts +301 -0
  296. package/Types/StatusPage/SubscriberNotificationTemplateVariables.ts +217 -4
  297. package/Types/StatusPage/SubscriberUpdateNotification.ts +32 -11
  298. package/Types/Telemetry/InventoryLiveness.ts +26 -0
  299. package/Types/Topology/TopologyApi.ts +428 -0
  300. package/Types/Topology/TopologyTypeRules.ts +191 -0
  301. package/UI/Components/CustomFields/CustomFieldFormFields.ts +317 -0
  302. package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +395 -0
  303. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +95 -55
  304. package/UI/Components/Forms/ModelForm.tsx +70 -8
  305. package/UI/Components/Forms/Validation.ts +1 -1
  306. package/UI/Components/ModelFormModal/ModelFormModal.tsx +2 -4
  307. package/UI/Components/ModelTable/AnalyticsModelTable.tsx +2 -6
  308. package/UI/Components/ModelTable/BaseModelTable.tsx +9 -8
  309. package/UI/Components/ModelTable/CustomFieldColumns.tsx +14 -1
  310. package/UI/Components/ModelTable/ModelTable.tsx +6 -7
  311. package/UI/Utils/SensitiveUrlToken.ts +10 -5
  312. package/Utils/API.ts +247 -2
  313. package/Utils/Incident/IncidentFromAlerts.ts +78 -0
  314. package/Utils/Incident/IncidentNoteTemplateVariables.ts +304 -0
  315. package/Utils/Markdown/MarkdownEscape.ts +65 -0
  316. package/Utils/ProbeNetworkFailureUtil.ts +673 -0
  317. package/build/dist/Models/DatabaseModels/Incident.js +267 -1
  318. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  319. package/build/dist/Models/DatabaseModels/IncidentCustomField.js +207 -1
  320. package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
  321. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +31 -0
  322. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  323. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +124 -1
  324. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  325. package/build/dist/Models/DatabaseModels/InventoryItem.js +20 -11
  326. package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
  327. package/build/dist/Models/DatabaseModels/StatusPage.js +56 -0
  328. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  329. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +131 -0
  330. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
  331. package/build/dist/Server/API/IncidentAPI.js +100 -1
  332. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  333. package/build/dist/Server/API/StatusPageAPI.js +417 -176
  334. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  335. package/build/dist/Server/API/StatusPageSubscriberAPI.js +52 -13
  336. package/build/dist/Server/API/StatusPageSubscriberAPI.js.map +1 -1
  337. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js +165 -0
  338. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js.map +1 -0
  339. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js +41 -0
  340. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js.map +1 -0
  341. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js +57 -0
  342. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js.map +1 -0
  343. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js +23 -0
  344. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js.map +1 -0
  345. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js +40 -0
  346. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js.map +1 -0
  347. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js +26 -0
  348. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js.map +1 -0
  349. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js +89 -0
  350. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js.map +1 -0
  351. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js +29 -0
  352. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js.map +1 -0
  353. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +16 -0
  354. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  355. package/build/dist/Server/Infrastructure/Semaphore.js +3 -0
  356. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  357. package/build/dist/Server/Middleware/IdentityRateLimit.js +89 -3
  358. package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
  359. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js +149 -0
  360. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js.map +1 -0
  361. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js +190 -0
  362. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js.map +1 -0
  363. package/build/dist/Server/Services/AlertService.js +21 -33
  364. package/build/dist/Server/Services/AlertService.js.map +1 -1
  365. package/build/dist/Server/Services/DatabaseService.js +56 -7
  366. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  367. package/build/dist/Server/Services/IncidentAlertService.js +457 -3
  368. package/build/dist/Server/Services/IncidentAlertService.js.map +1 -1
  369. package/build/dist/Server/Services/IncidentCustomFieldService.js +204 -8
  370. package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
  371. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +24 -0
  372. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  373. package/build/dist/Server/Services/IncidentEpisodeService.js +14 -0
  374. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  375. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +26 -0
  376. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  377. package/build/dist/Server/Services/IncidentPublicNoteService.js +26 -1
  378. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  379. package/build/dist/Server/Services/IncidentService.js +1148 -42
  380. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  381. package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
  382. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  383. package/build/dist/Server/Services/IncidentTemplateService.js +83 -0
  384. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  385. package/build/dist/Server/Services/InventoryItemService.js +31 -0
  386. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  387. package/build/dist/Server/Services/IoTDeviceCredentialService.js +12 -5
  388. package/build/dist/Server/Services/IoTDeviceCredentialService.js.map +1 -1
  389. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +26 -1
  390. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  391. package/build/dist/Server/Services/ScheduledMaintenanceService.js +84 -57
  392. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  393. package/build/dist/Server/Services/SmsService.js +2 -22
  394. package/build/dist/Server/Services/SmsService.js.map +1 -1
  395. package/build/dist/Server/Services/StatusPageService.js +47 -9
  396. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  397. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +237 -32
  398. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  399. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js +109 -0
  400. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js.map +1 -1
  401. package/build/dist/Server/Services/StatusPageSubscriberService.js +925 -99
  402. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  403. package/build/dist/Server/Services/UserNotificationRuleService.js +55 -51
  404. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  405. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +42 -25
  406. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
  407. package/build/dist/Server/Types/Markdown.js +110 -6
  408. package/build/dist/Server/Types/Markdown.js.map +1 -1
  409. package/build/dist/Server/Types/Workflow/Components/Email.js +55 -6
  410. package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
  411. package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js +405 -0
  412. package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js.map +1 -0
  413. package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js +112 -0
  414. package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js.map +1 -0
  415. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js +211 -0
  416. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js.map +1 -0
  417. package/build/dist/Server/Utils/DataSource/EgressGuard.js +41 -12
  418. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
  419. package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js +81 -0
  420. package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js.map +1 -0
  421. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js +101 -0
  422. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js.map +1 -0
  423. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
  424. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  425. package/build/dist/Server/Utils/Response.js +30 -0
  426. package/build/dist/Server/Utils/Response.js.map +1 -1
  427. package/build/dist/Server/Utils/SSRFProtection.js +48 -6
  428. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  429. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js +631 -0
  430. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js.map +1 -0
  431. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js +403 -0
  432. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js.map +1 -0
  433. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js +451 -0
  434. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js.map +1 -0
  435. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js +13 -0
  436. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js.map +1 -0
  437. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js +91 -0
  438. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js.map +1 -0
  439. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js +101 -0
  440. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js.map +1 -0
  441. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js +47 -0
  442. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js.map +1 -0
  443. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js +273 -0
  444. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js.map +1 -0
  445. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js +73 -0
  446. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js.map +1 -0
  447. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js +722 -0
  448. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js.map +1 -0
  449. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js +102 -0
  450. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js.map +1 -0
  451. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js +527 -0
  452. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js.map +1 -0
  453. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js +278 -0
  454. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js.map +1 -0
  455. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js +137 -0
  456. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js.map +1 -0
  457. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js +103 -0
  458. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js.map +1 -0
  459. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js +114 -0
  460. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js.map +1 -0
  461. package/build/dist/Server/Utils/StatusPageResource.js +39 -7
  462. package/build/dist/Server/Utils/StatusPageResource.js.map +1 -1
  463. package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js +178 -0
  464. package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js.map +1 -0
  465. package/build/dist/Server/Utils/Topology/TopologyQueries.js +884 -0
  466. package/build/dist/Server/Utils/Topology/TopologyQueries.js.map +1 -0
  467. package/build/dist/Server/Utils/Topology/TopologyRequest.js +275 -0
  468. package/build/dist/Server/Utils/Topology/TopologyRequest.js.map +1 -0
  469. package/build/dist/Server/Utils/Topology/TopologyResponseCache.js +146 -0
  470. package/build/dist/Server/Utils/Topology/TopologyResponseCache.js.map +1 -0
  471. package/build/dist/Server/Utils/Topology/TopologySql.js +688 -0
  472. package/build/dist/Server/Utils/Topology/TopologySql.js.map +1 -0
  473. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  474. package/build/dist/Server/Utils/VM/VMRunner.js +1 -0
  475. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  476. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +3 -0
  477. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -1
  478. package/build/dist/Types/CustomField/CustomFieldOrder.js +36 -0
  479. package/build/dist/Types/CustomField/CustomFieldOrder.js.map +1 -0
  480. package/build/dist/Types/CustomField/CustomFieldSavedViews.js +135 -0
  481. package/build/dist/Types/CustomField/CustomFieldSavedViews.js.map +1 -0
  482. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js +27 -0
  483. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js.map +1 -0
  484. package/build/dist/Types/CustomField/CustomFieldType.js +12 -0
  485. package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
  486. package/build/dist/Types/CustomField/CustomFieldValueFormat.js +126 -0
  487. package/build/dist/Types/CustomField/CustomFieldValueFormat.js.map +1 -0
  488. package/build/dist/Types/CustomField/CustomFieldValueValidator.js +280 -0
  489. package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -0
  490. package/build/dist/Types/CustomField/CustomFieldVariableKey.js +150 -0
  491. package/build/dist/Types/CustomField/CustomFieldVariableKey.js.map +1 -0
  492. package/build/dist/Types/Incident/IncidentAlertLink.js +17 -0
  493. package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -1
  494. package/build/dist/Types/SafeHtml.js +84 -0
  495. package/build/dist/Types/SafeHtml.js.map +1 -0
  496. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js +117 -0
  497. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js.map +1 -0
  498. package/build/dist/Types/StatusPage/IncidentCreatedResend.js +86 -0
  499. package/build/dist/Types/StatusPage/IncidentCreatedResend.js.map +1 -0
  500. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js +246 -0
  501. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js.map +1 -0
  502. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js +183 -0
  503. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js.map +1 -0
  504. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js +277 -0
  505. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js.map +1 -0
  506. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js +27 -0
  507. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js.map +1 -0
  508. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js +190 -0
  509. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js.map +1 -0
  510. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js +102 -0
  511. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js.map +1 -0
  512. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js +97 -0
  513. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js.map +1 -0
  514. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js +175 -0
  515. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js.map +1 -0
  516. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js +144 -4
  517. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js.map +1 -1
  518. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js +29 -9
  519. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js.map +1 -1
  520. package/build/dist/Types/Telemetry/InventoryLiveness.js +23 -0
  521. package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -1
  522. package/build/dist/Types/Topology/TopologyApi.js +55 -0
  523. package/build/dist/Types/Topology/TopologyApi.js.map +1 -0
  524. package/build/dist/Types/Topology/TopologyTypeRules.js +165 -0
  525. package/build/dist/Types/Topology/TopologyTypeRules.js.map +1 -0
  526. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js +169 -0
  527. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js.map +1 -0
  528. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +212 -0
  529. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -0
  530. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +64 -49
  531. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  532. package/build/dist/UI/Components/Forms/ModelForm.js +39 -5
  533. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  534. package/build/dist/UI/Components/Forms/Validation.js +1 -1
  535. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  536. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  537. package/build/dist/UI/Components/ModelTable/AnalyticsModelTable.js.map +1 -1
  538. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +2 -2
  539. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  540. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +13 -1
  541. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  542. package/build/dist/UI/Components/ModelTable/ModelTable.js +1 -1
  543. package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
  544. package/build/dist/UI/Utils/SensitiveUrlToken.js +10 -5
  545. package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -1
  546. package/build/dist/Utils/API.js +177 -1
  547. package/build/dist/Utils/API.js.map +1 -1
  548. package/build/dist/Utils/Incident/IncidentFromAlerts.js +38 -0
  549. package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -1
  550. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js +155 -0
  551. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js.map +1 -0
  552. package/build/dist/Utils/Markdown/MarkdownEscape.js +48 -0
  553. package/build/dist/Utils/Markdown/MarkdownEscape.js.map +1 -1
  554. package/build/dist/Utils/ProbeNetworkFailureUtil.js +460 -0
  555. package/build/dist/Utils/ProbeNetworkFailureUtil.js.map +1 -0
  556. package/package.json +1 -1
@@ -0,0 +1,1350 @@
1
+ import { PostgresStatementTimeoutMs } from "../../EnvironmentConfig";
2
+ import InventoryItemService from "../../Services/InventoryItemService";
3
+ import CaptureSpan from "../Telemetry/CaptureSpan";
4
+ import { JSONObject } from "../../../Types/JSON";
5
+ import ObjectID from "../../../Types/ObjectID";
6
+ import EntityType from "../../../Types/Telemetry/EntityType";
7
+ import {
8
+ TOPOLOGY_API_FORMAT_VERSION,
9
+ TOPOLOGY_CONNECTION_SECTIONS,
10
+ TopologyApiLimits,
11
+ TopologyCollectionCursorJSON,
12
+ TopologyCollectionResponseJSON,
13
+ TopologyCollectionSearchResponseJSON,
14
+ TopologyConnectionRowJSON,
15
+ TopologyConnectionSection,
16
+ TopologyConnectionSectionJSON,
17
+ TopologyDependencyJSON,
18
+ TopologyEntityAllTimeConnectionsResponseJSON,
19
+ TopologyEntityAllTimeResponseJSON,
20
+ TopologyEntityConnectionsResponseJSON,
21
+ TopologyEntityDetailJSON,
22
+ TopologyEntityJSON,
23
+ TopologyEntityResponseJSON,
24
+ TopologyInfrastructureDependencyJSON,
25
+ TopologyInfrastructureNodeJSON,
26
+ TopologyInfrastructureResponseJSON,
27
+ TopologyInfrastructureServiceJSON,
28
+ TopologyResponseEnvelopeJSON,
29
+ TopologyRunsOnCountJSON,
30
+ TopologyServiceMapEntityJSON,
31
+ TopologyServiceMapResponseJSON,
32
+ } from "../../../Types/Topology/TopologyApi";
33
+ import {
34
+ isFlatInfrastructureType,
35
+ isTopologyInfrastructureType,
36
+ } from "../../../Types/Topology/TopologyTypeRules";
37
+ import {
38
+ TopologyCollectionRequest,
39
+ TopologyCollectionSearchRequest,
40
+ TopologyConnectionsPage,
41
+ TopologyEntityAllTimeConnectionsRequest,
42
+ TopologyEntityAllTimeRequest,
43
+ TopologyEntityConnectionsRequest,
44
+ TopologyEntityRequest,
45
+ TopologyEntityTarget,
46
+ TopologyRangeRequest,
47
+ rowsForSection,
48
+ } from "./TopologyRequest";
49
+ import {
50
+ TopologyEntityScope,
51
+ TopologyEntitySectionWindow,
52
+ TopologySqlStatement,
53
+ TopologyWinnerScope,
54
+ collectionPageStatement,
55
+ collectionSearchStatement,
56
+ entitySectionsStatement,
57
+ entityStatement,
58
+ infrastructureNodesStatement,
59
+ infrastructureTypeCountsStatement,
60
+ itemsByKeysStatement,
61
+ placementsStatement,
62
+ duplicateKeysStatement,
63
+ projectTypesStatement,
64
+ serviceMapDependenciesStatement,
65
+ serviceMapEntitiesStatement,
66
+ servicesStatement,
67
+ } from "./TopologySql";
68
+ import { EntityManager } from "typeorm";
69
+
70
+ /*
71
+ * The Topology API's reads: Postgres reduces the inventory graph and each
72
+ * view receives only the rows it draws, computed over the whole inventory.
73
+ * The SQL lives in TopologySql; this module runs it and shapes the contract
74
+ * JSON (Common/Types/Topology/TopologyApi.ts).
75
+ *
76
+ * Every request reads one consistent snapshot on one connection: a READ ONLY
77
+ * REPEATABLE READ transaction whose statements run one after another, so the
78
+ * service list, the relationships and the counts a response is built from can
79
+ * never disagree with each other. Parallel query is switched off for the
80
+ * transaction: the docker-compose Postgres has a 64 MB /dev/shm, and parallel
81
+ * hash joins over a large project fail there under concurrent load ("could not
82
+ * resize shared memory segment"); the heaviest statement here is a single pass
83
+ * over one project and does not need the workers. JIT is off too: it spends
84
+ * 100-200 ms compiling each large statement and wins nothing back on reads of
85
+ * this size.
86
+ *
87
+ * The statement timeout is set on the transaction as well, not left to the
88
+ * connection's startup parameter: PgBouncer drops that parameter, and behind
89
+ * it nothing but node-postgres's client-side query_timeout would remain — which
90
+ * abandons a query without cancelling it, leaving one of the heaviest reads in
91
+ * the app running on the server (holding its snapshot) after the user was
92
+ * already told it failed. The configured value stays below that client
93
+ * timeout, so Postgres cancels first.
94
+ */
95
+
96
+ /*
97
+ * `SET LOCAL statement_timeout = <ms>` for the configured statement timeout,
98
+ * or null when none is configured (0, or not a number): then the connection's
99
+ * own setting applies, as for every other query.
100
+ */
101
+ export function statementTimeoutSql(timeoutMs: number): string | null {
102
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 1) {
103
+ return null;
104
+ }
105
+ return `SET LOCAL statement_timeout = ${Math.floor(timeoutMs)}`;
106
+ }
107
+
108
+ export interface TopologyStatementRunner {
109
+ query(statement: TopologySqlStatement): Promise<Array<JSONObject>>;
110
+ }
111
+
112
+ export interface TopologyProjectScope {
113
+ /* Always the tenant the caller was authorized for, never a body value. */
114
+ projectId: ObjectID;
115
+ }
116
+
117
+ // ---------------------------------------------------------------- decoding
118
+
119
+ function readString(row: JSONObject, key: string): string {
120
+ const value: unknown = row[key];
121
+ return typeof value === "string" ? value : String(value ?? "");
122
+ }
123
+
124
+ function readNullableString(row: JSONObject, key: string): string | null {
125
+ const value: unknown = row[key];
126
+ if (value === null || value === undefined) {
127
+ return null;
128
+ }
129
+ return typeof value === "string" ? value : String(value);
130
+ }
131
+
132
+ function readNullableNumber(row: JSONObject, key: string): number | null {
133
+ const value: unknown = row[key];
134
+ if (value === null || value === undefined || value === "") {
135
+ return null;
136
+ }
137
+ const parsed: number = typeof value === "number" ? value : Number(value);
138
+ return Number.isFinite(parsed) ? parsed : null;
139
+ }
140
+
141
+ function readNumber(row: JSONObject, key: string): number {
142
+ return readNullableNumber(row, key) ?? 0;
143
+ }
144
+
145
+ function readBoolean(row: JSONObject, key: string): boolean {
146
+ const value: unknown = row[key];
147
+ return value === true || value === "t" || value === "true";
148
+ }
149
+
150
+ function readJsonObject(row: JSONObject, key: string): JSONObject | null {
151
+ const value: unknown = row[key];
152
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
153
+ return null;
154
+ }
155
+ return value as JSONObject;
156
+ }
157
+
158
+ /* A bag of string attributes, or undefined when it holds none. */
159
+ function readStringBag(
160
+ row: JSONObject,
161
+ key: string,
162
+ ): Record<string, string> | undefined {
163
+ const bag: JSONObject | null = readJsonObject(row, key);
164
+ if (!bag) {
165
+ return undefined;
166
+ }
167
+ const strings: Record<string, string> = {};
168
+ let count: number = 0;
169
+ for (const attribute of Object.keys(bag).sort()) {
170
+ const value: unknown = bag[attribute];
171
+ if (typeof value === "string") {
172
+ strings[attribute] = value;
173
+ count++;
174
+ }
175
+ }
176
+ return count > 0 ? strings : undefined;
177
+ }
178
+
179
+ function toLeanEntity(row: JSONObject): TopologyEntityJSON {
180
+ return {
181
+ key: readString(row, "key"),
182
+ type: readString(row, "type"),
183
+ name: readNullableString(row, "name"),
184
+ /* The column is NOT NULL; "" reads as "no source" on both sides. */
185
+ source: readNullableString(row, "source") ?? "",
186
+ lastSeenAt: readNullableNumber(row, "lastSeenAt"),
187
+ };
188
+ }
189
+
190
+ function toServiceMapEntity(row: JSONObject): TopologyServiceMapEntityJSON {
191
+ const entity: TopologyServiceMapEntityJSON = toLeanEntity(row);
192
+ const descriptive: Record<string, string> | undefined = readStringBag(
193
+ row,
194
+ "descriptiveAttributes",
195
+ );
196
+ const identifying: Record<string, string> | undefined = readStringBag(
197
+ row,
198
+ "identifyingAttributes",
199
+ );
200
+ if (descriptive) {
201
+ entity.descriptiveAttributes = descriptive;
202
+ }
203
+ if (identifying) {
204
+ entity.identifyingAttributes = identifying;
205
+ }
206
+ return entity;
207
+ }
208
+
209
+ function toConnectionRow(value: unknown): TopologyConnectionRowJSON {
210
+ const row: JSONObject = (value || {}) as JSONObject;
211
+ const connection: TopologyConnectionRowJSON = {
212
+ relationshipType: readString(row, "relationshipType"),
213
+ direction: row["direction"] === "in" ? "in" : "out",
214
+ otherKey: readString(row, "otherKey"),
215
+ otherKnown: readBoolean(row, "otherKnown"),
216
+ otherName: readNullableString(row, "otherName"),
217
+ otherType: readNullableString(row, "otherType"),
218
+ callCount: readNullableNumber(row, "callCount"),
219
+ errorCount: readNullableNumber(row, "errorCount"),
220
+ avgDurationMs: readNullableNumber(row, "avgDurationMs"),
221
+ lastSeenAt: readNullableNumber(row, "lastSeenAt"),
222
+ };
223
+ // Only the all-time statement selects it; the drawer's rows stay as they were.
224
+ if ("otherId" in row) {
225
+ connection.otherId = readNullableString(row, "otherId");
226
+ }
227
+ return connection;
228
+ }
229
+
230
+ function compareCodeUnits(left: string, right: string): number {
231
+ if (left < right) {
232
+ return -1;
233
+ }
234
+ return left > right ? 1 : 0;
235
+ }
236
+
237
+ function emptySections(): Record<
238
+ TopologyConnectionSection,
239
+ TopologyConnectionSectionJSON
240
+ > {
241
+ return {
242
+ calls: { total: 0, unknownTotal: 0, rows: [], nextOffset: null },
243
+ calledBy: { total: 0, unknownTotal: 0, rows: [], nextOffset: null },
244
+ runsOn: { total: 0, unknownTotal: 0, rows: [], nextOffset: null },
245
+ related: { total: 0, unknownTotal: 0, rows: [], nextOffset: null },
246
+ };
247
+ }
248
+
249
+ function isSection(value: unknown): value is TopologyConnectionSection {
250
+ return TOPOLOGY_CONNECTION_SECTIONS.some(
251
+ (section: TopologyConnectionSection): boolean => {
252
+ return section === value;
253
+ },
254
+ );
255
+ }
256
+
257
+ interface DecodedSections {
258
+ sections: Record<TopologyConnectionSection, TopologyConnectionSectionJSON>;
259
+ isScanLimited: boolean;
260
+ }
261
+
262
+ /*
263
+ * Decodes entitySectionsStatement's single row. `offset` is where the rows of
264
+ * each section start (0 for the drawer's first view).
265
+ */
266
+ function decodeSections(
267
+ rows: Array<JSONObject>,
268
+ offset: number,
269
+ ): DecodedSections {
270
+ const sections: Record<
271
+ TopologyConnectionSection,
272
+ TopologyConnectionSectionJSON
273
+ > = emptySections();
274
+ const row: JSONObject = rows[0] || {};
275
+
276
+ const totals: unknown = row["sections"];
277
+ for (const total of Array.isArray(totals) ? totals : []) {
278
+ const entry: JSONObject = (total || {}) as JSONObject;
279
+ const section: unknown = entry["section"];
280
+ if (isSection(section)) {
281
+ sections[section].total = readNumber(entry, "total");
282
+ sections[section].unknownTotal = readNumber(entry, "unknownTotal");
283
+ }
284
+ }
285
+
286
+ const connectionRows: unknown = row["rows"];
287
+ for (const connection of Array.isArray(connectionRows)
288
+ ? connectionRows
289
+ : []) {
290
+ const section: unknown = ((connection || {}) as JSONObject)["section"];
291
+ if (isSection(section)) {
292
+ sections[section].rows.push(toConnectionRow(connection));
293
+ }
294
+ }
295
+
296
+ for (const section of TOPOLOGY_CONNECTION_SECTIONS) {
297
+ const reached: number = offset + sections[section].rows.length;
298
+ sections[section].nextOffset =
299
+ sections[section].rows.length > 0 && reached < sections[section].total
300
+ ? reached
301
+ : null;
302
+ }
303
+
304
+ return {
305
+ sections,
306
+ isScanLimited:
307
+ readNumber(row, "scanned") > TopologyApiLimits.EntityConnectionScanLimit,
308
+ };
309
+ }
310
+
311
+ // ------------------------------------------------------------------ queries
312
+
313
+ export default class TopologyQueries {
314
+ /*
315
+ * Runs `work` on one pooled connection inside a READ ONLY REPEATABLE READ
316
+ * transaction with a server-side statement timeout. TypeORM commits on
317
+ * success, rolls back on failure and always releases the connection.
318
+ */
319
+ public static async inReadOnlySnapshot<T>(
320
+ work: (runner: TopologyStatementRunner) => Promise<T>,
321
+ ): Promise<T> {
322
+ const manager: EntityManager = InventoryItemService.getRepository().manager;
323
+ return await manager.transaction(
324
+ "REPEATABLE READ",
325
+ async (transaction: EntityManager): Promise<T> => {
326
+ await transaction.query("SET TRANSACTION READ ONLY");
327
+ const statementTimeout: string | null = statementTimeoutSql(
328
+ PostgresStatementTimeoutMs,
329
+ );
330
+ if (statementTimeout) {
331
+ await transaction.query(statementTimeout);
332
+ }
333
+ await transaction.query(
334
+ "SET LOCAL max_parallel_workers_per_gather = 0",
335
+ );
336
+ await transaction.query("SET LOCAL jit = off");
337
+ return await work({
338
+ query: async (
339
+ statement: TopologySqlStatement,
340
+ ): Promise<Array<JSONObject>> => {
341
+ return (await transaction.query(
342
+ statement.sql,
343
+ statement.params,
344
+ )) as Array<JSONObject>;
345
+ },
346
+ });
347
+ },
348
+ );
349
+ }
350
+
351
+ public static envelope(rangeStart: Date): TopologyResponseEnvelopeJSON {
352
+ return {
353
+ formatVersion: TOPOLOGY_API_FORMAT_VERSION,
354
+ rangeStart: rangeStart.toISOString(),
355
+ generatedAt: new Date().toISOString(),
356
+ };
357
+ }
358
+
359
+ /* The all-time responses have no range start to echo. */
360
+ public static allTimeEnvelope(): Omit<
361
+ TopologyResponseEnvelopeJSON,
362
+ "rangeStart"
363
+ > {
364
+ return {
365
+ formatVersion: TOPOLOGY_API_FORMAT_VERSION,
366
+ generatedAt: new Date().toISOString(),
367
+ };
368
+ }
369
+
370
+ /* The project's distinct entity types (see projectTypesStatement). */
371
+ public static async readProjectTypes(
372
+ runner: TopologyStatementRunner,
373
+ projectId: string,
374
+ ): Promise<Array<string>> {
375
+ const rows: Array<JSONObject> = await runner.query(
376
+ projectTypesStatement({ projectId }),
377
+ );
378
+ return rows
379
+ .map((row: JSONObject): string => {
380
+ return readString(row, "entityType");
381
+ })
382
+ .filter((type: string): boolean => {
383
+ return type.length > 0;
384
+ });
385
+ }
386
+
387
+ /*
388
+ * The project's types plus the keys more than one live row carries, read
389
+ * once per request so the statements that list items apply the winner rule
390
+ * only where it can matter (see winnerSql).
391
+ */
392
+ public static async readWinnerScope(
393
+ runner: TopologyStatementRunner,
394
+ projectId: string,
395
+ projectTypes: Array<string>,
396
+ ): Promise<TopologyWinnerScope> {
397
+ const rows: Array<JSONObject> = await runner.query(
398
+ duplicateKeysStatement({ projectId }),
399
+ );
400
+ return {
401
+ projectTypes,
402
+ duplicateKeys: rows
403
+ .map((row: JSONObject): string => {
404
+ return readString(row, "key");
405
+ })
406
+ .sort(compareCodeUnits),
407
+ };
408
+ }
409
+
410
+ /*
411
+ * Rows up to `cap`, or — when there are more — the first `cap` and the
412
+ * exact total from `count`. The row statement asks for cap + 1 so the
413
+ * common case (under the cap) costs no count at all.
414
+ */
415
+ private static async readCapped(
416
+ runner: TopologyStatementRunner,
417
+ rows: (limit: number) => TopologySqlStatement,
418
+ count: () => TopologySqlStatement,
419
+ cap: number,
420
+ ): Promise<{ rows: Array<JSONObject>; total: number; count: JSONObject }> {
421
+ const found: Array<JSONObject> = await runner.query(rows(cap + 1));
422
+ if (found.length <= cap) {
423
+ return { rows: found, total: found.length, count: {} };
424
+ }
425
+ const counted: Array<JSONObject> = await runner.query(count());
426
+ const countRow: JSONObject = counted[0] || {};
427
+ return {
428
+ rows: found.slice(0, cap),
429
+ total: Math.max(readNumber(countRow, "total"), cap),
430
+ count: countRow,
431
+ };
432
+ }
433
+
434
+ /*
435
+ * Per (service, target type): the distinct resources each service runs on
436
+ * (in-range runs-on / hosted-on onto live items of any type), and how many
437
+ * of those reported in range. Placements and targets are read separately so
438
+ * the target lookup is by an array of keys the planner can size.
439
+ */
440
+ private static async readRunsOnCounts(
441
+ runner: TopologyStatementRunner,
442
+ data: {
443
+ projectId: string;
444
+ rangeStart: string;
445
+ winner: TopologyWinnerScope;
446
+ serviceKeys: Array<string>;
447
+ },
448
+ ): Promise<Array<TopologyRunsOnCountJSON>> {
449
+ const placementRows: Array<JSONObject> = await runner.query(
450
+ placementsStatement({
451
+ projectId: data.projectId,
452
+ rangeStart: data.rangeStart,
453
+ serviceKeys: data.serviceKeys,
454
+ }),
455
+ );
456
+ if (placementRows.length === 0) {
457
+ return [];
458
+ }
459
+
460
+ const targetKeys: Array<string> = Array.from(
461
+ new Set<string>(
462
+ placementRows.map((row: JSONObject): string => {
463
+ return readString(row, "target");
464
+ }),
465
+ ),
466
+ ).sort(compareCodeUnits);
467
+ const targetRows: Array<JSONObject> = await runner.query(
468
+ itemsByKeysStatement({
469
+ projectId: data.projectId,
470
+ rangeStart: data.rangeStart,
471
+ winner: data.winner,
472
+ keys: targetKeys,
473
+ }),
474
+ );
475
+ const targetByKey: Map<string, { type: string; active: boolean }> = new Map<
476
+ string,
477
+ { type: string; active: boolean }
478
+ >();
479
+ for (const row of targetRows) {
480
+ targetByKey.set(readString(row, "key"), {
481
+ type: readString(row, "type"),
482
+ active: readBoolean(row, "active"),
483
+ });
484
+ }
485
+
486
+ const counts: Map<string, TopologyRunsOnCountJSON> = new Map<
487
+ string,
488
+ TopologyRunsOnCountJSON
489
+ >();
490
+ for (const row of placementRows) {
491
+ const service: string = readString(row, "service");
492
+ const target: { type: string; active: boolean } | undefined =
493
+ targetByKey.get(readString(row, "target"));
494
+ if (!target) {
495
+ continue;
496
+ }
497
+ const countKey: string = `${service}\u0000${target.type}`;
498
+ let count: TopologyRunsOnCountJSON | undefined = counts.get(countKey);
499
+ if (!count) {
500
+ count = { service, type: target.type, active: 0, total: 0 };
501
+ counts.set(countKey, count);
502
+ }
503
+ /* Placements are distinct (service, target) pairs: one per target. */
504
+ count.total++;
505
+ if (target.active) {
506
+ count.active++;
507
+ }
508
+ }
509
+
510
+ return Array.from(counts.values()).sort(
511
+ (
512
+ left: TopologyRunsOnCountJSON,
513
+ right: TopologyRunsOnCountJSON,
514
+ ): number => {
515
+ return (
516
+ compareCodeUnits(left.service, right.service) ||
517
+ compareCodeUnits(left.type, right.type)
518
+ );
519
+ },
520
+ );
521
+ }
522
+
523
+ /*
524
+ * Service Map: every live service, everything services call, the in-range
525
+ * calls between them, and per (service, target type) what the services run
526
+ * on. No pods or other infrastructure rows.
527
+ */
528
+ @CaptureSpan()
529
+ public static async getServiceMap(
530
+ request: TopologyRangeRequest & TopologyProjectScope,
531
+ ): Promise<TopologyServiceMapResponseJSON> {
532
+ const projectId: string = request.projectId.toString();
533
+ const rangeStart: string = request.rangeStart.toISOString();
534
+ const entityCap: number = TopologyApiLimits.MaxServiceMapEntities;
535
+ const dependencyCap: number = TopologyApiLimits.MaxServiceMapDependencies;
536
+
537
+ return await TopologyQueries.inReadOnlySnapshot(
538
+ async (
539
+ runner: TopologyStatementRunner,
540
+ ): Promise<TopologyServiceMapResponseJSON> => {
541
+ const response: TopologyServiceMapResponseJSON = {
542
+ ...TopologyQueries.envelope(request.rangeStart),
543
+ entities: [],
544
+ dependencies: [],
545
+ runsOn: [],
546
+ entityTruncation: null,
547
+ dependencyTruncation: null,
548
+ };
549
+
550
+ const projectTypes: Array<string> =
551
+ await TopologyQueries.readProjectTypes(runner, projectId);
552
+ if (!projectTypes.includes(EntityType.Service)) {
553
+ return response;
554
+ }
555
+ const winner: TopologyWinnerScope =
556
+ await TopologyQueries.readWinnerScope(
557
+ runner,
558
+ projectId,
559
+ projectTypes,
560
+ );
561
+
562
+ const services: {
563
+ rows: Array<JSONObject>;
564
+ total: number;
565
+ } = await TopologyQueries.readCapped(
566
+ runner,
567
+ (limit: number): TopologySqlStatement => {
568
+ return serviceMapEntitiesStatement({
569
+ projectId,
570
+ winner,
571
+ mode: "rows",
572
+ limit,
573
+ });
574
+ },
575
+ (): TopologySqlStatement => {
576
+ return serviceMapEntitiesStatement({
577
+ projectId,
578
+ winner,
579
+ mode: "count",
580
+ limit: 0,
581
+ });
582
+ },
583
+ entityCap,
584
+ );
585
+ const serviceKeys: Array<string> = services.rows.map(
586
+ (row: JSONObject): string => {
587
+ return readString(row, "key");
588
+ },
589
+ );
590
+ if (serviceKeys.length === 0) {
591
+ return response;
592
+ }
593
+
594
+ const dependencies: {
595
+ rows: Array<JSONObject>;
596
+ total: number;
597
+ } = await TopologyQueries.readCapped(
598
+ runner,
599
+ (limit: number): TopologySqlStatement => {
600
+ return serviceMapDependenciesStatement({
601
+ projectId,
602
+ rangeStart,
603
+ serviceKeys,
604
+ mode: "rows",
605
+ limit,
606
+ });
607
+ },
608
+ (): TopologySqlStatement => {
609
+ return serviceMapDependenciesStatement({
610
+ projectId,
611
+ rangeStart,
612
+ serviceKeys,
613
+ mode: "count",
614
+ limit: 0,
615
+ });
616
+ },
617
+ dependencyCap,
618
+ );
619
+ response.dependencies = dependencies.rows.map(
620
+ (row: JSONObject): TopologyDependencyJSON => {
621
+ return {
622
+ from: readString(row, "from"),
623
+ to: readString(row, "to"),
624
+ callCount: readNullableNumber(row, "callCount"),
625
+ errorCount: readNullableNumber(row, "errorCount"),
626
+ avgDurationMs: readNullableNumber(row, "avgDurationMs"),
627
+ };
628
+ },
629
+ );
630
+ if (dependencies.total > dependencies.rows.length) {
631
+ response.dependencyTruncation = {
632
+ shown: dependencies.rows.length,
633
+ total: dependencies.total,
634
+ };
635
+ }
636
+
637
+ /*
638
+ * Callees: the non-service items the shipped calls point at (a callee
639
+ * that is a service is already in the list above).
640
+ */
641
+ const serviceKeySet: Set<string> = new Set<string>(serviceKeys);
642
+ const calleeKeys: Array<string> = Array.from(
643
+ new Set<string>(
644
+ response.dependencies.map(
645
+ (dependency: TopologyDependencyJSON): string => {
646
+ return dependency.to;
647
+ },
648
+ ),
649
+ ),
650
+ )
651
+ .filter((key: string): boolean => {
652
+ return !serviceKeySet.has(key);
653
+ })
654
+ .sort(compareCodeUnits);
655
+
656
+ let callees: { rows: Array<JSONObject>; total: number } = {
657
+ rows: [],
658
+ total: 0,
659
+ };
660
+ if (calleeKeys.length > 0) {
661
+ callees = await TopologyQueries.readCapped(
662
+ runner,
663
+ (limit: number): TopologySqlStatement => {
664
+ return serviceMapEntitiesStatement({
665
+ projectId,
666
+ winner,
667
+ keys: calleeKeys,
668
+ mode: "rows",
669
+ limit,
670
+ });
671
+ },
672
+ (): TopologySqlStatement => {
673
+ return serviceMapEntitiesStatement({
674
+ projectId,
675
+ winner,
676
+ keys: calleeKeys,
677
+ mode: "count",
678
+ limit: 0,
679
+ });
680
+ },
681
+ entityCap - services.rows.length,
682
+ );
683
+ }
684
+
685
+ response.entities = [...services.rows, ...callees.rows].map(
686
+ toServiceMapEntity,
687
+ );
688
+ const entityTotal: number = services.total + callees.total;
689
+ if (entityTotal > response.entities.length) {
690
+ response.entityTruncation = {
691
+ shown: response.entities.length,
692
+ total: entityTotal,
693
+ };
694
+ }
695
+
696
+ response.runsOn = await TopologyQueries.readRunsOnCounts(runner, {
697
+ projectId,
698
+ rangeStart,
699
+ winner,
700
+ serviceKeys,
701
+ });
702
+
703
+ return response;
704
+ },
705
+ );
706
+ }
707
+
708
+ /*
709
+ * Infrastructure's traffic: the in-range calls between services that both
710
+ * run on a shipped node, so the map can draw them between the resources
711
+ * those services run on. A service placed nowhere could never be drawn, so
712
+ * calls to or from one are not read at all. Capped like the Service Map's
713
+ * dependencies (the first rows in its order, with the exact total), then
714
+ * ordered by (from, to) — each pair is at most one row (the unique index).
715
+ */
716
+ private static async readInfrastructureDependencies(
717
+ runner: TopologyStatementRunner,
718
+ data: {
719
+ projectId: string;
720
+ rangeStart: string;
721
+ response: TopologyInfrastructureResponseJSON;
722
+ serviceIndexByKey: Map<string, number>;
723
+ },
724
+ ): Promise<void> {
725
+ const placedServiceIndexes: Array<number> = Array.from(
726
+ new Set<number>(
727
+ data.response.placements.map((placement: [number, number]): number => {
728
+ return placement[0];
729
+ }),
730
+ ),
731
+ ).sort((left: number, right: number): number => {
732
+ return left - right;
733
+ });
734
+ /* A call needs two different services at its ends. */
735
+ if (placedServiceIndexes.length < 2) {
736
+ return;
737
+ }
738
+ const placedServiceKeys: Array<string> = placedServiceIndexes.map(
739
+ (index: number): string => {
740
+ return data.response.services[index]!.key;
741
+ },
742
+ );
743
+
744
+ const dependencies: { rows: Array<JSONObject>; total: number } =
745
+ await TopologyQueries.readCapped(
746
+ runner,
747
+ (limit: number): TopologySqlStatement => {
748
+ return serviceMapDependenciesStatement({
749
+ projectId: data.projectId,
750
+ rangeStart: data.rangeStart,
751
+ serviceKeys: placedServiceKeys,
752
+ calleeKeys: placedServiceKeys,
753
+ mode: "rows",
754
+ limit,
755
+ });
756
+ },
757
+ (): TopologySqlStatement => {
758
+ return serviceMapDependenciesStatement({
759
+ projectId: data.projectId,
760
+ rangeStart: data.rangeStart,
761
+ serviceKeys: placedServiceKeys,
762
+ calleeKeys: placedServiceKeys,
763
+ mode: "count",
764
+ limit: 0,
765
+ });
766
+ },
767
+ TopologyApiLimits.MaxServiceMapDependencies,
768
+ );
769
+
770
+ const shipped: Array<TopologyInfrastructureDependencyJSON> = [];
771
+ for (const row of dependencies.rows) {
772
+ const from: number | undefined = data.serviceIndexByKey.get(
773
+ readString(row, "from"),
774
+ );
775
+ const to: number | undefined = data.serviceIndexByKey.get(
776
+ readString(row, "to"),
777
+ );
778
+ if (from === undefined || to === undefined || from === to) {
779
+ continue;
780
+ }
781
+ shipped.push({
782
+ from,
783
+ to,
784
+ callCount: readNullableNumber(row, "callCount"),
785
+ errorCount: readNullableNumber(row, "errorCount"),
786
+ avgDurationMs: readNullableNumber(row, "avgDurationMs"),
787
+ });
788
+ }
789
+ shipped.sort(
790
+ (
791
+ left: TopologyInfrastructureDependencyJSON,
792
+ right: TopologyInfrastructureDependencyJSON,
793
+ ): number => {
794
+ return left.from - right.from || left.to - right.to;
795
+ },
796
+ );
797
+ data.response.dependencies = shipped;
798
+ if (dependencies.total > dependencies.rows.length) {
799
+ data.response.dependencyTruncation = {
800
+ shown: dependencies.rows.length,
801
+ total: dependencies.total,
802
+ };
803
+ }
804
+ }
805
+
806
+ /*
807
+ * Infrastructure: one lean row per resource with its container chosen in
808
+ * SQL, every live service, where services run, the calls between placed
809
+ * services, and flat types too large to ship row by row as collections
810
+ * with exact counts.
811
+ */
812
+ @CaptureSpan()
813
+ public static async getInfrastructure(
814
+ request: TopologyRangeRequest & TopologyProjectScope,
815
+ ): Promise<TopologyInfrastructureResponseJSON> {
816
+ const projectId: string = request.projectId.toString();
817
+ const rangeStart: string = request.rangeStart.toISOString();
818
+ const nodeCap: number = TopologyApiLimits.MaxInfrastructureNodes;
819
+
820
+ return await TopologyQueries.inReadOnlySnapshot(
821
+ async (
822
+ runner: TopologyStatementRunner,
823
+ ): Promise<TopologyInfrastructureResponseJSON> => {
824
+ const response: TopologyInfrastructureResponseJSON = {
825
+ ...TopologyQueries.envelope(request.rangeStart),
826
+ nodes: [],
827
+ services: [],
828
+ placements: [],
829
+ dependencies: [],
830
+ dependencyTruncation: null,
831
+ collections: [],
832
+ totals: { resources: 0, activeResources: 0 },
833
+ truncation: null,
834
+ };
835
+
836
+ const projectTypes: Array<string> =
837
+ await TopologyQueries.readProjectTypes(runner, projectId);
838
+ const infrastructureTypes: Array<string> = projectTypes.filter(
839
+ (type: string): boolean => {
840
+ return isTopologyInfrastructureType(type);
841
+ },
842
+ );
843
+
844
+ /*
845
+ * Collections: a flat type (nothing nests in it, it nests in nothing,
846
+ * nothing groups it) with more items than the inline budget. Its
847
+ * volume is not bounded by any discovery budget, so the browser gets
848
+ * exact counts and pages the rows on demand. Its counts are over its
849
+ * rows; keys are unique per type.
850
+ */
851
+ const typeCounts: Array<JSONObject> =
852
+ infrastructureTypes.length > 0
853
+ ? await runner.query(
854
+ infrastructureTypeCountsStatement({
855
+ projectId,
856
+ rangeStart,
857
+ infrastructureTypes,
858
+ }),
859
+ )
860
+ : [];
861
+ const collectedTypes: Set<string> = new Set<string>();
862
+ for (const row of typeCounts) {
863
+ const type: string = readString(row, "type");
864
+ const total: number = readNumber(row, "total");
865
+ if (
866
+ isFlatInfrastructureType(type) &&
867
+ total > TopologyApiLimits.InlineFlatItemsPerType
868
+ ) {
869
+ collectedTypes.add(type);
870
+ response.collections.push({
871
+ type,
872
+ total,
873
+ active: readNumber(row, "active"),
874
+ lastSeenAt: readNullableNumber(row, "lastSeenAt"),
875
+ activeLastSeenAt: readNullableNumber(row, "activeLastSeenAt"),
876
+ });
877
+ }
878
+ }
879
+
880
+ const nodeTypes: Array<string> = infrastructureTypes.filter(
881
+ (type: string): boolean => {
882
+ return !collectedTypes.has(type);
883
+ },
884
+ );
885
+ const hasServices: boolean = projectTypes.includes(EntityType.Service);
886
+ const winner: TopologyWinnerScope =
887
+ nodeTypes.length > 0 || hasServices
888
+ ? await TopologyQueries.readWinnerScope(
889
+ runner,
890
+ projectId,
891
+ projectTypes,
892
+ )
893
+ : { projectTypes, duplicateKeys: [] };
894
+
895
+ let nodes: {
896
+ rows: Array<JSONObject>;
897
+ total: number;
898
+ count: JSONObject;
899
+ } = { rows: [], total: 0, count: {} };
900
+ if (nodeTypes.length > 0) {
901
+ nodes = await TopologyQueries.readCapped(
902
+ runner,
903
+ (limit: number): TopologySqlStatement => {
904
+ return infrastructureNodesStatement({
905
+ projectId,
906
+ rangeStart,
907
+ winner,
908
+ nodeTypes,
909
+ mode: "rows",
910
+ limit,
911
+ });
912
+ },
913
+ (): TopologySqlStatement => {
914
+ return infrastructureNodesStatement({
915
+ projectId,
916
+ rangeStart,
917
+ winner,
918
+ nodeTypes,
919
+ mode: "count",
920
+ limit: 0,
921
+ });
922
+ },
923
+ nodeCap,
924
+ );
925
+ }
926
+
927
+ const nodeIndexByKey: Map<string, number> = new Map<string, number>();
928
+ nodes.rows.forEach((row: JSONObject, index: number): void => {
929
+ nodeIndexByKey.set(readString(row, "key"), index);
930
+ });
931
+
932
+ response.nodes = nodes.rows.map(
933
+ (row: JSONObject): TopologyInfrastructureNodeJSON => {
934
+ const node: TopologyInfrastructureNodeJSON = toLeanEntity(row);
935
+ const parentKey: string | null = readNullableString(row, "parent");
936
+ /* A container outside the shipped set (a truncated list) is dropped. */
937
+ const parent: number | undefined =
938
+ parentKey === null ? undefined : nodeIndexByKey.get(parentKey);
939
+ if (parent === undefined) {
940
+ return node;
941
+ }
942
+ node.parent = parent;
943
+ node.parentVia = readString(row, "parentVia");
944
+ if (readBoolean(row, "parentInactive")) {
945
+ const activeParentKey: string | null = readNullableString(
946
+ row,
947
+ "activeParent",
948
+ );
949
+ const activeParent: number | undefined =
950
+ activeParentKey === null
951
+ ? undefined
952
+ : nodeIndexByKey.get(activeParentKey);
953
+ if (activeParent === undefined) {
954
+ node.activeParent = -1;
955
+ } else {
956
+ node.activeParent = activeParent;
957
+ node.activeParentVia = readString(row, "activeParentVia");
958
+ }
959
+ }
960
+ return node;
961
+ },
962
+ );
963
+
964
+ let activeNodes: number = 0;
965
+ if (nodes.total > nodes.rows.length) {
966
+ activeNodes = readNumber(nodes.count, "active");
967
+ response.truncation = {
968
+ shown: nodes.rows.length,
969
+ total: nodes.total,
970
+ };
971
+ } else {
972
+ for (const row of nodes.rows) {
973
+ if (readBoolean(row, "active")) {
974
+ activeNodes++;
975
+ }
976
+ }
977
+ }
978
+
979
+ response.totals = {
980
+ resources: nodes.total,
981
+ activeResources: activeNodes,
982
+ };
983
+ for (const collection of response.collections) {
984
+ response.totals.resources += collection.total;
985
+ response.totals.activeResources += collection.active;
986
+ }
987
+
988
+ if (hasServices) {
989
+ const serviceRows: Array<JSONObject> = await runner.query(
990
+ servicesStatement({ projectId, winner }),
991
+ );
992
+ response.services = serviceRows.map(
993
+ (row: JSONObject): TopologyInfrastructureServiceJSON => {
994
+ return {
995
+ key: readString(row, "key"),
996
+ name: readNullableString(row, "name"),
997
+ };
998
+ },
999
+ );
1000
+ }
1001
+
1002
+ if (response.services.length > 0 && response.nodes.length > 0) {
1003
+ const serviceIndexByKey: Map<string, number> = new Map<
1004
+ string,
1005
+ number
1006
+ >();
1007
+ response.services.forEach(
1008
+ (
1009
+ service: TopologyInfrastructureServiceJSON,
1010
+ index: number,
1011
+ ): void => {
1012
+ serviceIndexByKey.set(service.key, index);
1013
+ },
1014
+ );
1015
+ const placementRows: Array<JSONObject> = await runner.query(
1016
+ placementsStatement({
1017
+ projectId,
1018
+ rangeStart,
1019
+ serviceKeys: response.services.map(
1020
+ (service: TopologyInfrastructureServiceJSON): string => {
1021
+ return service.key;
1022
+ },
1023
+ ),
1024
+ }),
1025
+ );
1026
+ const placements: Array<[number, number]> = [];
1027
+ for (const row of placementRows) {
1028
+ const service: number | undefined = serviceIndexByKey.get(
1029
+ readString(row, "service"),
1030
+ );
1031
+ const node: number | undefined = nodeIndexByKey.get(
1032
+ readString(row, "target"),
1033
+ );
1034
+ if (service !== undefined && node !== undefined) {
1035
+ placements.push([service, node]);
1036
+ }
1037
+ }
1038
+ placements.sort(
1039
+ (left: [number, number], right: [number, number]): number => {
1040
+ return left[0] - right[0] || left[1] - right[1];
1041
+ },
1042
+ );
1043
+ response.placements = placements;
1044
+
1045
+ await TopologyQueries.readInfrastructureDependencies(runner, {
1046
+ projectId,
1047
+ rangeStart,
1048
+ response,
1049
+ serviceIndexByKey,
1050
+ });
1051
+ }
1052
+
1053
+ return response;
1054
+ },
1055
+ );
1056
+ }
1057
+
1058
+ /* One keyset page of a collection, with the total matching the filters. */
1059
+ @CaptureSpan()
1060
+ public static async getCollectionPage(
1061
+ request: TopologyCollectionRequest & TopologyProjectScope,
1062
+ ): Promise<TopologyCollectionResponseJSON> {
1063
+ const projectId: string = request.projectId.toString();
1064
+ const rangeStart: string = request.rangeStart.toISOString();
1065
+
1066
+ return await TopologyQueries.inReadOnlySnapshot(
1067
+ async (
1068
+ runner: TopologyStatementRunner,
1069
+ ): Promise<TopologyCollectionResponseJSON> => {
1070
+ const filters: {
1071
+ projectId: string;
1072
+ rangeStart: string;
1073
+ entityType: string;
1074
+ includeInactive: boolean;
1075
+ nameTerms: Array<string>;
1076
+ cursor: TopologyCollectionCursorJSON | null;
1077
+ } = {
1078
+ projectId,
1079
+ rangeStart,
1080
+ entityType: request.entityType,
1081
+ includeInactive: request.includeInactive,
1082
+ nameTerms: request.nameTerms,
1083
+ cursor: request.cursor,
1084
+ };
1085
+ const countRows: Array<JSONObject> = await runner.query(
1086
+ collectionPageStatement({ ...filters, mode: "count", limit: 0 }),
1087
+ );
1088
+ const rows: Array<JSONObject> = await runner.query(
1089
+ collectionPageStatement({
1090
+ ...filters,
1091
+ mode: "rows",
1092
+ limit: request.limit + 1,
1093
+ }),
1094
+ );
1095
+ const items: Array<TopologyEntityJSON> = rows
1096
+ .slice(0, request.limit)
1097
+ .map(toLeanEntity);
1098
+ const last: TopologyEntityJSON | undefined = items[items.length - 1];
1099
+
1100
+ return {
1101
+ ...TopologyQueries.envelope(request.rangeStart),
1102
+ entityType: request.entityType,
1103
+ total: readNumber(countRows[0] || {}, "total"),
1104
+ items,
1105
+ nextCursor:
1106
+ rows.length > request.limit && last
1107
+ ? { name: last.name ?? "", key: last.key }
1108
+ : null,
1109
+ };
1110
+ },
1111
+ );
1112
+ }
1113
+
1114
+ /* How many items of each requested collection match its terms. */
1115
+ @CaptureSpan()
1116
+ public static async getCollectionSearch(
1117
+ request: TopologyCollectionSearchRequest & TopologyProjectScope,
1118
+ ): Promise<TopologyCollectionSearchResponseJSON> {
1119
+ const response: TopologyCollectionSearchResponseJSON = {
1120
+ ...TopologyQueries.envelope(request.rangeStart),
1121
+ matches: [],
1122
+ };
1123
+ if (request.types.length === 0) {
1124
+ return response;
1125
+ }
1126
+
1127
+ const rows: Array<JSONObject> = await TopologyQueries.inReadOnlySnapshot(
1128
+ async (runner: TopologyStatementRunner): Promise<Array<JSONObject>> => {
1129
+ return await runner.query(
1130
+ collectionSearchStatement({
1131
+ projectId: request.projectId.toString(),
1132
+ rangeStart: request.rangeStart.toISOString(),
1133
+ includeInactive: request.includeInactive,
1134
+ types: request.types,
1135
+ }),
1136
+ );
1137
+ },
1138
+ );
1139
+
1140
+ const countByType: Map<string, number> = new Map<string, number>();
1141
+ for (const row of rows) {
1142
+ countByType.set(readString(row, "entityType"), readNumber(row, "count"));
1143
+ }
1144
+ for (const type of request.types) {
1145
+ const count: number = countByType.get(type.entityType) || 0;
1146
+ if (count > 0) {
1147
+ response.matches.push({ entityType: type.entityType, count });
1148
+ }
1149
+ }
1150
+ return response;
1151
+ }
1152
+
1153
+ /*
1154
+ * The drawer: the entity's full row and its in-range relationships in four
1155
+ * sections with exact totals (lower bounds when the scan stopped) and the
1156
+ * top rows of each.
1157
+ */
1158
+ @CaptureSpan()
1159
+ public static async getEntity(
1160
+ request: TopologyEntityRequest & TopologyProjectScope,
1161
+ ): Promise<TopologyEntityResponseJSON> {
1162
+ const result: {
1163
+ entity: TopologyEntityDetailJSON | null;
1164
+ decoded: DecodedSections;
1165
+ } = await TopologyQueries.readEntityAndSections(
1166
+ request,
1167
+ { kind: "range", rangeStart: request.rangeStart.toISOString() },
1168
+ TopologyQueries.firstView(),
1169
+ );
1170
+
1171
+ return {
1172
+ ...TopologyQueries.envelope(request.rangeStart),
1173
+ entity: result.entity,
1174
+ sections: result.decoded.sections,
1175
+ isScanLimited: result.decoded.isScanLimited,
1176
+ };
1177
+ }
1178
+
1179
+ /* "Show more": the next page of one drawer section. */
1180
+ @CaptureSpan()
1181
+ public static async getEntityConnections(
1182
+ request: TopologyEntityConnectionsRequest & TopologyProjectScope,
1183
+ ): Promise<TopologyEntityConnectionsResponseJSON> {
1184
+ const result: {
1185
+ entity: TopologyEntityDetailJSON | null;
1186
+ decoded: DecodedSections;
1187
+ } = await TopologyQueries.readEntityAndSections(
1188
+ request,
1189
+ { kind: "range", rangeStart: request.rangeStart.toISOString() },
1190
+ TopologyQueries.pageWindow(request),
1191
+ );
1192
+
1193
+ return {
1194
+ ...TopologyQueries.envelope(request.rangeStart),
1195
+ section: request.section,
1196
+ connections: result.decoded.sections[request.section],
1197
+ isScanLimited: result.decoded.isScanLimited,
1198
+ };
1199
+ }
1200
+
1201
+ /*
1202
+ * The inventory item page: the drawer's sections over everything the
1203
+ * inventory holds — every relationship however long ago it was seen, for
1204
+ * an item archived or not, with archived other ends named and linkable.
1205
+ */
1206
+ @CaptureSpan()
1207
+ public static async getEntityAllTime(
1208
+ request: TopologyEntityAllTimeRequest & TopologyProjectScope,
1209
+ ): Promise<TopologyEntityAllTimeResponseJSON> {
1210
+ const result: {
1211
+ entity: TopologyEntityDetailJSON | null;
1212
+ decoded: DecodedSections;
1213
+ } = await TopologyQueries.readEntityAndSections(
1214
+ request,
1215
+ { kind: "allTime" },
1216
+ TopologyQueries.firstView(),
1217
+ );
1218
+
1219
+ return {
1220
+ ...TopologyQueries.allTimeEnvelope(),
1221
+ entity: result.entity,
1222
+ sections: result.decoded.sections,
1223
+ isScanLimited: result.decoded.isScanLimited,
1224
+ };
1225
+ }
1226
+
1227
+ /* "Show more" on the inventory item page: one all-time section's page. */
1228
+ @CaptureSpan()
1229
+ public static async getEntityAllTimeConnections(
1230
+ request: TopologyEntityAllTimeConnectionsRequest & TopologyProjectScope,
1231
+ ): Promise<TopologyEntityAllTimeConnectionsResponseJSON> {
1232
+ const result: {
1233
+ entity: TopologyEntityDetailJSON | null;
1234
+ decoded: DecodedSections;
1235
+ } = await TopologyQueries.readEntityAndSections(
1236
+ request,
1237
+ { kind: "allTime" },
1238
+ TopologyQueries.pageWindow(request),
1239
+ );
1240
+
1241
+ return {
1242
+ ...TopologyQueries.allTimeEnvelope(),
1243
+ section: request.section,
1244
+ connections: result.decoded.sections[request.section],
1245
+ isScanLimited: result.decoded.isScanLimited,
1246
+ };
1247
+ }
1248
+
1249
+ /* The first view: the top rows of every section. */
1250
+ private static firstView(): TopologyEntitySectionWindow {
1251
+ return {
1252
+ kind: "top",
1253
+ dependencyRows: TopologyApiLimits.EntityDependencyRows,
1254
+ otherRows: TopologyApiLimits.EntityOtherRows,
1255
+ };
1256
+ }
1257
+
1258
+ private static pageWindow(
1259
+ request: TopologyConnectionsPage,
1260
+ ): TopologyEntitySectionWindow {
1261
+ return {
1262
+ kind: "page",
1263
+ section: request.section,
1264
+ offset: request.offset,
1265
+ limit: request.limit || rowsForSection(request.section),
1266
+ };
1267
+ }
1268
+
1269
+ private static async readEntityAndSections(
1270
+ request: TopologyEntityTarget & TopologyProjectScope,
1271
+ scope: TopologyEntityScope,
1272
+ window: TopologyEntitySectionWindow,
1273
+ ): Promise<{
1274
+ entity: TopologyEntityDetailJSON | null;
1275
+ decoded: DecodedSections;
1276
+ }> {
1277
+ const projectId: string = request.projectId.toString();
1278
+ const allTime: boolean = scope.kind === "allTime";
1279
+
1280
+ return await TopologyQueries.inReadOnlySnapshot(
1281
+ async (
1282
+ runner: TopologyStatementRunner,
1283
+ ): Promise<{
1284
+ entity: TopologyEntityDetailJSON | null;
1285
+ decoded: DecodedSections;
1286
+ }> => {
1287
+ const nothing: DecodedSections = {
1288
+ sections: emptySections(),
1289
+ isScanLimited: false,
1290
+ };
1291
+
1292
+ const projectTypes: Array<string> =
1293
+ await TopologyQueries.readProjectTypes(runner, projectId);
1294
+ if (projectTypes.length === 0) {
1295
+ return { entity: null, decoded: nothing };
1296
+ }
1297
+
1298
+ const entityRows: Array<JSONObject> = await runner.query(
1299
+ entityStatement({
1300
+ projectId,
1301
+ projectTypes,
1302
+ entityKey: request.entityKey,
1303
+ entityType: request.entityType,
1304
+ includeArchived: allTime,
1305
+ }),
1306
+ );
1307
+ const entityRow: JSONObject | undefined = entityRows[0];
1308
+ if (!entityRow) {
1309
+ return { entity: null, decoded: nothing };
1310
+ }
1311
+
1312
+ const entity: TopologyEntityDetailJSON = {
1313
+ ...toLeanEntity(entityRow),
1314
+ id: readString(entityRow, "id"),
1315
+ firstSeenAt: readNullableNumber(entityRow, "firstSeenAt"),
1316
+ resourceType: readNullableString(entityRow, "resourceType"),
1317
+ resourceId: readNullableString(entityRow, "resourceId"),
1318
+ identifyingAttributes: readJsonObject(
1319
+ entityRow,
1320
+ "identifyingAttributes",
1321
+ ),
1322
+ descriptiveAttributes: readJsonObject(
1323
+ entityRow,
1324
+ "descriptiveAttributes",
1325
+ ),
1326
+ };
1327
+
1328
+ const sectionRows: Array<JSONObject> = await runner.query(
1329
+ entitySectionsStatement({
1330
+ projectId,
1331
+ scope,
1332
+ projectTypes,
1333
+ entityKey: entity.key,
1334
+ isService: entity.type === EntityType.Service,
1335
+ scanLimit: TopologyApiLimits.EntityConnectionScanLimit + 1,
1336
+ window,
1337
+ }),
1338
+ );
1339
+
1340
+ return {
1341
+ entity,
1342
+ decoded: decodeSections(
1343
+ sectionRows,
1344
+ window.kind === "page" ? window.offset : 0,
1345
+ ),
1346
+ };
1347
+ },
1348
+ );
1349
+ }
1350
+ }