Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(billing): return service/asset usage for current period TASK-1327 #5326

Merged
Merged
Show file tree
Hide file tree
Changes from all 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: 7 additions & 17 deletions jsapp/js/account/usage/usage.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@ export interface AssetWithUsage {
asset: string;
uid: string;
asset__name: string;
nlp_usage_current_month: {
total_nlp_asr_seconds: number;
total_nlp_mt_characters: number;
};
nlp_usage_current_year: {
nlp_usage_current_period: {
total_nlp_asr_seconds: number;
total_nlp_mt_characters: number;
};
Expand All @@ -27,28 +23,22 @@ export interface AssetWithUsage {
total_nlp_mt_characters: number;
};
storage_bytes: number;
submission_count_current_month: number;
submission_count_current_year: number;
submission_count_current_period: number;
submission_count_all_time: number;
deployment_status: string;
}

export interface UsageResponse {
current_month_start: string;
current_month_end: string;
current_year_start: string;
current_year_end: string;
current_period_start: string;
current_period_end: string;
total_submission_count: {
current_month: number;
current_year: number;
current_period: number;
all_time: number;
};
total_storage_bytes: number;
total_nlp_usage: {
asr_seconds_current_month: number;
mt_characters_current_month: number;
asr_seconds_current_year: number;
mt_characters_current_year: number;
asr_seconds_current_period: number;
mt_characters_current_period: number;
asr_seconds_all_time: number;
mt_characters_all_time: number;
};
Expand Down
22 changes: 4 additions & 18 deletions jsapp/js/account/usage/usage.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import useWhenStripeIsEnabled from 'js/hooks/useWhenStripeIsEnabled.hook';
import {ProductsContext} from '../useProducts.hook';
import {UsageContext} from 'js/account/usage/useUsage.hook';
import {OneTimeAddOnsContext} from '../useOneTimeAddonList.hook';
import moment from 'moment';
import {YourPlan} from 'js/account/usage/yourPlan.component';
import cx from 'classnames';
import LimitNotifications from 'js/components/usageLimits/limitNotifications.component';
Expand Down Expand Up @@ -55,27 +54,14 @@ export default function Usage() {
const location = useLocation();

const dateRange = useMemo(() => {
let startDate: string;
const endDate = usage.billingPeriodEnd
? formatDate(usage.billingPeriodEnd)
: formatDate(
moment(usage.currentMonthStart).add(1, 'month').toISOString()
);
switch (usage.trackingPeriod) {
case 'year':
startDate = formatDate(usage.currentYearStart);
break;
default:
startDate = formatDate(usage.currentMonthStart);
break;
}
const startDate = formatDate(usage.currentPeriodStart);
const endDate = formatDate(usage.currentPeriodEnd);
return t('##start_date## to ##end_date##')
.replace('##start_date##', startDate)
.replace('##end_date##', endDate);
}, [
usage.currentYearStart,
usage.currentMonthStart,
usage.billingPeriodEnd,
usage.currentPeriodStart,
usage.currentPeriodEnd,
usage.trackingPeriod,
]);

Expand Down
10 changes: 3 additions & 7 deletions jsapp/js/account/usage/usageProjectBreakdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,18 +129,14 @@ const ProjectBreakdown = () => {

const renderProjectRow = (project: AssetWithUsage) => {
const periodSubmissions =
project[
`submission_count_current_${usage.trackingPeriod}`
].toLocaleString();
project.submission_count_current_period.toLocaleString();

const periodASRSeconds = convertSecondsToMinutes(
project[`nlp_usage_current_${usage.trackingPeriod}`].total_nlp_asr_seconds
project.nlp_usage_current_period.total_nlp_asr_seconds
).toLocaleString();

const periodMTCharacters =
project[
`nlp_usage_current_${usage.trackingPeriod}`
].total_nlp_mt_characters.toLocaleString();
project.nlp_usage_current_period.total_nlp_mt_characters.toLocaleString();

return (
<tr key={project.asset}>
Expand Down
21 changes: 9 additions & 12 deletions jsapp/js/account/usage/useUsage.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ export interface UsageState {
submissions: number;
transcriptionMinutes: number;
translationChars: number;
currentMonthStart: string;
currentYearStart: string;
billingPeriodEnd: string | null;
currentPeriodStart: string;
currentPeriodEnd: string;
trackingPeriod: RecurringInterval;
lastUpdated?: String | null;
}
Expand All @@ -22,9 +21,8 @@ const INITIAL_USAGE_STATE: UsageState = Object.freeze({
submissions: 0,
transcriptionMinutes: 0,
translationChars: 0,
currentMonthStart: '',
currentYearStart: '',
billingPeriodEnd: null,
currentPeriodStart: '',
currentPeriodEnd: '',
trackingPeriod: 'month',
lastUpdated: '',
});
Expand All @@ -49,15 +47,14 @@ const loadUsage = async (
}
return {
storage: usage.total_storage_bytes,
submissions: usage.total_submission_count[`current_${trackingPeriod}`],
submissions: usage.total_submission_count.current_period,
transcriptionMinutes: convertSecondsToMinutes(
usage.total_nlp_usage[`asr_seconds_current_${trackingPeriod}`]
usage.total_nlp_usage.asr_seconds_current_period
),
translationChars:
usage.total_nlp_usage[`mt_characters_current_${trackingPeriod}`],
currentMonthStart: usage.current_month_start,
currentYearStart: usage.current_year_start,
billingPeriodEnd: usage[`current_${trackingPeriod}_end`],
usage.total_nlp_usage.mt_characters_current_period,
currentPeriodStart: usage.current_period_start,
currentPeriodEnd: usage.current_period_end,
trackingPeriod,
lastUpdated,
};
Expand Down
51 changes: 10 additions & 41 deletions kobo/apps/organizations/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
from kpi.models.object_permission import ObjectPermission


def get_monthly_billing_dates(organization: Union['Organization', None]):
"""Returns start and end dates of an organization's monthly billing cycle"""
def get_billing_dates(organization: Union['Organization', None]):
"""Returns start and end dates of an organization's billing cycle."""

now = timezone.now().replace(tzinfo=ZoneInfo('UTC'))
first_of_this_month = datetime(now.year, now.month, 1, tzinfo=ZoneInfo('UTC'))
Expand Down Expand Up @@ -49,7 +49,6 @@ def get_monthly_billing_dates(organization: Union['Organization', None]):
if not billing_details.get('billing_cycle_anchor'):
return first_of_this_month, first_of_next_month

# Subscription is billed monthly, use the current billing period dates
if billing_details.get('recurring_interval') == 'month':
period_start = billing_details.get('current_period_start').replace(
tzinfo=ZoneInfo('UTC')
Expand All @@ -59,38 +58,6 @@ def get_monthly_billing_dates(organization: Union['Organization', None]):
)
return period_start, period_end

# Subscription is billed yearly - count backwards from the end of the
# current billing year
period_start = billing_details.get('current_period_end').replace(
tzinfo=ZoneInfo('UTC')
)
while period_start > now:
period_start -= relativedelta(months=1)
period_end = period_start + relativedelta(months=1)
return period_start, period_end


def get_real_owner(user: User) -> User:
organization = user.organization
if organization.is_mmo:
return organization.owner_user_object
return user


def get_yearly_billing_dates(organization: Union['Organization', None]):
"""Returns start and end dates of an organization's annual billing cycle"""
now = timezone.now().replace(tzinfo=ZoneInfo('UTC'))
first_of_this_year = datetime(now.year, 1, 1, tzinfo=ZoneInfo('UTC'))
first_of_next_year = first_of_this_year + relativedelta(years=1)

if not organization:
return first_of_this_year, first_of_next_year
if not (billing_details := organization.active_subscription_billing_details()):
return first_of_this_year, first_of_next_year
if not (anchor_date := billing_details.get('billing_cycle_anchor')):
return first_of_this_year, first_of_next_year

# Subscription is billed yearly, use the dates from the subscription
if billing_details.get('recurring_interval') == 'year':
period_start = billing_details.get('current_period_start').replace(
tzinfo=ZoneInfo('UTC')
Expand All @@ -100,12 +67,14 @@ def get_yearly_billing_dates(organization: Union['Organization', None]):
)
return period_start, period_end

# Subscription is monthly, calculate this year's start based on anchor date
period_start = anchor_date.replace(tzinfo=ZoneInfo('UTC')) + relativedelta(years=1)
while period_start < now:
anchor_date += relativedelta(years=1)
period_end = period_start + relativedelta(years=1)
return period_start, period_end
return first_of_this_month, first_of_next_month


def get_real_owner(user: User) -> User:
organization = user.organization
if organization.is_mmo:
return organization.owner_user_object
return user


def revoke_org_asset_perms(organization: Organization, user_ids: list[int]):
Expand Down
20 changes: 8 additions & 12 deletions kobo/apps/organizations/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
)
from kpi.utils.object_permission import get_database_user
from kpi.views.v2.asset import AssetViewSet
from ..accounts.mfa.models import MfaMethod
from .models import Organization, OrganizationOwner, OrganizationUser
from .permissions import (
HasOrgRolePermission,
IsOrgAdminPermission,
OrganizationNestedHasOrgRolePermission,
)
from .serializers import OrganizationSerializer, OrganizationUserSerializer
from ..accounts.mfa.models import MfaMethod


class OrganizationAssetViewSet(AssetViewSet):
Expand Down Expand Up @@ -129,22 +129,18 @@ def service_usage(self, request, pk=None, *args, **kwargs):
> curl -X GET https://[kpi]/api/v2/organizations/{organization_id}/service_usage/
> {
> "total_nlp_usage": {
> "asr_seconds_current_month": {integer},
> "asr_seconds_current_year": {integer},
> "asr_seconds_current_period": {integer},
> "asr_seconds_all_time": {integer},
> "mt_characters_current_month": {integer},
> "mt_characters_current_year": {integer},
> "mt_characters_current_period": {integer},
> "mt_characters_all_time": {integer},
> },
> "total_storage_bytes": {integer},
> "total_submission_count": {
> "current_month": {integer},
> "current_year": {integer},
> "current_period": {integer},
> "all_time": {integer},
> },
> "current_month_start": {string (date), ISO format},
> "current_year_start": {string (date), ISO format},
> "billing_period_end": {string (date), ISO format}|{None},
> "current_period_start": {string (date), ISO format},
> "current_period_end": {string (date), ISO format}|{None},
> "last_updated": {string (date), ISO format},
> }
### CURRENT ENDPOINT
Expand Down Expand Up @@ -187,7 +183,7 @@ def asset_usage(self, request, pk=None, *args, **kwargs):
> "asset_type": {string},
> "asset": {asset_url},
> "asset_name": {string},
> "nlp_usage_current_month": {
> "nlp_usage_current_period": {
> "total_asr_seconds": {integer},
> "total_mt_characters": {integer},
> }
Expand All @@ -196,7 +192,7 @@ def asset_usage(self, request, pk=None, *args, **kwargs):
> "total_mt_characters": {integer},
> }
> "storage_bytes": {integer},
> "submission_count_current_month": {integer},
> "submission_count_current_period": {integer},
> "submission_count_all_time": {integer},
> "deployment_status": {string},
> },{...}
Expand Down
18 changes: 6 additions & 12 deletions kobo/apps/project_ownership/tests/api/v2/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,35 +365,29 @@ def __add_submissions(self):
def test_account_usage_transferred_to_new_user(self):
expected_data = {
'total_nlp_usage': {
'asr_seconds_current_year': 120,
'mt_characters_current_year': 1000,
'asr_seconds_current_month': 120,
'mt_characters_current_month': 1000,
'asr_seconds_current_period': 120,
'mt_characters_current_period': 1000,
'asr_seconds_all_time': 120,
'mt_characters_all_time': 1000,
},
'total_storage_bytes': 191642,
'total_submission_count': {
'all_time': 1,
'current_year': 1,
'current_month': 1,
'current_period': 1,
},
}

expected_empty_data = {
'total_nlp_usage': {
'asr_seconds_current_year': 0,
'mt_characters_current_year': 0,
'asr_seconds_current_month': 0,
'mt_characters_current_month': 0,
'asr_seconds_current_period': 0,
'mt_characters_current_period': 0,
'asr_seconds_all_time': 0,
'mt_characters_all_time': 0,
},
'total_storage_bytes': 0,
'total_submission_count': {
'all_time': 0,
'current_year': 0,
'current_month': 0,
'current_period': 0,
},
}

Expand Down
Loading
Loading