@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.
Files changed (196) hide show
  1. package/Models/DatabaseModels/Index.ts +4 -0
  2. package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +2 -0
  3. package/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.ts +522 -0
  4. package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +38 -4
  5. package/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.ts +603 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.ts +248 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.ts +19 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  9. package/Server/Services/DatabaseService.ts +10 -0
  10. package/Server/Services/Index.ts +4 -0
  11. package/Server/Services/MonitorService.ts +3 -2
  12. package/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.ts +385 -0
  13. package/Server/Services/ServiceLevelObjectiveLabelRuleService.ts +51 -0
  14. package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +92 -16
  15. package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +36 -1
  16. package/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.ts +521 -0
  17. package/Server/Services/ServiceLevelObjectiveOwnerRuleService.ts +51 -0
  18. package/Server/Services/ServiceLevelObjectiveService.ts +28 -0
  19. package/Server/Types/Workflow/Components/Telegram/SendMessageToChat.ts +27 -1
  20. package/Server/Utils/AI/Toolbox/SloTools.ts +1 -0
  21. package/Server/Utils/Monitor/AffectedResourceList.ts +31 -73
  22. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
  23. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +1 -1
  24. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +181 -136
  25. package/Server/Utils/Monitor/RootCauseList.ts +139 -0
  26. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +28 -0
  27. package/Server/Utils/Slo/SloRulePatternValidator.ts +42 -0
  28. package/Tests/App/Dashboard/FeedFilterSortIntegration.test.tsx +1632 -0
  29. package/Tests/App/Dashboard/FeedOptionsLocales.test.ts +250 -0
  30. package/Tests/App/Dashboard/FeedOrdering.test.ts +279 -9
  31. package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +60 -1
  32. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +594 -3
  33. package/Tests/App/Dashboard/ProductOrientation.test.tsx +646 -0
  34. package/Tests/App/Dashboard/ResourceFeed.test.tsx +612 -2
  35. package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +30 -0
  36. package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +1202 -0
  37. package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +52 -1
  38. package/Tests/App/Dashboard/SloFeed.test.tsx +106 -1
  39. package/Tests/App/Dashboard/SloMonitorRuleCriteria.test.tsx +566 -0
  40. package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +6 -4
  41. package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +186 -0
  42. package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +388 -44
  43. package/Tests/App/Dashboard/SloOverviewLayout.test.tsx +613 -0
  44. package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +0 -8
  45. package/Tests/Models/DatabaseModels/SloLabelAndOwnerRuleModels.test.ts +404 -0
  46. package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +17 -1
  47. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  48. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  49. package/Tests/Server/Infrastructure/Postgres/AddSloLabelAndOwnerRulesMigration.test.ts +407 -0
  50. package/Tests/Server/Infrastructure/Postgres/AddSloMonitorRuleMonitorTypeMigration.test.ts +171 -0
  51. package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +88 -4
  52. package/Tests/Server/Services/DatabaseServiceRuleCriteriaValidation.test.ts +104 -0
  53. package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +48 -1
  54. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -0
  55. package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +1170 -0
  56. package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleServices.test.ts +172 -0
  57. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +323 -0
  58. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +115 -0
  59. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleTriggers.test.ts +21 -2
  60. package/Tests/Server/Services/ServiceLevelObjectiveMonitorTypePostgres.test.ts +480 -0
  61. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +162 -0
  62. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +45 -6
  63. package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +745 -0
  64. package/Tests/Server/Types/Markdown.test.ts +2 -1
  65. package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +49 -0
  66. package/Tests/Server/Types/Workflow/Components/TelegramSendMessageToChat.test.ts +331 -0
  67. package/Tests/Server/Utils/AI/SloTools.test.ts +10 -0
  68. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +1 -1
  69. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +405 -0
  70. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +1101 -0
  71. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +106 -80
  72. package/Tests/Server/Utils/Monitor/RootCauseList.test.ts +252 -0
  73. package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +154 -0
  74. package/Tests/Server/Utils/SSRFProtection.test.ts +111 -0
  75. package/Tests/Server/Utils/Slo/SloRulePatternValidator.test.ts +67 -0
  76. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +213 -0
  77. package/Tests/Types/API/Route.test.ts +56 -0
  78. package/Tests/Types/API/URLSchemeLikePath.test.ts +304 -0
  79. package/Tests/UI/Components/FeedOptions.test.ts +1404 -0
  80. package/Tests/UI/Components/FeedOptionsButton.test.tsx +2603 -0
  81. package/Tests/UI/Components/Icon.test.tsx +26 -0
  82. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  83. package/Tests/UI/Components/useFeedItemsViewKey.test.tsx +1017 -0
  84. package/Tests/UI/Components/useFeedOptions.test.tsx +1107 -0
  85. package/Tests/Utils/LabelRuleImportExport.test.ts +102 -0
  86. package/Tests/Utils/Slo/SloDuration.test.ts +115 -24
  87. package/Tests/Utils/Slo/SloHealth.test.ts +93 -10
  88. package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +109 -0
  89. package/Tests/Utils/Slo/SloOverviewText.test.ts +64 -0
  90. package/Types/API/Route.ts +19 -2
  91. package/Types/API/URL.ts +26 -2
  92. package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +6 -5
  93. package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +9 -0
  94. package/Types/Permission.ts +90 -0
  95. package/Types/Rules/RuleCriteriaFieldRegistry.ts +11 -0
  96. package/Types/Rules/RuleRun.ts +4 -0
  97. package/UI/Components/Feed/FeedOptions.ts +427 -0
  98. package/UI/Components/Feed/FeedOptionsButton.tsx +692 -0
  99. package/UI/Components/Feed/useFeedItems.ts +28 -9
  100. package/UI/Components/Feed/useFeedOptions.ts +225 -0
  101. package/UI/Components/Icon/Icon.tsx +1 -1
  102. package/Utils/LabelRuleImportExport.ts +27 -9
  103. package/Utils/Monitor/MetricValueFormatter.ts +2 -2
  104. package/Utils/Rules/MonitorTypeRuleCriteria.ts +31 -0
  105. package/Utils/Slo/SloDuration.ts +22 -6
  106. package/Utils/Slo/SloHealth.ts +8 -7
  107. package/Utils/Slo/SloMonitorRuleCriteria.ts +33 -1
  108. package/Utils/Slo/SloOverviewText.ts +33 -0
  109. package/Utils/Slo/SloProjection.ts +4 -3
  110. package/build/dist/Models/DatabaseModels/Index.js +4 -0
  111. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +2 -0
  113. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -1
  114. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js +530 -0
  115. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js.map +1 -0
  116. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +39 -4
  117. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -1
  118. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js +612 -0
  119. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js.map +1 -0
  120. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js +93 -0
  121. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js.map +1 -0
  122. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js +12 -0
  123. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js.map +1 -0
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  125. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  126. package/build/dist/Server/Services/DatabaseService.js +7 -0
  127. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  128. package/build/dist/Server/Services/Index.js +4 -0
  129. package/build/dist/Server/Services/Index.js.map +1 -1
  130. package/build/dist/Server/Services/MonitorService.js +3 -2
  131. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  132. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js +295 -0
  133. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js.map +1 -0
  134. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js +56 -0
  135. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js.map +1 -0
  136. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +59 -14
  137. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
  138. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +22 -1
  139. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -1
  140. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js +400 -0
  141. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js.map +1 -0
  142. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js +56 -0
  143. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js.map +1 -0
  144. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +21 -0
  145. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  146. package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js +20 -1
  147. package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js.map +1 -1
  148. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +1 -0
  149. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
  150. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +22 -51
  151. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -1
  152. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
  153. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
  154. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +139 -109
  155. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  156. package/build/dist/Server/Utils/Monitor/RootCauseList.js +101 -0
  157. package/build/dist/Server/Utils/Monitor/RootCauseList.js.map +1 -0
  158. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +28 -0
  159. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  160. package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js +28 -0
  161. package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js.map +1 -0
  162. package/build/dist/Types/API/Route.js +18 -2
  163. package/build/dist/Types/API/Route.js.map +1 -1
  164. package/build/dist/Types/API/URL.js +23 -2
  165. package/build/dist/Types/API/URL.js.map +1 -1
  166. package/build/dist/Types/Permission.js +84 -0
  167. package/build/dist/Types/Permission.js.map +1 -1
  168. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +11 -0
  169. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  170. package/build/dist/Types/Rules/RuleRun.js +4 -0
  171. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  172. package/build/dist/UI/Components/Feed/FeedOptions.js +235 -0
  173. package/build/dist/UI/Components/Feed/FeedOptions.js.map +1 -0
  174. package/build/dist/UI/Components/Feed/FeedOptionsButton.js +346 -0
  175. package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -0
  176. package/build/dist/UI/Components/Feed/useFeedItems.js +17 -7
  177. package/build/dist/UI/Components/Feed/useFeedItems.js.map +1 -1
  178. package/build/dist/UI/Components/Feed/useFeedOptions.js +130 -0
  179. package/build/dist/UI/Components/Feed/useFeedOptions.js.map +1 -0
  180. package/build/dist/UI/Components/Icon/Icon.js +1 -1
  181. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  182. package/build/dist/Utils/LabelRuleImportExport.js +22 -6
  183. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  184. package/build/dist/Utils/Monitor/MetricValueFormatter.js +2 -2
  185. package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js +18 -0
  186. package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js.map +1 -0
  187. package/build/dist/Utils/Slo/SloDuration.js +13 -5
  188. package/build/dist/Utils/Slo/SloDuration.js.map +1 -1
  189. package/build/dist/Utils/Slo/SloHealth.js +8 -7
  190. package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
  191. package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +16 -1
  192. package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -1
  193. package/build/dist/Utils/Slo/SloOverviewText.js +16 -0
  194. package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -1
  195. package/build/dist/Utils/Slo/SloProjection.js.map +1 -1
  196. package/package.json +1 -1
@@ -0,0 +1,404 @@
1
+ import Models from "../../../Models/DatabaseModels/Index";
2
+ import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
3
+ import RuleBaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/RuleBaseModel";
4
+ import Label from "../../../Models/DatabaseModels/Label";
5
+ import ServiceLevelObjective from "../../../Models/DatabaseModels/ServiceLevelObjective";
6
+ import ServiceLevelObjectiveLabelRule from "../../../Models/DatabaseModels/ServiceLevelObjectiveLabelRule";
7
+ import ServiceLevelObjectiveOwnerRule from "../../../Models/DatabaseModels/ServiceLevelObjectiveOwnerRule";
8
+ import ServiceLevelObjectiveOwnerTeam from "../../../Models/DatabaseModels/ServiceLevelObjectiveOwnerTeam";
9
+ import ServiceLevelObjectiveOwnerUser from "../../../Models/DatabaseModels/ServiceLevelObjectiveOwnerUser";
10
+ import Team from "../../../Models/DatabaseModels/Team";
11
+ import User from "../../../Models/DatabaseModels/User";
12
+ import Services from "../../../Server/Services/Index";
13
+ import ServiceLevelObjectiveLabelRuleEngineService from "../../../Server/Services/ServiceLevelObjectiveLabelRuleEngineService";
14
+ import ServiceLevelObjectiveLabelRuleService from "../../../Server/Services/ServiceLevelObjectiveLabelRuleService";
15
+ import ServiceLevelObjectiveOwnerRuleEngineService from "../../../Server/Services/ServiceLevelObjectiveOwnerRuleEngineService";
16
+ import ServiceLevelObjectiveOwnerRuleService from "../../../Server/Services/ServiceLevelObjectiveOwnerRuleService";
17
+ import ServiceLevelObjectiveService from "../../../Server/Services/ServiceLevelObjectiveService";
18
+ import RuleRunRegistry, {
19
+ RuleRunDefinition,
20
+ } from "../../../Server/Utils/Rules/RuleRun/RuleRunRegistry";
21
+ import { ColumnAccessControl } from "../../../Types/BaseDatabase/AccessControl";
22
+ import { TableColumnMetadata } from "../../../Types/Database/TableColumn";
23
+ import TableColumnType from "../../../Types/Database/TableColumnType";
24
+ import Permission, {
25
+ PermissionGroup,
26
+ PermissionHelper,
27
+ PermissionProps,
28
+ } from "../../../Types/Permission";
29
+ import {
30
+ RuleRunAction,
31
+ RuleRunType,
32
+ RuleRunTypeUtil,
33
+ } from "../../../Types/Rules/RuleRun";
34
+ import RULE_CRITERIA_FIELDS_BY_MODEL, {
35
+ getRuleCriteriaFieldsForModel,
36
+ } from "../../../Types/Rules/RuleCriteriaFieldRegistry";
37
+ import { LABEL_RULE_MODELS } from "../../../Utils/LabelRuleImportExport";
38
+ import { describe, expect, test } from "@jest/globals";
39
+ import { getMetadataArgsStorage } from "typeorm";
40
+ import type { JoinTableMetadataArgs } from "typeorm/metadata-args/JoinTableMetadataArgs";
41
+
42
+ /*
43
+ * SLO label rules and SLO owner rules, at the schema and registration level.
44
+ *
45
+ * Service and engine tests pass just as happily against a rule model nobody
46
+ * can reach over the API, that the wrong role can write, whose join tables
47
+ * drift from the migration, or that "Run now" never learned about. Every name
48
+ * pinned here is also pinned by 1794100000000-AddSloLabelAndOwnerRules, the
49
+ * dashboard pages and the criteria registry, so a rename fails here first.
50
+ */
51
+
52
+ type ModelType = { new (): BaseModel };
53
+
54
+ const MODEL_TYPES: Array<ModelType> = Models as Array<ModelType>;
55
+ const PERMISSION_PROPS: Array<PermissionProps> =
56
+ PermissionHelper.getAllPermissionProps();
57
+
58
+ const LEGACY_MATCH_FIELDS: Array<string> = [
59
+ "serviceLevelObjectiveLabels",
60
+ "serviceLevelObjectiveNamePattern",
61
+ "serviceLevelObjectiveDescriptionPattern",
62
+ ];
63
+
64
+ interface RuleModelCase {
65
+ name: string;
66
+ modelType: ModelType;
67
+ route: string;
68
+ singularName: string;
69
+ pluralName: string;
70
+ create: Permission;
71
+ read: Permission;
72
+ update: Permission;
73
+ delete: Permission;
74
+ labelJoinTable: string;
75
+ ruleIdColumn: string;
76
+ }
77
+
78
+ const RULE_MODELS: Array<RuleModelCase> = [
79
+ {
80
+ name: "ServiceLevelObjectiveLabelRule",
81
+ modelType: ServiceLevelObjectiveLabelRule,
82
+ route: "/service-level-objective-label-rule",
83
+ singularName: "SLO Label Rule",
84
+ pluralName: "SLO Label Rules",
85
+ create: Permission.CreateServiceLevelObjectiveLabelRule,
86
+ read: Permission.ReadServiceLevelObjectiveLabelRule,
87
+ update: Permission.EditServiceLevelObjectiveLabelRule,
88
+ delete: Permission.DeleteServiceLevelObjectiveLabelRule,
89
+ labelJoinTable: "ServiceLevelObjectiveLabelRuleServiceLevelObjectiveLabel",
90
+ ruleIdColumn: "serviceLevelObjectiveLabelRuleId",
91
+ },
92
+ {
93
+ name: "ServiceLevelObjectiveOwnerRule",
94
+ modelType: ServiceLevelObjectiveOwnerRule,
95
+ route: "/service-level-objective-owner-rule",
96
+ singularName: "SLO Owner Rule",
97
+ pluralName: "SLO Owner Rules",
98
+ create: Permission.CreateServiceLevelObjectiveOwnerRule,
99
+ read: Permission.ReadServiceLevelObjectiveOwnerRule,
100
+ update: Permission.EditServiceLevelObjectiveOwnerRule,
101
+ delete: Permission.DeleteServiceLevelObjectiveOwnerRule,
102
+ labelJoinTable: "ServiceLevelObjectiveOwnerRuleServiceLevelObjectiveLabel",
103
+ ruleIdColumn: "serviceLevelObjectiveOwnerRuleId",
104
+ },
105
+ ];
106
+
107
+ type JoinTableShapeFunction = (
108
+ target: unknown,
109
+ propertyName: string,
110
+ ) => {
111
+ name: string | undefined;
112
+ joinColumn: string | undefined;
113
+ inverseJoinColumn: string | undefined;
114
+ };
115
+
116
+ const joinTableShape: JoinTableShapeFunction = (
117
+ target: unknown,
118
+ propertyName: string,
119
+ ): {
120
+ name: string | undefined;
121
+ joinColumn: string | undefined;
122
+ inverseJoinColumn: string | undefined;
123
+ } => {
124
+ const args: JoinTableMetadataArgs | undefined =
125
+ getMetadataArgsStorage().joinTables.find(
126
+ (joinTable: JoinTableMetadataArgs): boolean => {
127
+ return (
128
+ joinTable.target === target && joinTable.propertyName === propertyName
129
+ );
130
+ },
131
+ );
132
+
133
+ return {
134
+ name: args?.name,
135
+ joinColumn: args?.joinColumns?.[0]?.name,
136
+ inverseJoinColumn: args?.inverseJoinColumns?.[0]?.name,
137
+ };
138
+ };
139
+
140
+ describe.each(RULE_MODELS)("$name", (c: RuleModelCase) => {
141
+ const model: BaseModel = new c.modelType();
142
+
143
+ test("is in Models/Index.ts, so its table is created and its API mounted", () => {
144
+ expect(MODEL_TYPES).toContain(c.modelType);
145
+ });
146
+
147
+ test("is served from its own CRUD route and table", () => {
148
+ expect(model.getCrudApiPath()?.toString()).toBe(c.route);
149
+ expect(model.tableName).toBe(c.name);
150
+ expect(model.singularName).toBe(c.singularName);
151
+ expect(model.pluralName).toBe(c.pluralName);
152
+
153
+ // A duplicate route silently shadows another model's whole API.
154
+ const claimants: Array<string> = MODEL_TYPES.filter(
155
+ (candidate: ModelType): boolean => {
156
+ return new candidate().getCrudApiPath()?.toString() === c.route;
157
+ },
158
+ ).map((candidate: ModelType): string => {
159
+ return candidate.name;
160
+ });
161
+
162
+ expect(claimants).toEqual([c.name]);
163
+ });
164
+
165
+ test("is scoped to its project, not to one SLO", () => {
166
+ expect(model.getTenantColumn()).toBe("projectId");
167
+ // A project-wide rule decides which SLOs it reaches; it belongs to none.
168
+ expect(model.getTableColumns().columns).not.toContain(
169
+ "serviceLevelObjectiveId",
170
+ );
171
+ });
172
+
173
+ test("is written only by owners, admins and its own permissions, and read like every rule", () => {
174
+ expect(model.getCreatePermissions()).toEqual([
175
+ Permission.ProjectOwner,
176
+ Permission.ProjectAdmin,
177
+ c.create,
178
+ ]);
179
+ expect(model.getUpdatePermissions()).toEqual([
180
+ Permission.ProjectOwner,
181
+ Permission.ProjectAdmin,
182
+ c.update,
183
+ ]);
184
+ expect(model.getDeletePermissions()).toEqual([
185
+ Permission.ProjectOwner,
186
+ Permission.ProjectAdmin,
187
+ c.delete,
188
+ ]);
189
+ expect(model.getReadPermissions()).toEqual([
190
+ Permission.ProjectOwner,
191
+ Permission.ProjectAdmin,
192
+ Permission.ProjectMember,
193
+ Permission.Viewer,
194
+ c.read,
195
+ ]);
196
+ });
197
+
198
+ test("has its four permissions, assignable to a team, in the SLO group", () => {
199
+ for (const permission of [c.create, c.read, c.update, c.delete]) {
200
+ const props: PermissionProps | undefined = PERMISSION_PROPS.find(
201
+ (candidate: PermissionProps): boolean => {
202
+ return candidate.permission === permission;
203
+ },
204
+ );
205
+
206
+ expect(props).toBeDefined();
207
+ expect(props!.group).toBe(PermissionGroup.SLO);
208
+ expect(props!.isAssignableToTenant).toBe(true);
209
+ expect(props!.isAccessControlPermission).toBe(false);
210
+ expect(props!.title).toMatch(/^(Create|Read|Edit|Delete) SLO .* Rule$/);
211
+ expect(props!.description.length).toBeGreaterThan(0);
212
+ }
213
+ });
214
+
215
+ test("inherits the versioned criteria column, with the table's own access", () => {
216
+ expect(model).toBeInstanceOf(RuleBaseModel);
217
+ expect(model.getTableColumnMetadata("criteria").type).toBe(
218
+ TableColumnType.JSON,
219
+ );
220
+
221
+ const criteriaAccess: ColumnAccessControl | undefined =
222
+ model.getColumnAccessControlForAllColumns()["criteria"];
223
+
224
+ expect(criteriaAccess?.create).toEqual(model.getCreatePermissions());
225
+ expect(criteriaAccess?.read).toEqual(model.getReadPermissions());
226
+ expect(criteriaAccess?.update).toEqual(model.getUpdatePermissions());
227
+ });
228
+
229
+ test("registers exactly its legacy match columns as criteria fields", () => {
230
+ expect(getRuleCriteriaFieldsForModel(c.name)).toEqual(LEGACY_MATCH_FIELDS);
231
+
232
+ for (const field of LEGACY_MATCH_FIELDS) {
233
+ expect(model.getTableColumns().columns).toContain(field);
234
+ }
235
+ });
236
+
237
+ test("matches on the SLO's labels through its own join table", () => {
238
+ const metadata: TableColumnMetadata = model.getTableColumnMetadata(
239
+ "serviceLevelObjectiveLabels",
240
+ );
241
+
242
+ expect(metadata.type).toBe(TableColumnType.EntityArray);
243
+ expect(metadata.modelType).toBe(Label);
244
+ expect(metadata.title).toBe("SLO Labels");
245
+ expect(joinTableShape(c.modelType, "serviceLevelObjectiveLabels")).toEqual({
246
+ name: c.labelJoinTable,
247
+ joinColumn: c.ruleIdColumn,
248
+ inverseJoinColumn: "labelId",
249
+ });
250
+ // Postgres truncates identifiers past 63 bytes.
251
+ expect(c.labelJoinTable.length).toBeLessThanOrEqual(63);
252
+ });
253
+
254
+ test("matches on the SLO's name and description as long text patterns", () => {
255
+ for (const [field, title] of [
256
+ ["serviceLevelObjectiveNamePattern", "SLO Name Pattern"],
257
+ ["serviceLevelObjectiveDescriptionPattern", "SLO Description Pattern"],
258
+ ] as Array<[string, string]>) {
259
+ const metadata: TableColumnMetadata = model.getTableColumnMetadata(field);
260
+
261
+ expect(metadata.type).toBe(TableColumnType.LongText);
262
+ expect(metadata.required).toBe(false);
263
+ expect(metadata.title).toBe(title);
264
+ }
265
+ });
266
+
267
+ test("is enabled by default", () => {
268
+ expect(model.isDefaultValueColumn("isEnabled")).toBe(true);
269
+ expect(model.getTableColumnMetadata("isEnabled").defaultValue).toBe(true);
270
+ });
271
+
272
+ test("can be run on existing SLOs", () => {
273
+ const ruleType: RuleRunType | null = RuleRunTypeUtil.fromTableName(
274
+ model.tableName,
275
+ );
276
+
277
+ expect(ruleType).toBe(c.name);
278
+ expect(RuleRunTypeUtil.getMetadata(ruleType!)).toEqual({
279
+ action:
280
+ c.modelType === ServiceLevelObjectiveLabelRule
281
+ ? RuleRunAction.AddLabels
282
+ : RuleRunAction.AddOwners,
283
+ resourceSingular: "SLO",
284
+ resourcePlural: "SLOs",
285
+ });
286
+ });
287
+ });
288
+
289
+ describe("ServiceLevelObjectiveLabelRule's action", () => {
290
+ test("attaches labels through its own join table", () => {
291
+ const metadata: TableColumnMetadata =
292
+ new ServiceLevelObjectiveLabelRule().getTableColumnMetadata(
293
+ "labelsToAdd",
294
+ );
295
+
296
+ expect(metadata.type).toBe(TableColumnType.EntityArray);
297
+ expect(metadata.modelType).toBe(Label);
298
+ expect(
299
+ joinTableShape(ServiceLevelObjectiveLabelRule, "labelsToAdd"),
300
+ ).toEqual({
301
+ name: "ServiceLevelObjectiveLabelRuleLabelToAdd",
302
+ joinColumn: "serviceLevelObjectiveLabelRuleId",
303
+ inverseJoinColumn: "labelId",
304
+ });
305
+ });
306
+
307
+ test("can be exported and imported like every other label rule", () => {
308
+ expect(LABEL_RULE_MODELS).toContain(ServiceLevelObjectiveLabelRule);
309
+ // Only label rules travel as portable files.
310
+ expect(LABEL_RULE_MODELS).not.toContain(ServiceLevelObjectiveOwnerRule);
311
+ });
312
+ });
313
+
314
+ describe("ServiceLevelObjectiveOwnerRule's action", () => {
315
+ const model: ServiceLevelObjectiveOwnerRule =
316
+ new ServiceLevelObjectiveOwnerRule();
317
+
318
+ test.each([
319
+ ["ownerUsers", User, "ServiceLevelObjectiveOwnerRuleOwnerUser", "userId"],
320
+ ["ownerTeams", Team, "ServiceLevelObjectiveOwnerRuleOwnerTeam", "teamId"],
321
+ ])(
322
+ "adds %s through its own join table",
323
+ (
324
+ column: string,
325
+ relatedModel: unknown,
326
+ joinTable: string,
327
+ inverseJoinColumn: string,
328
+ ) => {
329
+ const metadata: TableColumnMetadata =
330
+ model.getTableColumnMetadata(column);
331
+
332
+ expect(metadata.type).toBe(TableColumnType.EntityArray);
333
+ expect(metadata.modelType).toBe(relatedModel);
334
+ expect(joinTableShape(ServiceLevelObjectiveOwnerRule, column)).toEqual({
335
+ name: joinTable,
336
+ joinColumn: "serviceLevelObjectiveOwnerRuleId",
337
+ inverseJoinColumn: inverseJoinColumn,
338
+ });
339
+ },
340
+ );
341
+
342
+ // Owners added by a rule are notified unless the rule says otherwise.
343
+ test("notifies the owners it adds by default", () => {
344
+ expect(model.isDefaultValueColumn("notifyOwners")).toBe(true);
345
+ expect(model.getTableColumnMetadata("notifyOwners").defaultValue).toBe(
346
+ true,
347
+ );
348
+ });
349
+ });
350
+
351
+ describe("SLO label and owner rules are wired into Run Now", () => {
352
+ test.each([
353
+ [
354
+ RuleRunType.ServiceLevelObjectiveLabelRule,
355
+ ServiceLevelObjectiveLabelRule,
356
+ ServiceLevelObjectiveLabelRuleService,
357
+ ServiceLevelObjectiveLabelRuleEngineService,
358
+ undefined,
359
+ ],
360
+ [
361
+ RuleRunType.ServiceLevelObjectiveOwnerRule,
362
+ ServiceLevelObjectiveOwnerRule,
363
+ ServiceLevelObjectiveOwnerRuleService,
364
+ ServiceLevelObjectiveOwnerRuleEngineService,
365
+ [ServiceLevelObjectiveOwnerUser, ServiceLevelObjectiveOwnerTeam],
366
+ ],
367
+ ])(
368
+ "%s walks the project's SLOs with its own engine",
369
+ (
370
+ ruleType: RuleRunType,
371
+ ruleModelType: unknown,
372
+ ruleService: unknown,
373
+ engine: unknown,
374
+ ownerModelTypes: unknown,
375
+ ) => {
376
+ const definition: RuleRunDefinition | null =
377
+ RuleRunRegistry.getDefinition(ruleType);
378
+
379
+ expect(definition).not.toBeNull();
380
+ expect(definition!.ruleModelType).toBe(ruleModelType);
381
+ expect(definition!.resourceModelType).toBe(ServiceLevelObjective);
382
+ expect(definition!.ruleService).toBe(ruleService);
383
+ expect(definition!.resourceService).toBe(ServiceLevelObjectiveService);
384
+ expect(definition!.engine).toBe(engine);
385
+ expect(definition!.ownerModelTypes).toEqual(ownerModelTypes);
386
+ // Unlike the SLO monitor rule, these walk resources, not one SLO.
387
+ expect(RuleRunRegistry.isSyncRuleRunType(ruleType)).toBe(false);
388
+ },
389
+ );
390
+
391
+ test("both rule services are registered, so their APIs and jobs see them", () => {
392
+ expect(Services).toContain(ServiceLevelObjectiveLabelRuleService);
393
+ expect(Services).toContain(ServiceLevelObjectiveOwnerRuleService);
394
+ });
395
+
396
+ test("the criteria registry knows both rule models", () => {
397
+ expect(Object.keys(RULE_CRITERIA_FIELDS_BY_MODEL)).toEqual(
398
+ expect.arrayContaining([
399
+ "ServiceLevelObjectiveLabelRule",
400
+ "ServiceLevelObjectiveOwnerRule",
401
+ ]),
402
+ );
403
+ });
404
+ });
@@ -226,6 +226,7 @@ describe("ServiceLevelObjectiveMonitorRule", () => {
226
226
  "description",
227
227
  "isEnabled",
228
228
  "monitorLabels",
229
+ "monitorType",
229
230
  "monitorNamePattern",
230
231
  "monitorDescriptionPattern",
231
232
  "createdByUserId",
@@ -235,11 +236,12 @@ describe("ServiceLevelObjectiveMonitorRule", () => {
235
236
  }
236
237
  });
237
238
 
238
- test("registers exactly the three legacy match fields for criteria", () => {
239
+ test("registers every monitor match field for criteria", () => {
239
240
  expect(
240
241
  RULE_CRITERIA_FIELDS_BY_MODEL["ServiceLevelObjectiveMonitorRule"],
241
242
  ).toEqual([
242
243
  "monitorLabels",
244
+ "monitorType",
243
245
  "monitorNamePattern",
244
246
  "monitorDescriptionPattern",
245
247
  ]);
@@ -283,6 +285,7 @@ describe("ServiceLevelObjectiveMonitorRule", () => {
283
285
  test("lets the match criteria be edited on a saved rule", () => {
284
286
  for (const column of [
285
287
  "monitorLabels",
288
+ "monitorType",
286
289
  "monitorNamePattern",
287
290
  "monitorDescriptionPattern",
288
291
  "isEnabled",
@@ -313,6 +316,17 @@ describe("ServiceLevelObjectiveMonitorRule", () => {
313
316
  });
314
317
  });
315
318
 
319
+ test("stores an optional monitor type with the monitor enum metadata", () => {
320
+ const metadata: TableColumnMetadata =
321
+ model.getTableColumnMetadata("monitorType");
322
+ expect(metadata.type).toBe(TableColumnType.MonitorType);
323
+ expect(metadata.required).toBe(false);
324
+ expect(metadata.title).toBe("Monitor Type");
325
+ expect(
326
+ columnArgs(ServiceLevelObjectiveMonitorRule, "monitorType")?.options,
327
+ ).toMatchObject({ nullable: true, length: 100 });
328
+ });
329
+
316
330
  test("keeps both patterns nullable, so a label-only rule is valid", () => {
317
331
  for (const column of ["monitorNamePattern", "monitorDescriptionPattern"]) {
318
332
  expect(
@@ -430,6 +444,8 @@ describe("ServiceLevelObjectiveFeed", () => {
430
444
  "OwnerUserRemoved",
431
445
  "OwnerTeamAdded",
432
446
  "OwnerTeamRemoved",
447
+ "LabelRuleExecuted",
448
+ "OwnerRuleExecuted",
433
449
  ].sort(),
434
450
  );
435
451
 
@@ -86,6 +86,8 @@ const STATIC_MATCH_CRITERIA_RULE_MODEL_NAMES: Array<string> = [
86
86
  "NetworkDeviceAutoImportRule",
87
87
  "NetworkSiteAssignmentRule",
88
88
  "ServiceLevelObjectiveMonitorRule",
89
+ "ServiceLevelObjectiveLabelRule",
90
+ "ServiceLevelObjectiveOwnerRule",
89
91
  ];
90
92
 
91
93
  const RELATION_ONLY_RULE_MODEL_NAMES: Array<string> = [
@@ -108,8 +110,8 @@ function getRegisteredModelType(modelName: string): ModelType {
108
110
 
109
111
  describe("static match-criteria rule model inheritance", () => {
110
112
  it("keeps the explicit model inventory complete and duplicate-free", () => {
111
- expect(STATIC_MATCH_CRITERIA_RULE_MODEL_NAMES).toHaveLength(74);
112
- expect(new Set(STATIC_MATCH_CRITERIA_RULE_MODEL_NAMES).size).toBe(74);
113
+ expect(STATIC_MATCH_CRITERIA_RULE_MODEL_NAMES).toHaveLength(76);
114
+ expect(new Set(STATIC_MATCH_CRITERIA_RULE_MODEL_NAMES).size).toBe(76);
113
115
  });
114
116
 
115
117
  it.each(STATIC_MATCH_CRITERIA_RULE_MODEL_NAMES)(
@@ -25,6 +25,9 @@ const NEVER_MATCH_PATTERN: string = "(?!)";
25
25
  const RULE_MODELS_INTRODUCED_AFTER_MIGRATION: ReadonlyArray<string> = [
26
26
  // 1793100000000-SloProductOverhaul
27
27
  "ServiceLevelObjectiveMonitorRule",
28
+ // 1794100000000-AddSloLabelAndOwnerRules
29
+ "ServiceLevelObjectiveLabelRule",
30
+ "ServiceLevelObjectiveOwnerRule",
28
31
  ];
29
32
 
30
33
  type IsCoveredByMigrationFunction = (modelName: string) => boolean;