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

Conversation

@ajay-sentry
Copy link
Contributor

Purpose/Motivation

As part of Milestone 2 for making the developer plan like the team plan, we want to move all the business logic from Gazebo to API; making Gazebo the presentation layer it should've been in the first place.

Currently Gazebo has these helper functions:

  • isFreePlan
  • isTeamPlan
  • isEnterprisePlan
  • isBasicPlan
  • isPaidPlan -- This will be removed in favor of a check for the "billingRate" prop being none
  • isMonthlyPlan -- This will be removed in favor of checking the billingRate prop being "Monthly"
  • isAnnualPlan -- This will be removed in favor of checking the billingRate prop being "Annual"
  • isSentryPlan
  • isCodecovProPlan -- This will be removed outright
  • isProPlan
  • isTrialPlan
  • useProPlans -- this is a copy of findProPlans now that the enterprise flag has been removed lol
  • findProPlans -- next PR
  • findSentryPlans -- next PR
  • findTeamPlans -- next PR

Each of which needs to be updated / modified in tandem with any plan changes that occur on the API side. We basically have 2 sources of Truth for this information, and anytime we want to update that information on one end we end up having to "remember" to update it on the other to avoid breaking something.

The goal of this PR is to tackle creating all the new resolvers and graphQL types we need for the is____Plan helpers. The find___Plan helpers will be tackled in a separate PR.

Links to relevant tickets

Related ticket -- #2996

What does this PR do?

So in this PR, I declare a couple extra properties on the plan service, and just do a check for the current plan name being in any of the existing representations. In the future, when we create the new plan configuration table, we may need to update these functions one more time to just pull the properties from the table directly (depending on how the columns end up shaking up there)

Notes to Reviewer

Anything to note to the team? Any tips on how to review, or where to start?

Legal Boilerplate

Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. In 2022 this entity acquired Codecov and as result Sentry is going to need some rights from me in order to utilize my contributions in this PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms.

@ajay-sentry ajay-sentry requested a review from a team as a code owner December 10, 2024 17:40
@codecov
Copy link

codecov bot commented Dec 10, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 96.00%. Comparing base (7749e0b) to head (f3148ad).
Report is 1 commits behind head on main.

✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1039   +/-   ##
=======================================
  Coverage   96.00%   96.00%           
=======================================
  Files         828      828           
  Lines       19373    19397   +24     
=======================================
+ Hits        18599    18623   +24     
  Misses        774      774           
Flag Coverage Δ
unit 92.27% <100.00%> (+0.01%) ⬆️
unit-latest-uploader 92.27% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@codecov-notifications
Copy link

codecov-notifications bot commented Dec 10, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@codecov-qa
Copy link

codecov-qa bot commented Dec 10, 2024

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
2665 2 2663 6
View the top 2 failed tests by shortest run time
graphql_api/tests/test_plan.py::TestPlanType::test_owner_plan_data_with_account
Stack Traces | 0.447s run time
self = <graphql_api.tests.test_plan.TestPlanType testMethod=test_owner_plan_data_with_account>

    def test_owner_plan_data_with_account(self):
        self.current_org.account = AccountFactory(
            plan=PlanName.CODECOV_PRO_YEARLY.value,
            plan_seat_count=25,
        )
        self.current_org.save()
        query = """{
                owner(username: "%s") {
                    plan {
                        marketingName
                        planName
                        value
                        tierName
                        billingRate
                        baseUnitPrice
                        planUserCount
                        isEnterprisePlan
                        isFreePlan
                        isProPlan
                        isSentryPlan
                        isTeamPlan
                        isTrialPlan
                    }
                }
            }
            """ % (self.current_org.username)
        data = self.gql_request(query, owner=self.current_org)
>       assert data["owner"]["plan"] == {
            "marketingName": "Pro",
            "planName": "users-pr-inappy",
            "value": "users-pr-inappy",
            "tierName": "pro",
            "billingRate": "annually",
            "baseUnitPrice": 10,
            "planUserCount": 25,
            "isEnterprisePlan": False,
            "isFreePlan": False,
            "isProPlan": True,
            "isSentryPlan": False,
            "isTeamPlan": False,
            "isTrialPlan": False,
        }
E       AssertionError: assert None == {'baseUnitPrice': 10, 'billingRate': 'annually', 'isEnterprisePlan': False, 'isFreePlan': False, ...}

graphql_api/tests/test_plan.py:127: AssertionError
graphql_api/tests/test_plan.py::TestPlanType::test_owner_plan_data_when_trialing
Stack Traces | 0.716s run time
self = <graphql_api.tests.test_plan.TestPlanType testMethod=test_owner_plan_data_when_trialing>

    @freeze_time("2023-06-19")
    def test_owner_plan_data_when_trialing(self):
        now = timezone.now()
        later = timezone.now() + timedelta(days=14)
        current_org = OwnerFactory(
            username="random-plan-user",
            service="github",
            plan=PlanName.TRIAL_PLAN_NAME.value,
            trial_start_date=now,
            trial_end_date=later,
            trial_status=TrialStatus.ONGOING.value,
            pretrial_users_count=234,
            plan_user_count=123,
        )
        query = """{
            owner(username: "%s") {
                plan {
                    trialStatus
                    trialEndDate
                    trialStartDate
                    trialTotalDays
                    marketingName
                    planName
                    value
                    tierName
                    billingRate
                    baseUnitPrice
                    benefits
                    monthlyUploadLimit
                    pretrialUsersCount
                    planUserCount
                    isEnterprisePlan
                    isFreePlan
                    isProPlan
                    isSentryPlan
                    isTeamPlan
                    isTrialPlan
                }
            }
        }
        """ % (current_org.username)
        data = self.gql_request(query, owner=current_org)
>       assert data["owner"]["plan"] == {
            "trialStatus": "ONGOING",
            "trialEndDate": "2023-07-03T00:00:00",
            "trialStartDate": "2023-06-19T00:00:00",
            "trialTotalDays": None,
            "marketingName": "Developer",
            "planName": "users-trial",
            "value": "users-trial",
            "tierName": "pro",
            "billingRate": None,
            "baseUnitPrice": 0,
            "benefits": [
                "Configurable # of users",
                "Unlimited public repositories",
                "Unlimited private repositories",
                "Priority Support",
            ],
            "monthlyUploadLimit": None,
            "pretrialUsersCount": 234,
            "planUserCount": 123,
            "isEnterprisePlan": False,
            "isFreePlan": False,
            "isProPlan": False,
            "isSentryPlan": False,
            "isTeamPlan": False,
            "isTrialPlan": True,
        }
E       AssertionError: assert None == {'baseUnitPrice': 0, 'benefits': ['Configurable # of users', 'Unlimited public repositories', 'Unlimited private repositories', 'Priority Support'], 'billingRate': None, 'isEnterprisePlan': False, ...}

graphql_api/tests/test_plan.py:72: AssertionError

To view more test analytics, go to the Test Analytics Dashboard
📢 Thoughts on this report? Let us know!

@codecov-public-qa
Copy link

codecov-public-qa bot commented Dec 10, 2024

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
2665 2 2663 6
View the top 2 failed tests by shortest run time
graphql_api/tests/test_plan.py::TestPlanType::test_owner_plan_data_with_account
Stack Traces | 0.447s run time
self = <graphql_api.tests.test_plan.TestPlanType testMethod=test_owner_plan_data_with_account>

    def test_owner_plan_data_with_account(self):
        self.current_org.account = AccountFactory(
            plan=PlanName.CODECOV_PRO_YEARLY.value,
            plan_seat_count=25,
        )
        self.current_org.save()
        query = """{
                owner(username: "%s") {
                    plan {
                        marketingName
                        planName
                        value
                        tierName
                        billingRate
                        baseUnitPrice
                        planUserCount
                        isEnterprisePlan
                        isFreePlan
                        isProPlan
                        isSentryPlan
                        isTeamPlan
                        isTrialPlan
                    }
                }
            }
            """ % (self.current_org.username)
        data = self.gql_request(query, owner=self.current_org)
>       assert data["owner"]["plan"] == {
            "marketingName": "Pro",
            "planName": "users-pr-inappy",
            "value": "users-pr-inappy",
            "tierName": "pro",
            "billingRate": "annually",
            "baseUnitPrice": 10,
            "planUserCount": 25,
            "isEnterprisePlan": False,
            "isFreePlan": False,
            "isProPlan": True,
            "isSentryPlan": False,
            "isTeamPlan": False,
            "isTrialPlan": False,
        }
E       AssertionError: assert None == {'baseUnitPrice': 10, 'billingRate': 'annually', 'isEnterprisePlan': False, 'isFreePlan': False, ...}

graphql_api/tests/test_plan.py:127: AssertionError
graphql_api/tests/test_plan.py::TestPlanType::test_owner_plan_data_when_trialing
Stack Traces | 0.716s run time
self = <graphql_api.tests.test_plan.TestPlanType testMethod=test_owner_plan_data_when_trialing>

    @freeze_time("2023-06-19")
    def test_owner_plan_data_when_trialing(self):
        now = timezone.now()
        later = timezone.now() + timedelta(days=14)
        current_org = OwnerFactory(
            username="random-plan-user",
            service="github",
            plan=PlanName.TRIAL_PLAN_NAME.value,
            trial_start_date=now,
            trial_end_date=later,
            trial_status=TrialStatus.ONGOING.value,
            pretrial_users_count=234,
            plan_user_count=123,
        )
        query = """{
            owner(username: "%s") {
                plan {
                    trialStatus
                    trialEndDate
                    trialStartDate
                    trialTotalDays
                    marketingName
                    planName
                    value
                    tierName
                    billingRate
                    baseUnitPrice
                    benefits
                    monthlyUploadLimit
                    pretrialUsersCount
                    planUserCount
                    isEnterprisePlan
                    isFreePlan
                    isProPlan
                    isSentryPlan
                    isTeamPlan
                    isTrialPlan
                }
            }
        }
        """ % (current_org.username)
        data = self.gql_request(query, owner=current_org)
>       assert data["owner"]["plan"] == {
            "trialStatus": "ONGOING",
            "trialEndDate": "2023-07-03T00:00:00",
            "trialStartDate": "2023-06-19T00:00:00",
            "trialTotalDays": None,
            "marketingName": "Developer",
            "planName": "users-trial",
            "value": "users-trial",
            "tierName": "pro",
            "billingRate": None,
            "baseUnitPrice": 0,
            "benefits": [
                "Configurable # of users",
                "Unlimited public repositories",
                "Unlimited private repositories",
                "Priority Support",
            ],
            "monthlyUploadLimit": None,
            "pretrialUsersCount": 234,
            "planUserCount": 123,
            "isEnterprisePlan": False,
            "isFreePlan": False,
            "isProPlan": False,
            "isSentryPlan": False,
            "isTeamPlan": False,
            "isTrialPlan": True,
        }
E       AssertionError: assert None == {'baseUnitPrice': 0, 'benefits': ['Configurable # of users', 'Unlimited public repositories', 'Unlimited private repositories', 'Priority Support'], 'billingRate': None, 'isEnterprisePlan': False, ...}

graphql_api/tests/test_plan.py:72: AssertionError

To view more test analytics, go to the Test Analytics Dashboard
📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Contributor

github-actions bot commented Dec 10, 2024

✅ All tests successful. No failed tests were found.

📣 Thoughts on this report? Let Codecov know! | Powered by Codecov

@plan_bindable.field("isEnterprisePlan")
@sync_to_async
def resolve_is_enterprise_plan(plan_service: PlanService, info) -> bool:
return plan_service.is_enterprise_plan
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where are these PlanService.is_* attributes defined, I couldn't find them in shared.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOL I totally forgot that we swapped over that stuff to shared, let me open up a new PR for that part. It worked on my local because I modified shared directly

isProPlan: Boolean!
isSentryPlan: Boolean!
isTeamPlan: Boolean!
isTrialPlan: Boolean!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are all these boolean fields different from using a enum for plan name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple aspects to it; first being there are multiple pro / sentry / team / enterprise / free plans so we can't rely on just the name of the plan to uniquely identify the plan type

Secondly, we could use the 'tier name' attribute, and I was thinking about that route as well, but then in the gazebo code instead of having something like

BEFORE
isFreePlan(accountDetails?.plan?.value)

AFTER
accountDetails?.plan?.isFreePlan

POTENTIAL AFTER
accountDetails?.plan?.tier == 'free'

which then kind of leaves us in a similar situation as currently where we have some business logic in the FE. I could go either way tbh, I was just slightly leaning toward keeping gazebo more presentational

Copy link
Contributor

@JerrySentry JerrySentry Dec 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok got it

there are multiple pro / sentry / team / enterprise / free plans

does this mean multiple of these is_* booleans can be true for a given org?

@github-actions
Copy link
Contributor

This PR includes changes to shared. Please review them here: codecov/shared@2262286...1c4ca00

@ajay-sentry ajay-sentry added this pull request to the merge queue Dec 10, 2024
trialEndDate: DateTime
trialTotalDays: Int
pretrialUsersCount: Int
baseUnitPrice: Int!
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just organizing this in alphabetical

Merged via the queue into main with commit db54697 Dec 10, 2024
18 of 19 checks passed
@ajay-sentry ajay-sentry deleted the Ajay/2996-plan-gql-fields branch December 10, 2024 20:41
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants