@oneuptime/common 10.0.71 → 10.0.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/Alert.ts +55 -0
- package/Models/DatabaseModels/Incident.ts +55 -0
- package/Models/DatabaseModels/StatusPage.ts +80 -0
- package/Server/API/StatusPageAPI.ts +4 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1776940714709-MigrationName.ts +41 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1776971364783-AddStatusPageLanguageSettings.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/AnalyticsDatabaseService.ts +17 -7
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +175 -29
- package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +71 -0
- package/Server/Utils/Monitor/MonitorAlert.ts +91 -7
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +171 -2
- package/Server/Utils/Monitor/MonitorIncident.ts +133 -8
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +423 -1
- package/Server/Utils/Monitor/MonitorResource.ts +2 -0
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +99 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +268 -0
- package/Types/Infrastructure/BasicMetrics.ts +75 -0
- package/Types/Metrics/MetricQueryData.ts +11 -0
- package/Types/Monitor/CriteriaFilter.ts +10 -0
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +11 -0
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +10 -0
- package/Types/Monitor/MetricMonitor/MetricSeriesResult.ts +20 -0
- package/Types/Monitor/MonitorMetricType.ts +34 -0
- package/Types/Monitor/ServerMonitor/ServerMonitorResponse.ts +8 -0
- package/Types/Probe/ProbeApiIngestResponse.ts +25 -0
- package/Types/StatusPage/StatusPageLanguage.ts +29 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +17 -12
- package/UI/Components/Charts/Bar/BarChart.tsx +16 -11
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +23 -0
- package/UI/Components/Charts/Line/LineChart.tsx +16 -11
- package/UI/Components/Filters/FiltersForm.tsx +26 -2
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +453 -0
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.tsx +229 -0
- package/Utils/Metrics/MetricSeriesFingerprint.ts +97 -0
- package/Utils/Monitor/MonitorMetricType.ts +309 -19
- package/build/dist/Models/DatabaseModels/Alert.js +57 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +57 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +82 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +4 -0
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776940714709-MigrationName.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776940714709-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776971364783-AddStatusPageLanguageSettings.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1776971364783-AddStatusPageLanguageSettings.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +14 -4
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +132 -30
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +58 -7
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +66 -12
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +112 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +91 -15
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +373 -0
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +2 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +65 -0
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.js +199 -0
- package/build/dist/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +10 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/MetricMonitor/MetricSeriesResult.js +2 -0
- package/build/dist/Types/Monitor/MetricMonitor/MetricSeriesResult.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js +28 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Types/StatusPage/StatusPageLanguage.js +21 -0
- package/build/dist/Types/StatusPage/StatusPageLanguage.js.map +1 -0
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +13 -12
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +12 -11
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +11 -3
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +12 -11
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Filters/FiltersForm.js +6 -2
- package/build/dist/UI/Components/Filters/FiltersForm.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +383 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js +109 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js.map +1 -0
- package/build/dist/Utils/Metrics/MetricSeriesFingerprint.js +81 -0
- package/build/dist/Utils/Metrics/MetricSeriesFingerprint.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorMetricType.js +287 -19
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/package.json +1 -1
|
@@ -975,6 +975,61 @@ export default class Alert extends BaseModel {
|
|
|
975
975
|
})
|
|
976
976
|
public createdCriteriaId?: string = undefined;
|
|
977
977
|
|
|
978
|
+
@ColumnAccessControl({
|
|
979
|
+
create: [],
|
|
980
|
+
read: [
|
|
981
|
+
Permission.ProjectOwner,
|
|
982
|
+
Permission.ProjectAdmin,
|
|
983
|
+
Permission.ProjectMember,
|
|
984
|
+
Permission.Viewer,
|
|
985
|
+
Permission.AlertManager,
|
|
986
|
+
Permission.ReadAlert,
|
|
987
|
+
Permission.ReadAllProjectResources,
|
|
988
|
+
],
|
|
989
|
+
update: [],
|
|
990
|
+
})
|
|
991
|
+
@Index()
|
|
992
|
+
@TableColumn({
|
|
993
|
+
type: TableColumnType.LongText,
|
|
994
|
+
required: false,
|
|
995
|
+
isDefaultValueColumn: false,
|
|
996
|
+
title: "Series Fingerprint",
|
|
997
|
+
description:
|
|
998
|
+
"For metric monitors with per-series alerting (e.g. grouped by host.name), this is a stable hash of the series label values so one alert is created per affected series.",
|
|
999
|
+
})
|
|
1000
|
+
@Column({
|
|
1001
|
+
type: ColumnType.LongText,
|
|
1002
|
+
nullable: true,
|
|
1003
|
+
})
|
|
1004
|
+
public seriesFingerprint?: string = undefined;
|
|
1005
|
+
|
|
1006
|
+
@ColumnAccessControl({
|
|
1007
|
+
create: [],
|
|
1008
|
+
read: [
|
|
1009
|
+
Permission.ProjectOwner,
|
|
1010
|
+
Permission.ProjectAdmin,
|
|
1011
|
+
Permission.ProjectMember,
|
|
1012
|
+
Permission.Viewer,
|
|
1013
|
+
Permission.AlertManager,
|
|
1014
|
+
Permission.ReadAlert,
|
|
1015
|
+
Permission.ReadAllProjectResources,
|
|
1016
|
+
],
|
|
1017
|
+
update: [],
|
|
1018
|
+
})
|
|
1019
|
+
@TableColumn({
|
|
1020
|
+
type: TableColumnType.JSON,
|
|
1021
|
+
required: false,
|
|
1022
|
+
isDefaultValueColumn: false,
|
|
1023
|
+
title: "Series Labels",
|
|
1024
|
+
description:
|
|
1025
|
+
"Attribute key/value pairs that identify the affected series (e.g. {host.name: prod-db-01}) when this alert was created from a per-series metric breach.",
|
|
1026
|
+
})
|
|
1027
|
+
@Column({
|
|
1028
|
+
type: ColumnType.JSON,
|
|
1029
|
+
nullable: true,
|
|
1030
|
+
})
|
|
1031
|
+
public seriesLabels?: JSONObject = undefined;
|
|
1032
|
+
|
|
978
1033
|
@ColumnAccessControl({
|
|
979
1034
|
create: [],
|
|
980
1035
|
read: [
|
|
@@ -1456,6 +1456,61 @@ export default class Incident extends BaseModel {
|
|
|
1456
1456
|
})
|
|
1457
1457
|
public createdIncidentTemplateId?: string = undefined;
|
|
1458
1458
|
|
|
1459
|
+
@ColumnAccessControl({
|
|
1460
|
+
create: [],
|
|
1461
|
+
read: [
|
|
1462
|
+
Permission.ProjectOwner,
|
|
1463
|
+
Permission.ProjectAdmin,
|
|
1464
|
+
Permission.ProjectMember,
|
|
1465
|
+
Permission.Viewer,
|
|
1466
|
+
Permission.IncidentManager,
|
|
1467
|
+
Permission.ReadProjectIncident,
|
|
1468
|
+
Permission.ReadAllProjectResources,
|
|
1469
|
+
],
|
|
1470
|
+
update: [],
|
|
1471
|
+
})
|
|
1472
|
+
@Index()
|
|
1473
|
+
@TableColumn({
|
|
1474
|
+
type: TableColumnType.LongText,
|
|
1475
|
+
required: false,
|
|
1476
|
+
isDefaultValueColumn: false,
|
|
1477
|
+
title: "Series Fingerprint",
|
|
1478
|
+
description:
|
|
1479
|
+
"For metric monitors with per-series alerting (e.g. grouped by host.name), this is a stable hash of the series label values so one incident is created per affected series.",
|
|
1480
|
+
})
|
|
1481
|
+
@Column({
|
|
1482
|
+
type: ColumnType.LongText,
|
|
1483
|
+
nullable: true,
|
|
1484
|
+
})
|
|
1485
|
+
public seriesFingerprint?: string = undefined;
|
|
1486
|
+
|
|
1487
|
+
@ColumnAccessControl({
|
|
1488
|
+
create: [],
|
|
1489
|
+
read: [
|
|
1490
|
+
Permission.ProjectOwner,
|
|
1491
|
+
Permission.ProjectAdmin,
|
|
1492
|
+
Permission.ProjectMember,
|
|
1493
|
+
Permission.Viewer,
|
|
1494
|
+
Permission.IncidentManager,
|
|
1495
|
+
Permission.ReadProjectIncident,
|
|
1496
|
+
Permission.ReadAllProjectResources,
|
|
1497
|
+
],
|
|
1498
|
+
update: [],
|
|
1499
|
+
})
|
|
1500
|
+
@TableColumn({
|
|
1501
|
+
type: TableColumnType.JSON,
|
|
1502
|
+
required: false,
|
|
1503
|
+
isDefaultValueColumn: false,
|
|
1504
|
+
title: "Series Labels",
|
|
1505
|
+
description:
|
|
1506
|
+
"Attribute key/value pairs that identify the affected series (e.g. {host.name: prod-db-01}) when this incident was created from a per-series metric breach.",
|
|
1507
|
+
})
|
|
1508
|
+
@Column({
|
|
1509
|
+
type: ColumnType.JSON,
|
|
1510
|
+
nullable: true,
|
|
1511
|
+
})
|
|
1512
|
+
public seriesLabels?: JSONObject = undefined;
|
|
1513
|
+
|
|
1459
1514
|
@ColumnAccessControl({
|
|
1460
1515
|
create: [],
|
|
1461
1516
|
read: [
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
ManyToOne,
|
|
44
44
|
} from "typeorm";
|
|
45
45
|
import UptimePrecision from "../../Types/StatusPage/UptimePrecision";
|
|
46
|
+
import { DEFAULT_STATUS_PAGE_LANGUAGE } from "../../Types/StatusPage/StatusPageLanguage";
|
|
46
47
|
|
|
47
48
|
@EnableDocumentation()
|
|
48
49
|
@EnableMCP()
|
|
@@ -2991,4 +2992,83 @@ export default class StatusPage extends BaseModel {
|
|
|
2991
2992
|
create: PlanType.Free,
|
|
2992
2993
|
})
|
|
2993
2994
|
public embeddedOverallStatusToken?: string = undefined;
|
|
2995
|
+
|
|
2996
|
+
@ColumnAccessControl({
|
|
2997
|
+
create: [
|
|
2998
|
+
Permission.ProjectOwner,
|
|
2999
|
+
Permission.ProjectAdmin,
|
|
3000
|
+
Permission.ProjectMember,
|
|
3001
|
+
Permission.StatusPageManager,
|
|
3002
|
+
Permission.CreateProjectStatusPage,
|
|
3003
|
+
],
|
|
3004
|
+
read: [
|
|
3005
|
+
Permission.ProjectOwner,
|
|
3006
|
+
Permission.ProjectAdmin,
|
|
3007
|
+
Permission.ProjectMember,
|
|
3008
|
+
Permission.Viewer,
|
|
3009
|
+
Permission.StatusPageManager,
|
|
3010
|
+
Permission.ReadProjectStatusPage,
|
|
3011
|
+
Permission.ReadAllProjectResources,
|
|
3012
|
+
],
|
|
3013
|
+
update: [
|
|
3014
|
+
Permission.ProjectOwner,
|
|
3015
|
+
Permission.ProjectAdmin,
|
|
3016
|
+
Permission.ProjectMember,
|
|
3017
|
+
Permission.StatusPageManager,
|
|
3018
|
+
Permission.EditProjectStatusPage,
|
|
3019
|
+
],
|
|
3020
|
+
})
|
|
3021
|
+
@TableColumn({
|
|
3022
|
+
type: TableColumnType.ShortText,
|
|
3023
|
+
title: "Default Language",
|
|
3024
|
+
required: false,
|
|
3025
|
+
defaultValue: DEFAULT_STATUS_PAGE_LANGUAGE,
|
|
3026
|
+
description:
|
|
3027
|
+
"Default language that the status page is shown in when a visitor arrives for the first time.",
|
|
3028
|
+
})
|
|
3029
|
+
@Column({
|
|
3030
|
+
type: ColumnType.ShortText,
|
|
3031
|
+
nullable: true,
|
|
3032
|
+
default: DEFAULT_STATUS_PAGE_LANGUAGE,
|
|
3033
|
+
})
|
|
3034
|
+
public defaultLanguage?: string = undefined;
|
|
3035
|
+
|
|
3036
|
+
@ColumnAccessControl({
|
|
3037
|
+
create: [
|
|
3038
|
+
Permission.ProjectOwner,
|
|
3039
|
+
Permission.ProjectAdmin,
|
|
3040
|
+
Permission.ProjectMember,
|
|
3041
|
+
Permission.StatusPageManager,
|
|
3042
|
+
Permission.CreateProjectStatusPage,
|
|
3043
|
+
],
|
|
3044
|
+
read: [
|
|
3045
|
+
Permission.ProjectOwner,
|
|
3046
|
+
Permission.ProjectAdmin,
|
|
3047
|
+
Permission.ProjectMember,
|
|
3048
|
+
Permission.Viewer,
|
|
3049
|
+
Permission.StatusPageManager,
|
|
3050
|
+
Permission.ReadProjectStatusPage,
|
|
3051
|
+
Permission.ReadAllProjectResources,
|
|
3052
|
+
],
|
|
3053
|
+
update: [
|
|
3054
|
+
Permission.ProjectOwner,
|
|
3055
|
+
Permission.ProjectAdmin,
|
|
3056
|
+
Permission.ProjectMember,
|
|
3057
|
+
Permission.StatusPageManager,
|
|
3058
|
+
Permission.EditProjectStatusPage,
|
|
3059
|
+
],
|
|
3060
|
+
})
|
|
3061
|
+
@TableColumn({
|
|
3062
|
+
isDefaultValueColumn: false,
|
|
3063
|
+
required: false,
|
|
3064
|
+
type: TableColumnType.JSON,
|
|
3065
|
+
title: "Enabled Languages",
|
|
3066
|
+
description:
|
|
3067
|
+
"Languages offered in the footer language switcher. Leave empty to offer all supported languages.",
|
|
3068
|
+
})
|
|
3069
|
+
@Column({
|
|
3070
|
+
type: ColumnType.JSON,
|
|
3071
|
+
nullable: true,
|
|
3072
|
+
})
|
|
3073
|
+
public enabledLanguages?: Array<string> = undefined;
|
|
2994
3074
|
}
|
|
@@ -225,6 +225,7 @@ export default class StatusPageAPI extends BaseAPI<
|
|
|
225
225
|
pageTitle: true,
|
|
226
226
|
pageDescription: true,
|
|
227
227
|
name: true,
|
|
228
|
+
defaultLanguage: true,
|
|
228
229
|
},
|
|
229
230
|
props: {
|
|
230
231
|
isRoot: true,
|
|
@@ -244,6 +245,7 @@ export default class StatusPageAPI extends BaseAPI<
|
|
|
244
245
|
title: statusPage.pageTitle || statusPage.name,
|
|
245
246
|
description: statusPage.pageDescription,
|
|
246
247
|
_id: statusPage._id?.toString(),
|
|
248
|
+
defaultLanguage: statusPage.defaultLanguage || null,
|
|
247
249
|
});
|
|
248
250
|
},
|
|
249
251
|
);
|
|
@@ -883,6 +885,8 @@ export default class StatusPageAPI extends BaseAPI<
|
|
|
883
885
|
showAnnouncementsOnStatusPage: true,
|
|
884
886
|
showScheduledMaintenanceEventsOnStatusPage: true,
|
|
885
887
|
showSubscriberPageOnStatusPage: true,
|
|
888
|
+
defaultLanguage: true,
|
|
889
|
+
enabledLanguages: true,
|
|
886
890
|
};
|
|
887
891
|
|
|
888
892
|
const hasEnabledSSO: PositiveNumber =
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class MigrationName1776940714709 implements MigrationInterface {
|
|
4
|
+
public name = "MigrationName1776940714709";
|
|
5
|
+
|
|
6
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
7
|
+
await queryRunner.query(
|
|
8
|
+
`ALTER TABLE "Incident" ADD "seriesFingerprint" character varying`,
|
|
9
|
+
);
|
|
10
|
+
await queryRunner.query(`ALTER TABLE "Incident" ADD "seriesLabels" jsonb`);
|
|
11
|
+
await queryRunner.query(
|
|
12
|
+
`ALTER TABLE "Alert" ADD "seriesFingerprint" character varying`,
|
|
13
|
+
);
|
|
14
|
+
await queryRunner.query(`ALTER TABLE "Alert" ADD "seriesLabels" jsonb`);
|
|
15
|
+
await queryRunner.query(
|
|
16
|
+
`CREATE INDEX "IDX_865fc7905f35947b294ca36b83" ON "Incident" ("seriesFingerprint") `,
|
|
17
|
+
);
|
|
18
|
+
await queryRunner.query(
|
|
19
|
+
`CREATE INDEX "IDX_5705362784705d225735b1a844" ON "Alert" ("seriesFingerprint") `,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
24
|
+
await queryRunner.query(
|
|
25
|
+
`DROP INDEX "public"."IDX_5705362784705d225735b1a844"`,
|
|
26
|
+
);
|
|
27
|
+
await queryRunner.query(
|
|
28
|
+
`DROP INDEX "public"."IDX_865fc7905f35947b294ca36b83"`,
|
|
29
|
+
);
|
|
30
|
+
await queryRunner.query(`ALTER TABLE "Alert" DROP COLUMN "seriesLabels"`);
|
|
31
|
+
await queryRunner.query(
|
|
32
|
+
`ALTER TABLE "Alert" DROP COLUMN "seriesFingerprint"`,
|
|
33
|
+
);
|
|
34
|
+
await queryRunner.query(
|
|
35
|
+
`ALTER TABLE "Incident" DROP COLUMN "seriesLabels"`,
|
|
36
|
+
);
|
|
37
|
+
await queryRunner.query(
|
|
38
|
+
`ALTER TABLE "Incident" DROP COLUMN "seriesFingerprint"`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
2
|
+
|
|
3
|
+
export class AddStatusPageLanguageSettings1776971364783
|
|
4
|
+
implements MigrationInterface
|
|
5
|
+
{
|
|
6
|
+
public name: string = "AddStatusPageLanguageSettings1776971364783";
|
|
7
|
+
|
|
8
|
+
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
9
|
+
await queryRunner.query(
|
|
10
|
+
`ALTER TABLE "StatusPage" ADD "defaultLanguage" character varying DEFAULT 'en'`,
|
|
11
|
+
);
|
|
12
|
+
await queryRunner.query(
|
|
13
|
+
`ALTER TABLE "StatusPage" ADD "enabledLanguages" jsonb`,
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
18
|
+
await queryRunner.query(
|
|
19
|
+
`ALTER TABLE "StatusPage" DROP COLUMN "enabledLanguages"`,
|
|
20
|
+
);
|
|
21
|
+
await queryRunner.query(
|
|
22
|
+
`ALTER TABLE "StatusPage" DROP COLUMN "defaultLanguage"`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -289,6 +289,8 @@ import { MigrationName1776761171349 } from "./1776761171349-MigrationName";
|
|
|
289
289
|
import { MigrationName1776801030808 } from "./1776801030808-MigrationName";
|
|
290
290
|
import { MigrationName1776865086264 } from "./1776865086264-MigrationName";
|
|
291
291
|
import { DedupeKubernetesClustersAndAddUniqueIndex1776881254913 } from "./1776881254913-DedupeKubernetesClustersAndAddUniqueIndex";
|
|
292
|
+
import { MigrationName1776940714709 } from "./1776940714709-MigrationName";
|
|
293
|
+
import { AddStatusPageLanguageSettings1776971364783 } from "./1776971364783-AddStatusPageLanguageSettings";
|
|
292
294
|
export default [
|
|
293
295
|
InitialMigration,
|
|
294
296
|
MigrationName1717678334852,
|
|
@@ -581,4 +583,6 @@ export default [
|
|
|
581
583
|
MigrationName1776801030808,
|
|
582
584
|
MigrationName1776865086264,
|
|
583
585
|
DedupeKubernetesClustersAndAddUniqueIndex1776881254913,
|
|
586
|
+
MigrationName1776940714709,
|
|
587
|
+
AddStatusPageLanguageSettings1776971364783,
|
|
584
588
|
];
|
|
@@ -454,10 +454,9 @@ export default class AnalyticsDatabaseService<
|
|
|
454
454
|
|
|
455
455
|
// convert date column from string to date.
|
|
456
456
|
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
: undefined;
|
|
457
|
+
const groupByColumnNames: Array<string> = aggregateBy.groupBy
|
|
458
|
+
? Object.keys(aggregateBy.groupBy)
|
|
459
|
+
: [];
|
|
461
460
|
|
|
462
461
|
for (const item of items) {
|
|
463
462
|
if (
|
|
@@ -483,6 +482,14 @@ export default class AnalyticsDatabaseService<
|
|
|
483
482
|
);
|
|
484
483
|
}
|
|
485
484
|
|
|
485
|
+
/*
|
|
486
|
+
* Preserve every group-by column on the aggregated row. The
|
|
487
|
+
* previous implementation only copied the first column, which
|
|
488
|
+
* silently dropped the rest when callers grouped by more than
|
|
489
|
+
* one dimension (e.g. attributes + name). `AggregatedModel`'s
|
|
490
|
+
* index signature already accepts arbitrary keys, so existing
|
|
491
|
+
* single-column consumers still work.
|
|
492
|
+
*/
|
|
486
493
|
const aggregatedModel: AggregatedModel = {
|
|
487
494
|
timestamp: OneUptimeDate.fromString(
|
|
488
495
|
(item as JSONObject)[
|
|
@@ -492,11 +499,14 @@ export default class AnalyticsDatabaseService<
|
|
|
492
499
|
value: (item as JSONObject)[
|
|
493
500
|
aggregateBy.aggregateColumnName as string
|
|
494
501
|
] as number,
|
|
495
|
-
[groupByColumnName as string]: (item as JSONObject)[
|
|
496
|
-
groupByColumnName as string
|
|
497
|
-
],
|
|
498
502
|
};
|
|
499
503
|
|
|
504
|
+
for (const groupByColumnName of groupByColumnNames) {
|
|
505
|
+
aggregatedModel[groupByColumnName] = (item as JSONObject)[
|
|
506
|
+
groupByColumnName
|
|
507
|
+
] as AggregatedModel[string];
|
|
508
|
+
}
|
|
509
|
+
|
|
500
510
|
aggregatedItems.push(aggregatedModel);
|
|
501
511
|
}
|
|
502
512
|
|
|
@@ -8,6 +8,7 @@ import MetricCriteriaContext, {
|
|
|
8
8
|
MetricComponent,
|
|
9
9
|
MetricComponentValue,
|
|
10
10
|
} from "../../../../Types/Monitor/MetricMonitor/MetricCriteriaContext";
|
|
11
|
+
import MetricSeriesResult from "../../../../Types/Monitor/MetricMonitor/MetricSeriesResult";
|
|
11
12
|
import MonitorStep from "../../../../Types/Monitor/MonitorStep";
|
|
12
13
|
import { JSONObject } from "../../../../Types/JSON";
|
|
13
14
|
import DataToProcess from "../DataToProcess";
|
|
@@ -23,6 +24,20 @@ import CaptureSpan from "../../Telemetry/CaptureSpan";
|
|
|
23
24
|
import MetricUnitUtil from "../../../../Utils/MetricUnitUtil";
|
|
24
25
|
import MetricFormulaEvaluator from "../../../../Utils/Metrics/MetricFormulaEvaluator";
|
|
25
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Result of evaluating a single criteria filter against a single metric
|
|
29
|
+
* series. `rootCause` is null when the filter did not match; otherwise
|
|
30
|
+
* it's the human-readable comparison message. `context` always reflects
|
|
31
|
+
* the metric identity for this series (used to render the metric
|
|
32
|
+
* details + breaching samples section of the incident root cause).
|
|
33
|
+
*/
|
|
34
|
+
export interface MetricSeriesEvaluationResult {
|
|
35
|
+
fingerprint: string | undefined;
|
|
36
|
+
labels: JSONObject;
|
|
37
|
+
rootCause: string | null;
|
|
38
|
+
context: MetricCriteriaContext;
|
|
39
|
+
}
|
|
40
|
+
|
|
26
41
|
export default class MetricMonitorCriteria {
|
|
27
42
|
@CaptureSpan()
|
|
28
43
|
public static async isMonitorInstanceCriteriaFilterMet(input: {
|
|
@@ -30,8 +45,50 @@ export default class MetricMonitorCriteria {
|
|
|
30
45
|
criteriaFilter: CriteriaFilter;
|
|
31
46
|
monitorStep: MonitorStep;
|
|
32
47
|
}): Promise<string | null> {
|
|
33
|
-
|
|
48
|
+
const evaluations: Array<MetricSeriesEvaluationResult> =
|
|
49
|
+
MetricMonitorCriteria.evaluateAllSeries(input);
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
* Backwards-compat: the scalar entrypoint collapses per-series
|
|
53
|
+
* evaluation down to the first matching series so existing callers
|
|
54
|
+
* (single-incident path) keep working. The per-series code path uses
|
|
55
|
+
* `evaluateAllSeries` directly.
|
|
56
|
+
*/
|
|
57
|
+
const match: MetricSeriesEvaluationResult | undefined = evaluations.find(
|
|
58
|
+
(e: MetricSeriesEvaluationResult) => {
|
|
59
|
+
return e.rootCause !== null;
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
* Always populate the legacy single-context field so the root-cause
|
|
65
|
+
* renderer can still read metric identity from the criteria filter
|
|
66
|
+
* even when nothing matched. Pick the first evaluation's context.
|
|
67
|
+
*/
|
|
68
|
+
if (evaluations.length > 0) {
|
|
69
|
+
input.criteriaFilter.metricCriteriaContext = (
|
|
70
|
+
match || evaluations[0]!
|
|
71
|
+
).context;
|
|
72
|
+
}
|
|
34
73
|
|
|
74
|
+
return match ? match.rootCause : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Evaluate a single criteria filter against every series produced by
|
|
79
|
+
* the monitor. For monitors without group-by, this returns a single
|
|
80
|
+
* evaluation covering all aggregated results (legacy behavior). For
|
|
81
|
+
* monitors with group-by attributes set, it returns one evaluation
|
|
82
|
+
* per unique series fingerprint — each with its own
|
|
83
|
+
* `MetricCriteriaContext` carrying that series' breaching samples
|
|
84
|
+
* and labels. The caller fans this out into one incident per
|
|
85
|
+
* breaching series.
|
|
86
|
+
*/
|
|
87
|
+
public static evaluateAllSeries(input: {
|
|
88
|
+
dataToProcess: DataToProcess;
|
|
89
|
+
criteriaFilter: CriteriaFilter;
|
|
90
|
+
monitorStep: MonitorStep;
|
|
91
|
+
}): Array<MetricSeriesEvaluationResult> {
|
|
35
92
|
if (
|
|
36
93
|
input.criteriaFilter.metricMonitorOptions &&
|
|
37
94
|
!input.criteriaFilter.metricMonitorOptions.metricAggregationType
|
|
@@ -41,20 +98,14 @@ export default class MetricMonitorCriteria {
|
|
|
41
98
|
}
|
|
42
99
|
|
|
43
100
|
if (input.criteriaFilter.checkOn !== CheckOn.MetricValue) {
|
|
44
|
-
return
|
|
101
|
+
return [];
|
|
45
102
|
}
|
|
46
103
|
|
|
47
|
-
const rawThreshold: number | null = CompareCriteria.convertToNumber(
|
|
48
|
-
input.criteriaFilter.value,
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
const metricAlias: string =
|
|
52
|
-
input.criteriaFilter.metricMonitorOptions?.metricAlias || "";
|
|
53
|
-
|
|
54
104
|
const metricResponse: MetricMonitorResponse =
|
|
55
105
|
input.dataToProcess as MetricMonitorResponse;
|
|
56
|
-
|
|
57
|
-
|
|
106
|
+
|
|
107
|
+
const seriesBreakdown: Array<MetricSeriesResult> | undefined =
|
|
108
|
+
metricResponse.seriesBreakdown;
|
|
58
109
|
|
|
59
110
|
const queryConfigs: Array<MetricQueryConfigData> =
|
|
60
111
|
input.monitorStep.data?.metricMonitor?.metricViewConfig?.queryConfigs ||
|
|
@@ -63,6 +114,60 @@ export default class MetricMonitorCriteria {
|
|
|
63
114
|
input.monitorStep.data?.metricMonitor?.metricViewConfig?.formulaConfigs ||
|
|
64
115
|
[];
|
|
65
116
|
|
|
117
|
+
/*
|
|
118
|
+
* Series-less path: one synthetic "all-series" evaluation over the
|
|
119
|
+
* flat metricResult. Preserves the pre-group-by behavior exactly.
|
|
120
|
+
*/
|
|
121
|
+
if (!seriesBreakdown || seriesBreakdown.length === 0) {
|
|
122
|
+
const result: MetricSeriesEvaluationResult =
|
|
123
|
+
MetricMonitorCriteria.evaluateOneSeries({
|
|
124
|
+
criteriaFilter: input.criteriaFilter,
|
|
125
|
+
aggregatedResults: metricResponse.metricResult || [],
|
|
126
|
+
queryConfigs,
|
|
127
|
+
formulaConfigs,
|
|
128
|
+
seriesFingerprint: undefined,
|
|
129
|
+
seriesLabels: {},
|
|
130
|
+
});
|
|
131
|
+
return [result];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return seriesBreakdown.map((series: MetricSeriesResult) => {
|
|
135
|
+
return MetricMonitorCriteria.evaluateOneSeries({
|
|
136
|
+
criteriaFilter: input.criteriaFilter,
|
|
137
|
+
aggregatedResults: series.aggregatedResults,
|
|
138
|
+
queryConfigs,
|
|
139
|
+
formulaConfigs,
|
|
140
|
+
seriesFingerprint: series.fingerprint,
|
|
141
|
+
seriesLabels: series.labels,
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Core evaluation loop: compare the samples for one metric series
|
|
148
|
+
* against the criteria threshold. Builds the metric identity context,
|
|
149
|
+
* identifies breaching samples, and assembles the human-readable
|
|
150
|
+
* root-cause message. Factored out so `evaluateAllSeries` can invoke
|
|
151
|
+
* it once per series without duplicating logic.
|
|
152
|
+
*/
|
|
153
|
+
private static evaluateOneSeries(input: {
|
|
154
|
+
criteriaFilter: CriteriaFilter;
|
|
155
|
+
aggregatedResults: Array<AggregatedResult>;
|
|
156
|
+
queryConfigs: Array<MetricQueryConfigData>;
|
|
157
|
+
formulaConfigs: Array<MetricFormulaConfigData>;
|
|
158
|
+
seriesFingerprint: string | undefined;
|
|
159
|
+
seriesLabels: JSONObject;
|
|
160
|
+
}): MetricSeriesEvaluationResult {
|
|
161
|
+
const rawThreshold: number | null = CompareCriteria.convertToNumber(
|
|
162
|
+
input.criteriaFilter.value,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const metricAlias: string =
|
|
166
|
+
input.criteriaFilter.metricMonitorOptions?.metricAlias || "";
|
|
167
|
+
|
|
168
|
+
const metricAggregatedResult: Array<AggregatedResult> =
|
|
169
|
+
input.aggregatedResults;
|
|
170
|
+
|
|
66
171
|
/*
|
|
67
172
|
* Resolve which query/formula the alias refers to. Use explicit index
|
|
68
173
|
* checks (not `findIndex() || -1`, which incorrectly falls back to -1
|
|
@@ -73,25 +178,25 @@ export default class MetricMonitorCriteria {
|
|
|
73
178
|
let aliasIndex: number = -1;
|
|
74
179
|
|
|
75
180
|
if (metricAlias) {
|
|
76
|
-
const qIdx: number = queryConfigs.findIndex(
|
|
181
|
+
const qIdx: number = input.queryConfigs.findIndex(
|
|
77
182
|
(q: MetricQueryConfigData) => {
|
|
78
183
|
return q.metricAliasData?.metricVariable === metricAlias;
|
|
79
184
|
},
|
|
80
185
|
);
|
|
81
186
|
|
|
82
187
|
if (qIdx >= 0) {
|
|
83
|
-
matchedQuery = queryConfigs[qIdx] || null;
|
|
188
|
+
matchedQuery = input.queryConfigs[qIdx] || null;
|
|
84
189
|
aliasIndex = qIdx;
|
|
85
190
|
} else {
|
|
86
|
-
const fIdx: number = formulaConfigs.findIndex(
|
|
191
|
+
const fIdx: number = input.formulaConfigs.findIndex(
|
|
87
192
|
(f: MetricFormulaConfigData) => {
|
|
88
193
|
return f.metricAliasData?.metricVariable === metricAlias;
|
|
89
194
|
},
|
|
90
195
|
);
|
|
91
196
|
|
|
92
197
|
if (fIdx >= 0) {
|
|
93
|
-
matchedFormula = formulaConfigs[fIdx] || null;
|
|
94
|
-
aliasIndex = queryConfigs.length + fIdx;
|
|
198
|
+
matchedFormula = input.formulaConfigs[fIdx] || null;
|
|
199
|
+
aliasIndex = input.queryConfigs.length + fIdx;
|
|
95
200
|
}
|
|
96
201
|
}
|
|
97
202
|
}
|
|
@@ -105,8 +210,8 @@ export default class MetricMonitorCriteria {
|
|
|
105
210
|
? metricAggregatedResult[aliasIndex]
|
|
106
211
|
: metricAggregatedResult[0];
|
|
107
212
|
|
|
108
|
-
if (!matchedQuery && !matchedFormula && queryConfigs[0]) {
|
|
109
|
-
matchedQuery = queryConfigs[0];
|
|
213
|
+
if (!matchedQuery && !matchedFormula && input.queryConfigs[0]) {
|
|
214
|
+
matchedQuery = input.queryConfigs[0];
|
|
110
215
|
}
|
|
111
216
|
|
|
112
217
|
/*
|
|
@@ -119,14 +224,24 @@ export default class MetricMonitorCriteria {
|
|
|
119
224
|
matchedFormula,
|
|
120
225
|
metricAlias,
|
|
121
226
|
criteriaFilter: input.criteriaFilter,
|
|
122
|
-
queryConfigs,
|
|
123
|
-
formulaConfigs,
|
|
227
|
+
queryConfigs: input.queryConfigs,
|
|
228
|
+
formulaConfigs: input.formulaConfigs,
|
|
124
229
|
});
|
|
125
230
|
|
|
126
|
-
input.
|
|
231
|
+
if (input.seriesFingerprint) {
|
|
232
|
+
metricContext.seriesFingerprint = input.seriesFingerprint;
|
|
233
|
+
}
|
|
234
|
+
if (input.seriesLabels && Object.keys(input.seriesLabels).length > 0) {
|
|
235
|
+
metricContext.seriesLabels = input.seriesLabels;
|
|
236
|
+
}
|
|
127
237
|
|
|
128
238
|
if (rawThreshold === null) {
|
|
129
|
-
return
|
|
239
|
+
return {
|
|
240
|
+
fingerprint: input.seriesFingerprint,
|
|
241
|
+
labels: input.seriesLabels,
|
|
242
|
+
rootCause: null,
|
|
243
|
+
context: metricContext,
|
|
244
|
+
};
|
|
130
245
|
}
|
|
131
246
|
|
|
132
247
|
/*
|
|
@@ -180,11 +295,21 @@ export default class MetricMonitorCriteria {
|
|
|
180
295
|
NoDataPolicy.Ignore;
|
|
181
296
|
|
|
182
297
|
if (policy === NoDataPolicy.Ignore) {
|
|
183
|
-
return
|
|
298
|
+
return {
|
|
299
|
+
fingerprint: input.seriesFingerprint,
|
|
300
|
+
labels: input.seriesLabels,
|
|
301
|
+
rootCause: null,
|
|
302
|
+
context: metricContext,
|
|
303
|
+
};
|
|
184
304
|
}
|
|
185
305
|
|
|
186
306
|
if (policy === NoDataPolicy.Trigger) {
|
|
187
|
-
return
|
|
307
|
+
return {
|
|
308
|
+
fingerprint: input.seriesFingerprint,
|
|
309
|
+
labels: input.seriesLabels,
|
|
310
|
+
rootCause: `No data received for ${metricContext.metricName} in the evaluation window — triggering per no-data policy.`,
|
|
311
|
+
context: metricContext,
|
|
312
|
+
};
|
|
188
313
|
}
|
|
189
314
|
|
|
190
315
|
// TreatAsZero: fall through to the comparator with value 0.
|
|
@@ -206,7 +331,12 @@ export default class MetricMonitorCriteria {
|
|
|
206
331
|
});
|
|
207
332
|
|
|
208
333
|
if (!comparisonMessage) {
|
|
209
|
-
return
|
|
334
|
+
return {
|
|
335
|
+
fingerprint: input.seriesFingerprint,
|
|
336
|
+
labels: input.seriesLabels,
|
|
337
|
+
rootCause: null,
|
|
338
|
+
context: metricContext,
|
|
339
|
+
};
|
|
210
340
|
}
|
|
211
341
|
|
|
212
342
|
/*
|
|
@@ -232,8 +362,8 @@ export default class MetricMonitorCriteria {
|
|
|
232
362
|
matchedFormula
|
|
233
363
|
? MetricMonitorCriteria.buildComponentValueLookup({
|
|
234
364
|
components: metricContext.components || [],
|
|
235
|
-
queryConfigs,
|
|
236
|
-
formulaConfigs,
|
|
365
|
+
queryConfigs: input.queryConfigs,
|
|
366
|
+
formulaConfigs: input.formulaConfigs,
|
|
237
367
|
metricAggregatedResult,
|
|
238
368
|
})
|
|
239
369
|
: null;
|
|
@@ -276,7 +406,12 @@ export default class MetricMonitorCriteria {
|
|
|
276
406
|
metricContext.breachingSamples = breachingSamples;
|
|
277
407
|
}
|
|
278
408
|
|
|
279
|
-
return
|
|
409
|
+
return {
|
|
410
|
+
fingerprint: input.seriesFingerprint,
|
|
411
|
+
labels: input.seriesLabels,
|
|
412
|
+
rootCause: comparisonMessage,
|
|
413
|
+
context: metricContext,
|
|
414
|
+
};
|
|
280
415
|
}
|
|
281
416
|
|
|
282
417
|
private static buildComponentValueLookup(input: {
|
|
@@ -438,6 +573,17 @@ export default class MetricMonitorCriteria {
|
|
|
438
573
|
? Object.keys(q.metricQueryData.groupBy as Record<string, unknown>)
|
|
439
574
|
: [];
|
|
440
575
|
|
|
576
|
+
/*
|
|
577
|
+
* Include user-selected attribute keys as part of the groupBy view
|
|
578
|
+
* so the root-cause block shows "Grouped By: host.name" not just the
|
|
579
|
+
* raw columns ClickHouse was asked to partition on.
|
|
580
|
+
*/
|
|
581
|
+
const groupByAttributeKeys: Array<string> =
|
|
582
|
+
q?.metricQueryData?.groupByAttributeKeys || [];
|
|
583
|
+
const allGroupBy: Array<string> = Array.from(
|
|
584
|
+
new Set([...groupBy, ...groupByAttributeKeys]),
|
|
585
|
+
);
|
|
586
|
+
|
|
441
587
|
const components: Array<MetricComponent> | undefined = f
|
|
442
588
|
? MetricMonitorCriteria.buildFormulaComponents({
|
|
443
589
|
formulaConfig: f,
|
|
@@ -454,7 +600,7 @@ export default class MetricMonitorCriteria {
|
|
|
454
600
|
isFormula: Boolean(f),
|
|
455
601
|
formulaExpression: f?.metricFormulaData?.metricFormula,
|
|
456
602
|
filterAttributes,
|
|
457
|
-
groupBy,
|
|
603
|
+
groupBy: allGroupBy,
|
|
458
604
|
timeWindowMinutes:
|
|
459
605
|
input.criteriaFilter.evaluateOverTimeOptions?.timeValueInMinutes,
|
|
460
606
|
...(components && components.length > 0 ? { components } : {}),
|