Skip to content

Commit

Permalink
refactor: [M3-6492, M3-6315] - React Query - Linode Detail - Backups (#…
Browse files Browse the repository at this point in the history
…9079)

- React Query for Linode Backups 🚀
- Modernizes the Linode Backups section of the Linodes Details page
- Breaks up Backup related code into more components with more understandable names
- Adds `available` to the LinodeBackup type because it was missing
- Resolves Sentry errors caused by this section of the app (M3-6315)
- Makes some minor UI and UX improvements 🎨

---------

Co-authored-by: Banks Nussman <banks@nussman.us>
  • Loading branch information
bnussman-akamai and bnussman authored May 8, 2023
1 parent 2e53946 commit 741a11d
Show file tree
Hide file tree
Showing 26 changed files with 1,148 additions and 1,232 deletions.
1 change: 1 addition & 0 deletions packages/api-v4/src/linodes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export interface LinodeBackup {
finished: string;
configs: string[];
disks: LinodeBackupDisk[];
available: boolean;
}

export type LinodeBackupType = 'auto' | 'snapshot';
Expand Down
2 changes: 1 addition & 1 deletion packages/manager/cypress/e2e/linodes/backup-linode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
Expand Down
5 changes: 4 additions & 1 deletion packages/manager/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
26 changes: 26 additions & 0 deletions packages/manager/src/factories/linodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
CreateLinodeRequest,
Linode,
LinodeAlerts,
LinodeBackup,
LinodeBackups,
LinodeIPsResponse,
LinodeSpecs,
Expand Down Expand Up @@ -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
Expand Up @@ -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;
}
Expand Down
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 = {
Expand Down Expand Up @@ -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>
Expand All @@ -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()
)
)}
Expand All @@ -91,7 +89,7 @@ const BackupTableRow: React.FC<Props> = (props) => {
backup={backup}
disabled={disabled}
onRestore={handleRestore}
onDeploy={onDeploy}
onDeploy={handleDeploy}
/>
</TableCell>
</TableRow>
Expand Down
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)',
Expand All @@ -22,7 +22,7 @@ const useStyles = makeStyles(() => ({
}));

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

const { backupsMonthlyPrice, linodeId, disabled } = props;

Expand Down
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 () => {
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

0 comments on commit 741a11d

Please sign in to comment.