Skip to content
This repository was archived by the owner on May 5, 2025. It is now read-only.

Commit 74c0888

Browse files
Shared: Migrate to Plan / Tier Tables (#479)
* shared stuff should be migrated with this, first try with available_plans * boilerplate updates, better way to do the plan searches * update logic a bit to work with new plan, and types as well, start fixing tests * 43 tests failing locally * fix some tests: * uncomment stuff * more tests fixing * 5 more to go * wrap up tests in test_plan * resolve all tests * update test imports and helper, remove trial_days from DTO, start offboarding the consts * lint * Update with pretty plan changes * setupClass from setUp and clean up a bunch of the available plan tests * remove ttestcase if not needed * print * use cached property, remove setter * missed a couple prefetch references --------- Co-authored-by: RulaKhaled <[email protected]>
1 parent 32938c0 commit 74c0888

File tree

9 files changed

+715
-247
lines changed

9 files changed

+715
-247
lines changed

shared/django_apps/codecov_auth/models.py

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import logging
33
import os
44
import uuid
5-
from dataclasses import asdict
65
from datetime import datetime
76
from hashlib import md5
87
from typing import Optional, Self
@@ -30,7 +29,7 @@
3029
from shared.django_apps.codecov_auth.managers import OwnerManager
3130
from shared.django_apps.core.managers import RepositoryManager
3231
from shared.django_apps.core.models import DateTimeWithoutTZField, Repository
33-
from shared.plan.constants import USER_PLAN_REPRESENTATIONS, PlanName
32+
from shared.plan.constants import PlanName, TrialDaysAmount
3433

3534
# Added to avoid 'doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS' error\
3635
# Needs to be called the same as the API app
@@ -221,10 +220,22 @@ def pretty_plan(self) -> dict | None:
221220
This is how we represent the details of a plan to a user, see plan.constants.py
222221
We inject quantity to make plan management easier on api, see PlanSerializer
223222
"""
224-
if self.plan in USER_PLAN_REPRESENTATIONS:
225-
plan_details = asdict(USER_PLAN_REPRESENTATIONS[self.plan])
226-
plan_details.update({"quantity": self.plan_seat_count})
227-
return plan_details
223+
plan_details = Plan.objects.select_related("tier").get(name=self.plan)
224+
if plan_details:
225+
return {
226+
"marketing_name": plan_details.marketing_name,
227+
"value": plan_details.name,
228+
"billing_rate": plan_details.billing_rate,
229+
"base_unit_price": plan_details.base_unit_price,
230+
"benefits": plan_details.benefits,
231+
"tier_name": plan_details.tier.tier_name,
232+
"monthly_uploads_limit": plan_details.monthly_uploads_limit,
233+
"trial_days": TrialDaysAmount.CODECOV_SENTRY.value
234+
if plan_details.name == PlanName.TRIAL_PLAN_NAME.value
235+
else None,
236+
"quantity": self.plan_seat_count,
237+
}
238+
return None
228239

229240
def can_activate_user(self, user: User | None = None) -> bool:
230241
"""
@@ -593,16 +604,23 @@ def avatar_url(self, size=DEFAULT_AVATAR_SIZE):
593604
def pretty_plan(self):
594605
if self.account:
595606
return self.account.pretty_plan
596-
if self.plan in USER_PLAN_REPRESENTATIONS:
597-
plan_details = asdict(USER_PLAN_REPRESENTATIONS[self.plan])
598607

599-
# update with quantity they've purchased
600-
# allows api users to update the quantity
601-
# by modifying the "plan", sidestepping
602-
# some iffy data modeling
603-
604-
plan_details.update({"quantity": self.plan_user_count})
605-
return plan_details
608+
plan_details = Plan.objects.select_related("tier").get(name=self.plan)
609+
if plan_details:
610+
return {
611+
"marketing_name": plan_details.marketing_name,
612+
"value": plan_details.name,
613+
"billing_rate": plan_details.billing_rate,
614+
"base_unit_price": plan_details.base_unit_price,
615+
"benefits": plan_details.benefits,
616+
"tier_name": plan_details.tier.tier_name,
617+
"monthly_uploads_limit": plan_details.monthly_uploads_limit,
618+
"trial_days": TrialDaysAmount.CODECOV_SENTRY.value
619+
if plan_details.name == PlanName.TRIAL_PLAN_NAME.value
620+
else None,
621+
"quantity": self.plan_user_count,
622+
}
623+
return None
606624

607625
def can_activate_user(self, owner_user: Self) -> bool:
608626
owner_org = self

shared/django_apps/codecov_auth/services/org_level_token_service.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
from django.dispatch import receiver
66
from django.forms import ValidationError
77

8-
from shared.django_apps.codecov_auth.models import OrganizationLevelToken, Owner
9-
from shared.plan.constants import USER_PLAN_REPRESENTATIONS
8+
from shared.django_apps.codecov_auth.models import OrganizationLevelToken, Owner, Plan
109

1110
log = logging.getLogger(__name__)
1211

@@ -18,9 +17,10 @@ class OrgLevelTokenService(object):
1817
-- only 1 token per Owner
1918
"""
2019

20+
# MIGHT BE ABLE TO REMOVE THIS AND SUBSEQUENT DOWNSTREAM STUFF
2121
@classmethod
2222
def org_can_have_upload_token(cls, org: Owner):
23-
return org.plan in USER_PLAN_REPRESENTATIONS
23+
return Plan.objects.filter(name=org.plan, is_active=True).exists()
2424

2525
@classmethod
2626
def get_or_create_org_token(cls, org: Owner):

shared/django_apps/codecov_auth/tests/factories.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
UserToken,
2424
)
2525
from shared.encryption.oauth import get_encryptor_from_configuration
26-
from shared.plan.constants import TrialStatus
26+
from shared.plan.constants import PlanName, TierName, TrialStatus
2727

2828
encryptor = get_encryptor_from_configuration()
2929

@@ -177,20 +177,26 @@ class TierFactory(DjangoModelFactory):
177177
class Meta:
178178
model = Tier
179179

180-
tier_name = factory.Faker("name")
180+
tier_name = TierName.BASIC.value
181+
bundle_analysis = False
182+
test_analytics = False
183+
flaky_test_detection = False
184+
project_coverage = False
185+
private_repo_support = False
181186

182187

183188
class PlanFactory(DjangoModelFactory):
184189
class Meta:
185190
model = Plan
186191

187-
base_unit_price = factory.Faker("pyint")
188-
benefits = []
192+
tier = factory.SubFactory(TierFactory)
193+
base_unit_price = 0
194+
benefits = factory.LazyFunction(lambda: ["Benefit 1", "Benefit 2", "Benefit 3"])
189195
billing_rate = None
190196
is_active = True
191-
marketing_name = factory.Faker("name")
192-
max_seats = None
197+
marketing_name = factory.Faker("catch_phrase")
198+
max_seats = 1
193199
monthly_uploads_limit = None
194-
paid_plan = True
195-
name = factory.Faker("name")
196-
tier = factory.SubFactory(TierFactory)
200+
name = PlanName.BASIC_PLAN_NAME.value
201+
paid_plan = False
202+
stripe_id = None

shared/plan/constants.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,26 @@ class TierName(enum.Enum):
7373
TEAM = "team"
7474
PRO = "pro"
7575
ENTERPRISE = "enterprise"
76+
SENTRY = "sentry"
77+
TRIAL = "trial"
78+
79+
80+
def convert_to_DTO(plan) -> dict:
81+
return {
82+
"marketing_name": plan.marketing_name,
83+
"value": plan.name,
84+
"billing_rate": plan.billing_rate,
85+
"base_unit_price": plan.base_unit_price,
86+
"benefits": plan.benefits,
87+
"tier_name": plan.tier.tier_name,
88+
"monthly_uploads_limit": plan.monthly_uploads_limit,
89+
"is_free_plan": not plan.paid_plan,
90+
"is_pro_plan": plan.tier.tier_name == TierName.PRO.value,
91+
"is_team_plan": plan.tier.tier_name == TierName.TEAM.value,
92+
"is_enterprise_plan": plan.tier.tier_name == TierName.ENTERPRISE.value,
93+
"is_trial_plan": plan.tier.tier_name == TierName.TRIAL.value,
94+
"is_sentry_plan": plan.tier.tier_name == TierName.SENTRY.value,
95+
}
7696

7797

7898
@dataclass(repr=False)
@@ -99,7 +119,6 @@ def convert_to_DTO(self) -> dict:
99119
"benefits": self.benefits,
100120
"tier_name": self.tier_name,
101121
"monthly_uploads_limit": self.monthly_uploads_limit,
102-
"trial_days": self.trial_days,
103122
"is_free_plan": self.tier_name == TierName.BASIC.value,
104123
"is_pro_plan": self.tier_name == TierName.PRO.value,
105124
"is_team_plan": self.tier_name == TierName.TEAM.value,
@@ -189,7 +208,7 @@ def convert_to_DTO(self) -> dict:
189208
"Unlimited private repositories",
190209
"Priority Support",
191210
],
192-
tier_name=TierName.PRO.value,
211+
tier_name=TierName.SENTRY.value,
193212
trial_days=TrialDaysAmount.CODECOV_SENTRY.value,
194213
monthly_uploads_limit=None,
195214
),
@@ -205,7 +224,7 @@ def convert_to_DTO(self) -> dict:
205224
"Unlimited private repositories",
206225
"Priority Support",
207226
],
208-
tier_name=TierName.PRO.value,
227+
tier_name=TierName.SENTRY.value,
209228
trial_days=TrialDaysAmount.CODECOV_SENTRY.value,
210229
monthly_uploads_limit=None,
211230
),
@@ -342,7 +361,7 @@ def convert_to_DTO(self) -> dict:
342361
"Unlimited private repositories",
343362
"Priority Support",
344363
],
345-
tier_name=TierName.PRO.value,
364+
tier_name=TierName.TRIAL.value,
346365
trial_days=None,
347366
monthly_uploads_limit=None,
348367
),

0 commit comments

Comments
 (0)