@oneuptime/common 14.0.2 → 14.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/Models/DatabaseModels/AlertFeed.ts +2 -0
  2. package/Models/DatabaseModels/IncidentAlert.ts +540 -0
  3. package/Models/DatabaseModels/IncidentFeed.ts +2 -0
  4. package/Models/DatabaseModels/Index.ts +2 -0
  5. package/Models/DatabaseModels/Project.ts +60 -0
  6. package/Models/DatabaseModels/RumApplication.ts +52 -1
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.ts +28 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.ts +89 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  10. package/Server/Services/IncidentAlertService.ts +1811 -0
  11. package/Server/Services/IncidentService.ts +210 -10
  12. package/Server/Services/IncidentStateTimelineService.ts +24 -0
  13. package/Server/Services/Index.ts +2 -0
  14. package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +24 -2
  15. package/Tests/App/Dashboard/BulkIncidentLinkActions.test.tsx +869 -0
  16. package/Tests/App/Dashboard/CephMetricTooltips.test.tsx +1777 -0
  17. package/Tests/App/Dashboard/CloudFleetSummary.test.ts +8 -0
  18. package/Tests/App/Dashboard/CloudMetricTooltips.test.tsx +887 -0
  19. package/Tests/App/Dashboard/ContainerHostMetricTooltips.test.tsx +1309 -0
  20. package/Tests/App/Dashboard/DiscoveryScanLivePage.test.tsx +39 -0
  21. package/Tests/App/Dashboard/DockerSwarmClustersListTooltips.test.tsx +414 -0
  22. package/Tests/App/Dashboard/DockerSwarmMetricTooltips.test.ts +796 -0
  23. package/Tests/App/Dashboard/DockerSwarmOverviewTooltips.test.tsx +747 -0
  24. package/Tests/App/Dashboard/ExceptionMonitorEnvironmentFilter.test.tsx +38 -18
  25. package/Tests/App/Dashboard/HostDetailViewTooltips.test.tsx +725 -0
  26. package/Tests/App/Dashboard/HostListColumnTooltips.test.tsx +472 -0
  27. package/Tests/App/Dashboard/HostMetricTooltips.test.ts +709 -0
  28. package/Tests/App/Dashboard/HostOverviewTooltips.test.tsx +698 -0
  29. package/Tests/App/Dashboard/HostTooltipHarness.ts +124 -0
  30. package/Tests/App/Dashboard/HostsListTooltips.test.tsx +395 -0
  31. package/Tests/App/Dashboard/IncidentAlertLinkDialog.test.tsx +691 -0
  32. package/Tests/App/Dashboard/IncidentAlertLinkHelpers.test.ts +458 -0
  33. package/Tests/App/Dashboard/IncidentAlertLinkPages.test.tsx +828 -0
  34. package/Tests/App/Dashboard/IncidentAlertLinkSideMenus.test.tsx +231 -0
  35. package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +689 -0
  36. package/Tests/App/Dashboard/IoTMetricTooltips.test.tsx +1123 -0
  37. package/Tests/App/Dashboard/KubernetesClusterMetricTooltips.test.ts +837 -0
  38. package/Tests/App/Dashboard/KubernetesClusterOverviewTooltips.test.tsx +699 -0
  39. package/Tests/App/Dashboard/KubernetesContainersTabTooltips.test.tsx +320 -0
  40. package/Tests/App/Dashboard/KubernetesCostTooltips.test.tsx +502 -0
  41. package/Tests/App/Dashboard/KubernetesInsightsTooltips.test.tsx +222 -0
  42. package/Tests/App/Dashboard/KubernetesResourceMetricTooltips.test.ts +561 -0
  43. package/Tests/App/Dashboard/KubernetesResourcePagesTooltips.test.tsx +1168 -0
  44. package/Tests/App/Dashboard/LinkIncidentAlertModal.test.tsx +154 -0
  45. package/Tests/App/Dashboard/MetricDescriptionRules.ts +170 -0
  46. package/Tests/App/Dashboard/MetricDescriptionsCatalog.test.ts +206 -0
  47. package/Tests/App/Dashboard/MetricDescriptionsConsistency.test.ts +275 -0
  48. package/Tests/App/Dashboard/NetworkDeviceTooltips.test.tsx +852 -0
  49. package/Tests/App/Dashboard/NetworkDiagnosticsTooltips.test.tsx +556 -0
  50. package/Tests/App/Dashboard/NetworkMetricTooltips.test.ts +1348 -0
  51. package/Tests/App/Dashboard/NetworkSiteTooltips.test.tsx +679 -0
  52. package/Tests/App/Dashboard/ProxmoxMetricTooltips.test.ts +592 -0
  53. package/Tests/App/Dashboard/ProxmoxOverviewTooltips.test.tsx +1002 -0
  54. package/Tests/App/Dashboard/ProxmoxResourcePageTooltips.test.tsx +923 -0
  55. package/Tests/App/Dashboard/ResourceOverviewTileTooltips.test.tsx +1066 -0
  56. package/Tests/App/Dashboard/RumOverviewPage.test.tsx +1195 -0
  57. package/Tests/App/Dashboard/ServerlessMetricTooltips.test.tsx +474 -0
  58. package/Tests/App/Dashboard/ServiceMetricTooltips.test.tsx +772 -0
  59. package/Tests/App/Dashboard/SessionReplayAuditTable.test.tsx +56 -1
  60. package/Tests/App/Dashboard/TelemetryMetricsSignals.test.ts +1207 -0
  61. package/Tests/App/Dashboard/TelemetryResourceTabTooltips.test.tsx +437 -0
  62. package/Tests/App/Dashboard/VMwareMetricTooltips.test.ts +801 -0
  63. package/Tests/App/Dashboard/VMwareOverviewTooltips.test.tsx +601 -0
  64. package/Tests/App/Dashboard/VMwareResourcePagesTooltips.test.tsx +646 -0
  65. package/Tests/App/Dashboard/VMwareTooltipHarness.ts +145 -0
  66. package/Tests/Models/DatabaseModels/IncidentAlertModel.test.ts +554 -0
  67. package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +117 -0
  68. package/Tests/Server/Infrastructure/Postgres/AddIncidentAlertMigration.test.ts +341 -0
  69. package/Tests/Server/Infrastructure/Postgres/AddSessionReplaySameOriginTracePropagationMigration.test.ts +247 -0
  70. package/Tests/Server/Services/IncidentAlertPostgres.test.ts +903 -0
  71. package/Tests/Server/Services/IncidentAlertRegistration.test.ts +96 -0
  72. package/Tests/Server/Services/IncidentAlertService.test.ts +2225 -0
  73. package/Tests/Server/Services/IncidentAlertStateCascade.test.ts +970 -0
  74. package/Tests/Server/Services/IncidentCreateFromAlerts.test.ts +1284 -0
  75. package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +103 -0
  76. package/Tests/Types/Rum/WebVitals.test.ts +173 -0
  77. package/Tests/UI/Components/FieldLabelInfoCardTooltip.test.tsx +413 -0
  78. package/Tests/UI/Components/InfoTooltip.test.tsx +242 -0
  79. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDelete.test.tsx +207 -3
  80. package/Tests/UI/Components/ModelTable/BaseModelTableBulkDeleteVerb.test.tsx +367 -0
  81. package/Tests/UI/Components/ModelTable/BaseModelTableCreateButtonTitle.test.tsx +275 -0
  82. package/Tests/UI/Components/TableHeaderTooltip.test.tsx +662 -0
  83. package/Tests/UI/Rum/PrivacySummaryCard.test.tsx +166 -9
  84. package/Tests/UI/Rum/RecordingHealthTooltips.test.tsx +447 -0
  85. package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +164 -0
  86. package/Tests/UI/Rum/ReplayFrameCapture.test.ts +6750 -0
  87. package/Tests/UI/Rum/ReplayRail.test.tsx +723 -12
  88. package/Tests/UI/Rum/ReplayRailDetail.test.tsx +86 -0
  89. package/Tests/UI/Rum/ReplayScreenshot.test.ts +2124 -0
  90. package/Tests/UI/Rum/ReplayScreenshotActions.test.tsx +3288 -0
  91. package/Tests/UI/Rum/ReplayStage.test.tsx +62 -0
  92. package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +983 -2
  93. package/Tests/UI/Rum/SessionReplaySettingsPolicyLoading.test.tsx +149 -0
  94. package/Tests/UI/Rum/SessionReplaySetupGuide.test.tsx +107 -6
  95. package/Tests/UI/Rum/SessionReplayUsersTableTooltips.test.tsx +330 -0
  96. package/Tests/UI/Rum/UserFlowTooltips.test.tsx +886 -0
  97. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
  98. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  99. package/Tests/UI/Utils/PermissionGate.test.ts +76 -0
  100. package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +696 -0
  101. package/Tests/Utils/Rum/SessionTraceState.test.ts +239 -0
  102. package/Types/Incident/IncidentAlertLink.ts +38 -0
  103. package/Types/Permission.ts +48 -0
  104. package/Types/Rum/SessionReplay.ts +25 -6
  105. package/Types/Rum/WebVitals.ts +52 -0
  106. package/UI/Components/Detail/FieldLabel.tsx +8 -0
  107. package/UI/Components/InfoCard/InfoCard.tsx +47 -1
  108. package/UI/Components/ModelTable/BaseModelTable.tsx +95 -17
  109. package/UI/Components/ModelTable/Column.ts +5 -0
  110. package/UI/Components/Table/TableHeader.tsx +47 -2
  111. package/UI/Components/Table/Types/Column.ts +7 -0
  112. package/UI/Components/Tooltip/InfoTooltip.tsx +78 -0
  113. package/UI/Utils/PermissionGate.ts +11 -2
  114. package/Utils/Incident/IncidentFromAlerts.ts +516 -0
  115. package/Utils/Rum/SessionTraceState.ts +212 -0
  116. package/build/dist/Models/DatabaseModels/AlertFeed.js +2 -0
  117. package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
  118. package/build/dist/Models/DatabaseModels/IncidentAlert.js +553 -0
  119. package/build/dist/Models/DatabaseModels/IncidentAlert.js.map +1 -0
  120. package/build/dist/Models/DatabaseModels/IncidentFeed.js +2 -0
  121. package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
  122. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  123. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  124. package/build/dist/Models/DatabaseModels/Project.js +62 -0
  125. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  126. package/build/dist/Models/DatabaseModels/RumApplication.js +53 -1
  127. package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
  128. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js +20 -0
  129. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794800000000-AddSessionReplaySameOriginTracePropagation.js.map +1 -0
  130. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js +42 -0
  131. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794900000000-AddIncidentAlert.js.map +1 -0
  132. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  133. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  134. package/build/dist/Server/Services/IncidentAlertService.js +1370 -0
  135. package/build/dist/Server/Services/IncidentAlertService.js.map +1 -0
  136. package/build/dist/Server/Services/IncidentService.js +164 -12
  137. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  138. package/build/dist/Server/Services/IncidentStateTimelineService.js +21 -0
  139. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  140. package/build/dist/Server/Services/Index.js +2 -0
  141. package/build/dist/Server/Services/Index.js.map +1 -1
  142. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +8 -0
  143. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
  144. package/build/dist/Types/Incident/IncidentAlertLink.js +34 -0
  145. package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -0
  146. package/build/dist/Types/Permission.js +42 -0
  147. package/build/dist/Types/Permission.js.map +1 -1
  148. package/build/dist/Types/Rum/WebVitals.js +25 -0
  149. package/build/dist/Types/Rum/WebVitals.js.map +1 -1
  150. package/build/dist/UI/Components/Detail/FieldLabel.js +2 -0
  151. package/build/dist/UI/Components/Detail/FieldLabel.js.map +1 -1
  152. package/build/dist/UI/Components/InfoCard/InfoCard.js +22 -1
  153. package/build/dist/UI/Components/InfoCard/InfoCard.js.map +1 -1
  154. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +66 -14
  155. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  156. package/build/dist/UI/Components/Table/TableHeader.js +18 -2
  157. package/build/dist/UI/Components/Table/TableHeader.js.map +1 -1
  158. package/build/dist/UI/Components/Tooltip/InfoTooltip.js +28 -0
  159. package/build/dist/UI/Components/Tooltip/InfoTooltip.js.map +1 -0
  160. package/build/dist/UI/Utils/PermissionGate.js +4 -2
  161. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  162. package/build/dist/Utils/Incident/IncidentFromAlerts.js +306 -0
  163. package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -0
  164. package/build/dist/Utils/Rum/SessionTraceState.js +154 -0
  165. package/build/dist/Utils/Rum/SessionTraceState.js.map +1 -0
  166. package/package.json +1 -1
@@ -111,6 +111,10 @@ import IncidentAIContextBuilder, {
111
111
  AIGenerationContext,
112
112
  IncidentContextData,
113
113
  } from "../Utils/AI/IncidentAIContextBuilder";
114
+ import IncidentAlertService, {
115
+ LinkAlertsToIncidentResult,
116
+ } from "./IncidentAlertService";
117
+ import { INCIDENT_ALERT_IDS_TO_LINK_KEY } from "../../Types/Incident/IncidentAlertLink";
114
118
 
115
119
  // key is incidentId for this dictionary.
116
120
  type UpdateCarryForward = Dictionary<{
@@ -120,6 +124,15 @@ type UpdateCarryForward = Dictionary<{
120
124
  newMonitorChangeStatusIdTo: ObjectID | undefined;
121
125
  }>;
122
126
 
127
+ /*
128
+ * What onBeforeCreate hands to onCreateSuccess. Null unless the incident is
129
+ * being declared from alerts.
130
+ */
131
+ type IncidentCreateCarryForward = {
132
+ // Validated, deduplicated alert ids to link once the incident exists.
133
+ alertIdsToLink: Array<ObjectID>;
134
+ } | null;
135
+
123
136
  type IncidentUpdatePayload = {
124
137
  postmortemNote?: string | null;
125
138
  title?: string | null;
@@ -783,6 +796,30 @@ export class Service extends DatabaseService<Model> {
783
796
  const projectId: ObjectID =
784
797
  createBy.props.tenantId || createBy.data.projectId!;
785
798
 
799
+ /*
800
+ * Declaring the incident from alerts. The alert ids are checked here,
801
+ * before the incident number is taken and before anything is written, so
802
+ * a bad id (a typo, another project's alert, an alert the caller cannot
803
+ * see, too many alerts) rejects the whole request instead of leaving an
804
+ * incident behind that is missing some of its alerts. The alerts are
805
+ * linked in onCreateSuccess, once the incident exists.
806
+ */
807
+ let carryForward: IncidentCreateCarryForward = null;
808
+
809
+ const alertIdsToLink: unknown =
810
+ createBy.miscDataProps?.[INCIDENT_ALERT_IDS_TO_LINK_KEY];
811
+
812
+ if (alertIdsToLink !== undefined && alertIdsToLink !== null) {
813
+ carryForward = {
814
+ alertIdsToLink:
815
+ await IncidentAlertService.validateAlertIdsForNewIncident({
816
+ projectId: projectId,
817
+ alertIds: alertIdsToLink,
818
+ props: createBy.props,
819
+ }),
820
+ };
821
+ }
822
+
786
823
  if (!createBy.data.declaredAt) {
787
824
  createBy.data.declaredAt = OneUptimeDate.getCurrentDate();
788
825
  } else {
@@ -1179,7 +1216,7 @@ export class Service extends DatabaseService<Model> {
1179
1216
 
1180
1217
  return {
1181
1218
  createBy,
1182
- carryForward: null,
1219
+ carryForward: carryForward,
1183
1220
  };
1184
1221
  }
1185
1222
 
@@ -1267,14 +1304,16 @@ export class Service extends DatabaseService<Model> {
1267
1304
  */
1268
1305
  let aiInvestigationEnqueued: boolean = false;
1269
1306
 
1270
- // Execute operations sequentially with error handling
1271
- Promise.resolve()
1272
- .then(async () => {
1273
- /*
1274
- * Apply privacy rules BEFORE workspace operations so the workspace
1275
- * channel is created with the correct privacy setting. This may set
1276
- * createdItem.isPrivate=true in memory.
1277
- */
1307
+ /*
1308
+ * Apply privacy rules BEFORE workspace operations so the workspace
1309
+ * channel is created with the correct privacy setting. This may set
1310
+ * createdItem.isPrivate=true in memory. Kept as its own promise (it never
1311
+ * rejects) so declaring from alerts can wait for it before linking: the
1312
+ * links' feed entries leave out a private incident's title, so they must
1313
+ * see the privacy the rules settle on.
1314
+ */
1315
+ const privacyRulesApplied: Promise<void> = Promise.resolve().then(
1316
+ async () => {
1278
1317
  try {
1279
1318
  await IncidentPrivacyRuleEngineService.applyRulesToIncident(
1280
1319
  createdItem,
@@ -1288,7 +1327,11 @@ export class Service extends DatabaseService<Model> {
1288
1327
  } as LogAttributes,
1289
1328
  );
1290
1329
  }
1291
- })
1330
+ },
1331
+ );
1332
+
1333
+ // Execute operations sequentially with error handling
1334
+ privacyRulesApplied
1292
1335
  .then(async () => {
1293
1336
  try {
1294
1337
  if (createdItem.projectId && createdItem.id) {
@@ -1324,6 +1367,67 @@ export class Service extends DatabaseService<Model> {
1324
1367
  return Promise.resolve();
1325
1368
  }
1326
1369
  })
1370
+ .then(async () => {
1371
+ /*
1372
+ * Declared from alerts: announce the alerts once, here - after
1373
+ * "Incident Created" and after the incident's Slack / Microsoft
1374
+ * Teams channels exist - rather than once per alert as each link is
1375
+ * written (see linkAlertsDeclaredWithIncident). Uses the validated
1376
+ * ids, so it does not wait for the links.
1377
+ */
1378
+ try {
1379
+ const alertIds: Array<ObjectID> =
1380
+ this.getAlertIdsDeclaredWith(onCreate);
1381
+
1382
+ if (alertIds.length > 0) {
1383
+ await IncidentAlertService.createDeclaredFromAlertsFeedItem({
1384
+ projectId: createdItem.projectId!,
1385
+ incidentId: createdItem.id!,
1386
+ alertIds: alertIds,
1387
+ actorUserId: this.getDeclaringUserId(onCreate, createdItem),
1388
+ });
1389
+ }
1390
+ } catch (error) {
1391
+ logger.error(
1392
+ `Announcing the alerts an incident was declared from failed in IncidentService.onCreateSuccess: ${error}`,
1393
+ {
1394
+ projectId: createdItem.projectId?.toString(),
1395
+ incidentId: createdItem.id?.toString(),
1396
+ } as LogAttributes,
1397
+ );
1398
+ }
1399
+ })
1400
+ .then(async () => {
1401
+ /*
1402
+ * A private incident declared from alerts - private from the form or
1403
+ * made private by a privacy rule above - gets the alerts' owners as
1404
+ * its owners, so the people who could see the alerts can see the
1405
+ * incident. Here rather than before the request returns: the owners'
1406
+ * own hooks invite them to the incident's Slack / Microsoft Teams
1407
+ * channels, which exist only from the workspace step above, and their
1408
+ * "owner added" entries belong after "Incident Created".
1409
+ */
1410
+ try {
1411
+ const alertIds: Array<ObjectID> =
1412
+ this.getAlertIdsDeclaredWith(onCreate);
1413
+
1414
+ if (alertIds.length > 0 && createdItem.isPrivate === true) {
1415
+ await IncidentAlertService.copyAlertOwnersToIncident({
1416
+ projectId: createdItem.projectId!,
1417
+ incidentId: createdItem.id!,
1418
+ alertIds: alertIds,
1419
+ });
1420
+ }
1421
+ } catch (error) {
1422
+ logger.error(
1423
+ `Adding the owners of the alerts a private incident was declared from failed in IncidentService.onCreateSuccess: ${error}`,
1424
+ {
1425
+ projectId: createdItem.projectId?.toString(),
1426
+ incidentId: createdItem.id?.toString(),
1427
+ } as LogAttributes,
1428
+ );
1429
+ }
1430
+ })
1327
1431
  .then(async () => {
1328
1432
  try {
1329
1433
  return await this.handleIncidentStateChangeAsync(createdItem);
@@ -1623,9 +1727,105 @@ export class Service extends DatabaseService<Model> {
1623
1727
  );
1624
1728
  });
1625
1729
 
1730
+ await this.linkAlertsDeclaredWithIncident(
1731
+ onCreate,
1732
+ createdItem,
1733
+ privacyRulesApplied,
1734
+ );
1735
+
1626
1736
  return createdItem;
1627
1737
  }
1628
1738
 
1739
+ // The validated alert ids an incident is being declared from, if any.
1740
+ private getAlertIdsDeclaredWith(onCreate: OnCreate<Model>): Array<ObjectID> {
1741
+ const carryForward: IncidentCreateCarryForward =
1742
+ (onCreate.carryForward as IncidentCreateCarryForward) || null;
1743
+
1744
+ return carryForward?.alertIdsToLink || [];
1745
+ }
1746
+
1747
+ /*
1748
+ * Who declared the incident, as "Linked by" on the links and the actor of
1749
+ * their feed entries. A user is recorded as themselves and an API key as
1750
+ * nobody: Incident.createdByUserId is writable by the create payload, and
1751
+ * an API key must not be able to name somebody else as the one who linked
1752
+ * the alerts (IncidentAlertService.onBeforeCreate applies the same rule to
1753
+ * a link created directly). Only an internal root caller is trusted to
1754
+ * name the incident's creator.
1755
+ */
1756
+ private getDeclaringUserId(
1757
+ onCreate: OnCreate<Model>,
1758
+ createdItem: Model,
1759
+ ): ObjectID | undefined {
1760
+ if (onCreate.createBy.props.isRoot) {
1761
+ return createdItem.createdByUserId || undefined;
1762
+ }
1763
+
1764
+ return onCreate.createBy.props.userId || undefined;
1765
+ }
1766
+
1767
+ /*
1768
+ * The incident was declared from alerts (see onBeforeCreate): link them now
1769
+ * that it exists. Awaited, unlike the chain above, so the incident page the
1770
+ * user lands on already lists its alerts. Written as root because the
1771
+ * caller's right to link them was checked before the incident was created;
1772
+ * the user who declared the incident is recorded as the one who linked
1773
+ * them. The links write no incident-side feed entries: the chain above
1774
+ * posts one "Declared from N alerts" entry instead. A link that fails is
1775
+ * logged and never fails the incident.
1776
+ *
1777
+ * The privacy rules run first: each link's entry on its alert leaves out
1778
+ * the incident's title when the incident is private, and a rule can make
1779
+ * it private after it was saved. (The alerts' owners are added to a private
1780
+ * incident later in the chain, once its workspace channels exist.)
1781
+ */
1782
+ @CaptureSpan()
1783
+ private async linkAlertsDeclaredWithIncident(
1784
+ onCreate: OnCreate<Model>,
1785
+ createdItem: Model,
1786
+ privacyRulesApplied: Promise<void>,
1787
+ ): Promise<void> {
1788
+ const alertIds: Array<ObjectID> = this.getAlertIdsDeclaredWith(onCreate);
1789
+
1790
+ if (alertIds.length === 0 || !createdItem.projectId || !createdItem.id) {
1791
+ return;
1792
+ }
1793
+
1794
+ await privacyRulesApplied;
1795
+
1796
+ try {
1797
+ const result: LinkAlertsToIncidentResult =
1798
+ await IncidentAlertService.linkAlertsToIncident({
1799
+ projectId: createdItem.projectId,
1800
+ incidentId: createdItem.id,
1801
+ alertIds: alertIds,
1802
+ createdByUserId: this.getDeclaringUserId(onCreate, createdItem),
1803
+ declaredWithIncident: true,
1804
+ props: {
1805
+ isRoot: true,
1806
+ },
1807
+ });
1808
+
1809
+ if (result.failed.length > 0) {
1810
+ logger.error(
1811
+ `${result.failed.length} of ${alertIds.length} alerts could not be linked to the incident they were declared with.`,
1812
+ {
1813
+ projectId: createdItem.projectId.toString(),
1814
+ incidentId: createdItem.id.toString(),
1815
+ } as LogAttributes,
1816
+ );
1817
+ }
1818
+ } catch (error) {
1819
+ logger.error(
1820
+ `Linking the alerts an incident was declared from failed in IncidentService.onCreateSuccess: ${error}`,
1821
+ {
1822
+ projectId: createdItem.projectId.toString(),
1823
+ incidentId: createdItem.id.toString(),
1824
+ } as LogAttributes,
1825
+ );
1826
+ }
1827
+ }
1828
+
1629
1829
  @CaptureSpan()
1630
1830
  private async handleIncidentWorkspaceOperationsAsync(
1631
1831
  createdItem: Model,
@@ -37,6 +37,7 @@ import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
37
37
  import { LIMIT_PER_PROJECT } from "../../Types/Database/LimitMax";
38
38
  import WorkspaceNotificationRuleService from "./WorkspaceNotificationRuleService";
39
39
  import Semaphore, { SemaphoreMutex } from "../Infrastructure/Semaphore";
40
+ import IncidentAlertService from "./IncidentAlertService";
40
41
 
41
42
  export class Service extends DatabaseService<IncidentStateTimeline> {
42
43
  public constructor() {
@@ -485,6 +486,29 @@ export class Service extends DatabaseService<IncidentStateTimeline> {
485
486
  }
486
487
  }
487
488
 
489
+ /*
490
+ * Carry the incident's new state over to its linked alerts, when the
491
+ * project has opted in (acknowledge and/or resolve them with the
492
+ * incident). Only for the incident's current state - a back-dated row has
493
+ * an endsAt - and after the mutex is released, since it writes alert
494
+ * timelines. Fire-and-forget: it never fails or delays the state change.
495
+ */
496
+ if (!createdItem.endsAt && createdItem.projectId) {
497
+ IncidentAlertService.cascadeIncidentStateToLinkedAlerts({
498
+ projectId: createdItem.projectId,
499
+ incidentId: createdItem.incidentId,
500
+ incidentStateId: createdItem.incidentStateId,
501
+ }).catch((error: Error) => {
502
+ logger.error(
503
+ `Error while carrying the incident state over to linked alerts: ${error}`,
504
+ {
505
+ projectId: createdItem.projectId?.toString(),
506
+ incidentId: createdItem.incidentId?.toString(),
507
+ } as LogAttributes,
508
+ );
509
+ });
510
+ }
511
+
488
512
  const stateName: string = incidentState?.name || "";
489
513
  let stateEmoji: string = "➡️";
490
514
 
@@ -361,6 +361,7 @@ import IncidentEpisodeLabelRuleService from "./IncidentEpisodeLabelRuleService";
361
361
  import IncidentEpisodeFeedService from "./IncidentEpisodeFeedService";
362
362
  import IncidentEpisodeInternalNoteService from "./IncidentEpisodeInternalNoteService";
363
363
  import IncidentEpisodeMemberService from "./IncidentEpisodeMemberService";
364
+ import IncidentAlertService from "./IncidentAlertService";
364
365
  import IncidentEpisodeRoleMemberService from "./IncidentEpisodeRoleMemberService";
365
366
  import IncidentEpisodeOwnerTeamService from "./IncidentEpisodeOwnerTeamService";
366
367
  import IncidentEpisodeOwnerRuleService from "./IncidentEpisodeOwnerRuleService";
@@ -797,6 +798,7 @@ const services: Array<BaseService> = [
797
798
  IncidentEpisodeFeedService,
798
799
  IncidentEpisodeInternalNoteService,
799
800
  IncidentEpisodeMemberService,
801
+ IncidentAlertService,
800
802
  IncidentEpisodeRoleMemberService,
801
803
  IncidentEpisodeOwnerTeamService,
802
804
  IncidentEpisodeOwnerRuleService,
@@ -90,6 +90,7 @@ const RUM_APPLICATION_POLICY_COLUMNS: ReadonlyArray<string> = [
90
90
  "sessionReplayMonthlyBudgetInGB",
91
91
  "sessionReplayIgnoreErrorPatterns",
92
92
  "sessionReplayTracePropagationOrigins",
93
+ "sessionReplaySameOriginTracePropagation",
93
94
  "sessionReplayLcpBudgetMs",
94
95
  "sessionReplayLongTaskBudgetMs",
95
96
  "sessionReplaySlowRequestBudgetMs",
@@ -159,11 +160,24 @@ export interface SessionReplayGatePolicy {
159
160
  ignoreErrorPatterns: Array<string>;
160
161
 
161
162
  /*
162
- * Origins the recorder may inject a W3C traceparent header into.
163
- * Empty = never inject; the empty default is the CORS safety mechanism.
163
+ * CROSS-origin APIs the recorder may inject a W3C traceparent header
164
+ * into (traceparent only, never the session id). Empty = no cross-origin
165
+ * injection; the empty default is the CORS safety mechanism. The page's
166
+ * own origin is governed by sameOriginTracePropagation, not by this list.
164
167
  */
165
168
  tracePropagationOrigins: Array<string>;
166
169
 
170
+ /*
171
+ * Whether the recorder adds traceparent plus a tracestate member carrying
172
+ * the replay session id to requests to the page's OWN origin while a
173
+ * session uploads, so the backend telemetry they cause links to the
174
+ * recording with no customer code. A same-origin request is never
175
+ * preflighted, so this defaults ON; the switch exists to turn it off
176
+ * without a customer redeploy (a same-origin request that redirects to
177
+ * another origin does need that origin to allow the headers).
178
+ */
179
+ sameOriginTracePropagation: boolean;
180
+
167
181
  /* Performance capture budgets in milliseconds; 0 disables each. */
168
182
  lcpBudgetMs: number;
169
183
  longTaskBudgetMs: number;
@@ -514,6 +528,14 @@ export default class SessionReplayGateCache {
514
528
  tracePropagationOrigins: this.readStringArray(
515
529
  appView["sessionReplayTracePropagationOrigins"],
516
530
  ),
531
+ /*
532
+ * Anything but an explicit false reads as on: the column defaults
533
+ * to true, so an absent or null value is the model default, like
534
+ * the consent / trigger / sample fallbacks above. Only an operator
535
+ * who switched it off gets it off.
536
+ */
537
+ sameOriginTracePropagation:
538
+ appView["sessionReplaySameOriginTracePropagation"] !== false,
517
539
  lcpBudgetMs: this.readNumber(appView["sessionReplayLcpBudgetMs"], 0),
518
540
  longTaskBudgetMs: this.readNumber(
519
541
  appView["sessionReplayLongTaskBudgetMs"],