diff --git a/packages/api-v4/src/regions/types.ts b/packages/api-v4/src/regions/types.ts
index 23204247879..84871a6a6b0 100644
--- a/packages/api-v4/src/regions/types.ts
+++ b/packages/api-v4/src/regions/types.ts
@@ -24,6 +24,8 @@ export interface DNSResolvers {
export type RegionStatus = 'ok' | 'outage';
+export type RegionSite = 'core' | 'edge';
+
export interface Region {
id: string;
label: string;
@@ -33,6 +35,7 @@ export interface Region {
maximum_vms_per_pg: number;
status: RegionStatus;
resolvers: DNSResolvers;
+ site_type: RegionSite;
}
export interface RegionAvailability {
diff --git a/packages/manager/.changeset/pr-10194-upcoming-features-1708456171781.md b/packages/manager/.changeset/pr-10194-upcoming-features-1708456171781.md
new file mode 100644
index 00000000000..7e255b7427c
--- /dev/null
+++ b/packages/manager/.changeset/pr-10194-upcoming-features-1708456171781.md
@@ -0,0 +1,5 @@
+---
+"@linode/manager": Upcoming Features
+---
+
+Update Region Select for edge sites ([#10194](https://github.com/linode/manager/pull/10194))
diff --git a/packages/manager/src/__data__/regionsData.ts b/packages/manager/src/__data__/regionsData.ts
index c8afbb336d6..6e44c8dd739 100644
--- a/packages/manager/src/__data__/regionsData.ts
+++ b/packages/manager/src/__data__/regionsData.ts
@@ -24,6 +24,7 @@ export const regions: Region[] = [
ipv6:
'2400:8904::f03c:91ff:fea5:659, 2400:8904::f03c:91ff:fea5:9282, 2400:8904::f03c:91ff:fea5:b9b3, 2400:8904::f03c:91ff:fea5:925a, 2400:8904::f03c:91ff:fea5:22cb, 2400:8904::f03c:91ff:fea5:227a, 2400:8904::f03c:91ff:fea5:924c, 2400:8904::f03c:91ff:fea5:f7e2, 2400:8904::f03c:91ff:fea5:2205, 2400:8904::f03c:91ff:fea5:9207',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -48,6 +49,7 @@ export const regions: Region[] = [
ipv6:
'2600:3c04::f03c:91ff:fea9:f63, 2600:3c04::f03c:91ff:fea9:f6d, 2600:3c04::f03c:91ff:fea9:f80, 2600:3c04::f03c:91ff:fea9:f0f, 2600:3c04::f03c:91ff:fea9:f99, 2600:3c04::f03c:91ff:fea9:fbd, 2600:3c04::f03c:91ff:fea9:fdd, 2600:3c04::f03c:91ff:fea9:fe2, 2600:3c04::f03c:91ff:fea9:f68, 2600:3c04::f03c:91ff:fea9:f4a',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -72,6 +74,7 @@ export const regions: Region[] = [
ipv6:
'2400:8907::f03c:92ff:fe6e:ec8, 2400:8907::f03c:92ff:fe6e:98e4, 2400:8907::f03c:92ff:fe6e:1c58, 2400:8907::f03c:92ff:fe6e:c299, 2400:8907::f03c:92ff:fe6e:c210, 2400:8907::f03c:92ff:fe6e:c219, 2400:8907::f03c:92ff:fe6e:1c5c, 2400:8907::f03c:92ff:fe6e:c24e, 2400:8907::f03c:92ff:fe6e:e6b, 2400:8907::f03c:92ff:fe6e:e3d',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -98,6 +101,7 @@ export const regions: Region[] = [
ipv6:
'2600:3c05::f03c:93ff:feb6:43b6, 2600:3c05::f03c:93ff:feb6:4365, 2600:3c05::f03c:93ff:feb6:43c2, 2600:3c05::f03c:93ff:feb6:e441, 2600:3c05::f03c:93ff:feb6:94ef, 2600:3c05::f03c:93ff:feb6:94ba, 2600:3c05::f03c:93ff:feb6:94a8, 2600:3c05::f03c:93ff:feb6:9413, 2600:3c05::f03c:93ff:feb6:9443, 2600:3c05::f03c:93ff:feb6:94e0',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -124,6 +128,7 @@ export const regions: Region[] = [
ipv6:
'2600:3c06::f03c:93ff:fed0:e5fc, 2600:3c06::f03c:93ff:fed0:e54b, 2600:3c06::f03c:93ff:fed0:e572, 2600:3c06::f03c:93ff:fed0:e530, 2600:3c06::f03c:93ff:fed0:e597, 2600:3c06::f03c:93ff:fed0:e511, 2600:3c06::f03c:93ff:fed0:e5f2, 2600:3c06::f03c:93ff:fed0:e5bf, 2600:3c06::f03c:93ff:fed0:e529, 2600:3c06::f03c:93ff:fed0:e5a3',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -150,6 +155,7 @@ export const regions: Region[] = [
ipv6:
'2600:3c07::f03c:93ff:fef2:2e63, 2600:3c07::f03c:93ff:fef2:2ec7, 2600:3c07::f03c:93ff:fef2:0dee, 2600:3c07::f03c:93ff:fef2:0d25, 2600:3c07::f03c:93ff:fef2:0de0, 2600:3c07::f03c:93ff:fef2:2e29, 2600:3c07::f03c:93ff:fef2:0dda, 2600:3c07::f03c:93ff:fef2:0d82, 2600:3c07::f03c:93ff:fef2:b3ac, 2600:3c07::f03c:93ff:fef2:b3a8',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -175,6 +181,7 @@ export const regions: Region[] = [
ipv6:
'2600:3c0a::f03c:93ff:fe54:c6da, 2600:3c0a::f03c:93ff:fe54:c691, 2600:3c0a::f03c:93ff:fe54:c68d, 2600:3c0a::f03c:93ff:fe54:c61e, 2600:3c0a::f03c:93ff:fe54:c653, 2600:3c0a::f03c:93ff:fe54:c64c, 2600:3c0a::f03c:93ff:fe54:c68a, 2600:3c0a::f03c:93ff:fe54:c697, 2600:3c0a::f03c:93ff:fe54:c60f, 2600:3c0a::f03c:93ff:fe54:c6a0',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -200,6 +207,7 @@ export const regions: Region[] = [
ipv6:
'2600:3c0d::f03c:93ff:fe3d:51cb, 2600:3c0d::f03c:93ff:fe3d:51a7, 2600:3c0d::f03c:93ff:fe3d:51a9, 2600:3c0d::f03c:93ff:fe3d:5119, 2600:3c0d::f03c:93ff:fe3d:51fe, 2600:3c0d::f03c:93ff:fe3d:517c, 2600:3c0d::f03c:93ff:fe3d:5144, 2600:3c0d::f03c:93ff:fe3d:5170, 2600:3c0d::f03c:93ff:fe3d:51cc, 2600:3c0d::f03c:93ff:fe3d:516c',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -225,6 +233,7 @@ export const regions: Region[] = [
ipv6:
'2600:3c0e::f03c:93ff:fe9d:2d10, 2600:3c0e::f03c:93ff:fe9d:2d89, 2600:3c0e::f03c:93ff:fe9d:2d79, 2600:3c0e::f03c:93ff:fe9d:2d96, 2600:3c0e::f03c:93ff:fe9d:2da5, 2600:3c0e::f03c:93ff:fe9d:2d34, 2600:3c0e::f03c:93ff:fe9d:2d68, 2600:3c0e::f03c:93ff:fe9d:2d17, 2600:3c0e::f03c:93ff:fe9d:2d45, 2600:3c0e::f03c:93ff:fe9d:2d5c',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -250,6 +259,7 @@ export const regions: Region[] = [
ipv6:
'2600:3c09::f03c:93ff:fea9:4dbe, 2600:3c09::f03c:93ff:fea9:4d63, 2600:3c09::f03c:93ff:fea9:4dce, 2600:3c09::f03c:93ff:fea9:4dbb, 2600:3c09::f03c:93ff:fea9:4d99, 2600:3c09::f03c:93ff:fea9:4d26, 2600:3c09::f03c:93ff:fea9:4de0, 2600:3c09::f03c:93ff:fea9:4d69, 2600:3c09::f03c:93ff:fea9:4dbf, 2600:3c09::f03c:93ff:fea9:4da6',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -275,6 +285,7 @@ export const regions: Region[] = [
ipv6:
'2600:3c08::f03c:93ff:fe7c:1135, 2600:3c08::f03c:93ff:fe7c:11f8, 2600:3c08::f03c:93ff:fe7c:11d2, 2600:3c08::f03c:93ff:fe7c:11a7, 2600:3c08::f03c:93ff:fe7c:11ad, 2600:3c08::f03c:93ff:fe7c:110a, 2600:3c08::f03c:93ff:fe7c:11f9, 2600:3c08::f03c:93ff:fe7c:1137, 2600:3c08::f03c:93ff:fe7c:11db, 2600:3c08::f03c:93ff:fe7c:1164',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -300,6 +311,7 @@ export const regions: Region[] = [
ipv6:
'2400:8905::f03c:93ff:fe9d:b085, 2400:8905::f03c:93ff:fe9d:b012, 2400:8905::f03c:93ff:fe9d:b09b, 2400:8905::f03c:93ff:fe9d:b0d8, 2400:8905::f03c:93ff:fe9d:259f, 2400:8905::f03c:93ff:fe9d:b006, 2400:8905::f03c:93ff:fe9d:b084, 2400:8905::f03c:93ff:fe9d:b0ce, 2400:8905::f03c:93ff:fe9d:25ea, 2400:8905::f03c:93ff:fe9d:b086',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -325,6 +337,7 @@ export const regions: Region[] = [
ipv6:
'2600:3c0b::f03c:93ff:feba:d513, 2600:3c0b::f03c:93ff:feba:d5c3, 2600:3c0b::f03c:93ff:feba:d597, 2600:3c0b::f03c:93ff:feba:d5fb, 2600:3c0b::f03c:93ff:feba:d51f, 2600:3c0b::f03c:93ff:feba:d58e, 2600:3c0b::f03c:93ff:feba:d5d5, 2600:3c0b::f03c:93ff:feba:d534, 2600:3c0b::f03c:93ff:feba:d57c, 2600:3c0b::f03c:93ff:feba:d529',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -350,6 +363,7 @@ export const regions: Region[] = [
ipv6:
'2a01:7e04::f03c:93ff:fead:d31f, 2a01:7e04::f03c:93ff:fead:d37f, 2a01:7e04::f03c:93ff:fead:d30c, 2a01:7e04::f03c:93ff:fead:d318, 2a01:7e04::f03c:93ff:fead:d316, 2a01:7e04::f03c:93ff:fead:d339, 2a01:7e04::f03c:93ff:fead:d367, 2a01:7e04::f03c:93ff:fead:d395, 2a01:7e04::f03c:93ff:fead:d3d0, 2a01:7e04::f03c:93ff:fead:d38e',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -375,6 +389,7 @@ export const regions: Region[] = [
ipv6:
'2600:3c0c::f03c:93ff:feed:a90b, 2600:3c0c::f03c:93ff:feed:a9a5, 2600:3c0c::f03c:93ff:feed:a935, 2600:3c0c::f03c:93ff:feed:a930, 2600:3c0c::f03c:93ff:feed:a95c, 2600:3c0c::f03c:93ff:feed:a9ad, 2600:3c0c::f03c:93ff:feed:a9f2, 2600:3c0c::f03c:93ff:feed:a9ff, 2600:3c0c::f03c:93ff:feed:a9c8, 2600:3c0c::f03c:93ff:feed:a96b',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -400,6 +415,7 @@ export const regions: Region[] = [
ipv6:
'2a01:7e03::f03c:93ff:feb1:b789, 2a01:7e03::f03c:93ff:feb1:b717, 2a01:7e03::f03c:93ff:feb1:b707, 2a01:7e03::f03c:93ff:feb1:b7ab, 2a01:7e03::f03c:93ff:feb1:b7e2, 2a01:7e03::f03c:93ff:feb1:b709, 2a01:7e03::f03c:93ff:feb1:b7a6, 2a01:7e03::f03c:93ff:feb1:b750, 2a01:7e03::f03c:93ff:feb1:b76e, 2a01:7e03::f03c:93ff:feb1:b7a2',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -424,6 +440,7 @@ export const regions: Region[] = [
ipv6:
'2600:3c00::2, 2600:3c00::9, 2600:3c00::7, 2600:3c00::5, 2600:3c00::3, 2600:3c00::8, 2600:3c00::6, 2600:3c00::4, 2600:3c00::c, 2600:3c00::b',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -447,6 +464,7 @@ export const regions: Region[] = [
ipv6:
'2600:3c01::2, 2600:3c01::9, 2600:3c01::5, 2600:3c01::7, 2600:3c01::3, 2600:3c01::8, 2600:3c01::4, 2600:3c01::b, 2600:3c01::c, 2600:3c01::6',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -473,6 +491,7 @@ export const regions: Region[] = [
ipv6:
'2600:3c02::3, 2600:3c02::5, 2600:3c02::4, 2600:3c02::6, 2600:3c02::c, 2600:3c02::7, 2600:3c02::2, 2600:3c02::9, 2600:3c02::8, 2600:3c02::b',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -500,6 +519,7 @@ export const regions: Region[] = [
ipv6:
'2600:3c03::7, 2600:3c03::4, 2600:3c03::9, 2600:3c03::6, 2600:3c03::3, 2600:3c03::c, 2600:3c03::5, 2600:3c03::b, 2600:3c03::2, 2600:3c03::8',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -524,6 +544,7 @@ export const regions: Region[] = [
ipv6:
'2a01:7e00::9, 2a01:7e00::3, 2a01:7e00::c, 2a01:7e00::5, 2a01:7e00::6, 2a01:7e00::8, 2a01:7e00::b, 2a01:7e00::4, 2a01:7e00::7, 2a01:7e00::2',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -550,6 +571,7 @@ export const regions: Region[] = [
ipv6:
'2400:8901::5, 2400:8901::4, 2400:8901::b, 2400:8901::3, 2400:8901::9, 2400:8901::2, 2400:8901::8, 2400:8901::7, 2400:8901::c, 2400:8901::6',
},
+ site_type: 'core',
status: 'ok',
},
{
@@ -576,6 +598,23 @@ export const regions: Region[] = [
ipv6:
'2a01:7e01::5, 2a01:7e01::9, 2a01:7e01::7, 2a01:7e01::c, 2a01:7e01::2, 2a01:7e01::4, 2a01:7e01::3, 2a01:7e01::6, 2a01:7e01::b, 2a01:7e01::8',
},
+ site_type: 'core',
+ status: 'ok',
+ },
+ {
+ capabilities: ['Linodes'],
+ country: 'us',
+ id: 'us-edgetest',
+ label: 'Gecko Edge Test',
+ maximum_pgs_per_customer: 5,
+ maximum_vms_per_pg: 10,
+ resolvers: {
+ ipv4:
+ '139.162.130.5, 139.162.131.5, 139.162.132.5, 139.162.133.5, 139.162.134.5, 139.162.135.5, 139.162.136.5, 139.162.137.5, 139.162.138.5, 139.162.139.5',
+ ipv6:
+ '2a01:7e01::5, 2a01:7e01::9, 2a01:7e01::7, 2a01:7e01::c, 2a01:7e01::2, 2a01:7e01::4, 2a01:7e01::3, 2a01:7e01::6, 2a01:7e01::b, 2a01:7e01::8',
+ },
+ site_type: 'edge',
status: 'ok',
},
];
diff --git a/packages/manager/src/assets/icons/entityIcons/edge-server.svg b/packages/manager/src/assets/icons/entityIcons/edge-server.svg
new file mode 100644
index 00000000000..1ca05241491
--- /dev/null
+++ b/packages/manager/src/assets/icons/entityIcons/edge-server.svg
@@ -0,0 +1,7 @@
+
+
diff --git a/packages/manager/src/components/Autocomplete/Autocomplete.tsx b/packages/manager/src/components/Autocomplete/Autocomplete.tsx
index 3aa494f3c23..6d615d60e02 100644
--- a/packages/manager/src/components/Autocomplete/Autocomplete.tsx
+++ b/packages/manager/src/components/Autocomplete/Autocomplete.tsx
@@ -113,6 +113,12 @@ export const Autocomplete = <
InputProps={{
...params.InputProps,
...textFieldProps?.InputProps,
+ endAdornment: (
+
+ {textFieldProps?.InputProps?.endAdornment}
+ {params.InputProps.endAdornment}
+
+ ),
}}
/>
)}
diff --git a/packages/manager/src/components/RegionSelect/RegionMultiSelect.tsx b/packages/manager/src/components/RegionSelect/RegionMultiSelect.tsx
index 4b37ee42435..aa235200cb3 100644
--- a/packages/manager/src/components/RegionSelect/RegionMultiSelect.tsx
+++ b/packages/manager/src/components/RegionSelect/RegionMultiSelect.tsx
@@ -117,6 +117,11 @@ export const RegionMultiSelect = React.memo((props: RegionMultiSelectProps) => {
/>
);
}}
+ sx={(theme) => ({
+ [theme.breakpoints.up('md')]: {
+ width: '416px',
+ },
+ })}
textFieldProps={{
InputProps: {
required,
diff --git a/packages/manager/src/components/RegionSelect/RegionOption.tsx b/packages/manager/src/components/RegionSelect/RegionOption.tsx
index 38fe2feaccd..7186b1cc6c3 100644
--- a/packages/manager/src/components/RegionSelect/RegionOption.tsx
+++ b/packages/manager/src/components/RegionSelect/RegionOption.tsx
@@ -1,27 +1,36 @@
import { visuallyHidden } from '@mui/utils';
import React from 'react';
+import EdgeServer from 'src/assets/icons/entityIcons/edge-server.svg';
import { Box } from 'src/components/Box';
import { Flag } from 'src/components/Flag';
import { Link } from 'src/components/Link';
import { Tooltip } from 'src/components/Tooltip';
+import { TooltipIcon } from 'src/components/TooltipIcon';
import {
SelectedIcon,
StyledFlagContainer,
StyledListItem,
+ sxEdgeIcon,
} from './RegionSelect.styles';
import { RegionSelectOption } from './RegionSelect.types';
import type { ListItemComponentsPropsOverrides } from '@mui/material/ListItem';
type Props = {
+ displayEdgeServerIcon?: boolean;
option: RegionSelectOption;
props: React.HTMLAttributes;
- selected: boolean;
+ selected?: boolean;
};
-export const RegionOption = ({ option, props, selected }: Props) => {
+export const RegionOption = ({
+ displayEdgeServerIcon,
+ option,
+ props,
+ selected,
+}: Props) => {
const isDisabledMenuItem = option.unavailable;
return (
@@ -78,6 +87,11 @@ export const RegionOption = ({ option, props, selected }: Props) => {
{option.label}
+ {displayEdgeServerIcon && (
+
+ (This region is an Edge site.)
+
+ )}
{isDisabledMenuItem && (
Disabled option - There may be limited capacity in this region.
@@ -87,6 +101,14 @@ export const RegionOption = ({ option, props, selected }: Props) => {
)}
{selected && }
+ {displayEdgeServerIcon && (
+ }
+ status="other"
+ sxTooltipIcon={sxEdgeIcon}
+ text="This region is an edge site."
+ />
+ )}
>
diff --git a/packages/manager/src/components/RegionSelect/RegionSelect.styles.ts b/packages/manager/src/components/RegionSelect/RegionSelect.styles.ts
index b71ef206b31..d4a72010e19 100644
--- a/packages/manager/src/components/RegionSelect/RegionSelect.styles.ts
+++ b/packages/manager/src/components/RegionSelect/RegionSelect.styles.ts
@@ -20,8 +20,51 @@ export const StyledAutocompleteContainer = styled(Box, {
marginTop: -8,
},
},
+ '& .MuiAutocomplete-root .MuiAutocomplete-inputRoot': {
+ paddingRight: 8,
+ },
+ display: 'flex',
+ [theme.breakpoints.down('md')]: {
+ flexDirection: 'column',
+ },
}));
+export const sxEdgeIcon = {
+ '& svg': {
+ color: 'inherit !important',
+ height: 21,
+ width: 24,
+ },
+ '&:hover': {
+ color: 'inherit',
+ },
+ color: 'inherit',
+ padding: 0,
+};
+
+export const StyledEdgeBox = styled(Box, { label: 'StyledEdgeBox' })(
+ ({ theme }) => ({
+ '& svg': {
+ height: 21,
+ marginLeft: 8,
+ marginRight: 8,
+ width: 24,
+ },
+ alignSelf: 'end',
+ color: 'inherit',
+ display: 'flex',
+ marginLeft: 8,
+ padding: '8px 0',
+ [theme.breakpoints.down('md')]: {
+ '& svg': {
+ marginLeft: 0,
+ },
+ alignSelf: 'start',
+ marginLeft: 0,
+ },
+ })
+);
+
export const StyledFlagContainer = styled('div', {
label: 'RegionSelectFlagContainer',
})(({ theme }) => ({
diff --git a/packages/manager/src/components/RegionSelect/RegionSelect.test.tsx b/packages/manager/src/components/RegionSelect/RegionSelect.test.tsx
index 33f6cd426f0..dc1ed580610 100644
--- a/packages/manager/src/components/RegionSelect/RegionSelect.test.tsx
+++ b/packages/manager/src/components/RegionSelect/RegionSelect.test.tsx
@@ -64,4 +64,22 @@ describe('RegionSelect', () => {
);
expect(getByTestId('textfield-input')).toBeDisabled();
});
+
+ it('should render a Select component with edge region text', () => {
+ const newProps = {
+ ...props,
+ showGeckoHelperText: true,
+ };
+ const { getByTestId } = renderWithTheme();
+ expect(getByTestId('region-select-edge-text')).toBeInTheDocument();
+ });
+
+ it('should render a Select component with no edge region text', () => {
+ const newProps = {
+ ...props,
+ showGeckoHelperText: false,
+ };
+ const { queryByTestId } = renderWithTheme();
+ expect(queryByTestId('region-select-edge-text')).not.toBeInTheDocument();
+ });
});
diff --git a/packages/manager/src/components/RegionSelect/RegionSelect.tsx b/packages/manager/src/components/RegionSelect/RegionSelect.tsx
index 683728688ef..c1ea6e51432 100644
--- a/packages/manager/src/components/RegionSelect/RegionSelect.tsx
+++ b/packages/manager/src/components/RegionSelect/RegionSelect.tsx
@@ -1,13 +1,19 @@
+import { Typography } from '@mui/material';
import * as React from 'react';
+import EdgeServer from 'src/assets/icons/entityIcons/edge-server.svg';
import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
import { Flag } from 'src/components/Flag';
+import { Link } from 'src/components/Link';
+import { TooltipIcon } from 'src/components/TooltipIcon';
import { useAccountAvailabilitiesQueryUnpaginated } from 'src/queries/accountAvailability';
import { RegionOption } from './RegionOption';
import {
StyledAutocompleteContainer,
+ StyledEdgeBox,
StyledFlagContainer,
+ sxEdgeIcon,
} from './RegionSelect.styles';
import { getRegionOptions, getSelectedRegionById } from './RegionSelect.utils';
@@ -20,14 +26,17 @@ import type {
* A specific select for regions.
*
* The RegionSelect automatically filters regions based on capability using its `currentCapability` prop. For example, if
- * `currentCapability="VPCs"`, only regions that support VPCs will appear in the RegionSelect dropdown. There is no need to
- * prefilter regions when passing them to the RegionSelect. See the description of `currentCapability` prop for more information.
+ * `currentCapability="VPCs"`, only regions that support VPCs will appear in the RegionSelect dropdown. Edge regions are filtered based on the `hideEdgeServers` prop.
+ * There is no need to pre-filter regions when passing them to the RegionSelect. See the description of `currentCapability` prop for more information.
+ *
+ * We do not display the selected check mark for single selects.
*/
export const RegionSelect = React.memo((props: RegionSelectProps) => {
const {
currentCapability,
disabled,
errorText,
+ geckoEnabled,
handleSelection,
helperText,
isClearable,
@@ -35,6 +44,7 @@ export const RegionSelect = React.memo((props: RegionSelectProps) => {
regions,
required,
selectedId,
+ showGeckoHelperText,
width,
} = props;
@@ -74,9 +84,10 @@ export const RegionSelect = React.memo((props: RegionSelectProps) => {
getRegionOptions({
accountAvailabilityData: accountAvailability,
currentCapability,
+ hideEdgeServers: !geckoEnabled,
regions,
}),
- [accountAvailability, currentCapability, regions]
+ [accountAvailability, currentCapability, regions, geckoEnabled]
);
return (
@@ -95,18 +106,34 @@ export const RegionSelect = React.memo((props: RegionSelectProps) => {
handleRegionChange(null);
}
}}
- renderOption={(props, option, { selected }) => {
+ renderOption={(props, option) => {
return (
);
}}
+ sx={(theme) => ({
+ [theme.breakpoints.up('md')]: {
+ width: '416px',
+ },
+ })}
textFieldProps={{
InputProps: {
+ endAdornment: geckoEnabled &&
+ selectedRegion?.site_type === 'edge' && (
+ }
+ status="other"
+ sxTooltipIcon={sxEdgeIcon}
+ text="This region is an Edge server."
+ />
+ ),
required,
startAdornment: selectedRegion && (
@@ -132,6 +159,22 @@ export const RegionSelect = React.memo((props: RegionSelectProps) => {
placeholder="Select a Region"
value={selectedRegion}
/>
+ {showGeckoHelperText && ( // @TODO Gecko MVP: Add docs link
+
+
+
+ {' '}
+ Indicates an Edge server region.{' '}
+
+ Learn more
+
+ .
+
+
+ )}
);
});
diff --git a/packages/manager/src/components/RegionSelect/RegionSelect.types.ts b/packages/manager/src/components/RegionSelect/RegionSelect.types.ts
index 90460892f2f..1d6b6b16073 100644
--- a/packages/manager/src/components/RegionSelect/RegionSelect.types.ts
+++ b/packages/manager/src/components/RegionSelect/RegionSelect.types.ts
@@ -5,6 +5,7 @@ import type {
Capabilities,
Country,
Region,
+ RegionSite,
} from '@linode/api-v4';
import type { EnhancedAutocompleteProps } from 'src/components/Autocomplete/Autocomplete';
@@ -14,6 +15,7 @@ export interface RegionSelectOption {
region: string;
};
label: string;
+ site_type: RegionSite;
unavailable: boolean;
value: string;
}
@@ -31,6 +33,7 @@ export interface RegionSelectProps
* See `ImageUpload.tsx` for an example of a RegionSelect with an undefined `currentCapability` - there is no capability associated with Images yet.
*/
currentCapability: Capabilities | undefined;
+ geckoEnabled?: boolean;
handleSelection: (id: string) => void;
helperText?: string;
isClearable?: boolean;
@@ -38,6 +41,7 @@ export interface RegionSelectProps
regions: Region[];
required?: boolean;
selectedId: null | string;
+ showGeckoHelperText?: boolean;
width?: number;
}
@@ -68,6 +72,7 @@ export interface RegionOptionAvailability {
}
export interface GetRegionOptions extends RegionOptionAvailability {
+ hideEdgeServers?: boolean;
regions: Region[];
}
@@ -86,3 +91,5 @@ export interface GetSelectedRegionsByIdsArgs {
regions: Region[];
selectedRegionIds: string[];
}
+
+export type SupportedEdgeTypes = 'Distributions' | 'StackScripts';
diff --git a/packages/manager/src/components/RegionSelect/RegionSelect.utils.test.tsx b/packages/manager/src/components/RegionSelect/RegionSelect.utils.test.tsx
index f7a4b1c2acc..5b424acc1c7 100644
--- a/packages/manager/src/components/RegionSelect/RegionSelect.utils.test.tsx
+++ b/packages/manager/src/components/RegionSelect/RegionSelect.utils.test.tsx
@@ -38,6 +38,17 @@ const regions: Region[] = [
}),
];
+const regionsWithEdge = [
+ ...regions,
+ regionFactory.build({
+ capabilities: ['Linodes'],
+ country: 'us',
+ id: 'us-edge-1',
+ label: 'Gecko Edge Test',
+ site_type: 'edge',
+ }),
+];
+
const expectedRegions: RegionSelectOption[] = [
{
data: {
@@ -45,18 +56,21 @@ const expectedRegions: RegionSelectOption[] = [
region: 'North America',
},
label: 'US Location (us-1)',
+ site_type: 'core',
unavailable: false,
value: 'us-1',
},
{
data: { country: 'ca', region: 'North America' },
label: 'CA Location (ca-1)',
+ site_type: 'core',
unavailable: false,
value: 'ca-1',
},
{
data: { country: 'jp', region: 'Asia' },
label: 'JP Location (jp-1)',
+ site_type: 'core',
unavailable: false,
value: 'jp-1',
},
@@ -103,6 +117,39 @@ describe('getRegionOptions', () => {
expect(result).toEqual(expectedRegions);
});
+
+ it('should filter out edge regions if hideEdgeServers is true', () => {
+ const result: RegionSelectOption[] = getRegionOptions({
+ accountAvailabilityData,
+ currentCapability: 'Linodes',
+ hideEdgeServers: true,
+ regions: regionsWithEdge,
+ });
+
+ expect(result).toEqual(expectedRegions);
+ });
+
+ it('should not filter out edge regions if hideEdgeServers is false', () => {
+ const expectedRegionsWithEdge = [
+ {
+ data: { country: 'us', region: 'North America' },
+ label: 'Gecko Edge Test (us-edge-1)',
+ site_type: 'edge',
+ unavailable: false,
+ value: 'us-edge-1',
+ },
+ ...expectedRegions,
+ ];
+
+ const result: RegionSelectOption[] = getRegionOptions({
+ accountAvailabilityData,
+ currentCapability: 'Linodes',
+ hideEdgeServers: false,
+ regions: regionsWithEdge,
+ });
+
+ expect(result).toEqual(expectedRegionsWithEdge);
+ });
});
describe('getSelectedRegionById', () => {
@@ -123,6 +170,7 @@ describe('getSelectedRegionById', () => {
region: 'North America',
},
label: 'US Location (us-1)',
+ site_type: 'core',
unavailable: false,
value: 'us-1',
};
@@ -188,6 +236,7 @@ describe('getSelectedRegionsByIds', () => {
region: 'North America',
},
label: 'US Location (us-1)',
+ site_type: 'core',
unavailable: false,
value: 'us-1',
},
@@ -197,6 +246,7 @@ describe('getSelectedRegionsByIds', () => {
region: 'North America',
},
label: 'CA Location (ca-1)',
+ site_type: 'core',
unavailable: false,
value: 'ca-1',
},
@@ -222,6 +272,7 @@ describe('getSelectedRegionsByIds', () => {
region: 'North America',
},
label: 'US Location (us-1)',
+ site_type: 'core',
unavailable: false,
value: 'us-1',
},
diff --git a/packages/manager/src/components/RegionSelect/RegionSelect.utils.ts b/packages/manager/src/components/RegionSelect/RegionSelect.utils.ts
index d5f54ca0304..597b7b1276d 100644
--- a/packages/manager/src/components/RegionSelect/RegionSelect.utils.ts
+++ b/packages/manager/src/components/RegionSelect/RegionSelect.utils.ts
@@ -1,5 +1,6 @@
import { CONTINENT_CODE_TO_CONTINENT } from '@linode/api-v4';
+import { useFlags } from 'src/hooks/useFlags';
import {
getRegionCountryGroup,
getSelectedRegion,
@@ -11,8 +12,10 @@ import type {
GetSelectedRegionById,
GetSelectedRegionsByIdsArgs,
RegionSelectOption,
+ SupportedEdgeTypes,
} from './RegionSelect.types';
import type { AccountAvailability, Region } from '@linode/api-v4';
+import type { LinodeCreateType } from 'src/features/Linodes/LinodesCreate/types';
const NORTH_AMERICA = CONTINENT_CODE_TO_CONTINENT.NA;
@@ -25,15 +28,22 @@ const NORTH_AMERICA = CONTINENT_CODE_TO_CONTINENT.NA;
export const getRegionOptions = ({
accountAvailabilityData,
currentCapability,
+ hideEdgeServers = false,
regions,
}: GetRegionOptions): RegionSelectOption[] => {
- const filteredRegions = currentCapability
+ const filteredRegionsByCapability = currentCapability
? regions.filter((region) =>
region.capabilities.includes(currentCapability)
)
: regions;
- return filteredRegions
+ const filteredRegionsByCapabilityAndSiteType = hideEdgeServers
+ ? filteredRegionsByCapability.filter(
+ (region) => region.site_type !== 'edge'
+ )
+ : filteredRegionsByCapability;
+
+ return filteredRegionsByCapabilityAndSiteType
.map((region: Region) => {
const group = getRegionCountryGroup(region);
@@ -43,6 +53,7 @@ export const getRegionOptions = ({
region: group,
},
label: `${region.label} (${region.id})`,
+ site_type: region.site_type,
unavailable: getRegionOptionAvailability({
accountAvailabilityData,
currentCapability,
@@ -116,6 +127,7 @@ export const getSelectedRegionById = ({
region: group,
},
label: `${selectedRegion.label} (${selectedRegion.id})`,
+ site_type: selectedRegion.site_type,
unavailable: getRegionOptionAvailability({
accountAvailabilityData,
currentCapability,
@@ -175,3 +187,21 @@ export const getSelectedRegionsByIds = ({
)
.filter((region): region is RegionSelectOption => !!region);
};
+
+/**
+ * Util to determine whether a create type has support for edge regions.
+ *
+ * @returns a boolean indicating whether or not to enable gecko.
+ */
+export const useIsGeckoEnabled = (createType: LinodeCreateType) => {
+ const flags = useFlags();
+
+ const supportedEdgeTypes: SupportedEdgeTypes[] = [
+ 'Distributions',
+ 'StackScripts',
+ ];
+ return Boolean(
+ flags.gecko &&
+ !supportedEdgeTypes.includes(createType as SupportedEdgeTypes)
+ );
+};
diff --git a/packages/manager/src/components/SelectRegionPanel/SelectRegionPanel.test.tsx b/packages/manager/src/components/SelectRegionPanel/SelectRegionPanel.test.tsx
index 2a21db6cd10..ab77c8c36be 100644
--- a/packages/manager/src/components/SelectRegionPanel/SelectRegionPanel.test.tsx
+++ b/packages/manager/src/components/SelectRegionPanel/SelectRegionPanel.test.tsx
@@ -11,6 +11,7 @@ const pricingMocks = vi.hoisted(() => ({
}));
const queryParamMocks = vi.hoisted(() => ({
+ getQueryParamFromQueryString: vi.fn().mockReturnValue({}),
getQueryParamsFromQueryString: vi.fn().mockReturnValue({}),
}));
@@ -20,6 +21,7 @@ vi.mock('src/utilities/pricing/linodes', () => ({
}));
vi.mock('src/utilities/queryParams', () => ({
+ getQueryParamFromQueryString: queryParamMocks.getQueryParamFromQueryString,
getQueryParamsFromQueryString: queryParamMocks.getQueryParamsFromQueryString,
}));
diff --git a/packages/manager/src/components/SelectRegionPanel/SelectRegionPanel.tsx b/packages/manager/src/components/SelectRegionPanel/SelectRegionPanel.tsx
index 4db3bdde0ce..0d3db1b4740 100644
--- a/packages/manager/src/components/SelectRegionPanel/SelectRegionPanel.tsx
+++ b/packages/manager/src/components/SelectRegionPanel/SelectRegionPanel.tsx
@@ -6,6 +6,7 @@ import { useLocation } from 'react-router-dom';
import { Notice } from 'src/components/Notice/Notice';
import { Paper } from 'src/components/Paper';
import { RegionSelect } from 'src/components/RegionSelect/RegionSelect';
+import { useIsGeckoEnabled } from 'src/components/RegionSelect/RegionSelect.utils';
import { RegionHelperText } from 'src/components/SelectRegionPanel/RegionHelperText';
import { Typography } from 'src/components/Typography';
import { CROSS_DATA_CENTER_CLONE_WARNING } from 'src/features/Linodes/LinodesCreate/constants';
@@ -22,6 +23,8 @@ import { Box } from '../Box';
import { DocsLink } from '../DocsLink/DocsLink';
import { Link } from '../Link';
+import type { LinodeCreateType } from 'src/features/Linodes/LinodesCreate/types';
+
interface SelectRegionPanelProps {
currentCapability: Capabilities;
disabled?: boolean;
@@ -72,6 +75,18 @@ export const SelectRegionPanel = (props: SelectRegionPanelProps) => {
type,
});
+ const geckoEnabled = useIsGeckoEnabled(params.type as LinodeCreateType);
+
+ const showGeckoHelperText = Boolean(
+ geckoEnabled &&
+ currentCapability &&
+ regions.find(
+ (region) =>
+ region.site_type === 'edge' &&
+ region.capabilities.includes(currentCapability)
+ )
+ );
+
if (props.regions.length === 0) {
return null;
}
@@ -116,10 +131,12 @@ export const SelectRegionPanel = (props: SelectRegionPanelProps) => {
currentCapability={currentCapability}
disabled={disabled}
errorText={error}
+ geckoEnabled={geckoEnabled}
handleSelection={handleSelection}
helperText={helperText}
regions={regions}
selectedId={selectedId || null}
+ showGeckoHelperText={showGeckoHelperText}
/>
{showClonePriceWarning && (
({
maximum_pgs_per_customer: 5,
maximum_vms_per_pg: 10,
resolvers: resolverFactory.build(),
+ site_type: 'core',
status: 'ok',
});
@@ -42,6 +43,7 @@ export const regionWithDynamicPricingFactory = Factory.Sync.makeFactory(
maximum_pgs_per_customer: 5,
maximum_vms_per_pg: 10,
resolvers: resolverFactory.build(),
+ site_type: 'core',
status: 'ok',
}
);
diff --git a/packages/manager/src/featureFlags.ts b/packages/manager/src/featureFlags.ts
index c972cf17c3f..8e03993b944 100644
--- a/packages/manager/src/featureFlags.ts
+++ b/packages/manager/src/featureFlags.ts
@@ -47,6 +47,7 @@ export interface Flags {
databaseScaleUp: boolean;
databases: boolean;
firewallNodebalancer: boolean;
+ gecko: boolean;
ipv6Sharing: boolean;
linodeCloneUIChanges: boolean;
linodeCreateWithFirewall: boolean;
diff --git a/packages/manager/src/features/Images/ImagesCreate/CreateImageTab.tsx b/packages/manager/src/features/Images/ImagesCreate/CreateImageTab.tsx
index 6773a912703..88717cc573e 100644
--- a/packages/manager/src/features/Images/ImagesCreate/CreateImageTab.tsx
+++ b/packages/manager/src/features/Images/ImagesCreate/CreateImageTab.tsx
@@ -21,6 +21,7 @@ import { useFlags } from 'src/hooks/useFlags';
import { useEventsPollingActions } from 'src/queries/events/events';
import { useCreateImageMutation } from 'src/queries/images';
import { useGrants, useProfile } from 'src/queries/profile';
+import { useRegionsQuery } from 'src/queries/regions';
import { getAPIErrorOrDefault } from 'src/utilities/errorUtils';
import { getAPIErrorFor } from 'src/utilities/getAPIErrorFor';
@@ -99,6 +100,7 @@ export const CreateImageTab: React.FC = (props) => {
const { data: profile } = useProfile();
const { data: grants } = useGrants();
const flags = useFlags();
+ const { data: regions } = useRegionsQuery();
const { mutateAsync: createImage } = useCreateImageMutation();
@@ -236,6 +238,9 @@ export const CreateImageTab: React.FC = (props) => {
const linodeError = hasErrorFor('linode_id');
const diskError = hasErrorFor('disk_id');
+ const linodeIsNotInEdgeRegion = (linodeRegion: string) =>
+ regions?.find((region) => region.id === linodeRegion)?.site_type !== 'edge';
+
return (
{!canCreateImage ? (
@@ -251,7 +256,9 @@ export const CreateImageTab: React.FC = (props) => {
- availableLinodesToImagize?.includes(linode.id) ?? true
+ (linodeIsNotInEdgeRegion(linode.region) &&
+ availableLinodesToImagize?.includes(linode.id)) ??
+ true
}
disabled={!canCreateImage}
errorText={linodeError}
diff --git a/packages/manager/src/features/Linodes/LinodesCreate/LinodeCreate.tsx b/packages/manager/src/features/Linodes/LinodesCreate/LinodeCreate.tsx
index 63f2f259c6c..8559fe6cb9c 100644
--- a/packages/manager/src/features/Linodes/LinodesCreate/LinodeCreate.tsx
+++ b/packages/manager/src/features/Linodes/LinodesCreate/LinodeCreate.tsx
@@ -39,11 +39,11 @@ import { RegionsProps } from 'src/containers/regions.container';
import { WithTypesProps } from 'src/containers/types.container';
import { WithLinodesProps } from 'src/containers/withLinodes.container';
import { EUAgreementCheckbox } from 'src/features/Account/Agreements/EUAgreementCheckbox';
-import { regionSupportsMetadata } from 'src/features/Linodes/LinodesCreate/utilities';
import {
getMonthlyAndHourlyNodePricing,
utoa,
} from 'src/features/Linodes/LinodesCreate/utilities';
+import { regionSupportsMetadata } from 'src/features/Linodes/LinodesCreate/utilities';
import { SMTPRestrictionText } from 'src/features/Linodes/SMTPRestrictionText';
import { hasPlacementGroupReachedCapacity } from 'src/features/PlacementGroups/utils';
import {
@@ -102,6 +102,7 @@ import {
} from './types';
import type { Tab } from 'src/components/Tabs/TabLinkList';
+import type { LinodeCreateType } from 'src/features/Linodes/LinodesCreate/types';
export interface LinodeCreateProps {
additionalIPv4RangesForVPC: ExtendedIP[];
@@ -205,7 +206,7 @@ export class LinodeCreate extends React.PureComponent<
/** Get the query params as an object, excluding the "?" */
const queryParams = getQueryParamsFromQueryString(location.search);
- const _tabs = [
+ const _tabs: LinodeCreateType[] = [
'Distributions',
'One-Click',
'StackScripts',
diff --git a/packages/manager/src/features/Linodes/LinodesCreate/types.ts b/packages/manager/src/features/Linodes/LinodesCreate/types.ts
index c7efd6d39bb..0907d08a61c 100644
--- a/packages/manager/src/features/Linodes/LinodesCreate/types.ts
+++ b/packages/manager/src/features/Linodes/LinodesCreate/types.ts
@@ -167,3 +167,11 @@ export type AllFormStateAndHandlers = BaseFormStateAndHandlers &
CloneFormStateHandlers &
StackScriptFormStateHandlers &
BackupFormStateHandlers;
+
+export type LinodeCreateType =
+ | 'Backups'
+ | 'Clone Linode'
+ | 'Distributions'
+ | 'Images'
+ | 'One-Click'
+ | 'StackScripts';
diff --git a/packages/manager/src/mocks/serverHandlers.ts b/packages/manager/src/mocks/serverHandlers.ts
index dc0496d1fe3..8a577f9c373 100644
--- a/packages/manager/src/mocks/serverHandlers.ts
+++ b/packages/manager/src/mocks/serverHandlers.ts
@@ -667,6 +667,11 @@ export const handlers = [
label: 'metadata-test-region',
region: 'eu-west',
});
+ const linodeInEdgeRegion = linodeFactory.build({
+ image: 'edge-test-image',
+ label: 'edge-test-region',
+ region: 'us-southeast',
+ });
const onlineLinodes = linodeFactory.buildList(40, {
backups: { enabled: false },
ipv4: ['000.000.000.000'],
@@ -697,6 +702,7 @@ export const handlers = [
const linodes = [
metadataLinodeWithCompatibleImage,
metadataLinodeWithCompatibleImageAndRegion,
+ linodeInEdgeRegion,
...onlineLinodes,
linodeWithEligibleVolumes,
...offlineLinodes,
diff --git a/packages/manager/src/utilities/regions.test.ts b/packages/manager/src/utilities/regions.test.ts
index 99eee287b3d..c705afefcc4 100644
--- a/packages/manager/src/utilities/regions.test.ts
+++ b/packages/manager/src/utilities/regions.test.ts
@@ -13,6 +13,7 @@ describe('getRegionsByRegionId', () => {
maximum_pgs_per_customer: 5,
maximum_vms_per_pg: 10,
resolvers: { ipv4: '', ipv6: '' },
+ site_type: 'core',
status: 'ok',
},
{
@@ -23,6 +24,7 @@ describe('getRegionsByRegionId', () => {
maximum_pgs_per_customer: 5,
maximum_vms_per_pg: 10,
resolvers: { ipv4: '', ipv6: '' },
+ site_type: 'core',
status: 'ok',
},
];
@@ -36,6 +38,7 @@ describe('getRegionsByRegionId', () => {
maximum_pgs_per_customer: 5,
maximum_vms_per_pg: 10,
resolvers: { ipv4: '', ipv6: '' },
+ site_type: 'core',
status: 'ok',
},
'us-southeast': {
@@ -46,6 +49,7 @@ describe('getRegionsByRegionId', () => {
maximum_pgs_per_customer: 5,
maximum_vms_per_pg: 10,
resolvers: { ipv4: '', ipv6: '' },
+ site_type: 'core',
status: 'ok',
},
};