@oneuptime/common 10.2.14 → 10.2.16

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.
@@ -346,11 +346,6 @@ export default class DashboardAPI extends BaseAPI<
346
346
  throw new BadDataException("attributeKey is required.");
347
347
  }
348
348
 
349
- const metricNameRaw: string | undefined =
350
- req.body && (req.body["metricName"] as string);
351
- const metricName: string | undefined =
352
- metricNameRaw && metricNameRaw.trim() ? metricNameRaw : undefined;
353
-
354
349
  const telemetryTypeRaw: string | undefined =
355
350
  req.body && (req.body["telemetryType"] as string);
356
351
  let telemetryType: TelemetryType = TelemetryType.Metric;
@@ -386,7 +381,6 @@ export default class DashboardAPI extends BaseAPI<
386
381
  projectId: dashboard.projectId,
387
382
  telemetryType,
388
383
  attributeKey: attributeKey.trim(),
389
- metricName,
390
384
  });
391
385
 
392
386
  return Response.sendJsonObjectResponse(req, res, {
@@ -49,6 +49,7 @@ import Metric, {
49
49
  import OneUptimeDate from "../../Types/Date";
50
50
  import TelemetryUtil from "../Utils/Telemetry/Telemetry";
51
51
  import logger, { LogAttributes } from "../Utils/Logger";
52
+ import NotEqual from "../../Types/BaseDatabase/NotEqual";
52
53
  import IncidentFeedService from "./IncidentFeedService";
53
54
  import IncidentSlaService from "./IncidentSlaService";
54
55
  import { setIsPublicForMarkdownImages } from "../Utils/InlineImageAccessTokenSync";
@@ -2563,12 +2564,25 @@ ${incidentSeverity.name}
2563
2564
  const firstIncidentStateTimeline: IncidentStateTimeline | undefined =
2564
2565
  incidentStateTimelines[0];
2565
2566
 
2566
- // delete all the incident metrics with this incident id because it's a refresh.
2567
-
2567
+ /*
2568
+ * Delete the existing metrics for this incident so the time-varying
2569
+ * ones (TimeToAcknowledge / TimeToResolve / IncidentDuration /
2570
+ * TimeInState) get rewritten with the latest state-timeline values
2571
+ * on this refresh. IncidentCount is excluded from the delete: it is
2572
+ * a constant `value = 1` keyed by `serviceId + bucketTime` that
2573
+ * never changes. Re-emitting it across refreshes inflated the
2574
+ * 1-minute aggregating materialized view (`MetricItemAggMV1m_mv`),
2575
+ * because the MV trigger only fires on inserts — ALTER DELETE
2576
+ * mutations don't roll back the previously-accumulated
2577
+ * `sumState` / `countState`. That's why the Incident Dashboard
2578
+ * sum-of-IncidentCount widget read ~33% higher than the actual
2579
+ * unique-incident count.
2580
+ */
2568
2581
  await MetricService.deleteBy({
2569
2582
  query: {
2570
2583
  projectId: incident.projectId,
2571
2584
  serviceId: data.incidentId,
2585
+ name: new NotEqual<string>(IncidentMetricType.IncidentCount),
2572
2586
  },
2573
2587
  props: {
2574
2588
  isRoot: true,
@@ -2623,36 +2637,59 @@ ${incidentSeverity.name}
2623
2637
  ownerTeamNames: ownerTeamNames.join(", "),
2624
2638
  };
2625
2639
 
2626
- const incidentCountMetric: Metric = new Metric();
2640
+ /*
2641
+ * Only emit IncidentCount on the very first refresh (i.e. when no
2642
+ * existing IncidentCount row is present for this serviceId). See
2643
+ * the delete comment above — emitting it on every refresh would
2644
+ * accumulate phantom `sumState` entries in the MV that ALTER
2645
+ * DELETE can't undo. By keeping the original row alive and never
2646
+ * re-emitting, the dashboard Sum stays equal to the true count of
2647
+ * distinct incidents.
2648
+ */
2649
+ const existingIncidentCount: PositiveNumber = await MetricService.countBy({
2650
+ query: {
2651
+ projectId: incident.projectId,
2652
+ serviceId: data.incidentId,
2653
+ name: IncidentMetricType.IncidentCount,
2654
+ },
2655
+ skip: 0,
2656
+ limit: 1,
2657
+ props: {
2658
+ isRoot: true,
2659
+ },
2660
+ });
2627
2661
 
2628
- incidentCountMetric.projectId = incident.projectId;
2629
- incidentCountMetric.serviceId = incident.id!;
2630
- incidentCountMetric.serviceType = ServiceType.Incident;
2631
- incidentCountMetric.name = IncidentMetricType.IncidentCount;
2632
- incidentCountMetric.value = 1;
2633
- incidentCountMetric.attributes = { ...baseMetricAttributes };
2634
- incidentCountMetric.attributeKeys = TelemetryUtil.getAttributeKeys(
2635
- incidentCountMetric.attributes,
2636
- );
2662
+ if (existingIncidentCount.toNumber() === 0) {
2663
+ const incidentCountMetric: Metric = new Metric();
2664
+
2665
+ incidentCountMetric.projectId = incident.projectId;
2666
+ incidentCountMetric.serviceId = incident.id!;
2667
+ incidentCountMetric.serviceType = ServiceType.Incident;
2668
+ incidentCountMetric.name = IncidentMetricType.IncidentCount;
2669
+ incidentCountMetric.value = 1;
2670
+ incidentCountMetric.attributes = { ...baseMetricAttributes };
2671
+ incidentCountMetric.attributeKeys = TelemetryUtil.getAttributeKeys(
2672
+ incidentCountMetric.attributes,
2673
+ );
2637
2674
 
2638
- incidentCountMetric.time = incidentStartsAt;
2639
- incidentCountMetric.timeUnixNano = OneUptimeDate.toUnixNano(
2640
- incidentCountMetric.time,
2641
- );
2642
- incidentCountMetric.metricPointType = MetricPointType.Sum;
2643
- incidentCountMetric.retentionDate = incidentMetricRetentionDate;
2675
+ incidentCountMetric.time = incidentStartsAt;
2676
+ incidentCountMetric.timeUnixNano = OneUptimeDate.toUnixNano(
2677
+ incidentCountMetric.time,
2678
+ );
2679
+ incidentCountMetric.metricPointType = MetricPointType.Sum;
2680
+ incidentCountMetric.retentionDate = incidentMetricRetentionDate;
2644
2681
 
2645
- itemsToSave.push(incidentCountMetric);
2682
+ itemsToSave.push(incidentCountMetric);
2683
+ }
2646
2684
 
2647
- // add metric type for this to map.
2685
+ // Always register the metric type so it shows up in the type catalog.
2648
2686
  const metricType: MetricType = new MetricType();
2649
- metricType.name = incidentCountMetric.name;
2687
+ metricType.name = IncidentMetricType.IncidentCount;
2650
2688
  metricType.description = "Number of incidents created";
2651
2689
  metricType.unit = "";
2652
2690
  metricType.services = [];
2653
2691
 
2654
- // add to map.
2655
- metricTypesMap[incidentCountMetric.name] = metricType;
2692
+ metricTypesMap[IncidentMetricType.IncidentCount] = metricType;
2656
2693
 
2657
2694
  // is the incident acknowledged?
2658
2695
  const isIncidentAcknowledged: boolean = incidentStateTimelines.some(