CNS-130 Add Cluster Replica sub-rows to cluster table - #38150
Open
jdonelson wants to merge 8 commits into
Open
Conversation
…ing is now consistent across row types; clu sters with single replica are now expandable; updated tests.
Contributor
def-
reviewed
Aug 11, 2026
Contributor
There was a problem hiding this comment.
Here's some extra tests, all failing currently on this PR:
diff --git a/console/src/platform/clusters/ClustersList.test.tsx b/console/src/platform/clusters/ClustersList.test.tsx
index 1a2ae37a88..5f5f1a954f 100644
--- a/console/src/platform/clusters/ClustersList.test.tsx
+++ b/console/src/platform/clusters/ClustersList.test.tsx
@@ -7,7 +7,7 @@
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0.
-import { screen, within } from "@testing-library/react";
+import { screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
@@ -15,7 +15,7 @@ import { Cluster, Replica } from "~/api/materialize/cluster/clusterList";
import { getStore } from "~/jotai";
import { allClusters } from "~/store/allClusters";
import { mockSubscribeState } from "~/test/mockSubscribe";
-import { renderComponent } from "~/test/utils";
+import { renderComponent, RenderWithPathname } from "~/test/utils";
import {
formatDate,
FRIENDLY_DATETIME_FORMAT_NO_SECONDS,
@@ -30,6 +30,16 @@ vi.mock("~/hooks/useFlags", () => ({
useFlags: () => ({ "usage-metrics-in-cluster-list-CNS121": true }),
}));
+vi.mock("./queries", async () => {
+ const actual = await vi.importActual<typeof import("./queries")>("./queries");
+ return {
+ ...actual,
+ useAvailableClusterSizes: () => ({ data: undefined }),
+ useMaxReplicasPerCluster: () => ({ data: undefined }),
+ useOwners: () => ({ isOwner: () => true }),
+ };
+});
+
// The list opens a websocket subscribe to surface out-of-memory warnings.
// Stubbing it keeps these tests off the socket and, because it reports no
// error, leaves the `lastStatusChange` column visible.
@@ -92,7 +102,11 @@ const buildCluster = (overrides: Partial<Cluster> = {}): Cluster => ({
const renderClustersList = async (clusters: Cluster[]) => {
getStore().set(allClusters, mockSubscribeState({ data: clusters }));
- return renderComponent(<ClustersListPage />);
+ return renderComponent(
+ <RenderWithPathname>
+ <ClustersListPage />
+ </RenderWithPathname>,
+ );
};
/**
@@ -109,6 +123,16 @@ const expandCluster = async (
await user.keyboard("{Enter}");
};
+const tabTo = async (
+ user: ReturnType<typeof userEvent.setup>,
+ target: HTMLElement,
+) => {
+ for (let i = 0; i < 10 && document.activeElement !== target; i += 1) {
+ await user.tab();
+ }
+ expect(target).toHaveFocus();
+};
+
/** Text of every visible cell in the row containing `rowLabel`. */
const cellsForRow = (rowLabel: string) => {
const row = screen.getByText(rowLabel).closest("tr");
@@ -153,6 +177,36 @@ describe("ClustersList replica rows", () => {
expect(cellsForRow("r2")[2]).toBe("100cc");
});
+ it("navigates when the cluster link is activated with the keyboard", async () => {
+ const user = userEvent.setup();
+ await renderClustersList([buildCluster()]);
+
+ await tabTo(user, screen.getByRole("link", { name: "compute" }));
+
+ await user.keyboard("{Enter}");
+
+ expect(screen.getByTestId("pathname")).toHaveTextContent("/u1/compute");
+ });
+
+ it("keeps replicas expandable when searching by cluster name", async () => {
+ const user = userEvent.setup();
+ await renderClustersList([
+ buildCluster(),
+ buildCluster({ id: "u2", name: "serving", replicas: [] }),
+ ]);
+
+ await user.type(
+ screen.getByPlaceholderText("Search clusters..."),
+ "compute",
+ );
+ await waitFor(() => {
+ expect(screen.queryByText("serving")).not.toBeInTheDocument();
+ });
+ await expandCluster(user, "compute");
+
+ expect(screen.getByText("r1")).toBeInTheDocument();
+ });
+
it("renders the most recent status when a replica has several processes", async () => {
const user = userEvent.setup();
const newest = "2024-03-07T09:00:00.000Z";
@@ -250,4 +304,21 @@ describe("ClustersList replica rows", () => {
expect(cells[2]).toBe("50cc, 100cc");
expect(cells[3]).toBe(formatted(STATUS_UPDATED_AT));
});
+
+ it("opens the action menu when its button is activated with the keyboard", async () => {
+ const user = userEvent.setup();
+ await renderClustersList([buildCluster()]);
+
+ const actionButton = await screen.findByRole("button", {
+ name: "More actions",
+ });
+ await tabTo(user, actionButton);
+
+ await user.keyboard("[Space]");
+
+ expect(actionButton).toHaveAttribute("aria-expanded", "true");
+ expect(
+ screen.getByRole("menuitem", { name: "Alter cluster" }),
+ ).toBeVisible();
+ });
});| const row = screen.getByText(clusterName).closest("tr"); | ||
| if (!row) throw new Error(`no row found for cluster "${clusterName}"`); | ||
| row.focus(); | ||
| await user.keyboard("{Enter}"); |
Contributor
There was a problem hiding this comment.
Doesn't this break the existing enter (also space) handling on this page?
Contributor
Author
There was a problem hiding this comment.
Aaaahhh keyboard navigation! 🤦 Thanks for calling it out.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Motivation
Implements CNS-130
To support https://linear.app/materializeinc/issue/CNS-121/add-memory-swap-and-cpu-metrics-in-the-clusters-list
Usage metrics are provided per replica. In order to provide meaningful data, the cluster table needs to display data at the replica level of granularity.
Description
Each row in the cluster table can be clicked to reveal child rows. Each child row displays the data for each replica that has been created of the selected cluster. While this change is not meaningful in its current state, this is a dependency for the goal state of providing usage metrics.
A feature flag usage-metrics-in-cluster-list-CNS121 has been put in place to hide this change until the full project (displaying the metrics) has been completed.
Verification
Added unit tests to confirm behavior of the replica rows. Confirmed the behavior locally while using Organization Impersonation. Also confirmed that behavior is hidden when feature flag is set.
Feature Flag = true

Feature Flag = false
