@oneuptime/common 14.0.6 → 14.0.7

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 (127) hide show
  1. package/Models/DatabaseModels/InventoryItem.ts +23 -12
  2. package/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.ts +217 -0
  3. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  4. package/Server/Services/AlertService.ts +32 -36
  5. package/Server/Services/IncidentAlertService.ts +609 -2
  6. package/Server/Services/IncidentService.ts +134 -44
  7. package/Server/Services/InventoryItemService.ts +38 -0
  8. package/Server/Services/IoTDeviceCredentialService.ts +13 -4
  9. package/Server/Services/ScheduledMaintenanceService.ts +50 -46
  10. package/Server/Services/UserNotificationRuleService.ts +55 -53
  11. package/Server/Services/WorkspaceNotificationSummaryService.ts +38 -20
  12. package/Server/Utils/AffectedResources/LinkedAffectedResources.ts +576 -0
  13. package/Server/Utils/Alert/AlertStateChangeAuthorization.ts +129 -0
  14. package/Server/Utils/Inventory/InventoryOverviewAggregation.ts +111 -0
  15. package/Server/Utils/Response.ts +31 -0
  16. package/Server/Utils/Topology/TopologyConcurrencyLimiter.ts +229 -0
  17. package/Server/Utils/Topology/TopologyQueries.ts +1350 -0
  18. package/Server/Utils/Topology/TopologyRequest.ts +463 -0
  19. package/Server/Utils/Topology/TopologyResponseCache.ts +183 -0
  20. package/Server/Utils/Topology/TopologySql.ts +1024 -0
  21. package/Tests/App/Dashboard/AcknowledgeAlertsOnDeclare.test.ts +369 -0
  22. package/Tests/App/Dashboard/AffectedResourcesSlo.test.tsx +1 -1
  23. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +510 -123
  24. package/Tests/App/Dashboard/AlertsTableAffectedResources.test.tsx +490 -0
  25. package/Tests/App/Dashboard/ChangeAlertStateDeclareIncident.test.tsx +886 -0
  26. package/Tests/App/Dashboard/CreateReviewStepRoleSummary.test.tsx +1250 -0
  27. package/Tests/App/Dashboard/CreateReviewStepSelectionSummary.test.tsx +738 -0
  28. package/Tests/App/Dashboard/DeclareIncidentFromAlert.test.ts +417 -0
  29. package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +1948 -229
  30. package/Tests/App/Dashboard/EntityDetailPanelLifecycle.test.tsx +493 -0
  31. package/Tests/App/Dashboard/EntityDetailPanelNavigation.test.tsx +439 -0
  32. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +292 -1
  33. package/Tests/App/Dashboard/EventStatusPanel.test.tsx +798 -2
  34. package/Tests/App/Dashboard/FetchStateAndSeverityElements.test.tsx +669 -0
  35. package/Tests/App/Dashboard/FetchUsers.test.tsx +230 -0
  36. package/Tests/App/Dashboard/IncidentCreateAcknowledgeAlerts.test.tsx +1611 -0
  37. package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +1365 -57
  38. package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +964 -23
  39. package/Tests/App/Dashboard/InventoryRelationships.test.tsx +885 -0
  40. package/Tests/App/Dashboard/ServiceMapDatabaseLink.test.tsx +201 -15
  41. package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +508 -36
  42. package/Tests/App/Dashboard/TopologyDataLifecycle.test.tsx +583 -0
  43. package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +982 -218
  44. package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +818 -114
  45. package/Tests/Server/Infrastructure/Postgres/RenameInventoryItemMigration.test.ts +90 -21
  46. package/Tests/Server/Services/DomainService.test.ts +525 -0
  47. package/Tests/Server/Services/IncidentAlertAcknowledgeOnDeclare.test.ts +2630 -0
  48. package/Tests/Server/Services/IncidentAlertPostgres.test.ts +1625 -1
  49. package/Tests/Server/Services/IncidentDeclareAcknowledgeAlerts.test.ts +2189 -0
  50. package/Tests/Server/Services/InventoryItemServiceOverviewCounts.test.ts +345 -0
  51. package/Tests/Server/Services/InventoryOverviewPostgres.test.ts +254 -0
  52. package/Tests/Server/Services/IoTDeviceCredentialService.test.ts +819 -0
  53. package/Tests/Server/Services/LinkedAffectedResourcesCreatedFeed.test.ts +420 -0
  54. package/Tests/Server/Services/SloAffectedResourceCreatedFeed.test.ts +92 -36
  55. package/Tests/Server/Utils/AffectedResources/LinkedAffectedResources.test.ts +858 -0
  56. package/Tests/Server/Utils/Alert/AlertStateChangeAuthorization.test.ts +1172 -0
  57. package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigration.test.ts +386 -0
  58. package/Tests/Server/Utils/Topology/InventoryItemEntityKeyIndexMigrationPostgres.test.ts +430 -0
  59. package/Tests/Server/Utils/Topology/TopologyActivityParityPostgres.test.ts +279 -0
  60. package/Tests/Server/Utils/Topology/TopologyConcurrencyLimiter.test.ts +534 -0
  61. package/Tests/Server/Utils/Topology/TopologyEstateGenerator.ts +624 -0
  62. package/Tests/Server/Utils/Topology/TopologyJsonResponse.test.ts +90 -0
  63. package/Tests/Server/Utils/Topology/TopologyQueries.test.ts +1634 -0
  64. package/Tests/Server/Utils/Topology/TopologyQueriesDifferentialPostgres.test.ts +683 -0
  65. package/Tests/Server/Utils/Topology/TopologyQueriesPostgres.test.ts +2624 -0
  66. package/Tests/Server/Utils/Topology/TopologyReference.ts +1018 -0
  67. package/Tests/Server/Utils/Topology/TopologyRequest.test.ts +643 -0
  68. package/Tests/Server/Utils/Topology/TopologyResponseCache.test.ts +305 -0
  69. package/Tests/Server/Utils/Topology/TopologySql.test.ts +973 -0
  70. package/Tests/Types/Topology/TopologyTypeRules.test.ts +176 -0
  71. package/Tests/Utils/Incident/IncidentFromAlerts.test.ts +302 -0
  72. package/Types/Incident/IncidentAlertLink.ts +19 -0
  73. package/Types/Telemetry/InventoryLiveness.ts +26 -0
  74. package/Types/Topology/TopologyApi.ts +428 -0
  75. package/Types/Topology/TopologyTypeRules.ts +191 -0
  76. package/Utils/Incident/IncidentFromAlerts.ts +78 -0
  77. package/build/dist/Models/DatabaseModels/InventoryItem.js +20 -11
  78. package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
  79. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js +165 -0
  80. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795200000000-AddInventoryItemProjectEntityKeyIndex.js.map +1 -0
  81. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  82. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  83. package/build/dist/Server/Services/AlertService.js +21 -33
  84. package/build/dist/Server/Services/AlertService.js.map +1 -1
  85. package/build/dist/Server/Services/IncidentAlertService.js +457 -3
  86. package/build/dist/Server/Services/IncidentAlertService.js.map +1 -1
  87. package/build/dist/Server/Services/IncidentService.js +96 -42
  88. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  89. package/build/dist/Server/Services/InventoryItemService.js +31 -0
  90. package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
  91. package/build/dist/Server/Services/IoTDeviceCredentialService.js +12 -5
  92. package/build/dist/Server/Services/IoTDeviceCredentialService.js.map +1 -1
  93. package/build/dist/Server/Services/ScheduledMaintenanceService.js +37 -40
  94. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  95. package/build/dist/Server/Services/UserNotificationRuleService.js +55 -51
  96. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  97. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js +42 -25
  98. package/build/dist/Server/Services/WorkspaceNotificationSummaryService.js.map +1 -1
  99. package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js +405 -0
  100. package/build/dist/Server/Utils/AffectedResources/LinkedAffectedResources.js.map +1 -0
  101. package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js +112 -0
  102. package/build/dist/Server/Utils/Alert/AlertStateChangeAuthorization.js.map +1 -0
  103. package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js +81 -0
  104. package/build/dist/Server/Utils/Inventory/InventoryOverviewAggregation.js.map +1 -0
  105. package/build/dist/Server/Utils/Response.js +30 -0
  106. package/build/dist/Server/Utils/Response.js.map +1 -1
  107. package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js +178 -0
  108. package/build/dist/Server/Utils/Topology/TopologyConcurrencyLimiter.js.map +1 -0
  109. package/build/dist/Server/Utils/Topology/TopologyQueries.js +884 -0
  110. package/build/dist/Server/Utils/Topology/TopologyQueries.js.map +1 -0
  111. package/build/dist/Server/Utils/Topology/TopologyRequest.js +275 -0
  112. package/build/dist/Server/Utils/Topology/TopologyRequest.js.map +1 -0
  113. package/build/dist/Server/Utils/Topology/TopologyResponseCache.js +146 -0
  114. package/build/dist/Server/Utils/Topology/TopologyResponseCache.js.map +1 -0
  115. package/build/dist/Server/Utils/Topology/TopologySql.js +688 -0
  116. package/build/dist/Server/Utils/Topology/TopologySql.js.map +1 -0
  117. package/build/dist/Types/Incident/IncidentAlertLink.js +17 -0
  118. package/build/dist/Types/Incident/IncidentAlertLink.js.map +1 -1
  119. package/build/dist/Types/Telemetry/InventoryLiveness.js +23 -0
  120. package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -1
  121. package/build/dist/Types/Topology/TopologyApi.js +55 -0
  122. package/build/dist/Types/Topology/TopologyApi.js.map +1 -0
  123. package/build/dist/Types/Topology/TopologyTypeRules.js +165 -0
  124. package/build/dist/Types/Topology/TopologyTypeRules.js.map +1 -0
  125. package/build/dist/Utils/Incident/IncidentFromAlerts.js +38 -0
  126. package/build/dist/Utils/Incident/IncidentFromAlerts.js.map +1 -1
  127. package/package.json +1 -1
@@ -17,7 +17,7 @@ import {
17
17
  waitFor,
18
18
  within,
19
19
  } from "@testing-library/react";
20
- import React, { ReactElement, ReactNode } from "react";
20
+ import React, { FunctionComponent, ReactElement, ReactNode } from "react";
21
21
  import getJestMockFunction, { MockFunction } from "../../MockType";
22
22
 
23
23
  /*
@@ -318,6 +318,7 @@ import IncidentState from "../../../Models/DatabaseModels/IncidentState";
318
318
  import IncidentStateTimeline from "../../../Models/DatabaseModels/IncidentStateTimeline";
319
319
  import Monitor from "../../../Models/DatabaseModels/Monitor";
320
320
  import Probe from "../../../Models/DatabaseModels/Probe";
321
+ import ServiceLevelObjective from "../../../Models/DatabaseModels/ServiceLevelObjective";
321
322
  import AIRunHumanVerdict from "../../../Types/AI/AIRunHumanVerdict";
322
323
  import AIRunStatus from "../../../Types/AI/AIRunStatus";
323
324
  import Route from "../../../Types/API/Route";
@@ -326,6 +327,7 @@ import OneUptimeDate from "../../../Types/Date";
326
327
  import ObjectID from "../../../Types/ObjectID";
327
328
  import StatusPageSubscriberNotificationStatus from "../../../Types/StatusPage/StatusPageSubscriberNotificationStatus";
328
329
  import { DetailStyle } from "../../../UI/Components/Detail/Detail";
330
+ import FormFieldSchemaType from "../../../UI/Components/Forms/Types/FormFieldSchemaType";
329
331
  import Navigation from "../../../UI/Utils/Navigation";
330
332
 
331
333
  const EVENT_ID: string = "11111111-1111-4111-8111-111111111111";
@@ -1849,6 +1851,90 @@ describe.each([
1849
1851
  });
1850
1852
  });
1851
1853
 
1854
+ /*
1855
+ * The Affected Resources card. CardModelDetail is stubbed, so the card's
1856
+ * display field is read from its latest props and rendered here the way the
1857
+ * real card renders it. AffectedResourcesDisplay is stubbed for the page too,
1858
+ * so the element its getElement returns is re-rendered with the real
1859
+ * display: what is asserted is what a reader of the card would see.
1860
+ */
1861
+ interface ResourcesFormFieldProps {
1862
+ field?: Record<string, unknown>;
1863
+ fieldType?: FormFieldSchemaType;
1864
+ showIf?: (values: unknown) => boolean;
1865
+ getCustomElement?: (values: unknown, elementProps: unknown) => ReactElement;
1866
+ }
1867
+
1868
+ interface ResourcesCardProps {
1869
+ cardProps: { description?: string };
1870
+ formFields: Array<ResourcesFormFieldProps>;
1871
+ modelDetailProps: { fields: Array<DetailFieldProps> };
1872
+ }
1873
+
1874
+ type ResourcesCardFunction = () => ResourcesCardProps;
1875
+
1876
+ const resourcesCard: ResourcesCardFunction = (): ResourcesCardProps => {
1877
+ return latestProps<ResourcesCardProps>("CardModelDetail:Affected Resources");
1878
+ };
1879
+
1880
+ type ResourcesDisplayFieldFunction = () => DetailFieldProps;
1881
+
1882
+ const resourcesDisplayField: ResourcesDisplayFieldFunction =
1883
+ (): DetailFieldProps => {
1884
+ const displayField: DetailFieldProps | undefined =
1885
+ resourcesCard().modelDetailProps.fields.find(
1886
+ (field: DetailFieldProps): boolean => {
1887
+ return Boolean(field.getElement);
1888
+ },
1889
+ );
1890
+
1891
+ if (!displayField) {
1892
+ throw new Error("The Affected Resources card has no display field");
1893
+ }
1894
+
1895
+ return displayField;
1896
+ };
1897
+
1898
+ type RenderResourcesDisplayFunction = (item: Incident | Alert) => RenderResult;
1899
+
1900
+ const renderResourcesDisplay: RenderResourcesDisplayFunction = (
1901
+ item: Incident | Alert,
1902
+ ): RenderResult => {
1903
+ const RealAffectedResourcesDisplay: FunctionComponent<
1904
+ Record<string, unknown>
1905
+ > = (
1906
+ jest.requireActual(
1907
+ "../../../../App/FeatureSet/Dashboard/src/Components/AffectedResources/AffectedResourcesDisplay",
1908
+ ) as { default: FunctionComponent<Record<string, unknown>> }
1909
+ ).default;
1910
+
1911
+ const display: ReactElement = resourcesDisplayField().getElement!(item);
1912
+
1913
+ return render(
1914
+ <RealAffectedResourcesDisplay
1915
+ {...(display.props as Record<string, unknown>)}
1916
+ />,
1917
+ );
1918
+ };
1919
+
1920
+ type NamedMonitorFunction = (name: string) => Monitor;
1921
+
1922
+ const namedMonitor: NamedMonitorFunction = (name: string): Monitor => {
1923
+ const monitor: Monitor = new Monitor();
1924
+ monitor.id = new ObjectID(MONITOR_ID);
1925
+ monitor.name = name;
1926
+ return monitor;
1927
+ };
1928
+
1929
+ type NamedSloFunction = (name: string) => ServiceLevelObjective;
1930
+
1931
+ const namedSlo: NamedSloFunction = (name: string): ServiceLevelObjective => {
1932
+ const slo: ServiceLevelObjective = new ServiceLevelObjective();
1933
+ slo.id = new ObjectID("dddddddd-dddd-4ddd-8ddd-ddddddddddd1");
1934
+ slo.name = name;
1935
+ return slo;
1936
+ };
1937
+
1852
1938
  describe("incident-only behaviour", () => {
1853
1939
  test("the header names who declared the incident once the details card has read it", async () => {
1854
1940
  serve(INCIDENT_CASE, {
@@ -2301,6 +2387,42 @@ describe("incident-only behaviour", () => {
2301
2387
  })?.value,
2302
2388
  ).toBe("Probe AP");
2303
2389
  });
2390
+
2391
+ /*
2392
+ * The alert card's counterpart: Incident.monitors is ManyToMany and was
2393
+ * always read and shown here, so the two cards agree on what an affected
2394
+ * monitor looks like.
2395
+ */
2396
+ test("the Affected Resources card reads the incident's monitors and lists them under Monitors", async () => {
2397
+ serve(INCIDENT_CASE, {
2398
+ timeline: REOPENED_TIMELINE,
2399
+ title: "Checkout slow",
2400
+ });
2401
+
2402
+ INCIDENT_CASE.renderPage();
2403
+ await waitForPage();
2404
+
2405
+ expect(resourcesDisplayField().field?.["monitors"]).toEqual({
2406
+ name: true,
2407
+ _id: true,
2408
+ });
2409
+
2410
+ const incident: Incident = new Incident();
2411
+ incident.monitors = [namedMonitor("Developer portal")];
2412
+
2413
+ const view: RenderResult = renderResourcesDisplay(incident);
2414
+ const grid: HTMLElement = within(view.container).getByTestId(
2415
+ "affected-resources-grid",
2416
+ );
2417
+
2418
+ expect(within(view.container).queryByText("No resources affected.")).toBe(
2419
+ null,
2420
+ );
2421
+ expect(within(grid).getByText("Monitors")).toBeInTheDocument();
2422
+ expect(within(grid).getByTestId("monitor-element")).toHaveTextContent(
2423
+ "Developer portal",
2424
+ );
2425
+ });
2304
2426
  });
2305
2427
 
2306
2428
  describe("alert-only behaviour", () => {
@@ -2447,4 +2569,173 @@ describe("alert-only behaviour", () => {
2447
2569
  ).toEqual(["Created"]);
2448
2570
  }
2449
2571
  });
2572
+
2573
+ /*
2574
+ * An alert is raised on one monitor (Alert.monitor, singular, set when the
2575
+ * alert is created), and the alert's "created" feed item lists that
2576
+ * monitor under Resources Affected. The Affected Resources card used to
2577
+ * hide monitors and never read the relation, so an alert raised on the
2578
+ * "Developer portal" monitor said "No resources affected." right beside a
2579
+ * feed item naming that monitor.
2580
+ */
2581
+ describe("the Affected Resources card", () => {
2582
+ beforeEach(async () => {
2583
+ serve(ALERT_CASE, {
2584
+ timeline: REOPENED_TIMELINE,
2585
+ title: "Checkout slow",
2586
+ });
2587
+
2588
+ ALERT_CASE.renderPage();
2589
+ await waitForPage();
2590
+ });
2591
+
2592
+ test("reads the alert's monitor alongside the other resources", () => {
2593
+ const select: Record<string, unknown> =
2594
+ resourcesDisplayField().field || {};
2595
+
2596
+ expect(select["monitor"]).toEqual({ name: true, _id: true });
2597
+ expect(select).toEqual(
2598
+ expect.objectContaining({
2599
+ hosts: { name: true, _id: true },
2600
+ services: { name: true, _id: true, serviceColor: true },
2601
+ serviceLevelObjectives: { name: true, _id: true },
2602
+ }),
2603
+ );
2604
+ });
2605
+
2606
+ test("lists the monitor an alert was raised on under Monitors, never 'No resources affected.'", () => {
2607
+ const alert: Alert = new Alert();
2608
+ alert.monitor = namedMonitor("Developer portal");
2609
+
2610
+ const view: RenderResult = renderResourcesDisplay(alert);
2611
+
2612
+ expect(
2613
+ within(view.container).queryByText("No resources affected."),
2614
+ ).toBeNull();
2615
+
2616
+ const grid: HTMLElement = within(view.container).getByTestId(
2617
+ "affected-resources-grid",
2618
+ );
2619
+
2620
+ expect(grid.children).toHaveLength(1);
2621
+ expect(within(grid).getByText("Monitors")).toBeInTheDocument();
2622
+ expect(
2623
+ within(grid).getByTestId("affected-resource-item"),
2624
+ ).toHaveAttribute("title", "Developer portal");
2625
+ expect(within(grid).getByTestId("monitor-element")).toHaveTextContent(
2626
+ "Developer portal",
2627
+ );
2628
+ expect(within(view.container).getByText("1 resource")).toBeVisible();
2629
+ });
2630
+
2631
+ test("shows both the monitor and an SLO, the monitor first", () => {
2632
+ const alert: Alert = new Alert();
2633
+ alert.monitor = namedMonitor("Developer portal");
2634
+ alert.serviceLevelObjectives = [namedSlo("Checkout availability")];
2635
+
2636
+ const view: RenderResult = renderResourcesDisplay(alert);
2637
+ const grid: HTMLElement = within(view.container).getByTestId(
2638
+ "affected-resources-grid",
2639
+ );
2640
+ const categories: Array<HTMLElement> = Array.from(
2641
+ grid.children,
2642
+ ) as Array<HTMLElement>;
2643
+
2644
+ expect(categories).toHaveLength(2);
2645
+ expect(within(categories[0]!).getByText("Monitors")).toBeVisible();
2646
+ expect(
2647
+ within(categories[0]!).getByTestId("monitor-element"),
2648
+ ).toHaveTextContent("Developer portal");
2649
+ expect(within(categories[1]!).getByText("SLOs")).toBeVisible();
2650
+ expect(
2651
+ within(categories[1]!).getByText("Checkout availability"),
2652
+ ).toBeVisible();
2653
+ expect(within(view.container).getByText("2 resources")).toBeVisible();
2654
+ expect(
2655
+ within(view.container).getByText("across 2 categories"),
2656
+ ).toBeVisible();
2657
+ });
2658
+
2659
+ test("an alert with no monitor and nothing else is still 'No resources affected.'", () => {
2660
+ const view: RenderResult = renderResourcesDisplay(new Alert());
2661
+
2662
+ expect(
2663
+ within(view.container).getByText("No resources affected."),
2664
+ ).toBeVisible();
2665
+ expect(
2666
+ within(view.container).queryByTestId("affected-resources-grid"),
2667
+ ).toBeNull();
2668
+ expect(within(view.container).queryByTestId("monitor-element")).toBe(
2669
+ null,
2670
+ );
2671
+ });
2672
+
2673
+ /*
2674
+ * The monitor is shown, never edited: ModelForm loads and saves only the
2675
+ * fields registered here, so leaving the monitor out means saving the
2676
+ * card can neither move the alert to another monitor nor clear it.
2677
+ */
2678
+ test("its edit form never loads the monitor, so saving cannot change or clear it", () => {
2679
+ const formFields: Array<ResourcesFormFieldProps> =
2680
+ resourcesCard().formFields;
2681
+ const keysOf: (field: ResourcesFormFieldProps) => Array<string> = (
2682
+ field: ResourcesFormFieldProps,
2683
+ ): Array<string> => {
2684
+ return Object.keys(field.field || {});
2685
+ };
2686
+
2687
+ const formKeys: Array<string> = formFields.flatMap(keysOf);
2688
+
2689
+ expect(formKeys).not.toContain("monitor");
2690
+ expect(formKeys).not.toContain("monitors");
2691
+
2692
+ const visibleFields: Array<ResourcesFormFieldProps> = formFields.filter(
2693
+ (field: ResourcesFormFieldProps): boolean => {
2694
+ return !field.showIf || field.showIf({});
2695
+ },
2696
+ );
2697
+ const hiddenFields: Array<ResourcesFormFieldProps> = formFields.filter(
2698
+ (field: ResourcesFormFieldProps): boolean => {
2699
+ return Boolean(field.showIf) && !field.showIf!({});
2700
+ },
2701
+ );
2702
+
2703
+ // The one visible field is the resource picker, registered on hosts.
2704
+ expect(visibleFields.map(keysOf)).toEqual([["hosts"]]);
2705
+ expect(visibleFields[0]!.fieldType).toBe(
2706
+ FormFieldSchemaType.CustomComponent,
2707
+ );
2708
+ // Hidden registrations so the form still loads the other relations.
2709
+ expect(hiddenFields.flatMap(keysOf)).toEqual([
2710
+ "kubernetesClusters",
2711
+ "dockerHosts",
2712
+ "podmanHosts",
2713
+ "proxmoxClusters",
2714
+ "vmwareVCenters",
2715
+ "cephClusters",
2716
+ "dockerSwarmClusters",
2717
+ "iotFleets",
2718
+ "databaseServers",
2719
+ "services",
2720
+ ]);
2721
+
2722
+ // The picker it renders offers no monitors to attach either.
2723
+ render(visibleFields[0]!.getCustomElement!({}, {}));
2724
+
2725
+ const picker: { monitors?: unknown; resourceTypes?: Array<string> } =
2726
+ latestProps<{ monitors?: unknown; resourceTypes?: Array<string> }>(
2727
+ "AffectedResourcesPicker",
2728
+ );
2729
+
2730
+ expect(picker.monitors).toBeUndefined();
2731
+ expect(picker.resourceTypes).toContain("Host");
2732
+ expect(picker.resourceTypes).not.toContain("Monitor");
2733
+ });
2734
+
2735
+ test("says it covers monitors", () => {
2736
+ expect(resourcesCard().cardProps.description).toBe(
2737
+ "Monitors, services, infrastructure and SLOs this alert affects.",
2738
+ );
2739
+ });
2740
+ });
2450
2741
  });