@oneuptime/common 11.3.9 → 11.3.11

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 (54) hide show
  1. package/Models/AnalyticsModels/MutableMetric.ts +333 -0
  2. package/Server/Services/AlertService.ts +167 -206
  3. package/Server/Services/AlertStateTimelineService.ts +121 -1
  4. package/Server/Services/AnalyticsDatabaseService.ts +19 -23
  5. package/Server/Services/IncidentService.ts +179 -252
  6. package/Server/Services/IncidentStateTimelineService.ts +120 -1
  7. package/Server/Services/Index.ts +2 -0
  8. package/Server/Services/MetricAggregationService.ts +73 -3
  9. package/Server/Services/MetricService.ts +228 -0
  10. package/Server/Services/MutableMetricService.ts +275 -0
  11. package/Server/Services/TelemetryAttributeService.ts +183 -1
  12. package/Server/Utils/AnalyticsDatabase/ClusterConfig.ts +2 -0
  13. package/Server/Utils/AnalyticsDatabase/Statement.ts +2 -0
  14. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +145 -0
  15. package/Tests/Types/Monitor/MonitorStepEntityScope.test.ts +0 -56
  16. package/Types/AnalyticsDatabase/AnalyticsTableEngine.ts +1 -0
  17. package/Types/AnalyticsDatabase/AnalyticsTableName.ts +1 -0
  18. package/Types/Dashboard/DashboardTemplates.ts +267 -7
  19. package/Types/Monitor/MonitorStepMetricMonitor.ts +0 -27
  20. package/build/dist/Models/AnalyticsModels/MutableMetric.js +275 -0
  21. package/build/dist/Models/AnalyticsModels/MutableMetric.js.map +1 -0
  22. package/build/dist/Server/Services/AlertService.js +133 -170
  23. package/build/dist/Server/Services/AlertService.js.map +1 -1
  24. package/build/dist/Server/Services/AlertStateTimelineService.js +97 -0
  25. package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
  26. package/build/dist/Server/Services/AnalyticsDatabaseService.js +16 -15
  27. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  28. package/build/dist/Server/Services/IncidentService.js +142 -207
  29. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  30. package/build/dist/Server/Services/IncidentStateTimelineService.js +97 -0
  31. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  32. package/build/dist/Server/Services/Index.js +2 -0
  33. package/build/dist/Server/Services/Index.js.map +1 -1
  34. package/build/dist/Server/Services/MetricAggregationService.js +49 -3
  35. package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
  36. package/build/dist/Server/Services/MetricService.js +155 -0
  37. package/build/dist/Server/Services/MetricService.js.map +1 -1
  38. package/build/dist/Server/Services/MutableMetricService.js +182 -0
  39. package/build/dist/Server/Services/MutableMetricService.js.map +1 -0
  40. package/build/dist/Server/Services/TelemetryAttributeService.js +131 -6
  41. package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
  42. package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js +2 -0
  43. package/build/dist/Server/Utils/AnalyticsDatabase/ClusterConfig.js.map +1 -1
  44. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +2 -0
  45. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
  46. package/build/dist/Types/AnalyticsDatabase/AnalyticsTableEngine.js +1 -0
  47. package/build/dist/Types/AnalyticsDatabase/AnalyticsTableEngine.js.map +1 -1
  48. package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +1 -0
  49. package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -1
  50. package/build/dist/Types/Dashboard/DashboardTemplates.js +252 -6
  51. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  52. package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js +0 -13
  53. package/build/dist/Types/Monitor/MonitorStepMetricMonitor.js.map +1 -1
  54. package/package.json +1 -1
@@ -13,6 +13,8 @@ import TableColumnType from "../../../Types/AnalyticsDatabase/TableColumnType";
13
13
  import AggregationType from "../../../Types/BaseDatabase/AggregationType";
14
14
  import BadDataException from "../../../Types/Exception/BadDataException";
15
15
  import GenericObject from "../../../Types/GenericObject";
16
+ import ObjectID from "../../../Types/ObjectID";
17
+ import OneUptimeDate from "../../../Types/Date";
16
18
  import {
17
19
  describe,
18
20
  expect,
@@ -379,6 +381,149 @@ describe("AnalyticsDatabaseService", () => {
379
381
  * ids per call. Must be strictly additive: option-less calls keep the
380
382
  * exact pre-existing exec/query payload.
381
383
  */
384
+ /*
385
+ * Required-field validation. A required column is satisfied by any present
386
+ * value (including the boolean `false`); only null/undefined triggers a
387
+ * default-fill or a "<field> is required" error. This is regression coverage
388
+ * for the boolean branch that previously rejected every required Boolean
389
+ * column (both true and false), which made models like MutableMetric — whose
390
+ * `isDeleted` tombstone flag is a required Boolean — impossible to insert.
391
+ */
392
+ describe("checkRequiredFields", () => {
393
+ class RequiredFieldsModel extends AnalyticsBaseModel {
394
+ public constructor() {
395
+ super({
396
+ tableName: "<required-fields-table>",
397
+ singularName: "<singular-name>",
398
+ pluralName: "<plural-name>",
399
+ tableColumns: [
400
+ new AnalyticsTableColumn({
401
+ key: "requiredText",
402
+ title: "<title>",
403
+ description: "<description>",
404
+ required: true,
405
+ type: TableColumnType.Text,
406
+ }),
407
+ new AnalyticsTableColumn({
408
+ key: "requiredBoolWithDefault",
409
+ title: "<title>",
410
+ description: "<description>",
411
+ required: true,
412
+ type: TableColumnType.Boolean,
413
+ defaultValue: false,
414
+ }),
415
+ ],
416
+ crudApiPath: new Route("route"),
417
+ primaryKeys: ["requiredText"],
418
+ sortKeys: ["requiredText"],
419
+ partitionKey: "requiredText",
420
+ tableEngine: AnalyticsTableEngine.MergeTree,
421
+ });
422
+ }
423
+
424
+ /*
425
+ * checkRequiredFields reads columns via property accessors, so the model
426
+ * must expose getters/setters that delegate to get/setColumnValue (real
427
+ * analytics models do the same for every column).
428
+ */
429
+ public get requiredText(): string | undefined {
430
+ return this.getColumnValue("requiredText") as string | undefined;
431
+ }
432
+ public set requiredText(v: string | undefined) {
433
+ this.setColumnValue("requiredText", v);
434
+ }
435
+ public get requiredBoolWithDefault(): boolean | undefined {
436
+ return this.getColumnValue("requiredBoolWithDefault") as
437
+ | boolean
438
+ | undefined;
439
+ }
440
+ public set requiredBoolWithDefault(v: boolean | undefined) {
441
+ this.setColumnValue("requiredBoolWithDefault", v);
442
+ }
443
+ }
444
+
445
+ let requiredFieldsService: AnalyticsDatabaseService<RequiredFieldsModel>;
446
+
447
+ beforeEach(() => {
448
+ requiredFieldsService = new AnalyticsDatabaseService({
449
+ modelType: RequiredFieldsModel,
450
+ });
451
+ });
452
+
453
+ // Build a model with every required column satisfied except as overridden.
454
+ const buildModel: (
455
+ overrides?: (model: RequiredFieldsModel) => void,
456
+ ) => RequiredFieldsModel = (
457
+ overrides?: (model: RequiredFieldsModel) => void,
458
+ ): RequiredFieldsModel => {
459
+ const model: RequiredFieldsModel = new RequiredFieldsModel();
460
+ // _id and createdAt are required on the base model and carry no default.
461
+ model.setColumnValue("_id", ObjectID.generate());
462
+ model.setColumnValue("createdAt", OneUptimeDate.getCurrentDate());
463
+ model.setColumnValue("requiredText", "value");
464
+ if (overrides) {
465
+ overrides(model);
466
+ }
467
+ return model;
468
+ };
469
+
470
+ const check: (model: RequiredFieldsModel) => RequiredFieldsModel = (
471
+ model: RequiredFieldsModel,
472
+ ): RequiredFieldsModel => {
473
+ return (requiredFieldsService as any).checkRequiredFields(model);
474
+ };
475
+
476
+ test("accepts a required boolean set to false (regression)", () => {
477
+ const model: RequiredFieldsModel = buildModel(
478
+ (m: RequiredFieldsModel): void => {
479
+ m.setColumnValue("requiredBoolWithDefault", false);
480
+ },
481
+ );
482
+
483
+ expect(() => {
484
+ return check(model);
485
+ }).not.toThrow();
486
+ expect(model.getColumnValue("requiredBoolWithDefault")).toBe(false);
487
+ });
488
+
489
+ test("accepts a required boolean set to true", () => {
490
+ const model: RequiredFieldsModel = buildModel(
491
+ (m: RequiredFieldsModel): void => {
492
+ m.setColumnValue("requiredBoolWithDefault", true);
493
+ },
494
+ );
495
+
496
+ expect(() => {
497
+ return check(model);
498
+ }).not.toThrow();
499
+ expect(model.getColumnValue("requiredBoolWithDefault")).toBe(true);
500
+ });
501
+
502
+ test("fills the default when a required boolean is unset", () => {
503
+ const model: RequiredFieldsModel = buildModel();
504
+
505
+ expect(() => {
506
+ return check(model);
507
+ }).not.toThrow();
508
+ expect(model.getColumnValue("requiredBoolWithDefault")).toBe(false);
509
+ });
510
+
511
+ test("throws when a required column without a default is missing", () => {
512
+ const model: RequiredFieldsModel = new RequiredFieldsModel();
513
+ model.setColumnValue("_id", ObjectID.generate());
514
+ model.setColumnValue("createdAt", OneUptimeDate.getCurrentDate());
515
+ model.setColumnValue("requiredBoolWithDefault", false);
516
+ // requiredText (required, no default) intentionally left unset.
517
+
518
+ expect(() => {
519
+ return check(model);
520
+ }).toThrow(BadDataException);
521
+ expect(() => {
522
+ return check(model);
523
+ }).toThrow("requiredText is required");
524
+ });
525
+ });
526
+
382
527
  describe("execute / executeQuery per-call options", () => {
383
528
  let exec: ReturnType<typeof jest.fn>;
384
529
  let query: ReturnType<typeof jest.fn>;
@@ -4,7 +4,6 @@ import Log from "../../../Models/AnalyticsModels/Log";
4
4
  import Span from "../../../Models/AnalyticsModels/Span";
5
5
  import ExceptionInstance from "../../../Models/AnalyticsModels/ExceptionInstance";
6
6
  import Profile from "../../../Models/AnalyticsModels/Profile";
7
- import Metric from "../../../Models/AnalyticsModels/Metric";
8
7
  import MonitorStepLogMonitor, {
9
8
  MonitorStepLogMonitorUtil,
10
9
  } from "../../../Types/Monitor/MonitorStepLogMonitor";
@@ -17,9 +16,6 @@ import MonitorStepExceptionMonitor, {
17
16
  import MonitorStepProfileMonitor, {
18
17
  MonitorStepProfileMonitorUtil,
19
18
  } from "../../../Types/Monitor/MonitorStepProfileMonitor";
20
- import MonitorStepMetricMonitor, {
21
- MonitorStepMetricMonitorUtil,
22
- } from "../../../Types/Monitor/MonitorStepMetricMonitor";
23
19
  import { JSONObject } from "../../../Types/JSON";
24
20
 
25
21
  /*
@@ -221,55 +217,3 @@ describe("MonitorStepProfileMonitorUtil.toQuery entity scoping", () => {
221
217
  expectIncludes(query.entityKeys, TWO_KEYS);
222
218
  });
223
219
  });
224
-
225
- describe("MonitorStepMetricMonitorUtil.applyEntityScopeToQuery", () => {
226
- test("is a no-op when entityKeys is undefined (pre-existing saved monitors)", () => {
227
- const step: MonitorStepMetricMonitor =
228
- MonitorStepMetricMonitorUtil.getDefault();
229
- delete step.entityKeys;
230
-
231
- const query: Query<Metric> = {};
232
- MonitorStepMetricMonitorUtil.applyEntityScopeToQuery(query, step);
233
-
234
- expect(query.entityKeys).toBeUndefined();
235
- expect(Object.keys(query)).toHaveLength(0);
236
- });
237
-
238
- test("is a no-op when entityKeys is an empty array", () => {
239
- const step: MonitorStepMetricMonitor =
240
- MonitorStepMetricMonitorUtil.getDefault();
241
- step.entityKeys = [];
242
-
243
- const query: Query<Metric> = {};
244
- MonitorStepMetricMonitorUtil.applyEntityScopeToQuery(query, step);
245
-
246
- expect(query.entityKeys).toBeUndefined();
247
- });
248
-
249
- test("stamps an Includes on entityKeys for two keys and preserves other predicates", () => {
250
- const step: MonitorStepMetricMonitor =
251
- MonitorStepMetricMonitorUtil.getDefault();
252
- step.entityKeys = TWO_KEYS;
253
-
254
- const query: Query<Metric> = { name: "cpu.usage" };
255
- const returned: Query<Metric> =
256
- MonitorStepMetricMonitorUtil.applyEntityScopeToQuery(query, step);
257
-
258
- expect(returned).toBe(query);
259
- expect(query.name).toBe("cpu.usage");
260
- expectIncludes(query.entityKeys, TWO_KEYS);
261
- });
262
-
263
- test("fromJSON preserves entityKeys (pass-through serialization)", () => {
264
- const step: MonitorStepMetricMonitor =
265
- MonitorStepMetricMonitorUtil.getDefault();
266
- step.entityKeys = TWO_KEYS;
267
-
268
- const roundTripped: MonitorStepMetricMonitor =
269
- MonitorStepMetricMonitorUtil.fromJSON(
270
- MonitorStepMetricMonitorUtil.toJSON(step),
271
- );
272
-
273
- expect(roundTripped.entityKeys).toEqual(TWO_KEYS);
274
- });
275
- });
@@ -1,5 +1,6 @@
1
1
  enum AnalyticsTableEngine {
2
2
  MergeTree = "MergeTree",
3
+ ReplacingMergeTree = "ReplacingMergeTree",
3
4
  /*
4
5
  * Target-table engine for materialized views that store
5
6
  * AggregateFunction states. Background merges combine partial state
@@ -21,6 +21,7 @@ enum AnalyticsTableName {
21
21
  */
22
22
  MetricItemAggMV1mByHostV2 = "MetricItemAggMV1mByHostV2",
23
23
  MetricBaselineHourly = "MetricBaselineHourly",
24
+ MutableMetric = "MutableMetricItem",
24
25
  }
25
26
 
26
27
  export default AnalyticsTableName;
@@ -9,6 +9,7 @@ import DashboardVariable, { DashboardVariableType } from "./DashboardVariable";
9
9
  import IconProp from "../Icon/IconProp";
10
10
  import MetricsAggregationType from "../Metrics/MetricsAggregationType";
11
11
  import IncidentMetricType from "../Incident/IncidentMetricType";
12
+ import AlertMetricType from "../Alerts/AlertMetricType";
12
13
  import MonitorMetricType from "../Monitor/MonitorMetricType";
13
14
  import MetricDashboardMetricType from "../Metrics/MetricDashboardMetricType";
14
15
  import { DashboardValueTrendDirection } from "./DashboardComponents/DashboardValueComponent";
@@ -24,6 +25,7 @@ export enum DashboardTemplateType {
24
25
  Blank = "Blank",
25
26
  Monitor = "Monitor",
26
27
  Incident = "Incident",
28
+ Alert = "Alert",
27
29
  Kubernetes = "Kubernetes",
28
30
  Host = "Host",
29
31
  Proxmox = "Proxmox",
@@ -32,11 +34,32 @@ export enum DashboardTemplateType {
32
34
  Metrics = "Metrics",
33
35
  }
34
36
 
37
+ /*
38
+ * Templates are grouped into categories so the "Create from Template"
39
+ * modal can render them under labelled sections instead of one flat
40
+ * grid. The enum values double as the section headings, and
41
+ * DashboardTemplateCategories below fixes the display order.
42
+ */
43
+ export enum DashboardTemplateCategory {
44
+ GetStarted = "Get Started",
45
+ Monitoring = "Monitoring & APM",
46
+ IncidentResponse = "Incident Response",
47
+ Infrastructure = "Infrastructure",
48
+ }
49
+
50
+ export const DashboardTemplateCategories: Array<DashboardTemplateCategory> = [
51
+ DashboardTemplateCategory.GetStarted,
52
+ DashboardTemplateCategory.Monitoring,
53
+ DashboardTemplateCategory.IncidentResponse,
54
+ DashboardTemplateCategory.Infrastructure,
55
+ ];
56
+
35
57
  export interface DashboardTemplate {
36
58
  type: DashboardTemplateType;
37
59
  name: string;
38
60
  description: string;
39
61
  icon: IconProp;
62
+ category: DashboardTemplateCategory;
40
63
  }
41
64
 
42
65
  export const DashboardTemplates: Array<DashboardTemplate> = [
@@ -45,6 +68,7 @@ export const DashboardTemplates: Array<DashboardTemplate> = [
45
68
  name: "Blank Dashboard",
46
69
  description: "Start from scratch with an empty dashboard.",
47
70
  icon: IconProp.Add,
71
+ category: DashboardTemplateCategory.GetStarted,
48
72
  },
49
73
  {
50
74
  type: DashboardTemplateType.Monitor,
@@ -52,6 +76,15 @@ export const DashboardTemplates: Array<DashboardTemplate> = [
52
76
  description:
53
77
  "Response time, uptime, status codes, CPU/memory health gauges, and breakdown table for synthetic and server monitors.",
54
78
  icon: IconProp.Heartbeat,
79
+ category: DashboardTemplateCategory.Monitoring,
80
+ },
81
+ {
82
+ type: DashboardTemplateType.Metrics,
83
+ name: "Metrics Dashboard",
84
+ description:
85
+ "HTTP request rate, latency, error rate, CPU utilization gauge, memory usage, disk and network I/O, and runtime metrics.",
86
+ icon: IconProp.ChartBar,
87
+ category: DashboardTemplateCategory.Monitoring,
55
88
  },
56
89
  {
57
90
  type: DashboardTemplateType.Incident,
@@ -59,6 +92,15 @@ export const DashboardTemplates: Array<DashboardTemplate> = [
59
92
  description:
60
93
  "Incident count, MTTR/MTTA gauges, duration trends, severity breakdown, time-in-state, and longest-incident tables.",
61
94
  icon: IconProp.Alert,
95
+ category: DashboardTemplateCategory.IncidentResponse,
96
+ },
97
+ {
98
+ type: DashboardTemplateType.Alert,
99
+ name: "Alert Dashboard",
100
+ description:
101
+ "Alert count, MTTR/MTTA gauges, duration trends, alert volume over time, and longest-alert / acknowledgement tables.",
102
+ icon: IconProp.Bell,
103
+ category: DashboardTemplateCategory.IncidentResponse,
62
104
  },
63
105
  {
64
106
  type: DashboardTemplateType.Kubernetes,
@@ -66,6 +108,7 @@ export const DashboardTemplates: Array<DashboardTemplate> = [
66
108
  description:
67
109
  "Pod/node CPU and memory averages, utilization gauges, live pod and node lists, network I/O, restarts, and cluster logs.",
68
110
  icon: IconProp.Kubernetes,
111
+ category: DashboardTemplateCategory.Infrastructure,
69
112
  },
70
113
  {
71
114
  type: DashboardTemplateType.Host,
@@ -73,6 +116,7 @@ export const DashboardTemplates: Array<DashboardTemplate> = [
73
116
  description:
74
117
  "Per-host CPU, memory, disk and network charts, a live host inventory, CPU utilization gauge, process counts, and recent logs.",
75
118
  icon: IconProp.Server,
119
+ category: DashboardTemplateCategory.Infrastructure,
76
120
  },
77
121
  {
78
122
  type: DashboardTemplateType.Proxmox,
@@ -80,6 +124,7 @@ export const DashboardTemplates: Array<DashboardTemplate> = [
80
124
  description:
81
125
  "Live node and guest inventories with status, CPU/memory trends, network throughput, and cluster logs.",
82
126
  icon: IconProp.ServerStack,
127
+ category: DashboardTemplateCategory.Infrastructure,
83
128
  },
84
129
  {
85
130
  type: DashboardTemplateType.Ceph,
@@ -87,6 +132,7 @@ export const DashboardTemplates: Array<DashboardTemplate> = [
87
132
  description:
88
133
  "OSD wall and pool capacity lists, health and capacity stats, degraded-PG trends, client throughput, and cluster logs.",
89
134
  icon: IconProp.Database,
135
+ category: DashboardTemplateCategory.Infrastructure,
90
136
  },
91
137
  {
92
138
  type: DashboardTemplateType.DockerSwarm,
@@ -94,16 +140,22 @@ export const DashboardTemplates: Array<DashboardTemplate> = [
94
140
  description:
95
141
  "Live node and service inventories with role/status, container CPU and memory trends, PID counts, and cluster logs.",
96
142
  icon: IconProp.Cube,
97
- },
98
- {
99
- type: DashboardTemplateType.Metrics,
100
- name: "Metrics Dashboard",
101
- description:
102
- "HTTP request rate, latency, error rate, CPU utilization gauge, memory usage, disk and network I/O, and runtime metrics.",
103
- icon: IconProp.ChartBar,
143
+ category: DashboardTemplateCategory.Infrastructure,
104
144
  },
105
145
  ];
106
146
 
147
+ /*
148
+ * Templates for a category in their declared order. The modal uses this
149
+ * to render one labelled section per category.
150
+ */
151
+ export function getDashboardTemplatesByCategory(
152
+ category: DashboardTemplateCategory,
153
+ ): Array<DashboardTemplate> {
154
+ return DashboardTemplates.filter((template: DashboardTemplate): boolean => {
155
+ return template.category === category;
156
+ });
157
+ }
158
+
107
159
  // -- Metric query config helpers --
108
160
 
109
161
  interface MetricConfig {
@@ -1105,6 +1157,212 @@ function createIncidentDashboardConfig(): DashboardViewConfig {
1105
1157
  };
1106
1158
  }
1107
1159
 
1160
+ function createAlertDashboardConfig(): DashboardViewConfig {
1161
+ /*
1162
+ * Alert metrics (TimeToResolve, TimeToAcknowledge, AlertDuration) are
1163
+ * emitted with unit "seconds" by AlertService, and AlertCount is a
1164
+ * unitless "1 per alert" counter — the same shape as the Incident
1165
+ * metrics. As with the Incident template we drop legendUnit overrides
1166
+ * (ValueFormatter scales `seconds` to sec/min/hr based on magnitude)
1167
+ * and author the MTTR/MTTA gauge ranges in seconds so the 0-100% sweep
1168
+ * is meaningful.
1169
+ *
1170
+ * Alerts have a leaner metric catalog than incidents: there are no
1171
+ * severity-change, time-in-state, or postmortem metrics, so this
1172
+ * template trades those widgets for the four alert metrics that are
1173
+ * actually emitted.
1174
+ */
1175
+ const components: Array<DashboardBaseComponent> = [
1176
+ // Row 0: Title
1177
+ createTextComponent({
1178
+ text: "Alert Dashboard",
1179
+ top: 0,
1180
+ left: 0,
1181
+ width: 12,
1182
+ height: 1,
1183
+ isBold: true,
1184
+ }),
1185
+
1186
+ // Row 1: Key alert metrics — every one is "higher = worse".
1187
+ createValueComponent({
1188
+ title: "Alert Count",
1189
+ top: 1,
1190
+ left: 0,
1191
+ width: 3,
1192
+ metricConfig: {
1193
+ metricName: AlertMetricType.AlertCount,
1194
+ aggregationType: MetricsAggregationType.Sum,
1195
+ },
1196
+ trendDirection: DashboardValueTrendDirection.HigherIsWorse,
1197
+ }),
1198
+ createValueComponent({
1199
+ title: "MTTR",
1200
+ top: 1,
1201
+ left: 3,
1202
+ width: 3,
1203
+ metricConfig: {
1204
+ metricName: AlertMetricType.TimeToResolve,
1205
+ aggregationType: MetricsAggregationType.Avg,
1206
+ },
1207
+ trendDirection: DashboardValueTrendDirection.HigherIsWorse,
1208
+ }),
1209
+ createValueComponent({
1210
+ title: "MTTA",
1211
+ top: 1,
1212
+ left: 6,
1213
+ width: 3,
1214
+ metricConfig: {
1215
+ metricName: AlertMetricType.TimeToAcknowledge,
1216
+ aggregationType: MetricsAggregationType.Avg,
1217
+ },
1218
+ trendDirection: DashboardValueTrendDirection.HigherIsWorse,
1219
+ }),
1220
+ createValueComponent({
1221
+ title: "Avg Duration",
1222
+ top: 1,
1223
+ left: 9,
1224
+ width: 3,
1225
+ metricConfig: {
1226
+ metricName: AlertMetricType.AlertDuration,
1227
+ aggregationType: MetricsAggregationType.Avg,
1228
+ },
1229
+ trendDirection: DashboardValueTrendDirection.HigherIsWorse,
1230
+ }),
1231
+
1232
+ // Row 2-4: Alert trends
1233
+ createChartComponent({
1234
+ title: "Alerts Over Time",
1235
+ chartType: DashboardChartType.Bar,
1236
+ top: 2,
1237
+ left: 0,
1238
+ width: 6,
1239
+ height: 3,
1240
+ metricConfig: {
1241
+ metricName: AlertMetricType.AlertCount,
1242
+ aggregationType: MetricsAggregationType.Sum,
1243
+ legend: "Alerts",
1244
+ },
1245
+ }),
1246
+ createChartComponent({
1247
+ title: "Alert Duration Over Time",
1248
+ chartType: DashboardChartType.Line,
1249
+ top: 2,
1250
+ left: 6,
1251
+ width: 6,
1252
+ height: 3,
1253
+ metricConfig: {
1254
+ metricName: AlertMetricType.AlertDuration,
1255
+ aggregationType: MetricsAggregationType.Avg,
1256
+ legend: "Avg Duration",
1257
+ },
1258
+ }),
1259
+
1260
+ // Row 5: Section header
1261
+ createTextComponent({
1262
+ text: "Response Performance",
1263
+ top: 5,
1264
+ left: 0,
1265
+ width: 12,
1266
+ height: 1,
1267
+ isBold: true,
1268
+ }),
1269
+
1270
+ /*
1271
+ * Row 6-8: MTTR/MTTA gauges. Ranges and thresholds are in seconds
1272
+ * (matching the stored metric unit) and mirror the Incident
1273
+ * template: MTTR full scale 2 hours (warn 1 hour, critical 1.5
1274
+ * hours); MTTA full scale 1 hour (warn 15 min, critical 30 min).
1275
+ */
1276
+ createGaugeComponent({
1277
+ title: "MTTR",
1278
+ top: 6,
1279
+ left: 0,
1280
+ width: 3,
1281
+ height: 3,
1282
+ minValue: 0,
1283
+ maxValue: 7200,
1284
+ warningThreshold: 3600,
1285
+ criticalThreshold: 5400,
1286
+ metricConfig: {
1287
+ metricName: AlertMetricType.TimeToResolve,
1288
+ aggregationType: MetricsAggregationType.Avg,
1289
+ },
1290
+ }),
1291
+ createGaugeComponent({
1292
+ title: "MTTA",
1293
+ top: 6,
1294
+ left: 3,
1295
+ width: 3,
1296
+ height: 3,
1297
+ minValue: 0,
1298
+ maxValue: 3600,
1299
+ warningThreshold: 900,
1300
+ criticalThreshold: 1800,
1301
+ metricConfig: {
1302
+ metricName: AlertMetricType.TimeToAcknowledge,
1303
+ aggregationType: MetricsAggregationType.Avg,
1304
+ },
1305
+ }),
1306
+ createChartComponent({
1307
+ title: "MTTR and MTTA Over Time",
1308
+ chartType: DashboardChartType.Area,
1309
+ top: 6,
1310
+ left: 6,
1311
+ width: 6,
1312
+ height: 3,
1313
+ metricConfig: {
1314
+ metricName: AlertMetricType.TimeToResolve,
1315
+ aggregationType: MetricsAggregationType.Avg,
1316
+ legend: "MTTR",
1317
+ },
1318
+ }),
1319
+
1320
+ // Row 9: Section header
1321
+ createTextComponent({
1322
+ text: "Alert Details",
1323
+ top: 9,
1324
+ left: 0,
1325
+ width: 12,
1326
+ height: 1,
1327
+ isBold: true,
1328
+ }),
1329
+
1330
+ /*
1331
+ * Row 10-12: Operational tables. Like the Incident template, logs /
1332
+ * traces are intentionally absent — alert records are rows in
1333
+ * Postgres, not log/trace sources.
1334
+ */
1335
+ createTableComponent({
1336
+ title: "Alerts by Duration",
1337
+ top: 10,
1338
+ left: 0,
1339
+ width: 6,
1340
+ height: 3,
1341
+ metricConfig: {
1342
+ metricName: AlertMetricType.AlertDuration,
1343
+ aggregationType: MetricsAggregationType.Max,
1344
+ },
1345
+ }),
1346
+ createTableComponent({
1347
+ title: "Time to Acknowledge",
1348
+ top: 10,
1349
+ left: 6,
1350
+ width: 6,
1351
+ height: 3,
1352
+ metricConfig: {
1353
+ metricName: AlertMetricType.TimeToAcknowledge,
1354
+ aggregationType: MetricsAggregationType.Avg,
1355
+ },
1356
+ }),
1357
+ ];
1358
+
1359
+ return {
1360
+ _type: ObjectType.DashboardViewConfig,
1361
+ components,
1362
+ heightInDashboardUnits: Math.max(DashboardSize.heightInDashboardUnits, 13),
1363
+ };
1364
+ }
1365
+
1108
1366
  function createKubernetesDashboardConfig(): DashboardViewConfig {
1109
1367
  /*
1110
1368
  * Layout notes:
@@ -2427,6 +2685,8 @@ export function getTemplateConfig(
2427
2685
  return createMonitorDashboardConfig();
2428
2686
  case DashboardTemplateType.Incident:
2429
2687
  return createIncidentDashboardConfig();
2688
+ case DashboardTemplateType.Alert:
2689
+ return createAlertDashboardConfig();
2430
2690
  case DashboardTemplateType.Kubernetes:
2431
2691
  return createKubernetesDashboardConfig();
2432
2692
  case DashboardTemplateType.Host:
@@ -1,6 +1,3 @@
1
- import Metric from "../../Models/AnalyticsModels/Metric";
2
- import Includes from "../BaseDatabase/Includes";
3
- import Query from "../BaseDatabase/Query";
4
1
  import { JSONObject } from "../JSON";
5
2
  import MetricsViewConfig from "../Metrics/MetricsViewConfig";
6
3
  import RollingTime from "../RollingTime/RollingTime";
@@ -8,13 +5,6 @@ import RollingTime from "../RollingTime/RollingTime";
8
5
  export default interface MonitorStepMetricMonitor {
9
6
  metricViewConfig: MetricsViewConfig;
10
7
  rollingTime: RollingTime;
11
- /*
12
- * Stable telemetry entity keys (host / pod / container / ...) — scopes
13
- * every metric query in this step to rows carrying any of these in their
14
- * entityKeys column. Optional: monitors saved before this field existed
15
- * have it undefined.
16
- */
17
- entityKeys?: Array<string> | undefined;
18
8
  }
19
9
 
20
10
  export class MonitorStepMetricMonitorUtil {
@@ -25,26 +15,9 @@ export class MonitorStepMetricMonitorUtil {
25
15
  formulaConfigs: [],
26
16
  },
27
17
  rollingTime: RollingTime.Past1Minute,
28
- entityKeys: [],
29
18
  };
30
19
  }
31
20
 
32
- /*
33
- * Stamps the step's entity scope onto a per-queryConfig Query<Metric>.
34
- * Compiles to hasAny(entityKeys, [...]) server-side. Undefined/empty is
35
- * a no-op so monitors saved before the field existed are unaffected.
36
- */
37
- public static applyEntityScopeToQuery(
38
- query: Query<Metric>,
39
- monitor: MonitorStepMetricMonitor,
40
- ): Query<Metric> {
41
- if (monitor.entityKeys && monitor.entityKeys.length > 0) {
42
- query.entityKeys = new Includes(monitor.entityKeys);
43
- }
44
-
45
- return query;
46
- }
47
-
48
21
  public static fromJSON(json: JSONObject): MonitorStepMetricMonitor {
49
22
  return json as any as MonitorStepMetricMonitor;
50
23
  }