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

refactor: [M3-6492, M3-6315] - React Query - Linode Detail - Backups #9079

Merged
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions packages/api-v4/src/linodes/types.ts
Original file line number Diff line number Diff line change
@@ -101,6 +101,7 @@ export interface LinodeBackup {
finished: string;
configs: string[];
disks: LinodeBackupDisk[];
available: boolean;
}

export type LinodeBackupType = 'auto' | 'snapshot';
Original file line number Diff line number Diff line change
@@ -70,7 +70,7 @@ describe('linode backups', () => {
getClick('[data-qa-confirm="true"]');
}
cy.wait('@enableBackups').its('response.statusCode').should('eq', 200);
ui.toast.assertMessage('A snapshot is being taken');
ui.toast.assertMessage('Starting to capture snapshot');
deleteLinodeById(linode.id);
});
});
5 changes: 4 additions & 1 deletion packages/manager/src/App.tsx
Original file line number Diff line number Diff line change
@@ -171,7 +171,10 @@ export class App extends React.Component<CombinedProps, State> {

events$
.filter(
({ event }) => event.action.startsWith('linode') && !event._initial
({ event }) =>
(event.action.startsWith('linode') ||
event.action.startsWith('backups')) &&
!event._initial
)
.subscribe(linodeEventsHandler);

26 changes: 26 additions & 0 deletions packages/manager/src/factories/linodes.ts
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import {
CreateLinodeRequest,
Linode,
LinodeAlerts,
LinodeBackup,
LinodeBackups,
LinodeIPsResponse,
LinodeSpecs,
@@ -216,3 +217,28 @@ export const createLinodeRequestFactory = Factory.Sync.makeFactory<CreateLinodeR
booted: true,
}
);

export const backupFactory = Factory.Sync.makeFactory<LinodeBackup>({
id: Factory.each((i) => i),
region: 'us-central',
type: 'auto',
status: 'successful',
available: true,
created: '2023-05-03T04:00:47',
updated: '2023-05-03T04:04:07',
finished: '2023-05-03T04:02:11',
label: null,
configs: ['Restore 319718 - My Alpine 3.17 Disk Profile'],
disks: [
{
label: 'Restore 319718 - Alpine 3.17 Disk',
size: 25088,
filesystem: 'ext4',
},
{
label: 'Restore 319718 - 512 MB Swap Image',
size: 512,
filesystem: 'swap',
},
],
});
Original file line number Diff line number Diff line change
@@ -14,13 +14,25 @@ import Grid from '@mui/material/Unstable_Grid2';
import Notice from 'src/components/Notice';
import RenderGuard, { RenderGuardProps } from 'src/components/RenderGuard';
import SelectionCard from 'src/components/SelectionCard';
import { aggregateBackups } from 'src/features/linodes/LinodesDetail/LinodeBackup';
import { formatDate } from 'src/utilities/formatDate';
import {
withProfile,
WithProfileProps,
} from 'src/containers/profile.container';

export const aggregateBackups = (
backups: LinodeBackupsResponse
): LinodeBackup[] => {
const manualSnapshot =
backups?.snapshot.in_progress?.status === 'needsPostProcessing'
? backups?.snapshot.in_progress
: backups?.snapshot.current;
return (
backups &&
[...backups.automatic!, manualSnapshot!].filter((b) => Boolean(b))
);
};

export interface LinodeWithBackups extends Linode {
currentBackups: LinodeBackupsResponse;
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { LinodeBackup } from '@linode/api-v4/lib/linodes';
import { Duration } from 'luxon';
import { DateTime, Duration } from 'luxon';
import * as React from 'react';
import { DateTimeDisplay } from 'src/components/DateTimeDisplay';
import { StatusIcon, Status } from 'src/components/StatusIcon/StatusIcon';
import { TableCell } from 'src/components/TableCell';
import { TableRow } from 'src/components/TableRow';
import { parseAPIDate } from 'src/utilities/date';
import { formatDuration } from 'src/utilities/formatDuration';
import LinodeBackupActionMenu from './LinodeBackupActionMenu';
import { LinodeBackupActionMenu } from './LinodeBackupActionMenu';

interface Props {
backup: LinodeBackup;
disabled: boolean;
handleRestore: (backup: LinodeBackup) => void;
handleDeploy: (backup: LinodeBackup) => void;
handleRestore: () => void;
handleDeploy: () => void;
}

const typeMap = {
@@ -41,12 +41,8 @@ const statusIconMap: Record<LinodeBackup['status'], Status> = {
userAborted: 'error',
};

const BackupTableRow: React.FC<Props> = (props) => {
const { backup, disabled, handleRestore } = props;

const onDeploy = () => {
props.handleDeploy(props.backup);
};
const BackupTableRow = (props: Props) => {
const { backup, disabled, handleRestore, handleDeploy } = props;

return (
<TableRow key={backup.id} data-qa-backup>
@@ -71,7 +67,9 @@ const BackupTableRow: React.FC<Props> = (props) => {
<TableCell parentColumn="Duration">
{formatDuration(
Duration.fromMillis(
parseAPIDate(backup.finished).toMillis() -
(backup.finished
? parseAPIDate(backup.finished).toMillis()
: DateTime.now().toMillis()) -
parseAPIDate(backup.created).toMillis()
)
)}
@@ -91,7 +89,7 @@ const BackupTableRow: React.FC<Props> = (props) => {
backup={backup}
disabled={disabled}
onRestore={handleRestore}
onDeploy={onDeploy}
onDeploy={handleDeploy}
/>
</TableCell>
</TableRow>
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import * as React from 'react';
import VolumeIcon from 'src/assets/icons/entityIcons/volume.svg';
import { makeStyles } from '@mui/styles';
import { makeStyles } from 'tss-react/mui';
import Typography from 'src/components/core/Typography';
import { Currency } from 'src/components/Currency';
import Placeholder from 'src/components/Placeholder';
import LinodePermissionsError from '../LinodePermissionsError';
import EnableBackupsDialog from './EnableBackupsDialog';
import { EnableBackupsDialog } from './EnableBackupsDialog';

interface Props {
backupsMonthlyPrice?: number;
disabled: boolean;
linodeId: number;
}

const useStyles = makeStyles(() => ({
const useStyles = makeStyles()(() => ({
empty: {
'& svg': {
transform: 'scale(0.75)',
@@ -22,7 +22,7 @@ const useStyles = makeStyles(() => ({
}));

export const BackupsPlaceholder = (props: Props) => {
const classes = useStyles();
const { classes } = useStyles();

const { backupsMonthlyPrice, linodeId, disabled } = props;

Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as React from 'react';
import { useSnackbar } from 'notistack';
import { resetEventsPolling } from 'src/eventsPolling';
import { useLinodeBackupsCancelMutation } from 'src/queries/linodes/backups';
import { sendBackupsDisabledEvent } from 'src/utilities/ga';
import Typography from 'src/components/core/Typography';
import { ConfirmationDialog } from 'src/components/ConfirmationDialog/ConfirmationDialog';
import ActionsPanel from 'src/components/ActionsPanel/ActionsPanel';
import Button from 'src/components/Button/Button';

interface Props {
isOpen: boolean;
onClose: () => void;
linodeId: number;
}

export const CancelBackupsDialog = (props: Props) => {
const { isOpen, onClose, linodeId } = props;
const { enqueueSnackbar } = useSnackbar();

const {
mutateAsync: cancelBackups,
error,
isLoading,
} = useLinodeBackupsCancelMutation(linodeId);

const onCancelBackups = async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor: Did we want to do handleCancelBackups?

await cancelBackups();
enqueueSnackbar('Backups are being canceled for this Linode', {
variant: 'info',
});
onClose();
resetEventsPolling();
sendBackupsDisabledEvent();
};

return (
<ConfirmationDialog
open={isOpen}
onClose={onClose}
title="Confirm Cancellation"
error={error?.[0].reason}
actions={
<ActionsPanel style={{ padding: 0 }}>
<Button
buttonType="secondary"
onClick={onClose}
data-qa-cancel-cancel
>
Close
</Button>
<Button
buttonType="primary"
onClick={onCancelBackups}
loading={isLoading}
data-qa-confirm-cancel
>
Cancel Backups
</Button>
</ActionsPanel>
}
>
<Typography>
Canceling backups associated with this Linode will delete all existing
backups. Are you sure?
</Typography>
<Typography style={{ marginTop: 12 }}>
<strong>Note: </strong>
Once backups for this Linode have been canceled, you cannot re-enable
them for 24 hours.
</Typography>
</ConfirmationDialog>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as React from 'react';
import { renderWithTheme } from 'src/utilities/testHelpers';
import { rest, server } from 'src/mocks/testServer';
import { linodeFactory } from 'src/factories/linodes';
import { CaptureSnapshot } from './CaptureSnapshot';
import userEvent from '@testing-library/user-event';

describe('CaptureSnapshot', () => {
it('renders heading and copy', async () => {
server.use(
rest.get('*/linode/instances/1', (req, res, ctx) => {
return res(
ctx.json(linodeFactory.build({ id: 1, backups: { enabled: true } }))
);
})
);

const { getByText } = renderWithTheme(
<CaptureSnapshot linodeId={1} isReadOnly={false} />
);

getByText('Manual Snapshot');
getByText(
/You can make a manual backup of your Linode by taking a snapshot./
);
});
it('a confirmation dialog should open when you attempt to take a snapshot', async () => {
server.use(
rest.get('*/linode/instances/1', (req, res, ctx) => {
return res(
ctx.json(linodeFactory.build({ id: 1, backups: { enabled: true } }))
);
})
);

const { getByLabelText, getByText } = renderWithTheme(
<CaptureSnapshot linodeId={1} isReadOnly={false} />
);

userEvent.type(getByLabelText('Name Snapshot'), 'my-linode-snapshot');

userEvent.click(getByText('Take Snapshot'));

expect(
getByText(
/Taking a snapshot will back up your Linode in its current state, overriding your previous snapshot. Are you sure?/
)
).toBeVisible();
});
});
Loading