Skip to content
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-10604-changed-1719003322690.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Changed
---

Auto-populate Image label based on Linode and Disk names ([#10604](https://github.com/linode/manager/pull/10604))
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ describe('create image (e2e)', () => {
cy.findByLabelText('Label')
.should('be.enabled')
.should('be.visible')
.clear()
.type(label);

// Give the Image a description
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ describe('create image (using mocks)', () => {
cy.findByLabelText('Label')
.should('be.enabled')
.should('be.visible')
.clear()
.type(mockNewImage.label);

// Give the Image a description
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,71 @@ describe('CreateImageTab', () => {
// Verify encryption notice renders
await findByText('Virtual Machine Images are not encrypted.');
});

it('should auto-populate image label based on linode and disk', async () => {
const linode = linodeFactory.build();
const disk1 = linodeDiskFactory.build();
const disk2 = linodeDiskFactory.build();
const image = imageFactory.build();

server.use(
http.get('*/v4/linode/instances', () => {
return HttpResponse.json(makeResourcePage([linode]));
}),
http.get('*/v4/linode/instances/:id', () => {
return HttpResponse.json(linode);
}),
http.get('*/v4/linode/instances/:id/disks', () => {
return HttpResponse.json(makeResourcePage([disk1, disk2]));
}),
http.post('*/v4/images', () => {
return HttpResponse.json(image);
})
);

const { findByText, getByLabelText, queryByText } = renderWithTheme(
<CreateImageTab />
);

const linodeSelect = getByLabelText('Linode');

await userEvent.click(linodeSelect);

const linodeOption = await findByText(linode.label);

await userEvent.click(linodeOption);

const diskSelect = getByLabelText('Disk');

// Once a Linode is selected, the Disk select should become enabled
expect(diskSelect).toBeEnabled();
expect(queryByText('Select a Linode to see available disks')).toBeNull();

await userEvent.click(diskSelect);

const diskOption = await findByText(disk1.label);

await userEvent.click(diskOption);

// Image label should auto-populate
const imageLabel = getByLabelText('Label');
expect(imageLabel).toHaveValue(`${linode.label}-${disk1.label}`);

// Image label should update
await userEvent.click(diskSelect);

const disk2Option = await findByText(disk2.label);
await userEvent.click(disk2Option);

expect(imageLabel).toHaveValue(`${linode.label}-${disk2.label}`);

// Image label should not override user input
const customLabel = 'custom-label';
await userEvent.clear(imageLabel);
await userEvent.type(imageLabel, customLabel);
expect(imageLabel).toHaveValue(customLabel);
await userEvent.click(diskSelect);
await userEvent.click(diskOption);
expect(imageLabel).toHaveValue(customLabel);
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { yupResolver } from '@hookform/resolvers/yup';
import { CreateImagePayload } from '@linode/api-v4';
import { createImageSchema } from '@linode/validation';
import { useSnackbar } from 'notistack';
import * as React from 'react';
Expand Down Expand Up @@ -32,17 +31,16 @@ import { useLinodeQuery } from 'src/queries/linodes/linodes';
import { useGrants } from 'src/queries/profile/profile';
import { useRegionsQuery } from 'src/queries/regions/regions';

export const CreateImageTab = () => {
const [selectedLinodeId, setSelectedLinodeId] = React.useState<null | number>(
null
);
import type { CreateImagePayload } from '@linode/api-v4';

export const CreateImageTab = () => {
const {
control,
formState,
handleSubmit,
resetField,
setError,
setValue,
watch,
} = useForm<CreateImagePayload>({
mode: 'onBlur',
Expand Down Expand Up @@ -89,6 +87,15 @@ export const CreateImageTab = () => {
}
});

const [selectedLinodeId, setSelectedLinodeId] = React.useState<null | number>(
null
);

const { data: selectedLinode } = useLinodeQuery(
selectedLinodeId ?? -1,
selectedLinodeId !== null
);

const {
data: disks,
error: disksError,
Expand All @@ -99,18 +106,30 @@ export const CreateImageTab = () => {
const selectedDisk =
disks?.find((disk) => disk.id === selectedDiskId) ?? null;

React.useEffect(() => {
if (formState.touchedFields.label) {
return;
}
if (selectedLinode) {
setValue('label', `${selectedLinode.label}-${selectedDisk?.label ?? ''}`);
} else {
resetField('label');
}
}, [
selectedLinode,
selectedDisk,
formState.touchedFields.label,
setValue,
resetField,
]);

const isRawDisk = selectedDisk?.filesystem === 'raw';

const { data: regionsData } = useRegionsQuery();

const { data: linode } = useLinodeQuery(
selectedLinodeId ?? -1,
selectedLinodeId !== null
);

const linodeIsInDistributedRegion = getIsDistributedRegion(
regionsData ?? [],
linode?.region ?? ''
selectedLinode?.region ?? ''
);

/*
Expand Down