sts-sponsors team mailing list archive
-
sts-sponsors team
-
Mailing list archive
-
Message #06209
[Merge] ~petermakowski/maas-site-manager:feat-heartbeat-status-icons-MAASENG-1469 into maas-site-manager:main
Peter Makowski has proposed merging ~petermakowski/maas-site-manager:feat-heartbeat-status-icons-MAASENG-1469 into maas-site-manager:main.
Commit message:
feat(sites): heartbeat status icons MAASENG-1469
Requested reviews:
MAAS Committers (maas-committers)
For more details, see:
https://code.launchpad.net/~petermakowski/maas-site-manager/+git/site-manager/+merge/439217
QA Steps:
1. Go to sites page
2. Verify that each site has a correct connection status text along with a status icon
--
Your team MAAS Committers is requested to review the proposed merge of ~petermakowski/maas-site-manager:feat-heartbeat-status-icons-MAASENG-1469 into maas-site-manager:main.
diff --git a/frontend/src/App.scss b/frontend/src/App.scss
index ff53c94..9923a4c 100644
--- a/frontend/src/App.scss
+++ b/frontend/src/App.scss
@@ -5,6 +5,12 @@
@include vf-p-grid;
@include vf-l-application;
+@import "patterns_icons"; // include common styles shared by all icons
+@include vf-p-icons-common;
+@include vf-p-icon-status-failed-small;
+@include vf-p-icon-status-waiting-small;
+@include vf-p-icon-status-succeeded-small;
+@include vf-p-icons;
@import "./utils";
@import "./patterns_icons";
@import "./patterns_typography";
diff --git a/frontend/src/_utils.scss b/frontend/src/_utils.scss
index 8190eef..0ff7a85 100644
--- a/frontend/src/_utils.scss
+++ b/frontend/src/_utils.scss
@@ -10,3 +10,6 @@
.u-flex--column {
flex-direction: column !important;
}
+.u-capitalize {
+ text-transform: capitalize !important;
+}
diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts
index 690882e..8166a68 100644
--- a/frontend/src/api/types.ts
+++ b/frontend/src/api/types.ts
@@ -2,7 +2,7 @@ export type Site = {
identifier: string;
name: string;
url: string; // <full URL including protocol>,
- connection: "stable" | "unstable" | "stale" | "lost";
+ connection: "stable" | "lost" | "unknown";
last_seen: string; // <ISO 8601 date>,
address: {
countrycode: string; // <alpha2 country code>,
diff --git a/frontend/src/components/SitesList/components/ConnectionInfo.test.tsx b/frontend/src/components/SitesList/components/ConnectionInfo.test.tsx
new file mode 100644
index 0000000..88e355c
--- /dev/null
+++ b/frontend/src/components/SitesList/components/ConnectionInfo.test.tsx
@@ -0,0 +1,13 @@
+import ConnectionInfo, { connectionIcons, connectionLabels } from "./ConnectionInfo";
+
+import { connections } from "@/mocks/factories";
+import { render, screen } from "@/test-utils";
+
+connections.forEach((connection) => {
+ it(`displays correct connection status icon and label for ${connection} connection`, () => {
+ const { container } = render(<ConnectionInfo connection={connection} />);
+ expect(screen.getByText(connectionLabels[connection])).toBeInTheDocument();
+ // eslint-disable-next-line testing-library/no-container
+ expect(container.querySelector(".status-icon")).toHaveClass(connectionIcons[connection]);
+ });
+});
diff --git a/frontend/src/components/SitesList/components/ConnectionInfo.tsx b/frontend/src/components/SitesList/components/ConnectionInfo.tsx
new file mode 100644
index 0000000..17c9d5b
--- /dev/null
+++ b/frontend/src/components/SitesList/components/ConnectionInfo.tsx
@@ -0,0 +1,28 @@
+import classNames from "classnames";
+import get from "lodash/get";
+
+import type { Site } from "@/api/types";
+
+// Stable, Lost and "Waiting for first" are the only possible values
+export const connectionIcons: Record<Site["connection"], string> = {
+ stable: "is-stable",
+ lost: "is-lost",
+ unknown: "is-unknown",
+} as const;
+export const connectionLabels: Record<Site["connection"], string> = {
+ stable: "Stable",
+ lost: "Lost",
+ unknown: "Waiting for first",
+} as const;
+
+type ConnectionInfoProps = { connection: Site["connection"]; lastSeen?: Site["last_seen"] };
+
+const ConnectionInfo = ({ connection, lastSeen }: ConnectionInfoProps) => (
+ <>
+ <div className={classNames("connection__text", "status-icon", get(connectionIcons, connection))}>
+ {get(connectionLabels, connection)}
+ </div>
+ <div className="connection__text u-text--muted">{lastSeen}</div>
+ </>
+);
+export default ConnectionInfo;
diff --git a/frontend/src/components/SitesList/components/SitesTable.scss b/frontend/src/components/SitesList/components/SitesTable.scss
index 37b435f..da82d6e 100644
--- a/frontend/src/components/SitesList/components/SitesTable.scss
+++ b/frontend/src/components/SitesList/components/SitesTable.scss
@@ -2,4 +2,35 @@
thead th:first-child {
width: 3rem;
}
+ th.connection {
+ padding-left: 1.5rem;
+ }
+ td.connection {
+ padding-left: 0;
+ .connection__text {
+ padding-left: 1.5rem;
+ }
+ }
+ .status-icon {
+ display: inline-block;
+ position: relative;
+ padding-left: 1.5rem;
+ }
+ .status-icon::before {
+ content: "\00B7";
+ font-size: 5rem;
+ position: absolute;
+ left: 0;
+ top: -6px;
+ color: transparent;
+ }
+ .status-icon.is-lost::before {
+ color: #c7162b;
+ }
+ .status-icon.is-stable::before {
+ color: #0e8420;
+ }
+ .status-icon.is-unknown::before {
+ color: #cdcdcd;
+ }
}
diff --git a/frontend/src/components/SitesList/components/SitesTable.tsx b/frontend/src/components/SitesList/components/SitesTable.tsx
index f32ca38..b80b413 100644
--- a/frontend/src/components/SitesList/components/SitesTable.tsx
+++ b/frontend/src/components/SitesList/components/SitesTable.tsx
@@ -6,6 +6,7 @@ import type { ColumnDef, Column } from "@tanstack/react-table";
import pick from "lodash/fp/pick";
import useLocalStorageState from "use-local-storage-state";
+import ConnectionInfo from "./ConnectionInfo";
import SitesTableControls from "./SitesTableControls";
import type { SitesQueryResult } from "@/api/types";
@@ -87,16 +88,14 @@ const SitesTable = ({
accessorFn: createAccessor(["connection", "last_seen"]),
header: () => (
<>
- <div>connection</div>
- <div className="u-text--muted">last seen</div>
- </>
- ),
- cell: ({ getValue }) => (
- <>
- <div>{getValue().connection}</div>
- <div className="u-text--muted">{getValue().last_seen}</div>
+ <div className="connection__text">connection</div>
+ <div className="connection__text u-text--muted">last seen</div>
</>
),
+ cell: ({ getValue }) => {
+ const { connection, last_seen } = getValue();
+ return connection ? <ConnectionInfo connection={connection} lastSeen={last_seen} /> : null;
+ },
},
{
id: "address",
@@ -201,7 +200,7 @@ const SitesTable = ({
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
- <th colSpan={header.colSpan} key={header.id}>
+ <th className={`${header.column.id}`} colSpan={header.colSpan} key={header.id}>
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
{header.column.getCanResize() && (
<div
@@ -224,7 +223,11 @@ const SitesTable = ({
return (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => {
- return <td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>;
+ return (
+ <td className={`${cell.column.id}`} key={cell.id}>
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+ </td>
+ );
})}
</tr>
);
diff --git a/frontend/src/mocks/factories.ts b/frontend/src/mocks/factories.ts
index afb3c09..d1dccba 100644
--- a/frontend/src/mocks/factories.ts
+++ b/frontend/src/mocks/factories.ts
@@ -4,7 +4,7 @@ import { uniqueNamesGenerator, adjectives, colors, animals } from "unique-names-
import type { Site, Token } from "@/api/types";
-const connections: Site["connection"][] = ["stable", "lost", "stale", "unstable"];
+export const connections: Site["connection"][] = ["stable", "lost", "unknown"];
export const siteFactory = Factory.define<Site>(({ sequence }) => {
const chance = new Chance(`maas-${sequence}`);
Follow ups