Skip to content

Commit 10d0798

Browse files
fix(settings): Source setting defaults MAASENG-6063 (#5942)
1 parent 7efefab commit 10d0798

File tree

20 files changed

+565
-111
lines changed

20 files changed

+565
-111
lines changed

.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
MAAS_URL = "http://maas-ui-demo.internal:5240/"
1+
MAAS_URL = "https://maas-ui-demo.internal:5443/"
22
MAAS_WEBSOCKET_HOST=
33
MAAS_WEBSOCKET_PORT=
44
BASENAME="/MAAS"

src/app/api/query/imageSources.test.ts

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import {
22
useChangeImageSource,
3+
useFetchImageSource,
34
useGetImageSource,
45
useImageSources,
6+
useUpdateImageSource,
57
} from "./imageSources";
68

7-
import type { BootSourceCreateRequest } from "@/app/apiclient";
9+
import type {
10+
BootSourceCreateRequest,
11+
BootSourceFetchRequest,
12+
BootSourceUpdateRequest,
13+
} from "@/app/apiclient";
814
import {
915
imageSourceResolvers,
1016
mockImageSources,
@@ -20,6 +26,7 @@ const mockServer = setupMockServer(
2026
imageSourceResolvers.getImageSource.handler(),
2127
imageSourceResolvers.fetchImageSource.handler(),
2228
imageSourceResolvers.createImageSource.handler(),
29+
imageSourceResolvers.updateImageSource.handler(),
2330
imageSourceResolvers.deleteImageSource.handler()
2431
);
2532

@@ -69,7 +76,7 @@ describe("useGetImageSource", () => {
6976
});
7077

7178
describe("useChangeImageSource", () => {
72-
const updatedImageSource: BootSourceCreateRequest = {
79+
const newImageSource: BootSourceCreateRequest = {
7380
url: "http://updated.images.io/",
7481
keyring_filename: "/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg",
7582
keyring_data: "newdata",
@@ -87,13 +94,10 @@ describe("useChangeImageSource", () => {
8794
const { result } = renderHookWithProviders(() => useChangeImageSource());
8895
result.current.mutate({
8996
body: {
90-
...updatedImageSource,
97+
...newImageSource,
9198
current_boot_source_id: 1,
9299
},
93100
});
94-
await waitFor(() => {
95-
expect(imageSourceResolvers.fetchImageSource.resolved).toBe(true);
96-
});
97101
await waitFor(() => {
98102
expect(imageSourceResolvers.createImageSource.resolved).toBe(true);
99103
});
@@ -105,39 +109,58 @@ describe("useChangeImageSource", () => {
105109
});
106110
});
107111

108-
it("should terminate if fetch fails", async () => {
109-
mockServer.use(imageSourceResolvers.fetchImageSource.error());
112+
it("should terminate if create fails", async () => {
113+
mockServer.use(imageSourceResolvers.createImageSource.error());
110114
const { result } = renderHookWithProviders(() => useChangeImageSource());
111115
result.current.mutate({
112116
body: {
113-
...updatedImageSource,
117+
...newImageSource,
114118
current_boot_source_id: 1,
115119
},
116120
});
117121
await waitFor(() => {
118-
expect(imageSourceResolvers.fetchImageSource.resolved).toBe(true);
122+
expect(imageSourceResolvers.createImageSource.resolved).toBe(true);
119123
});
120124
await waitFor(() => {
121125
expect(result.current.isError).toBe(true);
122126
});
123-
expect(imageSourceResolvers.createImageSource.resolved).toBe(false);
127+
expect(imageSourceResolvers.deleteImageSource.resolved).toBe(false);
124128
});
129+
});
125130

126-
it("should terminate if create fails", async () => {
127-
mockServer.use(imageSourceResolvers.createImageSource.error());
128-
const { result } = renderHookWithProviders(() => useChangeImageSource());
131+
describe("useUpdateImageSource", () => {
132+
it("should update a pool", async () => {
133+
const updatedImageSource: BootSourceUpdateRequest = {
134+
keyring_filename: "/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg",
135+
keyring_data: "newdata",
136+
priority: 5,
137+
skip_keyring_verification: false,
138+
};
139+
const { result } = renderHookWithProviders(() => useUpdateImageSource());
129140
result.current.mutate({
130-
body: {
131-
...updatedImageSource,
132-
current_boot_source_id: 1,
133-
},
141+
body: updatedImageSource,
142+
path: { boot_source_id: 1 },
134143
});
135144
await waitFor(() => {
136-
expect(imageSourceResolvers.createImageSource.resolved).toBe(true);
145+
expect(result.current.isSuccess).toBe(true);
146+
});
147+
});
148+
});
149+
150+
describe("useFetchImageSource", () => {
151+
it("should fetch the available images of the source", async () => {
152+
const newImageSource: BootSourceFetchRequest = {
153+
url: "http://updated.images.io/",
154+
keyring_filename: "/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg",
155+
keyring_data: "newdata",
156+
skip_keyring_verification: false,
157+
};
158+
const { result } = renderHookWithProviders(() => useFetchImageSource());
159+
result.current.mutate({
160+
body: newImageSource,
137161
});
138162
await waitFor(() => {
139-
expect(result.current.isError).toBe(true);
163+
expect(result.current.isSuccess).toBe(true);
140164
});
141-
expect(imageSourceResolvers.deleteImageSource.resolved).toBe(false);
142165
});
143166
});

src/app/api/query/imageSources.ts

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
22

33
import { useWebsocketAwareQuery } from "@/app/api/query/base";
44
import { IMAGES_WORKFLOW_KEY } from "@/app/api/query/images";
5-
import { queryOptionsWithHeaders } from "@/app/api/utils";
5+
import {
6+
mutationOptionsWithHeaders,
7+
queryOptionsWithHeaders,
8+
} from "@/app/api/utils";
69
import type {
710
GetBootsourceData,
811
GetBootsourceErrors,
@@ -15,8 +18,15 @@ import type {
1518
CreateBootsourceData,
1619
CreateBootsourceErrors,
1720
CreateBootsourceResponses,
21+
UpdateBootsourceData,
22+
UpdateBootsourceResponses,
23+
UpdateBootsourceErrors,
24+
FetchBootsourcesAvailableImagesData,
25+
FetchBootsourcesAvailableImagesResponses,
26+
FetchBootsourcesAvailableImagesErrors,
1827
} from "@/app/apiclient";
1928
import {
29+
updateBootsource,
2030
createBootsource,
2131
deleteBootsource,
2232
fetchBootsourcesAvailableImages,
@@ -63,24 +73,13 @@ export const useChangeImageSource = () => {
6373
Options<CreateBootsourceData & { body: { current_boot_source_id: number } }>
6474
>({
6575
mutationFn: async (params) => {
66-
// Step 1: Fetch to validate source using URL from createData
67-
await fetchBootsourcesAvailableImages({
68-
body: {
69-
url: params.body.url,
70-
keyring_filename: params.body.keyring_filename,
71-
keyring_data: params.body.keyring_data,
72-
skip_keyring_verification: params.body.skip_keyring_verification,
73-
},
74-
throwOnError: true,
75-
});
76-
77-
// Step 2: Create new source
76+
// Step 1: Create new source
7877
const createResult = await createBootsource({
7978
...params,
8079
throwOnError: true,
8180
});
8281

83-
// Step 3: Delete old source
82+
// Step 2: Delete old source
8483
await deleteBootsource({
8584
path: { boot_source_id: params.body.current_boot_source_id },
8685
throwOnError: true,
@@ -101,3 +100,33 @@ export const useChangeImageSource = () => {
101100
},
102101
});
103102
};
103+
104+
export const useUpdateImageSource = (
105+
mutationOptions?: Options<UpdateBootsourceData>
106+
) => {
107+
const queryClient = useQueryClient();
108+
return useMutation({
109+
...mutationOptionsWithHeaders<
110+
UpdateBootsourceResponses,
111+
UpdateBootsourceErrors,
112+
UpdateBootsourceData
113+
>(mutationOptions, updateBootsource),
114+
onSuccess: () => {
115+
return queryClient.invalidateQueries({
116+
queryKey: listBootsourcesQueryKey(),
117+
});
118+
},
119+
});
120+
};
121+
122+
export const useFetchImageSource = (
123+
mutationOptions?: Options<FetchBootsourcesAvailableImagesData>
124+
) => {
125+
return useMutation({
126+
...mutationOptionsWithHeaders<
127+
FetchBootsourcesAvailableImagesResponses,
128+
FetchBootsourcesAvailableImagesErrors,
129+
FetchBootsourcesAvailableImagesData
130+
>(mutationOptions, fetchBootsourcesAvailableImages),
131+
});
132+
};

src/app/base/components/FormikForm/FormikForm.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ const FormikForm = <V extends object, E = null>({
4242
saving,
4343
savingLabel,
4444
secondarySubmit,
45+
secondarySubmitSaved,
46+
secondarySubmitSaving,
4547
secondarySubmitDisabled,
4648
secondarySubmitLabel,
4749
secondarySubmitTooltip,
@@ -82,6 +84,8 @@ const FormikForm = <V extends object, E = null>({
8284
secondarySubmit={secondarySubmit}
8385
secondarySubmitDisabled={secondarySubmitDisabled}
8486
secondarySubmitLabel={secondarySubmitLabel}
87+
secondarySubmitSaved={secondarySubmitSaved}
88+
secondarySubmitSaving={secondarySubmitSaving}
8589
secondarySubmitTooltip={secondarySubmitTooltip}
8690
submitAppearance={submitAppearance}
8791
submitDisabled={submitDisabled}

src/app/base/components/FormikFormButtons/FormikFormButtons.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export type Props<V> = {
2323
saving?: boolean;
2424
savingLabel?: string | null;
2525
secondarySubmit?: FormikContextFunc<V> | null;
26+
secondarySubmitSaved?: boolean;
27+
secondarySubmitSaving?: boolean;
2628
secondarySubmitDisabled?: boolean;
2729
secondarySubmitLabel?: FormikContextFunc<V, string> | string | null;
2830
secondarySubmitTooltip?: string | null;
@@ -62,6 +64,8 @@ export const FormikFormButtons = <V,>({
6264
saving,
6365
savingLabel,
6466
secondarySubmit,
67+
secondarySubmitSaved,
68+
secondarySubmitSaving,
6569
secondarySubmitDisabled,
6670
secondarySubmitLabel,
6771
secondarySubmitTooltip,
@@ -81,25 +85,28 @@ export const FormikFormButtons = <V,>({
8185
? secondarySubmitLabel(values, formikContext)
8286
: secondarySubmitLabel;
8387
const button = (
84-
<Button
88+
<ActionButton
89+
appearance="default"
8590
className="formik-form-buttons__button"
8691
data-testid={TestIds.SecondarySubmit}
8792
disabled={
8893
buttonsBehavior === "coupled"
8994
? secondarySubmitDisabled || submitDisabled
9095
: secondarySubmitDisabled
9196
}
97+
loading={secondarySubmitSaving}
9298
onClick={
9399
secondarySubmit
94100
? () => {
95101
secondarySubmit(values, formikContext);
96102
}
97103
: undefined
98104
}
105+
success={secondarySubmitSaved}
99106
type="button"
100107
>
101108
{secondaryLabel}
102-
</Button>
109+
</ActionButton>
103110
);
104111
if (secondarySubmitTooltip) {
105112
secondaryButton = (

src/app/images/constants.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
import type { Accept } from "react-dropzone";
22

3+
export const MAAS_IO_URLS = {
4+
stable: "http://images.maas.io/ephemeral-v3/stable/",
5+
candidate: "http://images.maas.io/ephemeral-v3/candidate/",
6+
} as const;
7+
8+
export const MAAS_IO_DEFAULT_KEYRING_FILE_PATHS = {
9+
deb: "/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg",
10+
snap: "/snap/maas/current/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg",
11+
};
12+
313
export const MAAS_IO_DEFAULTS = {
4-
url: "http://images.maas.io/ephemeral-v3/stable/",
14+
url: MAAS_IO_URLS.stable,
515
keyring_filename:
616
"/snap/maas/current/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg",
717
keyring_data: "",

src/app/images/views/ImageList/ImageListHeader/ImageListHeader.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ import { useSidePanel } from "@/app/base/side-panel-context";
1111
import DeleteImages from "@/app/images/components/DeleteImages";
1212
import SelectUpstreamImagesForm from "@/app/images/components/SelectUpstreamImagesForm";
1313
import UploadCustomImage from "@/app/images/components/UploadCustomImage";
14-
import { MAAS_IO_DEFAULTS } from "@/app/images/constants";
15-
import { BootResourceSourceType } from "@/app/images/types";
14+
import { MAAS_IO_URLS } from "@/app/images/constants";
1615

1716
type ImageListHeaderProps = {
1817
selectedRows: RowSelectionState;
@@ -22,13 +21,10 @@ type ImageListHeaderProps = {
2221
const getImageSyncText = (sources: BootSourceResponse[]) => {
2322
if (sources.length === 1) {
2423
const mainSource = sources[0];
25-
const sourceType = new RegExp(MAAS_IO_DEFAULTS.url).test(
26-
mainSource.url ?? ""
27-
)
28-
? BootResourceSourceType.MAAS_IO
29-
: BootResourceSourceType.CUSTOM;
30-
if (sourceType === BootResourceSourceType.MAAS_IO) {
24+
if (new RegExp(MAAS_IO_URLS.stable).test(mainSource.url ?? "")) {
3125
return "maas.io";
26+
} else if (new RegExp(MAAS_IO_URLS.candidate).test(mainSource.url ?? "")) {
27+
return "maas.io (candidate)";
3228
}
3329
return mainSource.url;
3430
}

0 commit comments

Comments
 (0)