Skip to content

Commit

Permalink
[UI v2] feat: Adds deployment parameter table (#17059)
Browse files Browse the repository at this point in the history
  • Loading branch information
devinvillarosa authored Feb 10, 2025
1 parent 35a86f4 commit 05f0122
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 1 deletion.
3 changes: 2 additions & 1 deletion ui-v2/src/components/deployments/deployment-details-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { type JSX } from "react";

import { DeploymentConfiguration } from "./deployment-configuration";
import { DeploymentDescription } from "./deployment-description";
import { DeploymentParametersTable } from "./deployment-parameters-table";

const routeApi = getRouteApi("/deployments/deployment/$id");

Expand Down Expand Up @@ -59,7 +60,7 @@ export const DeploymentDetailsTabs = ({
),
ViewComponent: () => (
<TabsContent value="Parameters">
<div className="border border-red-400">{"<ParametersView />"}</div>
<DeploymentParametersTable deployment={deployment} />
</TabsContent>
),
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { Meta, StoryObj } from "@storybook/react";

import { createFakeDeployment } from "@/mocks";
import { DeploymentParametersTable } from "./deployment-parameters-table";

const meta = {
title: "Components/Deployments/DeploymentParametersTable",
component: DeploymentParametersTable,
args: {
deployment: createFakeDeployment({
parameter_openapi_schema: {
title: "Parameters",
type: "object",
properties: {
name: {
default: "world",
position: 0,
title: "name",
type: "string",
},
goodbye: {
default: false,
position: 1,
title: "goodbye",
type: "boolean",
},
},
},
parameters: {
// @ts-expect-error TODO: Fix OpenAPI Schema
goodbye: false,
// @ts-expect-error TODO: Fix OpenAPI Schema
name: "world",
},
}),
},
} satisfies Meta<typeof DeploymentParametersTable>;

export default meta;

export const story: StoryObj = { name: "DeploymentParametersTable" };
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it } from "vitest";

import { createFakeDeployment } from "@/mocks";
import { DeploymentParametersTable } from "./deployment-parameters-table";

const MOCK_DEPLOYMENT = createFakeDeployment({
parameter_openapi_schema: {
title: "Parameters",
type: "object",
properties: {
name: {
default: "world",
position: 0,
title: "name",
type: "string",
},
goodbye: {
default: false,
position: 1,
title: "goodbye",
type: "boolean",
},
},
},
parameters: {
// @ts-expect-error TODO: Fix OpenAPI Schema
goodbye: false,
// @ts-expect-error TODO: Fix OpenAPI Schema
name: "world",
},
});

describe("DeploymentParametersTable", () => {
it("renders table with rows", () => {
// ------------ Setup
render(<DeploymentParametersTable deployment={MOCK_DEPLOYMENT} />);

// ------------ Assert
expect(screen.getByRole("cell", { name: /name/i })).toBeVisible();
expect(screen.getByRole("cell", { name: /goodbye/i })).toBeVisible();
});

it("filters table rows", async () => {
// ------------ Setup
const user = userEvent.setup();

render(<DeploymentParametersTable deployment={MOCK_DEPLOYMENT} />);

// ------------ Act
await user.type(screen.getByRole("textbox"), "name");

// ------------ Assert
await waitFor(() =>
expect(
screen.queryByRole("cell", { name: /goodbye/i }),
).not.toBeInTheDocument(),
);
expect(screen.getByRole("cell", { name: /name/i })).toBeVisible();
});

it("filters no results", async () => {
// ------------ Setup
const user = userEvent.setup();

render(<DeploymentParametersTable deployment={MOCK_DEPLOYMENT} />);

// ------------ Act
await user.type(screen.getByRole("textbox"), "no results found");

// ------------ Assert
await waitFor(() =>
expect(
screen.queryByRole("cell", { name: /goodbye/i }),
).not.toBeInTheDocument(),
);
expect(
screen.queryByRole("cell", { name: /name/i }),
).not.toBeInTheDocument();

expect(screen.getByText("No results.")).toBeVisible();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { Deployment } from "@/api/deployments";
import { DataTable } from "@/components/ui/data-table";
import { SearchInput } from "@/components/ui/input";
import { Typography } from "@/components/ui/typography";
import { pluralize } from "@/utils";
import {
createColumnHelper,
getCoreRowModel,
getPaginationRowModel,
useReactTable,
} from "@tanstack/react-table";
import { useDeferredValue, useMemo, useState } from "react";

type DeploymentParametersTableProps = {
deployment: Deployment;
};

type ParameterOpenApiSchema = {
default: unknown;
position: number;
title: string;
type: "boolean" | "number" | "null" | "string";
};

type ParametersTableColumns = {
key: string;
value: unknown;
defaultValue: unknown;
type: string | undefined;
};

const columnHelper = createColumnHelper<ParametersTableColumns>();

const columns = [
columnHelper.accessor("key", { header: "Key" }),
columnHelper.accessor("value", { header: "Override" }),
columnHelper.accessor("defaultValue", { header: "Default" }),
columnHelper.accessor("type", { header: "Type" }),
];

/**
*
* @param deployment
* @returns converts a deployment schema into a parameters table schema
*/
const useDeploymentParametersToTable = (
deployment: Deployment,
): Array<ParametersTableColumns> =>
useMemo(() => {
const parameterOpenApiSchema = deployment.parameter_openapi_schema
?.properties as Record<string, ParameterOpenApiSchema>;
const parameters = deployment.parameters as Record<string, unknown>;
return Object.keys(parameterOpenApiSchema)
.sort((a, b) => {
return (
parameterOpenApiSchema[a].position -
parameterOpenApiSchema[b].position
);
})
.map((key) => {
const parameter = parameterOpenApiSchema[key];
return {
key,
value: parameters[key],
defaultValue: parameter.default,
type: parameter.type,
};
});
}, [deployment]);

export const DeploymentParametersTable = ({
deployment,
}: DeploymentParametersTableProps) => {
const [search, setSearch] = useState("");
const data = useDeploymentParametersToTable(deployment);

// nb: This table does search via client side
const deferredSearch = useDeferredValue(search);
const filteredData = useMemo(() => {
return data.filter(
(parameter) =>
parameter.key.toLowerCase().includes(deferredSearch.toLowerCase()) ||
parameter.value
?.toString()
.toLowerCase()
.includes(deferredSearch.toLowerCase()) ||
parameter.defaultValue
?.toString()
.toLowerCase()
.includes(deferredSearch.toLowerCase()) ||
parameter.type
?.toString()
.toLowerCase()
.includes(deferredSearch.toLowerCase()),
);
}, [data, deferredSearch]);

const table = useReactTable({
data: filteredData,

columns,
getCoreRowModel: getCoreRowModel(),
defaultColumn: { maxSize: 300 },
getPaginationRowModel: getPaginationRowModel(), //load client-side pagination code
});

return (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between">
<Typography variant="bodySmall" className="text-muted-foreground">
{filteredData.length} {pluralize(filteredData.length, "parameter")}
</Typography>
<div className="sm:col-span-2 md:col-span-2 lg:col-span-3">
<SearchInput
className="sm:col-span-2 md:col-span-2 lg:col-span-3"
placeholder="Search parameters"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
</div>
<DataTable table={table} />
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { DeploymentParametersTable } from "./deployment-parameters-table";

0 comments on commit 05f0122

Please sign in to comment.