From 0fc340b3f78cf4a095e9b89752e2c31704ae57db Mon Sep 17 00:00:00 2001 From: James Kiger <68701146+jamesrkiger@users.noreply.github.com> Date: Fri, 24 Jan 2025 09:55:36 -0500 Subject: [PATCH] fix(billing): require org id for service/asset usage TASK-1476 (#5443) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### 📣 Summary Removes option for API calls to retrieve user-based service/asset usage. ### 💭 Notes We previously allowed for the possibility that users would not be organization members. If a user wasn't an org member, we used a separate API endpoint to retrieve their usage data. Now, however, from a frontend perspective users are always organization members and we should always use the organization usage endpoints. ### 👀 Preview steps 1. As a member of an MMO with at least one admin member, create and deploy a project and make a submission. 2. Access the organization usage page as an admin and view the per-project usage table. 3. On main, this table will be empty. On this PR branch, it will include the project. 4. Access the organization usage page as the owner and view the per-project usage table. 5. On main, the project will be shown as a draft. On this branch it will show up as deployed. --- jsapp/js/account/usage/usage.api.ts | 59 ++++++++----------- .../account/usage/usageProjectBreakdown.tsx | 16 ++--- jsapp/js/account/usage/useUsage.hook.ts | 4 +- 3 files changed, 34 insertions(+), 45 deletions(-) diff --git a/jsapp/js/account/usage/usage.api.ts b/jsapp/js/account/usage/usage.api.ts index ea3014cc39..3d4e9ad084 100644 --- a/jsapp/js/account/usage/usage.api.ts +++ b/jsapp/js/account/usage/usage.api.ts @@ -44,46 +44,30 @@ export interface UsageResponse { }; } -const USAGE_URL = '/api/v2/service_usage/'; -const ORGANIZATION_USAGE_URL = '/api/v2/organizations/:organization_id/service_usage/'; +const ORG_SERVICE_USAGE_URL = + '/api/v2/organizations/:organization_id/service_usage/'; +const ORG_ASSET_USAGE_URL = + '/api/v2/organizations/:organization_id/asset_usage/'; -const ASSET_USAGE_URL = '/api/v2/asset_usage/'; -const ORGANIZATION_ASSET_USAGE_URL = '/api/v2/organizations/:organization_id/asset_usage/'; - -export async function getUsage(organization_id: string | null = null) { - if (organization_id) { - return fetchGet( - ORGANIZATION_USAGE_URL.replace(':organization_id', organization_id), - { - includeHeaders: true, - errorMessageDisplay: t('There was an error fetching usage data.'), - } - ); - } - return fetchGet(USAGE_URL, { - includeHeaders: true, - errorMessageDisplay: t('There was an error fetching usage data.'), - }); -} - -export async function getAssetUsage(url = ASSET_USAGE_URL) { - return fetchGet(url, { - includeHeaders: true, - errorMessageDisplay: t('There was an error fetching asset usage data.'), - }); +export async function getOrgServiceUsage(organization_id: string) { + return fetchGet( + ORG_SERVICE_USAGE_URL.replace(':organization_id', organization_id), + { + includeHeaders: true, + errorMessageDisplay: t('There was an error fetching usage data.'), + } + ); } -export async function getAssetUsageForOrganization( +export async function getOrgAssetUsage( pageNumber: number | string, - order?: ProjectsTableOrder, - organizationId = '' + organizationId: string, + order?: ProjectsTableOrder ) { - // if the user isn't in an organization, just get their personal asset usage - if (!organizationId) { - return await getAssetUsage(ASSET_USAGE_URL); - } - - const apiUrl = ORGANIZATION_ASSET_USAGE_URL.replace(':organization_id', organizationId); + const apiUrl = ORG_ASSET_USAGE_URL.replace( + ':organization_id', + organizationId + ); const params = new URLSearchParams({ page: pageNumber.toString(), @@ -99,5 +83,8 @@ export async function getAssetUsageForOrganization( params.set('ordering', orderingPrefix + fieldDefinition.apiOrderingName); } - return await getAssetUsage(`${apiUrl}?${params}`); + return fetchGet(`${apiUrl}?${params}`, { + includeHeaders: true, + errorMessageDisplay: t('There was an error fetching asset usage data.'), + }); } diff --git a/jsapp/js/account/usage/usageProjectBreakdown.tsx b/jsapp/js/account/usage/usageProjectBreakdown.tsx index fcdbe442be..94d72cc209 100644 --- a/jsapp/js/account/usage/usageProjectBreakdown.tsx +++ b/jsapp/js/account/usage/usageProjectBreakdown.tsx @@ -7,7 +7,7 @@ import AssetStatusBadge from 'jsapp/js/components/common/assetStatusBadge'; import LoadingSpinner from 'jsapp/js/components/common/loadingSpinner'; import prettyBytes from 'pretty-bytes'; import type {AssetUsage, AssetWithUsage} from 'js/account/usage/usage.api'; -import {getAssetUsageForOrganization} from 'js/account/usage/usage.api'; +import {getOrgAssetUsage} from 'js/account/usage/usage.api'; import {USAGE_ASSETS_PER_PAGE} from 'jsapp/js/constants'; import SortableProjectColumnHeader from 'jsapp/js/projects/projectsTable/sortableProjectColumnHeader'; import type {ProjectFieldDefinition} from 'jsapp/js/projects/projectViews/constants'; @@ -35,11 +35,11 @@ const ProjectBreakdown = () => { const orgQuery = useOrganizationQuery(); useEffect(() => { - async function fetchData() { - const data = await getAssetUsageForOrganization( + async function fetchData(orgId: string) { + const data = await getOrgAssetUsage( currentPage, - order, - orgQuery.data?.id + orgId, + order ); const updatedResults = data.results.map((projectResult) => { const assetParts = projectResult.asset.split('/'); @@ -57,8 +57,10 @@ const ProjectBreakdown = () => { setLoading(false); } - fetchData(); - }, [currentPage, order]); + if (orgQuery.data) { + fetchData(orgQuery.data.id); + } + }, [currentPage, order, orgQuery.data]); if (loading) { return ; diff --git a/jsapp/js/account/usage/useUsage.hook.ts b/jsapp/js/account/usage/useUsage.hook.ts index ffb487e55b..7acc744397 100644 --- a/jsapp/js/account/usage/useUsage.hook.ts +++ b/jsapp/js/account/usage/useUsage.hook.ts @@ -2,7 +2,7 @@ import {createContext} from 'react'; import type {RecurringInterval} from 'js/account/stripe.types'; import {getSubscriptionInterval} from 'js/account/stripe.api'; import {convertSecondsToMinutes, formatRelativeTime} from 'js/utils'; -import {getUsage} from 'js/account/usage/usage.api'; +import {getOrgServiceUsage} from 'js/account/usage/usage.api'; import {useApiFetcher, withApiFetcher} from 'js/hooks/useApiFetcher.hook'; export interface UsageState { @@ -34,7 +34,7 @@ const loadUsage = async ( throw Error(t('No organization found')); } const trackingPeriod = await getSubscriptionInterval(); - const usage = await getUsage(organizationId); + const usage = await getOrgServiceUsage(organizationId); if (!usage) { throw Error(t("Couldn't get usage data")); }