From 1b0ef7dc774b30b54070f9baebd0f291cde8cb8f Mon Sep 17 00:00:00 2001 From: Alban Bailly <130582365+abailly-akamai@users.noreply.github.com> Date: Thu, 5 Oct 2023 12:04:58 -0400 Subject: [PATCH 1/2] hotfix: [M3-7234] - Disable Tokyo in RegionSelect (#9758) * Implement fake region and logic * Bump package * Cleanup * File renaming * Changelog entry * Changelog entry update * Improve code quality * Increase width of volume create fields * Increase width of volume create fields - mobile * Consolidate API * Exclude paths logic --- packages/manager/CHANGELOG.md | 6 ++ packages/manager/package.json | 2 +- .../RegionSelect/RegionSelect.test.tsx | 19 +++++- .../variants/RegionSelect/RegionSelect.tsx | 51 ++++++++++++++- .../variants/RegionSelect/disabledRegions.tsx | 65 +++++++++++++++++++ .../manager/src/dev-tools/FeatureFlagTool.tsx | 1 + packages/manager/src/featureFlags.ts | 1 + .../src/features/Volumes/VolumeCreate.tsx | 20 ++++-- 8 files changed, 151 insertions(+), 14 deletions(-) create mode 100644 packages/manager/src/components/EnhancedSelect/variants/RegionSelect/disabledRegions.tsx diff --git a/packages/manager/CHANGELOG.md b/packages/manager/CHANGELOG.md index c4470f444c7..21b794450b4 100644 --- a/packages/manager/CHANGELOG.md +++ b/packages/manager/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [2023-10-02] - v1.104.1 + +### Fixed: + +- Add disabled Tokyo RegionSelect menu entry ([#9758](https://github.com/linode/manager/pull/9758)) + ## [2023-10-02] - v1.104.0 ### Added: diff --git a/packages/manager/package.json b/packages/manager/package.json index dce0cd4ac97..2705aee58f9 100644 --- a/packages/manager/package.json +++ b/packages/manager/package.json @@ -2,7 +2,7 @@ "name": "linode-manager", "author": "Linode", "description": "The Linode Manager website", - "version": "1.104.0", + "version": "1.104.1", "private": true, "bugs": { "url": "https://github.com/Linode/manager/issues" diff --git a/packages/manager/src/components/EnhancedSelect/variants/RegionSelect/RegionSelect.test.tsx b/packages/manager/src/components/EnhancedSelect/variants/RegionSelect/RegionSelect.test.tsx index c06f841388a..eee41ff1732 100644 --- a/packages/manager/src/components/EnhancedSelect/variants/RegionSelect/RegionSelect.test.tsx +++ b/packages/manager/src/components/EnhancedSelect/variants/RegionSelect/RegionSelect.test.tsx @@ -3,11 +3,16 @@ import { regions } from 'src/__data__/regionsData'; import { getRegionOptions, getSelectedRegionById } from './RegionSelect'; const fakeRegion = { ...regions[0], country: 'fake iso code' }; +const flags = {}; describe('Region Select helper functions', () => { describe('getRegionOptions', () => { it('should return a list of items grouped by continent', () => { - const groupedRegions = getRegionOptions(regions); + const groupedRegions = getRegionOptions( + regions, + flags, + '/linodes/create' + ); const [r1, r2, r3, r4, r5] = groupedRegions; expect(groupedRegions).toHaveLength(8); expect(r1.options).toHaveLength(5); @@ -18,7 +23,11 @@ describe('Region Select helper functions', () => { }); it('should group unrecognized regions as Other', () => { - const groupedRegions = getRegionOptions([fakeRegion]); + const groupedRegions = getRegionOptions( + [fakeRegion], + flags, + '/linodes/create' + ); expect( groupedRegions.find((group) => group.label === 'Other') ).toBeDefined(); @@ -27,7 +36,11 @@ describe('Region Select helper functions', () => { describe('getSelectedRegionById', () => { it('should return the matching Item from a list of GroupedItems', () => { - const groupedRegions = getRegionOptions(regions); + const groupedRegions = getRegionOptions( + regions, + flags, + '/linodes/create' + ); const selectedID = regions[1].id; expect(getSelectedRegionById(selectedID, groupedRegions)).toHaveProperty( 'value', diff --git a/packages/manager/src/components/EnhancedSelect/variants/RegionSelect/RegionSelect.tsx b/packages/manager/src/components/EnhancedSelect/variants/RegionSelect/RegionSelect.tsx index bec5e5cdf55..3e4b50da8b0 100644 --- a/packages/manager/src/components/EnhancedSelect/variants/RegionSelect/RegionSelect.tsx +++ b/packages/manager/src/components/EnhancedSelect/variants/RegionSelect/RegionSelect.tsx @@ -1,5 +1,7 @@ +/* eslint-disable perfectionist/sort-objects */ import { Region } from '@linode/api-v4/lib/regions'; import * as React from 'react'; +import { useLocation } from 'react-router-dom'; import Select, { BaseSelectProps, @@ -8,11 +10,15 @@ import Select, { } from 'src/components/EnhancedSelect/Select'; import { _SingleValue } from 'src/components/EnhancedSelect/components/SingleValue'; import { Flag } from 'src/components/Flag'; +import { useFlags } from 'src/hooks/useFlags'; import { getRegionCountryGroup } from 'src/utilities/formatRegion'; import { RegionItem, RegionOption } from './RegionOption'; +import { listOfDisabledRegions } from './disabledRegions'; import { ContinentNames, Country } from './utils'; +import type { FlagSet } from 'src/featureFlags'; + interface Props extends Omit< BaseSelectProps, false, IsClearable>, @@ -33,7 +39,11 @@ export const selectStyles = { type RegionGroup = 'Other' | ContinentNames; -export const getRegionOptions = (regions: Region[]) => { +export const getRegionOptions = ( + regions: Region[], + flags: FlagSet, + path: string +) => { // Note: Do not re-order this list even though ESLint is complaining. const groups: Record = { 'North America': [], @@ -46,11 +56,40 @@ export const getRegionOptions = (regions: Region[]) => { Other: [], }; - for (const region of regions) { + const hasUserAccessToDisabledRegions = listOfDisabledRegions.some( + (disabledRegion) => + regions.some((region) => region.id === disabledRegion.fakeRegion.id) + ); + const allRegions = [ + ...regions, + ...listOfDisabledRegions + .filter( + (disabledRegion) => + // Only display a fake region if the feature flag for it is enabled + // We may want to consider modifying this logic if we end up with disabled regions that don't rely on feature flags + flags[disabledRegion.featureFlag] && + // Don't display a fake region if it's included in the real /regions response + !regions.some( + (region) => region.id === disabledRegion.fakeRegion.id + ) && + // Don't display a fake region if it's excluded by the current path + !disabledRegion.excludePaths?.some((pathToExclude) => + path.includes(pathToExclude) + ) + ) + .map((disabledRegion) => disabledRegion.fakeRegion), + ]; + + for (const region of allRegions) { const group = getRegionCountryGroup(region); groups[group].push({ country: region.country, + disabledMessage: hasUserAccessToDisabledRegions + ? undefined + : listOfDisabledRegions.find( + (disabledRegion) => disabledRegion.fakeRegion.id === region.id + )?.disabledMessage, flag: } />, label: `${region.label} (${region.id})`, value: region.id, @@ -108,6 +147,9 @@ export const RegionSelect = React.memo( ...restOfReactSelectProps } = props; + const flags = useFlags(); + const location = useLocation(); + const path = location.pathname; const onChange = React.useCallback( (selection: RegionItem | null) => { if (selection === null) { @@ -124,7 +166,10 @@ export const RegionSelect = React.memo( [handleSelection] ); - const options = React.useMemo(() => getRegionOptions(regions), [regions]); + const options = React.useMemo( + () => getRegionOptions(regions, flags, path), + [flags, regions] + ); return (
diff --git a/packages/manager/src/components/EnhancedSelect/variants/RegionSelect/disabledRegions.tsx b/packages/manager/src/components/EnhancedSelect/variants/RegionSelect/disabledRegions.tsx new file mode 100644 index 00000000000..0b3ec94fe8b --- /dev/null +++ b/packages/manager/src/components/EnhancedSelect/variants/RegionSelect/disabledRegions.tsx @@ -0,0 +1,65 @@ +import * as React from 'react'; + +import { Link } from 'src/components/Link'; +import { Typography } from 'src/components/Typography'; + +import type { Capabilities, Region, RegionStatus } from '@linode/api-v4'; + +// DATA +const tokyoDisabledMessage = ( + + Tokyo is sold out while we expand our capacity. We recommend deploying + workloads in Osaka.{` `} +
+ + Learn more + + . +
+); + +const fakeTokyo: FakeRegion = { + capabilities: ['Linodes', 'NodeBalancers'] as Capabilities[], + country: 'jp', + disabled: true, + display: 'Tokyo, JP', + id: 'ap-northeast', + label: 'Tokyo, JP', + resolvers: { + ipv4: + '173.230.129.5,173.230.136.5,173.230.140.5,66.228.59.5,66.228.62.5,50.116.35.5,50.116.41.5,23.239.18.5', + ipv6: '', + }, + status: 'ok' as RegionStatus, +}; + +type FakeRegion = Region & { disabled: boolean; display: string }; + +// UTILS +interface DisabledRegion { + /** + * The message to display when the region is disabled. + */ + disabledMessage: JSX.Element; + /** + * A list of paths that should not display the fake region. + */ + excludePaths?: string[]; + /** + * The fake region to display. + */ + fakeRegion: FakeRegion; + /** + * The feature flag that controls whether the fake region should be displayed. + */ + featureFlag: string; +} + +export const listOfDisabledRegions: DisabledRegion[] = [ + { + disabledMessage: tokyoDisabledMessage, + excludePaths: ['/object-storage/buckets/create'], + fakeRegion: fakeTokyo, + featureFlag: 'soldOutTokyo', + }, +]; diff --git a/packages/manager/src/dev-tools/FeatureFlagTool.tsx b/packages/manager/src/dev-tools/FeatureFlagTool.tsx index 5fd4a62d9a1..051da63d9fa 100644 --- a/packages/manager/src/dev-tools/FeatureFlagTool.tsx +++ b/packages/manager/src/dev-tools/FeatureFlagTool.tsx @@ -17,6 +17,7 @@ const options: { flag: keyof Flags; label: string }[] = [ { flag: 'aglb', label: 'AGLB' }, { flag: 'dcSpecificPricing', label: 'DC-Specific Pricing' }, { flag: 'selfServeBetas', label: 'Self Serve Betas' }, + { flag: 'soldOutTokyo', label: 'Sold Out Tokyo' }, ]; const FeatureFlagTool: React.FC<{}> = () => { diff --git a/packages/manager/src/featureFlags.ts b/packages/manager/src/featureFlags.ts index b5daae97e18..e0fd102aa0b 100644 --- a/packages/manager/src/featureFlags.ts +++ b/packages/manager/src/featureFlags.ts @@ -58,6 +58,7 @@ export interface Flags { referralBannerText: ReferralBannerText; regionDropdown: boolean; selfServeBetas: boolean; + soldOutTokyo: boolean; taxBanner: TaxBanner; taxCollectionBanner: TaxCollectionBanner; taxes: Taxes; diff --git a/packages/manager/src/features/Volumes/VolumeCreate.tsx b/packages/manager/src/features/Volumes/VolumeCreate.tsx index 015406a7352..7c36cc5dce2 100644 --- a/packages/manager/src/features/Volumes/VolumeCreate.tsx +++ b/packages/manager/src/features/Volumes/VolumeCreate.tsx @@ -9,7 +9,9 @@ import { useHistory } from 'react-router-dom'; import { Box } from 'src/components/Box'; import { Button } from 'src/components/Button/Button'; +import { DocumentTitleSegment } from 'src/components/DocumentTitle'; import { RegionSelect } from 'src/components/EnhancedSelect/variants/RegionSelect'; +import { LandingHeader } from 'src/components/LandingHeader'; import { Notice } from 'src/components/Notice/Notice'; import { Paper } from 'src/components/Paper'; import { TextField } from 'src/components/TextField'; @@ -38,8 +40,6 @@ import { maybeCastToNumber } from 'src/utilities/maybeCastToNumber'; import { ConfigSelect } from './VolumeDrawer/ConfigSelect'; import { SizeField } from './VolumeDrawer/SizeField'; -import { LandingHeader } from 'src/components/LandingHeader'; -import { DocumentTitleSegment } from 'src/components/DocumentTitle'; export const SIZE_FIELD_WIDTH = 160; @@ -86,7 +86,10 @@ const useStyles = makeStyles((theme: Theme) => ({ lineHeight: '18px', }, select: { - width: 320, + [theme.breakpoints.down('sm')]: { + width: 320, + }, + width: 400, }, size: { position: 'relative', @@ -156,6 +159,7 @@ export const VolumeCreate = () => { touched, values, } = useFormik({ + initialValues, onSubmit: (values, { resetForm, setErrors, setStatus, setSubmitting }) => { const { config_id, label, linode_id, region, size } = values; @@ -200,7 +204,6 @@ export const VolumeCreate = () => { ); }); }, - initialValues, validationSchema: CreateVolumeSchema, }); @@ -317,7 +320,7 @@ export const VolumeCreate = () => { name="region" onBlur={handleBlur} selectedID={values.region} - width={320} + width={400} /> {renderSelectTooltip( 'Volumes must be created in a region. You can choose to create a Volume in a region and attach it later to a Linode in the same region.' @@ -349,7 +352,10 @@ export const VolumeCreate = () => { linodeRegion === valuesRegion; }} sx={{ - width: '320px', + [theme.breakpoints.down('sm')]: { + width: 320, + }, + width: '400px', }} clearable disabled={doesNotHavePermission} @@ -370,7 +376,7 @@ export const VolumeCreate = () => { onBlur={handleBlur} onChange={(id: number) => setFieldValue('config_id', id)} value={config_id} - width={320} + width={[theme.breakpoints.down('sm')] ? 320 : 400} /> From d1d726c4acfda3073bf67c817cec8e73c7ae8430 Mon Sep 17 00:00:00 2001 From: Mariah Jacobs <114685994+mjac0bs@users.noreply.github.com> Date: Thu, 5 Oct 2023 10:14:22 -0700 Subject: [PATCH 2/2] hotfix: DC-specific pricing -- monthly price decimals and invoice region column (#9759) * Hide regions in past invoices * Display two decimal places for monthly prices in plan selections * Display two decimal places in lke node pools drawers * Clean up invoice util * Update e2es * Fix typo in test comments * Use two decimals only if not an int * Add unit test coverage for util * Unskip a test I forgot to reenable * Update changelog * Fix linting warning --- packages/manager/CHANGELOG.md | 1 + .../e2e/core/billing/billing-invoices.spec.ts | 80 +++++++++++++++---- .../e2e/core/kubernetes/lke-update.spec.ts | 44 +++++----- .../e2e/core/linodes/create-linode.spec.ts | 4 +- .../support/constants/dc-specific-pricing.ts | 7 +- packages/manager/src/factories/types.ts | 4 +- .../Billing/InvoiceDetail/InvoiceDetail.tsx | 16 +++- .../Billing/InvoiceDetail/InvoiceTable.tsx | 11 ++- .../Billing/PdfGenerator/PdfGenerator.ts | 2 + .../Billing/PdfGenerator/utils.test.ts | 19 ++++- .../features/Billing/PdfGenerator/utils.ts | 38 ++++++++- .../NodePoolsDisplay/AddNodePoolDrawer.tsx | 5 +- .../NodePoolsDisplay/ResizeNodePoolDrawer.tsx | 15 +++- .../KubernetesPlanSelection.test.tsx | 2 +- .../KubernetesPlanSelection.tsx | 10 ++- .../Linodes/LinodesCreate/LinodeCreate.tsx | 10 ++- .../MigrateLinode/ConfigureForm.test.tsx | 4 +- .../PlansPanel/PlanSelection.test.tsx | 2 +- .../components/PlansPanel/PlanSelection.tsx | 10 ++- .../src/utilities/pricing/constants.ts | 3 + .../utilities/pricing/dynamicPricing.test.ts | 23 +++++- .../src/utilities/pricing/dynamicPricing.ts | 12 +++ 22 files changed, 250 insertions(+), 72 deletions(-) diff --git a/packages/manager/CHANGELOG.md b/packages/manager/CHANGELOG.md index 21b794450b4..da435083957 100644 --- a/packages/manager/CHANGELOG.md +++ b/packages/manager/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ### Fixed: - Add disabled Tokyo RegionSelect menu entry ([#9758](https://github.com/linode/manager/pull/9758)) +- Display DC-specific monthly prices to two decimal places and hide blank Region column on past invoices ([#9759](https://github.com/linode/manager/pull/9759)) ## [2023-10-02] - v1.104.0 diff --git a/packages/manager/cypress/e2e/core/billing/billing-invoices.spec.ts b/packages/manager/cypress/e2e/core/billing/billing-invoices.spec.ts index 3ef131194ed..e84d8746146 100644 --- a/packages/manager/cypress/e2e/core/billing/billing-invoices.spec.ts +++ b/packages/manager/cypress/e2e/core/billing/billing-invoices.spec.ts @@ -5,6 +5,7 @@ import type { InvoiceItem, TaxSummary } from '@linode/api-v4'; import { invoiceFactory, invoiceItemFactory } from '@src/factories'; import { DateTime } from 'luxon'; +import { MAGIC_DATE_THAT_DC_SPECIFIC_PRICING_WAS_IMPLEMENTED } from 'support/constants/dc-specific-pricing'; import { mockGetInvoice, mockGetInvoiceItems, @@ -20,6 +21,18 @@ import { makeFeatureFlagData } from 'support/util/feature-flags'; import { randomItem, randomLabel, randomNumber } from 'support/util/random'; import { chooseRegion, getRegionById } from 'support/util/regions'; +/** + * Returns a string representation of a region, as shown on the invoice details page. + * + * @param regionId - ID of region for which to get label. + * + * @returns Region label in `