Skip to content

Commit 3c36bd6

Browse files
committed
Alert about zFCP issues in the summary
1 parent 51564ee commit 3c36bd6

File tree

2 files changed

+56
-18
lines changed

2 files changed

+56
-18
lines changed

web/src/components/overview/StorageSummary.test.tsx

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
useFlattenDevices as useProposalFlattenDevices,
3030
useActions,
3131
} from "~/hooks/model/proposal/storage";
32-
import { useIssues } from "~/hooks/model/issue";
32+
import * as issueHooks from "~/hooks/model/issue";
3333
import { STORAGE } from "~/routes/paths";
3434
import StorageSummary from "./StorageSummary";
3535

@@ -40,7 +40,8 @@ const mockUseDevicesFn: jest.Mock<ReturnType<typeof useDevices>> = jest.fn();
4040
const mockUseProposalFlattenDevicesFn: jest.Mock<ReturnType<typeof useProposalFlattenDevices>> =
4141
jest.fn();
4242
const mockUseActionsFn: jest.Mock<ReturnType<typeof useActions>> = jest.fn();
43-
const mockUseIssuesFn: jest.Mock<ReturnType<typeof useIssues>> = jest.fn();
43+
const mockStorageIssuesFn: jest.Mock<ReturnType<typeof issueHooks.useIssues>> = jest.fn();
44+
const mockZFCPIssuesFn: jest.Mock<ReturnType<typeof issueHooks.useIssues>> = jest.fn();
4445

4546
// Mock all the hooks
4647
jest.mock("~/hooks/model/storage/config-model", () => ({
@@ -61,10 +62,11 @@ jest.mock("~/hooks/model/proposal/storage", () => ({
6162
useActions: () => mockUseActionsFn(),
6263
}));
6364

64-
jest.mock("~/hooks/model/issue", () => ({
65-
...jest.requireActual("~/hooks/model/issue"),
66-
useIssues: () => mockUseIssuesFn(),
67-
}));
65+
jest.spyOn(issueHooks, "useIssues").mockImplementation((scope) => {
66+
if (scope === "storage") return mockStorageIssuesFn();
67+
if (scope === "zfcp") return mockZFCPIssuesFn();
68+
return [];
69+
});
6870

6971
// Mock device for tests
7072
const mockDevice = {
@@ -99,7 +101,8 @@ describe("StorageSummary", () => {
99101
mockUseFlattenDevicesFn.mockReturnValue([]);
100102
mockUseProposalFlattenDevicesFn.mockReturnValue([]);
101103
mockUseActionsFn.mockReturnValue([]);
102-
mockUseIssuesFn.mockReturnValue([]);
104+
mockStorageIssuesFn.mockReturnValue([]);
105+
mockZFCPIssuesFn.mockReturnValue([]);
103106
});
104107

105108
afterEach(() => {
@@ -170,26 +173,39 @@ describe("StorageSummary", () => {
170173
});
171174

172175
it("shows invalid settings warning when config issues exist", () => {
173-
mockUseIssuesFn.mockReturnValue([
176+
mockStorageIssuesFn.mockReturnValue([
174177
{
175-
description: "Fake Issue",
178+
description: "Fake issue",
176179
class: "generic",
177-
details: "Fake Issue details",
180+
details: "Fake issue details",
178181
scope: "storage",
179182
},
180183
]);
181184
installerRender(<StorageSummary />);
182185
screen.getByText("Invalid settings");
183186
});
184187

188+
it("shows invalid ZFCP settings warning when ZFCP issues exist", () => {
189+
mockZFCPIssuesFn.mockReturnValue([
190+
{
191+
description: "Fake issue",
192+
class: "generic",
193+
scope: "zfcp",
194+
},
195+
]);
196+
installerRender(<StorageSummary />);
197+
screen.getByText(/Invalid/);
198+
screen.getByText(/zFCP/);
199+
});
200+
185201
it("shows advanced configuration message when model is unavailable", () => {
186202
mockUseConfigModelFn.mockReturnValue(null);
187203
installerRender(<StorageSummary />);
188204
screen.getByText("Using an advanced storage configuration");
189205
});
190206

191207
it("ignores proposal class issues when checking config validity", () => {
192-
mockUseIssuesFn.mockReturnValue([
208+
mockStorageIssuesFn.mockReturnValue([
193209
{
194210
description: "Fake Issue",
195211
class: "proposal",
@@ -254,7 +270,7 @@ describe("StorageSummary", () => {
254270
});
255271

256272
it("hides description when config issues exist", () => {
257-
mockUseIssuesFn.mockReturnValue([
273+
mockStorageIssuesFn.mockReturnValue([
258274
{
259275
description: "Fake Issue",
260276
class: "generic",

web/src/components/overview/StorageSummary.tsx

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { _, formatList } from "~/i18n";
4343

4444
import type { Storage } from "~/model/system";
4545
import type { ConfigModel } from "~/model/storage/config-model";
46+
import { isEmpty } from "radashi";
4647

4748
const findDriveDevice = (drive: ConfigModel.Drive, devices: Storage.Device[]) =>
4849
devices.find((d) => d.name === drive.name);
@@ -82,16 +83,33 @@ const ModelSummary = ({ model }: { model: ConfigModel.Config }): React.ReactNode
8283
return <SingleDeviceSummary target={targets[0]} />;
8384
};
8485

86+
const InvalidZFCP = (): React.ReactNode => {
87+
// TRANSLATORS: The text in [] is used as a link.
88+
const text = _("Invalid [zFCP] settings");
89+
const [textStart, textLink, textEnd] = text.split(/[[\]]/);
90+
return (
91+
<p>
92+
{textStart}
93+
<Link to={STORAGE.zfcp.root} variant="link" isInline>
94+
{textLink}
95+
</Link>
96+
{textEnd}
97+
</p>
98+
);
99+
};
100+
85101
const Value = () => {
86102
const availableDevices = useAvailableDevices();
87103
const model = useConfigModel();
88104
const issues = useIssues("storage");
105+
const zfcpIssues = useIssues("zfcp");
89106
const configIssues = issues.filter((i) => i.class !== "proposal");
90107

91-
if (!availableDevices.length) return _("There are no disks available for the installation");
92-
if (configIssues.length) {
108+
if (isEmpty(availableDevices)) return _("There are no disks available for the installation");
109+
if (!isEmpty(configIssues)) {
93110
return _("Invalid settings");
94111
}
112+
if (!isEmpty(zfcpIssues)) return <InvalidZFCP />;
95113

96114
if (!model) return _("Using an advanced storage configuration");
97115

@@ -103,17 +121,18 @@ const Description = () => {
103121
const staging = useProposalFlattenDevices();
104122
const actions = useActions();
105123
const issues = useIssues("storage");
124+
const zfcpIssues = useIssues("zfcp");
106125
const configIssues = issues.filter((i) => i.class !== "proposal");
107126
const manager = new DevicesManager(system, staging, actions);
108127

109-
if (configIssues.length) return;
110-
if (!actions.length) return _("Failed to calculate a storage layout");
128+
if (!isEmpty(configIssues) || !isEmpty(zfcpIssues)) return;
129+
if (isEmpty(actions)) return _("Failed to calculate a storage layout");
111130

112131
const deleteActions = manager.actions.filter((a) => a.delete && !a.subvol).length;
113132
if (!deleteActions) return _("No data loss is expected");
114133

115134
const systems = manager.deletedSystems();
116-
if (systems.length) {
135+
if (!isEmpty(systems)) {
117136
return sprintf(
118137
// TRANSLATORS: %s will be replaced by a formatted list of affected systems
119138
// like "Windows and openSUSE Tumbleweed".
@@ -155,7 +174,10 @@ const Description = () => {
155174
*/
156175
export default function StorageSummary() {
157176
const { loading } = useProgressTracking("storage");
158-
const hasIssues = !!useIssues("storage").length;
177+
const issues = useIssues("storage");
178+
const zfcpIssues = useIssues("zfcp");
179+
180+
const hasIssues = !isEmpty(issues) || !isEmpty(zfcpIssues);
159181

160182
return (
161183
<Summary

0 commit comments

Comments
 (0)