Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [M3-6752] - Assign Linodes to Subnet drawer #9687

Merged
merged 39 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
582318e
starting the subnet assign drawer (very much a wip)
coliu-akamai Sep 14, 2023
e61c5aa
assigning linode to subnet logic
coliu-akamai Sep 15, 2023
bf43cbe
accidentally removed queryClient
coliu-akamai Sep 15, 2023
2a6c376
error handling of assigning a linode
coliu-akamai Sep 18, 2023
5318375
using autocomplete for unassigning linodes for now
coliu-akamai Sep 18, 2023
fbd3330
formatting
coliu-akamai Sep 18, 2023
c51340c
unassign linode while in assign drawer
coliu-akamai Sep 18, 2023
572aae2
unit tests and some error handling
coliu-akamai Sep 18, 2023
741019f
error handling and other cleanup
coliu-akamai Sep 19, 2023
169e305
use list not autocomplete, download linodes, dismissable banner
coliu-akamai Sep 19, 2023
ccf410f
cleanup + new naming convention
coliu-akamai Sep 19, 2023
e86ee49
Merge branch 'develop' into feat-m3-6752
coliu-akamai Sep 19, 2023
869a46c
update comments, make csvHeaders consistent with unassign linodes drawer
coliu-akamai Sep 20, 2023
fe35f89
feedback: first pass
coliu-akamai Sep 20, 2023
e664729
feedback: create custom hook + logic for up-to-date subnet
coliu-akamai Sep 20, 2023
26eff18
feedback: pass 2 - custom hook
coliu-akamai Sep 21, 2023
d9c5abf
feedback: pass 3 - revert some changes ;-; + new component
coliu-akamai Sep 21, 2023
62ef660
update changeset
coliu-akamai Sep 21, 2023
f1141ec
storybook and tests for new component
coliu-akamai Sep 21, 2023
a328b4c
feedback: remove styled downloadcsv, put back in useEffect, update na…
coliu-akamai Sep 21, 2023
2cb4fb1
wip
jaalah Sep 22, 2023
105f2b7
Merge pull request #3 from jaalah-akamai/example-1234
coliu-akamai Sep 22, 2023
9cf605d
remove duplicate useEffect, other changes
coliu-akamai Sep 22, 2023
e381a49
update headers
coliu-akamai Sep 22, 2023
afce9c2
Update packages/api-v4/.changeset/pr-9687-changed-1695235363178.md
coliu-akamai Sep 22, 2023
f8cb601
remove reliance on API erroring when unassigning linodes
coliu-akamai Sep 22, 2023
67f5ee3
delete regions.json file take 2...
coliu-akamai Sep 22, 2023
33ae047
Merge branch 'develop' into feat-m3-6752
coliu-akamai Sep 22, 2023
61202a0
update comments, styling, and disabling/error handling
coliu-akamai Sep 22, 2023
79168c2
get back regions file
coliu-akamai Sep 22, 2023
4ec8f85
feedback
coliu-akamai Sep 25, 2023
b982c43
initial feedback
coliu-akamai Sep 25, 2023
3694419
trying to get rid of mui warning
coliu-akamai Sep 25, 2023
ea9ead2
think it's working now
coliu-akamai Sep 25, 2023
52203b3
remove unnecessary dependencies
coliu-akamai Sep 25, 2023
e615eae
documentation
coliu-akamai Sep 25, 2023
8869caa
Merge pull request #4 from coliu-akamai/assign-linodes-test
coliu-akamai Sep 25, 2023
54f5a0c
update available IP address text
coliu-akamai Sep 25, 2023
0ee481a
Update changelog
coliu-akamai Sep 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/api-v4/.changeset/pr-9687-changed-1695235363178.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/api-v4": Upcoming Features
---

Add type `DeleteLinodeConfigInterfacePayload` for deleting Linode config interfaces ([#9687](https://github.com/linode/manager/pull/9687))
4 changes: 2 additions & 2 deletions packages/api-v4/src/linodes/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
UpdateConfigInterfaceOrderSchema,
UpdateConfigInterfaceSchema,
UpdateLinodeConfigSchema,
linodeInterfaceSchema,
LinodeInterfaceSchema,
coliu-akamai marked this conversation as resolved.
Show resolved Hide resolved
} from '@linode/validation/lib/linodes.schema';
import { API_ROOT } from '../constants';
import Request, {
Expand Down Expand Up @@ -180,7 +180,7 @@ export const appendConfigInterface = (
)}/configs/${encodeURIComponent(configId)}/interfaces`
),
setMethod('POST'),
setData(data, linodeInterfaceSchema)
setData(data, LinodeInterfaceSchema)
);

/**
Expand Down
6 changes: 6 additions & 0 deletions packages/api-v4/src/linodes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,9 @@ export interface ResizeLinodePayload {
/** @default true */
allow_auto_disk_resize?: boolean;
}

export interface DeleteLinodeConfigInterfacePayload {
linodeId: number;
configId: number;
interfaceId: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Subnet Assign Linodes Drawer and new list component, `RemovableSelectionsList` ([#9687](https://github.com/linode/manager/pull/9687))
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const CollapsibleRow = (props: Props) => {
<StyledOuterTableRow>
<TableCell component="th" scope="row">
<IconButton
aria-label="expand row"
aria-label={`${label} expand row`}
onClick={() => setOpen(!open)}
size="small"
sx={{ padding: 1 }}
Expand Down
33 changes: 23 additions & 10 deletions packages/manager/src/components/DownloadCSV/DownloadCSV.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { SxProps } from '@mui/system';
import * as React from 'react';
import { CSVLink } from 'react-csv';

import DownloadIcon from 'src/assets/icons/lke-download.svg';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes in this file match @jaalah-akamai 's ticket for the Unassign Linode subnet drawer (copy-paste tbh), since they're super similar -- may have merge conflicts later

import { Button } from 'src/components/Button/Button';
import { StyledLinkButton } from 'src/components/Button/StyledLinkButton';

import type { ButtonType } from 'src/components/Button/Button';

interface DownloadCSVProps {
buttonType?: ButtonType;
buttonType?: 'styledLink' | ButtonType;
children?: React.ReactNode;
className?: string;
csvRef?: React.RefObject<any>;
Expand Down Expand Up @@ -39,14 +41,13 @@ export const DownloadCSV = ({
sx,
text = 'Download CSV',
}: DownloadCSVProps) => {
return (
<CSVLink
className={className}
data={cleanCSVData(data)}
filename={filename}
headers={headers}
ref={csvRef}
>
const renderButton =
buttonType === 'styledLink' ? (
<StyledLinkButton onClick={onClick} sx={sx}>
<DownloadIcon />
{text}
</StyledLinkButton>
) : (
<Button
buttonType={buttonType}
component="span"
Expand All @@ -57,7 +58,19 @@ export const DownloadCSV = ({
>
{text}
</Button>
</CSVLink>
);

return (
<>
<CSVLink
className={className}
data={cleanCSVData(data)}
filename={filename}
headers={headers}
ref={csvRef}
/>
{renderButton}
</>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import * as React from 'react';

import { Button } from '../Button/Button';
import { RemovableSelectionsList } from './RemovableSelectionsList';

import type { RemovableItem } from './RemovableSelectionsList';
import type { Meta, StoryObj } from '@storybook/react';

type Story = StoryObj<typeof RemovableSelectionsList>;

const defaultListItems = Array.from({ length: 20 }, (_, index) => {
const num = index + 1;
return { id: num, label: `my-linode-${num}` };
});

const diffLabelListItems = Array.from({ length: 5 }, (_, index) => {
const num = index + 1;
return {
id: num,
label: `my-linode-${num}`,
preferredLabel: `my-linode-preferred-${num}`,
};
});

interface Dimensions {
maxHeight?: number;
maxWidth?: number;
}
const DefaultRemovableSelectionsListWrapper = (props: Dimensions) => {
const { maxHeight, maxWidth } = props;
const [data, setData] = React.useState(defaultListItems);

const handleRemove = (item: RemovableItem) => {
setData([...data].filter((data) => data.id !== item.id));
};

const resetList = () => {
setData([...defaultListItems]);
};

return (
<>
<RemovableSelectionsList
headerText="Linodes to remove"
maxHeight={maxHeight}
maxWidth={maxWidth}
noDataText="No Linodes available"
onRemove={handleRemove}
selectionData={data}
/>
<Button onClick={resetList} sx={{ marginTop: 2 }}>
Reset list
</Button>
</>
);
};

/**
* Interactable example of a RemovableSelectionsList
*/
export const InteractableDefault: Story = {
render: () => <DefaultRemovableSelectionsListWrapper />,
};

/**
* Example of a RemovableSelectionsList with a specified label option. Data passed in
* has the shape of _{ id: number; label: string; preferredLabel: string; }_, where the
* content in _preferredLabel_ is being rendered for each list item's label here.
*/
export const SpecifiedLabelExample: Story = {
render: () => {
const SpecifiedLabelWrapper = () => {
const [data, setData] = React.useState(diffLabelListItems);

const handleRemove = (item: RemovableItem) => {
setData([...data].filter((data) => data.id !== item.id));
};

const resetList = () => {
setData([...diffLabelListItems]);
};

return (
<>
<RemovableSelectionsList
headerText="Linodes to remove"
noDataText="No Linodes available"
onRemove={handleRemove}
preferredDataLabel="preferredLabel"
selectionData={data}
/>
<Button onClick={resetList} sx={{ marginTop: 2 }}>
Reset list
</Button>
</>
);
};

return <SpecifiedLabelWrapper />;
},
};

/**
* Example of a RemovableSelectionsList with a custom height and width
*/
export const CustomHeightAndWidth: Story = {
render: () => (
<DefaultRemovableSelectionsListWrapper maxHeight={300} maxWidth={200} />
),
};

/**
* Example of a RemovableSelectionsList with no data to remove
*/
export const NoDataExample: Story = {
args: {
headerText: 'Linodes to remove',
noDataText: 'No Linodes available',
onRemove: (_) => {},
selectionData: [],
},
render: (args) => {
return <RemovableSelectionsList {...args} />;
},
};

const meta: Meta<typeof RemovableSelectionsList> = {
component: RemovableSelectionsList,
title: 'Components/RemovableSelectionsList',
};

export default meta;
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { fireEvent } from '@testing-library/react';
import * as React from 'react';

import { renderWithTheme } from 'src/utilities/testHelpers';

import { RemovableSelectionsList } from './RemovableSelectionsList';

const defaultList = Array.from({ length: 5 }, (_, index) => {
const num = index + 1;
return {
id: num,
label: `my-linode-${num}`,
preferredLabel: `my-linode-preferred-${num}`,
};
});

const diffLabelList = Array.from({ length: 5 }, (_, index) => {
const num = index + 1;
return {
id: num,
label: `my-linode-${num}`,
preferredLabel: `my-linode-preferred-${num}`,
};
});

const props = {
headerText: 'Linodes to remove',
noDataText: 'No Linodes available',
onRemove: jest.fn(),
selectionData: defaultList,
};

describe('Removable Selections List', () => {
it('should render the list correctly', () => {
const screen = renderWithTheme(<RemovableSelectionsList {...props} />);
const header = screen.getByText('Linodes to remove');
expect(header).toBeVisible();

for (let i = 0; i < 5; i++) {
const data = screen.getByText(`my-linode-${i + 1}`);
expect(data).toBeVisible();
const removeButton = screen.getByLabelText(`remove my-linode-${i + 1}`);
expect(removeButton).toBeInTheDocument();
}
});

it('should display the no data text if there is no data', () => {
const screen = renderWithTheme(
<RemovableSelectionsList {...props} selectionData={[]} />
);
const header = screen.getByText('Linodes to remove');
expect(header).toBeVisible();
const removable = screen.getByText('No Linodes available');
expect(removable).toBeVisible();
});

it('should render the preferred label option if that is provided', () => {
const screen = renderWithTheme(
<RemovableSelectionsList
{...props}
preferredDataLabel="preferredLabel"
selectionData={diffLabelList}
/>
);
const header = screen.getByText('Linodes to remove');
expect(header).toBeVisible();

for (let i = 0; i < 5; i++) {
const data = screen.getByText(`my-linode-preferred-${i + 1}`);
expect(data).toBeVisible();
const removeButton = screen.getByLabelText(
`remove my-linode-preferred-${i + 1}`
);
expect(removeButton).toBeInTheDocument();
}
});

it('should call the onRemove function', () => {
const screen = renderWithTheme(<RemovableSelectionsList {...props} />);
const removeButton = screen.getByLabelText(`remove my-linode-1`);
fireEvent.click(removeButton);
expect(props.onRemove).toHaveBeenCalled();
});
});
Loading