Skip to content

Commit

Permalink
refactor: [M3-8783] - Migrate /volumes to Tanstack Router (#11154)
Browse files Browse the repository at this point in the history
* initial commit: wire new router

* Migrate test to use new helper

* volumes landing routing

* doin the filtering

* wire actions

* cleanup

* cleanup

* moar cleanup

* more work on params

* usePaginationV2

* cleanup

* oops fix types

* fix e2e

* useOrderV2

* and... dont forget the util 🤦

* revert unwarranted change

* useOrderV2 test

* console error

* A bit more cleanup

* usePaginationV2 test

* usePaginationV2 test

* route level error handling

* xFilter util

* xFilter util improvements

* post rebase fix

* testing xQuery builder

* moar testing and cleanup

* Added changeset: Migrate `/volumes` to Tanstack router

* Save progress

* fix one test

* fix remaining test

* feedback @jaalah-akamai

* more work on preloading

* save progress

* Walking back and using a more progressive approach

* cleanup

* entity not found logic

* post rebase fix

* post rebase fix

* update loading state

* fix the smoke tests

* Feedback @bnussman-akamai

* JsDocs for Tanstack link components

* Improve new useOrder hook

* feedback @coliu-akamai @hkhalil-akamai
  • Loading branch information
abailly-akamai authored Dec 10, 2024
1 parent 652b2aa commit ac15267
Show file tree
Hide file tree
Showing 49 changed files with 1,284 additions and 206 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Tech Stories
---

Migrate `/volumes` to Tanstack router ([#11154](https://github.com/linode/manager/pull/11154))
6 changes: 5 additions & 1 deletion packages/manager/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,14 @@ module.exports = {
{
files: [
// for each new features added to the migration router, add its directory here
'src/features/Betas/*',
'src/features/Betas/**/*',
'src/features/Volumes/**/*',
],
rules: {
'no-restricted-imports': [
// This needs to remain an error however trying to link to a feature that is not yet migrated will break the router
// For those cases react-router-dom history.push is still needed
// using `eslint-disable-next-line no-restricted-imports` can help bypass those imports
'error',
{
paths: [
Expand Down
13 changes: 10 additions & 3 deletions packages/manager/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import {
Controls,
Stories,
} from '@storybook/blocks';
import { wrapWithTheme } from '../src/utilities/testHelpers';
import {
wrapWithTheme,
wrapWithThemeAndRouter,
} from '../src/utilities/testHelpers';
import { useDarkMode } from 'storybook-dark-mode';
import { DocsContainer as BaseContainer } from '@storybook/addon-docs';
import { themes } from '@storybook/theming';
Expand Down Expand Up @@ -42,9 +45,13 @@ export const DocsContainer = ({ children, context }) => {

const preview: Preview = {
decorators: [
(Story) => {
(Story, context) => {
const isDark = useDarkMode();
return wrapWithTheme(<Story />, { theme: isDark ? 'dark' : 'light' });
return context.parameters.tanStackRouter
? wrapWithThemeAndRouter(<Story />, {
theme: isDark ? 'dark' : 'light',
})
: wrapWithTheme(<Story />, { theme: isDark ? 'dark' : 'light' });
},
],
loaders: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from 'support/intercepts/linodes';
import {
mockCreateVolume,
mockGetVolume,
mockGetVolumes,
mockDetachVolume,
mockGetVolumeTypesError,
Expand Down Expand Up @@ -85,6 +86,7 @@ describe('volumes', () => {

mockGetVolumes([]).as('getVolumes');
mockCreateVolume(mockVolume).as('createVolume');
mockGetVolume(mockVolume).as('getVolume');
mockGetVolumeTypes(mockVolumeTypes).as('getVolumeTypes');

cy.visitWithLogin('/volumes', {
Expand Down Expand Up @@ -114,7 +116,7 @@ describe('volumes', () => {

mockGetVolumes([mockVolume]).as('getVolumes');
ui.button.findByTitle('Create Volume').should('be.visible').click();
cy.wait(['@createVolume', '@getVolumes']);
cy.wait(['@createVolume', '@getVolume', '@getVolumes']);
validateBasicVolume(mockVolume.label);

ui.actionMenu
Expand Down Expand Up @@ -193,6 +195,7 @@ describe('volumes', () => {

mockDetachVolume(mockAttachedVolume.id).as('detachVolume');
mockGetVolumes([mockAttachedVolume]).as('getAttachedVolumes');
mockGetVolume(mockAttachedVolume).as('getVolume');
cy.visitWithLogin('/volumes', {
preferenceOverrides,
localStorageOverrides,
Expand All @@ -209,6 +212,8 @@ describe('volumes', () => {

ui.actionMenuItem.findByTitle('Detach').click();

cy.wait('@getVolume');

ui.dialog
.findByTitle(`Detach Volume ${mockAttachedVolume.label}?`)
.should('be.visible')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import {
mockGetLinodeDisks,
mockGetLinodeVolumes,
} from 'support/intercepts/linodes';
import { mockMigrateVolumes, mockGetVolumes } from 'support/intercepts/volumes';
import {
mockMigrateVolumes,
mockGetVolumes,
mockGetVolume,
} from 'support/intercepts/volumes';
import { ui } from 'support/ui';

describe('volume upgrade/migration', () => {
Expand All @@ -23,6 +27,7 @@ describe('volume upgrade/migration', () => {
});

mockGetVolumes([volume]).as('getVolumes');
mockGetVolume(volume).as('getVolume');
mockMigrateVolumes().as('migrateVolumes');
mockGetNotifications([migrationScheduledNotification]).as(
'getNotifications'
Expand Down Expand Up @@ -53,7 +58,7 @@ describe('volume upgrade/migration', () => {
.click();
});

cy.wait(['@migrateVolumes', '@getNotifications']);
cy.wait(['@migrateVolumes', '@getVolume', '@getNotifications']);

cy.findByText('UPGRADE PENDING').should('be.visible');

Expand Down
6 changes: 3 additions & 3 deletions packages/manager/src/MainContent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Box } from '@linode/ui';
import Grid from '@mui/material/Unstable_Grid2';
import { useQueryClient } from '@tanstack/react-query';
import { RouterProvider } from '@tanstack/react-router';
import * as React from 'react';
import { Redirect, Route, Switch } from 'react-router-dom';
Expand Down Expand Up @@ -130,7 +131,6 @@ const LinodesRoutes = React.lazy(() =>
default: module.LinodesRoutes,
}))
);
const Volumes = React.lazy(() => import('src/features/Volumes'));
const Domains = React.lazy(() =>
import('src/features/Domains').then((module) => ({
default: module.DomainsRoutes,
Expand Down Expand Up @@ -207,6 +207,7 @@ export const MainContent = () => {
const { classes, cx } = useStyles();
const { data: preferences } = usePreferences();
const { mutateAsync: updatePreferences } = useMutatePreferences();
const queryClient = useQueryClient();

const globalErrors = useGlobalErrors();

Expand Down Expand Up @@ -336,8 +337,6 @@ export const MainContent = () => {
path="/placement-groups"
/>
)}
<Route component={Volumes} path="/volumes" />
<Redirect path="/volumes*" to="/volumes" />
<Route
component={NodeBalancers}
path="/nodebalancers"
Expand Down Expand Up @@ -382,6 +381,7 @@ export const MainContent = () => {
*/}
<Route path="*">
<RouterProvider
context={{ queryClient }}
router={migrationRouter as AnyRouter}
/>
</Route>
Expand Down
2 changes: 2 additions & 0 deletions packages/manager/src/Router.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { QueryClient } from '@tanstack/react-query';
import { RouterProvider } from '@tanstack/react-router';
import * as React from 'react';

Expand All @@ -24,6 +25,7 @@ export const Router = () => {
isACLPEnabled,
isDatabasesEnabled,
isPlacementGroupsEnabled,
queryClient: new QueryClient(),
},
});

Expand Down
38 changes: 38 additions & 0 deletions packages/manager/src/components/TanstackLink.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';

import { TanstackLink } from './TanstackLinks';

import type { TanstackLinkComponentProps } from './TanstackLinks';
import type { Meta, StoryObj } from '@storybook/react';

export const AsButtonPrimary: StoryObj<TanstackLinkComponentProps> = {
render: () => (
<TanstackLink linkType="primary" to="/">
Home
</TanstackLink>
),
};

export const AsButtonSecondary: StoryObj<TanstackLinkComponentProps> = {
render: () => (
<TanstackLink linkType="secondary" to="/">
Home
</TanstackLink>
),
};

export const AsLink: StoryObj<TanstackLinkComponentProps> = {
render: () => (
<TanstackLink linkType="link" to="/">
Home
</TanstackLink>
),
};

const meta: Meta<TanstackLinkComponentProps> = {
parameters: {
tanStackRouter: true,
},
title: 'Foundations/Link/Tanstack Link',
};
export default meta;
83 changes: 83 additions & 0 deletions packages/manager/src/components/TanstackLinks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Button } from '@linode/ui';
import { omitProps } from '@linode/ui';
import { LinkComponent } from '@tanstack/react-router';
import { createLink } from '@tanstack/react-router';
import * as React from 'react';

import { MenuItem } from 'src/components/MenuItem';

import type { ButtonProps, ButtonType } from '@linode/ui';
import type { LinkProps as TanStackLinkProps } from '@tanstack/react-router';

export interface TanstackLinkComponentProps
extends Omit<ButtonProps, 'buttonType' | 'href'> {
linkType: 'link' | ButtonType;
tooltipAnalyticsEvent?: (() => void) | undefined;
tooltipText?: string;
}

export interface TanStackLinkRoutingProps {
linkType: TanstackLinkComponentProps['linkType'];
params?: TanStackLinkProps['params'];
preload?: TanStackLinkProps['preload'];
search?: TanStackLinkProps['search'];
to: TanStackLinkProps['to'];
}

const LinkComponent = React.forwardRef<
HTMLButtonElement,
TanstackLinkComponentProps
>((props, ref) => {
const _props = omitProps(props, ['linkType']);

return <Button component="a" ref={ref} {..._props} />;
});

const MenuItemLinkComponent = React.forwardRef<
HTMLButtonElement,
TanstackLinkComponentProps
>((props, ref) => {
const _props = omitProps(props, ['linkType']);

return <MenuItem component="a" ref={ref} {..._props} />;
});

const CreatedLinkComponent = createLink(LinkComponent);
const CreatedMenuItemLinkComponent = createLink(MenuItemLinkComponent);

/**
* @deprecated
* This is marked as deprecated to discourage usage until the migration is complete.
* While not technically deprecated, these components should not be used anywhere in the app.
* They will be replacing our Links eventually, but have only been introduced early to test their functionality.
*/
export const TanstackLink: LinkComponent<typeof LinkComponent> = (props) => {
return (
<CreatedLinkComponent
{...props}
sx={(theme) => ({
...(props.linkType === 'link' && {
'&:hover': {
textDecoration: 'underline',
},
fontFamily: theme.font.normal,
fontSize: '0.875rem',
minWidth: 'initial',
padding: 0,
}),
})}
/>
);
};

/**
* @deprecated
* This is marked as deprecated to discourage usage until the migration is complete.
* While not technically deprecated, these components should not be used anywhere in the app.
* They will be replacing our Links eventually, but have only been introduced early to test their functionality.
*/
export const TanstackMenuItemLink: LinkComponent<
typeof MenuItemLinkComponent
> = (props) => {
return <CreatedMenuItemLinkComponent {...props} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export const TypeToConfirmDialog = (props: CombinedProps) => {
entity,
errors,
inputProps,
isFetching,
label,
loading,
onClick,
Expand Down Expand Up @@ -131,6 +132,7 @@ export const TypeToConfirmDialog = (props: CombinedProps) => {
<ConfirmationDialog
actions={actions}
error={errors ? errors[0].reason : undefined}
isFetching={isFetching}
onClose={onClose}
open={open}
title={title}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as React from 'react';

import { accountFactory, volumeFactory } from 'src/factories';
import { HttpResponse, http, server } from 'src/mocks/testServer';
import { renderWithTheme } from 'src/utilities/testHelpers';
import { renderWithThemeAndRouter } from 'src/utilities/testHelpers';

import { AttachVolumeDrawer } from './AttachVolumeDrawer';

Expand All @@ -24,7 +24,7 @@ describe('AttachVolumeDrawer', () => {
})
);

const { getByLabelText } = renderWithTheme(
const { getByLabelText } = await renderWithThemeAndRouter(
<AttachVolumeDrawer onClose={vi.fn} open volume={volume} />,
{
flags: { blockStorageEncryption: true },
Expand All @@ -48,7 +48,7 @@ describe('AttachVolumeDrawer', () => {
})
);

const { queryByRole } = renderWithTheme(
const { queryByRole } = await renderWithThemeAndRouter(
<AttachVolumeDrawer onClose={vi.fn} open volume={volume} />,
{
flags: { blockStorageEncryption: false },
Expand All @@ -69,7 +69,7 @@ describe('AttachVolumeDrawer', () => {
})
);

const { queryByRole } = renderWithTheme(
const { queryByRole } = await renderWithThemeAndRouter(
<AttachVolumeDrawer onClose={vi.fn} open volume={volume} />,
{
flags: { blockStorageEncryption: true },
Expand Down
4 changes: 3 additions & 1 deletion packages/manager/src/features/Volumes/AttachVolumeDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ConfigSelect } from './VolumeDrawer/ConfigSelect';
import type { Volume } from '@linode/api-v4';

interface Props {
isFetching?: boolean;
onClose: () => void;
open: boolean;
volume: Volume | undefined;
Expand All @@ -35,7 +36,7 @@ const AttachVolumeValidationSchema = object({
});

export const AttachVolumeDrawer = React.memo((props: Props) => {
const { open, volume } = props;
const { isFetching, open, volume } = props;

const { enqueueSnackbar } = useSnackbar();

Expand Down Expand Up @@ -97,6 +98,7 @@ export const AttachVolumeDrawer = React.memo((props: Props) => {

return (
<Drawer
isFetching={isFetching}
onClose={handleClose}
open={open}
title={`Attach Volume ${volume?.label}`}
Expand Down
Loading

0 comments on commit ac15267

Please sign in to comment.