Skip to content

Commit

Permalink
tech-story: [M3-8784] - Tanstack routing for Domains (linode#11418)
Browse files Browse the repository at this point in the history
* save progress

* SURGERY

* Implement basic routing

* Wrap up routing

* fix unit

* routes test update

* better handling in test

* fix smoke test

* Added changeset: Refactor Domains Feature Routing

* MSW fix

* edit changeset

* mock path consistency

* feedback @dwiley-akamai

* feedback @mjac0bs

* fix e2e
  • Loading branch information
abailly-akamai authored and dmcintyr-akamai committed Jan 9, 2025
1 parent f429949 commit 5f0f915
Show file tree
Hide file tree
Showing 25 changed files with 325 additions and 212 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Tech Stories
---

Refactor Domains Routing (Tanstack Router) ([#11418](https://github.com/linode/manager/pull/11418))
1 change: 1 addition & 0 deletions packages/manager/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ module.exports = {
files: [
// for each new features added to the migration router, add its directory here
'src/features/Betas/**/*',
'src/features/Domains/**/*',
'src/features/Volumes/**/*',
],
rules: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ describe('Clone a Domain', () => {
.click();
});
// After cloning a Domain, the user is redirected to the new Domain's details page
cy.url().should('endWith', 'domains');
cy.url().should('match', /\/domains\/\d+$/);

// Confirm that domain is cloned and cloned domains contain the same records as the original Domain.
cy.visitWithLogin('/domains');
Expand Down
6 changes: 0 additions & 6 deletions packages/manager/src/MainContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,6 @@ const LinodesRoutes = React.lazy(() =>
default: module.LinodesRoutes,
}))
);
const Domains = React.lazy(() =>
import('src/features/Domains').then((module) => ({
default: module.DomainsRoutes,
}))
);
const Images = React.lazy(() => import('src/features/Images'));
const Kubernetes = React.lazy(() =>
import('src/features/Kubernetes').then((module) => ({
Expand Down Expand Up @@ -343,7 +338,6 @@ export const MainContent = () => {
component={NodeBalancers}
path="/nodebalancers"
/>
<Route component={Domains} path="/domains" />
<Route component={Managed} path="/managed" />
<Route component={Longview} path="/longview" />
<Route component={Images} path="/images" />
Expand Down
2 changes: 1 addition & 1 deletion packages/manager/src/features/Betas/BetaSignup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { useCreateAccountBetaMutation } from 'src/queries/account/betas';
import { useBetaQuery } from 'src/queries/betas';

export const BetaSignup = () => {
const betaAgreement = `# Early Adopter Testing Program
const betaAgreement = `### Early Adopter Testing Program
This Early Adopter Testing Program Service Level Agreement (the “EAP”) is between Linode LLC (“Linode”) and
you, the customer who requests access and participation (the “Participant”) to the Linode Early Access Program
(the “Program”). This EAP is attached to and amends the master services agreement between you and Linode
Expand Down
19 changes: 14 additions & 5 deletions packages/manager/src/features/Domains/CloneDomainDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {
RadioGroup,
TextField,
} from '@linode/ui';
import { useNavigate } from '@tanstack/react-router';
import { useFormik } from 'formik';
import React from 'react';
import { useHistory } from 'react-router-dom';

import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
import { Drawer } from 'src/components/Drawer';
Expand All @@ -18,12 +18,13 @@ import type { Domain } from '@linode/api-v4';

interface CloneDomainDrawerProps {
domain: Domain | undefined;
isFetching: boolean;
onClose: () => void;
open: boolean;
}

export const CloneDomainDrawer = (props: CloneDomainDrawerProps) => {
const { domain, onClose: _onClose, open } = props;
const { domain, isFetching, onClose: _onClose, open } = props;

const { data: profile } = useProfile();
const { data: grants } = useGrants();
Expand All @@ -32,14 +33,17 @@ export const CloneDomainDrawer = (props: CloneDomainDrawerProps) => {
domain?.id ?? 0
);

const history = useHistory();
const navigate = useNavigate();

const formik = useFormik<{ domain: string }>({
initialValues: { domain: '' },
onSubmit: async (values) => {
const newDomain = await cloneDomain(values);
history.push(`/domains/${newDomain.id}`);
onClose();
navigate({
params: { domainId: newDomain.id },
to: '/domains/$domainId',
});
},
});

Expand All @@ -52,7 +56,12 @@ export const CloneDomainDrawer = (props: CloneDomainDrawerProps) => {
const noPermission = profile?.restricted && !grants?.global.add_domains;

return (
<Drawer onClose={onClose} open={open} title="Clone Domain">
<Drawer
isFetching={isFetching}
onClose={onClose}
open={open}
title="Clone Domain"
>
{noPermission && (
<Notice variant="error">
You do not have permission to create new Domains.
Expand Down
22 changes: 11 additions & 11 deletions packages/manager/src/features/Domains/CreateDomain/CreateDomain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ import {
import { createDomainSchema } from '@linode/validation/lib/domains.schema';
import { styled } from '@mui/material/styles';
import Grid from '@mui/material/Unstable_Grid2';
import { createLazyRoute } from '@tanstack/react-router';
import { useNavigate } from '@tanstack/react-router';
import { useFormik } from 'formik';
import { path } from 'ramda';
import * as React from 'react';
import { useHistory } from 'react-router-dom';

import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
import { DocumentTitleSegment } from 'src/components/DocumentTitle';
Expand Down Expand Up @@ -46,6 +45,7 @@ import type {
} from '@linode/api-v4/lib/domains';
import type { NodeBalancer } from '@linode/api-v4/lib/nodebalancers';
import type { APIError } from '@linode/api-v4/lib/types';
import type { DomainState } from 'src/routes/domains';
import type { ExtendedIP } from 'src/utilities/ipUtils';

interface DefaultRecordsSetting {
Expand All @@ -65,7 +65,7 @@ export const CreateDomain = () => {
// of the payload and must be handled separately.
const [errors, setErrors] = React.useState<APIError[] | undefined>(undefined);

const history = useHistory();
const navigate = useNavigate();

const defaultRecords: DefaultRecordsSetting[] = [
{
Expand Down Expand Up @@ -127,20 +127,24 @@ export const CreateDomain = () => {
const isCreatingPrimaryDomain = values.type === 'master';
const isCreatingSecondaryDomain = values.type === 'slave';

const redirect = (id: '' | number, state?: Record<string, string>) => {
const redirect = (id: null | number, state?: DomainState) => {
const returnPath = !!id ? `/domains/${id}` : '/domains';
history.push(returnPath, state);
navigate({
params: { domainId: Number(id) },
state: (prev) => ({ ...prev, ...state }),
to: returnPath,
});
};

const redirectToLandingOrDetail = (
type: 'master' | 'slave',
domainID: number,
state: Record<string, string> = {}
state: DomainState = {}
) => {
if (type === 'master' && domainID) {
redirect(domainID, state);
} else {
redirect('', state);
redirect(null, state);
}
};

Expand Down Expand Up @@ -449,10 +453,6 @@ export const CreateDomain = () => {
);
};

export const createDomainLazyRoute = createLazyRoute('/domains/create')({
component: CreateDomain,
});

const StyledGrid = styled(Grid, { label: 'StyledGrid' })({
width: '100%',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import type { Domain } from '@linode/api-v4';

interface DisableDomainDialogProps {
domain: Domain | undefined;
isFetching: boolean;
onClose: () => void;
open: boolean;
}

export const DisableDomainDialog = React.memo(
(props: DisableDomainDialogProps) => {
const { domain, onClose, open } = props;
const { domain, isFetching, onClose, open } = props;
const {
error,
isPending,
Expand Down Expand Up @@ -56,6 +57,7 @@ export const DisableDomainDialog = React.memo(
/>
}
error={error?.[0]?.reason}
isFetching={isFetching}
onClose={onClose}
open={open}
title={`Disable Domain ${domain?.domain}?`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { userEvent } from '@testing-library/user-event';
import * as React from 'react';

import { domainFactory } from 'src/factories/domain';
import { renderWithTheme } from 'src/utilities/testHelpers';
import { renderWithThemeAndRouter } from 'src/utilities/testHelpers';

import { DomainActionMenu } from './DomainActionMenu';

Expand All @@ -15,7 +15,7 @@ const props = {

describe('Domain action menu', () => {
it('should include basic Domain actions', async () => {
const { getByText, queryByLabelText } = renderWithTheme(
const { getByText, queryByLabelText } = await renderWithThemeAndRouter(
<DomainActionMenu domain={domainFactory.build()} {...props} />
);

Expand Down
26 changes: 15 additions & 11 deletions packages/manager/src/features/Domains/DomainDetail/DomainDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { CircleProgress, Notice, Paper, Typography } from '@linode/ui';
import { styled } from '@mui/material/styles';
import Grid from '@mui/material/Unstable_Grid2';
import { useLocation, useNavigate, useParams } from '@tanstack/react-router';
import * as React from 'react';
import { useHistory, useLocation, useParams } from 'react-router-dom';

import { ErrorState } from 'src/components/ErrorState/ErrorState';
import { LandingHeader } from 'src/components/LandingHeader';
Expand All @@ -18,14 +18,18 @@ import { DeleteDomain } from '../DeleteDomain';
import DomainRecords from '../DomainRecords';
import { DownloadDNSZoneFileButton } from '../DownloadDNSZoneFileButton';

export const DomainDetail = () => {
const params = useParams<{ domainId: string }>();
const domainId = Number(params.domainId);

const history = useHistory();
const location = useLocation<{ recordError?: string }>();
import type { DomainState } from 'src/routes/domains';

const { data: domain, error, isLoading } = useDomainQuery(domainId);
export const DomainDetail = () => {
const navigate = useNavigate();
const params = useParams({ from: '/domains/$domainId' });
const domainId = params.domainId;
const location = useLocation();
const locationState = location.state as DomainState;
const { data: domain, error, isLoading } = useDomainQuery(
domainId,
!!domainId
);
const { mutateAsync: updateDomain } = useUpdateDomainMutation();
const {
data: records,
Expand Down Expand Up @@ -110,8 +114,8 @@ export const DomainDetail = () => {
docsLink="https://techdocs.akamai.com/cloud-computing/docs/dns-manager"
title="Domain Details"
/>
{location.state && location.state.recordError && (
<StyledNotice text={location.state.recordError} variant="error" />
{locationState?.recordError && (
<StyledNotice text={locationState.recordError} variant="error" />
)}
<StyledRootGrid container>
<StyledMainGrid xs={12}>
Expand All @@ -138,7 +142,7 @@ export const DomainDetail = () => {
<DeleteDomain
domainId={domain.id}
domainLabel={domain.domain}
onSuccess={() => history.push('/domains')}
onSuccess={() => navigate({ to: '/domains' })}
/>
</StyledDiv>
</StyledTagSectionGrid>
Expand Down
11 changes: 3 additions & 8 deletions packages/manager/src/features/Domains/DomainDetail/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { CircleProgress } from '@linode/ui';
import { createLazyRoute } from '@tanstack/react-router';
import { useParams } from '@tanstack/react-router';
import * as React from 'react';
import { useParams } from 'react-router-dom';

import { ErrorState } from 'src/components/ErrorState/ErrorState';
import { NotFound } from 'src/components/NotFound';
Expand All @@ -17,8 +16,8 @@ const DomainDetail = React.lazy(() =>
);

export const DomainDetailRouting = () => {
const params = useParams<{ domainId: string }>();
const domainId = Number(params.domainId);
const params = useParams({ from: '/domains/$domainId' });
const domainId = params.domainId;

const { data: domain, error, isLoading } = useDomainQuery(domainId);

Expand All @@ -43,7 +42,3 @@ export const DomainDetailRouting = () => {
// page with an open drawer.
return <DomainsLanding domainForEditing={domain} />;
};

export const domainDetailLazyRoute = createLazyRoute('/domains/$domainId')({
component: DomainDetailRouting,
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Notice, TextField } from '@linode/ui';
import { useFormik } from 'formik';
import * as React from 'react';
import { useHistory } from 'react-router-dom';
import { useNavigate } from '@tanstack/react-router';

import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
import { Drawer } from 'src/components/Drawer';
Expand All @@ -21,7 +21,7 @@ export const DomainZoneImportDrawer = (props: DomainZoneImportDrawerProps) => {
const { data: profile } = useProfile();
const { data: grants } = useGrants();

const history = useHistory();
const navigate = useNavigate();

const { error, mutateAsync: importZone, reset } = useImportZoneMutation();

Expand All @@ -32,7 +32,10 @@ export const DomainZoneImportDrawer = (props: DomainZoneImportDrawerProps) => {
},
onSubmit: async (values) => {
const result = await importZone(values);
history.push(`/domains/${result.id}`);
navigate({
params: { domainId: result.id },
to: '/domains/$domainId',
});
},
});

Expand Down
22 changes: 19 additions & 3 deletions packages/manager/src/features/Domains/DomainsLanding.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
import * as React from 'react';

import { renderWithTheme } from 'src/utilities/testHelpers';
import { migrationRouteTree } from 'src/routes';
import { renderWithThemeAndRouter } from 'src/utilities/testHelpers';

import { DomainsLanding } from './DomainsLanding';

vi.mock('src/queries/domains', async () => {
const actual = await vi.importActual('src/queries/domains');
return {
...actual,
useDomainsQuery: vi.fn().mockReturnValue({
data: undefined,
error: null,
isLoading: true,
}),
};
});

describe('Domains Landing', () => {
it('should initially render a loading state', () => {
const { getByTestId } = renderWithTheme(<DomainsLanding />);
it('should initially render a loading state', async () => {
const { getByTestId } = await renderWithThemeAndRouter(<DomainsLanding />, {
initialRoute: '/domains',
routeTree: migrationRouteTree,
});
expect(getByTestId('circle-progress')).toBeInTheDocument();
});
});
Loading

0 comments on commit 5f0f915

Please sign in to comment.