@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
package/Types/API/URL.ts
CHANGED
|
@@ -459,7 +459,26 @@ export default class URL extends DatabaseProperty {
|
|
|
459
459
|
const path: string = pathSegments.join("/");
|
|
460
460
|
|
|
461
461
|
if (path || fragment) {
|
|
462
|
-
|
|
462
|
+
/*
|
|
463
|
+
* The route is held without the "/" that separates it from the
|
|
464
|
+
* authority ("api/v1/items"), and toString puts it back. Dropping it is
|
|
465
|
+
* only harmless while the first segment has no ":" — once it does, the
|
|
466
|
+
* bare path reads as a scheme ("bot123:ABC/sendMessage" is scheme
|
|
467
|
+
* "bot123", RFC 3986 4.2) and Route rejects it. Telegram puts the bot
|
|
468
|
+
* token, which always contains a ":", in exactly that segment, so every
|
|
469
|
+
* https://api.telegram.org/bot<token>/... URL failed to parse — which
|
|
470
|
+
* broke the Telegram workflow component, the API components, and the
|
|
471
|
+
* SSRF check on every sandboxed axios call to Telegram.
|
|
472
|
+
*
|
|
473
|
+
* Keep the "/" for those paths only. It is the path as written, it
|
|
474
|
+
* cannot be read as a scheme, and toString produces the same string
|
|
475
|
+
* either way; every path that parsed before parses to the same Route.
|
|
476
|
+
*/
|
|
477
|
+
const routeValue: string = path + fragment;
|
|
478
|
+
|
|
479
|
+
route = new Route(
|
|
480
|
+
Route.hasSchemePrefix(routeValue) ? "/" + routeValue : routeValue,
|
|
481
|
+
);
|
|
463
482
|
}
|
|
464
483
|
|
|
465
484
|
const queryString: string = URL.queryStringOf(url);
|
|
@@ -535,8 +554,13 @@ export default class URL extends DatabaseProperty {
|
|
|
535
554
|
}
|
|
536
555
|
|
|
537
556
|
public addRoute(route: Route | string): URL {
|
|
557
|
+
/*
|
|
558
|
+
* A string goes to Route.addRoute as-is, which prefixes the "/" before
|
|
559
|
+
* validating. Wrapping it in a Route first validated the bare form, so
|
|
560
|
+
* addRoute("bot123:ABC/sendMessage") threw for the same reason as above.
|
|
561
|
+
*/
|
|
538
562
|
if (typeof route === Typeof.String) {
|
|
539
|
-
this.route.addRoute(
|
|
563
|
+
this.route.addRoute(route.toString());
|
|
540
564
|
}
|
|
541
565
|
|
|
542
566
|
if (route instanceof Route) {
|
|
@@ -25,7 +25,8 @@ export interface MetricBreachingSample {
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Descriptor for one variable referenced by a formula, used to label
|
|
28
|
-
*
|
|
28
|
+
* each breaching sample's component values and render the Metric
|
|
29
|
+
* Details section. Name is the
|
|
29
30
|
* metric display name (usually the metric name, sometimes the formula
|
|
30
31
|
* expression for nested formulas) and unit is the variable's native
|
|
31
32
|
* unit as configured.
|
|
@@ -103,8 +104,8 @@ export default interface MetricCriteriaContext {
|
|
|
103
104
|
breachingSample?: MetricBreachingSample | undefined;
|
|
104
105
|
/**
|
|
105
106
|
* All samples in the evaluation window that breached the threshold,
|
|
106
|
-
* in chronological order. Used to render
|
|
107
|
-
* values on the incident root-cause page.
|
|
107
|
+
* in chronological order. Used to render the Breaching Samples list
|
|
108
|
+
* of timestamps and values on the incident root-cause page.
|
|
108
109
|
*/
|
|
109
110
|
breachingSamples?: Array<MetricBreachingSample> | undefined;
|
|
110
111
|
/**
|
|
@@ -116,8 +117,8 @@ export default interface MetricCriteriaContext {
|
|
|
116
117
|
/**
|
|
117
118
|
* For formulas: metadata for each variable the formula references,
|
|
118
119
|
* in the order they appear in the expression. Consumers use this to
|
|
119
|
-
* label
|
|
120
|
-
* names in the Metric Details section.
|
|
120
|
+
* label each breaching sample's component values and to surface
|
|
121
|
+
* underlying units/metric names in the Metric Details section.
|
|
121
122
|
*/
|
|
122
123
|
components?: Array<MetricComponent> | undefined;
|
|
123
124
|
}
|
|
@@ -13,7 +13,16 @@ export interface KubernetesAffectedResource {
|
|
|
13
13
|
containerName?: string | undefined;
|
|
14
14
|
workloadType?: string | undefined;
|
|
15
15
|
workloadName?: string | undefined;
|
|
16
|
+
/** Highest sample seen for this resource in the monitoring window. */
|
|
16
17
|
metricValue: number;
|
|
18
|
+
/**
|
|
19
|
+
* Lowest sample seen for this resource in the monitoring window. A
|
|
20
|
+
* criteria that fires when the metric FALLS (node Ready = 0, etcd has
|
|
21
|
+
* leader = 0) breached on this value, not on `metricValue`: a node that
|
|
22
|
+
* was NotReady for one scrape and Ready for the rest has a highest of 1.
|
|
23
|
+
* Absent on breakdowns built before this field existed.
|
|
24
|
+
*/
|
|
25
|
+
lowestMetricValue?: number | undefined;
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
export interface KubernetesResourceBreakdown {
|
package/Types/Permission.ts
CHANGED
|
@@ -2018,6 +2018,18 @@ enum Permission {
|
|
|
2018
2018
|
EditServiceLevelObjectiveOwnerTeam = "EditServiceLevelObjectiveOwnerTeam",
|
|
2019
2019
|
ReadServiceLevelObjectiveOwnerTeam = "ReadServiceLevelObjectiveOwnerTeam",
|
|
2020
2020
|
|
|
2021
|
+
// Service Level Objective Owner Rule Permissions
|
|
2022
|
+
CreateServiceLevelObjectiveOwnerRule = "CreateServiceLevelObjectiveOwnerRule",
|
|
2023
|
+
DeleteServiceLevelObjectiveOwnerRule = "DeleteServiceLevelObjectiveOwnerRule",
|
|
2024
|
+
EditServiceLevelObjectiveOwnerRule = "EditServiceLevelObjectiveOwnerRule",
|
|
2025
|
+
ReadServiceLevelObjectiveOwnerRule = "ReadServiceLevelObjectiveOwnerRule",
|
|
2026
|
+
|
|
2027
|
+
// Service Level Objective Label Rule Permissions
|
|
2028
|
+
CreateServiceLevelObjectiveLabelRule = "CreateServiceLevelObjectiveLabelRule",
|
|
2029
|
+
DeleteServiceLevelObjectiveLabelRule = "DeleteServiceLevelObjectiveLabelRule",
|
|
2030
|
+
EditServiceLevelObjectiveLabelRule = "EditServiceLevelObjectiveLabelRule",
|
|
2031
|
+
ReadServiceLevelObjectiveLabelRule = "ReadServiceLevelObjectiveLabelRule",
|
|
2032
|
+
|
|
2021
2033
|
/*
|
|
2022
2034
|
* Wildcard permissions covering all models marked @OperationalResource().
|
|
2023
2035
|
* These short-circuit table-level checks for that resource class. Scope on
|
|
@@ -16140,6 +16152,84 @@ export class PermissionHelper {
|
|
|
16140
16152
|
group: PermissionGroup.SLO,
|
|
16141
16153
|
},
|
|
16142
16154
|
|
|
16155
|
+
// Service Level Objective Owner Rule Permissions
|
|
16156
|
+
{
|
|
16157
|
+
permission: Permission.CreateServiceLevelObjectiveOwnerRule,
|
|
16158
|
+
title: "Create SLO Owner Rule",
|
|
16159
|
+
description:
|
|
16160
|
+
"Permission to create SLO Owner Rules, which add owner users and teams to matching SLOs, in this project.",
|
|
16161
|
+
isAssignableToTenant: true,
|
|
16162
|
+
isAccessControlPermission: false,
|
|
16163
|
+
isRolePermission: false,
|
|
16164
|
+
group: PermissionGroup.SLO,
|
|
16165
|
+
},
|
|
16166
|
+
{
|
|
16167
|
+
permission: Permission.DeleteServiceLevelObjectiveOwnerRule,
|
|
16168
|
+
title: "Delete SLO Owner Rule",
|
|
16169
|
+
description: "Permission to delete SLO Owner Rules of this project.",
|
|
16170
|
+
isAssignableToTenant: true,
|
|
16171
|
+
isAccessControlPermission: false,
|
|
16172
|
+
isRolePermission: false,
|
|
16173
|
+
group: PermissionGroup.SLO,
|
|
16174
|
+
},
|
|
16175
|
+
{
|
|
16176
|
+
permission: Permission.EditServiceLevelObjectiveOwnerRule,
|
|
16177
|
+
title: "Edit SLO Owner Rule",
|
|
16178
|
+
description: "Permission to edit SLO Owner Rules of this project.",
|
|
16179
|
+
isAssignableToTenant: true,
|
|
16180
|
+
isAccessControlPermission: false,
|
|
16181
|
+
isRolePermission: false,
|
|
16182
|
+
group: PermissionGroup.SLO,
|
|
16183
|
+
},
|
|
16184
|
+
{
|
|
16185
|
+
permission: Permission.ReadServiceLevelObjectiveOwnerRule,
|
|
16186
|
+
title: "Read SLO Owner Rule",
|
|
16187
|
+
description: "Permission to read SLO Owner Rules of this project.",
|
|
16188
|
+
isAssignableToTenant: true,
|
|
16189
|
+
isAccessControlPermission: false,
|
|
16190
|
+
isRolePermission: false,
|
|
16191
|
+
group: PermissionGroup.SLO,
|
|
16192
|
+
},
|
|
16193
|
+
|
|
16194
|
+
// Service Level Objective Label Rule Permissions
|
|
16195
|
+
{
|
|
16196
|
+
permission: Permission.CreateServiceLevelObjectiveLabelRule,
|
|
16197
|
+
title: "Create SLO Label Rule",
|
|
16198
|
+
description:
|
|
16199
|
+
"Permission to create SLO Label Rules, which attach labels to matching SLOs, in this project.",
|
|
16200
|
+
isAssignableToTenant: true,
|
|
16201
|
+
isAccessControlPermission: false,
|
|
16202
|
+
isRolePermission: false,
|
|
16203
|
+
group: PermissionGroup.SLO,
|
|
16204
|
+
},
|
|
16205
|
+
{
|
|
16206
|
+
permission: Permission.DeleteServiceLevelObjectiveLabelRule,
|
|
16207
|
+
title: "Delete SLO Label Rule",
|
|
16208
|
+
description: "Permission to delete SLO Label Rules of this project.",
|
|
16209
|
+
isAssignableToTenant: true,
|
|
16210
|
+
isAccessControlPermission: false,
|
|
16211
|
+
isRolePermission: false,
|
|
16212
|
+
group: PermissionGroup.SLO,
|
|
16213
|
+
},
|
|
16214
|
+
{
|
|
16215
|
+
permission: Permission.EditServiceLevelObjectiveLabelRule,
|
|
16216
|
+
title: "Edit SLO Label Rule",
|
|
16217
|
+
description: "Permission to edit SLO Label Rules of this project.",
|
|
16218
|
+
isAssignableToTenant: true,
|
|
16219
|
+
isAccessControlPermission: false,
|
|
16220
|
+
isRolePermission: false,
|
|
16221
|
+
group: PermissionGroup.SLO,
|
|
16222
|
+
},
|
|
16223
|
+
{
|
|
16224
|
+
permission: Permission.ReadServiceLevelObjectiveLabelRule,
|
|
16225
|
+
title: "Read SLO Label Rule",
|
|
16226
|
+
description: "Permission to read SLO Label Rules of this project.",
|
|
16227
|
+
isAssignableToTenant: true,
|
|
16228
|
+
isAccessControlPermission: false,
|
|
16229
|
+
isRolePermission: false,
|
|
16230
|
+
group: PermissionGroup.SLO,
|
|
16231
|
+
},
|
|
16232
|
+
|
|
16143
16233
|
// Operational Resource Wildcard Permissions
|
|
16144
16234
|
{
|
|
16145
16235
|
permission: Permission.ReadAllOperationalResources,
|
|
@@ -379,11 +379,22 @@ export const RULE_CRITERIA_FIELDS_BY_MODEL: Readonly<
|
|
|
379
379
|
"serviceNamePattern",
|
|
380
380
|
"serviceDescriptionPattern",
|
|
381
381
|
],
|
|
382
|
+
ServiceLevelObjectiveLabelRule: [
|
|
383
|
+
"serviceLevelObjectiveLabels",
|
|
384
|
+
"serviceLevelObjectiveNamePattern",
|
|
385
|
+
"serviceLevelObjectiveDescriptionPattern",
|
|
386
|
+
],
|
|
382
387
|
ServiceLevelObjectiveMonitorRule: [
|
|
383
388
|
"monitorLabels",
|
|
389
|
+
"monitorType",
|
|
384
390
|
"monitorNamePattern",
|
|
385
391
|
"monitorDescriptionPattern",
|
|
386
392
|
],
|
|
393
|
+
ServiceLevelObjectiveOwnerRule: [
|
|
394
|
+
"serviceLevelObjectiveLabels",
|
|
395
|
+
"serviceLevelObjectiveNamePattern",
|
|
396
|
+
"serviceLevelObjectiveDescriptionPattern",
|
|
397
|
+
],
|
|
387
398
|
ServiceOwnerRule: [
|
|
388
399
|
"serviceLabels",
|
|
389
400
|
"serviceNamePattern",
|
package/Types/Rules/RuleRun.ts
CHANGED
|
@@ -92,7 +92,9 @@ export enum RuleRunType {
|
|
|
92
92
|
ServerlessFunctionLabelRule = "ServerlessFunctionLabelRule",
|
|
93
93
|
ServerlessFunctionOwnerRule = "ServerlessFunctionOwnerRule",
|
|
94
94
|
ServiceLabelRule = "ServiceLabelRule",
|
|
95
|
+
ServiceLevelObjectiveLabelRule = "ServiceLevelObjectiveLabelRule",
|
|
95
96
|
ServiceLevelObjectiveMonitorRule = "ServiceLevelObjectiveMonitorRule",
|
|
97
|
+
ServiceLevelObjectiveOwnerRule = "ServiceLevelObjectiveOwnerRule",
|
|
96
98
|
ServiceOwnerRule = "ServiceOwnerRule",
|
|
97
99
|
StatusPageLabelRule = "StatusPageLabelRule",
|
|
98
100
|
StatusPageOwnerRule = "StatusPageOwnerRule",
|
|
@@ -316,11 +318,13 @@ export const RULE_RUN_TYPE_METADATA: Readonly<
|
|
|
316
318
|
"serverless functions",
|
|
317
319
|
),
|
|
318
320
|
[RuleRunType.ServiceLabelRule]: metadata(Labels, "service", "services"),
|
|
321
|
+
[RuleRunType.ServiceLevelObjectiveLabelRule]: metadata(Labels, "SLO", "SLOs"),
|
|
319
322
|
[RuleRunType.ServiceLevelObjectiveMonitorRule]: metadata(
|
|
320
323
|
RuleRunAction.SyncSloMonitors,
|
|
321
324
|
"monitor",
|
|
322
325
|
"monitors",
|
|
323
326
|
),
|
|
327
|
+
[RuleRunType.ServiceLevelObjectiveOwnerRule]: metadata(Owners, "SLO", "SLOs"),
|
|
324
328
|
[RuleRunType.ServiceOwnerRule]: metadata(Owners, "service", "services"),
|
|
325
329
|
[RuleRunType.StatusPageLabelRule]: metadata(
|
|
326
330
|
Labels,
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import GenericObject from "../../../Types/GenericObject";
|
|
2
|
+
import Includes from "../../../Types/BaseDatabase/Includes";
|
|
3
|
+
import Query from "../../../Types/BaseDatabase/Query";
|
|
4
|
+
import SortOrder from "../../../Types/BaseDatabase/SortOrder";
|
|
5
|
+
import IconProp from "../../../Types/Icon/IconProp";
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* What a reader can change about a dashboard activity feed from its one
|
|
9
|
+
* "Filter & Sort" button: which way the timeline runs, and which kinds of
|
|
10
|
+
* event it shows.
|
|
11
|
+
*
|
|
12
|
+
* Both are applied by the API, never to the rows already in the browser. A
|
|
13
|
+
* feed is read a window at a time (see useFeedItems), so filtering the loaded
|
|
14
|
+
* window would show "no items" while matching events sat one page further
|
|
15
|
+
* back, and reversing it would show the newest ten upside down instead of the
|
|
16
|
+
* oldest ten.
|
|
17
|
+
*
|
|
18
|
+
* This module is React-free so the rules can be tested on their own.
|
|
19
|
+
*/
|
|
20
|
+
export interface FeedOptions {
|
|
21
|
+
sortOrder: SortOrder;
|
|
22
|
+
/*
|
|
23
|
+
* The event types to show. Empty means no filter, so every event type is
|
|
24
|
+
* shown - including one a feed model gains after the reader last looked.
|
|
25
|
+
*/
|
|
26
|
+
eventTypes: Array<string>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const DEFAULT_FEED_SORT_ORDER: SortOrder = SortOrder.Descending;
|
|
30
|
+
|
|
31
|
+
export const DEFAULT_FEED_OPTIONS: FeedOptions = {
|
|
32
|
+
sortOrder: DEFAULT_FEED_SORT_ORDER,
|
|
33
|
+
eventTypes: [],
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export interface FeedSortOrderOption {
|
|
37
|
+
value: SortOrder;
|
|
38
|
+
label: string;
|
|
39
|
+
icon: IconProp;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const FEED_SORT_ORDER_OPTIONS: Array<FeedSortOrderOption> = [
|
|
43
|
+
{
|
|
44
|
+
value: SortOrder.Descending,
|
|
45
|
+
label: "Newest first",
|
|
46
|
+
icon: IconProp.BarsArrowDown,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
value: SortOrder.Ascending,
|
|
50
|
+
label: "Oldest first",
|
|
51
|
+
icon: IconProp.BarsArrowUp,
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
export interface FeedEventTypeOption {
|
|
56
|
+
value: string;
|
|
57
|
+
label: string;
|
|
58
|
+
icon?: IconProp | undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type GetFeedEventTypeLabelFunction = (
|
|
62
|
+
eventType: string,
|
|
63
|
+
) => string | undefined;
|
|
64
|
+
|
|
65
|
+
export type GetFeedEventTypeIconFunction = (
|
|
66
|
+
eventType: string,
|
|
67
|
+
) => IconProp | undefined;
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
* Event types shared by many feeds whose split name reads badly
|
|
71
|
+
* ("Owner User Added"). A Map rather than an object literal, so an event type
|
|
72
|
+
* that happens to share a name with an Object.prototype member ("toString")
|
|
73
|
+
* cannot come back as a function.
|
|
74
|
+
*/
|
|
75
|
+
const EVENT_TYPE_LABELS: Map<string, string> = new Map<string, string>([
|
|
76
|
+
["OwnerUserAdded", "User Added as Owner"],
|
|
77
|
+
["OwnerUserRemoved", "User Removed as Owner"],
|
|
78
|
+
["OwnerTeamAdded", "Team Added as Owner"],
|
|
79
|
+
["OwnerTeamRemoved", "Team Removed as Owner"],
|
|
80
|
+
["AutoRemediation", "Auto-Remediation"],
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
/*
|
|
84
|
+
* Runs that camel-case splitting would get wrong, matched before any other
|
|
85
|
+
* word: "VMware" would split into "V Mware", "OnCall" should read
|
|
86
|
+
* "On-Call", and the SLO feed's "ServiceLevelObjective" prefix is written
|
|
87
|
+
* "SLO" everywhere else in the dashboard.
|
|
88
|
+
*/
|
|
89
|
+
const EVENT_TYPE_WORDS: Array<{ word: string; label: string }> = [
|
|
90
|
+
{ word: "ServiceLevelObjective", label: "SLO" },
|
|
91
|
+
{ word: "VMware", label: "VMware" },
|
|
92
|
+
{ word: "VCenter", label: "vCenter" },
|
|
93
|
+
{ word: "OnCall", label: "On-Call" },
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
// Joining words stay lower case inside a title ("Added to Episode").
|
|
97
|
+
const LOWER_CASE_WORDS: Set<string> = new Set<string>([
|
|
98
|
+
"a",
|
|
99
|
+
"an",
|
|
100
|
+
"and",
|
|
101
|
+
"as",
|
|
102
|
+
"by",
|
|
103
|
+
"for",
|
|
104
|
+
"from",
|
|
105
|
+
"in",
|
|
106
|
+
"of",
|
|
107
|
+
"on",
|
|
108
|
+
"or",
|
|
109
|
+
"the",
|
|
110
|
+
"to",
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
const EVENT_TYPE_WORD_PATTERN: RegExp = new RegExp(
|
|
114
|
+
`${EVENT_TYPE_WORDS.map((entry: { word: string }) => {
|
|
115
|
+
return entry.word;
|
|
116
|
+
}).join("|")}|[A-Z]+(?![a-z])|[A-Z]?[a-z]+|[0-9]+`,
|
|
117
|
+
"g",
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
type GetFeedEventTypeLabel = (eventType: string) => string;
|
|
121
|
+
|
|
122
|
+
/*
|
|
123
|
+
* Feed event types are enum values named like "IncidentStateChanged", so a
|
|
124
|
+
* readable label is the value split into words. A feed with a better name for
|
|
125
|
+
* one of its own events passes getEventTypeLabel to getFeedEventTypeOptions.
|
|
126
|
+
*/
|
|
127
|
+
export const getFeedEventTypeLabel: GetFeedEventTypeLabel = (
|
|
128
|
+
eventType: string,
|
|
129
|
+
): string => {
|
|
130
|
+
const knownLabel: string | undefined = EVENT_TYPE_LABELS.get(eventType);
|
|
131
|
+
|
|
132
|
+
if (knownLabel) {
|
|
133
|
+
return knownLabel;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const words: Array<string> = eventType.match(EVENT_TYPE_WORD_PATTERN) || [];
|
|
137
|
+
|
|
138
|
+
if (words.length === 0) {
|
|
139
|
+
return eventType;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return words
|
|
143
|
+
.map((word: string, index: number): string => {
|
|
144
|
+
const knownWord: { word: string; label: string } | undefined =
|
|
145
|
+
EVENT_TYPE_WORDS.find((entry: { word: string }) => {
|
|
146
|
+
return entry.word === word;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (knownWord) {
|
|
150
|
+
return knownWord.label;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (index > 0 && LOWER_CASE_WORDS.has(word.toLowerCase())) {
|
|
154
|
+
return word.toLowerCase();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
158
|
+
})
|
|
159
|
+
.join(" ");
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
type GetFeedEventTypeOptions = (data: {
|
|
163
|
+
eventTypes: Array<string>;
|
|
164
|
+
getEventTypeLabel?: GetFeedEventTypeLabelFunction | undefined;
|
|
165
|
+
getEventTypeIcon?: GetFeedEventTypeIconFunction | undefined;
|
|
166
|
+
}) => Array<FeedEventTypeOption>;
|
|
167
|
+
|
|
168
|
+
/*
|
|
169
|
+
* The checklist behind the button: one entry per event type, alphabetical by
|
|
170
|
+
* label so the reader can scan for the one they want.
|
|
171
|
+
*/
|
|
172
|
+
export const getFeedEventTypeOptions: GetFeedEventTypeOptions = (data: {
|
|
173
|
+
eventTypes: Array<string>;
|
|
174
|
+
getEventTypeLabel?: GetFeedEventTypeLabelFunction | undefined;
|
|
175
|
+
getEventTypeIcon?: GetFeedEventTypeIconFunction | undefined;
|
|
176
|
+
}): Array<FeedEventTypeOption> => {
|
|
177
|
+
const uniqueEventTypes: Array<string> = Array.from(
|
|
178
|
+
new Set<string>(
|
|
179
|
+
data.eventTypes.filter((eventType: string) => {
|
|
180
|
+
return Boolean(eventType);
|
|
181
|
+
}),
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
return uniqueEventTypes
|
|
186
|
+
.map((eventType: string): FeedEventTypeOption => {
|
|
187
|
+
return {
|
|
188
|
+
value: eventType,
|
|
189
|
+
label:
|
|
190
|
+
data.getEventTypeLabel?.(eventType) ||
|
|
191
|
+
getFeedEventTypeLabel(eventType),
|
|
192
|
+
icon: data.getEventTypeIcon?.(eventType),
|
|
193
|
+
};
|
|
194
|
+
})
|
|
195
|
+
.sort((a: FeedEventTypeOption, b: FeedEventTypeOption): number => {
|
|
196
|
+
return a.label.localeCompare(b.label);
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
type SanitizeFeedEventTypes = (data: {
|
|
201
|
+
selectedEventTypes: Array<string>;
|
|
202
|
+
knownEventTypes: Array<string>;
|
|
203
|
+
}) => Array<string>;
|
|
204
|
+
|
|
205
|
+
/*
|
|
206
|
+
* Keep only event types the feed actually has, once each, in the feed's own
|
|
207
|
+
* order. The order matters: it makes the request - and so the reload key -
|
|
208
|
+
* the same whichever order the reader ticked the boxes in.
|
|
209
|
+
*/
|
|
210
|
+
export const sanitizeFeedEventTypes: SanitizeFeedEventTypes = (data: {
|
|
211
|
+
selectedEventTypes: Array<string>;
|
|
212
|
+
knownEventTypes: Array<string>;
|
|
213
|
+
}): Array<string> => {
|
|
214
|
+
const selected: Set<string> = new Set<string>(data.selectedEventTypes);
|
|
215
|
+
|
|
216
|
+
return Array.from(new Set<string>(data.knownEventTypes)).filter(
|
|
217
|
+
(eventType: string) => {
|
|
218
|
+
return selected.has(eventType);
|
|
219
|
+
},
|
|
220
|
+
);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
type IsFeedSortOrder = (value: unknown) => value is SortOrder;
|
|
224
|
+
|
|
225
|
+
export const isFeedSortOrder: IsFeedSortOrder = (
|
|
226
|
+
value: unknown,
|
|
227
|
+
): value is SortOrder => {
|
|
228
|
+
return value === SortOrder.Ascending || value === SortOrder.Descending;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
type GetFeedOptionsKey = (options: FeedOptions) => string;
|
|
232
|
+
|
|
233
|
+
/*
|
|
234
|
+
* A stable string for "what this feed is showing". useFeedItems re-reads the
|
|
235
|
+
* feed from its first window whenever it changes.
|
|
236
|
+
*/
|
|
237
|
+
export const getFeedOptionsKey: GetFeedOptionsKey = (
|
|
238
|
+
options: FeedOptions,
|
|
239
|
+
): string => {
|
|
240
|
+
return `${options.sortOrder}|${options.eventTypes.join(",")}`;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
type IsFeedFiltered = (options: FeedOptions) => boolean;
|
|
244
|
+
|
|
245
|
+
export const isFeedFiltered: IsFeedFiltered = (
|
|
246
|
+
options: FeedOptions,
|
|
247
|
+
): boolean => {
|
|
248
|
+
return options.eventTypes.length > 0;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
type IsDefaultFeedOptions = (options: FeedOptions) => boolean;
|
|
252
|
+
|
|
253
|
+
export const isDefaultFeedOptions: IsDefaultFeedOptions = (
|
|
254
|
+
options: FeedOptions,
|
|
255
|
+
): boolean => {
|
|
256
|
+
return (
|
|
257
|
+
options.sortOrder === DEFAULT_FEED_SORT_ORDER && !isFeedFiltered(options)
|
|
258
|
+
);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/*
|
|
262
|
+
* The query fragment for the event type filter, keyed by the feed model's
|
|
263
|
+
* event type column. It is empty when nothing is filtered, so an unfiltered
|
|
264
|
+
* feed sends exactly the query it always sent.
|
|
265
|
+
*/
|
|
266
|
+
export const getFeedEventTypeQuery: <TFeedModel extends GenericObject>(
|
|
267
|
+
eventTypeColumn: Extract<keyof TFeedModel, string>,
|
|
268
|
+
options: FeedOptions,
|
|
269
|
+
) => Query<TFeedModel> = <TFeedModel extends GenericObject>(
|
|
270
|
+
eventTypeColumn: Extract<keyof TFeedModel, string>,
|
|
271
|
+
options: FeedOptions,
|
|
272
|
+
): Query<TFeedModel> => {
|
|
273
|
+
if (!isFeedFiltered(options)) {
|
|
274
|
+
return {};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
[eventTypeColumn]: new Includes([...options.eventTypes]),
|
|
279
|
+
} as unknown as Query<TFeedModel>;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
export const FILTERED_FEED_NO_ITEMS_MESSAGE: string =
|
|
283
|
+
"No events in this feed match the selected event types.";
|
|
284
|
+
|
|
285
|
+
type GetFeedNoItemsMessage = (data: {
|
|
286
|
+
options: FeedOptions;
|
|
287
|
+
noItemsMessage: string;
|
|
288
|
+
}) => string;
|
|
289
|
+
|
|
290
|
+
/*
|
|
291
|
+
* An empty filtered feed says so, rather than claiming the resource has no
|
|
292
|
+
* activity at all.
|
|
293
|
+
*/
|
|
294
|
+
export const getFeedNoItemsMessage: GetFeedNoItemsMessage = (data: {
|
|
295
|
+
options: FeedOptions;
|
|
296
|
+
noItemsMessage: string;
|
|
297
|
+
}): string => {
|
|
298
|
+
return isFeedFiltered(data.options)
|
|
299
|
+
? FILTERED_FEED_NO_ITEMS_MESSAGE
|
|
300
|
+
: data.noItemsMessage;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
/*
|
|
304
|
+
* The control's own sentences, as the English keys the dashboard's locale
|
|
305
|
+
* files translate (see Utils/Translation). The ones with placeholders are
|
|
306
|
+
* looked up whole and filled afterwards, so a translation can reorder the
|
|
307
|
+
* words around the numbers.
|
|
308
|
+
*/
|
|
309
|
+
export const FEED_OPTIONS_TEXT: {
|
|
310
|
+
triggerLabel: string;
|
|
311
|
+
searchResults: string;
|
|
312
|
+
panelLabel: string;
|
|
313
|
+
sortHeading: string;
|
|
314
|
+
eventTypesHeading: string;
|
|
315
|
+
showAll: string;
|
|
316
|
+
searchPlaceholder: string;
|
|
317
|
+
showingEveryEventType: string;
|
|
318
|
+
showingSomeEventTypes: string;
|
|
319
|
+
noSearchMatches: string;
|
|
320
|
+
noEventTypes: string;
|
|
321
|
+
reset: string;
|
|
322
|
+
summaryAllEventTypes: string;
|
|
323
|
+
summarySomeEventTypes: string;
|
|
324
|
+
summarySomeOfOneEventType: string;
|
|
325
|
+
} = {
|
|
326
|
+
triggerLabel: "Filter & Sort",
|
|
327
|
+
searchResults: "Matching event types: {{count}}",
|
|
328
|
+
panelLabel: "Filter and sort feed",
|
|
329
|
+
sortHeading: "Sort by time",
|
|
330
|
+
eventTypesHeading: "Event types",
|
|
331
|
+
showAll: "Show all",
|
|
332
|
+
searchPlaceholder: "Search event types",
|
|
333
|
+
showingEveryEventType:
|
|
334
|
+
"Showing every event type. Tick one or more to narrow the feed.",
|
|
335
|
+
showingSomeEventTypes: "Showing {{selected}} of {{total}} event types.",
|
|
336
|
+
noSearchMatches: 'No event types match "{{search}}".',
|
|
337
|
+
noEventTypes: "This feed has no event types to filter by.",
|
|
338
|
+
reset: "Reset to default",
|
|
339
|
+
summaryAllEventTypes: "{{sortOrder}}, all event types",
|
|
340
|
+
summarySomeEventTypes: "{{sortOrder}}, {{selected}} of {{total}} event types",
|
|
341
|
+
summarySomeOfOneEventType:
|
|
342
|
+
"{{sortOrder}}, {{selected}} of {{total}} event type",
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// Looks a sentence up in the reader's language; undefined means "as written".
|
|
346
|
+
export type FeedOptionsTranslateFunction = (
|
|
347
|
+
text: string | undefined,
|
|
348
|
+
) => string | undefined;
|
|
349
|
+
|
|
350
|
+
const PLACEHOLDER_PATTERN: RegExp = /\{\{\s*([A-Za-z0-9_]+)\s*\}\}/g;
|
|
351
|
+
|
|
352
|
+
type TranslateFeedOptionsText = (data: {
|
|
353
|
+
text: string;
|
|
354
|
+
values?: Record<string, string | number> | undefined;
|
|
355
|
+
translate?: FeedOptionsTranslateFunction | undefined;
|
|
356
|
+
}) => string;
|
|
357
|
+
|
|
358
|
+
/*
|
|
359
|
+
* Translate, then fill the placeholders. A placeholder with no value is left
|
|
360
|
+
* showing, so a broken translation is visible rather than a sentence with a
|
|
361
|
+
* hole in it.
|
|
362
|
+
*/
|
|
363
|
+
export const translateFeedOptionsText: TranslateFeedOptionsText = (data: {
|
|
364
|
+
text: string;
|
|
365
|
+
values?: Record<string, string | number> | undefined;
|
|
366
|
+
translate?: FeedOptionsTranslateFunction | undefined;
|
|
367
|
+
}): string => {
|
|
368
|
+
const translated: string =
|
|
369
|
+
(data.translate ? data.translate(data.text) : undefined) || data.text;
|
|
370
|
+
const values: Record<string, string | number> = data.values || {};
|
|
371
|
+
|
|
372
|
+
return translated.replace(
|
|
373
|
+
PLACEHOLDER_PATTERN,
|
|
374
|
+
(placeholder: string, name: string): string => {
|
|
375
|
+
const value: string | number | undefined = values[name];
|
|
376
|
+
|
|
377
|
+
return value === undefined ? placeholder : String(value);
|
|
378
|
+
},
|
|
379
|
+
);
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
type GetFeedOptionsSummary = (data: {
|
|
383
|
+
options: FeedOptions;
|
|
384
|
+
eventTypeCount: number;
|
|
385
|
+
translate?: FeedOptionsTranslateFunction | undefined;
|
|
386
|
+
}) => string;
|
|
387
|
+
|
|
388
|
+
/*
|
|
389
|
+
* One sentence for the trigger's accessible name and tooltip, so the state
|
|
390
|
+
* hidden behind the button can be read without opening it.
|
|
391
|
+
*/
|
|
392
|
+
export const getFeedOptionsSummary: GetFeedOptionsSummary = (data: {
|
|
393
|
+
options: FeedOptions;
|
|
394
|
+
eventTypeCount: number;
|
|
395
|
+
translate?: FeedOptionsTranslateFunction | undefined;
|
|
396
|
+
}): string => {
|
|
397
|
+
const sortOrder: string = translateFeedOptionsText({
|
|
398
|
+
text:
|
|
399
|
+
FEED_SORT_ORDER_OPTIONS.find((option: FeedSortOrderOption) => {
|
|
400
|
+
return option.value === data.options.sortOrder;
|
|
401
|
+
})?.label || "Newest first",
|
|
402
|
+
translate: data.translate,
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const selectedCount: number = data.options.eventTypes.length;
|
|
406
|
+
|
|
407
|
+
if (selectedCount === 0) {
|
|
408
|
+
return translateFeedOptionsText({
|
|
409
|
+
text: FEED_OPTIONS_TEXT.summaryAllEventTypes,
|
|
410
|
+
values: { sortOrder },
|
|
411
|
+
translate: data.translate,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return translateFeedOptionsText({
|
|
416
|
+
text:
|
|
417
|
+
data.eventTypeCount === 1
|
|
418
|
+
? FEED_OPTIONS_TEXT.summarySomeOfOneEventType
|
|
419
|
+
: FEED_OPTIONS_TEXT.summarySomeEventTypes,
|
|
420
|
+
values: {
|
|
421
|
+
sortOrder,
|
|
422
|
+
selected: selectedCount,
|
|
423
|
+
total: data.eventTypeCount,
|
|
424
|
+
},
|
|
425
|
+
translate: data.translate,
|
|
426
|
+
});
|
|
427
|
+
};
|