sts-sponsors team mailing list archive
-
sts-sponsors team
-
Mailing list archive
-
Message #05333
[Merge] ~maas-committers/maas-site-manager/+git/maas-site-manager-frontend:react-router-dom-MAASENG-1399 into ~maas-committers/maas-site-manager/+git/maas-site-manager-frontend:main
Peter Makowski has proposed merging ~maas-committers/maas-site-manager/+git/maas-site-manager-frontend:react-router-dom-MAASENG-1399 into ~maas-committers/maas-site-manager/+git/maas-site-manager-frontend:main.
Requested reviews:
MAAS Committers (maas-committers)
For more details, see:
https://code.launchpad.net/~maas-committers/maas-site-manager/+git/maas-site-manager-frontend/+merge/437798
Add react router (react-router-dom) and setup base routes for the app
--
Your team MAAS Committers is requested to review the proposed merge of ~maas-committers/maas-site-manager/+git/maas-site-manager-frontend:react-router-dom-MAASENG-1399 into ~maas-committers/maas-site-manager/+git/maas-site-manager-frontend:main.
diff --git a/package.json b/package.json
index 1e65cf2..48cab3d 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-query": "3.39.3",
+ "react-router-dom": "6.8.1",
"vanilla-framework": "3.11.0"
},
"devDependencies": {
diff --git a/src/App.test.tsx b/src/App.test.tsx
index 7bb2bcf..e4c3b38 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -1,4 +1,4 @@
-import { render, screen } from "@testing-library/react";
+import { render, screen } from "./test-utils";
import App from "./App";
diff --git a/src/App.tsx b/src/App.tsx
index 1f20875..36cb2b5 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,20 +1,14 @@
import "./App.scss";
-import SitesList from "./components/SitesList";
import { QueryClient, QueryClientProvider } from "react-query";
+import router from "./router";
+import { RouterProvider } from "react-router-dom";
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
- <div className="App">
- <div className="row">
- <div className="col-12">
- <h1>MAAS Site Manager</h1>
- <SitesList />
- </div>
- </div>
- </div>
+ <RouterProvider router={router} />
</QueryClientProvider>
);
}
diff --git a/src/components/MainLayout/MainLayout.tsx b/src/components/MainLayout/MainLayout.tsx
new file mode 100644
index 0000000..a60b2d9
--- /dev/null
+++ b/src/components/MainLayout/MainLayout.tsx
@@ -0,0 +1,14 @@
+import { Outlet } from "react-router-dom";
+
+const MainLayout = () => (
+ <div className="App">
+ <div className="row">
+ <div className="col-12">
+ <h1>MAAS Site Manager</h1>
+ <Outlet />
+ </div>
+ </div>
+ </div>
+);
+
+export default MainLayout;
diff --git a/src/components/MainLayout/index.ts b/src/components/MainLayout/index.ts
new file mode 100644
index 0000000..ff87b06
--- /dev/null
+++ b/src/components/MainLayout/index.ts
@@ -0,0 +1 @@
+export { default } from "./MainLayout";
diff --git a/src/components/SitesList.scss b/src/components/SitesList/SitesList.scss
similarity index 100%
rename from src/components/SitesList.scss
rename to src/components/SitesList/SitesList.scss
diff --git a/src/components/SitesList/SitesList.test.tsx b/src/components/SitesList/SitesList.test.tsx
new file mode 100644
index 0000000..e23e81c
--- /dev/null
+++ b/src/components/SitesList/SitesList.test.tsx
@@ -0,0 +1,10 @@
+import SitesList from "./SitesList";
+import { render, screen } from "../../test-utils";
+
+it("renders header", () => {
+ render(<SitesList />);
+
+ expect(
+ screen.getByRole("heading", { name: /MAAS Regions/i })
+ ).toBeInTheDocument();
+});
diff --git a/src/components/SitesList.tsx b/src/components/SitesList/SitesList.tsx
similarity index 56%
rename from src/components/SitesList.tsx
rename to src/components/SitesList/SitesList.tsx
index b1ba613..9a49bd0 100644
--- a/src/components/SitesList.tsx
+++ b/src/components/SitesList/SitesList.tsx
@@ -1,70 +1,11 @@
import { useQuery } from "react-query";
+import SiteRow from "./components/SiteRow";
import "./SitesList.scss";
-
-type Site = {
- name: string;
- url: string; // <full URL including protocol>,
- connection: "stable" | "stale" | "lost";
- last_seen: string; // <ISO 8601 date>,
- address: {
- countrycode: string; // <alpha2 country code>,
- city: string;
- zip: string;
- street: string;
- };
- timezone: string; // <three letter abbreviation>,
- stats: {
- machines: number;
- occupied_machines: number;
- ready_machines: number;
- error_machines: number;
- };
-};
-
-type Sites = {
- items: Site[];
- total: number;
- page: number;
- size: number;
-};
-
-const SiteRow = ({ site }: { site: Site }) => {
- return (
- <tr>
- <td>
- <div>{site.name}</div>
- <div>
- <a href={site.url}>{site.url}</a>
- </div>
- </td>
- <td>
- <div>{site.connection}</div>
- <div>{site.last_seen}</div>
- </td>
- <td>
- <div>{site.address.countrycode}</div>
- <div>
- {site.address.street}, {site.address.city}, {site.address.zip}
- </div>
- </td>
- <td>
- <div>11:00 (local time)</div>
- <div>{site.timezone}</div>
- </td>
- <td>
- <div>{site.stats.machines}</div>
- <div>
- Ready: {site.stats.ready_machines}, Occupied:
- {site.stats.occupied_machines}, Error: {site.stats.error_machines}
- </div>
- </td>
- </tr>
- );
-};
+import { Sites } from "./types";
const SitesList = () => {
- const query = useQuery<Sites>("/sites", async function () {
- const response = await fetch("/sites");
+ const query = useQuery<Sites>("/api/sites", async function () {
+ const response = await fetch("/api/sites");
if (!response.ok) {
throw new Error("Network response was not ok");
}
@@ -74,7 +15,7 @@ const SitesList = () => {
return (
<div>
- <h2>Sites</h2>
+ <h2>{query?.data?.items?.length || ""} MAAS Regions</h2>
<table>
<thead>
<tr>
@@ -102,7 +43,9 @@ const SitesList = () => {
</thead>
<tbody>
{query.data
- ? query.data.items.map((site) => <SiteRow site={site} />)
+ ? query.data.items.map((site) => (
+ <SiteRow key={site.url} site={site} />
+ ))
: null}
</tbody>
</table>
diff --git a/src/components/SitesList/components/SiteRow.tsx b/src/components/SitesList/components/SiteRow.tsx
new file mode 100644
index 0000000..22ed089
--- /dev/null
+++ b/src/components/SitesList/components/SiteRow.tsx
@@ -0,0 +1,37 @@
+import { Site } from "../types";
+
+const SiteRow = ({ site }: { site: Site }) => {
+ return (
+ <tr>
+ <td>
+ <div>{site.name}</div>
+ <div>
+ <a href={site.url}>{site.url}</a>
+ </div>
+ </td>
+ <td>
+ <div>{site.connection}</div>
+ <div>{site.last_seen}</div>
+ </td>
+ <td>
+ <div>{site.address.countrycode}</div>
+ <div>
+ {site.address.street}, {site.address.city}, {site.address.zip}
+ </div>
+ </td>
+ <td>
+ <div>11:00 (local time)</div>
+ <div>{site.timezone}</div>
+ </td>
+ <td>
+ <div>{site.stats.machines}</div>
+ <div>
+ Ready: {site.stats.ready_machines}, Occupied:
+ {site.stats.occupied_machines}, Error: {site.stats.error_machines}
+ </div>
+ </td>
+ </tr>
+ );
+};
+
+export default SiteRow;
diff --git a/src/components/SitesList/index.ts b/src/components/SitesList/index.ts
new file mode 100644
index 0000000..75d6f99
--- /dev/null
+++ b/src/components/SitesList/index.ts
@@ -0,0 +1 @@
+export { default } from "./SitesList";
diff --git a/src/components/SitesList/types.ts b/src/components/SitesList/types.ts
new file mode 100644
index 0000000..c8495ac
--- /dev/null
+++ b/src/components/SitesList/types.ts
@@ -0,0 +1,26 @@
+export type Site = {
+ name: string;
+ url: string; // <full URL including protocol>,
+ connection: "stable" | "stale" | "lost";
+ last_seen: string; // <ISO 8601 date>,
+ address: {
+ countrycode: string; // <alpha2 country code>,
+ city: string;
+ zip: string;
+ street: string;
+ };
+ timezone: string; // <three letter abbreviation>,
+ stats: {
+ machines: number;
+ occupied_machines: number;
+ ready_machines: number;
+ error_machines: number;
+ };
+};
+
+export type Sites = {
+ items: Site[];
+ total: number;
+ page: number;
+ size: number;
+};
diff --git a/src/mocks/browser.ts b/src/mocks/browser.ts
index d84c82f..0f20911 100644
--- a/src/mocks/browser.ts
+++ b/src/mocks/browser.ts
@@ -1,50 +1,10 @@
// src/mocks/browser.js
import { setupWorker, rest } from "msw";
-
-const getMockSite = (site = {}) => ({
- name: "maas-example-region",
- url: "http://maas.example.com",
- connection: "stable",
- last_seen: "2020-10-20T12:00:00Z",
- address: {
- countrycode: "DE", // <alpha2 country code>,
- city: "Berlin",
- zip: "10405",
- street: "Prenzlauer Allee 132",
- },
- timezone: "CET", // <three letter abbreviation>,
- stats: {
- machines: 300,
- occupied_machines: 289,
- ready_machines: 11,
- error_machines: 0,
- },
- ...site,
-});
+import { sites } from "./factories";
const worker = setupWorker(
- rest.get("/sites", (req, res, ctx) => {
- return res(
- ctx.json({
- items: [
- getMockSite(),
- getMockSite({
- name: "maas-shanghai",
- url: "http://maas.shanghai.com",
- address: {
- countrycode: "CHN",
- city: "Shanghai",
- zip: "200000",
- street: "Xinzhen Road 3",
- },
- timezone: "CST",
- }),
- ],
- total: 42,
- page: 1,
- size: 20,
- })
- );
+ rest.get("/api/sites", (_req, res, ctx) => {
+ return res(ctx.json(sites));
})
);
diff --git a/src/mocks/factories.ts b/src/mocks/factories.ts
new file mode 100644
index 0000000..2e647e2
--- /dev/null
+++ b/src/mocks/factories.ts
@@ -0,0 +1,40 @@
+export const site = (site = {}) => ({
+ name: "maas-example-region",
+ url: "http://maas.example.com",
+ connection: "stable",
+ last_seen: "2020-10-20T12:00:00Z",
+ address: {
+ countrycode: "DE", // <alpha2 country code>,
+ city: "Berlin",
+ zip: "10405",
+ street: "Prenzlauer Allee 132",
+ },
+ timezone: "CET", // <three letter abbreviation>,
+ stats: {
+ machines: 300,
+ occupied_machines: 289,
+ ready_machines: 11,
+ error_machines: 0,
+ },
+ ...site,
+});
+
+export const sites = {
+ items: [
+ site(),
+ site({
+ name: "maas-shanghai",
+ url: "http://maas.shanghai.com",
+ address: {
+ countrycode: "CHN",
+ city: "Shanghai",
+ zip: "200000",
+ street: "Xinzhen Road 3",
+ },
+ timezone: "CST",
+ }),
+ ],
+ total: 42,
+ page: 1,
+ size: 20,
+};
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pages/index.tsx
diff --git a/src/pages/list.tsx b/src/pages/list.tsx
new file mode 100644
index 0000000..3c34f73
--- /dev/null
+++ b/src/pages/list.tsx
@@ -0,0 +1,5 @@
+import SitesList from "../components/SitesList";
+
+const List = () => <SitesList />;
+
+export default List;
diff --git a/src/pages/login.tsx b/src/pages/login.tsx
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pages/login.tsx
diff --git a/src/pages/logout.tsx b/src/pages/logout.tsx
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pages/logout.tsx
diff --git a/src/pages/requests.tsx b/src/pages/requests.tsx
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pages/requests.tsx
diff --git a/src/pages/tokens.tsx b/src/pages/tokens.tsx
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pages/tokens.tsx
diff --git a/src/pages/users.tsx b/src/pages/users.tsx
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/pages/users.tsx
diff --git a/src/router.tsx b/src/router.tsx
new file mode 100644
index 0000000..b8ec6f8
--- /dev/null
+++ b/src/router.tsx
@@ -0,0 +1,24 @@
+import {
+ createBrowserRouter,
+ createRoutesFromElements,
+ Link,
+ Outlet,
+ Route,
+} from "react-router-dom";
+import MainLayout from "./components/MainLayout";
+import SitesList from "./components/SitesList/SitesList";
+
+const router = createBrowserRouter(
+ createRoutesFromElements(
+ <Route path="/" element={<MainLayout />}>
+ <Route path="login" />
+ <Route path="logout" />
+ <Route path="sites" element={<SitesList />} />
+ <Route path="requests" />
+ <Route path="tokens" />
+ <Route path="users" />
+ </Route>
+ )
+);
+
+export default router;
diff --git a/src/test-utils.tsx b/src/test-utils.tsx
new file mode 100644
index 0000000..419ac79
--- /dev/null
+++ b/src/test-utils.tsx
@@ -0,0 +1,26 @@
+import React, { ReactElement } from "react";
+import { render, RenderOptions } from "@testing-library/react";
+
+import { QueryClient, QueryClientProvider } from "react-query";
+
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ },
+ },
+});
+
+const Providers = ({ children }: { children: React.ReactNode }) => {
+ return (
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
+ );
+};
+
+const customRender = (
+ ui: ReactElement,
+ options?: Omit<RenderOptions, "wrapper">
+) => render(ui, { wrapper: Providers, ...options });
+
+export * from "@testing-library/react";
+export { customRender as render };
diff --git a/yarn.lock b/yarn.lock
index a799953..8ecf88b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -229,6 +229,11 @@
resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca"
integrity sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==
+"@remix-run/router@1.3.2":
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.3.2.tgz#58cd2bd25df2acc16c628e1b6f6150ea6c7455bc"
+ integrity sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA==
+
"@sinclair/typebox@^0.25.16":
version "0.25.22"
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.22.tgz#2808d895e9c2722b20a622a9c8cb332f6720eb4a"
@@ -2002,6 +2007,21 @@ react-query@3.39.3:
broadcast-channel "^3.4.1"
match-sorter "^6.0.2"
+react-router-dom@6.8.1:
+ version "6.8.1"
+ resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.8.1.tgz#7e136b67d9866f55999e9a8482c7008e3c575ac9"
+ integrity sha512-67EXNfkQgf34P7+PSb6VlBuaacGhkKn3kpE51+P6zYSG2kiRoumXEL6e27zTa9+PGF2MNXbgIUHTVlleLbIcHQ==
+ dependencies:
+ "@remix-run/router" "1.3.2"
+ react-router "6.8.1"
+
+react-router@6.8.1:
+ version "6.8.1"
+ resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.8.1.tgz#e362caf93958a747c649be1b47cd505cf28ca63e"
+ integrity sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==
+ dependencies:
+ "@remix-run/router" "1.3.2"
+
react@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
Follow ups