Skip to content

Commit

Permalink
feat: [M3-7806] - Linode Create Refactor - Part 1 (#10268)
Browse files Browse the repository at this point in the history
* linode create v2 initial commit

* fix old linode create flow spacing

* Added changeset: Linode Create Refactor - Part 1

* clean up comments

* use `useRestrictedGlobalGrantCheck` helpers

* Added changeset: Make `type` and `region` required in `CreateLinodeRequest`

---------

Co-authored-by: Banks Nussman <banks@nussman.us>
  • Loading branch information
bnussman-akamai and bnussman authored Mar 12, 2024
1 parent 74695aa commit aa59941
Show file tree
Hide file tree
Showing 19 changed files with 383 additions and 176 deletions.
5 changes: 5 additions & 0 deletions packages/api-v4/.changeset/pr-10268-changed-1710182395545.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/api-v4": Changed
---

Make `type` and `region` required in `CreateLinodeRequest` ([#10268](https://github.com/linode/manager/pull/10268))
4 changes: 2 additions & 2 deletions packages/api-v4/src/linodes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,8 @@ export interface CreateLinodePlacementGroupPayload {
}

export interface CreateLinodeRequest {
type?: string;
region?: string;
type: string;
region: string;
stackscript_id?: number;
backup_id?: number;
swap_size?: number;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Tech Stories
---

Linode Create Refactor - Part 1 ([#10268](https://github.com/linode/manager/pull/10268))
2 changes: 1 addition & 1 deletion packages/manager/cypress/support/api/linodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const createMockLinodeList = (data?: {}, listNumber: number = 1) => {
);
};

const defaultLinodeRequestBody: Partial<CreateLinodeRequest> = {
const defaultLinodeRequestBody = {
authorized_users: [],
backups_enabled: false,
booted: true,
Expand Down
1 change: 1 addition & 0 deletions packages/manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"react-csv": "^2.0.3",
"react-dom": "^18.2.0",
"react-dropzone": "~11.2.0",
"react-hook-form": "^7.51.0",
"react-number-format": "^3.5.0",
"react-redux": "~7.1.3",
"react-router-dom": "~5.3.4",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Capabilities, Region } from '@linode/api-v4/lib/regions';
import { Capabilities } from '@linode/api-v4/lib/regions';
import { useTheme } from '@mui/material';
import * as React from 'react';
import { useLocation } from 'react-router-dom';
Expand All @@ -11,6 +11,7 @@ import { RegionHelperText } from 'src/components/SelectRegionPanel/RegionHelperT
import { Typography } from 'src/components/Typography';
import { CROSS_DATA_CENTER_CLONE_WARNING } from 'src/features/Linodes/LinodesCreate/constants';
import { useFlags } from 'src/hooks/useFlags';
import { useRegionsQuery } from 'src/queries/regions';
import { useTypeQuery } from 'src/queries/types';
import { sendLinodeCreateDocsEvent } from 'src/utilities/analytics';
import {
Expand All @@ -32,7 +33,6 @@ interface SelectRegionPanelProps {
error?: string;
handleSelection: (id: string) => void;
helperText?: string;
regions: Region[];
selectedId?: string;
/**
* Include a `selectedLinodeTypeId` so we can tell if the region selection will have an affect on price
Expand All @@ -47,7 +47,6 @@ export const SelectRegionPanel = (props: SelectRegionPanelProps) => {
error,
handleSelection,
helperText,
regions,
selectedId,
selectedLinodeTypeId,
} = props;
Expand All @@ -56,6 +55,7 @@ export const SelectRegionPanel = (props: SelectRegionPanelProps) => {
const location = useLocation();
const theme = useTheme();
const params = getQueryParamsFromQueryString(location.search);
const { data: regions } = useRegionsQuery();

const isCloning = /clone/i.test(params.type);

Expand Down Expand Up @@ -84,28 +84,27 @@ export const SelectRegionPanel = (props: SelectRegionPanelProps) => {
const showEdgeIconHelperText = Boolean(
!hideEdgeRegions &&
currentCapability &&
regions.find(
regions?.find(
(region) =>
region.site_type === 'edge' &&
region.capabilities.includes(currentCapability)
)
);

if (props.regions.length === 0) {
if (regions?.length === 0) {
return null;
}

return (
<Paper
sx={(theme) => ({
sx={{
'& svg': {
'& g': {
// Super hacky fix for Firefox rendering of some flag icons that had a clip-path property.
clipPath: 'none !important',
},
},
marginTop: theme.spacing(3),
})}
}}
>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography data-qa-tp="Region" variant="h2">
Expand Down Expand Up @@ -138,7 +137,7 @@ export const SelectRegionPanel = (props: SelectRegionPanelProps) => {
handleSelection={handleSelection}
helperText={helperText}
regionFilter={hideEdgeRegions ? 'core' : undefined}
regions={regions}
regions={regions ?? []}
selectedId={selectedId || null}
showEdgeIconHelperText={showEdgeIconHelperText}
/>
Expand Down
1 change: 1 addition & 0 deletions packages/manager/src/dev-tools/FeatureFlagTool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const options: { flag: keyof Flags; label: string }[] = [
{ flag: 'recharts', label: 'Recharts' },
{ flag: 'objMultiCluster', label: 'OBJ Multi-Cluster' },
{ flag: 'placementGroups', label: 'Placement Groups' },
{ flag: 'linodeCreateRefactor', label: 'Linode Create v2' },
];

export const FeatureFlagTool = withFeatureFlagProvider(() => {
Expand Down
1 change: 1 addition & 0 deletions packages/manager/src/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export interface Flags {
gecko: boolean;
ipv6Sharing: boolean;
linodeCloneUiChanges: boolean;
linodeCreateRefactor: boolean;
linodeCreateWithFirewall: boolean;
mainContentBanner: MainContentBanner;
metadata: boolean;
Expand Down
24 changes: 24 additions & 0 deletions packages/manager/src/features/Linodes/LinodeCreatev2/Error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Typography } from '@mui/material';
import React from 'react';
import { useFormContext } from 'react-hook-form';

import { Notice } from 'src/components/Notice/Notice';
import { Paper } from 'src/components/Paper';

import type { CreateLinodeRequest } from '@linode/api-v4';

export const Error = () => {
const { formState } = useFormContext<CreateLinodeRequest>();

if (!formState.errors.root?.message) {
return null;
}

return (
<Paper sx={{ p: 0 }}>
<Notice spacingBottom={0} spacingTop={0} variant="error">
<Typography py={2}>{formState.errors.root.message}</Typography>
</Notice>
</Paper>
);
};
53 changes: 53 additions & 0 deletions packages/manager/src/features/Linodes/LinodeCreatev2/Plan.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import { useController, useFormContext } from 'react-hook-form';

import { DocsLink } from 'src/components/DocsLink/DocsLink';
import { PlansPanel } from 'src/features/components/PlansPanel/PlansPanel';
import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck';
import { useRegionsQuery } from 'src/queries/regions';
import { useAllTypes } from 'src/queries/types';
import { sendLinodeCreateFlowDocsClickEvent } from 'src/utilities/analytics';
import { extendType } from 'src/utilities/extendType';

import type { CreateLinodeRequest } from '@linode/api-v4';

export const Plan = () => {
const { watch } = useFormContext<CreateLinodeRequest>();
const { field, fieldState } = useController<CreateLinodeRequest>({
name: 'type',
});

const { data: regions } = useRegionsQuery();
const { data: types } = useAllTypes();

const regionId = watch('region');

const isLinodeCreateRestricted = useRestrictedGlobalGrantCheck({
globalGrantType: 'add_linodes',
});

return (
<PlansPanel
docsLink={
<DocsLink
onClick={() => {
sendLinodeCreateFlowDocsClickEvent('Choosing a Plan');
}}
href="https://www.linode.com/docs/guides/choosing-a-compute-instance-plan/"
label="Choosing a Plan"
/>
}
data-qa-select-plan
disabled={isLinodeCreateRestricted}
error={fieldState.error?.message}
isCreate
linodeID={undefined} // @todo add cloning support
onSelect={field.onChange}
regionsData={regions} // @todo move this query deeper if possible
selectedId={field.value}
selectedRegionID={regionId}
showTransfer
types={types?.map(extendType) ?? []}
/>
);
};
27 changes: 27 additions & 0 deletions packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import { useController } from 'react-hook-form';

import { SelectRegionPanel } from 'src/components/SelectRegionPanel/SelectRegionPanel';
import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck';

import type { CreateLinodeRequest } from '@linode/api-v4';

export const Region = () => {
const { field, formState } = useController<CreateLinodeRequest>({
name: 'region',
});

const isLinodeCreateRestricted = useRestrictedGlobalGrantCheck({
globalGrantType: 'add_linodes',
});

return (
<SelectRegionPanel
currentCapability="Linodes"
disabled={isLinodeCreateRestricted}
error={formState.errors.region?.message}
handleSelection={field.onChange}
selectedId={field.value}
/>
);
};
22 changes: 22 additions & 0 deletions packages/manager/src/features/Linodes/LinodeCreatev2/Summary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { CreateLinodeRequest } from '@linode/api-v4';
import React from 'react';
import { useFormContext } from 'react-hook-form';

import { Button } from 'src/components/Button/Button';
import { Paper } from 'src/components/Paper';

export const Summary = () => {
const { formState } = useFormContext<CreateLinodeRequest>();

return (
<Paper sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button
buttonType="primary"
loading={formState.isSubmitting}
type="submit"
>
Create Linode
</Button>
</Paper>
);
};
52 changes: 52 additions & 0 deletions packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useHistory } from 'react-router-dom';

import { Stack } from 'src/components/Stack';
import { useCreateLinodeMutation } from 'src/queries/linodes/linodes';

import { Error } from './Error';
import { Plan } from './Plan';
import { Region } from './Region';
import { Summary } from './Summary';

import type { CreateLinodeRequest } from '@linode/api-v4';
import type { SubmitHandler } from 'react-hook-form';

export const LinodeCreatev2 = () => {
const methods = useForm<CreateLinodeRequest>();
const history = useHistory();

const { mutateAsync: createLinode } = useCreateLinodeMutation();

const onSubmit: SubmitHandler<CreateLinodeRequest> = async (data) => {
try {
const linode = await createLinode(data);

history.push(`/linodes/${linode.id}`);
} catch (errors) {
// @todo this is temporary API error handling. We will develop a more
// robust helper that can convert API errors to react-hook-form errors
for (const error of errors) {
if (error.field) {
methods.setError(error.field, { message: error.reason });
} else {
methods.setError('root', { message: error.reason });
}
}
}
};

return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
<Stack gap={2}>
<Error />
<Region />
<Plan />
<Summary />
</Stack>
</form>
</FormProvider>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ApiAwarenessModal, ApiAwarenessModalProps } from './ApiAwarenessModal';
const defaultProps: ApiAwarenessModalProps = {
isOpen: false,
onClose: vi.fn(),
payLoad: {},
payLoad: { region: '', type: '' },
route: '',
};

Expand Down
Loading

0 comments on commit aa59941

Please sign in to comment.