Skip to content

Commit

Permalink
feat: [M3-8016] - Add TagSelect to edit images drawer (#10466)
Browse files Browse the repository at this point in the history
* Add tags to images type

* Add tag select to ImagesDrawer

* Added changeset: TagSelect in Edit Image drawer

* Added changeset: `tags` field in `Image` type

* Further removal of unused logic

* Add unit tests for ImagesDrawer

* Fix unit tests

* Fix action menu items

* Add `tags` to `updateImageSchema`

* feedback @abailly-akamai
  • Loading branch information
hkhalil-akamai authored May 23, 2024
1 parent 32f836a commit 819356b
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 248 deletions.
5 changes: 5 additions & 0 deletions packages/api-v4/.changeset/pr-10466-added-1715709689737.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/api-v4": Added
---

`tags` field in `Image` type ([#10466](https://github.com/linode/manager/pull/10466))
4 changes: 3 additions & 1 deletion packages/api-v4/src/images/images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@ export const createImage = (data: CreateImagePayload) => {
export const updateImage = (
imageId: string,
label?: string,
description?: string
description?: string,
tags?: string[]
) => {
const data = {
...(label && { label }),
...(description && { description }),
...(tags && { tags }),
};

return Request<Image>(
Expand Down
1 change: 1 addition & 0 deletions packages/api-v4/src/images/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface Image {
expiry: null | string;
status: ImageStatus;
capabilities: ImageCapabilities[];
tags: string[];
}

export interface UploadImageResponse {
Expand Down
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-10466-added-1715709656767.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Added
---

TagSelect in Edit Image drawer ([#10466](https://github.com/linode/manager/pull/10466))
6 changes: 3 additions & 3 deletions packages/manager/src/components/TagsInput/TagsInput.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { APIError } from '@linode/api-v4/lib/types';
import { useQueryClient } from '@tanstack/react-query';
import { concat } from 'ramda';
import * as React from 'react';
import { useQueryClient } from '@tanstack/react-query';

import Select, {
Item,
Expand Down Expand Up @@ -46,15 +46,15 @@ export interface TagsInputProps {
/**
* Callback fired when the value changes.
*/
onChange: (selected: Item[]) => void;
onChange: (selected: Item<string, string>[]) => void;
/**
* An error to display beneath the input.
*/
tagError?: string;
/**
* The value of the input.
*/
value: Item[];
value: Item<string, string>[];
}

export const TagsInput = (props: TagsInputProps) => {
Expand Down
1 change: 1 addition & 0 deletions packages/manager/src/factories/images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const imageFactory = Factory.Sync.makeFactory<Image>({
label: Factory.each((i) => `image-${i}`),
size: 1500,
status: 'available',
tags: [],
type: 'manual',
updated: new Date().toISOString(),
vendor: null,
Expand Down
34 changes: 15 additions & 19 deletions packages/manager/src/features/Images/ImagesActionMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ export interface Handlers {
onCancelFailed?: (imageID: string) => void;
onDelete?: (label: string, imageID: string, status?: ImageStatus) => void;
onDeploy?: (imageID: string) => void;
onEdit?: (label: string, description: string, imageID: string) => void;
onEdit?: (
label: string,
description: string,
imageID: string,
tags: string[]
) => void;
onRestore?: (imageID: string) => void;
onRetry?: (
imageID: string,
Expand All @@ -23,6 +28,7 @@ interface Props extends Handlers {
id: string;
label: string;
status?: ImageStatus;
tags: string[];
}

export const ImagesActionMenu = (props: Props) => {
Expand All @@ -38,6 +44,7 @@ export const ImagesActionMenu = (props: Props) => {
onRestore,
onRetry,
status,
tags,
} = props;

const actions: Action[] = React.useMemo(() => {
Expand All @@ -47,53 +54,41 @@ export const ImagesActionMenu = (props: Props) => {
return isFailed
? [
{
onClick: () => {
onRetry?.(id, label, description);
},
onClick: () => onRetry?.(id, label, description),
title: 'Retry',
},
{
onClick: () => {
onCancelFailed?.(id);
},
onClick: () => onCancelFailed?.(id),
title: 'Cancel',
},
]
: [
{
disabled: isDisabled,
onClick: () => {
onEdit?.(label, description ?? ' ', id);
},
onClick: () => onEdit?.(label, description ?? ' ', id, tags),
title: 'Edit',
tooltip: isDisabled
? 'Image is not yet available for use.'
: undefined,
},
{
disabled: isDisabled,
onClick: () => {
onDeploy?.(id);
},
onClick: () => onDeploy?.(id),
title: 'Deploy to New Linode',
tooltip: isDisabled
? 'Image is not yet available for use.'
: undefined,
},
{
disabled: isDisabled,
onClick: () => {
onRestore?.(id);
},
onClick: () => onRestore?.(id),
title: 'Rebuild an Existing Linode',
tooltip: isDisabled
? 'Image is not yet available for use.'
: undefined,
},
{
onClick: () => {
onDelete?.(label, id, status);
},
onClick: () => onDelete?.(label, id, status),
title: isAvailable ? 'Delete' : 'Cancel Upload',
},
];
Expand All @@ -109,6 +104,7 @@ export const ImagesActionMenu = (props: Props) => {
onRetry,
onCancelFailed,
event,
tags,
]);

return (
Expand Down
97 changes: 97 additions & 0 deletions packages/manager/src/features/Images/ImagesDrawer.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';

import { linodeFactory } from 'src/factories';
import { makeResourcePage } from 'src/mocks/serverHandlers';
import { HttpResponse, http, server } from 'src/mocks/testServer';
import { renderWithTheme } from 'src/utilities/testHelpers';

import { ImagesDrawer, Props } from './ImagesDrawer';

const props: Props = {
changeDescription: vi.fn(),
changeDisk: vi.fn(),
changeLabel: vi.fn(),
changeLinode: vi.fn(),
changeTags: vi.fn(),
mode: 'edit',
onClose: vi.fn(),
open: true,
selectedLinode: null,
};

describe('ImagesDrawer edit mode', () => {
it('should render', async () => {
const { getByText } = renderWithTheme(
<ImagesDrawer {...props} mode="edit" />
);

// Verify title renders
getByText('Edit Image');
});

it('should allow editing image details', async () => {
const { getByLabelText, getByText } = renderWithTheme(
<ImagesDrawer {...props} mode="edit" />
);

fireEvent.change(getByLabelText('Label'), {
target: { value: 'test-image-label' },
});

fireEvent.change(getByLabelText('Description'), {
target: { value: 'test description' },
});

fireEvent.change(getByLabelText('Tags'), {
target: { value: 'new-tag' },
});
fireEvent.click(getByText('Create "new-tag"'));

fireEvent.click(getByText('Save Changes'));

expect(props.changeLabel).toBeCalledWith(
expect.objectContaining({
target: expect.objectContaining({ value: 'test-image-label' }),
})
);

expect(props.changeDescription).toBeCalledWith(
expect.objectContaining({
target: expect.objectContaining({ value: 'test description' }),
})
);

expect(props.changeTags).toBeCalledWith(['new-tag']);
});
});

describe('ImagesDrawer restore mode', () => {
it('should render', async () => {
const { getByText } = renderWithTheme(
<ImagesDrawer {...props} mode="restore" />
);

// Verify title renders
getByText('Restore from Image');
});

it('should allow editing image details', async () => {
const { findByText, getByRole, getByText } = renderWithTheme(
<ImagesDrawer {...props} mode="restore" />
);

server.use(
http.get('*/linode/instances', () => {
return HttpResponse.json(makeResourcePage(linodeFactory.buildList(5)));
})
);

await userEvent.click(getByRole('combobox'));
await userEvent.click(await findByText('linode-1'));
await userEvent.click(getByText('Restore Image'));

expect(props.changeLinode).toBeCalledWith(1);
});
});
Loading

0 comments on commit 819356b

Please sign in to comment.