@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.
- package/Models/DatabaseModels/Incident.ts +267 -1
- package/Models/DatabaseModels/IncidentCustomField.ts +202 -0
- package/Models/DatabaseModels/IncidentEpisode.ts +30 -0
- package/Models/DatabaseModels/IncidentTemplate.ts +126 -1
- package/Models/DatabaseModels/InventoryItem.ts +23 -12
- package/Models/DatabaseModels/StatusPage.ts +55 -0
- package/Models/DatabaseModels/StatusPageSubscriber.ts +128 -0
- package/Server/API/IncidentAPI.ts +162 -0
- package/Server/API/StatusPageAPI.ts +487 -171
- package/Server/API/StatusPageSubscriberAPI.ts +63 -15
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.ts +217 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.ts +49 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.ts +108 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.ts +168 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.ts +44 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +16 -0
- package/Server/Infrastructure/Semaphore.ts +10 -0
- package/Server/Middleware/IdentityRateLimit.ts +108 -3
- package/Server/Middleware/SubscriberNotificationPreviewRateLimit.ts +237 -0
- package/Server/Middleware/SubscriberNotificationTestSendRateLimit.ts +304 -0
- package/Server/Services/AlertService.ts +32 -36
- package/Server/Services/DatabaseService.ts +93 -10
- package/Server/Services/IncidentAlertService.ts +609 -2
- package/Server/Services/IncidentCustomFieldService.ts +276 -1
- package/Server/Services/IncidentEpisodePublicNoteService.ts +28 -0
- package/Server/Services/IncidentEpisodeService.ts +15 -0
- package/Server/Services/IncidentEpisodeStateTimelineService.ts +27 -1
- package/Server/Services/IncidentPublicNoteService.ts +30 -1
- package/Server/Services/IncidentService.ts +1663 -103
- package/Server/Services/IncidentStateTimelineService.ts +13 -0
- package/Server/Services/IncidentTemplateService.ts +109 -0
- package/Server/Services/InventoryItemService.ts +38 -0
- package/Server/Services/IoTDeviceCredentialService.ts +13 -4
- package/Server/Services/ScheduledMaintenancePublicNoteService.ts +30 -1
- package/Server/Services/ScheduledMaintenanceService.ts +118 -64
- package/Server/Services/SmsService.ts +10 -0
- package/Server/Services/StatusPageService.ts +65 -8
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +299 -37
- package/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.ts +129 -0
- package/Server/Services/StatusPageSubscriberService.ts +1161 -64
- package/Server/Services/UserNotificationRuleService.ts +55 -53
- package/Server/Services/WorkspaceNotificationSummaryService.ts +38 -20
- package/Server/Types/Markdown.ts +153 -6
- package/Server/Types/Workflow/Components/Email.ts +61 -2
- package/Server/Utils/AffectedResources/LinkedAffectedResources.ts +576 -0
- package/Server/Utils/Alert/AlertStateChangeAuthorization.ts +129 -0
- package/Server/Utils/CustomField/CustomFieldRename.ts +378 -0
- package/Server/Utils/DataSource/EgressGuard.ts +66 -21
- package/Server/Utils/Inventory/InventoryOverviewAggregation.ts +111 -0
- package/Server/Utils/Mail/PinnedSmtpSocket.ts +130 -0
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +10 -0
- package/Server/Utils/Response.ts +31 -0
- package/Server/Utils/SSRFProtection.ts +76 -16
- package/Server/Utils/StatusPage/IncidentStatusPageScope.ts +948 -0
- package/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.ts +656 -0
- package/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.ts +805 -0
- package/Server/Utils/StatusPage/StatusPageExclusion.ts +18 -0
- package/Server/Utils/StatusPage/StatusPageReadAccess.ts +125 -0
- package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.ts +151 -0
- package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.ts +70 -0
- package/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.ts +421 -0
- package/Server/Utils/StatusPage/SubscriberNotificationClaim.ts +107 -0
- package/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.ts +1049 -0
- package/Server/Utils/StatusPage/SubscriberNotificationFanOut.ts +180 -0
- package/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.ts +839 -0
- package/Server/Utils/StatusPage/SubscriberNotificationResendAccess.ts +393 -0
- package/Server/Utils/StatusPage/SubscriberNotificationRunLimit.ts +246 -0
- package/Server/Utils/StatusPage/SubscriberNotificationTiming.ts +133 -0
- package/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.ts +215 -0
- package/Server/Utils/StatusPageResource.ts +51 -20
- package/Server/Utils/Topology/TopologyConcurrencyLimiter.ts +229 -0
- package/Server/Utils/Topology/TopologyQueries.ts +1350 -0
- package/Server/Utils/Topology/TopologyRequest.ts +463 -0
- package/Server/Utils/Topology/TopologyResponseCache.ts +183 -0
- package/Server/Utils/Topology/TopologySql.ts +1024 -0
- package/Server/Utils/VM/VMAPI.ts +1 -0
- package/Server/Utils/VM/VMRunner.ts +10 -0
- package/Server/Utils/Workflow/OAuth2TokenClient.ts +10 -0
- package/Server/Views/Partials/SensitiveUrlToken.ejs +8 -5
- package/Tests/App/Accounts/RegisterVerifyEmail.test.tsx +748 -0
- package/Tests/App/Accounts/ResendVerificationEmail.test.tsx +1222 -0
- package/Tests/App/Accounts/VerificationEmailResendUtils.test.ts +797 -0
- package/Tests/App/Accounts/VerifyEmailPending.test.tsx +538 -0
- package/Tests/App/Accounts/VerifyEmailResend.test.tsx +779 -0
- package/Tests/App/Dashboard/AcknowledgeAlertsOnDeclare.test.ts +369 -0
- package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +1 -1
- package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +510 -123
- package/Tests/App/Dashboard/AlertsTableAffectedResources.test.tsx +490 -0
- package/Tests/App/Dashboard/ChangeAlertStateDeclareIncident.test.tsx +886 -0
- package/Tests/App/Dashboard/CreateReviewStepRoleSummary.test.tsx +1250 -0
- package/Tests/App/Dashboard/CreateReviewStepSelectionSummary.test.tsx +738 -0
- package/Tests/App/Dashboard/CustomFieldsSettingsIncidentOptions.test.tsx +357 -0
- package/Tests/App/Dashboard/DeclareIncidentFromAlert.test.ts +417 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +1948 -229
- package/Tests/App/Dashboard/EntityDetailPanelLifecycle.test.tsx +493 -0
- package/Tests/App/Dashboard/EntityDetailPanelNavigation.test.tsx +439 -0
- package/Tests/App/Dashboard/EventNotes.test.tsx +776 -0
- package/Tests/App/Dashboard/EventNotesPages.test.tsx +119 -0
- package/Tests/App/Dashboard/EventNotesUtil.test.ts +116 -17
- package/Tests/App/Dashboard/EventOverviewPages.test.tsx +534 -6
- package/Tests/App/Dashboard/EventStatusPanel.test.tsx +798 -2
- package/Tests/App/Dashboard/FetchStateAndSeverityElements.test.tsx +669 -0
- package/Tests/App/Dashboard/FetchUsers.test.tsx +230 -0
- package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +91 -1
- package/Tests/App/Dashboard/IncidentCreateAcknowledgeAlerts.test.tsx +1611 -0
- package/Tests/App/Dashboard/IncidentCreateDetailsStep.test.tsx +643 -0
- package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +8 -1
- package/Tests/App/Dashboard/IncidentCreateStatusPageScope.test.tsx +601 -0
- package/Tests/App/Dashboard/IncidentCustomFieldTemplateVariables.test.tsx +386 -0
- package/Tests/App/Dashboard/IncidentNoteTemplatePlaceholders.test.tsx +124 -0
- package/Tests/App/Dashboard/IncidentNoteTemplateVariablesLoader.test.ts +305 -0
- package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +112 -0
- package/Tests/App/Dashboard/IncidentSettingsRenotifyOnPublish.test.tsx +674 -0
- package/Tests/App/Dashboard/IncidentSettingsStatusPageScope.test.tsx +1051 -0
- package/Tests/App/Dashboard/IncidentTemplateCustomFields.test.tsx +384 -0
- package/Tests/App/Dashboard/IncidentTemplateStatusPageScopeCard.test.tsx +215 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +1365 -57
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +964 -23
- package/Tests/App/Dashboard/InventoryRelationships.test.tsx +885 -0
- package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +113 -1
- package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +201 -15
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +508 -36
- package/Tests/App/Dashboard/SubscriberAudienceSummary.test.tsx +512 -0
- package/Tests/App/Dashboard/SubscriberNotificationPreviewModal.test.tsx +837 -0
- package/Tests/App/Dashboard/SubscriberNotificationStatusLabel.test.tsx +88 -0
- package/Tests/App/Dashboard/SubscriberNotificationStatusResend.test.tsx +473 -0
- package/Tests/App/Dashboard/SubscriberTemplateLivePreview.test.tsx +196 -0
- package/Tests/App/Dashboard/TeamAddedSubscribersUnsubscribedNotice.test.tsx +248 -0
- package/Tests/App/Dashboard/TopologyDataLifecycle.test.tsx +583 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +982 -218
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +818 -114
- package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +64 -3
- package/Tests/App/StatusPage/StatusPageUnsubscribePage.test.tsx +412 -0
- package/Tests/Models/IncidentCustomFieldApiSchemaContract.test.ts +217 -0
- package/Tests/Models/IncidentCustomFieldCreateSettingsColumns.test.ts +252 -0
- package/Tests/Models/StatusPageSubscriberUnsubscribeColumns.test.ts +218 -0
- package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +7 -0
- package/Tests/Server/API/IncidentSubscriberAudienceAPI.test.ts +1408 -0
- package/Tests/Server/API/StatusPageIncidentScope.test.ts +1868 -0
- package/Tests/Server/API/StatusPageIncidentScopePostgres.test.ts +790 -0
- package/Tests/Server/API/StatusPageManageSubscriptionRecipient.test.ts +10 -1
- package/Tests/Server/API/StatusPageManageSubscriptionTemplateVariables.test.ts +206 -18
- package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +14 -2
- package/Tests/Server/API/StatusPageSubscriberCreateResponse.test.ts +217 -0
- package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +103 -2
- package/Tests/Server/API/StatusPageUnsubscribeAPI.test.ts +396 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentStatusPageScopeMigration.test.ts +395 -0
- package/Tests/Server/Infrastructure/Postgres/AddIncidentTemplateStatusPageScopeFlagMigration.test.ts +150 -0
- package/Tests/Server/Infrastructure/Postgres/AddSubscriberNotificationClaimedAtMigration.test.ts +156 -0
- package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +90 -21
- package/Tests/Server/Infrastructure/Postgres/SkipHiddenIncidentCreatedNotificationsMigration.test.ts +410 -0
- package/Tests/Server/Middleware/IdentityRateLimit.test.ts +551 -1
- package/Tests/Server/Middleware/SubscriberNotificationPreviewRateLimit.test.ts +238 -0
- package/Tests/Server/Middleware/SubscriberNotificationTestSendRateLimit.test.ts +245 -0
- package/Tests/Server/Services/AddStatusPageSubscriberUnsubscribeTokenMigration.test.ts +152 -0
- package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +221 -36
- package/Tests/Server/Services/DatabaseServiceCompareAndSetColumns.test.ts +191 -0
- package/Tests/Server/Services/DomainService.test.ts +525 -0
- package/Tests/Server/Services/IncidentAlertAcknowledgeOnDeclare.test.ts +2630 -0
- package/Tests/Server/Services/IncidentAlertPostgres.test.ts +1625 -1
- package/Tests/Server/Services/IncidentCreatedRenotifyOnPublish.test.ts +681 -0
- package/Tests/Server/Services/IncidentCreatedResend.test.ts +775 -0
- package/Tests/Server/Services/IncidentCustomFieldRenamePostgres.test.ts +713 -0
- package/Tests/Server/Services/IncidentCustomFieldService.test.ts +751 -0
- package/Tests/Server/Services/IncidentCustomFieldValueValidation.test.ts +556 -0
- package/Tests/Server/Services/IncidentDeclareAcknowledgeAlerts.test.ts +2189 -0
- package/Tests/Server/Services/IncidentStatusPageScope.test.ts +2971 -0
- package/Tests/Server/Services/IncidentStatusPageScopeFeed.test.ts +131 -0
- package/Tests/Server/Services/IncidentStatusPageScopePostgres.test.ts +625 -0
- package/Tests/Server/Services/IncidentTemplateCustomFieldsCopy.test.ts +311 -0
- package/Tests/Server/Services/IncidentTemplateStatusPageScope.test.ts +363 -0
- package/Tests/Server/Services/InventoryItemServiceOverviewCounts.test.ts +345 -0
- package/Tests/Server/Services/InventoryOverviewPostgres.test.ts +254 -0
- package/Tests/Server/Services/IoTDeviceCredentialService.test.ts +819 -0
- package/Tests/Server/Services/LinkedAffectedResourcesCreatedFeed.test.ts +420 -0
- package/Tests/Server/Services/PublicNoteEditFeedEventType.test.ts +213 -0
- package/Tests/Server/Services/PublicNoteResendAccess.test.ts +994 -0
- package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +289 -9
- package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +92 -36
- package/Tests/Server/Services/SmsServiceFailIfNotSent.test.ts +67 -0
- package/Tests/Server/Services/StatusPageReportIncidentCounts.test.ts +610 -0
- package/Tests/Server/Services/StatusPageSubscriberAudienceCounts.test.ts +201 -0
- package/Tests/Server/Services/StatusPageSubscriberAudienceCountsPostgres.test.ts +271 -0
- package/Tests/Server/Services/StatusPageSubscriberConfirmationTemplateEscaping.test.ts +200 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationLogRedaction.test.ts +54 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateCompile.test.ts +37 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateIncidentRecordAccess.test.ts +772 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookup.test.ts +506 -0
- package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookupPostgres.test.ts +261 -0
- package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +21 -5
- package/Tests/Server/Services/StatusPageSubscriberUnsubscribe.test.ts +1618 -0
- package/Tests/Server/Services/StatusPageSubscriberUnsubscribeLinkSenders.test.ts +373 -0
- package/Tests/Server/Services/StatusPageSubscriberUnsubscribePostgres.test.ts +388 -0
- package/Tests/Server/Services/SubscriberNotificationInFlightRequeue.test.ts +500 -0
- package/Tests/Server/Types/MarkdownEmailLinks.test.ts +320 -0
- package/Tests/Server/Types/Workflow/Components/Email.test.ts +491 -1
- package/Tests/Server/Types/Workflow/Components/EmailSmtpPinning.test.ts +398 -0
- package/Tests/Server/Utils/AffectedResources/LinkedAffectedResources.test.ts +858 -0
- package/Tests/Server/Utils/Alert/AlertStateChangeAuthorization.test.ts +1172 -0
- package/Tests/Server/Utils/CustomField/CustomFieldRename.test.ts +586 -0
- package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +246 -10
- package/Tests/Server/Utils/DataSource/EgressGuardDeploymentConfig.test.ts +226 -0
- package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +131 -0
- package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +4 -0
- package/Tests/Server/Utils/EgressPolicyEnvironment.ts +45 -0
- package/Tests/Server/Utils/Mail/PinnedSmtpSocket.test.ts +409 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +23 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +4 -0
- package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +4 -0
- package/Tests/Server/Utils/SSRFProtectionPrivateNetwork.test.ts +4 -0
- package/Tests/Server/Utils/SSRFProtectionResolutionDetail.test.ts +314 -0
- package/Tests/Server/Utils/StatusPage/IncidentStatusPageScope.test.ts +1699 -0
- package/Tests/Server/Utils/StatusPage/IncidentStatusPageScopeQueriesPostgres.test.ts +508 -0
- package/Tests/Server/Utils/StatusPage/IncidentSubscriberAudienceCallerPermission.test.ts +246 -0
- package/Tests/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.test.ts +1348 -0
- package/Tests/Server/Utils/StatusPage/StatusPageReadAccess.test.ts +221 -0
- package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.test.ts +132 -0
- package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.test.ts +155 -0
- package/Tests/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.test.ts +708 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaim.test.ts +255 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaimPostgres.test.ts +528 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.test.ts +1238 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationFanOut.test.ts +472 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.test.ts +1168 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationRunLimit.test.ts +275 -0
- package/Tests/Server/Utils/StatusPage/SubscriberNotificationTiming.test.ts +131 -0
- package/Tests/Server/Utils/StatusPageResource.test.ts +98 -0
- package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigration.test.ts +386 -0
- package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigrationPostgres.test.ts +430 -0
- package/Tests/Server/Utils/Topology/TopologyActivityParityPostgres.test.ts +279 -0
- package/Tests/Server/Utils/Topology/TopologyConcurrencyLimiter.test.ts +534 -0
- package/Tests/Server/Utils/Topology/TopologyEstateGenerator.ts +624 -0
- package/Tests/Server/Utils/Topology/TopologyJsonResponse.test.ts +90 -0
- package/Tests/Server/Utils/Topology/TopologyQueries.test.ts +1634 -0
- package/Tests/Server/Utils/Topology/TopologyQueriesDifferentialPostgres.test.ts +683 -0
- package/Tests/Server/Utils/Topology/TopologyQueriesPostgres.test.ts +2624 -0
- package/Tests/Server/Utils/Topology/TopologyReference.ts +1018 -0
- package/Tests/Server/Utils/Topology/TopologyRequest.test.ts +643 -0
- package/Tests/Server/Utils/Topology/TopologyResponseCache.test.ts +305 -0
- package/Tests/Server/Utils/Topology/TopologySql.test.ts +973 -0
- package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +17 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +75 -0
- package/Tests/Types/CustomField/CustomFieldSavedViews.test.ts +247 -0
- package/Tests/Types/CustomField/CustomFieldTemplateValues.test.ts +186 -0
- package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +48 -0
- package/Tests/Types/CustomField/CustomFieldValueFormat.test.ts +215 -0
- package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +343 -0
- package/Tests/Types/CustomField/CustomFieldVariableKey.test.ts +364 -0
- package/Tests/Types/SafeHtml.test.ts +102 -0
- package/Tests/Types/StatusPage/IncidentCreatedRenotify.test.ts +419 -0
- package/Tests/Types/StatusPage/IncidentCreatedResend.test.ts +118 -0
- package/Tests/Types/StatusPage/IncidentScopeAddedPagesNotification.test.ts +558 -0
- package/Tests/Types/StatusPage/IncidentSubscriberAudience.test.ts +284 -0
- package/Tests/Types/StatusPage/StatusPageSubscriberUnsubscribe.test.ts +341 -0
- package/Tests/Types/StatusPage/SubscriberNotificationPreview.test.ts +196 -0
- package/Tests/Types/StatusPage/SubscriberNotificationResend.test.ts +201 -0
- package/Tests/Types/StatusPage/SubscriberNotificationTemplateCompiler.test.ts +277 -0
- package/Tests/Types/StatusPage/SubscriberNotificationTemplatePreview.test.ts +179 -0
- package/Tests/Types/StatusPage/SubscriberNotificationTemplateVariables.test.ts +324 -0
- package/Tests/Types/StatusPage/SubscriberUpdateNotification.test.ts +60 -13
- package/Tests/Types/Topology/TopologyTypeRules.test.ts +176 -0
- package/Tests/UI/Components/CustomFields/CustomFieldFormFields.test.ts +432 -0
- package/Tests/UI/Components/CustomFields/CustomFieldModelFormFields.test.tsx +685 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetailOrder.test.tsx +469 -0
- package/Tests/UI/Components/Forms/ModelFormCustomFieldInputs.test.tsx +325 -0
- package/Tests/UI/Components/Forms/ModelFormDropdownOptionsRefused.test.tsx +358 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +22 -0
- package/Tests/UI/Monitor/NetworkPathView.test.tsx +289 -0
- package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +302 -0
- package/Tests/Utils/Incident/IncidentNoteTemplateVariables.test.ts +623 -0
- package/Tests/Utils/Markdown/MarkdownEscape.test.ts +89 -0
- package/Tests/Utils/ProbeNetworkFailureUtil.test.ts +992 -0
- package/Tests/Utils/RequestFailedDetails.test.ts +575 -0
- package/Types/CustomField/CustomFieldOrder.ts +55 -0
- package/Types/CustomField/CustomFieldSavedViews.ts +225 -0
- package/Types/CustomField/CustomFieldTemplateMerge.ts +71 -0
- package/Types/CustomField/CustomFieldType.ts +14 -0
- package/Types/CustomField/CustomFieldValueFormat.ts +161 -0
- package/Types/CustomField/CustomFieldValueValidator.ts +488 -0
- package/Types/CustomField/CustomFieldVariableKey.ts +211 -0
- package/Types/Incident/IncidentAlertLink.ts +19 -0
- package/Types/SafeHtml.ts +93 -0
- package/Types/StatusPage/IncidentCreatedRenotify.ts +224 -0
- package/Types/StatusPage/IncidentCreatedResend.ts +108 -0
- package/Types/StatusPage/IncidentScopeAddedPagesNotification.ts +343 -0
- package/Types/StatusPage/IncidentSubscriberAudience.ts +332 -0
- package/Types/StatusPage/StatusPageSubscriberUnsubscribe.ts +405 -0
- package/Types/StatusPage/SubscriberNotificationInterruption.ts +32 -0
- package/Types/StatusPage/SubscriberNotificationPreview.ts +322 -0
- package/Types/StatusPage/SubscriberNotificationResend.ts +132 -0
- package/Types/StatusPage/SubscriberNotificationTemplateCompiler.ts +180 -0
- package/Types/StatusPage/SubscriberNotificationTemplatePreview.ts +301 -0
- package/Types/StatusPage/SubscriberNotificationTemplateVariables.ts +217 -4
- package/Types/StatusPage/SubscriberUpdateNotification.ts +32 -11
- package/Types/Telemetry/InventoryLiveness.ts +26 -0
- package/Types/Topology/TopologyApi.ts +428 -0
- package/Types/Topology/TopologyTypeRules.ts +191 -0
- package/UI/Components/CustomFields/CustomFieldFormFields.ts +317 -0
- package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +395 -0
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +95 -55
- package/UI/Components/Forms/ModelForm.tsx +70 -8
- package/UI/Components/Forms/Validation.ts +1 -1
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +2 -4
- package/UI/Components/ModelTable/AnalyticsModelTable.tsx +2 -6
- package/UI/Components/ModelTable/BaseModelTable.tsx +9 -8
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +14 -1
- package/UI/Components/ModelTable/ModelTable.tsx +6 -7
- package/UI/Utils/SensitiveUrlToken.ts +10 -5
- package/Utils/API.ts +247 -2
- package/Utils/Incident/IncidentFromAlerts.ts +78 -0
- package/Utils/Incident/IncidentNoteTemplateVariables.ts +304 -0
- package/Utils/Markdown/MarkdownEscape.ts +65 -0
- package/Utils/ProbeNetworkFailureUtil.ts +673 -0
- package/build/dist/Models/DatabaseModels/Incident.js +267 -1
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js +207 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js +31 -0
- package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js +124 -1
- package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js +20 -11
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +56 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +131 -0
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
- package/build/dist/Server/API/IncidentAPI.js +100 -1
- package/build/dist/Server/API/IncidentAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +417 -176
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageSubscriberAPI.js +52 -13
- package/build/dist/Server/API/StatusPageSubscriberAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js +165 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js +41 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js +57 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js +40 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js +89 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js +29 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Semaphore.js +3 -0
- package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
- package/build/dist/Server/Middleware/IdentityRateLimit.js +89 -3
- package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
- package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js +149 -0
- package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js.map +1 -0
- package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js +190 -0
- package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js.map +1 -0
- package/build/dist/Server/Services/AlertService.js +21 -33
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +56 -7
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentAlertService.js +457 -3
- package/build/dist/Server/Services/IncidentAlertService.js.map +1 -1
- package/build/dist/Server/Services/IncidentCustomFieldService.js +204 -8
- package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +24 -0
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeService.js +14 -0
- package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +26 -0
- package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentPublicNoteService.js +26 -1
- package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +1148 -42
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncidentTemplateService.js +83 -0
- package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +31 -0
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceCredentialService.js +12 -5
- package/build/dist/Server/Services/IoTDeviceCredentialService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +26 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +84 -57
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/SmsService.js +2 -22
- package/build/dist/Server/Services/SmsService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +47 -9
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +237 -32
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js +109 -0
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +925 -99
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +55 -51
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +42 -25
- package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +110 -6
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Email.js +55 -6
- package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
- package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js +405 -0
- package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js.map +1 -0
- package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js +112 -0
- package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js.map +1 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldRename.js +211 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldRename.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/EgressGuard.js +41 -12
- package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
- package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js +81 -0
- package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js.map +1 -0
- package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js +101 -0
- package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Response.js +30 -0
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/SSRFProtection.js +48 -6
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js +631 -0
- package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js +403 -0
- package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js +451 -0
- package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js +13 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js +91 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js +101 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js +47 -0
- package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js +273 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js +73 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js +722 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js +102 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js +527 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js +278 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js +137 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js +103 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js +114 -0
- package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js.map +1 -0
- package/build/dist/Server/Utils/StatusPageResource.js +39 -7
- package/build/dist/Server/Utils/StatusPageResource.js.map +1 -1
- package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js +178 -0
- package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyQueries.js +884 -0
- package/build/dist/Server/Utils/Topology/TopologyQueries.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyRequest.js +275 -0
- package/build/dist/Server/Utils/Topology/TopologyRequest.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologyResponseCache.js +146 -0
- package/build/dist/Server/Utils/Topology/TopologyResponseCache.js.map +1 -0
- package/build/dist/Server/Utils/Topology/TopologySql.js +688 -0
- package/build/dist/Server/Utils/Topology/TopologySql.js.map +1 -0
- package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMRunner.js +1 -0
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +3 -0
- package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldOrder.js +36 -0
- package/build/dist/Types/CustomField/CustomFieldOrder.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldSavedViews.js +135 -0
- package/build/dist/Types/CustomField/CustomFieldSavedViews.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js +27 -0
- package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldType.js +12 -0
- package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldValueFormat.js +126 -0
- package/build/dist/Types/CustomField/CustomFieldValueFormat.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldValueValidator.js +280 -0
- package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldVariableKey.js +150 -0
- package/build/dist/Types/CustomField/CustomFieldVariableKey.js.map +1 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js +17 -0
- package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -1
- package/build/dist/Types/SafeHtml.js +84 -0
- package/build/dist/Types/SafeHtml.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js +117 -0
- package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentCreatedResend.js +86 -0
- package/build/dist/Types/StatusPage/IncidentCreatedResend.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js +246 -0
- package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js.map +1 -0
- package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js +183 -0
- package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js.map +1 -0
- package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js +277 -0
- package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js +27 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js +190 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationResend.js +102 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationResend.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js +97 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js +175 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js.map +1 -0
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js +144 -4
- package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js.map +1 -1
- package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js +29 -9
- package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +23 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -1
- package/build/dist/Types/Topology/TopologyApi.js +55 -0
- package/build/dist/Types/Topology/TopologyApi.js.map +1 -0
- package/build/dist/Types/Topology/TopologyTypeRules.js +165 -0
- package/build/dist/Types/Topology/TopologyTypeRules.js.map +1 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js +169 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js.map +1 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +212 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +64 -49
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Forms/ModelForm.js +39 -5
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +1 -1
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/AnalyticsModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +2 -2
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +13 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ModelTable.js +1 -1
- package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
- package/build/dist/UI/Utils/SensitiveUrlToken.js +10 -5
- package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -1
- package/build/dist/Utils/API.js +177 -1
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/Incident/IncidentFromAlerts.js +38 -0
- package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -1
- package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js +155 -0
- package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js.map +1 -0
- package/build/dist/Utils/Markdown/MarkdownEscape.js +48 -0
- package/build/dist/Utils/Markdown/MarkdownEscape.js.map +1 -1
- package/build/dist/Utils/ProbeNetworkFailureUtil.js +460 -0
- package/build/dist/Utils/ProbeNetworkFailureUtil.js.map +1 -0
- 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
|
+
}
|