@oneuptime/common 12.0.33 → 12.0.34
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/AnalyticsModels/RumSession.ts +117 -1
- package/Models/AnalyticsModels/RumSessionChunk.ts +13 -0
- package/Models/DatabaseModels/AlertCustomField.ts +77 -3
- package/Models/DatabaseModels/ApiKeyPermission.ts +3 -27
- package/Models/DatabaseModels/IncidentCustomField.ts +77 -3
- package/Models/DatabaseModels/InventoryItemCustomField.ts +77 -3
- package/Models/DatabaseModels/MonitorCustomField.ts +77 -3
- package/Models/DatabaseModels/OnCallDutyPolicyCustomField.ts +77 -3
- package/Models/DatabaseModels/RumApplication.ts +6 -6
- package/Models/DatabaseModels/ScheduledMaintenanceCustomField.ts +77 -3
- package/Models/DatabaseModels/StatusPageCustomField.ts +77 -3
- package/Models/DatabaseModels/TeamCustomField.ts +77 -3
- package/Models/DatabaseModels/TeamMember.ts +0 -9
- package/Models/DatabaseModels/TeamMemberCustomField.ts +77 -3
- package/Models/DatabaseModels/TeamPermission.ts +3 -24
- package/Models/DatabaseModels/TelemetryIngestionKey.ts +260 -0
- package/Models/DatabaseModels/UserTelegram.ts +0 -4
- package/Scripts/benchmark-fanin-capacity.js +181 -0
- package/Server/API/TelemetryAPI.ts +1284 -164
- package/Server/API/UserNotificationSettingAPI.ts +55 -0
- package/Server/API/UserTelegramAPI.ts +48 -4
- package/Server/EnvironmentConfig.ts +35 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791300000000-AddTelemetryIngestionKeyType.ts +91 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791400000000-SessionReplayRecordEverySessionByDefault.ts +67 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791500000000-WidenCustomFieldDropdownOptions.ts +139 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791600000000-AddCustomFieldValueMapping.ts +121 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Infrastructure/QueueWorker.ts +20 -14
- package/Server/Infrastructure/Semaphore.ts +2 -0
- package/Server/Middleware/TelemetryIngest.ts +453 -7
- package/Server/Services/AccessTokenService.ts +1 -0
- package/Server/Services/AlertCustomFieldService.ts +98 -0
- package/Server/Services/AlertService.ts +37 -0
- package/Server/Services/ApiKeyPermissionService.ts +461 -29
- package/Server/Services/CustomFieldMappingService.ts +879 -0
- package/Server/Services/GlobalConfigService.ts +194 -0
- package/Server/Services/IncidentCustomFieldService.ts +98 -0
- package/Server/Services/IncidentService.ts +35 -0
- package/Server/Services/MetricService.ts +194 -0
- package/Server/Services/MonitorService.ts +32 -0
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +78 -25
- package/Server/Services/ProjectService.ts +22 -0
- package/Server/Services/RoutineEmailSettingsService.ts +73 -0
- package/Server/Services/RumSessionReplayViewService.ts +104 -24
- package/Server/Services/ScheduledMaintenanceCustomFieldService.ts +98 -0
- package/Server/Services/ScheduledMaintenanceService.ts +32 -0
- package/Server/Services/TeamMemberService.ts +78 -0
- package/Server/Services/TeamPermissionService.ts +235 -3
- package/Server/Services/TelemetryIngestionKeyService.ts +722 -19
- package/Server/Services/UserNotificationSettingService.ts +34 -6
- package/Server/Services/UserTelegramService.ts +326 -5
- package/Server/Types/Database/QueryHelper.ts +2 -0
- package/Server/Utils/APIKey/AccessPermission.ts +5 -2
- package/Server/Utils/CustomField/CustomFieldDefinitionMappingHooks.ts +54 -0
- package/Server/Utils/CustomField/CustomFieldMappingRegistry.ts +415 -0
- package/Server/Utils/CustomField/CustomFieldMappingValidator.ts +335 -0
- package/Server/Utils/DataSource/EgressGuard.ts +177 -8
- package/Server/Utils/EmailRollup/EmailRollupConstants.ts +38 -5
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +57 -4
- package/Server/Utils/FrontendEnvironment.ts +40 -0
- package/Server/Utils/LogRedaction.ts +20 -0
- package/Server/Utils/Logger.ts +11 -0
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +338 -77
- package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +30 -4
- package/Server/Utils/Monitor/MonitorAlert.ts +29 -6
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +110 -15
- package/Server/Utils/Monitor/MonitorIncident.ts +22 -6
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +29 -0
- package/Server/Utils/Monitor/SeriesContextEnricher.ts +304 -0
- package/Server/Utils/SSRFProtection.ts +217 -10
- package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +239 -138
- package/Server/Utils/SessionReplay/SessionReplayHealthCounters.ts +305 -0
- package/Server/Utils/SessionReplay/SessionReplayReadService.ts +1430 -289
- package/Server/Utils/StartServer.ts +2 -17
- package/Server/Utils/TelegramVerificationToken.ts +185 -0
- package/Server/Utils/Telemetry/PinServiceName.ts +197 -0
- package/Server/Utils/Telemetry/TelemetryFanInWriter.ts +38 -16
- package/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.ts +92 -0
- package/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.ts +217 -0
- package/Server/Utils/VM/VMAPI.ts +1 -0
- package/Server/Utils/VM/VMRunner.ts +975 -95
- package/Tests/App/AdminDashboard/AdminHeaderSmallScreens.test.tsx +202 -0
- package/Tests/App/Dashboard/DashboardHeaderSmallScreens.test.tsx +404 -0
- package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +468 -0
- package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +399 -0
- package/Tests/App/StatusPage/StatusPageLastUpdated.test.tsx +263 -0
- package/Tests/App/StatusPage/StatusPageOverviewLiveAndSearch.test.tsx +753 -0
- package/Tests/App/StatusPage/StatusPageResourceSearchBox.test.tsx +224 -0
- package/Tests/Models/AnalyticsModels/RumSessionReplayColumns.test.ts +262 -0
- package/Tests/Models/CustomFieldMappingColumns.test.ts +166 -0
- package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +20 -6
- package/Tests/ResponsiveVisibility.test.ts +115 -0
- package/Tests/ResponsiveVisibility.ts +183 -0
- package/Tests/Server/API/SessionReplayAPI.test.ts +1940 -216
- package/Tests/Server/API/UserNotificationSettingAPI.test.ts +186 -0
- package/Tests/Server/API/UserTelegramAPISecurity.test.ts +231 -0
- package/Tests/Server/EnvironmentConfigFrontendSecurity.test.ts +234 -0
- package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +202 -0
- package/Tests/Server/Infrastructure/Postgres/InventoryItemArchiveMigration.test.ts +41 -0
- package/Tests/Server/Infrastructure/Postgres/SessionReplayRecordEverySessionByDefaultMigration.test.ts +169 -0
- package/Tests/Server/Infrastructure/QueueWorkerTimeout.test.ts +210 -0
- package/Tests/Server/Infrastructure/TelemetryExporterDeploymentConfig.test.ts +130 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -1
- package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +1163 -0
- package/Tests/Server/Middleware/TelemetryIngestTokenLog.test.ts +5 -3
- package/Tests/Server/Services/AddTelemetryIngestionKeyTypeMigration.test.ts +555 -0
- package/Tests/Server/Services/ApiKeyPermissionSecurity.test.ts +678 -0
- package/Tests/Server/Services/ApiKeyPermissionService.test.ts +276 -28
- package/Tests/Server/Services/CustomFieldDropdownOptionsColumnWidth.test.ts +431 -0
- package/Tests/Server/Services/CustomFieldMappingService.test.ts +850 -0
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +32 -0
- package/Tests/Server/Services/GlobalConfigService.test.ts +414 -1
- package/Tests/Server/Services/MetricRawEntityKeyPrune.test.ts +592 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +226 -1
- package/Tests/Server/Services/RoutineEmailSettingsPostgres.test.ts +329 -0
- package/Tests/Server/Services/RumSessionReplayViewService.test.ts +267 -0
- package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +9 -0
- package/Tests/Server/Services/TeamMemberInviteRegistrationToken.test.ts +9 -0
- package/Tests/Server/Services/TeamPrivilegeEscalation.test.ts +834 -0
- package/Tests/Server/Services/TelemetryIngestionKeyPolicyResolution.test.ts +814 -0
- package/Tests/Server/Services/TelemetryIngestionKeyValidation.test.ts +998 -0
- package/Tests/Server/Services/UserNotificationSettingRollupRouting.test.ts +141 -0
- package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +18 -10
- package/Tests/Server/Services/UserTelegramVerificationSecurity.test.ts +688 -0
- package/Tests/Server/Utils/AI/SRE/Insights/FixRouting.test.ts +497 -0
- package/Tests/Server/Utils/AI/Toolbox/Serializer.test.ts +383 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +7 -3
- package/Tests/Server/Utils/AnalyticsDatabase/ClusterConfig.test.ts +315 -0
- package/Tests/Server/Utils/CustomField/CustomFieldMappingValidator.test.ts +437 -0
- package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +300 -11
- package/Tests/Server/Utils/EmailRollup/EmailRollupBurstWindow.test.ts +83 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerPreferences.test.ts +362 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupTestHarness.ts +77 -1
- package/Tests/Server/Utils/FrontendEnvironment.test.ts +190 -0
- package/Tests/Server/Utils/LogRedaction.test.ts +26 -0
- package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +23 -9
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +584 -0
- package/Tests/Server/Utils/Monitor/Criteria/IncomingRequestHeaderCriteria.test.ts +236 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +437 -0
- package/Tests/Server/Utils/Monitor/MonitorTemplateUtilSeriesContext.test.ts +176 -0
- package/Tests/Server/Utils/Monitor/SeriesContextEnricher.test.ts +356 -0
- package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +595 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +198 -1
- package/Tests/Server/Utils/SessionReplay/SessionReplayHealthCounters.test.ts +453 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayReadServiceQueries.test.ts +1378 -0
- package/Tests/Server/Utils/SessionReplayOriginAllowListRefactor.test.ts +420 -0
- package/Tests/Server/Utils/TelegramVerificationToken.test.ts +253 -0
- package/Tests/Server/Utils/Telemetry/PinServiceName.test.ts +666 -0
- package/Tests/Server/Utils/Telemetry/TelemetryFanInWriterCapacity.test.ts +458 -0
- package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +424 -0
- package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.test.ts +528 -0
- package/Tests/Server/Utils/TelemetryExporterEnvironment.test.ts +56 -0
- package/Tests/Server/Utils/VM/VMRunnerHostBridgeLatency.test.ts +74 -0
- package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +100 -7
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +1176 -6
- package/Tests/Types/CustomField/CustomFieldMappingCatalog.test.ts +220 -0
- package/Tests/Types/CustomField/CustomFieldValueMapping.test.ts +348 -0
- package/Tests/Types/JSONFunctions.test.ts +260 -0
- package/Tests/Types/Monitor/CephAlertTemplates.test.ts +422 -53
- package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +495 -15
- package/Tests/Types/Monitor/DockerSwarmAlertTemplates.test.ts +191 -45
- package/Tests/Types/Monitor/HostAlertTemplates.test.ts +370 -42
- package/Tests/Types/Monitor/IotAlertTemplates.test.ts +375 -20
- package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +636 -42
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +189 -0
- package/Tests/Types/Monitor/KubernetesTemplateGroupByKeys.test.ts +95 -14
- package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +190 -21
- package/Tests/Types/Monitor/ProxmoxAlertTemplates.test.ts +352 -22
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationAlertDebuggability.test.ts +363 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +93 -18
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +259 -0
- package/Tests/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.test.ts +476 -0
- package/Tests/Types/Monitor/RumAlertTemplates.test.ts +136 -0
- package/Tests/Types/Monitor/SeriesContext/SeriesDebugHints.test.ts +661 -0
- package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +507 -0
- package/Tests/Types/Monitor/ServiceAlertTemplates.test.ts +270 -0
- package/Tests/Types/Monitor/TemplateGroupByKeys.test.ts +38 -10
- package/Tests/Types/Monitor/Utils/RecommendationCriteriaAssertions.ts +102 -0
- package/Tests/Types/NotificationSetting/RoutineEmailEvents.test.ts +47 -0
- package/Tests/Types/Rum/SessionReplayApiContracts.test.ts +608 -0
- package/Tests/Types/Rum/SessionReplayCustomEvents.test.ts +434 -0
- package/Tests/Types/WebsiteRequest.test.ts +285 -3
- package/Tests/UI/Components/Charts/ChartBucketIdentity.test.ts +16 -3
- package/Tests/UI/Components/Charts/ChartTrailingBucketGap.test.ts +691 -0
- package/Tests/UI/Components/ComponentsModal.test.tsx +12 -4
- package/Tests/UI/Components/ComponentsModalUsability.test.tsx +518 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetailMapping.test.tsx +324 -0
- package/Tests/UI/Components/CustomFields/MapFromCustomFieldInput.test.tsx +176 -0
- package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +484 -0
- package/Tests/UI/Components/HeaderRightRail.test.tsx +157 -0
- package/Tests/UI/Components/IconDropdownItem.test.tsx +102 -0
- package/Tests/UI/Components/JSONTablePrototypePollution.test.tsx +117 -0
- package/Tests/UI/Components/KeyboardShortcutsModal.test.tsx +214 -0
- package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +561 -0
- package/Tests/UI/Components/MonitorGraphs/UptimeBarDayModal.test.tsx +302 -0
- package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +28 -12
- package/Tests/UI/Components/ShortcutDialogGuard.test.tsx +74 -0
- package/Tests/UI/Components/StatusPage/ResourceGroupSectionAutoExpand.test.tsx +248 -0
- package/Tests/UI/Components/TableCellWrapping.test.tsx +625 -0
- package/Tests/UI/Components/TableLoadingStates.test.tsx +205 -0
- package/Tests/UI/Components/Workflow/NodePlacement.test.ts +148 -0
- package/Tests/UI/Components/Workflow/Workflow.test.tsx +981 -0
- package/Tests/UI/ConfigBrowserTelemetry.test.ts +70 -0
- package/Tests/UI/Rum/ChunkLoader.test.ts +1036 -2
- package/Tests/UI/Rum/FidelityNotices.test.ts +187 -0
- package/Tests/UI/Rum/InactivityMap.test.ts +341 -0
- package/Tests/UI/Rum/PrivacySummaryCard.test.tsx +259 -0
- package/Tests/UI/Rum/RecordingHealthCard.test.tsx +855 -0
- package/Tests/UI/Rum/RecordingHealthStrip.test.tsx +594 -0
- package/Tests/UI/Rum/ReplayCard.test.tsx +451 -0
- package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +536 -0
- package/Tests/UI/Rum/ReplayEngine.test.ts +2503 -0
- package/Tests/UI/Rum/ReplayEngineTypes.test.ts +171 -0
- package/Tests/UI/Rum/ReplayHeader.test.tsx +711 -0
- package/Tests/UI/Rum/ReplayLink.test.tsx +119 -0
- package/Tests/UI/Rum/ReplayPinControl.test.tsx +408 -0
- package/Tests/UI/Rum/ReplayPlaybackIntent.test.ts +152 -0
- package/Tests/UI/Rum/ReplayRail.test.tsx +1296 -0
- package/Tests/UI/Rum/ReplayRailDetail.test.tsx +900 -0
- package/Tests/UI/Rum/ReplayScrubber.test.tsx +763 -147
- package/Tests/UI/Rum/ReplaySignalTypes.test.ts +189 -0
- package/Tests/UI/Rum/ReplaySignals.test.ts +1579 -0
- package/Tests/UI/Rum/ReplayStage.test.tsx +396 -473
- package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +918 -0
- package/Tests/UI/Rum/ReplayTimeline.test.tsx +696 -0
- package/Tests/UI/Rum/SessionReplayEmptyState.test.tsx +499 -0
- package/Tests/UI/Rum/SessionReplaySearchBar.test.tsx +346 -0
- package/Tests/UI/Rum/SessionReplaySetupGuide.test.tsx +545 -0
- package/Tests/UI/Rum/SessionReplayTable.test.tsx +991 -0
- package/Tests/UI/Rum/TargetedCapturePanel.test.tsx +254 -0
- package/Tests/UI/Telemetry/BrowserExporterIsolation.test.ts +71 -0
- package/Tests/UI/Utils/GlobalKeyboardShortcut.test.ts +344 -0
- package/Tests/Utils/API.test.ts +308 -0
- package/Tests/Utils/HTTPResponseBodyReader.test.ts +323 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveryScanStatus.test.ts +117 -0
- package/Tests/Utils/Rum/ChunkMath.test.ts +83 -0
- package/Tests/Utils/Rum/SessionReplayHealthDiagnosis.test.ts +993 -0
- package/Tests/Utils/Rum/SessionReplayStringMap.test.ts +245 -0
- package/Tests/Utils/StatusPage/ResourceSearch.test.ts +631 -0
- package/Tests/Utils/Telemetry/OriginAllowList.test.ts +633 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtil.test.ts +461 -0
- package/Tests/Utils/ValueFormatter.test.ts +148 -0
- package/Types/CustomField/CustomFieldMappingCatalog.ts +161 -0
- package/Types/CustomField/CustomFieldMappingSourceResource.ts +19 -0
- package/Types/CustomField/CustomFieldValueMapping.ts +288 -0
- package/Types/Icon/IconProp.ts +1 -0
- package/Types/JSONFunctions.ts +189 -43
- package/Types/Monitor/CephAlertTemplates.ts +157 -195
- package/Types/Monitor/DockerAlertTemplates.ts +338 -128
- package/Types/Monitor/DockerSwarmAlertTemplates.ts +95 -112
- package/Types/Monitor/HostAlertTemplates.ts +302 -130
- package/Types/Monitor/IotAlertTemplates.ts +112 -104
- package/Types/Monitor/KubernetesAlertTemplates.ts +415 -224
- package/Types/Monitor/KubernetesMetricCatalog.ts +23 -19
- package/Types/Monitor/PodmanAlertTemplates.ts +116 -163
- package/Types/Monitor/ProxmoxAlertTemplates.ts +217 -99
- package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +98 -6
- package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +44 -7
- package/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.ts +383 -0
- package/Types/Monitor/RumAlertTemplates.ts +46 -7
- package/Types/Monitor/SeriesContext/SeriesDebugHints.ts +596 -0
- package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +554 -0
- package/Types/Monitor/ServiceAlertTemplates.ts +98 -11
- package/Types/Monitor/UptimeHistoryLabels.ts +56 -0
- package/Types/NotificationSetting/RoutineEmailEvents.ts +30 -0
- package/Types/Rum/SessionReplay.ts +209 -0
- package/Types/Rum/SessionReplayApi.ts +722 -0
- package/Types/Rum/SessionReplayCaptureTrigger.ts +21 -12
- package/Types/Rum/SessionReplayConsentMode.ts +11 -7
- package/Types/Rum/SessionReplayCustomEvents.ts +549 -0
- package/Types/Rum/SessionReplayHealth.ts +238 -0
- package/Types/Telemetry/TelemetryIngestSurface.ts +95 -0
- package/Types/Telemetry/TelemetryIngestionKeyPolicy.ts +75 -0
- package/Types/Telemetry/TelemetryIngestionKeyType.ts +29 -0
- package/Types/WebsiteRequest.ts +85 -3
- package/UI/Components/Charts/Area/AreaChart.tsx +13 -2
- package/UI/Components/Charts/Bar/BarChart.tsx +6 -2
- package/UI/Components/Charts/Line/LineChart.tsx +13 -2
- package/UI/Components/Charts/Types/XAxis/XAxis.ts +29 -0
- package/UI/Components/Charts/Utils/DataPoint.ts +22 -3
- package/UI/Components/Charts/Utils/TimeAnnotation.ts +76 -9
- package/UI/Components/Charts/Utils/XAxis.ts +217 -0
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +143 -25
- package/UI/Components/CustomFields/MapFromCustomFieldInput.tsx +216 -0
- package/UI/Components/Graphs/DayUptimeGraph.tsx +173 -8
- package/UI/Components/Graphs/UptimeBarTooltip.tsx +22 -278
- package/UI/Components/Graphs/UptimeDaySummary.tsx +327 -0
- package/UI/Components/Header/Header.tsx +25 -9
- package/UI/Components/Header/IconDropdown/IconDropdownItem.tsx +10 -1
- package/UI/Components/Header/IconDropdown/IconDropdownMenu.tsx +6 -1
- package/UI/Components/Header/ProjectPicker/ProjectPicker.tsx +6 -1
- package/UI/Components/HeaderAlert/NotificationBell/NotificationBellDropdown.tsx +6 -1
- package/UI/Components/Icon/Icon.tsx +11 -0
- package/UI/Components/JSONTable/JSONTable.tsx +2 -2
- package/UI/Components/KeyboardShortcut/KeyboardShortcutsModal.tsx +125 -0
- package/UI/Components/KeyboardShortcut/Screenshots/README.md +19 -0
- package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-command-palette.png +0 -0
- package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-dialog-dark.png +0 -0
- package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-dialog.png +0 -0
- package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-help-menu.png +0 -0
- package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-narrow.png +0 -0
- package/UI/Components/ModelTable/Column.ts +15 -0
- package/UI/Components/Monitor/SeriesDebugCommandsViewer.tsx +64 -0
- package/UI/Components/Monitor/SeriesLabelsViewer.tsx +93 -0
- package/UI/Components/MonitorGraphs/Uptime.tsx +14 -2
- package/UI/Components/MonitorGraphs/UptimeBarDayModal.tsx +63 -10
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +44 -0
- package/UI/Components/StatusPage/ResourceGroupSection.tsx +40 -0
- package/UI/Components/Table/CellClassName.ts +81 -0
- package/UI/Components/Table/TableRow.tsx +20 -14
- package/UI/Components/Table/TableSkeletonRows.tsx +9 -9
- package/UI/Components/Table/Types/Column.ts +36 -0
- package/UI/Components/Tooltip/Tooltip.tsx +11 -1
- package/UI/Components/Workflow/ComponentsModal.tsx +60 -39
- package/UI/Components/Workflow/NodePlacement.ts +57 -0
- package/UI/Components/Workflow/Workflow.tsx +47 -18
- package/UI/Config.ts +15 -33
- package/UI/Utils/GlobalKeyboardShortcut.ts +208 -0
- package/UI/Utils/Telemetry/BrowserTelemetryConfig.ts +28 -0
- package/UI/Utils/Telemetry/Telemetry.ts +10 -8
- package/Utils/API.ts +129 -3
- package/Utils/HTTPResponseBodyReader.ts +221 -0
- package/Utils/NetworkDiscovery/DiscoveryScanStatus.ts +77 -0
- package/Utils/Rum/ChunkMath.ts +106 -0
- package/Utils/Rum/SessionReplayHealth.ts +732 -0
- package/Utils/Rum/SessionReplayStringMap.ts +226 -0
- package/Utils/Schema/ModelSchema.ts +1 -0
- package/Utils/StatusPage/ResourceSearch.ts +290 -0
- package/Utils/Telemetry/OriginAllowList.ts +355 -0
- package/Utils/Uptime/DayUptimeGraphUtil.ts +205 -0
- package/Utils/ValueFormatter.ts +35 -1
- package/build/dist/Models/AnalyticsModels/RumSession.js +102 -19
- package/build/dist/Models/AnalyticsModels/RumSession.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/RumSessionChunk.js +11 -9
- package/build/dist/Models/AnalyticsModels/RumSessionChunk.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/AlertCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ApiKeyPermission.js +3 -27
- package/build/dist/Models/DatabaseModels/ApiKeyPermission.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/MonitorCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +6 -6
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/TeamCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamMember.js +0 -9
- package/build/dist/Models/DatabaseModels/TeamMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamPermission.js +3 -24
- package/build/dist/Models/DatabaseModels/TeamPermission.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryIngestionKey.js +267 -0
- package/build/dist/Models/DatabaseModels/TelemetryIngestionKey.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserTelegram.js +0 -4
- package/build/dist/Models/DatabaseModels/UserTelegram.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +769 -109
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/API/UserNotificationSettingAPI.js +35 -0
- package/build/dist/Server/API/UserNotificationSettingAPI.js.map +1 -0
- package/build/dist/Server/API/UserTelegramAPI.js +28 -6
- package/build/dist/Server/API/UserTelegramAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +29 -2
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791300000000-AddTelemetryIngestionKeyType.js +60 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791300000000-AddTelemetryIngestionKeyType.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791400000000-SessionReplayRecordEverySessionByDefault.js +53 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791400000000-SessionReplayRecordEverySessionByDefault.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791500000000-WidenCustomFieldDropdownOptions.js +82 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791500000000-WidenCustomFieldDropdownOptions.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791600000000-AddCustomFieldValueMapping.js +46 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791600000000-AddCustomFieldValueMapping.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/QueueWorker.js +19 -7
- package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
- package/build/dist/Server/Infrastructure/Semaphore.js +1 -0
- package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +315 -8
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AccessTokenService.js +1 -0
- package/build/dist/Server/Services/AccessTokenService.js.map +1 -1
- package/build/dist/Server/Services/AlertCustomFieldService.js +93 -0
- package/build/dist/Server/Services/AlertCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +34 -0
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/ApiKeyPermissionService.js +331 -28
- package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
- package/build/dist/Server/Services/CustomFieldMappingService.js +614 -0
- package/build/dist/Server/Services/CustomFieldMappingService.js.map +1 -0
- package/build/dist/Server/Services/GlobalConfigService.js +133 -0
- package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
- package/build/dist/Server/Services/IncidentCustomFieldService.js +93 -0
- package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +32 -0
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +163 -0
- package/build/dist/Server/Services/MetricService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +29 -0
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +79 -27
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +22 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/RoutineEmailSettingsService.js +69 -0
- package/build/dist/Server/Services/RoutineEmailSettingsService.js.map +1 -0
- package/build/dist/Server/Services/RumSessionReplayViewService.js +86 -24
- package/build/dist/Server/Services/RumSessionReplayViewService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceCustomFieldService.js +93 -0
- package/build/dist/Server/Services/ScheduledMaintenanceCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +29 -0
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +62 -2
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/TeamPermissionService.js +166 -3
- package/build/dist/Server/Services/TeamPermissionService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryIngestionKeyService.js +562 -18
- package/build/dist/Server/Services/TelemetryIngestionKeyService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +26 -6
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserTelegramService.js +257 -4
- package/build/dist/Server/Services/UserTelegramService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +2 -2
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/CustomField/CustomFieldDefinitionMappingHooks.js +27 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldDefinitionMappingHooks.js.map +1 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldMappingRegistry.js +226 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldMappingRegistry.js.map +1 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldMappingValidator.js +189 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldMappingValidator.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/EgressGuard.js +121 -7
- package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupConstants.js +38 -5
- package/build/dist/Server/Utils/EmailRollup/EmailRollupConstants.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +44 -2
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
- package/build/dist/Server/Utils/FrontendEnvironment.js +33 -0
- package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -0
- package/build/dist/Server/Utils/LogRedaction.js +20 -0
- package/build/dist/Server/Utils/LogRedaction.js.map +1 -1
- package/build/dist/Server/Utils/Logger.js +10 -0
- package/build/dist/Server/Utils/Logger.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +287 -68
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +20 -4
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +29 -6
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +88 -13
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +22 -6
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +22 -0
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesContextEnricher.js +237 -0
- package/build/dist/Server/Utils/Monitor/SeriesContextEnricher.js.map +1 -0
- package/build/dist/Server/Utils/SSRFProtection.js +136 -9
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +174 -89
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayHealthCounters.js +223 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayHealthCounters.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +945 -164
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +3 -14
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/TelegramVerificationToken.js +129 -0
- package/build/dist/Server/Utils/TelegramVerificationToken.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/PinServiceName.js +163 -0
- package/build/dist/Server/Utils/Telemetry/PinServiceName.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js +28 -15
- package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js +64 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.js +171 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.js.map +1 -0
- package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMRunner.js +780 -69
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js +60 -0
- package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldMappingSourceResource.js +20 -0
- package/build/dist/Types/CustomField/CustomFieldMappingSourceResource.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldValueMapping.js +141 -0
- package/build/dist/Types/CustomField/CustomFieldValueMapping.js.map +1 -0
- package/build/dist/Types/Icon/IconProp.js +1 -0
- package/build/dist/Types/Icon/IconProp.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +118 -37
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/Monitor/CephAlertTemplates.js +130 -171
- package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/DockerAlertTemplates.js +286 -121
- package/build/dist/Types/Monitor/DockerAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/DockerSwarmAlertTemplates.js +84 -105
- package/build/dist/Types/Monitor/DockerSwarmAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/HostAlertTemplates.js +250 -122
- package/build/dist/Types/Monitor/HostAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/IotAlertTemplates.js +81 -81
- package/build/dist/Types/Monitor/IotAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +375 -215
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/KubernetesMetricCatalog.js +19 -19
- package/build/dist/Types/Monitor/KubernetesMetricCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/PodmanAlertTemplates.js +104 -153
- package/build/dist/Types/Monitor/PodmanAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +156 -91
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +67 -6
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +38 -7
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js +261 -0
- package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js.map +1 -0
- package/build/dist/Types/Monitor/RumAlertTemplates.js +46 -7
- package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/SeriesContext/SeriesDebugHints.js +422 -0
- package/build/dist/Types/Monitor/SeriesContext/SeriesDebugHints.js.map +1 -0
- package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +441 -0
- package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -0
- package/build/dist/Types/Monitor/ServiceAlertTemplates.js +79 -15
- package/build/dist/Types/Monitor/ServiceAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/UptimeHistoryLabels.js +17 -0
- package/build/dist/Types/Monitor/UptimeHistoryLabels.js.map +1 -0
- package/build/dist/Types/NotificationSetting/RoutineEmailEvents.js +29 -0
- package/build/dist/Types/NotificationSetting/RoutineEmailEvents.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplay.js +117 -0
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayApi.js +181 -0
- package/build/dist/Types/Rum/SessionReplayApi.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplayCaptureTrigger.js +21 -12
- package/build/dist/Types/Rum/SessionReplayCaptureTrigger.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayConsentMode.js +11 -7
- package/build/dist/Types/Rum/SessionReplayConsentMode.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayCustomEvents.js +172 -0
- package/build/dist/Types/Rum/SessionReplayCustomEvents.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplayHealth.js +37 -0
- package/build/dist/Types/Rum/SessionReplayHealth.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +85 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryIngestionKeyPolicy.js +15 -0
- package/build/dist/Types/Telemetry/TelemetryIngestionKeyPolicy.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryIngestionKeyType.js +30 -0
- package/build/dist/Types/Telemetry/TelemetryIngestionKeyType.js.map +1 -0
- package/build/dist/Types/WebsiteRequest.js +55 -3
- package/build/dist/Types/WebsiteRequest.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +13 -2
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +6 -2
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +13 -2
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Types/XAxis/XAxis.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -3
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/TimeAnnotation.js +40 -9
- package/build/dist/UI/Components/Charts/Utils/TimeAnnotation.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/XAxis.js +186 -0
- package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +80 -6
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/MapFromCustomFieldInput.js +118 -0
- package/build/dist/UI/Components/CustomFields/MapFromCustomFieldInput.js.map +1 -0
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +112 -7
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
- package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js +5 -162
- package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js.map +1 -1
- package/build/dist/UI/Components/Graphs/UptimeDaySummary.js +181 -0
- package/build/dist/UI/Components/Graphs/UptimeDaySummary.js.map +1 -0
- package/build/dist/UI/Components/Header/Header.js +2 -3
- package/build/dist/UI/Components/Header/Header.js.map +1 -1
- package/build/dist/UI/Components/Header/IconDropdown/IconDropdownItem.js +2 -1
- package/build/dist/UI/Components/Header/IconDropdown/IconDropdownItem.js.map +1 -1
- package/build/dist/UI/Components/Header/IconDropdown/IconDropdownMenu.js +7 -1
- package/build/dist/UI/Components/Header/IconDropdown/IconDropdownMenu.js.map +1 -1
- package/build/dist/UI/Components/Header/ProjectPicker/ProjectPicker.js +7 -1
- package/build/dist/UI/Components/Header/ProjectPicker/ProjectPicker.js.map +1 -1
- package/build/dist/UI/Components/HeaderAlert/NotificationBell/NotificationBellDropdown.js +7 -1
- package/build/dist/UI/Components/HeaderAlert/NotificationBell/NotificationBellDropdown.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +5 -0
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/JSONTable/JSONTable.js +2 -2
- package/build/dist/UI/Components/JSONTable/JSONTable.js.map +1 -1
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcutsModal.js +35 -0
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcutsModal.js.map +1 -0
- package/build/dist/UI/Components/Monitor/SeriesDebugCommandsViewer.js +34 -0
- package/build/dist/UI/Components/Monitor/SeriesDebugCommandsViewer.js.map +1 -0
- package/build/dist/UI/Components/Monitor/SeriesLabelsViewer.js +49 -0
- package/build/dist/UI/Components/Monitor/SeriesLabelsViewer.js.map +1 -0
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js +1 -1
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
- package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js +32 -6
- package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +38 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js +22 -1
- package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js.map +1 -1
- package/build/dist/UI/Components/Table/CellClassName.js +55 -0
- package/build/dist/UI/Components/Table/CellClassName.js.map +1 -0
- package/build/dist/UI/Components/Table/TableRow.js +16 -13
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/Table/TableSkeletonRows.js +9 -8
- package/build/dist/UI/Components/Table/TableSkeletonRows.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js +25 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentsModal.js +47 -30
- package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/NodePlacement.js +37 -0
- package/build/dist/UI/Components/Workflow/NodePlacement.js.map +1 -0
- package/build/dist/UI/Components/Workflow/Workflow.js +35 -18
- package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
- package/build/dist/UI/Config.js +10 -20
- package/build/dist/UI/Config.js.map +1 -1
- package/build/dist/UI/Utils/GlobalKeyboardShortcut.js +138 -0
- package/build/dist/UI/Utils/GlobalKeyboardShortcut.js.map +1 -0
- package/build/dist/UI/Utils/Telemetry/BrowserTelemetryConfig.js +14 -0
- package/build/dist/UI/Utils/Telemetry/BrowserTelemetryConfig.js.map +1 -0
- package/build/dist/UI/Utils/Telemetry/Telemetry.js +7 -5
- package/build/dist/UI/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Utils/API.js +67 -5
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/HTTPResponseBodyReader.js +157 -0
- package/build/dist/Utils/HTTPResponseBodyReader.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveryScanStatus.js +69 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveryScanStatus.js.map +1 -0
- package/build/dist/Utils/Rum/ChunkMath.js +76 -0
- package/build/dist/Utils/Rum/ChunkMath.js.map +1 -1
- package/build/dist/Utils/Rum/SessionReplayHealth.js +534 -0
- package/build/dist/Utils/Rum/SessionReplayHealth.js.map +1 -0
- package/build/dist/Utils/Rum/SessionReplayStringMap.js +165 -0
- package/build/dist/Utils/Rum/SessionReplayStringMap.js.map +1 -0
- package/build/dist/Utils/Schema/ModelSchema.js +1 -0
- package/build/dist/Utils/Schema/ModelSchema.js.map +1 -1
- package/build/dist/Utils/StatusPage/ResourceSearch.js +200 -0
- package/build/dist/Utils/StatusPage/ResourceSearch.js.map +1 -0
- package/build/dist/Utils/Telemetry/OriginAllowList.js +299 -0
- package/build/dist/Utils/Telemetry/OriginAllowList.js.map +1 -0
- package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js +147 -0
- package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js.map +1 -0
- package/build/dist/Utils/ValueFormatter.js +32 -1
- package/build/dist/Utils/ValueFormatter.js.map +1 -1
- package/package.json +1 -1
|
@@ -11,11 +11,15 @@ import { SQL } from "../AnalyticsDatabase/Statement";
|
|
|
11
11
|
import { getQuerySettings } from "../AnalyticsDatabase/QuerySettingsHelper";
|
|
12
12
|
import RumSessionService from "../../Services/RumSessionService";
|
|
13
13
|
import RumSessionChunkService from "../../Services/RumSessionChunkService";
|
|
14
|
+
import ExceptionInstanceService from "../../Services/ExceptionInstanceService";
|
|
15
|
+
import logger from "../Logger";
|
|
14
16
|
import AnalyticsTableName from "../../../Types/AnalyticsDatabase/AnalyticsTableName";
|
|
15
17
|
import TableColumnType from "../../../Types/AnalyticsDatabase/TableColumnType";
|
|
16
18
|
import Includes from "../../../Types/BaseDatabase/Includes";
|
|
19
|
+
import OneUptimeDate from "../../../Types/Date";
|
|
17
20
|
import ChunkMath from "../../../Utils/Rum/ChunkMath";
|
|
18
|
-
import { MAX_SESSION_REPLAY_CHUNKS_PER_READ, MAX_SESSION_REPLAY_READ_BYTES, } from "../../../Types/Rum/SessionReplay";
|
|
21
|
+
import { MAX_SESSION_REPLAY_CHUNKS_PER_READ, MAX_SESSION_REPLAY_READ_BYTES, SESSION_REPLAY_LIST_SEARCH_MAX_LENGTH, SESSION_REPLAY_MAX_SESSION_MS, SESSION_REPLAY_MAX_TAG_KEYS, SESSION_REPLAY_RECORDER_CAPABILITIES, SessionReplaySealedReason, } from "../../../Types/Rum/SessionReplay";
|
|
22
|
+
import { SESSION_REPLAY_SORT_BY_VALUES, } from "../../../Types/Rum/SessionReplayApi";
|
|
19
23
|
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
20
24
|
import CaptureSpan from "../Telemetry/CaptureSpan";
|
|
21
25
|
/*
|
|
@@ -47,10 +51,9 @@ import CaptureSpan from "../Telemetry/CaptureSpan";
|
|
|
47
51
|
* 3. The manifest read must never name the `payload` column, so
|
|
48
52
|
* ClickHouse never touches (and never decompresses) the only column
|
|
49
53
|
* in the system that holds a recording of a real person's screen.
|
|
50
|
-
*
|
|
51
|
-
* `length(payload)`
|
|
52
|
-
*
|
|
53
|
-
* and the only honest measure of that is the stored column itself.
|
|
54
|
+
* getChunks is the one read that names it, and it measures
|
|
55
|
+
* `length(payload)` in the same statement that ships the bytes, so
|
|
56
|
+
* the column is decompressed exactly once per page.
|
|
54
57
|
*
|
|
55
58
|
* NOTE on aliases: ClickHouse substitutes SELECT aliases into same-level
|
|
56
59
|
* unqualified WHERE references, and an aggregate alias there is an
|
|
@@ -76,6 +79,26 @@ export const DEFAULT_SESSION_REPLAY_LIST_LIMIT = 50;
|
|
|
76
79
|
export const MAX_SESSION_REPLAY_LIST_LIMIT = 200;
|
|
77
80
|
/* Sessions returned by the exception -> replay lookup. */
|
|
78
81
|
export const MAX_SESSION_REPLAY_FOR_EXCEPTION_LIMIT = 20;
|
|
82
|
+
/*
|
|
83
|
+
* Default window for the exception -> replay lookup when the caller gives
|
|
84
|
+
* none. RumSession is partitioned by day, so an unbounded lookup scans
|
|
85
|
+
* every partition the project has ever written; 30 days covers every
|
|
86
|
+
* retention tier a recording can still be played under.
|
|
87
|
+
*/
|
|
88
|
+
export const DEFAULT_SESSION_REPLAY_FOR_EXCEPTION_WINDOW_DAYS = 30;
|
|
89
|
+
/*
|
|
90
|
+
* Sessions the exception-instance side index may name. The instance table
|
|
91
|
+
* carries the session id of the page that threw, which is how a session
|
|
92
|
+
* is found BEFORE the finalizer has written its fingerprint list.
|
|
93
|
+
*/
|
|
94
|
+
const MAX_EXCEPTION_INSTANCE_SESSION_IDS = 100;
|
|
95
|
+
/*
|
|
96
|
+
* Padding around an exception's own timestamp when the caller pins the
|
|
97
|
+
* lookup to a moment: a session that contains the error started at most
|
|
98
|
+
* SESSION_REPLAY_MAX_SESSION_MS before it, and clock skew between the
|
|
99
|
+
* browser and the server is bounded far below this.
|
|
100
|
+
*/
|
|
101
|
+
export const SESSION_REPLAY_EXCEPTION_WINDOW_PADDING_MS = 5 * 60 * 1000;
|
|
79
102
|
/*
|
|
80
103
|
* Row ceiling on one manifest. A session is capped at
|
|
81
104
|
* MAX_SESSION_REPLAY_CHUNKS_PER_SESSION (480) chunks PER TAB, and a
|
|
@@ -84,6 +107,18 @@ export const MAX_SESSION_REPLAY_FOR_EXCEPTION_LIMIT = 20;
|
|
|
84
107
|
* timeline.
|
|
85
108
|
*/
|
|
86
109
|
const MAX_MANIFEST_ROWS = 4096;
|
|
110
|
+
/*
|
|
111
|
+
* How long one application's activity summary is served from memory. The
|
|
112
|
+
* health card polls every 10-60s per viewer and the summary is a small
|
|
113
|
+
* aggregate over a day of headers, so a 30s cache turns N viewers into
|
|
114
|
+
* one ClickHouse query per pod per half minute.
|
|
115
|
+
*/
|
|
116
|
+
export const SESSION_REPLAY_ACTIVITY_SUMMARY_CACHE_TTL_MS = 30 * 1000;
|
|
117
|
+
const MAX_ACTIVITY_SUMMARY_CACHE_ENTRIES = 1000;
|
|
118
|
+
/* The header attribute the ingest writes chunk 0's capability list into. */
|
|
119
|
+
export const RECORDER_CAPABILITIES_ATTRIBUTE = "recorder.capabilities";
|
|
120
|
+
/* Routes projected onto a list row; the table shows three and says "(N pages)". */
|
|
121
|
+
export const MAX_LIST_ROUTES = 5;
|
|
87
122
|
function argMaxColumn(column) {
|
|
88
123
|
return `argMax(${column}, version)`;
|
|
89
124
|
}
|
|
@@ -93,6 +128,34 @@ function argMaxNumeric(column) {
|
|
|
93
128
|
function argMaxDateTime(column) {
|
|
94
129
|
return `toFloat64(toUnixTimestamp64Milli(${argMaxColumn(column)}))`;
|
|
95
130
|
}
|
|
131
|
+
/* A Date column (retentionDate) as unix milliseconds. */
|
|
132
|
+
function argMaxDate(column) {
|
|
133
|
+
return `toFloat64(toUnixTimestamp(${argMaxColumn(column)})) * 1000`;
|
|
134
|
+
}
|
|
135
|
+
/*
|
|
136
|
+
* Duration that stays honest for a session the finalizer has not reached.
|
|
137
|
+
*
|
|
138
|
+
* The provisional header is written on chunk 0 with durationMs 0 and
|
|
139
|
+
* endTime = chunk 0's end, and it stays that way for the 10+ minutes of
|
|
140
|
+
* idleness the finalizer waits for. Reported verbatim, every live or
|
|
141
|
+
* recently finished session read "0s" in the list and a "longer than"
|
|
142
|
+
* filter hid all of them - the first thing a person testing their install
|
|
143
|
+
* sees is a session that claims to be empty. Until the finalized row
|
|
144
|
+
* exists, the span the header itself asserts (endTime - startTime) is the
|
|
145
|
+
* best lower bound there is, so the live value is the larger of the two.
|
|
146
|
+
* The finalized row's durationMs is authoritative and is used as-is.
|
|
147
|
+
*/
|
|
148
|
+
/*
|
|
149
|
+
* durationMs is Int128 on disk while the clock arithmetic is Int64; both
|
|
150
|
+
* branches are cast to Int64 (a session is capped at four hours, so the
|
|
151
|
+
* cast cannot overflow) so `if` and `greatest` see one type.
|
|
152
|
+
*/
|
|
153
|
+
const LIVE_DURATION_EXPRESSION = `toFloat64(if(${argMaxColumn("isFinalized")}, toInt64(${argMaxColumn("durationMs")}), greatest(toInt64(${argMaxColumn("durationMs")}), toUnixTimestamp64Milli(${argMaxColumn("endTime")}) - toUnixTimestamp64Milli(${argMaxColumn("startTime")}))))`;
|
|
154
|
+
/*
|
|
155
|
+
* The frustration total, shared by the hasFrustration predicate and the
|
|
156
|
+
* "frustration" sort so the two can never disagree about what counts.
|
|
157
|
+
*/
|
|
158
|
+
const FRUSTRATION_TOTAL_EXPRESSION = "(aggRageClickCount + aggDeadClickCount + aggErrorClickCount + aggRefreshRageCount)";
|
|
96
159
|
/*
|
|
97
160
|
* Aliases deliberately differ from the physical column names. See the
|
|
98
161
|
* ILLEGAL_AGGREGATION note in the file header.
|
|
@@ -100,7 +163,7 @@ function argMaxDateTime(column) {
|
|
|
100
163
|
const HEADER_AGGREGATES = [
|
|
101
164
|
{ alias: "aggStartTime", expression: argMaxDateTime("startTime") },
|
|
102
165
|
{ alias: "aggEndTime", expression: argMaxDateTime("endTime") },
|
|
103
|
-
{ alias: "aggDurationMs", expression:
|
|
166
|
+
{ alias: "aggDurationMs", expression: LIVE_DURATION_EXPRESSION },
|
|
104
167
|
{ alias: "aggIsFinalized", expression: argMaxColumn("isFinalized") },
|
|
105
168
|
{ alias: "aggSealedReason", expression: argMaxColumn("sealedReason") },
|
|
106
169
|
{ alias: "aggChunkCount", expression: argMaxNumeric("chunkCount") },
|
|
@@ -124,6 +187,12 @@ const HEADER_AGGREGATES = [
|
|
|
124
187
|
{ alias: "aggTriggerReason", expression: argMaxColumn("triggerReason") },
|
|
125
188
|
{ alias: "aggEntryUrl", expression: argMaxColumn("entryUrl") },
|
|
126
189
|
{ alias: "aggExitUrl", expression: argMaxColumn("exitUrl") },
|
|
190
|
+
/*
|
|
191
|
+
* The full routes array: the list projects the first MAX_LIST_ROUTES and
|
|
192
|
+
* the urlPrefix / search predicates run over the argMax'd whole, never
|
|
193
|
+
* the raw column (which would match a superseded header version).
|
|
194
|
+
*/
|
|
195
|
+
{ alias: "aggRoutes", expression: argMaxColumn("routes") },
|
|
127
196
|
{ alias: "aggBrowserName", expression: argMaxColumn("browserName") },
|
|
128
197
|
{ alias: "aggBrowserVersion", expression: argMaxColumn("browserVersion") },
|
|
129
198
|
{ alias: "aggOsName", expression: argMaxColumn("osName") },
|
|
@@ -139,6 +208,36 @@ const HEADER_AGGREGATES = [
|
|
|
139
208
|
alias: "aggSamplePercentage",
|
|
140
209
|
expression: argMaxNumeric("samplePercentageAtCapture"),
|
|
141
210
|
},
|
|
211
|
+
/*
|
|
212
|
+
* Counts rather than the arrays themselves: the list only says "3 traces"
|
|
213
|
+
* and "2 exception groups", and the hasTraces predicate needs a number.
|
|
214
|
+
*/
|
|
215
|
+
{
|
|
216
|
+
alias: "aggTraceCount",
|
|
217
|
+
expression: `toFloat64(length(${argMaxColumn("traceIds")}))`,
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
alias: "aggExceptionGroupCount",
|
|
221
|
+
expression: `toFloat64(length(${argMaxColumn("exceptionFingerprints")}))`,
|
|
222
|
+
},
|
|
223
|
+
/*
|
|
224
|
+
* The first fingerprint, so the list's "3 errors" badge can link at the
|
|
225
|
+
* exception group instead of at an unfiltered Exceptions page. Empty
|
|
226
|
+
* string when the session recorded no exception group; arrayElement on
|
|
227
|
+
* an empty array returns the type's default, which for String is ''.
|
|
228
|
+
*/
|
|
229
|
+
{
|
|
230
|
+
alias: "aggTopExceptionFingerprint",
|
|
231
|
+
expression: `arrayElement(${argMaxColumn("exceptionFingerprints")}, 1)`,
|
|
232
|
+
},
|
|
233
|
+
{ alias: "aggClickCount", expression: argMaxNumeric("clickCount") },
|
|
234
|
+
{ alias: "aggActiveMs", expression: argMaxNumeric("activeMs") },
|
|
235
|
+
{
|
|
236
|
+
alias: "aggFirstErrorOffsetMs",
|
|
237
|
+
expression: argMaxNumeric("firstErrorOffsetMs"),
|
|
238
|
+
},
|
|
239
|
+
{ alias: "aggExpiresAt", expression: argMaxDate("retentionDate") },
|
|
240
|
+
{ alias: "aggTags", expression: argMaxColumn("tags") },
|
|
142
241
|
];
|
|
143
242
|
/* Only the manifest needs these; the list never renders them. */
|
|
144
243
|
const HEADER_DETAIL_AGGREGATES = [
|
|
@@ -149,7 +248,6 @@ const HEADER_DETAIL_AGGREGATES = [
|
|
|
149
248
|
{ alias: "aggRrwebVersion", expression: argMaxColumn("rrwebVersion") },
|
|
150
249
|
{ alias: "aggSchemaVersion", expression: argMaxNumeric("schemaVersion") },
|
|
151
250
|
{ alias: "aggWireVersion", expression: argMaxNumeric("wireVersion") },
|
|
152
|
-
{ alias: "aggRoutes", expression: argMaxColumn("routes") },
|
|
153
251
|
{ alias: "aggFidelityNotices", expression: argMaxColumn("fidelityNotices") },
|
|
154
252
|
{
|
|
155
253
|
alias: "aggFullSnapshotChunkIndexes",
|
|
@@ -161,6 +259,30 @@ const HEADER_DETAIL_AGGREGATES = [
|
|
|
161
259
|
expression: argMaxColumn("exceptionFingerprints"),
|
|
162
260
|
},
|
|
163
261
|
{ alias: "aggClockSkewMs", expression: argMaxNumeric("clockSkewMs") },
|
|
262
|
+
{
|
|
263
|
+
alias: "aggClientReportedStart",
|
|
264
|
+
expression: argMaxDateTime("clientReportedStartTime"),
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
alias: "aggCustomEventCount",
|
|
268
|
+
expression: argMaxNumeric("customEventCount"),
|
|
269
|
+
},
|
|
270
|
+
{ alias: "aggAttributes", expression: argMaxColumn("attributes") },
|
|
271
|
+
];
|
|
272
|
+
/*
|
|
273
|
+
* The two columns under the identity ACL. Named in a statement ONLY when
|
|
274
|
+
* the caller has already passed canReadIdentifiedUserLabel for the
|
|
275
|
+
* application the statement is pinned to.
|
|
276
|
+
*/
|
|
277
|
+
const IDENTITY_AGGREGATES = [
|
|
278
|
+
{
|
|
279
|
+
alias: "aggIdentifiedUserLabel",
|
|
280
|
+
expression: argMaxColumn("identifiedUserLabel"),
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
alias: "aggIdentifiedUserTraits",
|
|
284
|
+
expression: argMaxColumn("identifiedUserTraits"),
|
|
285
|
+
},
|
|
164
286
|
];
|
|
165
287
|
function toSelectList(columns) {
|
|
166
288
|
return columns
|
|
@@ -223,6 +345,28 @@ function readNumberArray(row, key) {
|
|
|
223
345
|
}
|
|
224
346
|
return numbers;
|
|
225
347
|
}
|
|
348
|
+
/*
|
|
349
|
+
* A Map(String, String) column. ClickHouse serialises it as a JSON object;
|
|
350
|
+
* anything else (including an array, or a row that predates the column)
|
|
351
|
+
* reads as an empty map.
|
|
352
|
+
*/
|
|
353
|
+
function readStringMap(row, key) {
|
|
354
|
+
const value = row[key];
|
|
355
|
+
const result = {};
|
|
356
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
357
|
+
return result;
|
|
358
|
+
}
|
|
359
|
+
for (const entryKey of Object.keys(value)) {
|
|
360
|
+
const entry = value[entryKey];
|
|
361
|
+
if (typeof entry === "string") {
|
|
362
|
+
result[entryKey] = entry;
|
|
363
|
+
}
|
|
364
|
+
else if (typeof entry === "number" || typeof entry === "boolean") {
|
|
365
|
+
result[entryKey] = String(entry);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
226
370
|
/*
|
|
227
371
|
* Unix millis -> Date. The queries return epoch milliseconds as a Float64
|
|
228
372
|
* precisely so no ClickHouse datetime string ever has to be re-parsed
|
|
@@ -232,7 +376,47 @@ function readNumberArray(row, key) {
|
|
|
232
376
|
function readDate(row, key) {
|
|
233
377
|
return new Date(readNumber(row, key));
|
|
234
378
|
}
|
|
379
|
+
/*
|
|
380
|
+
* The capability list chunk 0 declared, filtered to the vocabulary this
|
|
381
|
+
* build knows so a stored typo never reaches the player as a capability.
|
|
382
|
+
*/
|
|
383
|
+
function readRecorderCapabilities(row) {
|
|
384
|
+
const attributes = readStringMap(row, "aggAttributes");
|
|
385
|
+
const raw = attributes[RECORDER_CAPABILITIES_ATTRIBUTE];
|
|
386
|
+
if (!raw) {
|
|
387
|
+
return [];
|
|
388
|
+
}
|
|
389
|
+
return raw
|
|
390
|
+
.split(",")
|
|
391
|
+
.map((capability) => {
|
|
392
|
+
return capability.trim();
|
|
393
|
+
})
|
|
394
|
+
.filter((capability) => {
|
|
395
|
+
return SESSION_REPLAY_RECORDER_CAPABILITIES.includes(capability);
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
const activitySummaryCache = new Map();
|
|
399
|
+
let publishedRecorderVersionProvider = null;
|
|
235
400
|
export default class SessionReplayReadService {
|
|
401
|
+
static setPublishedRecorderVersionProvider(provider) {
|
|
402
|
+
publishedRecorderVersionProvider = provider;
|
|
403
|
+
}
|
|
404
|
+
static getPublishedRecorderVersion() {
|
|
405
|
+
if (!publishedRecorderVersionProvider) {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
const version = publishedRecorderVersionProvider();
|
|
410
|
+
return typeof version === "string" && version.length > 0 ? version : null;
|
|
411
|
+
}
|
|
412
|
+
catch (_a) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/* Test seam: the summary cache is process-local. */
|
|
417
|
+
static clearActivitySummaryCache() {
|
|
418
|
+
activitySummaryCache.clear();
|
|
419
|
+
}
|
|
236
420
|
/*
|
|
237
421
|
* Session list.
|
|
238
422
|
*
|
|
@@ -240,7 +424,10 @@ export default class SessionReplayReadService {
|
|
|
240
424
|
* are the first three elements of the sort key AND they are part of the
|
|
241
425
|
* ReplacingMergeTree replace key, so they are byte-identical on every
|
|
242
426
|
* duplicate row of a session. Filtering on them before the GROUP BY is
|
|
243
|
-
* therefore both index-friendly and safe.
|
|
427
|
+
* therefore both index-friendly and safe. Nothing else is ever added to
|
|
428
|
+
* the WHERE: any other predicate would have to run over raw rows and
|
|
429
|
+
* would either match a superseded header version or force a scan that
|
|
430
|
+
* the (projectId, rumApplicationId, startTime) prefix cannot prune.
|
|
244
431
|
*
|
|
245
432
|
* Everything else is filtered in HAVING against the argMax'd value.
|
|
246
433
|
* That is not a style choice: a provisional header (written on chunk 0,
|
|
@@ -251,6 +438,17 @@ export default class SessionReplayReadService {
|
|
|
251
438
|
*/
|
|
252
439
|
static async listSessions(request) {
|
|
253
440
|
const limit = Math.max(1, Math.min(request.limit, MAX_SESSION_REPLAY_LIST_LIMIT));
|
|
441
|
+
const sortBy = request.sortBy || "startTime";
|
|
442
|
+
if (!SESSION_REPLAY_SORT_BY_VALUES.includes(sortBy)) {
|
|
443
|
+
throw new BadDataException(`sortBy must be one of ${SESSION_REPLAY_SORT_BY_VALUES.join(", ")}.`);
|
|
444
|
+
}
|
|
445
|
+
if (request.cursor && request.cursor.sortBy !== sortBy) {
|
|
446
|
+
/*
|
|
447
|
+
* A cursor is a position in ONE ordering. Applying a "most errors"
|
|
448
|
+
* cursor to a "newest" list would silently skip or repeat sessions.
|
|
449
|
+
*/
|
|
450
|
+
throw new BadDataException(`The cursor belongs to a list sorted by ${request.cursor.sortBy}, not ${sortBy}. Start from the first page.`);
|
|
451
|
+
}
|
|
254
452
|
const selectList = toSelectList(HEADER_AGGREGATES);
|
|
255
453
|
const statement = SQL `
|
|
256
454
|
SELECT
|
|
@@ -259,7 +457,7 @@ export default class SessionReplayReadService {
|
|
|
259
457
|
`;
|
|
260
458
|
statement.append(` ${selectList}`);
|
|
261
459
|
if (request.includeIdentifiedUserLabel) {
|
|
262
|
-
statement.append(`,\n ${
|
|
460
|
+
statement.append(`,\n ${toSelectList(IDENTITY_AGGREGATES)}`);
|
|
263
461
|
}
|
|
264
462
|
statement.append(SQL `
|
|
265
463
|
FROM ${AnalyticsTableName.RumSession}
|
|
@@ -283,36 +481,45 @@ export default class SessionReplayReadService {
|
|
|
283
481
|
statement.append(RETENTION_FILTER);
|
|
284
482
|
/*
|
|
285
483
|
* Keyset cursor. The sort key is (projectId, rumApplicationId,
|
|
286
|
-
* startTime, sessionId) and the list is ordered by the
|
|
287
|
-
* descending, so the previous page's last startTime is a
|
|
288
|
-
* WHERE-level upper bound: it prunes granules instead of paging
|
|
289
|
-
* OFFSET, which on a wide time window would re-read and
|
|
290
|
-
* everything already returned. The exact ties are
|
|
291
|
-
* HAVING tiebreak below - the WHERE bound is
|
|
292
|
-
* so a row sharing the boundary timestamp is
|
|
484
|
+
* startTime, sessionId) and the newest-first list is ordered by the
|
|
485
|
+
* same tuple descending, so the previous page's last startTime is a
|
|
486
|
+
* valid WHERE-level upper bound: it prunes granules instead of paging
|
|
487
|
+
* with OFFSET, which on a wide time window would re-read and
|
|
488
|
+
* re-aggregate everything already returned. The exact ties are
|
|
489
|
+
* removed by the HAVING tiebreak below - the WHERE bound is
|
|
490
|
+
* deliberately inclusive so a row sharing the boundary timestamp is
|
|
491
|
+
* not skipped.
|
|
492
|
+
*
|
|
493
|
+
* Only for the startTime sort: for any other key the cursor value is
|
|
494
|
+
* an aggregate, and a WHERE on startTime would drop sessions that
|
|
495
|
+
* belong on later pages.
|
|
293
496
|
*/
|
|
294
|
-
if (request.cursor) {
|
|
497
|
+
if (request.cursor && sortBy === "startTime") {
|
|
295
498
|
statement.append(SQL ` AND startTime <= ${{
|
|
296
499
|
type: TableColumnType.DateTime64,
|
|
297
|
-
value: new Date(request.cursor.
|
|
500
|
+
value: new Date(request.cursor.sortValue),
|
|
298
501
|
}}`);
|
|
299
502
|
}
|
|
300
503
|
statement.append(" GROUP BY projectId, rumApplicationId, sessionId\n HAVING 1 = 1");
|
|
301
|
-
SessionReplayReadService.appendListHavingFilters(statement, request.filters);
|
|
504
|
+
SessionReplayReadService.appendListHavingFilters(statement, request.filters, request.includeIdentifiedUserLabel);
|
|
505
|
+
const sortExpression = SessionReplayReadService.getSortExpression(sortBy);
|
|
302
506
|
if (request.cursor) {
|
|
303
|
-
statement.append(
|
|
507
|
+
statement.append(` AND (${sortExpression} < `);
|
|
508
|
+
statement.append(SQL `${{
|
|
304
509
|
type: TableColumnType.Decimal,
|
|
305
|
-
value: request.cursor.
|
|
306
|
-
}}
|
|
510
|
+
value: request.cursor.sortValue,
|
|
511
|
+
}}`);
|
|
512
|
+
statement.append(` OR (${sortExpression} = `);
|
|
513
|
+
statement.append(SQL `${{
|
|
307
514
|
type: TableColumnType.Decimal,
|
|
308
|
-
value: request.cursor.
|
|
515
|
+
value: request.cursor.sortValue,
|
|
309
516
|
}} AND sessionId < ${{
|
|
310
517
|
type: TableColumnType.Text,
|
|
311
518
|
value: request.cursor.sessionId,
|
|
312
519
|
}}))`);
|
|
313
520
|
}
|
|
314
|
-
statement.append(
|
|
315
|
-
|
|
521
|
+
statement.append(` ORDER BY ${sortExpression} DESC, sessionId DESC`);
|
|
522
|
+
statement.append(SQL ` LIMIT ${{
|
|
316
523
|
type: TableColumnType.Number,
|
|
317
524
|
value: limit + 1,
|
|
318
525
|
}}`);
|
|
@@ -328,11 +535,13 @@ export default class SessionReplayReadService {
|
|
|
328
535
|
const hasMore = rows.length > limit;
|
|
329
536
|
const pageRows = hasMore ? rows.slice(0, limit) : rows;
|
|
330
537
|
const sessions = pageRows.map((row) => {
|
|
538
|
+
const startTime = readDate(row, "aggStartTime");
|
|
539
|
+
const endTime = readDate(row, "aggEndTime");
|
|
331
540
|
const item = {
|
|
332
541
|
sessionId: readString(row, "sessionId"),
|
|
333
542
|
rumApplicationId: readString(row, "applicationId"),
|
|
334
|
-
startTime:
|
|
335
|
-
endTime:
|
|
543
|
+
startTime: startTime,
|
|
544
|
+
endTime: endTime,
|
|
336
545
|
durationMs: readNumber(row, "aggDurationMs"),
|
|
337
546
|
isFinalized: readBoolean(row, "aggIsFinalized"),
|
|
338
547
|
sealedReason: readString(row, "aggSealedReason"),
|
|
@@ -360,9 +569,21 @@ export default class SessionReplayReadService {
|
|
|
360
569
|
viewportHeight: readNumber(row, "aggViewportHeight"),
|
|
361
570
|
identifiedUserKey: readString(row, "aggIdentifiedUserKey"),
|
|
362
571
|
samplePercentageAtCapture: readNumber(row, "aggSamplePercentage"),
|
|
572
|
+
routes: readStringArray(row, "aggRoutes").slice(0, MAX_LIST_ROUTES),
|
|
573
|
+
traceCount: readNumber(row, "aggTraceCount"),
|
|
574
|
+
exceptionGroupCount: readNumber(row, "aggExceptionGroupCount"),
|
|
575
|
+
topExceptionFingerprint: readString(row, "aggTopExceptionFingerprint"),
|
|
576
|
+
clickCount: readNumber(row, "aggClickCount"),
|
|
577
|
+
activeMs: readNumber(row, "aggActiveMs"),
|
|
578
|
+
firstErrorOffsetMs: readNumber(row, "aggFirstErrorOffsetMs"),
|
|
579
|
+
expiresAtUnixMs: readNumber(row, "aggExpiresAt"),
|
|
580
|
+
tags: readStringMap(row, "aggTags"),
|
|
581
|
+
startTimeUnixMs: startTime.getTime(),
|
|
582
|
+
endTimeUnixMs: endTime.getTime(),
|
|
363
583
|
};
|
|
364
584
|
if (request.includeIdentifiedUserLabel) {
|
|
365
585
|
item.identifiedUserLabel = readString(row, "aggIdentifiedUserLabel");
|
|
586
|
+
item.identifiedUserTraits = readStringMap(row, "aggIdentifiedUserTraits");
|
|
366
587
|
}
|
|
367
588
|
return item;
|
|
368
589
|
});
|
|
@@ -371,7 +592,8 @@ export default class SessionReplayReadService {
|
|
|
371
592
|
sessions: sessions,
|
|
372
593
|
nextCursor: hasMore && lastSession
|
|
373
594
|
? {
|
|
374
|
-
|
|
595
|
+
sortBy: sortBy,
|
|
596
|
+
sortValue: SessionReplayReadService.getSortValue(sortBy, lastSession),
|
|
375
597
|
sessionId: lastSession.sessionId,
|
|
376
598
|
}
|
|
377
599
|
: null,
|
|
@@ -383,9 +605,10 @@ export default class SessionReplayReadService {
|
|
|
383
605
|
*
|
|
384
606
|
* This is also what resolves a sessionId to its owning RUM application
|
|
385
607
|
* for the handler-level authorization check, which is why it is keyed
|
|
386
|
-
* on (projectId, sessionId)
|
|
387
|
-
*
|
|
388
|
-
*
|
|
608
|
+
* on (projectId, sessionId) and the optional rumApplicationId is a
|
|
609
|
+
* DISAMBIGUATOR, never a substitute for the check: a supplied id only
|
|
610
|
+
* narrows which header row is read, and the handler still authorizes
|
|
611
|
+
* the application that row names.
|
|
389
612
|
*/
|
|
390
613
|
static async getSessionHeader(data) {
|
|
391
614
|
const selectList = toSelectList([
|
|
@@ -410,6 +633,12 @@ export default class SessionReplayReadService {
|
|
|
410
633
|
value: data.sessionId,
|
|
411
634
|
}}
|
|
412
635
|
`);
|
|
636
|
+
if (data.rumApplicationId) {
|
|
637
|
+
statement.append(SQL ` AND rumApplicationId = ${{
|
|
638
|
+
type: TableColumnType.ObjectID,
|
|
639
|
+
value: data.rumApplicationId,
|
|
640
|
+
}}`);
|
|
641
|
+
}
|
|
413
642
|
statement.append(RETENTION_FILTER);
|
|
414
643
|
/*
|
|
415
644
|
* Grouped by the full replace-key identity minus startTime, so one
|
|
@@ -422,9 +651,10 @@ export default class SessionReplayReadService {
|
|
|
422
651
|
* sessionId share a key space. Picking the newest group would let
|
|
423
652
|
* anyone who can write to application A resolve a sessionId belonging
|
|
424
653
|
* to application B onto their own application and pass the label
|
|
425
|
-
* check. An ambiguous sessionId is refused outright
|
|
426
|
-
*
|
|
427
|
-
*
|
|
654
|
+
* check. An ambiguous sessionId is refused outright unless the caller
|
|
655
|
+
* named the application it wants (which is then authorized on its own
|
|
656
|
+
* merits): it is either an attack or a collision, and neither has a
|
|
657
|
+
* single correct recording to return.
|
|
428
658
|
*/
|
|
429
659
|
statement.append(" GROUP BY projectId, rumApplicationId, sessionId ORDER BY aggStartTime DESC LIMIT 2");
|
|
430
660
|
statement.append(READ_QUERY_SETTINGS);
|
|
@@ -432,18 +662,20 @@ export default class SessionReplayReadService {
|
|
|
432
662
|
const response = await dbResult.json();
|
|
433
663
|
const rows = response.data || [];
|
|
434
664
|
if (rows.length > 1) {
|
|
435
|
-
throw new BadDataException("This session id
|
|
665
|
+
throw new BadDataException("This session id was recorded under more than one application in this project. Open it from the session list of the application you want to watch, which passes rumApplicationId to choose the recording.");
|
|
436
666
|
}
|
|
437
667
|
const row = rows[0];
|
|
438
668
|
if (!row) {
|
|
439
669
|
return null;
|
|
440
670
|
}
|
|
671
|
+
const startTime = readDate(row, "aggStartTime");
|
|
672
|
+
const endTime = readDate(row, "aggEndTime");
|
|
441
673
|
return {
|
|
442
674
|
sessionId: readString(row, "sessionId"),
|
|
443
675
|
projectId: readString(row, "headerProjectId"),
|
|
444
676
|
rumApplicationId: readString(row, "applicationId"),
|
|
445
|
-
startTime:
|
|
446
|
-
endTime:
|
|
677
|
+
startTime: startTime,
|
|
678
|
+
endTime: endTime,
|
|
447
679
|
durationMs: readNumber(row, "aggDurationMs"),
|
|
448
680
|
isFinalized: readBoolean(row, "aggIsFinalized"),
|
|
449
681
|
sealedReason: readString(row, "aggSealedReason"),
|
|
@@ -482,6 +714,106 @@ export default class SessionReplayReadService {
|
|
|
482
714
|
traceIds: readStringArray(row, "aggTraceIds"),
|
|
483
715
|
exceptionFingerprints: readStringArray(row, "aggExceptionFingerprints"),
|
|
484
716
|
clockSkewMs: readNumber(row, "aggClockSkewMs"),
|
|
717
|
+
startTimeUnixMs: startTime.getTime(),
|
|
718
|
+
endTimeUnixMs: endTime.getTime(),
|
|
719
|
+
clientReportedStartUnixMs: readNumber(row, "aggClientReportedStart"),
|
|
720
|
+
tags: readStringMap(row, "aggTags"),
|
|
721
|
+
expiresAtUnixMs: readNumber(row, "aggExpiresAt"),
|
|
722
|
+
clickCount: readNumber(row, "aggClickCount"),
|
|
723
|
+
customEventCount: readNumber(row, "aggCustomEventCount"),
|
|
724
|
+
activeMs: readNumber(row, "aggActiveMs"),
|
|
725
|
+
firstErrorOffsetMs: readNumber(row, "aggFirstErrorOffsetMs"),
|
|
726
|
+
recorderCapabilities: readRecorderCapabilities(row),
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
/*
|
|
730
|
+
* The identity columns for one session, pinned to the application the
|
|
731
|
+
* caller was authorized against. This is the ONLY statement outside the
|
|
732
|
+
* identity-gated list projection that names identifiedUserLabel or
|
|
733
|
+
* identifiedUserTraits, and the manifest handler calls it strictly
|
|
734
|
+
* after canReadIdentifiedUserLabel has passed for this application. It
|
|
735
|
+
* is a separate, tiny read rather than two more columns on
|
|
736
|
+
* getSessionHeader because the header is resolved BEFORE the
|
|
737
|
+
* application (and therefore the identity decision) is known.
|
|
738
|
+
*/
|
|
739
|
+
static async getSessionIdentity(data) {
|
|
740
|
+
const statement = SQL `
|
|
741
|
+
SELECT
|
|
742
|
+
`;
|
|
743
|
+
statement.append(` ${toSelectList(IDENTITY_AGGREGATES)}`);
|
|
744
|
+
statement.append(SQL `
|
|
745
|
+
FROM ${AnalyticsTableName.RumSession}
|
|
746
|
+
WHERE projectId = ${{
|
|
747
|
+
type: TableColumnType.ObjectID,
|
|
748
|
+
value: data.projectId,
|
|
749
|
+
}}
|
|
750
|
+
AND rumApplicationId = ${{
|
|
751
|
+
type: TableColumnType.ObjectID,
|
|
752
|
+
value: data.rumApplicationId,
|
|
753
|
+
}}
|
|
754
|
+
AND sessionId = ${{
|
|
755
|
+
type: TableColumnType.Text,
|
|
756
|
+
value: data.sessionId,
|
|
757
|
+
}}
|
|
758
|
+
`);
|
|
759
|
+
statement.append(RETENTION_FILTER);
|
|
760
|
+
statement.append(" GROUP BY projectId, rumApplicationId, sessionId LIMIT 1");
|
|
761
|
+
statement.append(READ_QUERY_SETTINGS);
|
|
762
|
+
const dbResult = await RumSessionService.executeQuery(statement);
|
|
763
|
+
const response = await dbResult.json();
|
|
764
|
+
const row = (response.data || [])[0];
|
|
765
|
+
if (!row) {
|
|
766
|
+
return { identifiedUserLabel: "", identifiedUserTraits: {} };
|
|
767
|
+
}
|
|
768
|
+
return {
|
|
769
|
+
identifiedUserLabel: readString(row, "aggIdentifiedUserLabel"),
|
|
770
|
+
identifiedUserTraits: readStringMap(row, "aggIdentifiedUserTraits"),
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
/*
|
|
774
|
+
* For a sessionId that getSessionHeader could not find: did a header
|
|
775
|
+
* ever exist, and when did (or does) it expire? Runs WITHOUT the
|
|
776
|
+
* retention filter, which is safe only because it returns dates and an
|
|
777
|
+
* application id and never a row's content - it lets the handler say
|
|
778
|
+
* "this recording expired on <date>" instead of "not found".
|
|
779
|
+
*
|
|
780
|
+
* null when no row exists at all (never recorded, or already dropped by
|
|
781
|
+
* the ClickHouse TTL, or erased).
|
|
782
|
+
*/
|
|
783
|
+
static async getExpiredSessionInfo(data) {
|
|
784
|
+
const statement = SQL `
|
|
785
|
+
SELECT
|
|
786
|
+
toString(rumApplicationId) AS applicationId,
|
|
787
|
+
toFloat64(toUnixTimestamp(max(retentionDate))) * 1000 AS expiresAtUnixMs,
|
|
788
|
+
toFloat64(toUnixTimestamp64Milli(min(startTime))) AS startTimeUnixMs
|
|
789
|
+
FROM ${AnalyticsTableName.RumSession}
|
|
790
|
+
WHERE projectId = ${{
|
|
791
|
+
type: TableColumnType.ObjectID,
|
|
792
|
+
value: data.projectId,
|
|
793
|
+
}}
|
|
794
|
+
AND sessionId = ${{
|
|
795
|
+
type: TableColumnType.Text,
|
|
796
|
+
value: data.sessionId,
|
|
797
|
+
}}
|
|
798
|
+
`;
|
|
799
|
+
if (data.rumApplicationId) {
|
|
800
|
+
statement.append(SQL ` AND rumApplicationId = ${{
|
|
801
|
+
type: TableColumnType.ObjectID,
|
|
802
|
+
value: data.rumApplicationId,
|
|
803
|
+
}}`);
|
|
804
|
+
}
|
|
805
|
+
statement.append(" GROUP BY rumApplicationId ORDER BY expiresAtUnixMs DESC LIMIT 1");
|
|
806
|
+
statement.append(READ_QUERY_SETTINGS);
|
|
807
|
+
const dbResult = await RumSessionService.executeQuery(statement);
|
|
808
|
+
const response = await dbResult.json();
|
|
809
|
+
const row = (response.data || [])[0];
|
|
810
|
+
if (!row) {
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
return {
|
|
814
|
+
rumApplicationId: readString(row, "applicationId"),
|
|
815
|
+
startTime: readDate(row, "startTimeUnixMs"),
|
|
816
|
+
expiresAt: readDate(row, "expiresAtUnixMs"),
|
|
485
817
|
};
|
|
486
818
|
}
|
|
487
819
|
/*
|
|
@@ -501,6 +833,11 @@ export default class SessionReplayReadService {
|
|
|
501
833
|
* tabId/chunkIndex first (rather than by version alone) leaves the
|
|
502
834
|
* output already sorted for the caller - LIMIT BY runs after ORDER
|
|
503
835
|
* BY, so the version DESC tiebreak still selects the right row.
|
|
836
|
+
*
|
|
837
|
+
* clickCount and url are narrow columns: the activity lane and the
|
|
838
|
+
* URL bar read them before any chunk is decoded. payloadBytes stays
|
|
839
|
+
* the WIRE size the recorder posted; the stored size is only ever
|
|
840
|
+
* measured by getChunks, which is the read the cap actually bounds.
|
|
504
841
|
*/
|
|
505
842
|
const statement = SQL `
|
|
506
843
|
SELECT
|
|
@@ -516,7 +853,9 @@ export default class SessionReplayReadService {
|
|
|
516
853
|
deadClickCount,
|
|
517
854
|
errorClickCount,
|
|
518
855
|
refreshRageCount,
|
|
519
|
-
routeCount
|
|
856
|
+
routeCount,
|
|
857
|
+
clickCount,
|
|
858
|
+
url
|
|
520
859
|
FROM ${AnalyticsTableName.RumSessionChunk}
|
|
521
860
|
WHERE projectId = ${{
|
|
522
861
|
type: TableColumnType.ObjectID,
|
|
@@ -543,6 +882,9 @@ export default class SessionReplayReadService {
|
|
|
543
882
|
const response = await dbResult.json();
|
|
544
883
|
const rows = response.data || [];
|
|
545
884
|
const tabsById = new Map();
|
|
885
|
+
let liveDurationMs = 0;
|
|
886
|
+
let liveEventCount = 0;
|
|
887
|
+
let liveMaxChunkIndex = 0;
|
|
546
888
|
for (const row of rows) {
|
|
547
889
|
const tabId = readString(row, "tabId");
|
|
548
890
|
const entry = {
|
|
@@ -559,7 +901,12 @@ export default class SessionReplayReadService {
|
|
|
559
901
|
errorClickCount: readNumber(row, "errorClickCount"),
|
|
560
902
|
refreshRageCount: readNumber(row, "refreshRageCount"),
|
|
561
903
|
routeCount: readNumber(row, "routeCount"),
|
|
904
|
+
clickCount: readNumber(row, "clickCount"),
|
|
905
|
+
url: readString(row, "url"),
|
|
562
906
|
};
|
|
907
|
+
liveDurationMs = Math.max(liveDurationMs, entry.chunkEndOffsetMs);
|
|
908
|
+
liveEventCount += entry.eventCount;
|
|
909
|
+
liveMaxChunkIndex = Math.max(liveMaxChunkIndex, entry.chunkIndex);
|
|
563
910
|
const existing = tabsById.get(tabId);
|
|
564
911
|
if (existing) {
|
|
565
912
|
existing.push(entry);
|
|
@@ -596,161 +943,179 @@ export default class SessionReplayReadService {
|
|
|
596
943
|
totalPayloadBytes: entries.reduce((total, entry) => {
|
|
597
944
|
return total + entry.payloadBytes;
|
|
598
945
|
}, 0),
|
|
946
|
+
firstChunkStartOffsetMs: entries.reduce((min, entry) => {
|
|
947
|
+
return Math.min(min, entry.chunkStartOffsetMs);
|
|
948
|
+
}, Number.POSITIVE_INFINITY),
|
|
599
949
|
});
|
|
600
950
|
}
|
|
951
|
+
for (const tab of tabs) {
|
|
952
|
+
if (!Number.isFinite(tab.firstChunkStartOffsetMs)) {
|
|
953
|
+
tab.firstChunkStartOffsetMs = 0;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
601
956
|
return {
|
|
602
|
-
header:
|
|
957
|
+
header: SessionReplayReadService.reconcileLiveHeader({
|
|
958
|
+
header: data.header,
|
|
959
|
+
chunkRowCount: rows.length,
|
|
960
|
+
liveDurationMs: liveDurationMs,
|
|
961
|
+
liveEventCount: liveEventCount,
|
|
962
|
+
liveMaxChunkIndex: liveMaxChunkIndex,
|
|
963
|
+
}),
|
|
603
964
|
tabs: tabs,
|
|
604
965
|
isChunkIndexTruncated: rows.length >= MAX_MANIFEST_ROWS,
|
|
605
966
|
};
|
|
606
967
|
}
|
|
607
968
|
/*
|
|
608
|
-
*
|
|
609
|
-
*
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
* different quantities: payloadBytes is the post-gzip WIRE size the
|
|
613
|
-
* recorder uploaded (the metering signal), while the payload column
|
|
614
|
-
* holds the DECOMPRESSED JSON and is what this endpoint actually
|
|
615
|
-
* returns. rrweb JSON gzips 10-20x, so a cap applied to payloadBytes
|
|
616
|
-
* bounds a number an order of magnitude smaller than the response and
|
|
617
|
-
* therefore bounds nothing useful.
|
|
618
|
-
*
|
|
619
|
-
* The cost is that ClickHouse has to decompress the column to measure
|
|
620
|
-
* it, which is exactly what naming `payload` was meant to avoid. It is
|
|
621
|
-
* still worth doing before the read rather than after: the bytes are
|
|
622
|
-
* measured inside ClickHouse and never cross the wire, and the marks
|
|
623
|
-
* the following read needs are warm by the time it runs.
|
|
969
|
+
* A provisional header (isFinalized false) says durationMs 0, chunkCount
|
|
970
|
+
* 0 and eventCount 0 while its chunk rows say otherwise; the manifest
|
|
971
|
+
* has just read every chunk row, so it reports what the rows prove. A
|
|
972
|
+
* finalized header is authoritative and returned untouched.
|
|
624
973
|
*/
|
|
625
|
-
static
|
|
626
|
-
if (data.
|
|
627
|
-
return
|
|
974
|
+
static reconcileLiveHeader(data) {
|
|
975
|
+
if (data.header.isFinalized || data.chunkRowCount === 0) {
|
|
976
|
+
return data.header;
|
|
628
977
|
}
|
|
629
|
-
const
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
toFloat64(length(payload)) AS chunkStoredBytes
|
|
633
|
-
FROM ${AnalyticsTableName.RumSessionChunk}
|
|
634
|
-
WHERE projectId = ${{
|
|
635
|
-
type: TableColumnType.ObjectID,
|
|
636
|
-
value: data.projectId,
|
|
637
|
-
}}
|
|
638
|
-
AND rumApplicationId = ${{
|
|
639
|
-
type: TableColumnType.ObjectID,
|
|
640
|
-
value: data.rumApplicationId,
|
|
641
|
-
}}
|
|
642
|
-
AND sessionId = ${{
|
|
643
|
-
type: TableColumnType.Text,
|
|
644
|
-
value: data.sessionId,
|
|
645
|
-
}}
|
|
646
|
-
AND tabId = ${{
|
|
647
|
-
type: TableColumnType.Text,
|
|
648
|
-
value: data.tabId,
|
|
649
|
-
}}
|
|
650
|
-
AND chunkIndex IN (${{
|
|
651
|
-
type: TableColumnType.Number,
|
|
652
|
-
value: new Includes(data.chunkIndexes),
|
|
653
|
-
}})
|
|
654
|
-
`;
|
|
655
|
-
statement.append(RETENTION_FILTER);
|
|
656
|
-
/*
|
|
657
|
-
* Deduplicated exactly like the payload read, so the pre-check and
|
|
658
|
-
* the read it guards can never disagree about which rows count.
|
|
659
|
-
* Summed in TypeScript rather than in SQL because the row count is
|
|
660
|
-
* bounded by MAX_SESSION_REPLAY_CHUNKS_PER_READ, and a wrapping
|
|
661
|
-
* aggregate would need a subquery whose LIMIT BY semantics are
|
|
662
|
-
* easier to get subtly wrong than to read.
|
|
663
|
-
*/
|
|
664
|
-
statement.append(" ORDER BY chunkIndex ASC, version DESC LIMIT 1 BY chunkIndex");
|
|
665
|
-
statement.append(READ_QUERY_SETTINGS);
|
|
666
|
-
const dbResult = await RumSessionChunkService.executeQuery(statement);
|
|
667
|
-
const response = await dbResult.json();
|
|
668
|
-
return (response.data || []).reduce((total, row) => {
|
|
669
|
-
return total + readNumber(row, "chunkStoredBytes");
|
|
670
|
-
}, 0);
|
|
978
|
+
const durationMs = Math.max(data.header.durationMs, data.liveDurationMs);
|
|
979
|
+
const endTimeUnixMs = Math.max(data.header.endTimeUnixMs, data.header.startTimeUnixMs + durationMs);
|
|
980
|
+
return Object.assign(Object.assign({}, data.header), { durationMs: durationMs, endTime: new Date(endTimeUnixMs), endTimeUnixMs: endTimeUnixMs, chunkCount: Math.max(data.header.chunkCount, data.chunkRowCount), eventCount: Math.max(data.header.eventCount, data.liveEventCount), maxChunkIndex: Math.max(data.header.maxChunkIndex, data.liveMaxChunkIndex) });
|
|
671
981
|
}
|
|
672
982
|
/*
|
|
673
983
|
* The payload read. The only query in the system that names the
|
|
674
984
|
* `payload` column.
|
|
675
985
|
*
|
|
986
|
+
* The byte cap is measured on `length(payload)` - the DECOMPRESSED
|
|
987
|
+
* stored JSON that is actually returned - in the SAME statement that
|
|
988
|
+
* ships the bytes, so the column is decompressed once per page rather
|
|
989
|
+
* than once for a pre-check and again for the read. `payloadBytes` is
|
|
990
|
+
* the post-gzip WIRE size the recorder uploaded; rrweb JSON gzips
|
|
991
|
+
* 10-20x, so a cap on it bounds a number an order of magnitude smaller
|
|
992
|
+
* than the response and therefore bounds nothing useful.
|
|
993
|
+
*
|
|
994
|
+
* Prefix semantics rather than refusal. A page that does not fit is
|
|
995
|
+
* answered with the longest prefix of whole chunks that does, and ALWAYS
|
|
996
|
+
* with at least the first chunk: the ingest cap
|
|
997
|
+
* (SESSION_REPLAY_MAX_DECOMPRESSED_FRAME_BYTES) already bounds a single
|
|
998
|
+
* frame, so a lone chunk can never exceed what the ingest let in, and a
|
|
999
|
+
* single oversized snapshot that could never be served would dead-end
|
|
1000
|
+
* playback at that chunk forever. The player plans pages against the
|
|
1001
|
+
* wire size it has, requests, and reads back whichever chunks arrived.
|
|
1002
|
+
*
|
|
676
1003
|
* Both caps are enforced here rather than only at the route so no
|
|
677
1004
|
* future caller can reach the payload column without them.
|
|
678
1005
|
*/
|
|
679
1006
|
static async getChunks(data) {
|
|
680
1007
|
if (data.chunkIndexes.length === 0) {
|
|
681
|
-
return [];
|
|
1008
|
+
return { chunks: [], omittedChunkIndexes: [] };
|
|
682
1009
|
}
|
|
683
1010
|
if (data.chunkIndexes.length > MAX_SESSION_REPLAY_CHUNKS_PER_READ) {
|
|
684
1011
|
throw new BadDataException(`A maximum of ${MAX_SESSION_REPLAY_CHUNKS_PER_READ} chunks may be requested at a time.`);
|
|
685
1012
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
1013
|
+
/*
|
|
1014
|
+
* Innermost: the de-duplicated rows. A retried delivery is two
|
|
1015
|
+
* physically present rows on a ReplacingMergeTree until a merge runs.
|
|
1016
|
+
* Feeding both to the player would replay the same mutations twice,
|
|
1017
|
+
* which rrweb resolves against node ids and would either throw or
|
|
1018
|
+
* render a DOM that never existed.
|
|
1019
|
+
*
|
|
1020
|
+
* Middle: a running total of stored bytes in chunk order. Outermost:
|
|
1021
|
+
* a row is SERVED when the total up to and including it is under the
|
|
1022
|
+
* cap, or when it is the first row; a row that is not served keeps
|
|
1023
|
+
* its index (so the caller can name what was omitted) but ships an
|
|
1024
|
+
* empty payload, so the bytes crossing the wire are bounded inside
|
|
1025
|
+
* ClickHouse and never in the application.
|
|
1026
|
+
*
|
|
1027
|
+
* The outer projection is aliased servedPayload rather than payload:
|
|
1028
|
+
* an alias that names the column its own expression reads is a
|
|
1029
|
+
* cyclic alias to ClickHouse.
|
|
1030
|
+
*/
|
|
690
1031
|
const statement = SQL `
|
|
691
1032
|
SELECT
|
|
692
1033
|
chunkIndex,
|
|
693
|
-
payload
|
|
694
|
-
|
|
695
|
-
|
|
1034
|
+
if(isServed, payload, '') AS servedPayload,
|
|
1035
|
+
isServed
|
|
1036
|
+
FROM (
|
|
1037
|
+
SELECT
|
|
1038
|
+
chunkIndex,
|
|
1039
|
+
payload,
|
|
1040
|
+
(sum(length(payload)) OVER (ORDER BY chunkIndex ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) <= ${{
|
|
1041
|
+
type: TableColumnType.Decimal,
|
|
1042
|
+
value: MAX_SESSION_REPLAY_READ_BYTES,
|
|
1043
|
+
}}
|
|
1044
|
+
OR row_number() OVER (ORDER BY chunkIndex ASC) = 1) AS isServed
|
|
1045
|
+
FROM (
|
|
1046
|
+
SELECT
|
|
1047
|
+
chunkIndex,
|
|
1048
|
+
payload
|
|
1049
|
+
FROM ${AnalyticsTableName.RumSessionChunk}
|
|
1050
|
+
WHERE projectId = ${{
|
|
696
1051
|
type: TableColumnType.ObjectID,
|
|
697
1052
|
value: data.projectId,
|
|
698
1053
|
}}
|
|
699
|
-
|
|
1054
|
+
AND rumApplicationId = ${{
|
|
700
1055
|
type: TableColumnType.ObjectID,
|
|
701
1056
|
value: data.rumApplicationId,
|
|
702
1057
|
}}
|
|
703
|
-
|
|
1058
|
+
AND sessionId = ${{
|
|
704
1059
|
type: TableColumnType.Text,
|
|
705
1060
|
value: data.sessionId,
|
|
706
1061
|
}}
|
|
707
|
-
|
|
1062
|
+
AND tabId = ${{
|
|
708
1063
|
type: TableColumnType.Text,
|
|
709
1064
|
value: data.tabId,
|
|
710
1065
|
}}
|
|
711
|
-
|
|
1066
|
+
AND chunkIndex IN (${{
|
|
712
1067
|
type: TableColumnType.Number,
|
|
713
1068
|
value: new Includes(data.chunkIndexes),
|
|
714
1069
|
}})
|
|
715
1070
|
`;
|
|
716
1071
|
statement.append(RETENTION_FILTER);
|
|
717
|
-
|
|
718
|
-
* A retried delivery is two physically present rows on a
|
|
719
|
-
* ReplacingMergeTree until a merge runs. Feeding both to the player
|
|
720
|
-
* would replay the same mutations twice, which rrweb resolves against
|
|
721
|
-
* node ids and would either throw or render a DOM that never existed.
|
|
722
|
-
*/
|
|
723
|
-
statement.append(" ORDER BY chunkIndex ASC, version DESC LIMIT 1 BY chunkIndex");
|
|
1072
|
+
statement.append(" ORDER BY chunkIndex ASC, version DESC LIMIT 1 BY chunkIndex\n )\n )\n ORDER BY chunkIndex ASC");
|
|
724
1073
|
statement.append(READ_QUERY_SETTINGS);
|
|
725
1074
|
const dbResult = await RumSessionChunkService.executeQuery(statement);
|
|
726
1075
|
const response = await dbResult.json();
|
|
727
1076
|
/*
|
|
728
1077
|
* The cap is re-applied to the bytes actually being handed back, not
|
|
729
|
-
* only to what
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
*
|
|
734
|
-
* set.
|
|
1078
|
+
* only to what ClickHouse computed: any future change to how stored
|
|
1079
|
+
* size is derived would otherwise silently unbound the response. The
|
|
1080
|
+
* prefix is also re-established here - a served row behind an
|
|
1081
|
+
* unserved one would be a hole the player cannot play across, so the
|
|
1082
|
+
* served set stops at the first omission.
|
|
735
1083
|
*/
|
|
736
1084
|
let totalReturnedBytes = 0;
|
|
737
1085
|
const chunks = [];
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1086
|
+
const omittedChunkIndexes = [];
|
|
1087
|
+
const chunkRows = response.data || [];
|
|
1088
|
+
for (const row of chunkRows) {
|
|
1089
|
+
const chunkIndex = readNumber(row, "chunkIndex");
|
|
1090
|
+
const payload = readString(row, "servedPayload");
|
|
1091
|
+
const isServed = row["isServed"] === undefined ? true : readBoolean(row, "isServed");
|
|
1092
|
+
const payloadBytes = Buffer.byteLength(payload, "utf8");
|
|
1093
|
+
const fits = chunks.length === 0 ||
|
|
1094
|
+
totalReturnedBytes + payloadBytes <= MAX_SESSION_REPLAY_READ_BYTES;
|
|
1095
|
+
if (!isServed || !fits || omittedChunkIndexes.length > 0) {
|
|
1096
|
+
omittedChunkIndexes.push(chunkIndex);
|
|
1097
|
+
continue;
|
|
743
1098
|
}
|
|
1099
|
+
totalReturnedBytes += payloadBytes;
|
|
744
1100
|
chunks.push({
|
|
745
|
-
chunkIndex:
|
|
1101
|
+
chunkIndex: chunkIndex,
|
|
746
1102
|
payload: payload,
|
|
747
1103
|
});
|
|
748
1104
|
}
|
|
749
|
-
return chunks;
|
|
1105
|
+
return { chunks: chunks, omittedChunkIndexes: omittedChunkIndexes };
|
|
750
1106
|
}
|
|
751
1107
|
/*
|
|
752
1108
|
* Sessions that observed a given exception fingerprint.
|
|
753
1109
|
*
|
|
1110
|
+
* Two sources, one header query. The header's exceptionFingerprints
|
|
1111
|
+
* array is written by the finalizer, so for the first 10+ minutes after
|
|
1112
|
+
* the error - the whole incident, from the reporter's point of view -
|
|
1113
|
+
* the session's header knows nothing about it. The exception instance
|
|
1114
|
+
* table, however, carries the session id of the page that threw, from
|
|
1115
|
+
* the moment the exception is ingested. Those ids are looked up first
|
|
1116
|
+
* (cheap: bloom-indexed fingerprint, bounded window) and OR-ed into the
|
|
1117
|
+
* header predicate, so a live session is found as soon as its error is.
|
|
1118
|
+
*
|
|
754
1119
|
* hasAny() appears twice on purpose. In the WHERE it is a bloom-pruned
|
|
755
1120
|
* pre-filter over physical rows; a group survives it if ANY of its rows
|
|
756
1121
|
* carries the fingerprint, which necessarily includes the case where
|
|
@@ -758,6 +1123,10 @@ export default class SessionReplayReadService {
|
|
|
758
1123
|
* drop a true match. The HAVING then re-checks the argMax'd array so a
|
|
759
1124
|
* fingerprint present only on a superseded row does not produce a false
|
|
760
1125
|
* positive.
|
|
1126
|
+
*
|
|
1127
|
+
* Always windowed. RumSession is partitioned by day, so without a
|
|
1128
|
+
* window this scanned every partition the project ever wrote on every
|
|
1129
|
+
* exception page load.
|
|
761
1130
|
*/
|
|
762
1131
|
static async getSessionsForException(data) {
|
|
763
1132
|
if (data.accessibleRumApplicationIds &&
|
|
@@ -765,10 +1134,20 @@ export default class SessionReplayReadService {
|
|
|
765
1134
|
return [];
|
|
766
1135
|
}
|
|
767
1136
|
const limit = Math.max(1, Math.min(data.limit, MAX_SESSION_REPLAY_FOR_EXCEPTION_LIMIT));
|
|
1137
|
+
const endTime = data.endTime || OneUptimeDate.getCurrentDate();
|
|
1138
|
+
const startTime = data.startTime ||
|
|
1139
|
+
OneUptimeDate.addRemoveDays(endTime, -DEFAULT_SESSION_REPLAY_FOR_EXCEPTION_WINDOW_DAYS);
|
|
1140
|
+
const instanceSessionIds = await SessionReplayReadService.getSessionIdsForExceptionInstances({
|
|
1141
|
+
projectId: data.projectId,
|
|
1142
|
+
exceptionFingerprint: data.exceptionFingerprint,
|
|
1143
|
+
startTime: startTime,
|
|
1144
|
+
endTime: endTime,
|
|
1145
|
+
sessionId: data.sessionId,
|
|
1146
|
+
});
|
|
768
1147
|
const selectList = toSelectList([
|
|
769
1148
|
{ alias: "aggStartTime", expression: argMaxDateTime("startTime") },
|
|
770
1149
|
{ alias: "aggEndTime", expression: argMaxDateTime("endTime") },
|
|
771
|
-
{ alias: "aggDurationMs", expression:
|
|
1150
|
+
{ alias: "aggDurationMs", expression: LIVE_DURATION_EXPRESSION },
|
|
772
1151
|
{ alias: "aggHasError", expression: argMaxColumn("hasError") },
|
|
773
1152
|
{ alias: "aggErrorCount", expression: argMaxNumeric("errorCount") },
|
|
774
1153
|
/*
|
|
@@ -817,36 +1196,51 @@ export default class SessionReplayReadService {
|
|
|
817
1196
|
type: TableColumnType.ObjectID,
|
|
818
1197
|
value: data.projectId,
|
|
819
1198
|
}}
|
|
820
|
-
AND hasAny(exceptionFingerprints, [${{
|
|
821
|
-
type: TableColumnType.Text,
|
|
822
|
-
value: data.exceptionFingerprint,
|
|
823
|
-
}}])
|
|
824
1199
|
`);
|
|
825
|
-
statement.append(RETENTION_FILTER);
|
|
826
1200
|
if (data.accessibleRumApplicationIds) {
|
|
827
1201
|
statement.append(SQL ` AND rumApplicationId IN (${{
|
|
828
1202
|
type: TableColumnType.ObjectID,
|
|
829
1203
|
value: new Includes(data.accessibleRumApplicationIds),
|
|
830
1204
|
}})`);
|
|
831
1205
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1206
|
+
statement.append(SQL ` AND startTime >= ${{
|
|
1207
|
+
type: TableColumnType.DateTime64,
|
|
1208
|
+
value: startTime,
|
|
1209
|
+
}} AND startTime <= ${{
|
|
1210
|
+
type: TableColumnType.DateTime64,
|
|
1211
|
+
value: endTime,
|
|
1212
|
+
}}`);
|
|
1213
|
+
statement.append(RETENTION_FILTER);
|
|
1214
|
+
if (data.sessionId) {
|
|
1215
|
+
statement.append(SQL ` AND sessionId = ${{
|
|
1216
|
+
type: TableColumnType.Text,
|
|
1217
|
+
value: data.sessionId,
|
|
836
1218
|
}}`);
|
|
837
1219
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1220
|
+
statement.append(SQL ` AND (hasAny(exceptionFingerprints, [${{
|
|
1221
|
+
type: TableColumnType.Text,
|
|
1222
|
+
value: data.exceptionFingerprint,
|
|
1223
|
+
}}])`);
|
|
1224
|
+
if (instanceSessionIds.length > 0) {
|
|
1225
|
+
statement.append(SQL ` OR sessionId IN (${{
|
|
1226
|
+
type: TableColumnType.Text,
|
|
1227
|
+
value: new Includes(instanceSessionIds),
|
|
1228
|
+
}})`);
|
|
843
1229
|
}
|
|
1230
|
+
statement.append(")");
|
|
844
1231
|
statement.append(SQL ` GROUP BY projectId, rumApplicationId, sessionId
|
|
845
|
-
HAVING hasAny(aggExceptionFingerprints, [${{
|
|
1232
|
+
HAVING (hasAny(aggExceptionFingerprints, [${{
|
|
846
1233
|
type: TableColumnType.Text,
|
|
847
1234
|
value: data.exceptionFingerprint,
|
|
848
|
-
}}])
|
|
849
|
-
|
|
1235
|
+
}}])`);
|
|
1236
|
+
if (instanceSessionIds.length > 0) {
|
|
1237
|
+
statement.append(SQL ` OR sessionId IN (${{
|
|
1238
|
+
type: TableColumnType.Text,
|
|
1239
|
+
value: new Includes(instanceSessionIds),
|
|
1240
|
+
}})`);
|
|
1241
|
+
}
|
|
1242
|
+
statement.append(")");
|
|
1243
|
+
statement.append(SQL ` ORDER BY aggStartTime DESC
|
|
850
1244
|
LIMIT ${{
|
|
851
1245
|
type: TableColumnType.Number,
|
|
852
1246
|
value: limit,
|
|
@@ -877,7 +1271,273 @@ export default class SessionReplayReadService {
|
|
|
877
1271
|
};
|
|
878
1272
|
});
|
|
879
1273
|
}
|
|
880
|
-
|
|
1274
|
+
/*
|
|
1275
|
+
* Session ids of the pages that threw this exception, from the
|
|
1276
|
+
* exception instance table. The instance's `time` sits inside its
|
|
1277
|
+
* session, so a session that started inside the window threw inside
|
|
1278
|
+
* [startTime, endTime + max session length].
|
|
1279
|
+
*
|
|
1280
|
+
* Best-effort: the side index only ADDS live sessions to the answer, so
|
|
1281
|
+
* a failure here degrades to the finalized-only lookup with a warning
|
|
1282
|
+
* rather than failing the exception page's replay card.
|
|
1283
|
+
*
|
|
1284
|
+
* A caller-pinned sessionId narrows the lookup rather than bypassing it,
|
|
1285
|
+
* so the pin can never assert that a session threw something the
|
|
1286
|
+
* instance table has no record of it throwing.
|
|
1287
|
+
*/
|
|
1288
|
+
static async getSessionIdsForExceptionInstances(data) {
|
|
1289
|
+
const statement = SQL `
|
|
1290
|
+
SELECT DISTINCT sessionId
|
|
1291
|
+
FROM ${AnalyticsTableName.ExceptionInstance}
|
|
1292
|
+
WHERE projectId = ${{
|
|
1293
|
+
type: TableColumnType.ObjectID,
|
|
1294
|
+
value: data.projectId,
|
|
1295
|
+
}}
|
|
1296
|
+
AND fingerprint = ${{
|
|
1297
|
+
type: TableColumnType.Text,
|
|
1298
|
+
value: data.exceptionFingerprint,
|
|
1299
|
+
}}
|
|
1300
|
+
AND sessionId != ''
|
|
1301
|
+
AND time >= ${{
|
|
1302
|
+
type: TableColumnType.DateTime64,
|
|
1303
|
+
value: data.startTime,
|
|
1304
|
+
}}
|
|
1305
|
+
AND time <= ${{
|
|
1306
|
+
type: TableColumnType.DateTime64,
|
|
1307
|
+
value: new Date(data.endTime.getTime() + SESSION_REPLAY_MAX_SESSION_MS),
|
|
1308
|
+
}}
|
|
1309
|
+
`;
|
|
1310
|
+
/*
|
|
1311
|
+
* A pinned sessionId narrows this lookup; it does NOT replace it.
|
|
1312
|
+
*
|
|
1313
|
+
* Returning the pinned id unchecked made the caller's statement read
|
|
1314
|
+
* `sessionId = X AND (hasAny(fingerprints, [f]) OR sessionId IN (X))`,
|
|
1315
|
+
* whose second arm is trivially true - so the fingerprint constrained
|
|
1316
|
+
* nothing and the "Watch what the user saw" card would present any
|
|
1317
|
+
* accessible session as having observed this exception, on nothing but
|
|
1318
|
+
* a stale occurrence row. Asking the instance table whether THAT
|
|
1319
|
+
* session threw THIS fingerprint keeps the pin's real purpose (a live
|
|
1320
|
+
* session whose header has no fingerprints yet) while keeping the
|
|
1321
|
+
* claim true. A failure here answers [] and the header's mandatory
|
|
1322
|
+
* hasAny() predicate decides alone - fail closed.
|
|
1323
|
+
*/
|
|
1324
|
+
if (data.sessionId) {
|
|
1325
|
+
statement.append(SQL ` AND sessionId = ${{
|
|
1326
|
+
type: TableColumnType.Text,
|
|
1327
|
+
value: data.sessionId,
|
|
1328
|
+
}}`);
|
|
1329
|
+
}
|
|
1330
|
+
statement.append(SQL `
|
|
1331
|
+
ORDER BY sessionId ASC
|
|
1332
|
+
LIMIT ${{
|
|
1333
|
+
type: TableColumnType.Number,
|
|
1334
|
+
value: MAX_EXCEPTION_INSTANCE_SESSION_IDS,
|
|
1335
|
+
}}
|
|
1336
|
+
`);
|
|
1337
|
+
statement.append(READ_QUERY_SETTINGS);
|
|
1338
|
+
try {
|
|
1339
|
+
const dbResult = await ExceptionInstanceService.executeQuery(statement);
|
|
1340
|
+
const response = await dbResult.json();
|
|
1341
|
+
return (response.data || [])
|
|
1342
|
+
.map((row) => {
|
|
1343
|
+
return readString(row, "sessionId");
|
|
1344
|
+
})
|
|
1345
|
+
.filter((sessionId) => {
|
|
1346
|
+
return sessionId.length > 0;
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
catch (err) {
|
|
1350
|
+
logger.warn("SessionReplayReadService: could not look up exception instances by session; answering from finalized headers only");
|
|
1351
|
+
logger.warn(err);
|
|
1352
|
+
return [];
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
/*
|
|
1356
|
+
* Recording activity for one application over the last 24 hours, for
|
|
1357
|
+
* the health surface. No GROUP BY and no payload: uniqExact over the
|
|
1358
|
+
* sort-key range for the counts, and an ORDER BY startTime DESC LIMIT 1
|
|
1359
|
+
* (read in sort-key order, stops after one granule) for the most recent
|
|
1360
|
+
* start, which is NOT bounded to 24h so "the most recent was 3 days
|
|
1361
|
+
* ago" can be said when today is quiet.
|
|
1362
|
+
*
|
|
1363
|
+
* "Playable" is counted by subtraction: a session is unplayable only
|
|
1364
|
+
* when its FINALIZED row says it holds no chunks or was sealed as
|
|
1365
|
+
* recording-lost. Every other session - live, or finalized with footage
|
|
1366
|
+
* - can be watched. Counted that way because a finalized session still
|
|
1367
|
+
* has its provisional row on disk until a merge runs, and that row
|
|
1368
|
+
* would otherwise count a lost recording as live.
|
|
1369
|
+
*
|
|
1370
|
+
* ClickHouse trouble answers null (the UI says "unknown"), never 0:
|
|
1371
|
+
* "no sessions" and "could not count" are different diagnoses.
|
|
1372
|
+
*/
|
|
1373
|
+
static async getApplicationActivitySummary(data) {
|
|
1374
|
+
var _a;
|
|
1375
|
+
const nowUnixMs = (_a = data.nowUnixMs) !== null && _a !== void 0 ? _a : Date.now();
|
|
1376
|
+
const cacheKey = `${data.projectId.toString()}:${data.rumApplicationId.toString()}`;
|
|
1377
|
+
const cached = activitySummaryCache.get(cacheKey);
|
|
1378
|
+
if (cached && cached.expiresAt > nowUnixMs) {
|
|
1379
|
+
return cached.summary;
|
|
1380
|
+
}
|
|
1381
|
+
const summary = await SessionReplayReadService.readApplicationActivitySummary({
|
|
1382
|
+
projectId: data.projectId,
|
|
1383
|
+
rumApplicationId: data.rumApplicationId,
|
|
1384
|
+
nowUnixMs: nowUnixMs,
|
|
1385
|
+
});
|
|
1386
|
+
/*
|
|
1387
|
+
* Coarse LRU: evict the oldest entry when full and the key is new, so
|
|
1388
|
+
* a burst of distinct applications cannot grow the map without bound.
|
|
1389
|
+
*/
|
|
1390
|
+
if (activitySummaryCache.size >= MAX_ACTIVITY_SUMMARY_CACHE_ENTRIES &&
|
|
1391
|
+
!activitySummaryCache.has(cacheKey)) {
|
|
1392
|
+
const oldest = activitySummaryCache
|
|
1393
|
+
.keys()
|
|
1394
|
+
.next().value;
|
|
1395
|
+
if (oldest !== undefined) {
|
|
1396
|
+
activitySummaryCache.delete(oldest);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
activitySummaryCache.delete(cacheKey);
|
|
1400
|
+
activitySummaryCache.set(cacheKey, {
|
|
1401
|
+
summary: summary,
|
|
1402
|
+
expiresAt: nowUnixMs + SESSION_REPLAY_ACTIVITY_SUMMARY_CACHE_TTL_MS,
|
|
1403
|
+
});
|
|
1404
|
+
return summary;
|
|
1405
|
+
}
|
|
1406
|
+
static async readApplicationActivitySummary(data) {
|
|
1407
|
+
const countsStatement = SQL `
|
|
1408
|
+
SELECT
|
|
1409
|
+
toFloat64(uniqExact(sessionId)) AS sessionCount,
|
|
1410
|
+
toFloat64(uniqExactIf(sessionId, isFinalized AND (chunkCount = 0 OR sealedReason = ${{
|
|
1411
|
+
type: TableColumnType.Text,
|
|
1412
|
+
value: SessionReplaySealedReason.RecordingLost,
|
|
1413
|
+
}}))) AS unplayableCount
|
|
1414
|
+
FROM ${AnalyticsTableName.RumSession}
|
|
1415
|
+
WHERE projectId = ${{
|
|
1416
|
+
type: TableColumnType.ObjectID,
|
|
1417
|
+
value: data.projectId,
|
|
1418
|
+
}}
|
|
1419
|
+
AND rumApplicationId = ${{
|
|
1420
|
+
type: TableColumnType.ObjectID,
|
|
1421
|
+
value: data.rumApplicationId,
|
|
1422
|
+
}}
|
|
1423
|
+
AND startTime >= ${{
|
|
1424
|
+
type: TableColumnType.DateTime64,
|
|
1425
|
+
value: new Date(data.nowUnixMs - 24 * 60 * 60 * 1000),
|
|
1426
|
+
}}
|
|
1427
|
+
`;
|
|
1428
|
+
countsStatement.append(RETENTION_FILTER);
|
|
1429
|
+
countsStatement.append(READ_QUERY_SETTINGS);
|
|
1430
|
+
const lastStartStatement = SQL `
|
|
1431
|
+
SELECT
|
|
1432
|
+
toFloat64(toUnixTimestamp64Milli(startTime)) AS lastStartUnixMs,
|
|
1433
|
+
/*
|
|
1434
|
+
* The newest session's recorder capabilities, read off the same row
|
|
1435
|
+
* that answers "when did recording last start". Named directly (not
|
|
1436
|
+
* through the argMax alias set) because this statement has no GROUP
|
|
1437
|
+
* BY: it is one row, read in sort-key order, LIMIT 1.
|
|
1438
|
+
*/
|
|
1439
|
+
attributes AS aggAttributes
|
|
1440
|
+
FROM ${AnalyticsTableName.RumSession}
|
|
1441
|
+
WHERE projectId = ${{
|
|
1442
|
+
type: TableColumnType.ObjectID,
|
|
1443
|
+
value: data.projectId,
|
|
1444
|
+
}}
|
|
1445
|
+
AND rumApplicationId = ${{
|
|
1446
|
+
type: TableColumnType.ObjectID,
|
|
1447
|
+
value: data.rumApplicationId,
|
|
1448
|
+
}}
|
|
1449
|
+
`;
|
|
1450
|
+
lastStartStatement.append(RETENTION_FILTER);
|
|
1451
|
+
lastStartStatement.append(" ORDER BY startTime DESC LIMIT 1");
|
|
1452
|
+
lastStartStatement.append(READ_QUERY_SETTINGS);
|
|
1453
|
+
try {
|
|
1454
|
+
const [countsResult, lastStartResult] = await Promise.all([
|
|
1455
|
+
RumSessionService.executeQuery(countsStatement),
|
|
1456
|
+
RumSessionService.executeQuery(lastStartStatement),
|
|
1457
|
+
]);
|
|
1458
|
+
const countsResponse = await countsResult.json();
|
|
1459
|
+
const lastStartResponse = await lastStartResult.json();
|
|
1460
|
+
const countsRow = (countsResponse.data || [])[0];
|
|
1461
|
+
const lastStartRow = (lastStartResponse.data ||
|
|
1462
|
+
[])[0];
|
|
1463
|
+
const sessionCount = countsRow
|
|
1464
|
+
? readNumber(countsRow, "sessionCount")
|
|
1465
|
+
: 0;
|
|
1466
|
+
const unplayableCount = countsRow
|
|
1467
|
+
? readNumber(countsRow, "unplayableCount")
|
|
1468
|
+
: 0;
|
|
1469
|
+
const lastStartUnixMs = lastStartRow
|
|
1470
|
+
? readNumber(lastStartRow, "lastStartUnixMs")
|
|
1471
|
+
: 0;
|
|
1472
|
+
/*
|
|
1473
|
+
* An empty list means "the newest session declared none" (an old
|
|
1474
|
+
* recorder artifact), which is not the same as "we could not tell" -
|
|
1475
|
+
* but the health copy renders both as "not reported yet", and
|
|
1476
|
+
* claiming a recorder has NO capabilities would be a stronger
|
|
1477
|
+
* statement than the row supports. So an empty list answers null and
|
|
1478
|
+
* only a non-empty one is reported.
|
|
1479
|
+
*/
|
|
1480
|
+
const recorderCapabilities = lastStartRow
|
|
1481
|
+
? readRecorderCapabilities(lastStartRow)
|
|
1482
|
+
: [];
|
|
1483
|
+
return {
|
|
1484
|
+
sessionsLast24h: sessionCount,
|
|
1485
|
+
playableSessionsLast24h: Math.max(0, sessionCount - unplayableCount),
|
|
1486
|
+
lastSessionStartedAt: lastStartUnixMs > 0 ? new Date(lastStartUnixMs) : null,
|
|
1487
|
+
recorderCapabilities: recorderCapabilities.length > 0 ? recorderCapabilities : null,
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
catch (err) {
|
|
1491
|
+
logger.warn("SessionReplayReadService: could not read the application activity summary");
|
|
1492
|
+
logger.warn(err);
|
|
1493
|
+
return {
|
|
1494
|
+
sessionsLast24h: null,
|
|
1495
|
+
playableSessionsLast24h: null,
|
|
1496
|
+
lastSessionStartedAt: null,
|
|
1497
|
+
recorderCapabilities: null,
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
/* The HAVING/ORDER BY expression for a sort key. */
|
|
1502
|
+
static getSortExpression(sortBy) {
|
|
1503
|
+
switch (sortBy) {
|
|
1504
|
+
case "durationMs":
|
|
1505
|
+
return "aggDurationMs";
|
|
1506
|
+
case "errorCount":
|
|
1507
|
+
return "aggErrorCount";
|
|
1508
|
+
case "frustration":
|
|
1509
|
+
return FRUSTRATION_TOTAL_EXPRESSION;
|
|
1510
|
+
case "startTime":
|
|
1511
|
+
default:
|
|
1512
|
+
return "aggStartTime";
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
/* The cursor value of a row under a sort key: what the expression above yields. */
|
|
1516
|
+
static getSortValue(sortBy, item) {
|
|
1517
|
+
switch (sortBy) {
|
|
1518
|
+
case "durationMs":
|
|
1519
|
+
return item.durationMs;
|
|
1520
|
+
case "errorCount":
|
|
1521
|
+
return item.errorCount;
|
|
1522
|
+
case "frustration":
|
|
1523
|
+
return (item.rageClickCount +
|
|
1524
|
+
item.deadClickCount +
|
|
1525
|
+
item.errorClickCount +
|
|
1526
|
+
item.refreshRageCount);
|
|
1527
|
+
case "startTime":
|
|
1528
|
+
default:
|
|
1529
|
+
return item.startTime.getTime();
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
/*
|
|
1533
|
+
* Every list predicate, in cost order: booleans and equality over
|
|
1534
|
+
* aliases first, IN lists next, array membership after, and the
|
|
1535
|
+
* substring predicates (tags, urlPrefix, search) LAST. ClickHouse
|
|
1536
|
+
* evaluates HAVING per group after aggregation, so the order does not
|
|
1537
|
+
* change what is scanned, but a cheap predicate that fails first spares
|
|
1538
|
+
* the string work for every group it eliminates.
|
|
1539
|
+
*/
|
|
1540
|
+
static appendListHavingFilters(statement, filters, includeIdentifiedUserLabel) {
|
|
881
1541
|
if (filters.hasError !== undefined) {
|
|
882
1542
|
statement.append(SQL ` AND aggHasError = ${{
|
|
883
1543
|
type: TableColumnType.Boolean,
|
|
@@ -895,8 +1555,9 @@ export default class SessionReplayReadService {
|
|
|
895
1555
|
* honour false, so accepting the value and ignoring it returned the
|
|
896
1556
|
* whole unfiltered list with a 200 and no indication why.
|
|
897
1557
|
*/
|
|
898
|
-
|
|
899
|
-
|
|
1558
|
+
statement.append(filters.hasFrustration
|
|
1559
|
+
? ` AND ${FRUSTRATION_TOTAL_EXPRESSION} > 0`
|
|
1560
|
+
: ` AND ${FRUSTRATION_TOTAL_EXPRESSION} = 0`);
|
|
900
1561
|
}
|
|
901
1562
|
if (filters.isFinalized !== undefined) {
|
|
902
1563
|
statement.append(SQL ` AND aggIsFinalized = ${{
|
|
@@ -904,6 +1565,27 @@ export default class SessionReplayReadService {
|
|
|
904
1565
|
value: filters.isFinalized,
|
|
905
1566
|
}}`);
|
|
906
1567
|
}
|
|
1568
|
+
if (filters.hasIdentifiedUser !== undefined) {
|
|
1569
|
+
/*
|
|
1570
|
+
* The digest column, not the label: it is under the ordinary session
|
|
1571
|
+
* ACL, and "did somebody identify" discloses nothing about who.
|
|
1572
|
+
*/
|
|
1573
|
+
statement.append(filters.hasIdentifiedUser
|
|
1574
|
+
? " AND aggIdentifiedUserKey != ''"
|
|
1575
|
+
: " AND aggIdentifiedUserKey = ''");
|
|
1576
|
+
}
|
|
1577
|
+
if (filters.isPlayable !== undefined) {
|
|
1578
|
+
/*
|
|
1579
|
+
* A live session is playable (its chunks are being written); a
|
|
1580
|
+
* finalized one only when the finalizer counted chunks and did not
|
|
1581
|
+
* seal it as lost.
|
|
1582
|
+
*/
|
|
1583
|
+
const playable = `((aggIsFinalized = 0 OR aggChunkCount > 0) AND aggSealedReason != '${SessionReplaySealedReason.RecordingLost}')`;
|
|
1584
|
+
statement.append(filters.isPlayable ? ` AND ${playable}` : ` AND NOT ${playable}`);
|
|
1585
|
+
}
|
|
1586
|
+
if (filters.hasTraces !== undefined) {
|
|
1587
|
+
statement.append(filters.hasTraces ? " AND aggTraceCount > 0" : " AND aggTraceCount = 0");
|
|
1588
|
+
}
|
|
907
1589
|
if (filters.triggerReasons && filters.triggerReasons.length > 0) {
|
|
908
1590
|
statement.append(SQL ` AND aggTriggerReason IN (${{
|
|
909
1591
|
type: TableColumnType.Text,
|
|
@@ -942,16 +1624,10 @@ export default class SessionReplayReadService {
|
|
|
942
1624
|
}
|
|
943
1625
|
if (filters.route) {
|
|
944
1626
|
/*
|
|
945
|
-
* `has`, not a Search/LIKE
|
|
946
|
-
*
|
|
947
|
-
* performance fix" - a lowerUTF8 arrayExists has no bloom
|
|
948
|
-
* pre-filter and full-scans the table.
|
|
949
|
-
*
|
|
950
|
-
* The argMax expression is appended as raw SQL rather than
|
|
951
|
-
* interpolated into the template: a plain string substituted into
|
|
952
|
-
* an SQL`` literal is bound as an Identifier and would be quoted.
|
|
1627
|
+
* `has`, not a Search/LIKE: exact membership is the cheap array
|
|
1628
|
+
* path. Over the argMax alias, never the raw column.
|
|
953
1629
|
*/
|
|
954
|
-
statement.append(
|
|
1630
|
+
statement.append(" AND has(aggRoutes, ");
|
|
955
1631
|
statement.append(SQL `${{
|
|
956
1632
|
type: TableColumnType.Text,
|
|
957
1633
|
value: filters.route,
|
|
@@ -964,6 +1640,99 @@ export default class SessionReplayReadService {
|
|
|
964
1640
|
value: filters.minDurationMs,
|
|
965
1641
|
}}`);
|
|
966
1642
|
}
|
|
1643
|
+
if (filters.tags) {
|
|
1644
|
+
/*
|
|
1645
|
+
* Every pair must match. mapContains first so an absent key never
|
|
1646
|
+
* matches the empty string a Map subscript returns for it. Bounded
|
|
1647
|
+
* by the number of tags a session can even carry.
|
|
1648
|
+
*/
|
|
1649
|
+
const pairs = Object.entries(filters.tags)
|
|
1650
|
+
.filter(([key, value]) => {
|
|
1651
|
+
return key.length > 0 && typeof value === "string";
|
|
1652
|
+
})
|
|
1653
|
+
.slice(0, SESSION_REPLAY_MAX_TAG_KEYS);
|
|
1654
|
+
for (const [key, value] of pairs) {
|
|
1655
|
+
statement.append(" AND mapContains(aggTags, ");
|
|
1656
|
+
statement.append(SQL `${{
|
|
1657
|
+
type: TableColumnType.Text,
|
|
1658
|
+
value: key,
|
|
1659
|
+
}}) AND aggTags[${{
|
|
1660
|
+
type: TableColumnType.Text,
|
|
1661
|
+
value: key,
|
|
1662
|
+
}}] = ${{
|
|
1663
|
+
type: TableColumnType.Text,
|
|
1664
|
+
value: value,
|
|
1665
|
+
}}`);
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
if (filters.urlPrefix) {
|
|
1669
|
+
/*
|
|
1670
|
+
* "sessions that touched /checkout/*": a prefix over every route the
|
|
1671
|
+
* session visited and over the entry URL, which for a pre-migration
|
|
1672
|
+
* session is the only URL the header holds.
|
|
1673
|
+
*
|
|
1674
|
+
* The stored values are scrubbed ABSOLUTE urls (https://host/path),
|
|
1675
|
+
* but the filter a human types is a PATH - the search box routes any
|
|
1676
|
+
* value beginning with "/" here, and the docs promise `url:/checkout`
|
|
1677
|
+
* outright. Matching only the full string meant that documented
|
|
1678
|
+
* search never matched anything, in any project, with no error to
|
|
1679
|
+
* say so. So the path of each route and of the entry URL is matched
|
|
1680
|
+
* as well as the whole URL: an absolute prefix still matches on the
|
|
1681
|
+
* first arm, a path prefix on the second. ClickHouse's path() returns
|
|
1682
|
+
* the path component without host or query, which is exactly the
|
|
1683
|
+
* shape the recorder's route list is scrubbed down to.
|
|
1684
|
+
*/
|
|
1685
|
+
const prefixParameter = {
|
|
1686
|
+
type: TableColumnType.Text,
|
|
1687
|
+
value: filters.urlPrefix,
|
|
1688
|
+
};
|
|
1689
|
+
statement.append(" AND (arrayExists(r -> startsWith(r, ");
|
|
1690
|
+
statement.append(SQL `${prefixParameter}`);
|
|
1691
|
+
statement.append(") OR startsWith(path(r), ");
|
|
1692
|
+
statement.append(SQL `${prefixParameter}`);
|
|
1693
|
+
statement.append("), aggRoutes) OR startsWith(aggEntryUrl, ");
|
|
1694
|
+
statement.append(SQL `${prefixParameter}`);
|
|
1695
|
+
statement.append(") OR startsWith(path(aggEntryUrl), ");
|
|
1696
|
+
statement.append(SQL `${prefixParameter}`);
|
|
1697
|
+
statement.append("))");
|
|
1698
|
+
}
|
|
1699
|
+
if (filters.search) {
|
|
1700
|
+
SessionReplayReadService.appendSearchPredicate(statement, filters.search, includeIdentifiedUserLabel);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
/*
|
|
1704
|
+
* Free-text search, last of the predicates because it is the only one
|
|
1705
|
+
* that does substring work per group. The identified user label is
|
|
1706
|
+
* searched ONLY when the caller may read it: without that gate a caller
|
|
1707
|
+
* denied the label could ask "is jane@example.com here" and read every
|
|
1708
|
+
* other field of the answer.
|
|
1709
|
+
*/
|
|
1710
|
+
static appendSearchPredicate(statement, search, includeIdentifiedUserLabel) {
|
|
1711
|
+
const term = search
|
|
1712
|
+
.trim()
|
|
1713
|
+
.substring(0, SESSION_REPLAY_LIST_SEARCH_MAX_LENGTH);
|
|
1714
|
+
if (!term) {
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
const textParameter = {
|
|
1718
|
+
type: TableColumnType.Text,
|
|
1719
|
+
value: term,
|
|
1720
|
+
};
|
|
1721
|
+
statement.append(" AND (startsWith(sessionId, ");
|
|
1722
|
+
statement.append(SQL `${textParameter})`);
|
|
1723
|
+
statement.append(" OR positionCaseInsensitiveUTF8(aggEntryUrl, ");
|
|
1724
|
+
statement.append(SQL `${textParameter}) > 0`);
|
|
1725
|
+
statement.append(" OR positionCaseInsensitiveUTF8(aggExitUrl, ");
|
|
1726
|
+
statement.append(SQL `${textParameter}) > 0`);
|
|
1727
|
+
statement.append(" OR arrayExists(r -> positionCaseInsensitiveUTF8(r, ");
|
|
1728
|
+
statement.append(SQL `${textParameter}) > 0, aggRoutes)`);
|
|
1729
|
+
statement.append(` OR has(${argMaxColumn("traceIds")}, `);
|
|
1730
|
+
statement.append(SQL `${textParameter})`);
|
|
1731
|
+
if (includeIdentifiedUserLabel) {
|
|
1732
|
+
statement.append(" OR positionCaseInsensitiveUTF8(aggIdentifiedUserLabel, ");
|
|
1733
|
+
statement.append(SQL `${textParameter}) > 0`);
|
|
1734
|
+
}
|
|
1735
|
+
statement.append(")");
|
|
967
1736
|
}
|
|
968
1737
|
}
|
|
969
1738
|
__decorate([
|
|
@@ -983,13 +1752,19 @@ __decorate([
|
|
|
983
1752
|
__metadata("design:type", Function),
|
|
984
1753
|
__metadata("design:paramtypes", [Object]),
|
|
985
1754
|
__metadata("design:returntype", Promise)
|
|
986
|
-
], SessionReplayReadService, "
|
|
1755
|
+
], SessionReplayReadService, "getSessionIdentity", null);
|
|
987
1756
|
__decorate([
|
|
988
1757
|
CaptureSpan(),
|
|
989
1758
|
__metadata("design:type", Function),
|
|
990
1759
|
__metadata("design:paramtypes", [Object]),
|
|
991
1760
|
__metadata("design:returntype", Promise)
|
|
992
|
-
], SessionReplayReadService, "
|
|
1761
|
+
], SessionReplayReadService, "getExpiredSessionInfo", null);
|
|
1762
|
+
__decorate([
|
|
1763
|
+
CaptureSpan(),
|
|
1764
|
+
__metadata("design:type", Function),
|
|
1765
|
+
__metadata("design:paramtypes", [Object]),
|
|
1766
|
+
__metadata("design:returntype", Promise)
|
|
1767
|
+
], SessionReplayReadService, "getManifest", null);
|
|
993
1768
|
__decorate([
|
|
994
1769
|
CaptureSpan(),
|
|
995
1770
|
__metadata("design:type", Function),
|
|
@@ -1002,4 +1777,10 @@ __decorate([
|
|
|
1002
1777
|
__metadata("design:paramtypes", [Object]),
|
|
1003
1778
|
__metadata("design:returntype", Promise)
|
|
1004
1779
|
], SessionReplayReadService, "getSessionsForException", null);
|
|
1780
|
+
__decorate([
|
|
1781
|
+
CaptureSpan(),
|
|
1782
|
+
__metadata("design:type", Function),
|
|
1783
|
+
__metadata("design:paramtypes", [Object]),
|
|
1784
|
+
__metadata("design:returntype", Promise)
|
|
1785
|
+
], SessionReplayReadService, "getApplicationActivitySummary", null);
|
|
1005
1786
|
//# sourceMappingURL=SessionReplayReadService.js.map
|