diff --git a/foo b/foo
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/ui/.betterer.results b/ui/.betterer.results
index 572d24cc13..7eb68d235e 100644
--- a/ui/.betterer.results
+++ b/ui/.betterer.results
@@ -7,7 +7,7 @@ exports[`stricter compilation`] = {
[162, 4, 36, "Object is possibly \'null\'.", "1039669632"]
],
"src/app/App.tsx:3872899616": [
- [21, 7, 25, "Could not find a declaration file for module \'@maas-ui/maas-ui-shared\'. \'/home/caleb/Projects/maas-ui/shared/dist/index.js\' implicitly has an \'any\' type.\\n Try \`npm install @types/maas-ui__maas-ui-shared\` if it exists or add a new declaration (.d.ts) file containing \`declare module \'@maas-ui/maas-ui-shared\';\`", "1778274862"],
+ [21, 7, 25, "Could not find a declaration file for module \'@maas-ui/maas-ui-shared\'. \'/Users/kit/src/canonical/local/maas-ui/shared/dist/index.js\' implicitly has an \'any\' type.\\n Try \`npm install @types/maas-ui__maas-ui-shared\` if it exists or add a new declaration (.d.ts) file containing \`declare module \'@maas-ui/maas-ui-shared\';\`", "1778274862"],
[188, 17, 17, "Object is possibly \'null\'.", "2133029343"],
[193, 7, 7, "Variable \'content\' is used before being assigned.", "3716929964"]
],
@@ -44,7 +44,7 @@ exports[`stricter compilation`] = {
[75, 4, 5, "Argument of type \'boolean | undefined\' is not assignable to parameter of type \'boolean\'.\\n Type \'undefined\' is not assignable to type \'boolean\'.", "195688512"]
],
"src/app/base/components/LegacyLink/LegacyLink.tsx:2706551295": [
- [4, 52, 25, "Could not find a declaration file for module \'@maas-ui/maas-ui-shared\'. \'/home/caleb/Projects/maas-ui/shared/dist/index.js\' implicitly has an \'any\' type.\\n Try \`npm install @types/maas-ui__maas-ui-shared\` if it exists or add a new declaration (.d.ts) file containing \`declare module \'@maas-ui/maas-ui-shared\';\`", "1778274862"]
+ [4, 52, 25, "Could not find a declaration file for module \'@maas-ui/maas-ui-shared\'. \'/Users/kit/src/canonical/local/maas-ui/shared/dist/index.js\' implicitly has an \'any\' type.\\n Try \`npm install @types/maas-ui__maas-ui-shared\` if it exists or add a new declaration (.d.ts) file containing \`declare module \'@maas-ui/maas-ui-shared\';\`", "1778274862"]
],
"src/app/base/components/NotificationGroup/Notification/Notification.tsx:122297593": [
[26, 26, 12, "Argument of type \'Notification | null\' is not assignable to parameter of type \'Notification\'.\\n Type \'null\' is not assignable to type \'Notification\'.\\n Type \'null\' is not assignable to type \'Model\'.", "148512008"],
@@ -86,7 +86,7 @@ exports[`stricter compilation`] = {
[214, 7, 11, "Property \'placeholder\' is missing in type \'{ disabledTags: { id: number; name: string; }[]; initialSelected: { id: number; name: string; }[]; tags: { id: number; name: string; }[]; }\' but required in type \'Props\'.", "3766634306"]
],
"src/app/base/components/TagSelector/TagSelector.tsx:2755544058": [
- [1, 18, 51, "Could not find a declaration file for module \'@canonical/react-components/dist/components/Field\'. \'/home/caleb/Projects/maas-ui/node_modules/@canonical/react-components/dist/components/Field/index.js\' implicitly has an \'any\' type.\\n Try \`npm install @types/canonical__react-components\` if it exists or add a new declaration (.d.ts) file containing \`declare module \'@canonical/react-components/dist/components/Field\';\`", "1535046059"],
+ [1, 18, 51, "Could not find a declaration file for module \'@canonical/react-components/dist/components/Field\'. \'/Users/kit/src/canonical/local/maas-ui/node_modules/@canonical/react-components/dist/components/Field/index.js\' implicitly has an \'any\' type.\\n Try \`npm install @types/canonical__react-components\` if it exists or add a new declaration (.d.ts) file containing \`declare module \'@canonical/react-components/dist/components/Field\';\`", "1535046059"],
[37, 2, 12, "Binding element \'allowNewTags\' implicitly has an \'any\' type.", "3979358209"],
[38, 2, 6, "Binding element \'filter\' implicitly has an \'any\' type.", "1355726373"],
[39, 2, 12, "Binding element \'selectedTags\' implicitly has an \'any\' type.", "2698915821"],
@@ -298,7 +298,7 @@ exports[`stricter compilation`] = {
[50, 6, 8, "Type \'(values: MarkBrokenFormValues) => void\' is not assignable to type \'(...args: unknown[]) => void\'.\\n Types of parameters \'values\' and \'args\' are incompatible.\\n Type \'unknown\' is not assignable to type \'MarkBrokenFormValues\'.", "1301647696"],
[53, 27, 10, "Property \'markBroken\' does not exist on type \'{ fetch: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { model: any; method: string; }>; create: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { ...; }>; update: ActionCreatorWithPreparedPayload<...>; delete: ActionCreatorWithPreparedPayload<......\'.", "3004692879"]
],
- "src/app/machines/components/ActionFormWrapper/TestForm/TestForm.test.tsx:3198696153": [
+ "src/app/machines/components/ActionFormWrapper/TestForm/TestForm.test.tsx:3774525721": [
[95, 6, 66, "Cannot invoke an object which is possibly \'undefined\'.", "4166470712"],
[99, 10, 15, "Argument of type \'{ enableSSH: boolean; scripts: Scripts[]; scriptInputs: { \\"internet-connectivity\\": string; }; }\' is not assignable to parameter of type \'FormEvent<{}>\'.\\n Object literal may only specify known properties, and \'enableSSH\' does not exist in type \'FormEvent<{}>\'.", "2907345984"],
[216, 6, 66, "Cannot invoke an object which is possibly \'undefined\'.", "4166470712"],
@@ -346,23 +346,23 @@ exports[`stricter compilation`] = {
[40, 28, 3, "Property \'get\' does not exist on type \'{ fetch: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { model: any; method: string; }>; create: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { ...; }>; update: ActionCreatorWithPreparedPayload<...>; delete: ActionCreatorWithPreparedPayload<......\'.", "193411891"],
[105, 42, 10, "Property \'checkPower\' does not exist on type \'{ fetch: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { model: any; method: string; }>; create: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { ...; }>; update: ActionCreatorWithPreparedPayload<...>; delete: ActionCreatorWithPreparedPayload<......\'.", "953482588"]
],
- "src/app/machines/views/MachineDetails/MachineSummary/MachineSummary.tsx:564355202": [
+ "src/app/machines/views/MachineDetails/MachineSummary/MachineSummary.tsx:2439231865": [
[42, 28, 3, "Property \'get\' does not exist on type \'{ fetch: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { model: any; method: string; }>; create: ActionCreatorWithPreparedPayload<[params?: any], { params: any; }, string, never, { ...; }>; update: ActionCreatorWithPreparedPayload<...>; delete: ActionCreatorWithPreparedPayload<......\'.", "193411891"]
],
"src/app/machines/views/MachineDetails/MachineSummary/NumaCard/NumaCard.test.tsx:2502469861": [
[33, 27, 10, "Property \'numa_nodes\' does not exist on type \'Machine\'.\\n Property \'numa_nodes\' does not exist on type \'BaseMachine\'.", "3857696382"]
],
- "src/app/machines/views/MachineDetails/MachineSummary/OverviewCard/CpuCard/CpuCard.tsx:461133352": [
- [17, 18, 36, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'MachineDetails\'.\\n No index signature with a parameter of type \'string\' was found on type \'MachineDetails\'.", "830072625"],
- [146, 18, 7, "Type \'string | null\' is not assignable to type \'string | undefined\'.\\n Type \'null\' is not assignable to type \'string | undefined\'.", "1236122734"]
- ],
- "src/app/machines/views/MachineDetails/MachineSummary/OverviewCard/MemoryCard/MemoryCard.tsx:2120332270": [
- [16, 18, 36, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'MachineDetails\'.\\n No index signature with a parameter of type \'string\' was found on type \'MachineDetails\'.", "830072625"],
- [119, 16, 7, "Type \'string | null\' is not assignable to type \'string | undefined\'.\\n Type \'null\' is not assignable to type \'string | undefined\'.", "1236122734"]
- ],
- "src/app/machines/views/MachineDetails/MachineSummary/OverviewCard/StorageCard/StorageCard.tsx:3582856219": [
- [17, 18, 36, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'MachineDetails\'.\\n No index signature with a parameter of type \'string\' was found on type \'MachineDetails\'.", "830072625"],
- [127, 16, 7, "Type \'string | null\' is not assignable to type \'string | undefined\'.\\n Type \'null\' is not assignable to type \'string | undefined\'.", "1236122734"]
+ "src/app/machines/views/MachineDetails/MachineSummary/TestResults/TestResults.tsx:4182062532": [
+ [18, 18, 36, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'MachineDetails\'.\\n No index signature with a parameter of type \'string\' was found on type \'MachineDetails\'.", "830072625"],
+ [37, 9, 36, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'MachineDetails\'.\\n No index signature with a parameter of type \'string\' was found on type \'MachineDetails\'.", "830072625"],
+ [52, 15, 36, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'MachineDetails\'.\\n No index signature with a parameter of type \'string\' was found on type \'MachineDetails\'.", "830072625"],
+ [57, 9, 36, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'MachineDetails\'.\\n No index signature with a parameter of type \'string\' was found on type \'MachineDetails\'.", "830072625"],
+ [58, 10, 36, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'MachineDetails\'.\\n No index signature with a parameter of type \'string\' was found on type \'MachineDetails\'.", "830072625"],
+ [74, 15, 36, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'MachineDetails\'.\\n No index signature with a parameter of type \'string\' was found on type \'MachineDetails\'.", "830072625"],
+ [75, 16, 36, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'MachineDetails\'.\\n No index signature with a parameter of type \'string\' was found on type \'MachineDetails\'.", "830072625"],
+ [80, 9, 36, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'MachineDetails\'.\\n No index signature with a parameter of type \'string\' was found on type \'MachineDetails\'.", "830072625"],
+ [95, 15, 36, "Element implicitly has an \'any\' type because expression of type \'string\' can\'t be used to index type \'MachineDetails\'.\\n No index signature with a parameter of type \'string\' was found on type \'MachineDetails\'.", "830072625"],
+ [120, 14, 7, "Type \'string | null\' is not assignable to type \'string | undefined\'.\\n Type \'null\' is not assignable to type \'string | undefined\'.", "1236122734"]
],
"src/app/machines/views/MachineList/MachineListHeader/MachineListHeader.tsx:539216384": [
[104, 10, 17, "Type \'(action: MachineAction, deselect?: boolean | undefined) => void\' is not assignable to type \'SetSelectedAction\'.\\n Types of parameters \'action\' and \'action\' are incompatible.\\n Type \'SelectedAction | null\' is not assignable to type \'MachineAction\'.\\n Type \'null\' is not assignable to type \'MachineAction\'.", "167402512"],
diff --git a/ui/src/app/base/enum.ts b/ui/src/app/base/enum.ts
index 46b8629496..ccbb7792e2 100644
--- a/ui/src/app/base/enum.ts
+++ b/ui/src/app/base/enum.ts
@@ -68,7 +68,7 @@ export const scriptStatus = {
export enum HardwareType {
Node = 0,
- Cpu = 1,
+ CPU = 1,
Memory = 2,
Storage = 3,
Network = 4,
diff --git a/ui/src/app/machines/components/ActionFormWrapper/TestForm/TestForm.test.tsx b/ui/src/app/machines/components/ActionFormWrapper/TestForm/TestForm.test.tsx
index 35f0a408ce..50ab36e3bb 100644
--- a/ui/src/app/machines/components/ActionFormWrapper/TestForm/TestForm.test.tsx
+++ b/ui/src/app/machines/components/ActionFormWrapper/TestForm/TestForm.test.tsx
@@ -162,7 +162,7 @@ describe("TestForm", () => {
state.scripts.items = [
networkScript,
scriptsFactory({
- hardware_type: HardwareType.Cpu,
+ hardware_type: HardwareType.CPU,
type: ScriptType.Testing,
}),
scriptsFactory({
diff --git a/ui/src/app/machines/views/MachineDetails/MachineSummary/MachineSummary.tsx b/ui/src/app/machines/views/MachineDetails/MachineSummary/MachineSummary.tsx
index 94bd6e8cbd..e025a7ac52 100644
--- a/ui/src/app/machines/views/MachineDetails/MachineSummary/MachineSummary.tsx
+++ b/ui/src/app/machines/views/MachineDetails/MachineSummary/MachineSummary.tsx
@@ -52,7 +52,7 @@ const MachineSummary = ({ setSelectedAction }: Props): JSX.Element => {
-
+
);
};
diff --git a/ui/src/app/machines/views/MachineDetails/MachineSummary/NetworkCard/NetworkCard.test.tsx b/ui/src/app/machines/views/MachineDetails/MachineSummary/NetworkCard/NetworkCard.test.tsx
index e354de452f..129cf21baf 100644
--- a/ui/src/app/machines/views/MachineDetails/MachineSummary/NetworkCard/NetworkCard.test.tsx
+++ b/ui/src/app/machines/views/MachineDetails/MachineSummary/NetworkCard/NetworkCard.test.tsx
@@ -11,6 +11,7 @@ import {
machineState as machineStateFactory,
rootState as rootStateFactory,
vlanState as vlanStateFactory,
+ testStatus as testStatusFactory,
} from "testing/factories";
import NetworkCard from "./NetworkCard";
import type { RootState } from "app/store/root/types";
@@ -39,7 +40,9 @@ describe("NetworkCard", () => {
}
+ component={() => (
+
+ )}
/>
@@ -79,7 +82,9 @@ describe("NetworkCard", () => {
}
+ component={() => (
+
+ )}
/>
@@ -144,7 +149,9 @@ describe("NetworkCard", () => {
}
+ component={() => (
+
+ )}
/>
@@ -155,4 +162,132 @@ describe("NetworkCard", () => {
expect(wrapper.find("Table").at(2).find("tbody TableRow").length).toBe(2);
expect(wrapper.find("Table").at(3).find("tbody TableRow").length).toBe(1);
});
+
+ describe("test results", () => {
+ it("renders a link with a count of passed tests", () => {
+ const machine = machineDetailsFactory({
+ system_id: "abc123",
+ });
+
+ machine.network_test_status = testStatusFactory({
+ passed: 2,
+ });
+ state.machine.items = [machine];
+
+ const store = mockStore(state);
+ const wrapper = mount(
+
+
+
+
+
+ );
+
+ expect(
+ wrapper.find("[data-test='tests']").childAt(0).find("Button").text()
+ ).toEqual("2");
+ });
+
+ it("renders a link with a count of pending and running tests", () => {
+ const machine = machineDetailsFactory({
+ system_id: "abc123",
+ });
+
+ machine.network_test_status = testStatusFactory({
+ running: 1,
+ pending: 2,
+ });
+ state.machine.items = [machine];
+
+ const store = mockStore(state);
+ const wrapper = mount(
+
+
+
+
+
+ );
+
+ expect(
+ wrapper.find("[data-test='tests']").childAt(0).find("Button").text()
+ ).toEqual("3");
+ });
+
+ it("renders a link with a count of failed tests", () => {
+ const machine = machineDetailsFactory({
+ system_id: "abc123",
+ });
+ machine.network_test_status = testStatusFactory({
+ failed: 5,
+ });
+ state.machine.items = [machine];
+
+ const store = mockStore(state);
+ const wrapper = mount(
+
+
+
+
+
+ );
+
+ expect(
+ wrapper.find("[data-test='tests']").childAt(0).find("Button").text()
+ ).toEqual("5");
+ });
+
+ it("renders a results link", () => {
+ const machine = machineDetailsFactory({
+ system_id: "abc123",
+ });
+ machine.network_test_status = testStatusFactory({
+ failed: 5,
+ });
+ state.machine.items = [machine];
+
+ const store = mockStore(state);
+ const wrapper = mount(
+
+
+
+
+
+ );
+
+ expect(
+ wrapper.find("[data-test='tests']").childAt(1).find("Button").text()
+ ).toContain("View results");
+ });
+
+ it("renders a test storage link if no tests run", () => {
+ const machine = machineDetailsFactory({
+ system_id: "abc123",
+ });
+ machine.network_test_status = testStatusFactory();
+ state.machine.items = [machine];
+
+ const store = mockStore(state);
+ const wrapper = mount(
+
+
+
+
+
+ );
+
+ expect(
+ wrapper.find("[data-test='tests']").childAt(0).find("Button").text()
+ ).toContain("Test network");
+ });
+ });
});
diff --git a/ui/src/app/machines/views/MachineDetails/MachineSummary/NetworkCard/NetworkCard.tsx b/ui/src/app/machines/views/MachineDetails/MachineSummary/NetworkCard/NetworkCard.tsx
index 5b45695106..2ba4bf06a6 100644
--- a/ui/src/app/machines/views/MachineDetails/MachineSummary/NetworkCard/NetworkCard.tsx
+++ b/ui/src/app/machines/views/MachineDetails/MachineSummary/NetworkCard/NetworkCard.tsx
@@ -3,14 +3,17 @@ import { useDispatch, useSelector } from "react-redux";
import { Link } from "react-router-dom";
import React, { Fragment, useEffect } from "react";
+import NetworkCardTable from "./NetworkCardTable";
+import { SetSelectedAction } from "../MachineSummary";
+import TestResults from "../TestResults";
+import { HardwareType } from "app/base/enum";
import { actions as fabricActions } from "app/store/fabric";
-import { actions as vlanActions } from "app/store/vlan";
import fabricSelectors from "app/store/fabric/selectors";
import machineSelectors from "app/store/machine/selectors";
-import vlanSelectors from "app/store/vlan/selectors";
import type { Machine, NetworkInterface } from "app/store/machine/types";
+import { actions as vlanActions } from "app/store/vlan";
+import vlanSelectors from "app/store/vlan/selectors";
import type { RootState } from "app/store/root/types";
-import NetworkCardTable from "./NetworkCardTable";
type InterfaceGroup = {
firmwareVersion: string;
@@ -21,6 +24,7 @@ type InterfaceGroup = {
type Props = {
id: Machine["system_id"];
+ setSelectedAction: SetSelectedAction;
};
/**
@@ -92,7 +96,7 @@ const groupInterfaces = (interfaces: NetworkInterface[]): InterfaceGroup[] => {
return sortedGroups;
};
-const NetworkCard = ({ id }: Props): JSX.Element => {
+const NetworkCard = ({ id, setSelectedAction }: Props): JSX.Element => {
const dispatch = useDispatch();
const machine = useSelector((state: RootState) =>
machineSelectors.getById(state, id)
@@ -144,6 +148,12 @@ const NetworkCard = ({ id }: Props): JSX.Element => {
Information about tagged traffic can be seen in the{" "}
Network tab.
+
+
>
);
} else {
diff --git a/ui/src/app/machines/views/MachineDetails/MachineSummary/OverviewCard/CpuCard/CpuCard.tsx b/ui/src/app/machines/views/MachineDetails/MachineSummary/OverviewCard/CpuCard/CpuCard.tsx
index 47725b10bd..30bfa641ed 100644
--- a/ui/src/app/machines/views/MachineDetails/MachineSummary/OverviewCard/CpuCard/CpuCard.tsx
+++ b/ui/src/app/machines/views/MachineDetails/MachineSummary/OverviewCard/CpuCard/CpuCard.tsx
@@ -1,26 +1,16 @@
import pluralize from "pluralize";
import React from "react";
-import { Link } from "react-router-dom";
-
-import { Button, Icon, ICONS, Tooltip } from "@canonical/react-components";
import type { SetSelectedAction } from "../../MachineSummary";
-import { useSendAnalytics } from "app/base/hooks";
import type { MachineDetails } from "app/store/machine/types";
import { HardwareType } from "app/base/enum";
+import TestResults from "../../TestResults";
type Props = {
machine: MachineDetails;
setSelectedAction: SetSelectedAction;
};
-const hasTestsRun = (machine: MachineDetails, scriptType: string) => {
- const testObj = machine[`${scriptType}_test_status`];
- return (
- testObj.passed + testObj.pending + testObj.running + testObj.failed > 0
- );
-};
-
// Get the subtext for the CPU card. Only nodes commissioned after
// MAAS 2.4 will have the CPU speed.
const getCPUSubtext = (machine: MachineDetails) => {
@@ -39,146 +29,27 @@ const getCPUSubtext = (machine: MachineDetails) => {
return text;
};
-const CpuCard = ({ machine, setSelectedAction }: Props): JSX.Element => {
- const sendAnalytics = useSendAnalytics();
-
- const testsTabUrl = `/machine/${machine.system_id}/tests`;
-
- return (
- <>
-