Skip to content

Commit

Permalink
Add models table
Browse files Browse the repository at this point in the history
  • Loading branch information
steverydz committed Jul 4, 2023
1 parent 3048a51 commit 81a9dd7
Show file tree
Hide file tree
Showing 12 changed files with 504 additions and 38 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@tarekraafat/autocomplete.js": "10.2.7",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "14.0.0",
"@testing-library/user-event": "14.4.3",
"@types/jest": "29.4.0",
"@types/react-redux": "7.1.25",
"@types/react-router-dom": "5.3.3",
Expand Down Expand Up @@ -77,6 +78,7 @@
"react-dnd-html5-backend": "16.0.1",
"react-dom": "18.2.0",
"react-hook-form": "7.43.2",
"react-query": "3.39.3",
"react-redux": "8.0.5",
"react-router-dom": "6.8.2",
"react-sortable-hoc": "2.0.0",
Expand Down
72 changes: 42 additions & 30 deletions static/js/brand-store/components/App/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { QueryClient, QueryClientProvider } from "react-query";
import {
BrowserRouter as Router,
Routes,
Expand Down Expand Up @@ -29,42 +30,53 @@ function App() {
const brandStoresList: StoresList = useSelector(brandStoresListSelector);
const dispatch = useDispatch();

const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
refetchOnReconnect: false,
},
},
});

useEffect(() => {
dispatch(fetchStores() as any);
}, []);

return (
<Router>
<div className="l-application" role="presentation">
<Navigation />
<Routes>
<Route
path="/admin"
element={
!isLoading ? (
!brandStoresList || brandStoresList.length < 1 ? (
<StoreNotFound />
) : brandStoresList[0].id === "ubuntu" ? (
// Don't redirect to the global store by default
<Navigate to={`/admin/${brandStoresList[1].id}/snaps`} />
) : (
<Navigate to={`/admin/${brandStoresList[0].id}/snaps`} />
)
) : null
}
/>
<Route path="/admin/:id/snaps" element={<Snaps />} />
<Route path="/admin/:id/members" element={<Members />} />
<Route path="/admin/:id/settings" element={<Settings />} />
<Route path="/admin/:id/models" element={<Models />} />
<Route path="/admin/:id/models/:model_id" element={<Model />} />
<Route
path="/admin/:id/models/:model_id/policies"
element={<Policies />}
/>
<Route path="/admin/:id/signing-keys" element={<SigningKeys />} />
</Routes>
</div>
<QueryClientProvider client={queryClient}>
<div className="l-application" role="presentation">
<Navigation />
<Routes>
<Route
path="/admin"
element={
!isLoading ? (
!brandStoresList || brandStoresList.length < 1 ? (
<StoreNotFound />
) : brandStoresList[0].id === "ubuntu" ? (
// Don't redirect to the global store by default
<Navigate to={`/admin/${brandStoresList[1].id}/snaps`} />
) : (
<Navigate to={`/admin/${brandStoresList[0].id}/snaps`} />
)
) : null
}
/>
<Route path="/admin/:id/snaps" element={<Snaps />} />
<Route path="/admin/:id/members" element={<Members />} />
<Route path="/admin/:id/settings" element={<Settings />} />
<Route path="/admin/:id/models" element={<Models />} />
<Route path="/admin/:id/models/:model_id" element={<Model />} />
<Route
path="/admin/:id/models/:model_id/policies"
element={<Policies />}
/>
<Route path="/admin/:id/signing-keys" element={<SigningKeys />} />
</Routes>
</div>
</QueryClientProvider>
</Router>
);
}
Expand Down
46 changes: 46 additions & 0 deletions static/js/brand-store/components/Models/Models.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from "react";
import { BrowserRouter } from "react-router-dom";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

import "@testing-library/jest-dom";

import Models from "./Models";

let mockFilterQuery = "model-1";

jest.mock("react-router-dom", () => {
return {
...jest.requireActual("react-router-dom"),
useSearchParams: () => [new URLSearchParams({ filter: mockFilterQuery })],
};
});

describe("Models", () => {
it("displays a filter input", () => {
render(
<BrowserRouter>
<Models />
</BrowserRouter>
);
expect(screen.getByLabelText("Search models")).toBeInTheDocument();
});

it("populates filter with the filter query parameter", () => {
render(
<BrowserRouter>
<Models />
</BrowserRouter>
);
expect(screen.getByLabelText("Search models")).toHaveValue(mockFilterQuery);
});

it("displays a table of models", () => {
render(
<BrowserRouter>
<Models />
</BrowserRouter>
);
expect(screen.getByTestId("models-table")).toBeInTheDocument();
});
});
104 changes: 97 additions & 7 deletions static/js/brand-store/components/Models/Models.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
import React from "react";
import { Link, useParams } from "react-router-dom";
import { Link, useParams, useSearchParams } from "react-router-dom";
import { format } from "date-fns";
import { MainTable, SearchBox, Row, Col } from "@canonical/react-components";

import SectionNav from "../SectionNav";

import { getFilteredModels, maskString } from "../../utils";

import type { Model } from "../../types/shared";

// This is temporary until the API is connected
import modelsData from "./models-data";

function Models() {
const { id } = useParams();
const [searchParams, setSearchParams] = useSearchParams();
const models = getFilteredModels(modelsData.data, searchParams.get("filter"));

return (
<main className="l-main">
Expand All @@ -13,13 +24,92 @@ function Models() {
<div className="u-fixed-width">
<SectionNav sectionName="models" />
</div>
<Row>
<Col size={6}>
{/* Placeholder for "Create new model" button */}
</Col>
<Col size={6}>
<SearchBox
placeholder="Search models"
autoComplete="off"
value={searchParams.get("filter") || ""}
onChange={(value) => {
if (value) {
setSearchParams({
filter: value,
});
} else {
setSearchParams();
}
}}
/>
</Col>
</Row>
<div className="u-fixed-width">
<p>This is where the models table will be</p>
<p>
<Link to={`/admin/${id}/models/model-name`}>
Example model page
</Link>
</p>
{models.length > 0 && (
<MainTable
data-testid="models-table"
sortable
paginate={10}
emptyStateMsg="Fetching models data..."
headers={[
{
content: `Name (${models.length})`,
sortKey: "name",
},
{
content: "API key",
className: "u-align--right",
},
{
content: "Last updated",
className: "u-align--right",
sortKey: "modified-at",
},
{
content: "Created date",
className: "u-align--right",
sortKey: "created-at",
},
]}
rows={models.map((model: Model) => {
return {
columns: [
{
content: (
<Link to={`/admin/${id}/models/${model.name}`}>
{model.name}
</Link>
),
},
{
content: maskString(model["api-key"]),
className: "u-align--right",
},
{
content: format(
new Date(model["modified-at"]),
"dd/MM/yyyy"
),
className: "u-align--right",
},
{
content: format(
new Date(model["created-at"]),
"dd/MM/yyyy"
),
className: "u-align--right",
},
],
sortData: {
name: model.name,
"modified-at": model["modified-at"],
"created-at": model["created-at"],
},
};
})}
/>
)}
</div>
</div>
</div>
Expand Down
106 changes: 106 additions & 0 deletions static/js/brand-store/components/Models/models-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
const modelsData = {
success: true,
message: "The request has been successful",
data: [
{
name: "model-1",
"api-key": "e15a9abc390b4514a35752f5851e27b8",
"created-at": "2022-03-29T13:03:11.095Z",
"created-by": "publisherId",
"modified-at": "2023-06-27T13:06:25.541Z",
"modified-by": "publisherId",
},
{
name: "model-2",
"api-key": "80eb997804364d1a9008777584082e3d",
"created-at": "2018-07-14T13:03:35.452Z",
"created-by": "publisherId",
"modified-at": "2023-06-29T13:06:52.630Z",
"modified-by": "publisherId",
},
{
name: "model-3",
"api-key": "fea502ee59c249369073b96034b56770",
"created-at": "2021-10-31T13:07:10.090Z",
"created-by": "publisherId",
"modified-at": "2023-06-29T13:03:35.452Z",
"modified-by": "publisherId",
},
{
name: "model-4",
"api-key": "a19e3ac3d7d442e39c0d",
"created-at": "2022-03-29T13:03:11.095Z",
"created-by": "publisherId",
"modified-at": "2023-06-27T13:06:25.541Z",
"modified-by": "publisherId",
},
{
name: "model-5",
"api-key": "d24277fd31a8450ca326",
"created-at": "2018-07-14T13:03:35.452Z",
"created-by": "publisherId",
"modified-at": "2023-06-29T13:06:52.630Z",
"modified-by": "publisherId",
},
{
name: "model-6",
"api-key": "e9797541fbe44cf6b788",
"created-at": "2021-10-31T13:07:10.090Z",
"created-by": "publisherId",
"modified-at": "2023-06-29T13:03:35.452Z",
"modified-by": "publisherId",
},
{
name: "model-7",
"api-key": "9d022dcdb4b4413bb1a0",
"created-at": "2022-03-29T13:03:11.095Z",
"created-by": "publisherId",
"modified-at": "2023-06-27T13:06:25.541Z",
"modified-by": "publisherId",
},
{
name: "model-8",

"api-key": "892f97b614bf4c70a6ac",
"created-at": "2018-07-14T13:03:35.452Z",
"created-by": "publisherId",
"modified-at": "2023-06-29T13:06:52.630Z",
"modified-by": "publisherId",
},
{
name: "model-9",
"api-key": "48135fc256a840638dba",

"created-at": "2021-10-31T13:07:10.090Z",
"created-by": "publisherId",
"modified-at": "2023-06-29T13:03:35.452Z",
"modified-by": "publisherId",
},
{
name: "model-10",
"api-key": "87a5cf9c6bc84f219c34",
"created-at": "2022-03-29T13:03:11.095Z",
"created-by": "publisherId",
"modified-at": "2023-06-27T13:06:25.541Z",
"modified-by": "publisherId",
},
{
name: "model-11",
"api-key": "1f7a61567e944e53a584",
"created-at": "2018-07-14T13:03:35.452Z",
"created-by": "publisherId",
"modified-at": "2023-06-29T13:06:52.630Z",
"modified-by": "publisherId",
},
{
name: "model-12",
"api-key": "437d5599c88a4d15840d",
"created-at": "2021-10-31T13:07:10.090Z",
"created-by": "publisherId",
"modified-at": "2023-06-29T13:03:35.452Z",
"modified-by": "publisherId",
},
],
};

export default modelsData;
9 changes: 9 additions & 0 deletions static/js/brand-store/types/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,12 @@ export type Store = {
snaps: SnapsList;
userHasAccess: boolean;
};

export type Model = {
name: string;
"api-key": string;
"created-at": string;
"created-by": string;
"modified-at": string;
"modified-by": string;
};
Loading

0 comments on commit 81a9dd7

Please sign in to comment.