Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions graphql_api/tests/test_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ def test_owner_plan_data_when_trialing(self):
monthlyUploadLimit
pretrialUsersCount
planUserCount
isEnterprisePlan
isFreePlan
isProPlan
isSentryPlan
isTeamPlan
isTrialPlan
}
}
}
Expand All @@ -83,6 +89,12 @@ def test_owner_plan_data_when_trialing(self):
"monthlyUploadLimit": None,
"pretrialUsersCount": 234,
"planUserCount": 123,
"isEnterprisePlan": False,
"isFreePlan": False,
"isProPlan": False,
"isSentryPlan": False,
"isTeamPlan": False,
"isTrialPlan": True,
}

def test_owner_plan_data_with_account(self):
Expand All @@ -101,6 +113,12 @@ def test_owner_plan_data_with_account(self):
billingRate
baseUnitPrice
planUserCount
isEnterprisePlan
isFreePlan
isProPlan
isSentryPlan
isTeamPlan
isTrialPlan
}
}
}
Expand All @@ -114,6 +132,12 @@ def test_owner_plan_data_with_account(self):
"billingRate": "annually",
"baseUnitPrice": 10,
"planUserCount": 25,
"isEnterprisePlan": False,
"isFreePlan": False,
"isProPlan": True,
"isSentryPlan": False,
"isTeamPlan": False,
"isTrialPlan": False,
}

def test_owner_plan_data_has_seats_left(self):
Expand Down
30 changes: 18 additions & 12 deletions graphql_api/types/plan/plan.graphql
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
type Plan {
trialStatus: TrialStatus!
trialStartDate: DateTime
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

benefits: [String!]!
billingRate: String
hasSeatsLeft: Boolean!
isEnterprisePlan: Boolean!
isFreePlan: Boolean!
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?

marketingName: String!
monthlyUploadLimit: Int
planName: String!
@deprecated(
reason: "Plan representations have used `value` for a while, making the frontend code hard to change"
)
value: String!
tierName: String!
billingRate: String
baseUnitPrice: Int!
benefits: [String!]!
monthlyUploadLimit: Int
planUserCount: Int
hasSeatsLeft: Boolean!
pretrialUsersCount: Int
tierName: String!
trialEndDate: DateTime
trialStartDate: DateTime
trialStatus: TrialStatus!
trialTotalDays: Int
value: String!
}
42 changes: 38 additions & 4 deletions graphql_api/types/plan/plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
from typing import List, Optional

from ariadne import ObjectType
from shared.plan.constants import (
TrialStatus,
)
from shared.plan.constants import PlanBillingRate, TrialStatus
from shared.plan.service import PlanService

from codecov.db import sync_to_async
Expand Down Expand Up @@ -59,7 +57,7 @@ def resolve_tier_name(plan_service: PlanService, info) -> str:

@plan_bindable.field("billingRate")
@sync_to_async
def resolve_billing_rate(plan_service: PlanService, info) -> Optional[str]:
def resolve_billing_rate(plan_service: PlanService, info) -> Optional[PlanBillingRate]:
return plan_service.billing_rate


Expand Down Expand Up @@ -98,3 +96,39 @@ def resolve_plan_user_count(plan_service: PlanService, info) -> int:
@sync_to_async
def resolve_has_seats_left(plan_service: PlanService, info) -> bool:
return plan_service.has_seats_left


@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



@plan_bindable.field("isFreePlan")
@sync_to_async
def resolve_is_free_plan(plan_service: PlanService, info) -> bool:
return plan_service.is_free_plan


@plan_bindable.field("isProPlan")
@sync_to_async
def resolve_is_pro_plan(plan_service: PlanService, info) -> bool:
return plan_service.is_pro_plan


@plan_bindable.field("isSentryPlan")
@sync_to_async
def resolve_is_sentry_plan(plan_service: PlanService, info) -> bool:
return plan_service.is_sentry_plan


@plan_bindable.field("isTeamPlan")
@sync_to_async
def resolve_is_team_plan(plan_service: PlanService, info) -> bool:
return plan_service.is_team_plan


@plan_bindable.field("isTrialPlan")
@sync_to_async
def resolve_is_trial_plan(plan_service: PlanService, info) -> bool:
return plan_service.is_trial_plan
Loading