Skip to content

Commit a8668eb

Browse files
committed
upcoming: [DPS-34039] CR changes 1
1 parent 2efb613 commit a8668eb

File tree

8 files changed

+366
-239
lines changed

8 files changed

+366
-239
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { screen } from '@testing-library/react';
2+
import { userEvent } from '@testing-library/user-event';
3+
import * as React from 'react';
4+
5+
import { streamFactory } from 'src/factories/datastream';
6+
import { StreamActionMenu } from 'src/features/DataStream/Streams/StreamActionMenu';
7+
import { renderWithTheme } from 'src/utilities/testHelpers';
8+
9+
import type { StreamStatus } from '@linode/api-v4';
10+
11+
const fakeHandler = vi.fn();
12+
13+
describe('Stream action menu', () => {
14+
const renderComponent = (status: StreamStatus) => {
15+
renderWithTheme(
16+
<StreamActionMenu
17+
onDelete={fakeHandler}
18+
onDisableOrEnable={fakeHandler}
19+
onEdit={fakeHandler}
20+
stream={streamFactory.build({ status })}
21+
/>
22+
);
23+
};
24+
25+
describe('when stream is active', () => {
26+
it('should include proper Stream actions', async () => {
27+
renderComponent('active');
28+
29+
const actionMenuButton = screen.queryByLabelText(/^Action menu for/)!;
30+
31+
await userEvent.click(actionMenuButton);
32+
33+
for (const action of ['Edit', 'Disable', 'Delete']) {
34+
expect(screen.getByText(action)).toBeVisible();
35+
}
36+
});
37+
});
38+
39+
describe('when stream is inactive', () => {
40+
it('should include proper Stream actions', async () => {
41+
renderComponent('inactive');
42+
43+
const actionMenuButton = screen.queryByLabelText(/^Action menu for/)!;
44+
45+
await userEvent.click(actionMenuButton);
46+
47+
for (const action of ['Edit', 'Enable', 'Delete']) {
48+
expect(screen.getByText(action)).toBeVisible();
49+
}
50+
});
51+
});
52+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { type Stream, streamStatus } from '@linode/api-v4';
2+
import * as React from 'react';
3+
4+
import { ActionMenu } from 'src/components/ActionMenu/ActionMenu';
5+
6+
export interface Handlers {
7+
onDelete: (stream: Stream) => void;
8+
onDisableOrEnable: (stream: Stream) => void;
9+
onEdit: (stream: Stream) => void;
10+
}
11+
12+
interface StreamActionMenuProps extends Handlers {
13+
stream: Stream;
14+
}
15+
16+
export const StreamActionMenu = (props: StreamActionMenuProps) => {
17+
const { stream, onDelete, onDisableOrEnable, onEdit } = props;
18+
19+
const menuActions = [
20+
{
21+
onClick: () => {
22+
onEdit(stream);
23+
},
24+
title: 'Edit',
25+
},
26+
{
27+
onClick: () => {
28+
onDisableOrEnable(stream);
29+
},
30+
title: stream.status === streamStatus.Active ? 'Disable' : 'Enable',
31+
},
32+
{
33+
onClick: () => {
34+
onDelete(stream);
35+
},
36+
title: 'Delete',
37+
},
38+
];
39+
40+
return (
41+
<ActionMenu
42+
actionsList={menuActions}
43+
ariaLabel={`Action menu for Stream ${stream.label}`}
44+
/>
45+
);
46+
};

packages/manager/src/features/DataStream/Streams/StreamCreate/StreamCreate.tsx

Whitespace-only changes.

packages/manager/src/features/DataStream/Streams/StreamTableRow.test.tsx

Lines changed: 15 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -11,54 +11,23 @@ import {
1111
wrapWithTableBody,
1212
} from 'src/utilities/testHelpers';
1313

14-
const queryMocks = vi.hoisted(() => ({
15-
useNavigate: vi.fn(() => vi.fn()),
16-
useUpdateStreamMutation: vi.fn().mockReturnValue({
17-
mutateAsync: vi.fn(),
18-
}),
19-
useDeleteStreamMutation: vi.fn().mockReturnValue({
20-
mutateAsync: vi.fn(),
21-
}),
22-
}));
23-
24-
vi.mock('@tanstack/react-router', async () => {
25-
const actual = await vi.importActual('@tanstack/react-router');
26-
return {
27-
...actual,
28-
useNavigate: queryMocks.useNavigate,
29-
};
30-
});
31-
32-
vi.mock('@linode/queries', async () => {
33-
const actual = await vi.importActual('@linode/queries');
34-
return {
35-
...actual,
36-
useUpdateStreamMutation: queryMocks.useUpdateStreamMutation,
37-
useDeleteStreamMutation: queryMocks.useDeleteStreamMutation,
38-
};
39-
});
14+
const fakeHandler = vi.fn();
4015

4116
describe('StreamTableRow', () => {
4217
const stream = { ...streamFactory.build(), id: 1 };
4318

44-
const renderStreamTableRow = () => {
19+
it('should render a stream row', async () => {
4520
mockMatchMedia();
46-
renderWithTheme(wrapWithTableBody(<StreamTableRow stream={stream} />));
47-
};
48-
49-
const clickOnActionMenu = async () => {
50-
const actionMenu = screen.getByLabelText(
51-
`Action menu for ${stream.label} stream`
21+
renderWithTheme(
22+
wrapWithTableBody(
23+
<StreamTableRow
24+
onDelete={fakeHandler}
25+
onDisableOrEnable={fakeHandler}
26+
onEdit={fakeHandler}
27+
stream={stream}
28+
/>
29+
)
5230
);
53-
await userEvent.click(actionMenu);
54-
};
55-
56-
const clickOnActionMenuItem = async (itemText: string) => {
57-
await userEvent.click(screen.getByText(itemText));
58-
};
59-
60-
it('should render a stream row', async () => {
61-
renderStreamTableRow();
6231

6332
// Name:
6433
screen.getByText('Data Stream 1');
@@ -73,89 +42,13 @@ describe('StreamTableRow', () => {
7342
// Creation Time:
7443
screen.getByText(/2025-07-30/);
7544

76-
await clickOnActionMenu();
45+
const actionMenu = screen.getByLabelText(
46+
`Action menu for Stream ${stream.label}`
47+
);
48+
await userEvent.click(actionMenu);
7749

7850
expect(screen.getByText('Edit')).toBeVisible();
7951
expect(screen.getByText('Disable')).toBeVisible();
8052
expect(screen.getByText('Delete')).toBeVisible();
8153
});
82-
83-
describe('given action menu', () => {
84-
describe('when Edit clicked', () => {
85-
it('should navigate to edit page', async () => {
86-
const mockNavigate = vi.fn();
87-
queryMocks.useNavigate.mockReturnValue(mockNavigate);
88-
89-
renderStreamTableRow();
90-
await clickOnActionMenu();
91-
await clickOnActionMenuItem('Edit');
92-
93-
expect(mockNavigate).toHaveBeenCalledWith({
94-
to: '/datastream/streams/1/edit',
95-
});
96-
});
97-
});
98-
99-
describe('when Disable clicked', () => {
100-
it('should update stream with proper parameters', async () => {
101-
const mockUpdateStreamMutation = vi.fn().mockResolvedValue({});
102-
queryMocks.useUpdateStreamMutation.mockReturnValue({
103-
mutateAsync: mockUpdateStreamMutation,
104-
});
105-
106-
renderStreamTableRow();
107-
await clickOnActionMenu();
108-
await clickOnActionMenuItem('Disable');
109-
110-
expect(mockUpdateStreamMutation).toHaveBeenCalledWith({
111-
id: 1,
112-
status: 'inactive',
113-
label: 'Data Stream 1',
114-
destinations: [123],
115-
details: {},
116-
type: 'audit_logs',
117-
});
118-
});
119-
});
120-
121-
describe('when Enabled clicked', () => {
122-
it('should update stream with proper parameters', async () => {
123-
const mockUpdateStreamMutation = vi.fn().mockResolvedValue({});
124-
queryMocks.useUpdateStreamMutation.mockReturnValue({
125-
mutateAsync: mockUpdateStreamMutation,
126-
});
127-
128-
stream.status = 'inactive';
129-
renderStreamTableRow();
130-
await clickOnActionMenu();
131-
await clickOnActionMenuItem('Enable');
132-
133-
expect(mockUpdateStreamMutation).toHaveBeenCalledWith({
134-
id: 1,
135-
status: 'active',
136-
label: 'Data Stream 1',
137-
destinations: [123],
138-
details: {},
139-
type: 'audit_logs',
140-
});
141-
});
142-
});
143-
144-
describe('when Delete clicked', () => {
145-
it('should update stream with proper parameters', async () => {
146-
const mockDeleteStreamMutation = vi.fn().mockResolvedValue({});
147-
queryMocks.useDeleteStreamMutation.mockReturnValue({
148-
mutateAsync: mockDeleteStreamMutation,
149-
});
150-
151-
renderStreamTableRow();
152-
await clickOnActionMenu();
153-
await clickOnActionMenuItem('Delete');
154-
155-
expect(mockDeleteStreamMutation).toHaveBeenCalledWith({
156-
id: 1,
157-
});
158-
});
159-
});
160-
});
16154
});

packages/manager/src/features/DataStream/Streams/StreamTableRow.tsx

Lines changed: 9 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
1-
import { streamStatus } from '@linode/api-v4';
2-
import {
3-
useDeleteStreamMutation,
4-
useUpdateStreamMutation,
5-
} from '@linode/queries';
61
import { Hidden } from '@linode/ui';
7-
import { useNavigate } from '@tanstack/react-router';
8-
import { enqueueSnackbar } from 'notistack';
92
import * as React from 'react';
103

11-
import { ActionMenu } from 'src/components/ActionMenu/ActionMenu';
124
import { DateTimeDisplay } from 'src/components/DateTimeDisplay';
135
import { StatusIcon } from 'src/components/StatusIcon/StatusIcon';
146
import { TableCell } from 'src/components/TableCell';
@@ -17,21 +9,17 @@ import {
179
getDestinationTypeOption,
1810
getStreamTypeOption,
1911
} from 'src/features/DataStream/dataStreamUtils';
20-
import { getAPIErrorOrDefault } from 'src/utilities/errorUtils';
12+
import { StreamActionMenu } from 'src/features/DataStream/Streams/StreamActionMenu';
2113

14+
import type { Handlers as StreamHandlers } from './StreamActionMenu';
2215
import type { Stream, StreamStatus } from '@linode/api-v4';
2316

24-
interface StreamTableRowProps {
17+
interface StreamTableRowProps extends StreamHandlers {
2518
stream: Stream;
2619
}
2720

2821
export const StreamTableRow = React.memo((props: StreamTableRowProps) => {
29-
const { stream } = props;
30-
31-
const navigate = useNavigate();
32-
33-
const { mutateAsync: updateStream } = useUpdateStreamMutation();
34-
const { mutateAsync: deleteStream } = useDeleteStreamMutation();
22+
const { stream, onDelete, onDisableOrEnable, onEdit } = props;
3523

3624
return (
3725
<TableRow key={stream.id}>
@@ -53,79 +41,11 @@ export const StreamTableRow = React.memo((props: StreamTableRowProps) => {
5341
</TableCell>
5442
</Hidden>
5543
<TableCell actionCell>
56-
<ActionMenu
57-
actionsList={[
58-
{
59-
onClick: () => {
60-
navigate({ to: `/datastream/streams/${stream.id}/edit` });
61-
},
62-
title: 'Edit',
63-
},
64-
{
65-
onClick: () => {
66-
updateStream({
67-
id: stream.id,
68-
destinations: stream.destinations.map(({ id }) => id),
69-
details: stream.details,
70-
label: stream.label,
71-
type: stream.type,
72-
status:
73-
stream.status === streamStatus.Active
74-
? streamStatus.Inactive
75-
: streamStatus.Active,
76-
})
77-
.then(() => {
78-
return enqueueSnackbar(
79-
`Stream ${stream.label} ${stream.status === streamStatus.Active ? 'disabled' : 'enabled'}`,
80-
{
81-
variant: 'success',
82-
}
83-
);
84-
})
85-
.catch((error) => {
86-
return enqueueSnackbar(
87-
getAPIErrorOrDefault(
88-
error,
89-
`There was an issue ${stream.status === streamStatus.Active ? 'disabling' : 'enabling'} your stream`
90-
)[0].reason,
91-
{
92-
variant: 'error',
93-
}
94-
);
95-
});
96-
},
97-
title:
98-
stream.status === streamStatus.Active ? 'Disable' : 'Enable',
99-
},
100-
{
101-
onClick: () => {
102-
deleteStream({
103-
id: stream.id,
104-
})
105-
.then(() => {
106-
return enqueueSnackbar(
107-
`Stream ${stream.label} deleted successfully`,
108-
{
109-
variant: 'success',
110-
}
111-
);
112-
})
113-
.catch((error) => {
114-
return enqueueSnackbar(
115-
getAPIErrorOrDefault(
116-
error,
117-
`There was an issue deleting your stream`
118-
)[0].reason,
119-
{
120-
variant: 'error',
121-
}
122-
);
123-
});
124-
},
125-
title: 'Delete',
126-
},
127-
]}
128-
ariaLabel={`Action menu for ${stream.label} stream`}
44+
<StreamActionMenu
45+
onDelete={onDelete}
46+
onDisableOrEnable={onDisableOrEnable}
47+
onEdit={onEdit}
48+
stream={stream}
12949
/>
13050
</TableCell>
13151
</TableRow>

0 commit comments

Comments
 (0)