{
${renderMonthlyPriceToCorrectDecimalPlace(totalPrice)}/month (
{pluralize('node', 'nodes', selectedTypeInfo.count)} at $
- {renderMonthlyPriceToCorrectDecimalPlace(pricePerNode) ?? 0}
+ {renderMonthlyPriceToCorrectDecimalPlace(pricePerNode)}
/month)
{' '}
to this cluster.
diff --git a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/ResizeNodePoolDrawer.tsx b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/ResizeNodePoolDrawer.tsx
index bd3b8642f0a..558410c68f1 100644
--- a/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/ResizeNodePoolDrawer.tsx
+++ b/packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/ResizeNodePoolDrawer.tsx
@@ -14,9 +14,10 @@ import { useUpdateNodePoolMutation } from 'src/queries/kubernetes';
import { useSpecificTypes } from 'src/queries/types';
import { extendType } from 'src/utilities/extendType';
import { pluralize } from 'src/utilities/pluralize';
+import { PRICES_RELOAD_ERROR_NOTICE_TEXT } from 'src/utilities/pricing/constants';
import { renderMonthlyPriceToCorrectDecimalPlace } from 'src/utilities/pricing/dynamicPricing';
import { getKubernetesMonthlyPrice } from 'src/utilities/pricing/kubernetes';
-import { getLinodeRegionPrice } from 'src/utilities/pricing/linodes';
+import { getPrice } from 'src/utilities/pricing/linodes';
import { nodeWarning } from '../../kubeUtils';
@@ -98,10 +99,11 @@ export const ResizeNodePoolDrawer = (props: Props) => {
});
};
- const pricePerNode =
- (flags.dcSpecificPricing && planType
- ? getLinodeRegionPrice(planType, kubernetesRegionId)?.monthly
- : planType?.price.monthly) || 0;
+ const pricePerNode = getPrice(
+ planType,
+ kubernetesRegionId,
+ flags.dcSpecificPricing
+ )?.monthly;
const totalMonthlyPrice =
planType &&
@@ -127,13 +129,15 @@ export const ResizeNodePoolDrawer = (props: Props) => {
}}
>
-
- Current pool: $
- {renderMonthlyPriceToCorrectDecimalPlace(totalMonthlyPrice)}/month (
- {pluralize('node', 'nodes', nodePool.count)} at $
- {renderMonthlyPriceToCorrectDecimalPlace(pricePerNode)}
- /month)
-
+ {totalMonthlyPrice && (
+
+ Current pool: $
+ {renderMonthlyPriceToCorrectDecimalPlace(totalMonthlyPrice)}/month{' '}
+ ({pluralize('node', 'nodes', nodePool.count)} at $
+ {renderMonthlyPriceToCorrectDecimalPlace(pricePerNode)}
+ /month)
+
+ )}
{error && }
@@ -150,14 +154,17 @@ export const ResizeNodePoolDrawer = (props: Props) => {
-
- Resized pool: $
- {renderMonthlyPriceToCorrectDecimalPlace(
- updatedCount * pricePerNode
- )}
- /month ({pluralize('node', 'nodes', updatedCount)} at $
- {renderMonthlyPriceToCorrectDecimalPlace(pricePerNode)}/month)
-
+ {/* Renders total pool price/month for N nodes at price per node/month. */}
+ {pricePerNode && (
+
+ {`Resized pool: $${renderMonthlyPriceToCorrectDecimalPlace(
+ updatedCount * pricePerNode
+ )}/month`}{' '}
+ ({pluralize('node', 'nodes', updatedCount)} at $
+ {renderMonthlyPriceToCorrectDecimalPlace(pricePerNode)}
+ /month)
+
+ )}
{updatedCount < nodePool.count && (
@@ -168,6 +175,15 @@ export const ResizeNodePoolDrawer = (props: Props) => {
)}
+ {nodePool.count && (!pricePerNode || !totalMonthlyPrice) && (
+
+ )}
+
{type.heading}
-
- {' '}
- ${renderMonthlyPriceToCorrectDecimalPlace(price.monthly)}
+
+ ${renderMonthlyPriceToCorrectDecimalPlace(price?.monthly)}
+
+
+ ${price?.hourly ?? UNKNOWN_PRICE}
- ${price.hourly}
{convertMegabytesTo(type.memory, true)}
@@ -110,9 +124,11 @@ export const KubernetesPlanSelection = (
updatePlanCount(type.id, newCount)
@@ -123,7 +139,7 @@ export const KubernetesPlanSelection = (
{onAdd && (
-
+
{' '}
${renderMonthlyPriceToCorrectDecimalPlace(price?.monthly)}
-
+
{isGPU ? (
-
+
) : (
- `$${price?.hourly}`
+ `$${price?.hourly ?? UNKNOWN_PRICE}`
)}
diff --git a/packages/manager/src/utilities/pricing/backups.ts b/packages/manager/src/utilities/pricing/backups.ts
index 34074f457ed..4d31b0d766e 100644
--- a/packages/manager/src/utilities/pricing/backups.ts
+++ b/packages/manager/src/utilities/pricing/backups.ts
@@ -12,7 +12,10 @@ import type { FlagSet } from 'src/featureFlags';
export const getLinodeBackupPrice = (
type: LinodeType,
regionId: string
-): PriceObject => {
+): PriceObject | undefined => {
+ if (!type || !regionId) {
+ return undefined;
+ }
const regionSpecificBackupPrice = type.addons.backups.region_prices?.find(
(regionPrice) => regionPrice.id === regionId
);
@@ -24,7 +27,6 @@ export const getLinodeBackupPrice = (
};
}
- // TODO: M3-7063 (defaults)
return type.addons.backups.price;
};
@@ -35,20 +37,20 @@ interface BackupsPriceOptions {
}
/**
- *
+ * @returns The monthly backup price for a single linode without backups enabled;
+ * if price cannot be calculated, returns undefined.
*/
export const getMonthlyBackupsPrice = ({
flags,
region,
type,
-}: BackupsPriceOptions): PriceObject['monthly'] => {
+}: BackupsPriceOptions): PriceObject['monthly'] | undefined => {
if (!region || !type) {
- // TODO: M3-7063 (defaults)
- return 0;
+ return undefined;
}
return flags.dcSpecificPricing
- ? getLinodeBackupPrice(type, region).monthly
+ ? getLinodeBackupPrice(type, region)?.monthly
: type?.addons.backups.price.monthly;
};
@@ -68,22 +70,35 @@ export interface TotalBackupsPriceOptions {
types: LinodeType[];
}
+/**
+ * @returns The summed monthly backups prices for all linodes without backups enabled;
+ * if price cannot be calculated, returns undefined.
+ */
export const getTotalBackupsPrice = ({
flags,
linodes,
types,
}: TotalBackupsPriceOptions) => {
- return linodes.reduce((prevValue: number, linode: Linode) => {
+ return linodes.reduce((prevValue: number | undefined, linode: Linode) => {
const type = types.find((type) => type.id === linode.type);
- // TODO: M3-7063 (defaults)
- const backupsMonthlyPrice: PriceObject['monthly'] =
+ if (!type) {
+ return undefined;
+ }
+
+ const backupsMonthlyPrice: PriceObject['monthly'] | undefined =
getMonthlyBackupsPrice({
flags,
region: linode.region,
type,
- }) || 0;
+ }) || undefined;
+
+ if (backupsMonthlyPrice === null || backupsMonthlyPrice === undefined) {
+ return undefined;
+ }
- return prevValue + backupsMonthlyPrice;
+ return prevValue !== undefined
+ ? prevValue + backupsMonthlyPrice
+ : undefined;
}, 0);
};
diff --git a/packages/manager/src/utilities/pricing/constants.ts b/packages/manager/src/utilities/pricing/constants.ts
index f33f3bf74dc..7d4f4eb8432 100644
--- a/packages/manager/src/utilities/pricing/constants.ts
+++ b/packages/manager/src/utilities/pricing/constants.ts
@@ -14,6 +14,9 @@ export const OBJ_STORAGE_PRICE: ObjStoragePriceObject = {
transfer_overage: 0.005,
};
export const UNKNOWN_PRICE = '--.--';
+export const PRICE_ERROR_TOOLTIP_TEXT = 'There was an error loading the price.';
+export const PRICES_RELOAD_ERROR_NOTICE_TEXT =
+ 'There was an error retrieving prices. Please reload and try again.';
// Other constants
export const PLAN_SELECTION_NO_REGION_SELECTED_MESSAGE =
diff --git a/packages/manager/src/utilities/pricing/dynamicPricing.test.ts b/packages/manager/src/utilities/pricing/dynamicPricing.test.ts
index 8405cb84013..c24534aea9d 100644
--- a/packages/manager/src/utilities/pricing/dynamicPricing.test.ts
+++ b/packages/manager/src/utilities/pricing/dynamicPricing.test.ts
@@ -1,3 +1,5 @@
+import { UNKNOWN_PRICE } from 'src/utilities/pricing/constants';
+
import {
getDCSpecificPrice,
renderMonthlyPriceToCorrectDecimalPlace,
@@ -60,7 +62,7 @@ describe('getDCSpecificPricingDisplay', () => {
flags: { dcSpecificPricing: true },
regionId: 'invalid-region',
})
- ).toBe('0.00');
+ ).toBe(undefined);
});
});
@@ -74,10 +76,12 @@ describe('renderMonthlyPriceToCorrectDecimalPlace', () => {
});
it('renders monthly price as --.-- (unknown price) if the price is undefined', () => {
- expect(renderMonthlyPriceToCorrectDecimalPlace(undefined)).toBe('--.--');
+ expect(renderMonthlyPriceToCorrectDecimalPlace(undefined)).toBe(
+ UNKNOWN_PRICE
+ );
});
it('renders monthly price as --.-- (unknown price) if the price is null', () => {
- expect(renderMonthlyPriceToCorrectDecimalPlace(null)).toBe('--.--');
+ expect(renderMonthlyPriceToCorrectDecimalPlace(null)).toBe(UNKNOWN_PRICE);
});
});
diff --git a/packages/manager/src/utilities/pricing/dynamicPricing.ts b/packages/manager/src/utilities/pricing/dynamicPricing.ts
index 578cbe3a414..f5cc80a5d88 100644
--- a/packages/manager/src/utilities/pricing/dynamicPricing.ts
+++ b/packages/manager/src/utilities/pricing/dynamicPricing.ts
@@ -1,6 +1,7 @@
+import { UNKNOWN_PRICE } from './constants';
+
import type { Region } from '@linode/api-v4';
import type { FlagSet } from 'src/featureFlags';
-import { UNKNOWN_PRICE } from './constants';
export interface DataCenterPricingOptions {
/**
@@ -55,8 +56,11 @@ export const getDCSpecificPrice = ({
flags,
regionId,
}: DataCenterPricingOptions) => {
- if (!flags?.dcSpecificPricing || !regionId) {
- // TODO: M3-7063 (defaults)
+ if (!regionId || !basePrice) {
+ return undefined;
+ }
+
+ if (!flags?.dcSpecificPricing) {
return basePrice.toFixed(2);
}
diff --git a/packages/manager/src/utilities/pricing/kubernetes.test.tsx b/packages/manager/src/utilities/pricing/kubernetes.test.tsx
index d3c8f7d0c52..9d5607f9ea4 100644
--- a/packages/manager/src/utilities/pricing/kubernetes.test.tsx
+++ b/packages/manager/src/utilities/pricing/kubernetes.test.tsx
@@ -47,7 +47,7 @@ describe('helper functions', () => {
type: badPool.type,
types,
})
- ).toBe(0);
+ ).toBe(undefined);
});
});
diff --git a/packages/manager/src/utilities/pricing/kubernetes.ts b/packages/manager/src/utilities/pricing/kubernetes.ts
index 109ae1a8f5e..893e0333d53 100644
--- a/packages/manager/src/utilities/pricing/kubernetes.ts
+++ b/packages/manager/src/utilities/pricing/kubernetes.ts
@@ -1,4 +1,4 @@
-import { getLinodeRegionPrice } from 'src/utilities/pricing/linodes';
+import { getPrice } from 'src/utilities/pricing/linodes';
import type { KubeNodePoolResponse, Region } from '@linode/api-v4/lib';
import type { FlagSet } from 'src/featureFlags';
@@ -32,16 +32,14 @@ export const getKubernetesMonthlyPrice = ({
types,
}: MonthlyPriceOptions) => {
if (!types || !type || !region) {
- return 0; // TODO
+ return undefined;
}
const thisType = types.find((t: ExtendedType) => t.id === type);
- const monthlyPrice = flags.dcSpecificPricing
- ? thisType
- ? getLinodeRegionPrice(thisType, region)?.monthly
- : 0
- : thisType?.price.monthly;
- return thisType ? (monthlyPrice ?? 0) * count : 0;
+ const monthlyPrice = getPrice(thisType, region, flags.dcSpecificPricing)
+ ?.monthly;
+
+ return monthlyPrice ? monthlyPrice * count : monthlyPrice;
};
/**
@@ -56,16 +54,14 @@ export const getTotalClusterPrice = ({
types,
}: TotalClusterPriceOptions) => {
const price = pools.reduce((accumulator, node) => {
- return (
- accumulator +
- getKubernetesMonthlyPrice({
- count: node.count,
- flags,
- region,
- type: node.type,
- types,
- })
- );
+ const kubernetesMonthlyPrice = getKubernetesMonthlyPrice({
+ count: node.count,
+ flags,
+ region,
+ type: node.type,
+ types,
+ });
+ return accumulator + (kubernetesMonthlyPrice ?? 0);
}, 0);
return highAvailabilityPrice ? price + highAvailabilityPrice : price;
diff --git a/packages/manager/src/utilities/pricing/linodes.test.ts b/packages/manager/src/utilities/pricing/linodes.test.ts
index 04133d9f8ae..9ba8a5e1204 100644
--- a/packages/manager/src/utilities/pricing/linodes.test.ts
+++ b/packages/manager/src/utilities/pricing/linodes.test.ts
@@ -10,6 +10,20 @@ import {
} from './linodes';
describe('getLinodeRegionPrice', () => {
+ it('gets a linode price as undefined when regionId is undefined', () => {
+ const type = linodeTypeFactory.build({
+ price: undefined,
+ region_prices: [],
+ });
+ const actual = getLinodeRegionPrice(type, undefined);
+ const expected = undefined;
+ expect(actual).toEqual(expected);
+ });
+ it('gets a linode price as undefined when type is undefined', () => {
+ const actual = getLinodeRegionPrice(undefined, 'us-east');
+ const expected = undefined;
+ expect(actual).toEqual(expected);
+ });
it('gets a linode price without a region override', () => {
const type = linodeTypeFactory.build({
price: {
diff --git a/packages/manager/src/utilities/pricing/linodes.ts b/packages/manager/src/utilities/pricing/linodes.ts
index e824b0ec8b2..813ddaf1868 100644
--- a/packages/manager/src/utilities/pricing/linodes.ts
+++ b/packages/manager/src/utilities/pricing/linodes.ts
@@ -15,9 +15,13 @@ import type { ExtendedType } from 'src/utilities/extendType';
* @returns pricing information for this specific linode type in a region
*/
export const getLinodeRegionPrice = (
- type: ExtendedType | LinodeType | PlanSelectionType,
- regionId: string
-): PriceObject => {
+ type?: ExtendedType | LinodeType | PlanSelectionType,
+ regionId?: null | string
+): PriceObject | undefined => {
+ if (!type || !regionId) {
+ return undefined;
+ }
+
const regionSpecificPrice = type.region_prices?.find(
(regionPrice) => regionPrice.id === regionId
);
@@ -29,10 +33,33 @@ export const getLinodeRegionPrice = (
};
}
- // TODO: M3-7063 (defaults)
return type.price;
};
+/**
+ * Get the price based on provided conditions.
+ * @param type - The Linode type.
+ * @param selectedRegionId - The selected region ID.
+ * @param dcSpecificPricing - The data center specific pricing.
+ * @returns The price or undefined if not available.
+ * TODO: DC Pricing - M3-7073: Remove this function and replace with getLinodeRegionPrice once dcSpecificPricing flag is removed.
+ */
+
+export const getPrice = (
+ type: ExtendedType | LinodeType | PlanSelectionType | undefined,
+ selectedRegionId: string | undefined,
+ dcSpecificPricing: boolean | undefined
+) => {
+ // Check if both dcSpecificPricing and selectedRegionId are available
+ if (dcSpecificPricing && selectedRegionId) {
+ // If available, return price of a Linode type
+ return getLinodeRegionPrice(type, selectedRegionId);
+ } else {
+ // If not available, fall back to type.price (may still be undefined)
+ return type?.price;
+ }
+};
+
interface IsPriceDifferentOptions {
regionA: Region['id'] | undefined;
regionB: Region['id'] | undefined;
@@ -59,7 +86,7 @@ export const isLinodeTypeDifferentPriceInSelectedRegion = ({
const currentRegionPrice = getLinodeRegionPrice(type, regionA);
const selectedRegionPrice = getLinodeRegionPrice(type, regionB);
- if (currentRegionPrice.monthly !== selectedRegionPrice.monthly) {
+ if (currentRegionPrice?.monthly !== selectedRegionPrice?.monthly) {
return true;
}