Skip to content

Commit 0009c37

Browse files
authored
Merge branch 'master' into post_install_zypp_lock
2 parents 3e5c64d + a9e203c commit 0009c37

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2522
-1583
lines changed

rust/agama-lib/share/zfcp.schema.json

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,34 @@
1717
"devices": {
1818
"description": "List of zFCP devices.",
1919
"type": "array",
20-
"items": {
21-
"type": "object",
22-
"additionalProperties": false,
23-
"required": ["channel", "wwpn", "lun"],
24-
"properties": {
25-
"channel": {
26-
"description": "zFCP controller channel id.",
27-
"type": "string",
28-
"examples": ["0.0.fa00"]
29-
},
30-
"wwpn": {
31-
"description": "WWPN of the target port.",
32-
"type": "string",
33-
"examples": ["0x500507630300c562"]
34-
},
35-
"lun": {
36-
"description": "LUN of the SCSI device.",
37-
"type": "string",
38-
"examples": ["0x4010403300000000"]
39-
},
40-
"active": {
41-
"description": "Whether to activate the device.",
42-
"type": "boolean",
43-
"default": true
44-
}
20+
"items": { "$ref": "#/$defs/device" }
21+
}
22+
},
23+
"$defs": {
24+
"device": {
25+
"type": "object",
26+
"additionalProperties": false,
27+
"required": ["channel", "wwpn", "lun"],
28+
"properties": {
29+
"channel": {
30+
"description": "zFCP controller channel id.",
31+
"type": "string",
32+
"examples": ["0.0.fa00"]
33+
},
34+
"wwpn": {
35+
"description": "WWPN of the target port.",
36+
"type": "string",
37+
"examples": ["0x500507630300c562"]
38+
},
39+
"lun": {
40+
"description": "LUN of the SCSI device.",
41+
"type": "string",
42+
"examples": ["0x4010403300000000"]
43+
},
44+
"active": {
45+
"description": "Whether to activate the device.",
46+
"type": "boolean",
47+
"default": true
4548
}
4649
}
4750
}

web/src/assets/styles/index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
--pf-t--global--background--color--disabled--default: #dcdbdc;
137137
--pf-t--global--border--radius--pill: var(--pf-t--global--border--radius--small);
138138
--pf-t--global--color--brand--clicked: var(--agm-t--color--pine);
139+
--pf-t--global--text--color--link--default: var(--agm-t--color--pine);
139140
}
140141

141142
/*

web/src/components/core/SimpleSelector.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import type { TranslatedString } from "~/i18n";
3838
type SimpleSelectorProps = {
3939
label: TranslatedString;
4040
value: string;
41-
options: Record<string, TranslatedString>;
41+
options: Record<string, string>;
4242
onChange: SelectProps["onSelect"];
4343
};
4444

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: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@
2121
*/
2222

2323
import React from "react";
24+
import { isEmpty } from "radashi";
2425
import { sprintf } from "sprintf-js";
26+
import { Content } from "@patternfly/react-core";
2527
import Summary from "~/components/core/Summary";
2628
import Link from "~/components/core/Link";
29+
import Text from "~/components/core/Text";
2730
import { useProgressTracking } from "~/hooks/use-progress-tracking";
2831
import { useConfigModel } from "~/hooks/model/storage/config-model";
2932
import {
@@ -82,16 +85,33 @@ const ModelSummary = ({ model }: { model: ConfigModel.Config }): React.ReactNode
8285
return <SingleDeviceSummary target={targets[0]} />;
8386
};
8487

88+
const InvalidZFCP = (): React.ReactNode => {
89+
// TRANSLATORS: The text in [] is used as a link.
90+
const text = _("Invalid [zFCP] settings");
91+
const [textStart, textLink, textEnd] = text.split(/[[\]]/);
92+
return (
93+
<Content>
94+
{textStart}
95+
<Link to={STORAGE.zfcp.root} variant="link" isInline>
96+
<Text isBold>{textLink}</Text>
97+
</Link>
98+
{textEnd}
99+
</Content>
100+
);
101+
};
102+
85103
const Value = () => {
86104
const availableDevices = useAvailableDevices();
87105
const model = useConfigModel();
88106
const issues = useIssues("storage");
107+
const zfcpIssues = useIssues("zfcp");
89108
const configIssues = issues.filter((i) => i.class !== "proposal");
90109

91-
if (!availableDevices.length) return _("There are no disks available for the installation");
92-
if (configIssues.length) {
110+
if (isEmpty(availableDevices)) return _("There are no disks available for the installation");
111+
if (!isEmpty(configIssues)) {
93112
return _("Invalid settings");
94113
}
114+
if (!isEmpty(zfcpIssues)) return <InvalidZFCP />;
95115

96116
if (!model) return _("Using an advanced storage configuration");
97117

@@ -103,17 +123,18 @@ const Description = () => {
103123
const staging = useProposalFlattenDevices();
104124
const actions = useActions();
105125
const issues = useIssues("storage");
126+
const zfcpIssues = useIssues("zfcp");
106127
const configIssues = issues.filter((i) => i.class !== "proposal");
107128
const manager = new DevicesManager(system, staging, actions);
108129

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

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

115136
const systems = manager.deletedSystems();
116-
if (systems.length) {
137+
if (!isEmpty(systems)) {
117138
return sprintf(
118139
// TRANSLATORS: %s will be replaced by a formatted list of affected systems
119140
// like "Windows and openSUSE Tumbleweed".
@@ -155,7 +176,10 @@ const Description = () => {
155176
*/
156177
export default function StorageSummary() {
157178
const { loading } = useProgressTracking("storage");
158-
const hasIssues = !!useIssues("storage").length;
179+
const issues = useIssues("storage");
180+
const zfcpIssues = useIssues("zfcp");
181+
182+
const hasIssues = !isEmpty(issues) || !isEmpty(zfcpIssues);
159183

160184
return (
161185
<Summary

web/src/components/storage/ConnectedDevicesMenu.test.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ jest.mock("~/hooks/model/system/dasd", () => ({
3838
useSystem: () => mockUseDASDSystem(),
3939
}));
4040

41+
const mockUseZFCPSystem = jest.fn();
42+
jest.mock("~/hooks/model/system/zfcp", () => ({
43+
...jest.requireActual("~/hooks/model/system/zfcp"),
44+
useSystem: () => mockUseZFCPSystem(),
45+
}));
46+
4147
async function openMenu() {
4248
const { user } = installerRender(<ConnectedDevicesMenu />);
4349
const button = screen.getByRole("button", { name: "More storage options" });
@@ -68,11 +74,9 @@ it("allows users to configure iSCSI", async () => {
6874
});
6975

7076
describe("if zFCP is not supported", () => {
71-
/*
7277
beforeEach(() => {
73-
mockUseZFCPSupported.mockReturnValue(false);
78+
mockUseZFCPSystem.mockReturnValue(null);
7479
});
75-
*/
7680

7781
it("does not allow users to configure zFCP", async () => {
7882
const { menu } = await openMenu();
@@ -81,12 +85,10 @@ describe("if zFCP is not supported", () => {
8185
});
8286
});
8387

84-
describe.skip("if zFCP is supported", () => {
85-
/*
88+
describe("if zFCP is supported", () => {
8689
beforeEach(() => {
87-
mockUseZFCPSupported.mockReturnValue(true);
90+
mockUseZFCPSystem.mockReturnValue({});
8891
});
89-
*/
9092

9193
it("allows users to configure zFCP", async () => {
9294
const { user, menu } = await openMenu();
@@ -98,7 +100,7 @@ describe.skip("if zFCP is supported", () => {
98100

99101
describe("if DASD is not supported", () => {
100102
beforeEach(() => {
101-
mockUseDASDSystem.mockReturnValue(undefined);
103+
mockUseDASDSystem.mockReturnValue(null);
102104
});
103105

104106
it("does not allow users to configure DASD", async () => {

web/src/components/storage/ConnectedDevicesMenu.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ import { STORAGE } from "~/routes/paths";
2727
import Icon from "~/components/layout/Icon";
2828
import MenuButton from "~/components/core/MenuButton";
2929
import { useSystem as useDASDSystem } from "~/hooks/model/system/dasd";
30+
import { useSystem as useZFCPSystem } from "~/hooks/model/system/zfcp";
3031
import { _ } from "~/i18n";
3132

3233
export default function ConnectedDevicesMenu() {
3334
const navigate = useNavigate();
34-
const isZFCPSupported = false;
3535
const dasdSystem = useDASDSystem();
36+
const zfcpSystem = useZFCPSystem();
3637

3738
return (
3839
<MenuButton
@@ -55,7 +56,7 @@ export default function ConnectedDevicesMenu() {
5556
>
5657
{_("Configure iSCSI")}
5758
</MenuButton.Item>,
58-
isZFCPSupported && (
59+
zfcpSystem && (
5960
<MenuButton.Item
6061
key="zfcp-link"
6162
onClick={() => navigate(STORAGE.zfcp.root)}

web/src/components/storage/ProposalPage.test.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const mockUseConfigModel = jest.fn();
6565
const mockUseProposal = jest.fn();
6666
const mockUseIssues = jest.fn();
6767
const mockUseDASDSystem = jest.fn();
68+
const mockUseZFCPSystem = jest.fn();
6869

6970
jest.mock("~/hooks/model/system/storage", () => ({
7071
...jest.requireActual("~/hooks/model/system/storage"),
@@ -96,6 +97,11 @@ jest.mock("~/hooks/model/system/dasd", () => ({
9697
useSystem: () => mockUseDASDSystem(),
9798
}));
9899

100+
jest.mock("~/hooks/model/system/zfcp", () => ({
101+
...jest.requireActual("~/hooks/model/system/zfcp"),
102+
useSystem: () => mockUseZFCPSystem(),
103+
}));
104+
99105
jest.mock("./ProposalFailedInfo", () => () => <div>proposal failed info</div>);
100106
jest.mock("./UnsupportedModelInfo", () => () => <div>unsupported model info</div>);
101107
jest.mock("./FixableConfigInfo", () => () => <div>fixable config info</div>);
@@ -133,9 +139,9 @@ describe("if there are no devices", () => {
133139
});
134140

135141
describe("if zFCP is not supported", () => {
136-
// beforeEach(() => {
137-
// mockUseZFCPSupported.mockReturnValue(false);
138-
// });
142+
beforeEach(() => {
143+
mockUseZFCPSystem.mockReturnValue(null);
144+
});
139145

140146
it("does not render an option for activating zFCP", () => {
141147
installerRender(<ProposalPage />);
@@ -145,7 +151,7 @@ describe("if there are no devices", () => {
145151

146152
describe("if DASD is not supported", () => {
147153
beforeEach(() => {
148-
mockUseDASDSystem.mockReturnValue(undefined);
154+
mockUseDASDSystem.mockReturnValue(null);
149155
});
150156

151157
it("does not render an option for activating DASD", () => {
@@ -154,10 +160,10 @@ describe("if there are no devices", () => {
154160
});
155161
});
156162

157-
describe.skip("if zFCP is supported", () => {
158-
// beforeEach(() => {
159-
// mockUseZFCPSupported.mockReturnValue(true);
160-
// });
163+
describe("if zFCP is supported", () => {
164+
beforeEach(() => {
165+
mockUseZFCPSystem.mockReturnValue({});
166+
});
161167

162168
it("renders an option for activating zFCP", () => {
163169
installerRender(<ProposalPage />);

0 commit comments

Comments
 (0)