Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 44 additions & 13 deletions console/src/components/Table/TablePagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,39 @@ import { MaterializeTheme } from "~/theme";

import { TablePaginationProps } from "./tableTypes";

export const TablePagination = <TData,>({
table,
export interface PaginationControlsProps {
/** Zero-based. */
pageIndex: number;
pageSize: number;
pageCount: number;
totalItems: number;
itemLabel?: React.ReactNode;
onPrevious: () => void;
onNext: () => void;
}

/** "Showing 1-30 of 120" with previous and next buttons. Hidden for one page. */
export const PaginationControls = ({
pageIndex,
pageSize,
pageCount,
totalItems,
itemLabel = "results",
}: TablePaginationProps<TData>) => {
onPrevious,
onNext,
}: PaginationControlsProps) => {
const { colors } = useTheme<MaterializeTheme>();

const pageCount = table.getPageCount();
const totalRows = table.getFilteredRowModel().rows.length;
const { pageIndex, pageSize } = table.getState().pagination;

if (pageCount <= 1) return null;

const from = pageIndex * pageSize + 1;
const to = Math.min((pageIndex + 1) * pageSize, totalRows);
const to = Math.min((pageIndex + 1) * pageSize, totalItems);

return (
<HStack spacing={2} justify="space-between" px={4} py={2}>
<Text textStyle="text-ui-reg" color={colors.foreground.secondary}>
Showing {from.toLocaleString("en-US")}-{to.toLocaleString("en-US")} of{" "}
{totalRows.toLocaleString("en-US")} {itemLabel}
{totalItems.toLocaleString("en-US")} {itemLabel}
</Text>
<HStack spacing={2}>
<IconButton
Expand All @@ -43,8 +56,8 @@ export const TablePagination = <TData,>({
variant="secondary"
minW="8"
height="8"
isDisabled={!table.getCanPreviousPage()}
onClick={() => table.previousPage()}
isDisabled={pageIndex === 0}
onClick={onPrevious}
/>
<Text textStyle="text-ui-reg" color={colors.foreground.secondary}>
page {pageIndex + 1} of {pageCount}
Expand All @@ -55,10 +68,28 @@ export const TablePagination = <TData,>({
variant="secondary"
minW="8"
height="8"
isDisabled={!table.getCanNextPage()}
onClick={() => table.nextPage()}
isDisabled={pageIndex >= pageCount - 1}
onClick={onNext}
/>
</HStack>
</HStack>
);
};

export const TablePagination = <TData,>({
table,
itemLabel,
}: TablePaginationProps<TData>) => {
const { pageIndex, pageSize } = table.getState().pagination;
return (
<PaginationControls
pageIndex={pageIndex}
pageSize={pageSize}
pageCount={table.getPageCount()}
totalItems={table.getFilteredRowModel().rows.length}
itemLabel={itemLabel}
onPrevious={() => table.previousPage()}
onNext={() => table.nextPage()}
/>
);
};
30 changes: 28 additions & 2 deletions console/src/platform/clusters/ClusterDetail.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ vi.mock("~/platform/clusters/ClusterPage/ClusterWorkers", async () => {
),
};
});
vi.mock("~/platform/clusters/ClusterPage/Hydration/ClusterHydration", () => ({
ClusterHydration: () => <div>ClusterHydration component</div>,
}));
vi.mock("~/platform/clusters/ClusterReplicas", () => ({
default: () => <div>ClusterReplicas component</div>,
}));
Expand Down Expand Up @@ -192,9 +195,11 @@ describe("ClusterDetail redesign preview", () => {
.getAllByRole("link")
.map((link) => link.textContent)
.filter((name) =>
["Metrics", "Workers", "Objects", "Replicas"].includes(name ?? ""),
["Metrics", "Workers", "Objects", "Hydration", "Replicas"].includes(
name ?? "",
),
),
).toEqual(["Metrics", "Workers", "Objects"]);
).toEqual(["Metrics", "Workers", "Objects", "Hydration"]);
expect(
screen.getByRole("button", { name: "Configure cluster" }),
).toBeVisible();
Expand Down Expand Up @@ -270,6 +275,18 @@ describe("ClusterDetail redesign preview", () => {
);
});

it("opens the Hydration tab from its link", async () => {
const user = userEvent.setup();
await renderClusterDetail("/u1/default");

await user.click(await screen.findByRole("link", { name: "Hydration" }));

await expectPathname("/u1/default/hydration");
expect(
await screen.findByText("ClusterHydration component"),
).toBeVisible();
});

it("opens the Objects tab on its first object type", async () => {
const user = userEvent.setup();
await renderClusterDetail("/u1/default");
Expand Down Expand Up @@ -473,5 +490,14 @@ describe("ClusterDetail redesign preview", () => {
await expectPathname("/u1/default/sinks");
expect(await screen.findByText("Sinks component")).toBeVisible();
});

it("sends a redesigned Hydration URL to the classic overview", async () => {
await renderClusterDetail("/u1/default/hydration");

await expectPathname("/u1/default");
expect(
await screen.findByText("ClusterOverview component"),
).toBeVisible();
});
});
});
5 changes: 3 additions & 2 deletions console/src/platform/clusters/ClusterDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { ClusterDetailBreadcrumbs } from "./ClusterDetailBreadcrumbs";
import ClusterOverview from "./ClusterOverview";
import { CLUSTER_OBJECT_TYPES } from "./ClusterPage/clusterObjectTypes";
import ClusterPage, { ClusterPageHeader } from "./ClusterPage/ClusterPage";
import { WORKERS_TAB_PATH } from "./ClusterPage/tabLinks";
import { HYDRATION_TAB_PATH, WORKERS_TAB_PATH } from "./ClusterPage/tabLinks";
import ClusterReplicas from "./ClusterReplicas";
import IndexList from "./IndexList";
import MaterializedViewsList from "./MaterializedViewsList";
Expand Down Expand Up @@ -72,8 +72,9 @@ const ClassicClusterDetailPage = () => {
<Route path="sinks" element={<Sinks key={clusterName} />} />
<Route path="objects" element={<ClassicObjectsRedirect />} />
<Route path="objects/:objectType" element={<ClassicObjectsRedirect />} />
{/* The redesigned Workers tab has no classic counterpart. */}
{/* The redesigned Workers and Hydration tabs have no classic counterpart. */}
<Route path={WORKERS_TAB_PATH} element={<Navigate to=".." replace />} />
<Route path={HYDRATION_TAB_PATH} element={<Navigate to=".." replace />} />
</SentryRoutes>
);
};
Expand Down
18 changes: 16 additions & 2 deletions console/src/platform/clusters/ClusterPage/ClusterPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,27 @@ import {
CONFIGURATION_SECTIONS,
ConfigurationSection,
} from "./Configuration/clusterConfigurationModel";
import { replicaSearch, WORKERS_TAB_PATH } from "./tabLinks";
import { ClusterHydration } from "./Hydration/ClusterHydration";
import {
HYDRATION_TAB_PATH,
replicaSearch,
WORKERS_TAB_PATH,
} from "./tabLinks";
import { REPLICA_SEARCH_PARAM } from "./useSelectedReplica";

// The Metrics and Workers tabs share a replica choice, so their links carry it.
// The Metrics, Workers and Hydration tabs share a replica choice, so their
// links carry it.
const tabs = (replicaName: string | undefined): Tab[] => [
{ label: "Metrics", href: `..${replicaSearch(replicaName)}`, end: true },
{
label: "Workers",
href: `../${WORKERS_TAB_PATH}${replicaSearch(replicaName)}`,
},
{ label: "Objects", href: "../objects" },
{
label: "Hydration",
href: `../${HYDRATION_TAB_PATH}${replicaSearch(replicaName)}`,
},
];

// The configuration drawer's open section, in the URL so it survives a reload
Expand Down Expand Up @@ -174,6 +184,10 @@ const ClusterPage = () => {
path="objects/:objectType"
element={<ClusterObjects key={clusterName} />}
/>
<Route
path={HYDRATION_TAB_PATH}
element={<ClusterHydration key={clusterName} />}
/>
<Route
path="replicas"
element={
Expand Down
Loading
Loading