@oneuptime/common 14.0.0 → 14.0.1
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/Index.ts +4 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +2 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.ts +522 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +38 -4
- package/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.ts +603 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.ts +248 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/DatabaseService.ts +10 -0
- package/Server/Services/Index.ts +4 -0
- package/Server/Services/MonitorService.ts +3 -2
- package/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.ts +385 -0
- package/Server/Services/ServiceLevelObjectiveLabelRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +92 -16
- package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +36 -1
- package/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.ts +521 -0
- package/Server/Services/ServiceLevelObjectiveOwnerRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +28 -0
- package/Server/Types/Workflow/Components/Telegram/SendMessageToChat.ts +27 -1
- package/Server/Utils/AI/Toolbox/SloTools.ts +1 -0
- package/Server/Utils/Monitor/AffectedResourceList.ts +31 -73
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +1 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +181 -136
- package/Server/Utils/Monitor/RootCauseList.ts +139 -0
- package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +28 -0
- package/Server/Utils/Slo/SloRulePatternValidator.ts +42 -0
- package/Tests/App/Dashboard/FeedFilterSortIntegration.test.tsx +1632 -0
- package/Tests/App/Dashboard/FeedOptionsLocales.test.ts +250 -0
- package/Tests/App/Dashboard/FeedOrdering.test.ts +279 -9
- package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +60 -1
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +594 -3
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +646 -0
- package/Tests/App/Dashboard/ResourceFeed.test.tsx +612 -2
- package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +30 -0
- package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +1202 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +52 -1
- package/Tests/App/Dashboard/SloFeed.test.tsx +106 -1
- package/Tests/App/Dashboard/SloMonitorRuleCriteria.test.tsx +566 -0
- package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +6 -4
- package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +186 -0
- package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +388 -44
- package/Tests/App/Dashboard/SloOverviewLayout.test.tsx +613 -0
- package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +0 -8
- package/Tests/Models/DatabaseModels/SloLabelAndOwnerRuleModels.test.ts +404 -0
- package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +17 -1
- package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
- package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloLabelAndOwnerRulesMigration.test.ts +407 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloMonitorRuleMonitorTypeMigration.test.ts +171 -0
- package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +88 -4
- package/Tests/Server/Services/DatabaseServiceRuleCriteriaValidation.test.ts +104 -0
- package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +48 -1
- package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +1170 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleServices.test.ts +172 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +323 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +115 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleTriggers.test.ts +21 -2
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorTypePostgres.test.ts +480 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +162 -0
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +45 -6
- package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +745 -0
- package/Tests/Server/Types/Markdown.test.ts +2 -1
- package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +49 -0
- package/Tests/Server/Types/Workflow/Components/TelegramSendMessageToChat.test.ts +331 -0
- package/Tests/Server/Utils/AI/SloTools.test.ts +10 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +405 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +1101 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +106 -80
- package/Tests/Server/Utils/Monitor/RootCauseList.test.ts +252 -0
- package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +154 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +111 -0
- package/Tests/Server/Utils/Slo/SloRulePatternValidator.test.ts +67 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +213 -0
- package/Tests/Types/API/Route.test.ts +56 -0
- package/Tests/Types/API/URLSchemeLikePath.test.ts +304 -0
- package/Tests/UI/Components/FeedOptions.test.ts +1404 -0
- package/Tests/UI/Components/FeedOptionsButton.test.tsx +2603 -0
- package/Tests/UI/Components/Icon.test.tsx +26 -0
- package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
- package/Tests/UI/Components/useFeedItemsViewKey.test.tsx +1017 -0
- package/Tests/UI/Components/useFeedOptions.test.tsx +1107 -0
- package/Tests/Utils/LabelRuleImportExport.test.ts +102 -0
- package/Tests/Utils/Slo/SloDuration.test.ts +115 -24
- package/Tests/Utils/Slo/SloHealth.test.ts +93 -10
- package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +109 -0
- package/Tests/Utils/Slo/SloOverviewText.test.ts +64 -0
- package/Types/API/Route.ts +19 -2
- package/Types/API/URL.ts +26 -2
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +6 -5
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +9 -0
- package/Types/Permission.ts +90 -0
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +11 -0
- package/Types/Rules/RuleRun.ts +4 -0
- package/UI/Components/Feed/FeedOptions.ts +427 -0
- package/UI/Components/Feed/FeedOptionsButton.tsx +692 -0
- package/UI/Components/Feed/useFeedItems.ts +28 -9
- package/UI/Components/Feed/useFeedOptions.ts +225 -0
- package/UI/Components/Icon/Icon.tsx +1 -1
- package/Utils/LabelRuleImportExport.ts +27 -9
- package/Utils/Monitor/MetricValueFormatter.ts +2 -2
- package/Utils/Rules/MonitorTypeRuleCriteria.ts +31 -0
- package/Utils/Slo/SloDuration.ts +22 -6
- package/Utils/Slo/SloHealth.ts +8 -7
- package/Utils/Slo/SloMonitorRuleCriteria.ts +33 -1
- package/Utils/Slo/SloOverviewText.ts +33 -0
- package/Utils/Slo/SloProjection.ts +4 -3
- package/build/dist/Models/DatabaseModels/Index.js +4 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js +530 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +39 -4
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js +612 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.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/DatabaseService.js +7 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +4 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +3 -2
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js +295 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +59 -14
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +22 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js +400 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +21 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js +20 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +22 -51
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +139 -109
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/RootCauseList.js +101 -0
- package/build/dist/Server/Utils/Monitor/RootCauseList.js.map +1 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +28 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js +28 -0
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js.map +1 -0
- package/build/dist/Types/API/Route.js +18 -2
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/API/URL.js +23 -2
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/Permission.js +84 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +11 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
- package/build/dist/Types/Rules/RuleRun.js +4 -0
- package/build/dist/Types/Rules/RuleRun.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedOptions.js +235 -0
- package/build/dist/UI/Components/Feed/FeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js +346 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -0
- package/build/dist/UI/Components/Feed/useFeedItems.js +17 -7
- package/build/dist/UI/Components/Feed/useFeedItems.js.map +1 -1
- package/build/dist/UI/Components/Feed/useFeedOptions.js +130 -0
- package/build/dist/UI/Components/Feed/useFeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Icon/Icon.js +1 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/Utils/LabelRuleImportExport.js +22 -6
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +2 -2
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js +18 -0
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js.map +1 -0
- package/build/dist/Utils/Slo/SloDuration.js +13 -5
- package/build/dist/Utils/Slo/SloDuration.js.map +1 -1
- package/build/dist/Utils/Slo/SloHealth.js +8 -7
- package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +16 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -1
- package/build/dist/Utils/Slo/SloOverviewText.js +16 -0
- package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -1
- package/build/dist/Utils/Slo/SloProjection.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { describe, expect, it } from "@jest/globals";
|
|
2
|
+
import ServiceLevelObjectiveLabelRule from "../../../Models/DatabaseModels/ServiceLevelObjectiveLabelRule";
|
|
3
|
+
import ServiceLevelObjectiveOwnerRule from "../../../Models/DatabaseModels/ServiceLevelObjectiveOwnerRule";
|
|
4
|
+
import ServiceLevelObjectiveLabelRuleService from "../../../Server/Services/ServiceLevelObjectiveLabelRuleService";
|
|
5
|
+
import ServiceLevelObjectiveOwnerRuleService from "../../../Server/Services/ServiceLevelObjectiveOwnerRuleService";
|
|
6
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
7
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* The SLO label and owner rule services.
|
|
11
|
+
*
|
|
12
|
+
* A pattern that is neither a regex nor a '*' wildcard matches nothing, and the
|
|
13
|
+
* engine can only log that long after the user left the form - so the write is
|
|
14
|
+
* refused, on create AND on update, naming the field in the SLO's own words.
|
|
15
|
+
*
|
|
16
|
+
* And a rule is configuration: unlike the feed-style retention most rule
|
|
17
|
+
* services copy, it is never hard-deleted by age.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
21
|
+
"22222222-2222-4222-8222-222222222222",
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
type RuleService =
|
|
25
|
+
| typeof ServiceLevelObjectiveLabelRuleService
|
|
26
|
+
| typeof ServiceLevelObjectiveOwnerRuleService;
|
|
27
|
+
|
|
28
|
+
interface ServiceCase {
|
|
29
|
+
name: string;
|
|
30
|
+
service: RuleService;
|
|
31
|
+
makeRule: () =>
|
|
32
|
+
| ServiceLevelObjectiveLabelRule
|
|
33
|
+
| ServiceLevelObjectiveOwnerRule;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const SERVICES: Array<ServiceCase> = [
|
|
37
|
+
{
|
|
38
|
+
name: "ServiceLevelObjectiveLabelRuleService",
|
|
39
|
+
service: ServiceLevelObjectiveLabelRuleService,
|
|
40
|
+
makeRule: () => {
|
|
41
|
+
return new ServiceLevelObjectiveLabelRule();
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "ServiceLevelObjectiveOwnerRuleService",
|
|
46
|
+
service: ServiceLevelObjectiveOwnerRuleService,
|
|
47
|
+
makeRule: () => {
|
|
48
|
+
return new ServiceLevelObjectiveOwnerRule();
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
// Calls a protected hook without widening the service's public surface.
|
|
54
|
+
function callHook(
|
|
55
|
+
service: unknown,
|
|
56
|
+
name: string,
|
|
57
|
+
...args: Array<unknown>
|
|
58
|
+
): Promise<unknown> {
|
|
59
|
+
const hooks: Record<
|
|
60
|
+
string,
|
|
61
|
+
(...hookArgs: Array<unknown>) => Promise<unknown>
|
|
62
|
+
> = service as Record<
|
|
63
|
+
string,
|
|
64
|
+
(...hookArgs: Array<unknown>) => Promise<unknown>
|
|
65
|
+
>;
|
|
66
|
+
|
|
67
|
+
return hooks[name]!.apply(service, args);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
describe.each(SERVICES)("$name", (c: ServiceCase) => {
|
|
71
|
+
function create(fields: Record<string, unknown>): Promise<unknown> {
|
|
72
|
+
const rule:
|
|
73
|
+
| ServiceLevelObjectiveLabelRule
|
|
74
|
+
| ServiceLevelObjectiveOwnerRule = c.makeRule();
|
|
75
|
+
Object.assign(rule as unknown as Record<string, unknown>, {
|
|
76
|
+
projectId: PROJECT_ID,
|
|
77
|
+
name: "Checkout SLOs",
|
|
78
|
+
...fields,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return callHook(c.service, "onBeforeCreate", {
|
|
82
|
+
data: rule,
|
|
83
|
+
props: { isRoot: true },
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function update(data: Record<string, unknown>): Promise<unknown> {
|
|
88
|
+
return callHook(c.service, "onBeforeUpdate", {
|
|
89
|
+
query: { _id: ObjectID.generate().toString() },
|
|
90
|
+
data: data,
|
|
91
|
+
props: { isRoot: true },
|
|
92
|
+
limit: 1,
|
|
93
|
+
skip: 0,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
describe("on create", () => {
|
|
98
|
+
it.each([
|
|
99
|
+
["a regex", "^checkout-.*"],
|
|
100
|
+
["a '*' wildcard", "*checkout*"],
|
|
101
|
+
["a plain word", "checkout"],
|
|
102
|
+
])(
|
|
103
|
+
"accepts %s as the SLO name pattern",
|
|
104
|
+
async (_: string, pattern: string) => {
|
|
105
|
+
await expect(
|
|
106
|
+
create({ serviceLevelObjectiveNamePattern: pattern }),
|
|
107
|
+
).resolves.toEqual(expect.objectContaining({ carryForward: null }));
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
it("accepts a rule without patterns", async () => {
|
|
112
|
+
await expect(create({})).resolves.toBeDefined();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("refuses a name pattern that can never match, naming the field", async () => {
|
|
116
|
+
await expect(
|
|
117
|
+
create({ serviceLevelObjectiveNamePattern: "checkout-(01" }),
|
|
118
|
+
).rejects.toThrow(
|
|
119
|
+
new BadDataException(
|
|
120
|
+
`SLO Name Pattern "checkout-(01" is not a valid regular expression, and contains no '*' wildcard to fall back on, so it would never match an SLO. Use a regex such as checkout-.* or a wildcard such as *checkout*.`,
|
|
121
|
+
),
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("refuses a description pattern that can never match, naming the field", async () => {
|
|
126
|
+
await expect(
|
|
127
|
+
create({ serviceLevelObjectiveDescriptionPattern: "[unclosed" }),
|
|
128
|
+
).rejects.toThrow('SLO Description Pattern "[unclosed"');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("hands the create through unchanged", async () => {
|
|
132
|
+
const result: { createBy: { data: Record<string, unknown> } } =
|
|
133
|
+
(await create({
|
|
134
|
+
serviceLevelObjectiveNamePattern: "*checkout*",
|
|
135
|
+
})) as { createBy: { data: Record<string, unknown> } };
|
|
136
|
+
|
|
137
|
+
expect(result.createBy.data["serviceLevelObjectiveNamePattern"]).toBe(
|
|
138
|
+
"*checkout*",
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("on update", () => {
|
|
144
|
+
it("accepts a valid pattern", async () => {
|
|
145
|
+
await expect(
|
|
146
|
+
update({ serviceLevelObjectiveNamePattern: "*checkout*" }),
|
|
147
|
+
).resolves.toEqual(expect.objectContaining({ carryForward: null }));
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Updating another column must not trip over the patterns it leaves alone.
|
|
151
|
+
it("accepts an update that does not touch the patterns", async () => {
|
|
152
|
+
await expect(update({ isEnabled: false })).resolves.toBeDefined();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("refuses a name pattern that can never match", async () => {
|
|
156
|
+
await expect(
|
|
157
|
+
update({ serviceLevelObjectiveNamePattern: "api-(01" }),
|
|
158
|
+
).rejects.toThrow('SLO Name Pattern "api-(01"');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("refuses a description pattern that can never match", async () => {
|
|
162
|
+
await expect(
|
|
163
|
+
update({ serviceLevelObjectiveDescriptionPattern: "(" }),
|
|
164
|
+
).rejects.toThrow("SLO Description Pattern");
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("never hard-deletes rules by age", () => {
|
|
169
|
+
expect(c.service.hardDeleteItemByColumnName).toBe("");
|
|
170
|
+
expect(c.service.hardDeleteItemsOlderThanDays).toBe(0);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
@@ -14,6 +14,7 @@ import logger from "../../../Server/Utils/Logger";
|
|
|
14
14
|
import SloLegacyMonitorLabelAdoption from "../../../Server/Utils/Slo/SloLegacyMonitorLabelAdoption";
|
|
15
15
|
import FilterCondition from "../../../Types/Filter/FilterCondition";
|
|
16
16
|
import ObjectID from "../../../Types/ObjectID";
|
|
17
|
+
import MonitorType from "../../../Types/Monitor/MonitorType";
|
|
17
18
|
import RuleCriteria, {
|
|
18
19
|
RULE_CRITERIA_LEGACY_NEVER_MATCH_PATTERN,
|
|
19
20
|
RULE_CRITERIA_SCHEMA_VERSION,
|
|
@@ -93,6 +94,7 @@ function fakeMonitor(
|
|
|
93
94
|
description?: string | undefined;
|
|
94
95
|
labelIds?: Array<ObjectID> | undefined;
|
|
95
96
|
projectId?: ObjectID | null | undefined;
|
|
97
|
+
monitorType?: MonitorType | undefined;
|
|
96
98
|
},
|
|
97
99
|
): Monitor {
|
|
98
100
|
return {
|
|
@@ -101,6 +103,7 @@ function fakeMonitor(
|
|
|
101
103
|
projectId: fields?.projectId === undefined ? PROJECT_ID : fields.projectId,
|
|
102
104
|
name: fields?.name === undefined ? `Monitor ${id.toString()}` : fields.name,
|
|
103
105
|
description: fields?.description,
|
|
106
|
+
monitorType: fields?.monitorType,
|
|
104
107
|
labels: (fields?.labelIds || []).map(fakeLabel),
|
|
105
108
|
} as unknown as Monitor;
|
|
106
109
|
}
|
|
@@ -134,6 +137,7 @@ function fakeRule(fields: {
|
|
|
134
137
|
id?: ObjectID | undefined;
|
|
135
138
|
serviceLevelObjectiveId?: ObjectID | undefined;
|
|
136
139
|
labelIds?: Array<ObjectID> | undefined;
|
|
140
|
+
monitorType?: MonitorType | undefined;
|
|
137
141
|
monitorNamePattern?: string | undefined;
|
|
138
142
|
monitorDescriptionPattern?: string | undefined;
|
|
139
143
|
criteria?: RuleCriteria | null | undefined;
|
|
@@ -148,6 +152,7 @@ function fakeRule(fields: {
|
|
|
148
152
|
serviceLevelObjectiveId: fields.serviceLevelObjectiveId || SLO_ID,
|
|
149
153
|
isEnabled: fields.isEnabled === undefined ? true : fields.isEnabled,
|
|
150
154
|
monitorLabels: (fields.labelIds || []).map(fakeLabel),
|
|
155
|
+
monitorType: fields.monitorType,
|
|
151
156
|
monitorNamePattern: fields.monitorNamePattern,
|
|
152
157
|
monitorDescriptionPattern: fields.monitorDescriptionPattern,
|
|
153
158
|
criteria: fields.criteria,
|
|
@@ -298,6 +303,66 @@ describe("ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo", ()
|
|
|
298
303
|
jest.restoreAllMocks();
|
|
299
304
|
});
|
|
300
305
|
|
|
306
|
+
it.each([false, true])(
|
|
307
|
+
"backfills matching monitor types and detaches stale rule attachments with criteria=%p",
|
|
308
|
+
async (configured: boolean) => {
|
|
309
|
+
spies.sloFindOneById.mockResolvedValue(
|
|
310
|
+
fakeSlo({
|
|
311
|
+
monitors: [MONITOR_B_ID, MONITOR_C_ID],
|
|
312
|
+
autoAddedMonitors: [MONITOR_B_ID],
|
|
313
|
+
}),
|
|
314
|
+
);
|
|
315
|
+
spies.ruleFindBy.mockResolvedValue([
|
|
316
|
+
fakeRule(
|
|
317
|
+
configured
|
|
318
|
+
? {
|
|
319
|
+
criteria: criteria(FilterCondition.All, [
|
|
320
|
+
{
|
|
321
|
+
field: "monitorType",
|
|
322
|
+
operator: RuleCriteriaOperator.Equals,
|
|
323
|
+
value: MonitorType.API,
|
|
324
|
+
},
|
|
325
|
+
]),
|
|
326
|
+
monitorNamePattern: RULE_CRITERIA_LEGACY_NEVER_MATCH_PATTERN,
|
|
327
|
+
}
|
|
328
|
+
: { monitorType: MonitorType.API },
|
|
329
|
+
),
|
|
330
|
+
]);
|
|
331
|
+
spies.monitorFindBy.mockResolvedValue([
|
|
332
|
+
fakeMonitor(MONITOR_A_ID, { monitorType: MonitorType.API }),
|
|
333
|
+
fakeMonitor(MONITOR_B_ID, { monitorType: MonitorType.Website }),
|
|
334
|
+
fakeMonitor(MONITOR_C_ID, { monitorType: MonitorType.Ping }),
|
|
335
|
+
]);
|
|
336
|
+
|
|
337
|
+
const result: SloMonitorSyncResult = await syncSlo();
|
|
338
|
+
|
|
339
|
+
expect(result).toEqual({
|
|
340
|
+
monitorIdsAdded: [MONITOR_A_ID.toString()],
|
|
341
|
+
monitorIdsRemoved: [MONITOR_B_ID.toString()],
|
|
342
|
+
});
|
|
343
|
+
expect(writtenIds(spies.sloUpdateOneById, "monitors")).toEqual(
|
|
344
|
+
sortedIds([MONITOR_A_ID, MONITOR_C_ID]),
|
|
345
|
+
);
|
|
346
|
+
expect(writtenIds(spies.sloUpdateOneById, "autoAddedMonitors")).toEqual([
|
|
347
|
+
MONITOR_A_ID.toString(),
|
|
348
|
+
]);
|
|
349
|
+
expect(spies.monitorFindBy).toHaveBeenCalledWith(
|
|
350
|
+
expect.objectContaining({
|
|
351
|
+
query: { projectId: PROJECT_ID },
|
|
352
|
+
select: expect.objectContaining({ monitorType: true }),
|
|
353
|
+
}),
|
|
354
|
+
);
|
|
355
|
+
expect(spies.ruleFindBy).toHaveBeenCalledWith(
|
|
356
|
+
expect.objectContaining({
|
|
357
|
+
select: expect.objectContaining({
|
|
358
|
+
monitorType: true,
|
|
359
|
+
criteria: true,
|
|
360
|
+
}),
|
|
361
|
+
}),
|
|
362
|
+
);
|
|
363
|
+
},
|
|
364
|
+
);
|
|
365
|
+
|
|
301
366
|
it("attaches every monitor an enabled rule's labels match", async () => {
|
|
302
367
|
spies.ruleFindBy.mockResolvedValue([
|
|
303
368
|
fakeRule({ labelIds: [LABEL_PRODUCTION_ID] }),
|
|
@@ -536,6 +601,7 @@ describe("ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo", ()
|
|
|
536
601
|
_id: true,
|
|
537
602
|
name: true,
|
|
538
603
|
description: true,
|
|
604
|
+
monitorType: true,
|
|
539
605
|
labels: { _id: true },
|
|
540
606
|
});
|
|
541
607
|
expect(call.props).toEqual({ isRoot: true });
|
|
@@ -998,6 +1064,51 @@ describe("ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor", ()
|
|
|
998
1064
|
);
|
|
999
1065
|
}
|
|
1000
1066
|
|
|
1067
|
+
it.each([MonitorType.API, MonitorType.Website])(
|
|
1068
|
+
"reconciles monitor-side membership when the monitor type becomes %s",
|
|
1069
|
+
async (monitorType: MonitorType) => {
|
|
1070
|
+
const shouldMatch: boolean = monitorType === MonitorType.API;
|
|
1071
|
+
spies.monitorFindOneById.mockResolvedValue(
|
|
1072
|
+
fakeMonitor(MONITOR_A_ID, { monitorType }),
|
|
1073
|
+
);
|
|
1074
|
+
spies.ruleFindBy.mockResolvedValue([
|
|
1075
|
+
fakeRule({
|
|
1076
|
+
criteria: criteria(FilterCondition.All, [
|
|
1077
|
+
{
|
|
1078
|
+
field: "monitorType",
|
|
1079
|
+
operator: RuleCriteriaOperator.Equals,
|
|
1080
|
+
value: MonitorType.API,
|
|
1081
|
+
},
|
|
1082
|
+
]),
|
|
1083
|
+
}),
|
|
1084
|
+
]);
|
|
1085
|
+
spies.sloFindBy.mockImplementation(
|
|
1086
|
+
answerSloReads({
|
|
1087
|
+
byId: [
|
|
1088
|
+
fakeSlo({
|
|
1089
|
+
monitors: shouldMatch ? [] : [MONITOR_A_ID],
|
|
1090
|
+
autoAddedMonitors: shouldMatch ? [] : [MONITOR_A_ID],
|
|
1091
|
+
}),
|
|
1092
|
+
],
|
|
1093
|
+
}) as never,
|
|
1094
|
+
);
|
|
1095
|
+
|
|
1096
|
+
const results: Array<SloMonitorSyncResult> = await syncMonitor();
|
|
1097
|
+
|
|
1098
|
+
expect(results).toEqual([
|
|
1099
|
+
{
|
|
1100
|
+
monitorIdsAdded: shouldMatch ? [MONITOR_A_ID.toString()] : [],
|
|
1101
|
+
monitorIdsRemoved: shouldMatch ? [] : [MONITOR_A_ID.toString()],
|
|
1102
|
+
},
|
|
1103
|
+
]);
|
|
1104
|
+
expect(spies.monitorFindOneById).toHaveBeenCalledWith(
|
|
1105
|
+
expect.objectContaining({
|
|
1106
|
+
select: expect.objectContaining({ monitorType: true }),
|
|
1107
|
+
}),
|
|
1108
|
+
);
|
|
1109
|
+
},
|
|
1110
|
+
);
|
|
1111
|
+
|
|
1001
1112
|
it("attaches the monitor to an SLO whose enabled rule its labels satisfy", async () => {
|
|
1002
1113
|
spies.ruleFindBy.mockResolvedValue([
|
|
1003
1114
|
fakeRule({ labelIds: [LABEL_PRODUCTION_ID] }),
|
|
@@ -1320,6 +1431,7 @@ describe("ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor", ()
|
|
|
1320
1431
|
projectId: true,
|
|
1321
1432
|
name: true,
|
|
1322
1433
|
description: true,
|
|
1434
|
+
monitorType: true,
|
|
1323
1435
|
labels: { _id: true },
|
|
1324
1436
|
});
|
|
1325
1437
|
});
|
|
@@ -1793,3 +1905,214 @@ describe("ServiceLevelObjectiveMonitorRuleEngineService.doesMonitorMatchRule", (
|
|
|
1793
1905
|
});
|
|
1794
1906
|
}
|
|
1795
1907
|
});
|
|
1908
|
+
|
|
1909
|
+
describe("SLO monitor type matching", () => {
|
|
1910
|
+
function matches(
|
|
1911
|
+
filters: Array<RuleCriteriaFilter>,
|
|
1912
|
+
monitorType: MonitorType | undefined,
|
|
1913
|
+
condition: FilterCondition = FilterCondition.All,
|
|
1914
|
+
): boolean {
|
|
1915
|
+
return ServiceLevelObjectiveMonitorRuleEngineService.doesMonitorMatchRule({
|
|
1916
|
+
monitor: fakeMonitor(MONITOR_A_ID, {
|
|
1917
|
+
monitorType,
|
|
1918
|
+
name: "api-production",
|
|
1919
|
+
description: "primary",
|
|
1920
|
+
labelIds: [LABEL_PRODUCTION_ID],
|
|
1921
|
+
}),
|
|
1922
|
+
rule: fakeRule({
|
|
1923
|
+
criteria: criteria(condition, filters),
|
|
1924
|
+
monitorNamePattern: RULE_CRITERIA_LEGACY_NEVER_MATCH_PATTERN,
|
|
1925
|
+
}),
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
it.each(Object.values(MonitorType))(
|
|
1930
|
+
"matches exact enum type %s",
|
|
1931
|
+
(monitorType: MonitorType) => {
|
|
1932
|
+
expect(
|
|
1933
|
+
matches(
|
|
1934
|
+
[
|
|
1935
|
+
{
|
|
1936
|
+
field: "monitorType",
|
|
1937
|
+
operator: RuleCriteriaOperator.Equals,
|
|
1938
|
+
value: monitorType,
|
|
1939
|
+
},
|
|
1940
|
+
],
|
|
1941
|
+
monitorType,
|
|
1942
|
+
),
|
|
1943
|
+
).toBe(true);
|
|
1944
|
+
expect(
|
|
1945
|
+
matches(
|
|
1946
|
+
[
|
|
1947
|
+
{
|
|
1948
|
+
field: "monitorType",
|
|
1949
|
+
operator: RuleCriteriaOperator.NotEquals,
|
|
1950
|
+
value: monitorType,
|
|
1951
|
+
},
|
|
1952
|
+
],
|
|
1953
|
+
monitorType,
|
|
1954
|
+
),
|
|
1955
|
+
).toBe(false);
|
|
1956
|
+
},
|
|
1957
|
+
);
|
|
1958
|
+
|
|
1959
|
+
it("distinguishes Docker from Docker Swarm with both equality operators", () => {
|
|
1960
|
+
expect(
|
|
1961
|
+
matches(
|
|
1962
|
+
[
|
|
1963
|
+
{
|
|
1964
|
+
field: "monitorType",
|
|
1965
|
+
operator: RuleCriteriaOperator.Equals,
|
|
1966
|
+
value: MonitorType.Docker,
|
|
1967
|
+
},
|
|
1968
|
+
],
|
|
1969
|
+
MonitorType.DockerSwarm,
|
|
1970
|
+
),
|
|
1971
|
+
).toBe(false);
|
|
1972
|
+
expect(
|
|
1973
|
+
matches(
|
|
1974
|
+
[
|
|
1975
|
+
{
|
|
1976
|
+
field: "monitorType",
|
|
1977
|
+
operator: RuleCriteriaOperator.NotEquals,
|
|
1978
|
+
value: MonitorType.Docker,
|
|
1979
|
+
},
|
|
1980
|
+
],
|
|
1981
|
+
MonitorType.DockerSwarm,
|
|
1982
|
+
),
|
|
1983
|
+
).toBe(true);
|
|
1984
|
+
});
|
|
1985
|
+
|
|
1986
|
+
it.each([undefined, "Unknown" as MonitorType])(
|
|
1987
|
+
"does not match missing or invalid monitor types (%s), including negation",
|
|
1988
|
+
(monitorType: MonitorType | undefined) => {
|
|
1989
|
+
expect(
|
|
1990
|
+
matches(
|
|
1991
|
+
[
|
|
1992
|
+
{
|
|
1993
|
+
field: "monitorType",
|
|
1994
|
+
operator: RuleCriteriaOperator.Equals,
|
|
1995
|
+
value: MonitorType.API,
|
|
1996
|
+
},
|
|
1997
|
+
],
|
|
1998
|
+
monitorType,
|
|
1999
|
+
),
|
|
2000
|
+
).toBe(false);
|
|
2001
|
+
expect(
|
|
2002
|
+
matches(
|
|
2003
|
+
[
|
|
2004
|
+
{
|
|
2005
|
+
field: "monitorType",
|
|
2006
|
+
operator: RuleCriteriaOperator.NotEquals,
|
|
2007
|
+
value: MonitorType.API,
|
|
2008
|
+
},
|
|
2009
|
+
],
|
|
2010
|
+
monitorType,
|
|
2011
|
+
),
|
|
2012
|
+
).toBe(false);
|
|
2013
|
+
},
|
|
2014
|
+
);
|
|
2015
|
+
|
|
2016
|
+
it.each([
|
|
2017
|
+
{ operator: RuleCriteriaOperator.Equals, value: "Unknown" },
|
|
2018
|
+
{ operator: RuleCriteriaOperator.NotEquals, value: "api" },
|
|
2019
|
+
{ operator: RuleCriteriaOperator.NotEquals, value: true },
|
|
2020
|
+
{ operator: RuleCriteriaOperator.Contains, value: MonitorType.API },
|
|
2021
|
+
{ operator: RuleCriteriaOperator.DoesNotMatchPattern, value: ".*" },
|
|
2022
|
+
{ operator: RuleCriteriaOperator.HasNoneOf, value: [MonitorType.API] },
|
|
2023
|
+
])(
|
|
2024
|
+
"fails the entire rule for invalid type predicate %p even under Match any",
|
|
2025
|
+
(filter: Omit<RuleCriteriaFilter, "field">) => {
|
|
2026
|
+
expect(
|
|
2027
|
+
matches(
|
|
2028
|
+
[
|
|
2029
|
+
{ field: "monitorType", ...filter },
|
|
2030
|
+
{
|
|
2031
|
+
field: "monitorNamePattern",
|
|
2032
|
+
operator: RuleCriteriaOperator.Contains,
|
|
2033
|
+
value: "api",
|
|
2034
|
+
},
|
|
2035
|
+
],
|
|
2036
|
+
MonitorType.API,
|
|
2037
|
+
FilterCondition.Any,
|
|
2038
|
+
),
|
|
2039
|
+
).toBe(false);
|
|
2040
|
+
},
|
|
2041
|
+
);
|
|
2042
|
+
|
|
2043
|
+
it("combines types with name, description, and label conditions", () => {
|
|
2044
|
+
const filters: Array<RuleCriteriaFilter> = [
|
|
2045
|
+
{
|
|
2046
|
+
field: "monitorType",
|
|
2047
|
+
operator: RuleCriteriaOperator.Equals,
|
|
2048
|
+
value: MonitorType.API,
|
|
2049
|
+
},
|
|
2050
|
+
{
|
|
2051
|
+
field: "monitorNamePattern",
|
|
2052
|
+
operator: RuleCriteriaOperator.MatchesPattern,
|
|
2053
|
+
value: "api-*",
|
|
2054
|
+
},
|
|
2055
|
+
{
|
|
2056
|
+
field: "monitorDescriptionPattern",
|
|
2057
|
+
operator: RuleCriteriaOperator.DoesNotMatchPattern,
|
|
2058
|
+
value: "standby*",
|
|
2059
|
+
},
|
|
2060
|
+
{
|
|
2061
|
+
field: "monitorLabels",
|
|
2062
|
+
operator: RuleCriteriaOperator.HasAnyOf,
|
|
2063
|
+
value: [LABEL_PRODUCTION_ID.toString()],
|
|
2064
|
+
},
|
|
2065
|
+
];
|
|
2066
|
+
expect(matches(filters, MonitorType.API)).toBe(true);
|
|
2067
|
+
expect(matches(filters, MonitorType.Website)).toBe(false);
|
|
2068
|
+
expect(matches(filters, MonitorType.Website, FilterCondition.Any)).toBe(
|
|
2069
|
+
true,
|
|
2070
|
+
);
|
|
2071
|
+
});
|
|
2072
|
+
|
|
2073
|
+
it("supports several type alternatives", () => {
|
|
2074
|
+
const filters: Array<RuleCriteriaFilter> = [
|
|
2075
|
+
MonitorType.API,
|
|
2076
|
+
MonitorType.Website,
|
|
2077
|
+
].map((monitorType: MonitorType): RuleCriteriaFilter => {
|
|
2078
|
+
return {
|
|
2079
|
+
field: "monitorType",
|
|
2080
|
+
operator: RuleCriteriaOperator.Equals,
|
|
2081
|
+
value: monitorType,
|
|
2082
|
+
};
|
|
2083
|
+
});
|
|
2084
|
+
expect(matches(filters, MonitorType.Website, FilterCondition.Any)).toBe(
|
|
2085
|
+
true,
|
|
2086
|
+
);
|
|
2087
|
+
expect(matches(filters, MonitorType.Ping, FilterCondition.Any)).toBe(false);
|
|
2088
|
+
expect(matches(filters, MonitorType.API)).toBe(false);
|
|
2089
|
+
});
|
|
2090
|
+
|
|
2091
|
+
it("ANDs a legacy type with legacy name and labels", () => {
|
|
2092
|
+
const rule: ServiceLevelObjectiveMonitorRule = fakeRule({
|
|
2093
|
+
monitorType: MonitorType.Docker,
|
|
2094
|
+
monitorNamePattern: "api-*",
|
|
2095
|
+
labelIds: [LABEL_PRODUCTION_ID],
|
|
2096
|
+
});
|
|
2097
|
+
expect(
|
|
2098
|
+
ServiceLevelObjectiveMonitorRuleEngineService.doesMonitorMatchRule({
|
|
2099
|
+
rule,
|
|
2100
|
+
monitor: fakeMonitor(MONITOR_A_ID, {
|
|
2101
|
+
monitorType: MonitorType.Docker,
|
|
2102
|
+
name: "api-prod",
|
|
2103
|
+
labelIds: [LABEL_PRODUCTION_ID],
|
|
2104
|
+
}),
|
|
2105
|
+
}),
|
|
2106
|
+
).toBe(true);
|
|
2107
|
+
expect(
|
|
2108
|
+
ServiceLevelObjectiveMonitorRuleEngineService.doesMonitorMatchRule({
|
|
2109
|
+
rule,
|
|
2110
|
+
monitor: fakeMonitor(MONITOR_A_ID, {
|
|
2111
|
+
monitorType: MonitorType.DockerSwarm,
|
|
2112
|
+
name: "api-prod",
|
|
2113
|
+
labelIds: [LABEL_PRODUCTION_ID],
|
|
2114
|
+
}),
|
|
2115
|
+
}),
|
|
2116
|
+
).toBe(false);
|
|
2117
|
+
});
|
|
2118
|
+
});
|
|
@@ -21,6 +21,7 @@ import LIMIT_MAX from "../../../Types/Database/LimitMax";
|
|
|
21
21
|
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
22
22
|
import FilterCondition from "../../../Types/Filter/FilterCondition";
|
|
23
23
|
import ObjectID from "../../../Types/ObjectID";
|
|
24
|
+
import MonitorType from "../../../Types/Monitor/MonitorType";
|
|
24
25
|
import RuleCriteria, {
|
|
25
26
|
RULE_CRITERIA_SCHEMA_VERSION,
|
|
26
27
|
RuleCriteriaOperator,
|
|
@@ -101,6 +102,7 @@ function makeRule(fields: {
|
|
|
101
102
|
description?: string | undefined;
|
|
102
103
|
isEnabled?: boolean | undefined;
|
|
103
104
|
labelIds?: Array<ObjectID> | undefined;
|
|
105
|
+
monitorType?: MonitorType | undefined;
|
|
104
106
|
monitorNamePattern?: string | undefined;
|
|
105
107
|
monitorDescriptionPattern?: string | undefined;
|
|
106
108
|
criteria?: RuleCriteria | null | undefined;
|
|
@@ -119,6 +121,7 @@ function makeRule(fields: {
|
|
|
119
121
|
monitorLabels: (fields.labelIds || []).map((labelId: ObjectID) => {
|
|
120
122
|
return { id: labelId, _id: labelId.toString() } as unknown as Label;
|
|
121
123
|
}),
|
|
124
|
+
monitorType: fields.monitorType,
|
|
122
125
|
monitorNamePattern: fields.monitorNamePattern,
|
|
123
126
|
monitorDescriptionPattern: fields.monitorDescriptionPattern,
|
|
124
127
|
criteria: fields.criteria,
|
|
@@ -247,6 +250,72 @@ describe("ServiceLevelObjectiveMonitorRuleService.onBeforeCreate", () => {
|
|
|
247
250
|
jest.restoreAllMocks();
|
|
248
251
|
});
|
|
249
252
|
|
|
253
|
+
it.each([true, false])(
|
|
254
|
+
"accepts a monitor-type-only rule with configured criteria=%p",
|
|
255
|
+
async (configured: boolean) => {
|
|
256
|
+
await expect(
|
|
257
|
+
callHook(
|
|
258
|
+
"onBeforeCreate",
|
|
259
|
+
makeCreateBy({
|
|
260
|
+
projectId: PROJECT_ID,
|
|
261
|
+
serviceLevelObjectiveId: SLO_ID,
|
|
262
|
+
...(configured
|
|
263
|
+
? {
|
|
264
|
+
criteria: criteria(FilterCondition.All, [
|
|
265
|
+
{
|
|
266
|
+
field: "monitorType",
|
|
267
|
+
operator: RuleCriteriaOperator.Equals,
|
|
268
|
+
value: MonitorType.API,
|
|
269
|
+
},
|
|
270
|
+
]),
|
|
271
|
+
}
|
|
272
|
+
: { monitorType: MonitorType.API }),
|
|
273
|
+
}),
|
|
274
|
+
),
|
|
275
|
+
).resolves.toBeDefined();
|
|
276
|
+
},
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
it.each([
|
|
280
|
+
{ monitorType: "Unknown" },
|
|
281
|
+
{
|
|
282
|
+
criteria: criteria(FilterCondition.All, [
|
|
283
|
+
{
|
|
284
|
+
field: "monitorType",
|
|
285
|
+
operator: RuleCriteriaOperator.NotEquals,
|
|
286
|
+
value: "Unknown",
|
|
287
|
+
},
|
|
288
|
+
]),
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
criteria: criteria(FilterCondition.All, [
|
|
292
|
+
{
|
|
293
|
+
field: "monitorType",
|
|
294
|
+
operator: RuleCriteriaOperator.Contains,
|
|
295
|
+
value: MonitorType.API,
|
|
296
|
+
},
|
|
297
|
+
]),
|
|
298
|
+
},
|
|
299
|
+
])(
|
|
300
|
+
"rejects invalid monitor type conditions before checking or adopting an SLO: %p",
|
|
301
|
+
async (fields: Record<string, unknown>) => {
|
|
302
|
+
await expect(
|
|
303
|
+
callHook(
|
|
304
|
+
"onBeforeCreate",
|
|
305
|
+
makeCreateBy({
|
|
306
|
+
projectId: PROJECT_ID,
|
|
307
|
+
serviceLevelObjectiveId: SLO_ID,
|
|
308
|
+
...fields,
|
|
309
|
+
}),
|
|
310
|
+
),
|
|
311
|
+
).rejects.toThrow("Monitor type criteria");
|
|
312
|
+
expect(spies.referenceValidator).not.toHaveBeenCalled();
|
|
313
|
+
expect(
|
|
314
|
+
SloLegacyMonitorLabelAdoption.adoptLegacyMonitorLabels,
|
|
315
|
+
).not.toHaveBeenCalled();
|
|
316
|
+
},
|
|
317
|
+
);
|
|
318
|
+
|
|
250
319
|
it("refuses a rule that names no SLO", async () => {
|
|
251
320
|
const promise: Promise<unknown> = callHook(
|
|
252
321
|
"onBeforeCreate",
|
|
@@ -627,6 +696,52 @@ describe("ServiceLevelObjectiveMonitorRuleService.onBeforeUpdate", () => {
|
|
|
627
696
|
jest.restoreAllMocks();
|
|
628
697
|
});
|
|
629
698
|
|
|
699
|
+
it("keeps a legacy type condition when clearing a different condition", async () => {
|
|
700
|
+
spies.ruleFindBy.mockResolvedValue([
|
|
701
|
+
makeRule({ monitorType: MonitorType.API, monitorNamePattern: "api-*" }),
|
|
702
|
+
]);
|
|
703
|
+
await expect(
|
|
704
|
+
callHook("onBeforeUpdate", makeUpdateBy({ monitorNamePattern: null })),
|
|
705
|
+
).resolves.toBeDefined();
|
|
706
|
+
expect(spies.ruleFindBy).toHaveBeenCalledWith(
|
|
707
|
+
expect.objectContaining({
|
|
708
|
+
select: expect.objectContaining({ monitorType: true }),
|
|
709
|
+
}),
|
|
710
|
+
);
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
it("rejects removing the only legacy type condition", async () => {
|
|
714
|
+
spies.ruleFindBy.mockResolvedValue([
|
|
715
|
+
makeRule({ monitorType: MonitorType.API }),
|
|
716
|
+
]);
|
|
717
|
+
await expect(
|
|
718
|
+
callHook("onBeforeUpdate", makeUpdateBy({ monitorType: null })),
|
|
719
|
+
).rejects.toThrow("at least one match criterion");
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
it("validates a changed legacy monitor type and configured monitor type", async () => {
|
|
723
|
+
spies.ruleFindBy.mockResolvedValue([
|
|
724
|
+
makeRule({ monitorType: MonitorType.API }),
|
|
725
|
+
]);
|
|
726
|
+
await expect(
|
|
727
|
+
callHook("onBeforeUpdate", makeUpdateBy({ monitorType: "Unknown" })),
|
|
728
|
+
).rejects.toThrow("valid monitor type");
|
|
729
|
+
await expect(
|
|
730
|
+
callHook(
|
|
731
|
+
"onBeforeUpdate",
|
|
732
|
+
makeUpdateBy({
|
|
733
|
+
criteria: criteria(FilterCondition.All, [
|
|
734
|
+
{
|
|
735
|
+
field: "monitorType",
|
|
736
|
+
operator: RuleCriteriaOperator.DoesNotMatchPattern,
|
|
737
|
+
value: MonitorType.API,
|
|
738
|
+
},
|
|
739
|
+
]),
|
|
740
|
+
}),
|
|
741
|
+
),
|
|
742
|
+
).rejects.toThrow("only support Equals");
|
|
743
|
+
});
|
|
744
|
+
|
|
630
745
|
it("refuses a pattern the engine could never match, before reading anything", async () => {
|
|
631
746
|
await expect(
|
|
632
747
|
callHook(
|