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

Commit 191837f

Browse files
authored
PlanService updates for GitLab (#461)
* update PlanService so GitLab orgs use the root org's plan, fold is_pr_billing_plan into PlanService * start BillingPlan deprecation
1 parent 8607594 commit 191837f

File tree

4 files changed

+76
-29
lines changed

4 files changed

+76
-29
lines changed

shared/billing/__init__.py

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
from enum import Enum
22

33
from django.conf import settings
4+
from typing_extensions import deprecated
45

56
from shared.license import get_current_license
7+
from shared.plan.constants import PlanName
68

79

10+
@deprecated("Use PlanService")
811
class BillingPlan(Enum):
9-
users_ghm = "users"
10-
users_monthly = "users-inappm"
11-
users_yearly = "users-inappy"
12-
users_free = "users-free"
13-
users_basic = "users-basic"
14-
users_trial = "users-trial"
15-
pr_monthly = "users-pr-inappm"
16-
pr_yearly = "users-pr-inappy"
17-
enterprise_cloud_yearly = "users-enterprisey"
18-
enterprise_cloud_monthly = "users-enterprisem"
19-
team_monthly = "users-teamm"
20-
team_yearly = "users-teamy"
12+
users_basic = PlanName.BASIC_PLAN_NAME.value
13+
users_trial = PlanName.TRIAL_PLAN_NAME.value
14+
pr_monthly = PlanName.CODECOV_PRO_MONTHLY.value
15+
pr_yearly = PlanName.CODECOV_PRO_YEARLY.value
16+
SENTRY_MONTHLY = PlanName.SENTRY_MONTHLY.value
17+
SENTRY_YEARLY = PlanName.SENTRY_YEARLY.value
18+
team_monthly = PlanName.TEAM_MONTHLY.value
19+
team_yearly = PlanName.TEAM_YEARLY.value
20+
users_ghm = PlanName.GHM_PLAN_NAME.value
21+
users_free = PlanName.FREE_PLAN_NAME.value
22+
users_monthly = PlanName.CODECOV_PRO_MONTHLY_LEGACY.value
23+
users_yearly = PlanName.CODECOV_PRO_YEARLY_LEGACY.value
24+
enterprise_cloud_monthly = PlanName.ENTERPRISE_CLOUD_MONTHLY.value
25+
enterprise_cloud_yearly = PlanName.ENTERPRISE_CLOUD_YEARLY.value
2126

2227
def __init__(self, db_name):
2328
self.db_name = db_name
@@ -29,26 +34,20 @@ def from_str(cls, plan_name: str):
2934
return plan
3035

3136

37+
@deprecated("use is_enterprise_plan() in PlanService")
3238
def is_enterprise_cloud_plan(plan: BillingPlan) -> bool:
3339
return plan in [
3440
BillingPlan.enterprise_cloud_monthly,
3541
BillingPlan.enterprise_cloud_yearly,
3642
]
3743

3844

45+
@deprecated("use is_pr_billing_plan() in PlanService")
3946
def is_pr_billing_plan(plan: str) -> bool:
4047
if not settings.IS_ENTERPRISE:
41-
return plan in [
42-
BillingPlan.pr_monthly.value,
43-
BillingPlan.pr_yearly.value,
44-
BillingPlan.users_free.value,
45-
BillingPlan.users_basic.value,
46-
BillingPlan.users_trial.value,
47-
BillingPlan.enterprise_cloud_monthly.value,
48-
BillingPlan.enterprise_cloud_yearly.value,
49-
BillingPlan.team_monthly.value,
50-
BillingPlan.team_yearly.value,
51-
BillingPlan.users_ghm.value,
48+
return plan not in [
49+
PlanName.CODECOV_PRO_MONTHLY_LEGACY.value,
50+
PlanName.CODECOV_PRO_YEARLY_LEGACY.value,
5251
]
5352
else:
5453
return get_current_license().is_pr_billing

shared/django_apps/codecov_auth/models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import binascii
21
import logging
32
import os
43
import uuid
54
from dataclasses import asdict
65
from datetime import datetime
76
from hashlib import md5
8-
from typing import Self
7+
from typing import Self, Optional
98

9+
import binascii
1010
from django.contrib.postgres.fields import ArrayField, CITextField
1111
from django.contrib.sessions.models import Session as DjangoSession
1212
from django.db import models
@@ -425,7 +425,7 @@ def repo_total_credits(self):
425425
return int(self.plan[:-1])
426426

427427
@property
428-
def root_organization(self):
428+
def root_organization(self: "Owner") -> Optional["Owner"]:
429429
"""
430430
Find the root organization of Gitlab, by using the root_parent_service_id
431431
if it exists, otherwise iterating through the parents and caches it in root_parent_service_id

shared/plan/service.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
from datetime import datetime, timedelta
33
from typing import List, Optional
44

5+
from shared.billing import is_pr_billing_plan
56
from shared.config import get_config
67
from shared.django_apps.codecov.commands.exceptions import ValidationError
7-
from shared.django_apps.codecov_auth.models import Owner
8+
from shared.django_apps.codecov_auth.models import Owner, Service
89
from shared.plan.constants import (
910
BASIC_PLAN,
1011
ENTERPRISE_CLOUD_USER_PLAN_REPRESENTATIONS,
@@ -46,7 +47,14 @@ def __init__(self, current_org: Owner):
4647
Raises:
4748
ValueError: If the organization's plan is unsupported.
4849
"""
49-
self.current_org = current_org
50+
if (
51+
current_org.service == Service.GITLAB.value
52+
and current_org.parent_service_id
53+
):
54+
# for GitLab groups and subgroups, use the plan on the root org
55+
self.current_org = current_org.root_organization
56+
else:
57+
self.current_org = current_org
5058
if self.current_org.plan not in USER_PLAN_REPRESENTATIONS:
5159
raise ValueError("Unsupported plan")
5260
self._plan_data = None
@@ -340,3 +348,7 @@ def is_team_plan(self) -> bool:
340348
@property
341349
def is_trial_plan(self) -> bool:
342350
return self.plan_name in TRIAL_PLAN_REPRESENTATION
351+
352+
@property
353+
def is_pr_billing_plan(self) -> bool:
354+
return is_pr_billing_plan(plan=self.plan_name)

tests/unit/plan/test_plan.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from datetime import datetime, timedelta
22
from unittest.mock import patch
33

4-
from django.test import TestCase
4+
from django.test import TestCase, override_settings
55
from freezegun import freeze_time
66

77
from shared.django_apps.codecov.commands.exceptions import ValidationError
8+
from shared.django_apps.codecov_auth.models import Service
89
from shared.django_apps.codecov_auth.tests.factories import OwnerFactory
910
from shared.plan.constants import (
1011
BASIC_PLAN,
@@ -317,6 +318,34 @@ def test_plan_service_returns_if_owner_has_trial_dates(self):
317318

318319
assert plan_service.has_trial_dates == True
319320

321+
def test_plan_service_gitlab_with_root_org(self):
322+
root_owner_org = OwnerFactory(
323+
service=Service.GITLAB.value,
324+
plan=PlanName.FREE_PLAN_NAME.value,
325+
plan_user_count=1,
326+
service_id="1234",
327+
)
328+
middle_org = OwnerFactory(
329+
service=Service.GITLAB.value,
330+
service_id="5678",
331+
parent_service_id=root_owner_org.service_id,
332+
)
333+
child_owner_org = OwnerFactory(
334+
service=Service.GITLAB.value,
335+
plan=PlanName.CODECOV_PRO_MONTHLY.value,
336+
plan_user_count=20,
337+
parent_service_id=middle_org.service_id,
338+
)
339+
# root_plan and child_plan should be the same
340+
root_plan = PlanService(current_org=root_owner_org)
341+
child_plan = PlanService(current_org=child_owner_org)
342+
343+
assert root_plan.is_pro_plan == child_plan.is_pro_plan == False
344+
assert root_plan.plan_user_count == child_plan.plan_user_count == 1
345+
assert (
346+
root_plan.plan_name == child_plan.plan_name == PlanName.FREE_PLAN_NAME.value
347+
)
348+
320349

321350
class AvailablePlansBeforeTrial(TestCase):
322351
"""
@@ -815,6 +844,7 @@ def test_sentry_user(self, is_sentry_user):
815844
assert self.plan_service.available_plans(owner=self.owner) == expected_result
816845

817846

847+
@override_settings(IS_ENTERPRISE=False)
818848
class PlanServiceIs___PlanTests(TestCase):
819849
def test_is_trial_plan(self):
820850
self.current_org = OwnerFactory(
@@ -834,6 +864,7 @@ def test_is_trial_plan(self):
834864
assert self.plan_service.is_free_plan == False
835865
assert self.plan_service.is_pro_plan == False
836866
assert self.plan_service.is_enterprise_plan == False
867+
assert self.plan_service.is_pr_billing_plan == True
837868

838869
def test_is_team_plan(self):
839870
self.current_org = OwnerFactory(
@@ -849,6 +880,7 @@ def test_is_team_plan(self):
849880
assert self.plan_service.is_free_plan == False
850881
assert self.plan_service.is_pro_plan == False
851882
assert self.plan_service.is_enterprise_plan == False
883+
assert self.plan_service.is_pr_billing_plan == True
852884

853885
def test_is_sentry_plan(self):
854886
self.current_org = OwnerFactory(
@@ -864,6 +896,7 @@ def test_is_sentry_plan(self):
864896
assert self.plan_service.is_free_plan == False
865897
assert self.plan_service.is_pro_plan == True
866898
assert self.plan_service.is_enterprise_plan == False
899+
assert self.plan_service.is_pr_billing_plan == True
867900

868901
def test_is_free_plan(self):
869902
self.current_org = OwnerFactory(
@@ -878,6 +911,7 @@ def test_is_free_plan(self):
878911
assert self.plan_service.is_free_plan == True
879912
assert self.plan_service.is_pro_plan == False
880913
assert self.plan_service.is_enterprise_plan == False
914+
assert self.plan_service.is_pr_billing_plan == True
881915

882916
def test_is_pro_plan(self):
883917
self.current_org = OwnerFactory(
@@ -892,6 +926,7 @@ def test_is_pro_plan(self):
892926
assert self.plan_service.is_free_plan == False
893927
assert self.plan_service.is_pro_plan == True
894928
assert self.plan_service.is_enterprise_plan == False
929+
assert self.plan_service.is_pr_billing_plan == True
895930

896931
def test_is_enterprise_plan(self):
897932
self.current_org = OwnerFactory(
@@ -906,3 +941,4 @@ def test_is_enterprise_plan(self):
906941
assert self.plan_service.is_free_plan == False
907942
assert self.plan_service.is_pro_plan == False
908943
assert self.plan_service.is_enterprise_plan == True
944+
assert self.plan_service.is_pr_billing_plan == True

0 commit comments

Comments
 (0)