@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
@@ -1,4 +1,11 @@
1
- import { afterEach, describe, expect, jest, test } from "@jest/globals";
1
+ import {
2
+ afterEach,
3
+ beforeEach,
4
+ describe,
5
+ expect,
6
+ jest,
7
+ test,
8
+ } from "@jest/globals";
2
9
  import "@testing-library/jest-dom";
3
10
  import {
4
11
  act,
@@ -105,6 +112,7 @@ import SortOrder from "../../../Types/BaseDatabase/SortOrder";
105
112
  import { DEFAULT_LIMIT } from "../../../Types/Database/LimitMax";
106
113
  import IconProp from "../../../Types/Icon/IconProp";
107
114
  import ObjectID from "../../../Types/ObjectID";
115
+ import { getSortOrderStorageKey } from "../../../UI/Components/Feed/useFeedOptions";
108
116
 
109
117
  interface RenderedFeedItem {
110
118
  key: string;
@@ -204,6 +212,15 @@ async function chooseAction(text: string): Promise<void> {
204
212
  await flush();
205
213
  }
206
214
 
215
+ /*
216
+ * The feed opens in the sort order the reader last chose, read from
217
+ * localStorage on mount, so one test's choice must not reorder the next
218
+ * test's requests.
219
+ */
220
+ beforeEach(() => {
221
+ window.localStorage.clear();
222
+ });
223
+
207
224
  afterEach(() => {
208
225
  cleanup();
209
226
  getListMock.mockReset();
@@ -279,6 +296,40 @@ describe("ScheduledMaintenanceFeedElement refresh", () => {
279
296
  expect(lastRenderedItems()[0]!.icon).toBe(IconProp.ArrowCircleRight);
280
297
  });
281
298
 
299
+ /*
300
+ * The refresh after a state change re-reads the view the reader is looking
301
+ * at. A reader who keeps this feed oldest first must not see it flip back
302
+ * to newest first because the page asked for a refresh.
303
+ */
304
+ test("a new refreshToken re-reads the feed in the reader's remembered order", async () => {
305
+ window.localStorage.setItem(
306
+ getSortOrderStorageKey("scheduled-maintenance"),
307
+ SortOrder.Ascending,
308
+ );
309
+ getListMock.mockResolvedValue(listResult([]) as never);
310
+
311
+ const view: RenderResult = renderFeed(0);
312
+ await flush();
313
+
314
+ view.rerender(
315
+ <ScheduledMaintenanceFeedElement
316
+ scheduledMaintenanceId={new ObjectID(EVENT_ID)}
317
+ refreshToken={1}
318
+ />,
319
+ );
320
+ await flush();
321
+
322
+ expect(getListMock).toHaveBeenCalledTimes(2);
323
+
324
+ for (const call of getListMock.mock.calls) {
325
+ const request: FeedListRequest = call[0] as FeedListRequest;
326
+
327
+ expect(request.sort).toEqual({ postedAt: SortOrder.Ascending });
328
+ expect(request.skip).toBe(0);
329
+ expect(request.limit).toBe(DEFAULT_LIMIT);
330
+ }
331
+ });
332
+
282
333
  test("re-rendering with the same token, or a new ObjectID for the same event, does not refetch", async () => {
283
334
  getListMock.mockResolvedValue(listResult([]) as never);
284
335
 
@@ -7,7 +7,14 @@ import {
7
7
  test,
8
8
  } from "@jest/globals";
9
9
  import "@testing-library/jest-dom";
10
- import { cleanup, render, screen, waitFor } from "@testing-library/react";
10
+ import {
11
+ cleanup,
12
+ fireEvent,
13
+ render,
14
+ screen,
15
+ waitFor,
16
+ within,
17
+ } from "@testing-library/react";
11
18
  import * as React from "react";
12
19
 
13
20
  /*
@@ -49,8 +56,16 @@ import ServiceLevelObjectiveFeed, {
49
56
  ServiceLevelObjectiveFeedEventType,
50
57
  } from "../../../Models/DatabaseModels/ServiceLevelObjectiveFeed";
51
58
  import { Yellow } from "../../../Types/BrandColors";
59
+ import Includes from "../../../Types/BaseDatabase/Includes";
60
+ import SortOrder from "../../../Types/BaseDatabase/SortOrder";
52
61
  import IconProp from "../../../Types/Icon/IconProp";
53
62
  import ObjectID from "../../../Types/ObjectID";
63
+ import {
64
+ DEFAULT_FEED_OPTIONS,
65
+ FEED_OPTIONS_TEXT,
66
+ FeedOptions,
67
+ getFeedOptionsSummary,
68
+ } from "../../../UI/Components/Feed/FeedOptions";
54
69
 
55
70
  /*
56
71
  * The SLO feed is the generic ResourceFeed, wired once in
@@ -72,6 +87,8 @@ beforeEach(() => {
72
87
  mockGetListResponse = (): Promise<unknown> => {
73
88
  return Promise.resolve({ data: [], count: 0 });
74
89
  };
90
+ // A remembered sort order would change what the first request asks for.
91
+ window.localStorage.clear();
75
92
  });
76
93
 
77
94
  afterEach(() => {
@@ -94,6 +111,8 @@ describe("getSloResourceFeedProps", () => {
94
111
  );
95
112
  // Both the page and any embedded feed get the SLO icons from here.
96
113
  expect(props.getIcon).toBe(getSloFeedEventIcon);
114
+ // ...and the event types the Filter & Sort checklist offers.
115
+ expect(props.eventTypes).toEqual(ALL_EVENT_TYPES);
97
116
  });
98
117
 
99
118
  test("lets an embedded feed use its own title and description", () => {
@@ -254,6 +273,9 @@ describe("SloFeed", () => {
254
273
  expect(request.query.serviceLevelObjectiveId).toBe(SLO_ID);
255
274
  expect(request.select.serviceLevelObjectiveFeedEventType).toBe(true);
256
275
  expect(request.select.postedAt).toBe(true);
276
+ // Untouched, Filter & Sort adds nothing to the query and keeps the order.
277
+ expect(Object.keys(request.query)).toEqual(["serviceLevelObjectiveId"]);
278
+ expect(request.sort).toEqual({ postedAt: SortOrder.Descending });
257
279
  });
258
280
 
259
281
  test("renders what the SLO's history says, under the default title", async () => {
@@ -286,4 +308,87 @@ describe("SloFeed", () => {
286
308
 
287
309
  expect(screen.getByText("Recent activity")).toBeInTheDocument();
288
310
  });
311
+
312
+ test("offers Filter & Sort by its label, described by what the feed shows, and filters on the SLO's own event type column", async () => {
313
+ type GetExpectedSummary = (options: FeedOptions) => string;
314
+
315
+ /*
316
+ * Built by the same function the button uses, so the wording can change
317
+ * freely - what is pinned is that the description follows the feed's
318
+ * live order and filter, counted over the SLO's own event types.
319
+ */
320
+ const getExpectedSummary: GetExpectedSummary = (
321
+ options: FeedOptions,
322
+ ): string => {
323
+ return getFeedOptionsSummary({
324
+ options: options,
325
+ eventTypeCount: ALL_EVENT_TYPES.length,
326
+ });
327
+ };
328
+
329
+ const statusChanged: string =
330
+ ServiceLevelObjectiveFeedEventType.StatusChanged;
331
+
332
+ render(<SloFeed sloId={SLO_ID} />);
333
+
334
+ await waitFor(() => {
335
+ expect(
336
+ screen.getByText("No activity has been recorded for this SLO yet."),
337
+ ).toBeInTheDocument();
338
+ });
339
+
340
+ /*
341
+ * The button is named by its visible label alone; what the feed is
342
+ * showing is read out as its description, so a screen reader says each
343
+ * once.
344
+ */
345
+ const optionsButton: HTMLElement = screen.getByRole("button", {
346
+ name: "Filter & Sort",
347
+ });
348
+
349
+ expect(optionsButton).toHaveAccessibleDescription(
350
+ getExpectedSummary(DEFAULT_FEED_OPTIONS),
351
+ );
352
+
353
+ fireEvent.click(optionsButton);
354
+
355
+ const panel: HTMLElement = screen.getByRole("dialog", {
356
+ name: FEED_OPTIONS_TEXT.panelLabel,
357
+ });
358
+
359
+ // StatusChanged is one of the SLO's own events, not a shared one.
360
+ fireEvent.click(
361
+ within(panel).getByTestId(`feed-options-event-type-${statusChanged}`),
362
+ );
363
+
364
+ await waitFor(() => {
365
+ expect(mockGetListCalls).toHaveLength(2);
366
+ });
367
+
368
+ const request: GetListRequest = mockGetListCalls[1]!;
369
+ const query: Record<string, unknown> = request["query"] as Record<
370
+ string,
371
+ unknown
372
+ >;
373
+ const eventTypeFilter: unknown =
374
+ query["serviceLevelObjectiveFeedEventType"];
375
+
376
+ expect(Object.keys(query).sort()).toEqual(
377
+ ["serviceLevelObjectiveId", "serviceLevelObjectiveFeedEventType"].sort(),
378
+ );
379
+ expect(query["serviceLevelObjectiveId"]).toBe(SLO_ID);
380
+ expect(eventTypeFilter).toBeInstanceOf(Includes);
381
+ expect((eventTypeFilter as Includes).values).toEqual([statusChanged]);
382
+ expect(request["sort"]).toEqual({ postedAt: SortOrder.Descending });
383
+
384
+ // The name stays put with a count on the button; the description moves.
385
+ expect(
386
+ screen.getByRole("button", { name: "Filter & Sort" }),
387
+ ).toHaveAccessibleDescription(
388
+ getExpectedSummary({
389
+ sortOrder: SortOrder.Descending,
390
+ eventTypes: [statusChanged],
391
+ }),
392
+ );
393
+ });
289
394
  });