Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: [M3-7739, M3-7741, M3-7746, M3-7747] - Cherry pick zero price fixes for release #10177

5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-10153-fixed-1707249479775.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Fixed
---

Error when enabling backups for Linodes in regions with $0 pricing ([#10153](https://github.com/linode/manager/pull/10153))
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-10157-fixed-1707328749030.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Fixed
---

Error notices for $0 regions in LKE Resize and Add Node Pools drawers ([#10157](https://github.com/linode/manager/pull/10157))
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-10161-fixed-1707341493849.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Fixed
---

Error in Enable All Backups drawer when one or more Linode is in a $0 region ([#10161](https://github.com/linode/manager/pull/10161))
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-10166-fixed-1707414781493.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Fixed
---

Display $0.00 prices in Linode Migration dialog ([#10166](https://github.com/linode/manager/pull/10166))
225 changes: 225 additions & 0 deletions packages/manager/cypress/e2e/core/kubernetes/lke-update.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1011,4 +1011,229 @@ describe('LKE cluster updates for DC-specific prices', () => {
// Confirm total price updates in Kube Specs: $14.40/mo existing pool + $28.80/mo new pool.
cy.findByText('$43.20/month').should('be.visible');
});

/*
* - Confirms node pool resize UI flow using mocked API responses.
* - Confirms that pool size can be changed.
* - Confirms that drawer reflects $0 pricing.
* - Confirms that details page still shows $0 pricing after resizing.
*/
it('can resize pools with region prices of $0', () => {
const dcSpecificPricingRegion = getRegionById('us-southeast');

const mockCluster = kubernetesClusterFactory.build({
k8s_version: latestKubernetesVersion,
region: dcSpecificPricingRegion.id,
control_plane: {
high_availability: false,
},
});

const mockNodePoolResized = nodePoolFactory.build({
count: 3,
type: dcPricingMockLinodeTypes[2].id,
nodes: kubeLinodeFactory.buildList(3),
});

const mockNodePoolInitial = {
...mockNodePoolResized,
count: 1,
nodes: [mockNodePoolResized.nodes[0]],
};

const mockLinodes: Linode[] = mockNodePoolResized.nodes.map(
(node: PoolNodeResponse): Linode => {
return linodeFactory.build({
id: node.instance_id ?? undefined,
ipv4: [randomIp()],
region: dcSpecificPricingRegion.id,
type: dcPricingMockLinodeTypes[2].id,
});
}
);

const mockNodePoolDrawerTitle = 'Resize Pool: Linode 2 GB Plan';

mockGetCluster(mockCluster).as('getCluster');
mockGetClusterPools(mockCluster.id, [mockNodePoolInitial]).as(
'getNodePools'
);
mockGetLinodes(mockLinodes).as('getLinodes');
mockGetLinodeType(dcPricingMockLinodeTypes[2]).as('getLinodeType');
mockGetKubernetesVersions().as('getVersions');
mockGetDashboardUrl(mockCluster.id);
mockGetApiEndpoints(mockCluster.id);

cy.visitWithLogin(`/kubernetes/clusters/${mockCluster.id}`);
cy.wait([
'@getCluster',
'@getNodePools',
'@getLinodes',
'@getVersions',
'@getLinodeType',
]);

// Confirm that nodes are visible.
mockNodePoolInitial.nodes.forEach((node: PoolNodeResponse) => {
cy.get(`tr[data-qa-node-row="${node.id}"]`)
.should('be.visible')
.within(() => {
const nodeLinode = mockLinodes.find(
(linode: Linode) => linode.id === node.instance_id
);
if (nodeLinode) {
cy.findByText(nodeLinode.label).should('be.visible');
}
});
});

// Confirm total price is listed in Kube Specs.
cy.findByText('$0.00/month').should('be.visible');

// Click "Resize Pool" and increase size to 4 nodes.
ui.button
.findByTitle('Resize Pool')
.should('be.visible')
.should('be.enabled')
.click();

mockUpdateNodePool(mockCluster.id, mockNodePoolResized).as(
'resizeNodePool'
);
mockGetClusterPools(mockCluster.id, [mockNodePoolResized]).as(
'getNodePools'
);

ui.drawer
.findByTitle(mockNodePoolDrawerTitle)
.should('be.visible')
.within(() => {
ui.button
.findByTitle('Save Changes')
.should('be.visible')
.should('be.disabled');

cy.findByText('Current pool: $0/month (1 node at $0/month)').should(
'be.visible'
);
cy.findByText('Resized pool: $0/month (1 node at $0/month)').should(
'be.visible'
);

cy.findByLabelText('Add 1')
.should('be.visible')
.should('be.enabled')
.click()
.click()
.click();

cy.findByLabelText('Edit Quantity').should('have.value', '4');
cy.findByText('Current pool: $0/month (1 node at $0/month)').should(
'be.visible'
);
cy.findByText('Resized pool: $0/month (4 nodes at $0/month)').should(
'be.visible'
);

ui.button
.findByTitle('Save Changes')
.should('be.visible')
.should('be.enabled')
.click();
});

cy.wait(['@resizeNodePool', '@getNodePools']);

// Confirm total price is still $0 in Kube Specs.
cy.findByText('$0.00/month').should('be.visible');
});

/*
* - Confirms UI flow when adding node pools using mocked API responses.
* - Confirms that drawer reflects $0 prices.
* - Confirms that details page still shows $0 pricing after adding node pool.
*/
it('can add node pools with region prices of $0', () => {
const dcSpecificPricingRegion = getRegionById('us-southeast');

const mockCluster = kubernetesClusterFactory.build({
k8s_version: latestKubernetesVersion,
region: dcSpecificPricingRegion.id,
control_plane: {
high_availability: false,
},
});

const mockNewNodePool = nodePoolFactory.build({
count: 2,
type: dcPricingMockLinodeTypes[2].id,
nodes: kubeLinodeFactory.buildList(2),
});

const mockNodePool = nodePoolFactory.build({
count: 1,
type: dcPricingMockLinodeTypes[2].id,
nodes: kubeLinodeFactory.buildList(1),
});

mockGetCluster(mockCluster).as('getCluster');
mockGetClusterPools(mockCluster.id, [mockNodePool]).as('getNodePools');
mockGetKubernetesVersions().as('getVersions');
mockAddNodePool(mockCluster.id, mockNewNodePool).as('addNodePool');
mockGetLinodeType(dcPricingMockLinodeTypes[2]).as('getLinodeType');
mockGetLinodeTypes(dcPricingMockLinodeTypes);
mockGetDashboardUrl(mockCluster.id);
mockGetApiEndpoints(mockCluster.id);

cy.visitWithLogin(`/kubernetes/clusters/${mockCluster.id}`);
cy.wait(['@getCluster', '@getNodePools', '@getVersions', '@getLinodeType']);

// Assert that initial node pool is shown on the page.
cy.findByText('Linode 2 GB', { selector: 'h2' }).should('be.visible');

// Confirm total price of $0 is listed in Kube Specs.
cy.findByText('$0.00/month').should('be.visible');

// Add a new node pool, select plan, submit form in drawer.
ui.button
.findByTitle('Add a Node Pool')
.should('be.visible')
.should('be.enabled')
.click();

mockGetClusterPools(mockCluster.id, [mockNodePool, mockNewNodePool]).as(
'getNodePools'
);

ui.drawer
.findByTitle(`Add a Node Pool: ${mockCluster.label}`)
.should('be.visible')
.within(() => {
cy.findByText('Linode 2 GB')
.should('be.visible')
.closest('tr')
.within(() => {
// Assert that $0 prices are displayed the plan table, then add a node pool with 2 linodes.
cy.findAllByText('$0').should('have.length', 2);
cy.findByLabelText('Add 1').should('be.visible').click().click();
});

// Assert that $0 prices are displayed as helper text.
cy.contains(
'This pool will add $0/month (2 nodes at $0/month) to this cluster.'
).should('be.visible');

ui.button
.findByTitle('Add pool')
.should('be.visible')
.should('be.enabled')
.click();
});

// Wait for API responses.
cy.wait(['@addNodePool', '@getNodePools']);

// Confirm total price is still $0 in Kube Specs.
cy.findByText('$0.00/month').should('be.visible');
});
});
19 changes: 15 additions & 4 deletions packages/manager/cypress/e2e/core/linodes/backup-linode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,16 +270,22 @@ describe('"Enable Linode Backups" banner', () => {
// See `dcPricingMockLinodeTypes` exported from `support/constants/dc-specific-pricing.ts`.
linodeFactory.build({
label: randomLabel(),
region: 'us-east',
region: 'us-ord',
backups: { enabled: false },
type: dcPricingMockLinodeTypesForBackups[0].id,
}),
linodeFactory.build({
label: randomLabel(),
region: 'us-west',
region: 'us-east',
backups: { enabled: false },
type: dcPricingMockLinodeTypesForBackups[1].id,
}),
linodeFactory.build({
label: randomLabel(),
region: 'us-west',
backups: { enabled: false },
type: dcPricingMockLinodeTypesForBackups[2].id,
}),
linodeFactory.build({
label: randomLabel(),
region: 'us-central',
Expand Down Expand Up @@ -317,6 +323,7 @@ describe('"Enable Linode Backups" banner', () => {

// The expected backup price for each Linode, as shown in backups drawer table.
const expectedPrices = [
'$0.00/mo', // us-ord mocked price.
'$3.57/mo', // us-east mocked price.
'$4.17/mo', // us-west mocked price.
'$2.00/mo', // regular price.
Expand Down Expand Up @@ -358,7 +365,7 @@ describe('"Enable Linode Backups" banner', () => {
);

// Confirm that expected total cost is shown.
cy.contains(`Total for 3 Linodes: ${expectedTotal}`).should(
cy.contains(`Total for 4 Linodes: ${expectedTotal}`).should(
'be.visible'
);

Expand All @@ -377,6 +384,10 @@ describe('"Enable Linode Backups" banner', () => {
.closest('tr')
.within(() => {
cy.findByText(expectedPrice).should('be.visible');
// Confirm no error indicator appears for $0.00 prices.
cy.findByLabelText(
'There was an error loading the price.'
).should('not.exist');
});
});

Expand All @@ -398,7 +409,7 @@ describe('"Enable Linode Backups" banner', () => {
cy.wait([...enableBackupAliases, '@updateAccountSettings']);

ui.toast.assertMessage(
'3 Linodes have been enrolled in automatic backups, and all new Linodes will automatically be backed up.'
'4 Linodes have been enrolled in automatic backups, and all new Linodes will automatically be backed up.'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ export const dcPricingMockLinodeTypes = linodeTypeFactory.buildList(3, {
monthly: 12.2,
},
{
hourly: 0.006,
// Mock a DC with $0 region prices, which is possible in some circumstances (e.g. Limited Availability).
hourly: 0.0,
id: 'us-southeast',
monthly: 4.67,
monthly: 0.0,
},
],
});
Expand All @@ -92,6 +93,11 @@ export const dcPricingMockLinodeTypesForBackups = linodeTypeFactory.buildList(
monthly: 2.0,
},
region_prices: [
{
hourly: 0,
id: 'us-ord',
monthly: 0,
},
{
hourly: 0.0048,
id: 'us-east',
Expand Down
1 change: 1 addition & 0 deletions packages/manager/src/factories/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export * from './statusPage';
export * from './subnets';
export * from './support';
export * from './tags';
export * from './types';
export * from './volume';
export * from './vlans';
export * from './vpcs';
Expand Down
Loading
Loading