diff --git a/api/internal/owner/serializers.py b/api/internal/owner/serializers.py index 592fa6aeeb..954f2972fc 100644 --- a/api/internal/owner/serializers.py +++ b/api/internal/owner/serializers.py @@ -1,5 +1,4 @@ import logging -from dataclasses import asdict from datetime import datetime from dateutil.relativedelta import relativedelta @@ -127,10 +126,9 @@ def validate_value(self, value): current_owner = self.context["request"].current_owner plan_service = PlanService(current_org=current_org) - available_plans = [ - asdict(plan) for plan in plan_service.available_plans(current_owner) + plan_values = [ + plan["value"] for plan in plan_service.available_plans(current_owner) ] - plan_values = [plan["value"] for plan in available_plans] if value not in plan_values: if value in SENTRY_PAID_USER_PLAN_REPRESENTATIONS: log.warning( diff --git a/graphql_api/tests/test_owner.py b/graphql_api/tests/test_owner.py index 9e38121c38..0676e23b27 100644 --- a/graphql_api/tests/test_owner.py +++ b/graphql_api/tests/test_owner.py @@ -1078,3 +1078,86 @@ def test_fetch_activated_user_count_when_not_in_org_but_has_shared_account(self) """ % (other_owner.username) data = self.gql_request(query, owner=owner) assert data["owner"]["activatedUserCount"] == 2 + + def test_fetch_available_plans_is_enterprise_plan(self): + current_org = OwnerFactory( + username="random-plan-user", + service="github", + plan=PlanName.FREE_PLAN_NAME.value, + ) + + query = """{ + owner(username: "%s") { + availablePlans { + value + isEnterprisePlan + isProPlan + isTeamPlan + isSentryPlan + isFreePlan + isTrialPlan + } + } + } + """ % (current_org.username) + data = self.gql_request(query, owner=current_org) + assert data == { + "owner": { + "availablePlans": [ + { + "value": "users-basic", + "isEnterprisePlan": False, + "isProPlan": False, + "isTeamPlan": False, + "isSentryPlan": False, + "isFreePlan": True, + "isTrialPlan": False, + }, + { + "value": "users-free", + "isEnterprisePlan": False, + "isProPlan": False, + "isTeamPlan": False, + "isSentryPlan": False, + "isFreePlan": True, + "isTrialPlan": False, + }, + { + "value": "users-pr-inappm", + "isEnterprisePlan": False, + "isProPlan": True, + "isTeamPlan": False, + "isSentryPlan": False, + "isFreePlan": False, + "isTrialPlan": False, + }, + { + "value": "users-pr-inappy", + "isEnterprisePlan": False, + "isProPlan": True, + "isTeamPlan": False, + "isSentryPlan": False, + "isFreePlan": False, + "isTrialPlan": False, + }, + { + "value": "users-teamm", + "isEnterprisePlan": False, + "isProPlan": False, + "isTeamPlan": True, + "isSentryPlan": False, + "isFreePlan": False, + "isTrialPlan": False, + }, + { + "value": "users-teamy", + "isEnterprisePlan": False, + "isProPlan": False, + "isTeamPlan": True, + "isSentryPlan": False, + "isFreePlan": False, + "isTrialPlan": False, + }, + ] + } + } diff --git a/graphql_api/types/owner/owner.py b/graphql_api/types/owner/owner.py index 589a3d1d64..a268472156 100644 --- a/graphql_api/types/owner/owner.py +++ b/graphql_api/types/owner/owner.py @@ -112,7 +112,8 @@ def resolve_plan(owner: Owner, info: GraphQLResolveInfo) -> PlanService: @require_part_of_org def resolve_plan_representation(owner: Owner, info: GraphQLResolveInfo) -> PlanData: info.context["plan_service"] = PlanService(current_org=owner) - return FREE_PLAN_REPRESENTATIONS[PlanName.BASIC_PLAN_NAME.value] + free_plan = FREE_PLAN_REPRESENTATIONS[PlanName.BASIC_PLAN_NAME.value] + return free_plan.convert_to_DTO() @owner_bindable.field("availablePlans") diff --git a/graphql_api/types/plan_representation/plan_representation.graphql b/graphql_api/types/plan_representation/plan_representation.graphql index 0937ff6514..33a3948487 100644 --- a/graphql_api/types/plan_representation/plan_representation.graphql +++ b/graphql_api/types/plan_representation/plan_representation.graphql @@ -2,6 +2,12 @@ type PlanRepresentation { baseUnitPrice: Int! benefits: [String!]! billingRate: String + isEnterprisePlan: Boolean! + isFreePlan: Boolean! + isProPlan: Boolean! + isTeamPlan: Boolean! + isSentryPlan: Boolean! + isTrialPlan: Boolean! marketingName: String! monthlyUploadLimit: Int value: String! diff --git a/graphql_api/types/plan_representation/plan_representation.py b/graphql_api/types/plan_representation/plan_representation.py index 3bdbfac9cb..5c791fbe1c 100644 --- a/graphql_api/types/plan_representation/plan_representation.py +++ b/graphql_api/types/plan_representation/plan_representation.py @@ -14,22 +14,22 @@ @plan_representation_bindable.field("marketingName") def resolve_marketing_name(plan_data: PlanData, info) -> str: - return plan_data.marketing_name + return plan_data["marketing_name"] @plan_representation_bindable.field("value") def resolve_plan_value(plan_data: PlanData, info) -> str: - return plan_data.value + return plan_data["value"] @plan_representation_bindable.field("billingRate") def resolve_billing_rate(plan_data: PlanData, info) -> Optional[str]: - return plan_data.billing_rate + return plan_data["billing_rate"] @plan_representation_bindable.field("baseUnitPrice") def resolve_base_unit_price(plan_data: PlanData, info) -> int: - return plan_data.base_unit_price + return plan_data["base_unit_price"] @plan_representation_bindable.field("benefits") @@ -41,13 +41,43 @@ def resolve_benefits(plan_data: PlanData, info) -> List[str]: lambda benefit: benefit.replace( "Up to 1 user", f"Up to {plan_service.pretrial_users_count} users" ), - plan_data.benefits, + plan_data["benefits"], ) ) return benefits_with_pretrial_users - return plan_data.benefits + return plan_data["benefits"] @plan_representation_bindable.field("monthlyUploadLimit") def resolve_monthly_uploads_limit(plan_data: PlanData, info) -> Optional[int]: - return plan_data.monthly_uploads_limit + return plan_data["monthly_uploads_limit"] + + +@plan_representation_bindable.field("isEnterprisePlan") +def resolve_is_enterprise(plan_data: PlanData, info) -> bool: + return plan_data["is_enterprise_plan"] + + +@plan_representation_bindable.field("isFreePlan") +def resolve_is_free(plan_data: PlanData, info) -> bool: + return plan_data["is_free_plan"] + + +@plan_representation_bindable.field("isProPlan") +def resolve_is_pro(plan_data: PlanData, info) -> bool: + return plan_data["is_pro_plan"] + + +@plan_representation_bindable.field("isTeamPlan") +def resolve_is_team(plan_data: PlanData, info) -> bool: + return plan_data["is_team_plan"] + + +@plan_representation_bindable.field("isSentryPlan") +def resolve_is_sentry(plan_data: PlanData, info) -> bool: + return plan_data["is_sentry_plan"] + + +@plan_representation_bindable.field("isTrialPlan") +def resolve_is_trial(plan_data: PlanData, info) -> bool: + return plan_data["is_trial_plan"] diff --git a/requirements.in b/requirements.in index 695a4c4157..57299c7cad 100644 --- a/requirements.in +++ b/requirements.in @@ -26,7 +26,7 @@ freezegun google-cloud-pubsub gunicorn>=22.0.0 https://github.com/codecov/opentelem-python/archive/refs/tags/v0.0.4a1.tar.gz#egg=codecovopentelem -https://github.com/codecov/shared/archive/1c4ca00e35d95d1281e0415ce1897f6dbbc6368a.tar.gz#egg=shared +https://github.com/codecov/shared/archive/abc6b36feceb05c3b754050061436574111058f9.tar.gz#egg=shared https://github.com/photocrowd/django-cursor-pagination/archive/f560902696b0c8509e4d95c10ba0d62700181d84.tar.gz idna>=3.7 minio diff --git a/requirements.txt b/requirements.txt index 81d231ab4c..d75b4bcb2f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -414,7 +414,7 @@ sentry-sdk[celery]==2.13.0 # shared setproctitle==1.1.10 # via -r requirements.in -shared @ https://github.com/codecov/shared/archive/1c4ca00e35d95d1281e0415ce1897f6dbbc6368a.tar.gz +shared @ https://github.com/codecov/shared/archive/abc6b36feceb05c3b754050061436574111058f9.tar.gz # via -r requirements.in simplejson==3.17.2 # via -r requirements.in