@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,1024 @@
|
|
|
1
|
+
import EntityRelationshipType from "../../../Types/Telemetry/EntityRelationshipType";
|
|
2
|
+
import EntitySource from "../../../Types/Telemetry/EntitySource";
|
|
3
|
+
import EntityType from "../../../Types/Telemetry/EntityType";
|
|
4
|
+
import {
|
|
5
|
+
CONTAINER_SPECIFICITY,
|
|
6
|
+
NESTABLE_CHILD_TYPES,
|
|
7
|
+
NESTING_RELATIONSHIP_PRIORITY,
|
|
8
|
+
PLACEMENT_RELATIONSHIP_TYPES,
|
|
9
|
+
SERVICE_MAP_DETAIL_ATTRIBUTE_KEYS,
|
|
10
|
+
} from "../../../Types/Topology/TopologyTypeRules";
|
|
11
|
+
import {
|
|
12
|
+
TopologyCollectionCursorJSON,
|
|
13
|
+
TopologyConnectionSection,
|
|
14
|
+
} from "../../../Types/Topology/TopologyApi";
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* The SQL behind the Topology API (see Common/Types/Topology/TopologyApi.ts),
|
|
18
|
+
* as pure statement builders so the text and its parameters can be checked
|
|
19
|
+
* without a database.
|
|
20
|
+
*
|
|
21
|
+
* Rules every statement here follows:
|
|
22
|
+
*
|
|
23
|
+
* - Everything a caller or the database supplied is a bound parameter.
|
|
24
|
+
* The only text interpolated into SQL is table aliases and constants from
|
|
25
|
+
* this module and Common (entity types, relationship types, section
|
|
26
|
+
* names), never a request value.
|
|
27
|
+
* - The project is always bound and every table reference carries
|
|
28
|
+
* `"deletedAt" IS NULL`. Items are additionally `"isArchived" = false`
|
|
29
|
+
* (except in the project-types probe, which only widens a filter).
|
|
30
|
+
* - Lookups by entity key also constrain `"entityType" = ANY(<the project's
|
|
31
|
+
* types>)`, so the unique (projectId, entityType, entityKey) index serves
|
|
32
|
+
* them on installs where the (projectId, entityKey) index has not been
|
|
33
|
+
* built yet (Helm runs migrations asynchronously). Many keys travel as
|
|
34
|
+
* one array parameter, never as a sub-select over a CTE, so the planner
|
|
35
|
+
* knows how many there are (see itemsByKeysStatement).
|
|
36
|
+
* - Relationship end keys and tie-breaks compare with COLLATE "C": byte
|
|
37
|
+
* order of the UTF-8 encoding, which is code-POINT order — far cheaper
|
|
38
|
+
* to sort than the database collation, and what the Dashboard's
|
|
39
|
+
* code-point comparator (TopologyTypeRules.compareCodePoints) matches.
|
|
40
|
+
* Note it is not JavaScript's `<` (UTF-16 code-unit order), which
|
|
41
|
+
* disagrees where a character outside the Basic Multilingual Plane meets
|
|
42
|
+
* one in U+E000..U+FFFF.
|
|
43
|
+
* - Counts are cast to int (node-postgres returns bigint as a string) and
|
|
44
|
+
* timestamps leave the database as epoch milliseconds (float8), so hot
|
|
45
|
+
* result sets never allocate a Date per row.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
export interface TopologySqlStatement {
|
|
49
|
+
sql: string;
|
|
50
|
+
params: Array<unknown>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Collects bound parameters and hands out their placeholders in order. */
|
|
54
|
+
export class TopologySqlParams {
|
|
55
|
+
private values: Array<unknown> = [];
|
|
56
|
+
|
|
57
|
+
public add(value: unknown): string {
|
|
58
|
+
this.values.push(value);
|
|
59
|
+
return `$${this.values.length}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public list(): Array<unknown> {
|
|
63
|
+
return this.values;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
* A parameter bound the first time a statement's text needs it. Postgres
|
|
69
|
+
* refuses a parameter the text never references ("could not determine data
|
|
70
|
+
* type"), and several are only needed on some paths — the range start when
|
|
71
|
+
* inactive items are filtered out, the project's types when a duplicated key
|
|
72
|
+
* has to be resolved.
|
|
73
|
+
*/
|
|
74
|
+
export class TopologyLazyParam {
|
|
75
|
+
private placeholder: string | null = null;
|
|
76
|
+
|
|
77
|
+
public constructor(
|
|
78
|
+
private params: TopologySqlParams,
|
|
79
|
+
private value: unknown,
|
|
80
|
+
) {}
|
|
81
|
+
|
|
82
|
+
public get(): string {
|
|
83
|
+
if (!this.placeholder) {
|
|
84
|
+
this.placeholder = this.params.add(this.value);
|
|
85
|
+
}
|
|
86
|
+
return this.placeholder;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
* What the winner rule (winnerSql) needs: the project's types, so the
|
|
92
|
+
* duplicate probe can use the unique (projectId, entityType, entityKey)
|
|
93
|
+
* index, and the keys more than one live row carries (duplicateKeysStatement).
|
|
94
|
+
*/
|
|
95
|
+
export interface TopologyWinnerScope {
|
|
96
|
+
projectTypes: Array<string>;
|
|
97
|
+
duplicateKeys: Array<string>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/*
|
|
101
|
+
* Ranking tables for the nesting choice, bound as parallel arrays so the SQL
|
|
102
|
+
* can never disagree with the Dashboard's own copy of the rules (both read
|
|
103
|
+
* TopologyTypeRules).
|
|
104
|
+
*/
|
|
105
|
+
export interface TopologyRankTable {
|
|
106
|
+
keys: Array<string>;
|
|
107
|
+
ranks: Array<number>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function toRankTable(
|
|
111
|
+
table: Partial<Record<string, number | undefined>>,
|
|
112
|
+
): TopologyRankTable {
|
|
113
|
+
const keys: Array<string> = [];
|
|
114
|
+
const ranks: Array<number> = [];
|
|
115
|
+
for (const key of Object.keys(table).sort()) {
|
|
116
|
+
const rank: number | undefined = table[key];
|
|
117
|
+
if (typeof rank === "number") {
|
|
118
|
+
keys.push(key);
|
|
119
|
+
ranks.push(rank);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { keys, ranks };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const NESTING_RELATIONSHIP_RANKS: TopologyRankTable = toRankTable(
|
|
126
|
+
NESTING_RELATIONSHIP_PRIORITY,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
export const CONTAINER_SPECIFICITY_RANKS: TopologyRankTable = toRankTable(
|
|
130
|
+
CONTAINER_SPECIFICITY,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
export const NESTABLE_CHILD_TYPE_LIST: Array<string> =
|
|
134
|
+
Array.from(NESTABLE_CHILD_TYPES).sort();
|
|
135
|
+
|
|
136
|
+
export const PLACEMENT_RELATIONSHIP_TYPE_LIST: Array<string> = Array.from(
|
|
137
|
+
PLACEMENT_RELATIONSHIP_TYPES,
|
|
138
|
+
).sort();
|
|
139
|
+
|
|
140
|
+
export const SERVICE_MAP_ATTRIBUTE_KEY_LIST: Array<string> = [
|
|
141
|
+
...SERVICE_MAP_DETAIL_ATTRIBUTE_KEYS,
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
/* Labels the drawer orders its rows by; the browser translates its own. */
|
|
145
|
+
export const UNNAMED_RESOURCE_LABEL: string = "Unnamed resource";
|
|
146
|
+
export const UNDISCOVERED_RESOURCE_LABEL: string = "Undiscovered resource";
|
|
147
|
+
|
|
148
|
+
// ------------------------------------------------------------- fragments
|
|
149
|
+
|
|
150
|
+
/*
|
|
151
|
+
* "Did this resource report in the range?", the SQL twin of the Dashboard's
|
|
152
|
+
* TopologyActivity.isEntityActive. Only discovered rows have a heartbeat:
|
|
153
|
+
* manual and inventory-mirrored rows (any other non-blank source) are always
|
|
154
|
+
* active, and so is a row that has never reported. Change the two together.
|
|
155
|
+
*/
|
|
156
|
+
export function activeSql(alias: string, rangeStart: string): string {
|
|
157
|
+
return (
|
|
158
|
+
`CASE WHEN ${alias}."source" IS NOT NULL AND ${alias}."source" <> '' ` +
|
|
159
|
+
`AND ${alias}."source" <> '${EntitySource.Discovered}' THEN TRUE ` +
|
|
160
|
+
`WHEN ${alias}."lastSeenAt" IS NULL THEN TRUE ` +
|
|
161
|
+
`ELSE ${alias}."lastSeenAt" >= ${rangeStart} END`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* Epoch milliseconds, floored, as float8 (a JavaScript number). */
|
|
166
|
+
export function epochMsSql(expression: string): string {
|
|
167
|
+
return `floor(extract(epoch from ${expression}) * 1000)::float8`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* A non-archived, non-deleted item of the project. */
|
|
171
|
+
export function liveItemSql(alias: string, projectId: string): string {
|
|
172
|
+
return (
|
|
173
|
+
`${alias}."projectId" = ${projectId} AND ${alias}."isArchived" = false ` +
|
|
174
|
+
`AND ${alias}."deletedAt" IS NULL`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/*
|
|
179
|
+
* A non-deleted row of the project, for the all-time reads: an item archived
|
|
180
|
+
* or not, or a relationship however long ago it was seen.
|
|
181
|
+
*/
|
|
182
|
+
export function storedRowSql(alias: string, projectId: string): string {
|
|
183
|
+
return `${alias}."projectId" = ${projectId} AND ${alias}."deletedAt" IS NULL`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* A non-deleted relationship of the project that was seen in the range. */
|
|
187
|
+
export function inRangeRelationshipSql(
|
|
188
|
+
alias: string,
|
|
189
|
+
projectId: string,
|
|
190
|
+
rangeStart: string,
|
|
191
|
+
): string {
|
|
192
|
+
return (
|
|
193
|
+
`${alias}."projectId" = ${projectId} AND ${alias}."deletedAt" IS NULL ` +
|
|
194
|
+
`AND ${alias}."lastSeenAt" >= ${rangeStart}`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/*
|
|
199
|
+
* The row that represents its entity key. Keys are unique per type (the
|
|
200
|
+
* unique index) and hash the type into their preimage, so two live rows share
|
|
201
|
+
* a key only if something wrote one by hand — but when it happens every
|
|
202
|
+
* endpoint must pick the same row: the first by (createdAt ASC, _id DESC),
|
|
203
|
+
* which is the row the old client-side list kept (it iterated createdAt DESC,
|
|
204
|
+
* _id ASC and let the last write win).
|
|
205
|
+
*
|
|
206
|
+
* The duplicated keys are found once per request (duplicateKeysStatement) and
|
|
207
|
+
* the rule is applied to them alone. For every other key the row is its own
|
|
208
|
+
* winner, so the common case adds no predicate at all — an anti-join against
|
|
209
|
+
* the whole project would cost a full pass over it in every statement that
|
|
210
|
+
* lists items.
|
|
211
|
+
*/
|
|
212
|
+
export function winnerSql(
|
|
213
|
+
alias: string,
|
|
214
|
+
projectId: string,
|
|
215
|
+
projectTypes: string,
|
|
216
|
+
duplicateKeys: string,
|
|
217
|
+
): string {
|
|
218
|
+
return (
|
|
219
|
+
`(NOT (${alias}."entityKey" = ANY(${duplicateKeys}::text[])) ` +
|
|
220
|
+
`OR NOT EXISTS (SELECT 1 FROM "InventoryItem" dup ` +
|
|
221
|
+
`WHERE dup."projectId" = ${projectId} ` +
|
|
222
|
+
`AND dup."entityType" = ANY(${projectTypes}::text[]) ` +
|
|
223
|
+
`AND dup."entityKey" = ${alias}."entityKey" ` +
|
|
224
|
+
`AND dup."isArchived" = false AND dup."deletedAt" IS NULL ` +
|
|
225
|
+
`AND dup."_id" <> ${alias}."_id" ` +
|
|
226
|
+
`AND (dup."createdAt" < ${alias}."createdAt" ` +
|
|
227
|
+
`OR (dup."createdAt" = ${alias}."createdAt" AND dup."_id" > ${alias}."_id"))))`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* The winner predicate for `alias`, or nothing when no key is duplicated. */
|
|
232
|
+
function winnerPredicates(data: {
|
|
233
|
+
params: TopologySqlParams;
|
|
234
|
+
alias: string;
|
|
235
|
+
projectId: string;
|
|
236
|
+
projectTypes: TopologyLazyParam;
|
|
237
|
+
winner: TopologyWinnerScope;
|
|
238
|
+
}): Array<string> {
|
|
239
|
+
if (data.winner.duplicateKeys.length === 0) {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
return [
|
|
243
|
+
winnerSql(
|
|
244
|
+
data.alias,
|
|
245
|
+
data.projectId,
|
|
246
|
+
data.projectTypes.get(),
|
|
247
|
+
data.params.add(data.winner.duplicateKeys),
|
|
248
|
+
),
|
|
249
|
+
];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* Escapes LIKE metacharacters so a search term only ever matches literally. */
|
|
253
|
+
export function escapeLikeTerm(term: string): string {
|
|
254
|
+
return term.replace(/[\\%_]/g, (character: string): string => {
|
|
255
|
+
return `\\${character}`;
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* Every term must appear in the display name (case-insensitively). */
|
|
260
|
+
function nameTermsSql(
|
|
261
|
+
alias: string,
|
|
262
|
+
params: TopologySqlParams,
|
|
263
|
+
nameTerms: Array<string>,
|
|
264
|
+
): Array<string> {
|
|
265
|
+
return nameTerms.map((term: string): string => {
|
|
266
|
+
return `${alias}."displayName" ILIKE '%' || ${params.add(escapeLikeTerm(term))} || '%' ESCAPE '\\'`;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* The lean entity columns every map row is built from. */
|
|
271
|
+
function leanEntityColumnsSql(alias: string): string {
|
|
272
|
+
return (
|
|
273
|
+
`${alias}."entityKey" AS "key", ${alias}."entityType" AS "type", ` +
|
|
274
|
+
`${alias}."displayName" AS "name", ${alias}."source" AS "source", ` +
|
|
275
|
+
`${epochMsSql(`${alias}."lastSeenAt"`)} AS "lastSeenAt"`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/*
|
|
280
|
+
* Only string-valued detail attributes, per bag: the Service Map's reader
|
|
281
|
+
* needs a non-blank string and falls through to the next key otherwise, so
|
|
282
|
+
* anything else would change which label it shows.
|
|
283
|
+
*/
|
|
284
|
+
function detailAttributesSql(
|
|
285
|
+
alias: string,
|
|
286
|
+
column: string,
|
|
287
|
+
keys: string,
|
|
288
|
+
): string {
|
|
289
|
+
return (
|
|
290
|
+
`(SELECT jsonb_object_agg(k.key, ${alias}."${column}" -> k.key) ` +
|
|
291
|
+
`FROM unnest(${keys}::text[]) AS k(key) ` +
|
|
292
|
+
`WHERE jsonb_typeof(${alias}."${column}" -> k.key) = 'string')`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ----------------------------------------------------------- statements
|
|
297
|
+
|
|
298
|
+
/*
|
|
299
|
+
* The project's distinct entity types, read by skipping through the
|
|
300
|
+
* (projectId, entityType) index one type at a time (a "loose index scan"), so
|
|
301
|
+
* it costs one index probe per type however many items there are.
|
|
302
|
+
*/
|
|
303
|
+
export function projectTypesStatement(data: {
|
|
304
|
+
projectId: string;
|
|
305
|
+
}): TopologySqlStatement {
|
|
306
|
+
const params: TopologySqlParams = new TopologySqlParams();
|
|
307
|
+
const projectId: string = params.add(data.projectId);
|
|
308
|
+
return {
|
|
309
|
+
sql:
|
|
310
|
+
`WITH RECURSIVE project_types AS (` +
|
|
311
|
+
`(SELECT i."entityType" FROM "InventoryItem" i ` +
|
|
312
|
+
`WHERE i."projectId" = ${projectId} AND i."deletedAt" IS NULL ` +
|
|
313
|
+
`ORDER BY i."entityType" LIMIT 1) ` +
|
|
314
|
+
`UNION ALL ` +
|
|
315
|
+
`SELECT (SELECT i."entityType" FROM "InventoryItem" i ` +
|
|
316
|
+
`WHERE i."projectId" = ${projectId} AND i."deletedAt" IS NULL ` +
|
|
317
|
+
`AND i."entityType" > t."entityType" ORDER BY i."entityType" LIMIT 1) ` +
|
|
318
|
+
`FROM project_types t WHERE t."entityType" IS NOT NULL) ` +
|
|
319
|
+
`SELECT pt."entityType" AS "entityType" FROM project_types pt ` +
|
|
320
|
+
`WHERE pt."entityType" IS NOT NULL`,
|
|
321
|
+
params: params.list(),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/*
|
|
326
|
+
* Keys that more than one live row of the project carries (normally none).
|
|
327
|
+
* One pass over the project; with the (projectId, entityKey) index it streams
|
|
328
|
+
* in key order without holding the keys in memory.
|
|
329
|
+
*/
|
|
330
|
+
export function duplicateKeysStatement(data: {
|
|
331
|
+
projectId: string;
|
|
332
|
+
}): TopologySqlStatement {
|
|
333
|
+
const params: TopologySqlParams = new TopologySqlParams();
|
|
334
|
+
const projectId: string = params.add(data.projectId);
|
|
335
|
+
return {
|
|
336
|
+
sql:
|
|
337
|
+
`SELECT i."entityKey" AS "key" FROM "InventoryItem" i ` +
|
|
338
|
+
`WHERE ${liveItemSql("i", projectId)} ` +
|
|
339
|
+
`GROUP BY i."entityKey" HAVING COUNT(*) > 1`,
|
|
340
|
+
params: params.list(),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export type TopologyStatementMode = "rows" | "count";
|
|
345
|
+
|
|
346
|
+
/*
|
|
347
|
+
* Service Map entities of one kind: every non-archived service
|
|
348
|
+
* (`serviceKeys` absent), or the non-service items with the given keys (the
|
|
349
|
+
* things services call). Ordered by key so a truncated list is stable.
|
|
350
|
+
*/
|
|
351
|
+
export function serviceMapEntitiesStatement(data: {
|
|
352
|
+
projectId: string;
|
|
353
|
+
winner: TopologyWinnerScope;
|
|
354
|
+
/* Absent: the services. Present: these keys, services excluded. */
|
|
355
|
+
keys?: Array<string> | undefined;
|
|
356
|
+
mode: TopologyStatementMode;
|
|
357
|
+
limit: number;
|
|
358
|
+
}): TopologySqlStatement {
|
|
359
|
+
const params: TopologySqlParams = new TopologySqlParams();
|
|
360
|
+
const projectId: string = params.add(data.projectId);
|
|
361
|
+
const projectTypes: TopologyLazyParam = new TopologyLazyParam(
|
|
362
|
+
params,
|
|
363
|
+
data.winner.projectTypes,
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
const where: Array<string> = [liveItemSql("i", projectId)];
|
|
367
|
+
if (data.keys) {
|
|
368
|
+
where.push(`i."entityType" = ANY(${projectTypes.get()}::text[])`);
|
|
369
|
+
where.push(`i."entityKey" = ANY(${params.add(data.keys)}::text[])`);
|
|
370
|
+
where.push(`i."entityType" <> ${params.add(EntityType.Service)}`);
|
|
371
|
+
} else {
|
|
372
|
+
where.push(`i."entityType" = ${params.add(EntityType.Service)}`);
|
|
373
|
+
}
|
|
374
|
+
where.push(
|
|
375
|
+
...winnerPredicates({
|
|
376
|
+
params,
|
|
377
|
+
alias: "i",
|
|
378
|
+
projectId,
|
|
379
|
+
projectTypes,
|
|
380
|
+
winner: data.winner,
|
|
381
|
+
}),
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
if (data.mode === "count") {
|
|
385
|
+
return {
|
|
386
|
+
sql: `SELECT COUNT(*)::int AS "total" FROM "InventoryItem" i WHERE ${where.join(" AND ")}`,
|
|
387
|
+
params: params.list(),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const attributeKeys: string = params.add(SERVICE_MAP_ATTRIBUTE_KEY_LIST);
|
|
392
|
+
return {
|
|
393
|
+
sql:
|
|
394
|
+
`SELECT ${leanEntityColumnsSql("i")}, ` +
|
|
395
|
+
`${detailAttributesSql("i", "descriptiveAttributes", attributeKeys)} AS "descriptiveAttributes", ` +
|
|
396
|
+
`${detailAttributesSql("i", "identifyingAttributes", attributeKeys)} AS "identifyingAttributes" ` +
|
|
397
|
+
`FROM "InventoryItem" i WHERE ${where.join(" AND ")} ` +
|
|
398
|
+
`ORDER BY i."entityKey" COLLATE "C" ASC ` +
|
|
399
|
+
`LIMIT ${params.add(data.limit)}`,
|
|
400
|
+
params: params.list(),
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/*
|
|
405
|
+
* In-range `depends-on` relationships whose caller is one of the services, in
|
|
406
|
+
* the order the old list API returned them (createdAt DESC, _id ASC) so the
|
|
407
|
+
* browser sums traffic in the same order and averages come out bit-identical.
|
|
408
|
+
* `calleeKeys` narrows them to calls into those keys (Infrastructure asks
|
|
409
|
+
* only for calls between services it can place).
|
|
410
|
+
*/
|
|
411
|
+
export function serviceMapDependenciesStatement(data: {
|
|
412
|
+
projectId: string;
|
|
413
|
+
rangeStart: string;
|
|
414
|
+
serviceKeys: Array<string>;
|
|
415
|
+
calleeKeys?: Array<string> | undefined;
|
|
416
|
+
mode: TopologyStatementMode;
|
|
417
|
+
limit: number;
|
|
418
|
+
}): TopologySqlStatement {
|
|
419
|
+
const params: TopologySqlParams = new TopologySqlParams();
|
|
420
|
+
const projectId: string = params.add(data.projectId);
|
|
421
|
+
const rangeStart: string = params.add(data.rangeStart);
|
|
422
|
+
const where: string =
|
|
423
|
+
`${inRangeRelationshipSql("r", projectId, rangeStart)} ` +
|
|
424
|
+
`AND r."relationshipType" = ${params.add(EntityRelationshipType.DependsOn)} ` +
|
|
425
|
+
`AND r."fromEntityKey" <> r."toEntityKey" ` +
|
|
426
|
+
`AND r."fromEntityKey" = ANY(${params.add(data.serviceKeys)}::text[])` +
|
|
427
|
+
(data.calleeKeys
|
|
428
|
+
? ` AND r."toEntityKey" = ANY(${params.add(data.calleeKeys)}::text[])`
|
|
429
|
+
: "");
|
|
430
|
+
|
|
431
|
+
if (data.mode === "count") {
|
|
432
|
+
return {
|
|
433
|
+
sql: `SELECT COUNT(*)::int AS "total" FROM "InventoryItemRelationship" r WHERE ${where}`,
|
|
434
|
+
params: params.list(),
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
sql:
|
|
440
|
+
`SELECT r."fromEntityKey" AS "from", r."toEntityKey" AS "to", ` +
|
|
441
|
+
`r."callCount" AS "callCount", r."errorCount" AS "errorCount", ` +
|
|
442
|
+
`r."avgDurationMs" AS "avgDurationMs" ` +
|
|
443
|
+
`FROM "InventoryItemRelationship" r WHERE ${where} ` +
|
|
444
|
+
`ORDER BY r."createdAt" DESC, r."_id" ASC ` +
|
|
445
|
+
`LIMIT ${params.add(data.limit)}`,
|
|
446
|
+
params: params.list(),
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/*
|
|
451
|
+
* Where services run: distinct (service, resource) over in-range runs-on /
|
|
452
|
+
* hosted-on from the given services. The Service Map counts these per target
|
|
453
|
+
* type; Infrastructure keeps the ones that land on a shipped node.
|
|
454
|
+
*/
|
|
455
|
+
export function placementsStatement(data: {
|
|
456
|
+
projectId: string;
|
|
457
|
+
rangeStart: string;
|
|
458
|
+
serviceKeys: Array<string>;
|
|
459
|
+
}): TopologySqlStatement {
|
|
460
|
+
const params: TopologySqlParams = new TopologySqlParams();
|
|
461
|
+
const projectId: string = params.add(data.projectId);
|
|
462
|
+
const rangeStart: string = params.add(data.rangeStart);
|
|
463
|
+
return {
|
|
464
|
+
sql:
|
|
465
|
+
`SELECT DISTINCT r."fromEntityKey" AS "service", r."toEntityKey" AS "target" ` +
|
|
466
|
+
`FROM "InventoryItemRelationship" r ` +
|
|
467
|
+
`WHERE ${inRangeRelationshipSql("r", projectId, rangeStart)} ` +
|
|
468
|
+
`AND r."relationshipType" = ANY(${params.add(PLACEMENT_RELATIONSHIP_TYPE_LIST)}::text[]) ` +
|
|
469
|
+
`AND r."fromEntityKey" = ANY(${params.add(data.serviceKeys)}::text[])`,
|
|
470
|
+
params: params.list(),
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/*
|
|
475
|
+
* The live items (winning rows) with the given keys: their type and whether
|
|
476
|
+
* they reported in range. The keys are an array rather than a sub-select so
|
|
477
|
+
* the planner knows how many there are — a handful become index probes, tens
|
|
478
|
+
* of thousands one pass over the project (a sub-select over a CTE is
|
|
479
|
+
* estimated at a couple of hundred rows and gets probed key by key, once per
|
|
480
|
+
* project type, which is seconds on a large estate).
|
|
481
|
+
*/
|
|
482
|
+
export function itemsByKeysStatement(data: {
|
|
483
|
+
projectId: string;
|
|
484
|
+
rangeStart: string;
|
|
485
|
+
winner: TopologyWinnerScope;
|
|
486
|
+
keys: Array<string>;
|
|
487
|
+
}): TopologySqlStatement {
|
|
488
|
+
const params: TopologySqlParams = new TopologySqlParams();
|
|
489
|
+
const projectId: string = params.add(data.projectId);
|
|
490
|
+
const rangeStart: string = params.add(data.rangeStart);
|
|
491
|
+
const projectTypes: TopologyLazyParam = new TopologyLazyParam(
|
|
492
|
+
params,
|
|
493
|
+
data.winner.projectTypes,
|
|
494
|
+
);
|
|
495
|
+
const where: Array<string> = [
|
|
496
|
+
liveItemSql("i", projectId),
|
|
497
|
+
`i."entityType" = ANY(${projectTypes.get()}::text[])`,
|
|
498
|
+
`i."entityKey" = ANY(${params.add(data.keys)}::text[])`,
|
|
499
|
+
...winnerPredicates({
|
|
500
|
+
params,
|
|
501
|
+
alias: "i",
|
|
502
|
+
projectId,
|
|
503
|
+
projectTypes,
|
|
504
|
+
winner: data.winner,
|
|
505
|
+
}),
|
|
506
|
+
];
|
|
507
|
+
return {
|
|
508
|
+
sql:
|
|
509
|
+
`SELECT i."entityKey" AS "key", i."entityType" AS "type", ` +
|
|
510
|
+
`${activeSql("i", rangeStart)} AS "active" ` +
|
|
511
|
+
`FROM "InventoryItem" i WHERE ${where.join(" AND ")}`,
|
|
512
|
+
params: params.list(),
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/*
|
|
517
|
+
* Every live service's key and name (Infrastructure resolves placements and
|
|
518
|
+
* service labels from these).
|
|
519
|
+
*/
|
|
520
|
+
export function servicesStatement(data: {
|
|
521
|
+
projectId: string;
|
|
522
|
+
winner: TopologyWinnerScope;
|
|
523
|
+
}): TopologySqlStatement {
|
|
524
|
+
const params: TopologySqlParams = new TopologySqlParams();
|
|
525
|
+
const projectId: string = params.add(data.projectId);
|
|
526
|
+
const projectTypes: TopologyLazyParam = new TopologyLazyParam(
|
|
527
|
+
params,
|
|
528
|
+
data.winner.projectTypes,
|
|
529
|
+
);
|
|
530
|
+
const where: Array<string> = [
|
|
531
|
+
liveItemSql("i", projectId),
|
|
532
|
+
`i."entityType" = ${params.add(EntityType.Service)}`,
|
|
533
|
+
...winnerPredicates({
|
|
534
|
+
params,
|
|
535
|
+
alias: "i",
|
|
536
|
+
projectId,
|
|
537
|
+
projectTypes,
|
|
538
|
+
winner: data.winner,
|
|
539
|
+
}),
|
|
540
|
+
];
|
|
541
|
+
return {
|
|
542
|
+
sql:
|
|
543
|
+
`SELECT i."entityKey" AS "key", i."displayName" AS "name" ` +
|
|
544
|
+
`FROM "InventoryItem" i WHERE ${where.join(" AND ")} ` +
|
|
545
|
+
`ORDER BY i."entityKey" COLLATE "C" ASC`,
|
|
546
|
+
params: params.list(),
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/*
|
|
551
|
+
* Exact item counts per infrastructure type, in one pass: how many there
|
|
552
|
+
* are, how many reported in range, and the latest report over each. Decides
|
|
553
|
+
* which flat types become collections and feeds the totals.
|
|
554
|
+
*/
|
|
555
|
+
export function infrastructureTypeCountsStatement(data: {
|
|
556
|
+
projectId: string;
|
|
557
|
+
rangeStart: string;
|
|
558
|
+
infrastructureTypes: Array<string>;
|
|
559
|
+
}): TopologySqlStatement {
|
|
560
|
+
const params: TopologySqlParams = new TopologySqlParams();
|
|
561
|
+
const projectId: string = params.add(data.projectId);
|
|
562
|
+
const rangeStart: string = params.add(data.rangeStart);
|
|
563
|
+
const active: string = activeSql("i", rangeStart);
|
|
564
|
+
return {
|
|
565
|
+
sql:
|
|
566
|
+
`SELECT i."entityType" AS "type", COUNT(*)::int AS "total", ` +
|
|
567
|
+
`COUNT(*) FILTER (WHERE ${active})::int AS "active", ` +
|
|
568
|
+
`${epochMsSql(`MAX(i."lastSeenAt")`)} AS "lastSeenAt", ` +
|
|
569
|
+
`${epochMsSql(`MAX(i."lastSeenAt") FILTER (WHERE ${active})`)} AS "activeLastSeenAt" ` +
|
|
570
|
+
`FROM "InventoryItem" i ` +
|
|
571
|
+
`WHERE ${liveItemSql("i", projectId)} ` +
|
|
572
|
+
`AND i."entityType" = ANY(${params.add(data.infrastructureTypes)}::text[]) ` +
|
|
573
|
+
`GROUP BY i."entityType" ` +
|
|
574
|
+
`ORDER BY i."entityType" COLLATE "C" ASC`,
|
|
575
|
+
params: params.list(),
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/* The Infrastructure node set: live winners of the given (node) types. */
|
|
580
|
+
function infrastructureNodesCteSql(data: {
|
|
581
|
+
params: TopologySqlParams;
|
|
582
|
+
projectId: string;
|
|
583
|
+
rangeStart: string;
|
|
584
|
+
winner: TopologyWinnerScope;
|
|
585
|
+
nodeTypes: string;
|
|
586
|
+
}): string {
|
|
587
|
+
const where: Array<string> = [
|
|
588
|
+
liveItemSql("i", data.projectId),
|
|
589
|
+
`i."entityType" = ANY(${data.nodeTypes}::text[])`,
|
|
590
|
+
...winnerPredicates({
|
|
591
|
+
params: data.params,
|
|
592
|
+
alias: "i",
|
|
593
|
+
projectId: data.projectId,
|
|
594
|
+
projectTypes: new TopologyLazyParam(
|
|
595
|
+
data.params,
|
|
596
|
+
data.winner.projectTypes,
|
|
597
|
+
),
|
|
598
|
+
winner: data.winner,
|
|
599
|
+
}),
|
|
600
|
+
];
|
|
601
|
+
return (
|
|
602
|
+
`nodes AS MATERIALIZED (` +
|
|
603
|
+
`SELECT i."entityKey" AS "key", i."entityType" AS "type", ` +
|
|
604
|
+
`i."displayName" AS "name", i."source" AS "source", ` +
|
|
605
|
+
`i."lastSeenAt" AS "lastSeenAt", ` +
|
|
606
|
+
`${activeSql("i", data.rangeStart)} AS "active" ` +
|
|
607
|
+
`FROM "InventoryItem" i WHERE ${where.join(" AND ")})`
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/*
|
|
612
|
+
* One row per infrastructure resource with its container chosen here, using
|
|
613
|
+
* the Dashboard's nesting rules over EVERY in-range relationship:
|
|
614
|
+
*
|
|
615
|
+
* candidates: C -> P where C's type nests, the relationship type ranks,
|
|
616
|
+
* P is another node and P's type is a container.
|
|
617
|
+
* rank: relationship priority DESC, container specificity DESC,
|
|
618
|
+
* P's key (code-point order, COLLATE "C") ASC.
|
|
619
|
+
*
|
|
620
|
+
* `parent` is the best candidate over all nodes; `activeParent` the best
|
|
621
|
+
* over active nodes, which the browser falls back to when inactive resources
|
|
622
|
+
* are hidden and `parent` did not report in range. Taking the argmax over a
|
|
623
|
+
* subset that contains the global argmax gives the global argmax, so the
|
|
624
|
+
* browser builds exactly the tree all relationships would give it.
|
|
625
|
+
*/
|
|
626
|
+
export function infrastructureNodesStatement(data: {
|
|
627
|
+
projectId: string;
|
|
628
|
+
rangeStart: string;
|
|
629
|
+
winner: TopologyWinnerScope;
|
|
630
|
+
nodeTypes: Array<string>;
|
|
631
|
+
mode: TopologyStatementMode;
|
|
632
|
+
limit: number;
|
|
633
|
+
}): TopologySqlStatement {
|
|
634
|
+
const params: TopologySqlParams = new TopologySqlParams();
|
|
635
|
+
const projectId: string = params.add(data.projectId);
|
|
636
|
+
const rangeStart: string = params.add(data.rangeStart);
|
|
637
|
+
const nodeTypes: string = params.add(data.nodeTypes);
|
|
638
|
+
const nodesCte: string = infrastructureNodesCteSql({
|
|
639
|
+
params,
|
|
640
|
+
projectId,
|
|
641
|
+
rangeStart,
|
|
642
|
+
winner: data.winner,
|
|
643
|
+
nodeTypes,
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
if (data.mode === "count") {
|
|
647
|
+
return {
|
|
648
|
+
sql:
|
|
649
|
+
`WITH ${nodesCte} ` +
|
|
650
|
+
`SELECT COUNT(*)::int AS "total", ` +
|
|
651
|
+
`COUNT(*) FILTER (WHERE n."active")::int AS "active" FROM nodes n`,
|
|
652
|
+
params: params.list(),
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const relationshipTypes: string = params.add(NESTING_RELATIONSHIP_RANKS.keys);
|
|
657
|
+
const relationshipPriorities: string = params.add(
|
|
658
|
+
NESTING_RELATIONSHIP_RANKS.ranks,
|
|
659
|
+
);
|
|
660
|
+
const containerTypes: string = params.add(CONTAINER_SPECIFICITY_RANKS.keys);
|
|
661
|
+
const containerSpecificities: string = params.add(
|
|
662
|
+
CONTAINER_SPECIFICITY_RANKS.ranks,
|
|
663
|
+
);
|
|
664
|
+
const nestableTypes: string = params.add(NESTABLE_CHILD_TYPE_LIST);
|
|
665
|
+
/*
|
|
666
|
+
* Keys sort with COLLATE "C" (code-point order) here for two reasons: the
|
|
667
|
+
* Dashboard breaks the same tie with a code-point comparator, and a byte
|
|
668
|
+
* comparison is an order of magnitude cheaper than the database collation
|
|
669
|
+
* over a few hundred thousand candidates.
|
|
670
|
+
*/
|
|
671
|
+
const ranking: string =
|
|
672
|
+
`ORDER BY c."child" COLLATE "C", c."priority" DESC, c."specificity" DESC, ` +
|
|
673
|
+
`c."parent" COLLATE "C" ASC`;
|
|
674
|
+
|
|
675
|
+
return {
|
|
676
|
+
sql:
|
|
677
|
+
`WITH ${nodesCte}, ` +
|
|
678
|
+
`candidates AS MATERIALIZED (` +
|
|
679
|
+
`SELECT r."fromEntityKey" AS "child", r."toEntityKey" AS "parent", ` +
|
|
680
|
+
`r."relationshipType" AS "via", ` +
|
|
681
|
+
`(${relationshipPriorities}::int[])[array_position(${relationshipTypes}::text[], r."relationshipType"::text)] AS "priority", ` +
|
|
682
|
+
`(${containerSpecificities}::int[])[array_position(${containerTypes}::text[], p."type"::text)] AS "specificity", ` +
|
|
683
|
+
`p."active" AS "parentActive" ` +
|
|
684
|
+
`FROM "InventoryItemRelationship" r ` +
|
|
685
|
+
`JOIN nodes child ON child."key" = r."fromEntityKey" ` +
|
|
686
|
+
`JOIN nodes p ON p."key" = r."toEntityKey" ` +
|
|
687
|
+
`WHERE ${inRangeRelationshipSql("r", projectId, rangeStart)} ` +
|
|
688
|
+
`AND r."relationshipType" = ANY(${relationshipTypes}::text[]) ` +
|
|
689
|
+
`AND r."fromEntityKey" <> r."toEntityKey" ` +
|
|
690
|
+
`AND child."type" = ANY(${nestableTypes}::text[]) ` +
|
|
691
|
+
`AND p."type" = ANY(${containerTypes}::text[])), ` +
|
|
692
|
+
`best AS (SELECT DISTINCT ON (c."child" COLLATE "C") c."child", c."parent", c."via", c."parentActive" ` +
|
|
693
|
+
`FROM candidates c ${ranking}), ` +
|
|
694
|
+
`best_active AS (SELECT DISTINCT ON (c."child" COLLATE "C") c."child", c."parent", c."via" ` +
|
|
695
|
+
`FROM candidates c WHERE c."parentActive" ${ranking}) ` +
|
|
696
|
+
`SELECT n."key" AS "key", n."type" AS "type", n."name" AS "name", ` +
|
|
697
|
+
`n."source" AS "source", ${epochMsSql(`n."lastSeenAt"`)} AS "lastSeenAt", ` +
|
|
698
|
+
`n."active" AS "active", ` +
|
|
699
|
+
`b."parent" AS "parent", b."via" AS "parentVia", ` +
|
|
700
|
+
`(b."parent" IS NOT NULL AND NOT b."parentActive") AS "parentInactive", ` +
|
|
701
|
+
`ba."parent" AS "activeParent", ba."via" AS "activeParentVia" ` +
|
|
702
|
+
`FROM nodes n ` +
|
|
703
|
+
`LEFT JOIN best b ON b."child" = n."key" ` +
|
|
704
|
+
`LEFT JOIN best_active ba ON ba."child" = n."key" ` +
|
|
705
|
+
`ORDER BY n."type" COLLATE "C" ASC, n."key" COLLATE "C" ASC ` +
|
|
706
|
+
`LIMIT ${params.add(data.limit)}`,
|
|
707
|
+
params: params.list(),
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/* Filters shared by a collection's page, its total and its search count. */
|
|
712
|
+
function collectionWhereSql(data: {
|
|
713
|
+
params: TopologySqlParams;
|
|
714
|
+
projectId: string;
|
|
715
|
+
rangeStart: TopologyLazyParam;
|
|
716
|
+
entityType: string;
|
|
717
|
+
includeInactive: boolean;
|
|
718
|
+
nameTerms: Array<string>;
|
|
719
|
+
}): Array<string> {
|
|
720
|
+
const where: Array<string> = [
|
|
721
|
+
liveItemSql("i", data.projectId),
|
|
722
|
+
`i."entityType" = ${data.params.add(data.entityType)}`,
|
|
723
|
+
];
|
|
724
|
+
if (!data.includeInactive) {
|
|
725
|
+
where.push(`(${activeSql("i", data.rangeStart.get())})`);
|
|
726
|
+
}
|
|
727
|
+
where.push(...nameTermsSql("i", data.params, data.nameTerms));
|
|
728
|
+
return where;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/*
|
|
732
|
+
* One page of a collection, keyset-paged on (display name, key). The name
|
|
733
|
+
* sorts in the database's collation (what a person expects), the key with
|
|
734
|
+
* COLLATE "C" (code points); both orders are total, so a cursor never skips
|
|
735
|
+
* or repeats a row.
|
|
736
|
+
*/
|
|
737
|
+
export function collectionPageStatement(data: {
|
|
738
|
+
projectId: string;
|
|
739
|
+
rangeStart: string;
|
|
740
|
+
entityType: string;
|
|
741
|
+
includeInactive: boolean;
|
|
742
|
+
nameTerms: Array<string>;
|
|
743
|
+
cursor: TopologyCollectionCursorJSON | null;
|
|
744
|
+
mode: TopologyStatementMode;
|
|
745
|
+
limit: number;
|
|
746
|
+
}): TopologySqlStatement {
|
|
747
|
+
const params: TopologySqlParams = new TopologySqlParams();
|
|
748
|
+
const projectId: string = params.add(data.projectId);
|
|
749
|
+
const rangeStart: TopologyLazyParam = new TopologyLazyParam(
|
|
750
|
+
params,
|
|
751
|
+
data.rangeStart,
|
|
752
|
+
);
|
|
753
|
+
const where: Array<string> = collectionWhereSql({
|
|
754
|
+
params,
|
|
755
|
+
projectId,
|
|
756
|
+
rangeStart,
|
|
757
|
+
entityType: data.entityType,
|
|
758
|
+
includeInactive: data.includeInactive,
|
|
759
|
+
nameTerms: data.nameTerms,
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
if (data.mode === "count") {
|
|
763
|
+
return {
|
|
764
|
+
sql: `SELECT COUNT(*)::int AS "total" FROM "InventoryItem" i WHERE ${where.join(" AND ")}`,
|
|
765
|
+
params: params.list(),
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (data.cursor) {
|
|
770
|
+
const name: string = params.add(data.cursor.name);
|
|
771
|
+
const key: string = params.add(data.cursor.key);
|
|
772
|
+
where.push(
|
|
773
|
+
`(COALESCE(i."displayName", '') > ${name} ` +
|
|
774
|
+
`OR (COALESCE(i."displayName", '') = ${name} ` +
|
|
775
|
+
`AND i."entityKey" COLLATE "C" > ${key}))`,
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
return {
|
|
780
|
+
sql:
|
|
781
|
+
`SELECT ${leanEntityColumnsSql("i")} ` +
|
|
782
|
+
`FROM "InventoryItem" i WHERE ${where.join(" AND ")} ` +
|
|
783
|
+
`ORDER BY COALESCE(i."displayName", '') ASC, i."entityKey" COLLATE "C" ASC ` +
|
|
784
|
+
`LIMIT ${params.add(data.limit)}`,
|
|
785
|
+
params: params.list(),
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/* How many items of each requested collection match its search terms. */
|
|
790
|
+
export function collectionSearchStatement(data: {
|
|
791
|
+
projectId: string;
|
|
792
|
+
rangeStart: string;
|
|
793
|
+
includeInactive: boolean;
|
|
794
|
+
types: Array<{ entityType: string; nameTerms: Array<string> }>;
|
|
795
|
+
}): TopologySqlStatement {
|
|
796
|
+
const params: TopologySqlParams = new TopologySqlParams();
|
|
797
|
+
const projectId: string = params.add(data.projectId);
|
|
798
|
+
const rangeStart: TopologyLazyParam = new TopologyLazyParam(
|
|
799
|
+
params,
|
|
800
|
+
data.rangeStart,
|
|
801
|
+
);
|
|
802
|
+
const branches: Array<string> = data.types.map(
|
|
803
|
+
(type: { entityType: string; nameTerms: Array<string> }): string => {
|
|
804
|
+
const where: Array<string> = collectionWhereSql({
|
|
805
|
+
params,
|
|
806
|
+
projectId,
|
|
807
|
+
rangeStart,
|
|
808
|
+
entityType: type.entityType,
|
|
809
|
+
includeInactive: data.includeInactive,
|
|
810
|
+
nameTerms: type.nameTerms,
|
|
811
|
+
});
|
|
812
|
+
return (
|
|
813
|
+
`(SELECT ${params.add(type.entityType)}::text AS "entityType", ` +
|
|
814
|
+
`COUNT(*)::int AS "count" FROM "InventoryItem" i ` +
|
|
815
|
+
`WHERE ${where.join(" AND ")})`
|
|
816
|
+
);
|
|
817
|
+
},
|
|
818
|
+
);
|
|
819
|
+
return {
|
|
820
|
+
sql: branches.join(" UNION ALL "),
|
|
821
|
+
params: params.list(),
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/*
|
|
826
|
+
* The item behind a drawer or an inventory read. The key alone identifies it
|
|
827
|
+
* (the winning row); a known type narrows the choice when two rows share the
|
|
828
|
+
* key.
|
|
829
|
+
*/
|
|
830
|
+
export function entityStatement(data: {
|
|
831
|
+
projectId: string;
|
|
832
|
+
projectTypes: Array<string>;
|
|
833
|
+
entityKey: string;
|
|
834
|
+
entityType: string | null;
|
|
835
|
+
/*
|
|
836
|
+
* The all-time reads find archived items too; a live row still wins over
|
|
837
|
+
* an archived one that shares its key.
|
|
838
|
+
*/
|
|
839
|
+
includeArchived?: boolean | undefined;
|
|
840
|
+
}): TopologySqlStatement {
|
|
841
|
+
const params: TopologySqlParams = new TopologySqlParams();
|
|
842
|
+
const projectId: string = params.add(data.projectId);
|
|
843
|
+
const projectTypes: string = params.add(data.projectTypes);
|
|
844
|
+
return {
|
|
845
|
+
sql:
|
|
846
|
+
`SELECT i."_id"::text AS "id", ${leanEntityColumnsSql("i")}, ` +
|
|
847
|
+
`${epochMsSql(`i."firstSeenAt"`)} AS "firstSeenAt", ` +
|
|
848
|
+
`i."resourceType" AS "resourceType", i."resourceId"::text AS "resourceId", ` +
|
|
849
|
+
`i."identifyingAttributes" AS "identifyingAttributes", ` +
|
|
850
|
+
`i."descriptiveAttributes" AS "descriptiveAttributes" ` +
|
|
851
|
+
`FROM "InventoryItem" i ` +
|
|
852
|
+
`WHERE ${data.includeArchived ? storedRowSql("i", projectId) : liveItemSql("i", projectId)} ` +
|
|
853
|
+
`AND i."entityType" = ANY(${projectTypes}::text[]) ` +
|
|
854
|
+
`AND i."entityKey" = ${params.add(data.entityKey)} ` +
|
|
855
|
+
`ORDER BY (i."entityType" = ${params.add(data.entityType)}::text) DESC NULLS LAST, ` +
|
|
856
|
+
`${data.includeArchived ? `i."isArchived" ASC, ` : ""}` +
|
|
857
|
+
`i."createdAt" ASC, i."_id" DESC ` +
|
|
858
|
+
`LIMIT 1`,
|
|
859
|
+
params: params.list(),
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/*
|
|
864
|
+
* Which relationships and items an entity's sections cover: the drawer's
|
|
865
|
+
* range over live items, or — for the inventory item page, which has no
|
|
866
|
+
* range — everything the inventory holds: every non-deleted relationship
|
|
867
|
+
* however long ago it was seen, with archived items as known other ends.
|
|
868
|
+
*/
|
|
869
|
+
export type TopologyEntityScope =
|
|
870
|
+
| { kind: "range"; rangeStart: string }
|
|
871
|
+
| { kind: "allTime" };
|
|
872
|
+
|
|
873
|
+
export type TopologyEntitySectionWindow =
|
|
874
|
+
| {
|
|
875
|
+
/* The drawer's first view: the top rows of every section. */
|
|
876
|
+
kind: "top";
|
|
877
|
+
dependencyRows: number;
|
|
878
|
+
otherRows: number;
|
|
879
|
+
}
|
|
880
|
+
| {
|
|
881
|
+
/* "Show more": one page of one section. */
|
|
882
|
+
kind: "page";
|
|
883
|
+
section: TopologyConnectionSection;
|
|
884
|
+
offset: number;
|
|
885
|
+
limit: number;
|
|
886
|
+
};
|
|
887
|
+
|
|
888
|
+
/*
|
|
889
|
+
* An entity's relationships in `scope`, classified into the drawer's four
|
|
890
|
+
* sections, with exact per-section totals and the requested rows.
|
|
891
|
+
*
|
|
892
|
+
* At most `scanLimit` relationships are read — outbound first, then inbound,
|
|
893
|
+
* each newest first — so a cluster with hundreds of thousands of pods in
|
|
894
|
+
* range answers with lower bounds instead of timing out. A self-loop is read
|
|
895
|
+
* once, as outbound. The other end is looked up among live items (the key's
|
|
896
|
+
* winning row); a relationship to anything else stays in its section as an
|
|
897
|
+
* unknown row, and is counted in `unknownTotal`.
|
|
898
|
+
*
|
|
899
|
+
* All time, the other end is looked up among archived items too (a live row
|
|
900
|
+
* still wins a shared key) and each row carries its `otherId`. A relationship
|
|
901
|
+
* that never reported a lastSeenAt sorts first under DESC, so the scan limit
|
|
902
|
+
* drops the oldest discovered edges before it drops one of those.
|
|
903
|
+
*
|
|
904
|
+
* Returns one row: `scanned` (relationships read), `sections` (JSON array of
|
|
905
|
+
* {section, total, unknownTotal}) and `rows` (JSON array, section then rank).
|
|
906
|
+
*/
|
|
907
|
+
export function entitySectionsStatement(data: {
|
|
908
|
+
projectId: string;
|
|
909
|
+
scope: TopologyEntityScope;
|
|
910
|
+
projectTypes: Array<string>;
|
|
911
|
+
entityKey: string;
|
|
912
|
+
isService: boolean;
|
|
913
|
+
scanLimit: number;
|
|
914
|
+
window: TopologyEntitySectionWindow;
|
|
915
|
+
}): TopologySqlStatement {
|
|
916
|
+
const params: TopologySqlParams = new TopologySqlParams();
|
|
917
|
+
const projectId: string = params.add(data.projectId);
|
|
918
|
+
const allTime: boolean = data.scope.kind === "allTime";
|
|
919
|
+
const relationshipScope: string =
|
|
920
|
+
data.scope.kind === "range"
|
|
921
|
+
? inRangeRelationshipSql(
|
|
922
|
+
"r",
|
|
923
|
+
projectId,
|
|
924
|
+
params.add(data.scope.rangeStart),
|
|
925
|
+
)
|
|
926
|
+
: storedRowSql("r", projectId);
|
|
927
|
+
const projectTypes: string = params.add(data.projectTypes);
|
|
928
|
+
const entityKey: string = params.add(data.entityKey);
|
|
929
|
+
const scanLimit: string = params.add(data.scanLimit);
|
|
930
|
+
const dependsOn: string = params.add(EntityRelationshipType.DependsOn);
|
|
931
|
+
const isService: string = params.add(data.isService);
|
|
932
|
+
const placementTypes: string = params.add(PLACEMENT_RELATIONSHIP_TYPE_LIST);
|
|
933
|
+
|
|
934
|
+
const scanColumns: (direction: "out" | "in", other: string) => string = (
|
|
935
|
+
direction: "out" | "in",
|
|
936
|
+
other: string,
|
|
937
|
+
): string => {
|
|
938
|
+
return (
|
|
939
|
+
`r."relationshipType" AS "relationshipType", '${direction}'::text AS "direction", ` +
|
|
940
|
+
`r.${other} AS "otherKey", r."callCount" AS "callCount", ` +
|
|
941
|
+
`r."errorCount" AS "errorCount", r."avgDurationMs" AS "avgDurationMs", ` +
|
|
942
|
+
`r."lastSeenAt" AS "lastSeenAt"`
|
|
943
|
+
);
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
let rowWindow: string;
|
|
947
|
+
if (data.window.kind === "top") {
|
|
948
|
+
rowWindow =
|
|
949
|
+
`r."rank" <= CASE WHEN r."section" IN ('calls', 'calledBy') ` +
|
|
950
|
+
`THEN ${params.add(data.window.dependencyRows)}::int ` +
|
|
951
|
+
`ELSE ${params.add(data.window.otherRows)}::int END`;
|
|
952
|
+
} else {
|
|
953
|
+
const offset: string = params.add(data.window.offset);
|
|
954
|
+
rowWindow =
|
|
955
|
+
`r."section" = ${params.add(data.window.section)}::text ` +
|
|
956
|
+
`AND r."rank" > ${offset}::int ` +
|
|
957
|
+
`AND r."rank" <= ${offset}::int + ${params.add(data.window.limit)}::int`;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
return {
|
|
961
|
+
sql:
|
|
962
|
+
`WITH scan_out AS MATERIALIZED (` +
|
|
963
|
+
`SELECT ${scanColumns("out", `"toEntityKey"`)} ` +
|
|
964
|
+
`FROM "InventoryItemRelationship" r ` +
|
|
965
|
+
`WHERE ${relationshipScope} ` +
|
|
966
|
+
`AND r."fromEntityKey" = ${entityKey} ` +
|
|
967
|
+
`ORDER BY r."lastSeenAt" DESC, r."_id" ASC LIMIT ${scanLimit}::int), ` +
|
|
968
|
+
`scan_in AS MATERIALIZED (` +
|
|
969
|
+
`SELECT ${scanColumns("in", `"fromEntityKey"`)} ` +
|
|
970
|
+
`FROM "InventoryItemRelationship" r ` +
|
|
971
|
+
`WHERE ${relationshipScope} ` +
|
|
972
|
+
`AND r."toEntityKey" = ${entityKey} AND r."fromEntityKey" <> ${entityKey} ` +
|
|
973
|
+
`ORDER BY r."lastSeenAt" DESC, r."_id" ASC ` +
|
|
974
|
+
`LIMIT GREATEST(0, ${scanLimit}::int - (SELECT COUNT(*)::int FROM scan_out))), ` +
|
|
975
|
+
`scan AS (SELECT * FROM scan_out UNION ALL SELECT * FROM scan_in), ` +
|
|
976
|
+
`others AS MATERIALIZED (` +
|
|
977
|
+
`SELECT DISTINCT ON (i."entityKey") i."entityKey" AS "key", ` +
|
|
978
|
+
`${allTime ? `i."_id"::text AS "id", ` : ""}` +
|
|
979
|
+
`i."entityType" AS "type", i."displayName" AS "name" ` +
|
|
980
|
+
`FROM "InventoryItem" i ` +
|
|
981
|
+
`WHERE ${allTime ? storedRowSql("i", projectId) : liveItemSql("i", projectId)} ` +
|
|
982
|
+
`AND i."entityType" = ANY(${projectTypes}::text[]) ` +
|
|
983
|
+
`AND i."entityKey" IN (SELECT s."otherKey" FROM scan s) ` +
|
|
984
|
+
`ORDER BY i."entityKey", ${allTime ? `i."isArchived" ASC, ` : ""}` +
|
|
985
|
+
`i."createdAt" ASC, i."_id" DESC), ` +
|
|
986
|
+
`classified AS MATERIALIZED (` +
|
|
987
|
+
`SELECT s.*, (o."key" IS NOT NULL) AS "otherKnown", ` +
|
|
988
|
+
`${allTime ? `o."id" AS "otherId", ` : ""}` +
|
|
989
|
+
`o."type" AS "otherType", o."name" AS "otherName", ` +
|
|
990
|
+
`CASE WHEN s."relationshipType" = ${dependsOn} AND s."direction" = 'out' THEN 'calls' ` +
|
|
991
|
+
`WHEN s."relationshipType" = ${dependsOn} THEN 'calledBy' ` +
|
|
992
|
+
`WHEN s."direction" = 'out' AND ${isService}::boolean ` +
|
|
993
|
+
`AND s."relationshipType" = ANY(${placementTypes}::text[]) THEN 'runsOn' ` +
|
|
994
|
+
`ELSE 'related' END AS "section", ` +
|
|
995
|
+
`COALESCE(NULLIF(o."name", ''), CASE WHEN o."key" IS NOT NULL ` +
|
|
996
|
+
`THEN '${UNNAMED_RESOURCE_LABEL}' ELSE '${UNDISCOVERED_RESOURCE_LABEL}' END) AS "label" ` +
|
|
997
|
+
`FROM scan s LEFT JOIN others o ON o."key" = s."otherKey"), ` +
|
|
998
|
+
`ranked AS (` +
|
|
999
|
+
`SELECT c.*, ROW_NUMBER() OVER (PARTITION BY c."section" ORDER BY ` +
|
|
1000
|
+
`CASE WHEN c."section" IN ('calls', 'calledBy') THEN c."callCount" END DESC NULLS LAST, ` +
|
|
1001
|
+
`CASE WHEN c."section" IN ('runsOn', 'related') THEN c."otherKnown" END DESC NULLS LAST, ` +
|
|
1002
|
+
`c."label" ASC, c."otherKey" COLLATE "C" ASC, ` +
|
|
1003
|
+
`c."relationshipType" COLLATE "C" ASC, c."direction" ASC) AS "rank" ` +
|
|
1004
|
+
`FROM classified c) ` +
|
|
1005
|
+
`SELECT (SELECT COUNT(*)::int FROM scan) AS "scanned", ` +
|
|
1006
|
+
`(SELECT COALESCE(json_agg(json_build_object(` +
|
|
1007
|
+
`'section', t."section", 'total', t."total", 'unknownTotal', t."unknownTotal")), '[]'::json) ` +
|
|
1008
|
+
`FROM (SELECT c."section" AS "section", COUNT(*)::int AS "total", ` +
|
|
1009
|
+
`COUNT(*) FILTER (WHERE NOT c."otherKnown")::int AS "unknownTotal" ` +
|
|
1010
|
+
`FROM classified c GROUP BY c."section") t) AS "sections", ` +
|
|
1011
|
+
`(SELECT COALESCE(json_agg(json_build_object(` +
|
|
1012
|
+
`'section', r."section", 'relationshipType', r."relationshipType", ` +
|
|
1013
|
+
`'direction', r."direction", 'otherKey', r."otherKey", ` +
|
|
1014
|
+
`'otherKnown', r."otherKnown", ` +
|
|
1015
|
+
`${allTime ? `'otherId', r."otherId", ` : ""}` +
|
|
1016
|
+
`'otherName', r."otherName", ` +
|
|
1017
|
+
`'otherType', r."otherType", 'callCount', r."callCount", ` +
|
|
1018
|
+
`'errorCount', r."errorCount", 'avgDurationMs', r."avgDurationMs", ` +
|
|
1019
|
+
`'lastSeenAt', ${epochMsSql(`r."lastSeenAt"`)}) ` +
|
|
1020
|
+
`ORDER BY r."section", r."rank"), '[]'::json) ` +
|
|
1021
|
+
`FROM ranked r WHERE ${rowWindow}) AS "rows"`,
|
|
1022
|
+
params: params.list(),
|
|
1023
|
+
};
|
|
1024
|
+
}
|