diff --git a/console/src/api/materialize/cluster/clusterList.test.sql.ts b/console/src/api/materialize/cluster/clusterList.test.sql.ts index 3d91f77fb4a74..2d34957b25c5b 100644 --- a/console/src/api/materialize/cluster/clusterList.test.sql.ts +++ b/console/src/api/materialize/cluster/clusterList.test.sql.ts @@ -7,7 +7,10 @@ // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0. -import { executeSqlHttp } from "~/test/sql/materializeSqlClient"; +import { + executeSqlHttp, + getMaterializeClient, +} from "~/test/sql/materializeSqlClient"; import { testdrive } from "~/test/sql/mzcompose"; import { buildClustersQuery } from "./clusterList"; @@ -88,4 +91,63 @@ describe("buildClusterSubscribe", () => { }, ]); }); + + it("correctly associates statuses with multiple replicas", async () => { + const client = await getMaterializeClient(); + + await testdrive( + `> CREATE CLUSTER test_multi (SIZE 'scale=1,workers=1', REPLICATION FACTOR 2);`, + ); + + try { + const { + rows: [cluster], + } = await client.query<{ id: string }>( + "SELECT id FROM mz_clusters WHERE name = 'test_multi'", + ); + const { rows: replicas } = await client.query<{ + id: string; + name: string; + }>( + `SELECT id, name FROM mz_cluster_replicas WHERE cluster_id = '${cluster.id}' ORDER BY name`, + ); + const [r1, r2] = replicas; + + // Wait for both replicas to have statuses + const waitForStatuses = async () => { + for (let i = 0; i < 30; i++) { + const { rows } = await client.query<{ count: string }>( + `SELECT COUNT(DISTINCT replica_id)::text as count FROM mz_cluster_replica_statuses WHERE replica_id IN ('${r1.id}', '${r2.id}')`, + ); + if (rows[0].count === "2") return; + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + throw new Error("Timed out waiting for replica statuses"); + }; + await waitForStatuses(); + + const query = buildClustersQuery({ + queryOwnership: false, + includeSystemObjects: false, + }).compile(); + const result = await executeSqlHttp(query); + + const testCluster = result.rows.find((r) => r.name === "test_multi"); + expect(testCluster).toBeDefined(); + expect(testCluster!.replicas).toHaveLength(2); + + const replicaR1 = testCluster!.replicas.find((r) => r.name === "r1"); + const replicaR2 = testCluster!.replicas.find((r) => r.name === "r2"); + + // Each replica's statuses should only contain its own replica_id + expect(replicaR1!.statuses).toEqual([ + expect.objectContaining({ replica_id: r1.id }), + ]); + expect(replicaR2!.statuses).toEqual([ + expect.objectContaining({ replica_id: r2.id }), + ]); + } finally { + await testdrive(`> DROP CLUSTER IF EXISTS test_multi;`); + } + }); }); diff --git a/console/src/api/materialize/cluster/clusterList.ts b/console/src/api/materialize/cluster/clusterList.ts index ef2d2ca28d080..11b21c9499c46 100644 --- a/console/src/api/materialize/cluster/clusterList.ts +++ b/console/src/api/materialize/cluster/clusterList.ts @@ -74,13 +74,13 @@ export const buildClustersQuery = ({ }>( eb .selectFrom("mz_cluster_replicas as cr") - .select([ + .select((replicaEb) => [ "cr.id", "cr.name", "cr.size", "cr.disk", jsonArrayFrom( - eb + replicaEb .selectFrom("mz_cluster_replica_statuses as crs_inner") .select([ "crs_inner.replica_id", @@ -89,7 +89,7 @@ export const buildClustersQuery = ({ "crs_inner.reason", "crs_inner.updated_at", ]) - .whereRef("crs_inner.replica_id", "=", "c.id"), + .whereRef("crs_inner.replica_id", "=", "cr.id"), ).as("statuses"), ]) .whereRef("cr.cluster_id", "=", "c.id")