From 95b055bd14e3e211a67537f9de6209c979ff0777 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Fri, 20 Dec 2024 14:29:41 +0200 Subject: [PATCH 1/3] inital setup for plan represntation --- .../plan_representation.graphql | 5 +++ .../plan_representation.py | 41 +++++++++++++++---- requirements.in | 2 +- requirements.txt | 2 +- 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/graphql_api/types/plan_representation/plan_representation.graphql b/graphql_api/types/plan_representation/plan_representation.graphql index 327e9f2e93..07e844a503 100644 --- a/graphql_api/types/plan_representation/plan_representation.graphql +++ b/graphql_api/types/plan_representation/plan_representation.graphql @@ -9,4 +9,9 @@ type PlanRepresentation { baseUnitPrice: Int! benefits: [String!]! monthlyUploadLimit: Int + isEnterprisePlan: Boolean! + isFreePlan: Boolean! + isProPlan: Boolean! + isTeamPlan: Boolean! + isSentryPlan: Boolean! } diff --git a/graphql_api/types/plan_representation/plan_representation.py b/graphql_api/types/plan_representation/plan_representation.py index c7b9c6fb95..3f9a4001ad 100644 --- a/graphql_api/types/plan_representation/plan_representation.py +++ b/graphql_api/types/plan_representation/plan_representation.py @@ -14,27 +14,27 @@ @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("planName") def resolve_plan_name(plan_data: PlanData, info) -> str: - return plan_data.value + return plan_data["value"] @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") @@ -46,13 +46,38 @@ 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"] diff --git a/requirements.in b/requirements.in index 695a4c4157..d6146ed72d 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/3616116941c814af6c240063e8c252716eb37426.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..9258c7d88e 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/3616116941c814af6c240063e8c252716eb37426.tar.gz # via -r requirements.in simplejson==3.17.2 # via -r requirements.in From c1f6d0364b6e74bd932ab9f75853dcf85669ff4f Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Fri, 20 Dec 2024 16:41:56 +0200 Subject: [PATCH 2/3] update with tests --- api/internal/owner/serializers.py | 6 +- graphql_api/tests/test_owner.py | 65 +++++++++++++++++++ graphql_api/types/owner/owner.py | 3 +- .../plan_representation.graphql | 1 + .../plan_representation.py | 5 ++ 5 files changed, 75 insertions(+), 5 deletions(-) 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 73d20d0411..302d57e4be 100644 --- a/graphql_api/tests/test_owner.py +++ b/graphql_api/tests/test_owner.py @@ -1078,3 +1078,68 @@ 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 + isTrialPlan + isFreePlan + } + } + } + """ % (current_org.username) + data = self.gql_request(query, owner=current_org) + assert data["owner"]["availablePlans"][0]["isEnterprisePlan"] is False + assert data["owner"]["availablePlans"][0]["isFreePlan"] is True + assert data["owner"]["availablePlans"][0]["isProPlan"] is False + assert data["owner"]["availablePlans"][0]["isTeamPlan"] is False + assert data["owner"]["availablePlans"][0]["isSentryPlan"] is False + assert data["owner"]["availablePlans"][0]["isTrialPlan"] is False + + def test_fetch_available_plans_is_trial_plan(self): + current_org = OwnerFactory( + username="random-plan-user", + service="github", + plan=PlanName.TRIAL_PLAN_NAME.value, + ) + + query = """{ + owner(username: "%s") { + availablePlans { + isTrialPlan + } + } + } + """ % (current_org.username) + data = self.gql_request(query, owner=current_org) + assert data["owner"]["availablePlans"][0]["isTrialPlan"] is True + + def test_fetch_available_plans_is_pro_plan(self): + current_org = OwnerFactory( + username="random-plan-user", + service="github", + plan=PlanName.CODECOV_PRO_MONTHLY_LEGACY.value, + ) + + query = """{ + owner(username: "%s") { + availablePlans { + isProPlan + } + } + } + """ % (current_org.username) + data = self.gql_request(query, owner=current_org) + assert data["owner"]["availablePlans"][0]["isProPlan"] is True 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 07e844a503..a4447768be 100644 --- a/graphql_api/types/plan_representation/plan_representation.graphql +++ b/graphql_api/types/plan_representation/plan_representation.graphql @@ -14,4 +14,5 @@ type PlanRepresentation { isProPlan: Boolean! isTeamPlan: Boolean! isSentryPlan: Boolean! + isTrialPlan: Boolean! } diff --git a/graphql_api/types/plan_representation/plan_representation.py b/graphql_api/types/plan_representation/plan_representation.py index 3f9a4001ad..4e76467838 100644 --- a/graphql_api/types/plan_representation/plan_representation.py +++ b/graphql_api/types/plan_representation/plan_representation.py @@ -81,3 +81,8 @@ def resolve_is_team(plan_data: PlanData, info) -> bool: @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"] From ff6c17f5d07662706a1222986ca400d05da79b45 Mon Sep 17 00:00:00 2001 From: RulaKhaled Date: Fri, 20 Dec 2024 22:19:08 +0200 Subject: [PATCH 3/3] update shared version, tests --- graphql_api/tests/test_owner.py | 98 +++++++++++++++++++-------------- requirements.in | 2 +- requirements.txt | 2 +- 3 files changed, 60 insertions(+), 42 deletions(-) diff --git a/graphql_api/tests/test_owner.py b/graphql_api/tests/test_owner.py index 302d57e4be..463fa928eb 100644 --- a/graphql_api/tests/test_owner.py +++ b/graphql_api/tests/test_owner.py @@ -1094,52 +1094,70 @@ def test_fetch_available_plans_is_enterprise_plan(self): isProPlan isTeamPlan isSentryPlan - isTrialPlan isFreePlan - } - } - } - """ % (current_org.username) - data = self.gql_request(query, owner=current_org) - assert data["owner"]["availablePlans"][0]["isEnterprisePlan"] is False - assert data["owner"]["availablePlans"][0]["isFreePlan"] is True - assert data["owner"]["availablePlans"][0]["isProPlan"] is False - assert data["owner"]["availablePlans"][0]["isTeamPlan"] is False - assert data["owner"]["availablePlans"][0]["isSentryPlan"] is False - assert data["owner"]["availablePlans"][0]["isTrialPlan"] is False - - def test_fetch_available_plans_is_trial_plan(self): - current_org = OwnerFactory( - username="random-plan-user", - service="github", - plan=PlanName.TRIAL_PLAN_NAME.value, - ) - - query = """{ - owner(username: "%s") { - availablePlans { isTrialPlan } } } """ % (current_org.username) data = self.gql_request(query, owner=current_org) - assert data["owner"]["availablePlans"][0]["isTrialPlan"] is True - - def test_fetch_available_plans_is_pro_plan(self): - current_org = OwnerFactory( - username="random-plan-user", - service="github", - plan=PlanName.CODECOV_PRO_MONTHLY_LEGACY.value, - ) - - query = """{ - owner(username: "%s") { - availablePlans { - isProPlan - } + 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, + }, + ] } } - """ % (current_org.username) - data = self.gql_request(query, owner=current_org) - assert data["owner"]["availablePlans"][0]["isProPlan"] is True diff --git a/requirements.in b/requirements.in index d6146ed72d..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/3616116941c814af6c240063e8c252716eb37426.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 9258c7d88e..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/3616116941c814af6c240063e8c252716eb37426.tar.gz +shared @ https://github.com/codecov/shared/archive/abc6b36feceb05c3b754050061436574111058f9.tar.gz # via -r requirements.in simplejson==3.17.2 # via -r requirements.in