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

TBT-41 Disallow downgrade plan WEB #2825

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ecdee4c
refactor plan selector UI to disallow downgrade plan
nikolaTrichkovski May 23, 2024
058a977
add test for the filtered displayed plans
nikolaTrichkovski May 23, 2024
052f7d6
refactor displayedPlans property and test
nikolaTrichkovski May 24, 2024
63bd071
refactor with areAllAnnual
nikolaTrichkovski May 28, 2024
9869ac6
Merge branch 'master' of github.com-devtactics:travis-ci/travis-web i…
nikolaTrichkovski May 31, 2024
4005b88
Merge branch 'master' of github.com-devtactics:travis-ci/travis-web i…
nikolaTrichkovski Jun 6, 2024
9cbd1ba
remove plan name condition from template
nikolaTrichkovski Jun 6, 2024
5c31e84
add emptyPlans property
nikolaTrichkovski Jun 6, 2024
3d4de56
reverse changes
nikolaTrichkovski Jun 7, 2024
ddd6d71
refactor to include metered plans
nikolaTrichkovski Jun 8, 2024
ebc40f6
remove comment
nikolaTrichkovski Jun 13, 2024
2031b39
Merge branch 'master' of github.com-devtactics:travis-ci/travis-web i…
nikolaTrichkovski Jun 14, 2024
cf302eb
add canceledAt condition
nikolaTrichkovski Jun 18, 2024
1415c58
refactor plans
nikolaTrichkovski Jun 24, 2024
f8186a0
Merge branch 'master' of github.com-devtactics:travis-ci/travis-web i…
nikolaTrichkovski Jun 24, 2024
396a908
refactor separate functions
nikolaTrichkovski Jun 25, 2024
0c8e98f
remove filter import
nikolaTrichkovski Jun 25, 2024
7ab4d6c
set areAllAnnualPlans for metered subscriptions
nikolaTrichkovski Jun 25, 2024
761fa3b
reset areAllAnnualPlans
nikolaTrichkovski Jun 25, 2024
b629b18
Merge branch 'master' of github.com-devtactics:travis-ci/travis-web i…
nikolaTrichkovski Jun 27, 2024
e0b63e0
correct annualPlans passing true state to hbs & isCanceled
nikolaTrichkovski Jun 27, 2024
b35440d
set metered annual true
nikolaTrichkovski Jun 27, 2024
610858f
refactor annualPlans variable for metered plans to be array
nikolaTrichkovski Jun 27, 2024
94d1148
remove console.log
nikolaTrichkovski Jun 27, 2024
bf1d4f7
set display plans for canceled plans
nikolaTrichkovski Jun 27, 2024
4547e50
fix for annual cancelled plans older then one month
nikolaTrichkovski Jul 1, 2024
af037a8
remove console.log)
nikolaTrichkovski Jul 2, 2024
10c690a
set annualPlans property for old subscriptions
nikolaTrichkovski Jul 8, 2024
744a4b7
Merge branch 'master' of github.com-devtactics:travis-ci/travis-web i…
nikolaTrichkovski Jul 10, 2024
e7be137
add logic for checking if validity is more then month old
nikolaTrichkovski Jul 17, 2024
3418e53
set logic for showing monthly and annual cards based on validity and …
nikolaTrichkovski Jul 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 138 additions & 3 deletions app/components/billing/select-plan.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,120 @@ export default Component.extend({
isLoading: or('save.isRunning', 'accounts.fetchSubscriptions.isRunning', 'accounts.fetchV2Subscriptions.isRunning'),
showAnnual: false,
showCalculator: false,
annualPlans: [],

displayedPlans: reads('availablePlans'),
isCancellationMoreThanOneMonthOld: computed('subscription.{isCanceled,canceledAt}', function () {
if (!this.subscription || !this.subscription.isCanceled) {
return false;
}

const canceledAtTime = new Date(this.subscription.canceledAt).getTime();
const currentDate = new Date();
currentDate.setMonth(currentDate.getMonth() - 1);

const oneMonthAgoTime = currentDate.getTime();

return canceledAtTime < oneMonthAgoTime;
}),

isValidityMoreThanOneMonthOld: computed('subscription.validTo', function () {
if (!this.subscription || !this.subscription.validTo) {
return false;
}

const validToTime = new Date(this.subscription.validTo).getTime();
const currentDate = new Date();
currentDate.setMonth(currentDate.getMonth() - 1);

const oneMonthAgoTime = currentDate.getTime();

return validToTime < oneMonthAgoTime;
}),

displayedPlans: computed('availablePlans.[]', 'subscription.plan.startingPrice', function () {
if (!this.subscription || !this.subscription.plan || this.subscription.plan.trialPlan) {
return this.availablePlans;
}

if (this.isCancellationMoreThanOneMonthOld || this.isValidityMoreThanOneMonthOld) {
return this.availablePlans;
}

let allowedHybridPlans = this.availablePlans.filter(plan => plan.planType.includes('hybrid'));
let allowedMeteredPlans = this.availablePlans.filter(plan => plan.planType.includes('metered'));

let filteredPlans = this.filterPlansByStartingPrice(this.availablePlans, this.subscription.plan.startingPrice);

if (this.isHybridPlan(this.subscription.plan)) {
return this.handleHybridPlans.call(this, filteredPlans, allowedHybridPlans, allowedMeteredPlans);
} else if (this.isMeteredPlan(this.subscription.plan)) {
return this.handleMeteredPlans.call(this, allowedHybridPlans, allowedMeteredPlans);
} else {
// set the annualPlans property for old subscriptions
if (this.availablePlans.every(plan => plan.isAnnual)) {
this.set('annualPlans', this.availablePlans);
}
return this.availablePlans;
}
}),

filterPlansByStartingPrice(plans, startingPrice) {
return plans.filter(plan => plan.startingPrice > startingPrice);
},

isHybridPlan(plan) {
return plan.planType && plan.planType.includes('hybrid');
},

isMeteredPlan(plan) {
return plan.planType && plan.planType.includes('metered');
},

handleHybridPlans(filteredPlans, allowedHybridPlans, allowedMeteredPlans) {
let filteredHybridPlans = this.filterPlansByStartingPrice(allowedHybridPlans, this.subscription.plan.startingPrice);
filteredPlans = filteredHybridPlans.filter(plan => !allowedMeteredPlans.includes(plan));

if (filteredPlans.every(plan => plan.planType === 'hybrid annual')) {
this.set('annualPlans', filteredPlans);
}

const referencePlan = this.findReferencePlan('hybrid annual');
if (!referencePlan) {
return this.availablePlans;
}

const higherTierPlans = this.filterHigherTierPlans(referencePlan);
filteredPlans = filteredPlans.filter(plan => !higherTierPlans.includes(plan));

return filteredPlans;
},

handleMeteredPlans(allowedHybridPlans, allowedMeteredPlans) {
let filteredMeteredPlans = this.filterPlansByStartingPrice(allowedMeteredPlans, this.subscription.plan.startingPrice);
let filteredHybridPlans = this.filterPlansByStartingPrice(allowedHybridPlans, this.subscription.plan.startingPrice);

let filteredPlans = [...filteredMeteredPlans, ...filteredHybridPlans];

if (filteredPlans.every(plan => plan.isAnnual)) {
this.set('annualPlans', filteredPlans);
}

return filteredPlans;
},

findReferencePlan(planType) {
return this.availablePlans.find(plan =>
plan.name === this.subscription.plan.name && plan.planType === planType
);
},

filterHigherTierPlans(referencePlan) {
return this.availablePlans.filter(plan =>
plan.startingPrice > this.subscription.plan.startingPrice &&
plan.planType === referencePlan.planType &&
plan.startingPrice < referencePlan.startingPrice
);
},

selectedPlan: computed('displayedPlans.[].name', 'defaultPlanName', {
get() {
Expand All @@ -32,7 +144,7 @@ export default Component.extend({
set(key, value) {
this.set('_selectedPlan', value);
return this._selectedPlan;
}
},
}),

allowReactivation: computed(function () {
Expand Down Expand Up @@ -92,6 +204,29 @@ export default Component.extend({

hideCalculator() {
this.set('showCalculator', false);
},
},

// Determine if the user has either an annual plan or all to be displayed plans are annual so we can show the annual card immediately in the UI
didInsertElement() {
this._super(...arguments);

this.set('areAllAnnualPlans', Array.isArray(this.annualPlans) && this.annualPlans.length > 0);

if (this.annualPlans.length === 0) {
this.set('emptyAnnualPlans', true);
}
}

if (this.subscription && this.subscription.plan) {
if (!this.subscription.isCanceled && !this.isValidityMoreThanOneMonthOld) {
if (this.subscription.plan.isAnnual || this.areAllAnnualPlans) {
this.set('showAnnual', true);
}
} else if (this.subscription.isCanceled && !this.isCancellationMoreThanOneMonthOld) {
if (this.subscription.plan.isAnnual || this.areAllAnnualPlans) {
this.set('showAnnual', true);
}
}
}
},
});
3 changes: 3 additions & 0 deletions app/styles/app/layouts/billing-plans.scss
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,9 @@
width: 530px;
margin-left: auto;
margin-right: auto;
display: flex;
justify-content: center;
flex-wrap: wrap;

a {
cursor: pointer;
Expand Down
27 changes: 17 additions & 10 deletions app/templates/components/billing/select-plan.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,25 @@
<h3 data-test-billing-info-title>
Select Plan
</h3>
<h2>
Questions about plans? <a class='credits-calculator-button' href="mailto:[email protected]">Contact our Customer Success Team</a>
</h2>
<nav class="travistab-nav">
<ul>
<li>
<a class={{if (not this.showAnnual) 'active'}} onclick={{action 'showMonthlyPlans'}}>
Monthly Plans
</a>
</li>
<li>
<a class={{if this.showAnnual 'active'}} onclick={{action 'showAnnualPlans'}}>
Annual Plans
</a>
</li>
{{#unless (or (and this.subscription.plan.isAnnual (or (not this.isCancellationMoreThanOneMonthOld this.isValidityMoreThanOneMonthOld))) this.areAllAnnualPlans)}}
<li>
<a class={{if (not this.showAnnual) 'active'}} onclick={{action 'showMonthlyPlans'}}>
Monthly Plans
</a>
</li>
{{/unless}}
{{#unless (and (and this.subscription.plan.isAnnual (or (not this.isCancellationMoreThanOneMonthOld this.isValidityMoreThanOneMonthOld))) this.emptyAnnualPlans)}}
<li>
<a class={{if this.showAnnual 'active'}} onclick={{action 'showAnnualPlans'}}>
Annual Plans
</a>
</li>
{{/unless}}
</ul>
</nav>
<div class='plan-selector'>
Expand Down
22 changes: 21 additions & 1 deletion tests/integration/components/billing/select-plan-test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { module, skip } from 'qunit';
import { module, skip, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
Expand Down Expand Up @@ -27,6 +27,7 @@ module('Integration | Component | billing-select-plan', function (hooks) {
hasCreditAddons: true,
hasOSSCreditAddons: true,
hasUserLicenseAddons: true,
planType: 'hybrid annual',
};
this.plan1 = plan1;

Expand All @@ -43,6 +44,7 @@ module('Integration | Component | billing-select-plan', function (hooks) {
hasCreditAddons: true,
hasOSSCreditAddons: true,
hasUserLicenseAddons: true,
planType: 'hybrid annual',
};
this.plan2 = plan2;

Expand Down Expand Up @@ -77,4 +79,22 @@ module('Integration | Component | billing-select-plan', function (hooks) {
assert.dom(profilePage.billing.selectedPlan.name.scope).hasText(`${this.plan2.name}`);
assert.dom(profilePage.billing.selectedPlan.price.scope).hasText(`$${this.plan2.startingPrice / 100}/${this.plan2.isAnnual ? 'annualy' : 'monthly'}`);
});

test('displayedPlans should filter availablePlans based on subscription.plan.startingPrice', function (assert) {
let component = this.owner.lookup('component:billing/select-plan');
let subscription = { plan: { name: 'Standard Tier Plan', startingPrice: 3000, trialPlan: false } };

component.set('availablePlans', [this.plan1, this.plan2]);
component.set('subscription', subscription);
const referencePlan = component.get('availablePlans')
.find(plan => plan.name === component.get('subscription').plan.name && plan.planType === 'hybrid annual');

component.set('referencePlan', referencePlan);

let displayedPlans = component.get('displayedPlans');

assert.ok(displayedPlans, 'displayedPlans should be defined');
assert.equal(displayedPlans.length, 2, 'displayedPlans should contain 2 plans');
assert.deepEqual(displayedPlans, [this.plan1, this.plan2], 'displayedPlans should contain the correct plans');
});
});