diff --git a/studio/components/interfaces/BillingV2/Subscription/BillingBreakdown.tsx b/studio/components/interfaces/BillingV2/Subscription/BillingBreakdown.tsx index 02f76d7795c..5d5f97b9457 100644 --- a/studio/components/interfaces/BillingV2/Subscription/BillingBreakdown.tsx +++ b/studio/components/interfaces/BillingV2/Subscription/BillingBreakdown.tsx @@ -174,12 +174,10 @@ const BillingBreakdown = ({}: BillingBreakdownProps) => {

Exceeded limit

- ) : isApproachingLimit ? ( + ) : isApproachingLimit && !isUsageBillingEnabled ? (
-

- Reaching {isUsageBillingEnabled ? 'limit' : 'quota'} -

+

Reaching limit

) : null} diff --git a/studio/components/interfaces/BillingV2/Usage/Activity.tsx b/studio/components/interfaces/BillingV2/Usage/Activity.tsx index c56c895096e..8498bce240f 100644 --- a/studio/components/interfaces/BillingV2/Usage/Activity.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Activity.tsx @@ -16,9 +16,10 @@ import UsageBarChart from './UsageBarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' -import { getUpgradeUrl } from './Usage.utils' +import { ChartYFormatterCompactNumber, getUpgradeUrl } from './Usage.utils' import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants' import { DataPoint } from 'data/analytics/constants' +import Panel from 'components/ui/Panel' export interface ActivityProps { projectRef: string @@ -32,14 +33,22 @@ const Activity = ({ projectRef }: ActivityProps) => { current_period_start !== undefined ? new Date(current_period_start * 1000).toISOString() : undefined - const endDate = + let endDate = current_period_end !== undefined ? new Date(current_period_end * 1000).toISOString() : undefined + // If end date is in future, set end date to now + if (endDate && dayjs(endDate).isAfter(dayjs())) { + // LF seems to have an issue with the milliseconds, causes infinite loading sometimes + endDate = new Date().toISOString().slice(0, -5) + 'Z' + } + const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'activity') const upgradeUrl = getUpgradeUrl(projectRef, subscription) + const isFreeTier = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.FREE const isProTier = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.PRO - const exceededLimitStyle = isFreeTier || isProTier ? 'text-red-900' : 'text-amber-900' + const usageBasedBilling = !isFreeTier && !isProTier + const exceededLimitStyle = !usageBasedBilling ? 'text-red-900' : 'text-amber-900' const { data: mauData, isLoading: isLoadingMauData } = useDailyStatsQuery({ projectRef, @@ -149,27 +158,16 @@ const Activity = ({ projectRef }: ActivityProps) => { const chartData = chartMeta[attribute.key]?.data ?? [] - // [Joshen] Ideally this should come from the API imo, foresee some discrepancies - const lastZeroValue = chartData.find( - (x: any) => x.loopId > 0 && x[attribute.attribute] === 0 - ) - const lastKnownValue = - lastZeroValue !== undefined && !chartMeta[attribute.key]?.hasNoData - ? dayjs(lastZeroValue.period_start) - .subtract(1, 'day') - .format('DD MMM YYYY, HH:mma (ZZ)') - : undefined - return (
- {usageMeta?.available_in_plan && ( + {usageMeta?.available_in_plan ? ( <>

{attribute.name} quota usage

- {usageRatio >= 1 ? ( + {!usageBasedBilling && usageRatio >= 1 ? (
{ />

Exceeded limit

- ) : usageRatio >= USAGE_APPROACHING_THRESHOLD ? ( + ) : !usageBasedBilling && usageRatio >= USAGE_APPROACHING_THRESHOLD ? (
{
) : null}
- {isFreeTier && ( + {!usageBasedBilling && usageRatio >= USAGE_APPROACHING_THRESHOLD && ( + + +
+ + )}
diff --git a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx index 82cb5737949..43137183134 100644 --- a/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx +++ b/studio/components/interfaces/BillingV2/Usage/Bandwidth.tsx @@ -17,7 +17,7 @@ import { Button, IconAlertTriangle } from 'ui' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' -import { getUpgradeUrl } from './Usage.utils' +import { ChartYFormatterCompactNumber, getUpgradeUrl } from './Usage.utils' import UsageBarChart from './UsageBarChart' export interface BandwidthProps { @@ -32,14 +32,21 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { current_period_start !== undefined ? new Date(current_period_start * 1000).toISOString() : undefined - const endDate = + let endDate = current_period_end !== undefined ? new Date(current_period_end * 1000).toISOString() : undefined + // If end date is in future, set end date to now + if (endDate && dayjs(endDate).isAfter(dayjs())) { + // LF seems to have an issue with the milliseconds, causes infinite loading sometimes + endDate = new Date().toISOString().slice(0, -5) + 'Z' + } + const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'bandwidth') const upgradeUrl = getUpgradeUrl(projectRef, subscription) const isFreeTier = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.FREE const isProTier = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.PRO - const exceededLimitStyle = isFreeTier || isProTier ? 'text-red-900' : 'text-amber-900' + const usageBasedBilling = !isFreeTier && !isProTier + const exceededLimitStyle = !usageBasedBilling ? 'text-red-900' : 'text-amber-900' const { data: dbEgressData, isLoading: isLoadingDbEgressData } = useDailyStatsQuery({ projectRef, @@ -88,25 +95,14 @@ const Bandwidth = ({ projectRef }: BandwidthProps) => { const chartData = chartMeta[attribute.key]?.data ?? [] - // [Joshen] Ideally this should come from the API imo, foresee some discrepancies - const lastZeroValue = chartData.find( - (x: any) => x.loopId > 0 && x[attribute.attribute] === 0 - ) - const lastKnownValue = - lastZeroValue !== undefined && !chartMeta[attribute.key]?.hasNoData - ? dayjs(lastZeroValue.period_start) - .subtract(1, 'day') - .format('DD MMM YYYY, HH:mma (ZZ)') - : undefined - return (
- +

{attribute.name} quota usage

- {usageRatio >= 1 ? ( + {!usageBasedBilling && usageRatio >= 1 ? (
{ />

Exceeded limit

- ) : usageRatio >= USAGE_APPROACHING_THRESHOLD ? ( + ) : !usageBasedBilling && usageRatio >= USAGE_APPROACHING_THRESHOLD ? (

Approaching limit

) : null}
- {isFreeTier && ( + {!usageBasedBilling && usageRatio >= USAGE_APPROACHING_THRESHOLD && ( + + +
+ {attribute.chartDescription.split('\n').map((paragraph, idx) => (

{paragraph} @@ -195,7 +217,7 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => {

- ) : ( + ) : chartData.length ? ( { yFormatter={(value) => `${value}%`} yLimit={100} /> + ) : ( + + +
+

No data

+

+ There is no data in the timeframe available +

+
+
+
)}
diff --git a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx index 722f89a7f76..f2a8dec923b 100644 --- a/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SectionContent.tsx @@ -6,16 +6,14 @@ import Link from 'next/link' export interface SectionContent { section: CategoryAttribute includedInPlan?: boolean - lastKnownValue?: string } const SectionContent = ({ section, includedInPlan, - lastKnownValue, children, }: PropsWithChildren) => { - const { name, description, docsUrl } = section + const { name, description, links } = section return (
@@ -29,27 +27,28 @@ const SectionContent = ({ {includedInPlan === false && Not included}
{description.split('\n').map((value, idx) => ( -

+

{value}

))}
- {docsUrl !== undefined && ( -
+ {links && links.length && ( +

More information

- - -
-

Documentation

- -
-
- + {links.map((link) => ( +
+ + +
+

{link.name}

+ +
+
+ +
+ ))}
)} - {lastKnownValue !== undefined && ( -

Last updated at: {lastKnownValue}

- )}
{children}
diff --git a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx index 419a6098a7d..feb48a059c0 100644 --- a/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx +++ b/studio/components/interfaces/BillingV2/Usage/SizeAndCounts.tsx @@ -18,7 +18,7 @@ import UsageBarChart from './UsageBarChart' import SectionContent from './SectionContent' import SectionHeader from './SectionHeader' import { USAGE_CATEGORIES } from './Usage.constants' -import { getUpgradeUrl } from './Usage.utils' +import { ChartYFormatterCompactNumber, getUpgradeUrl } from './Usage.utils' import { DataPoint } from 'data/analytics/constants' export interface SizeAndCountsProps { @@ -33,14 +33,21 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { current_period_start !== undefined ? new Date(current_period_start * 1000).toISOString() : undefined - const endDate = + let endDate = current_period_end !== undefined ? new Date(current_period_end * 1000).toISOString() : undefined + // If end date is in future, set end date to now + if (endDate && dayjs(endDate).isAfter(dayjs())) { + // LF seems to have an issue with the milliseconds, causes infinite loading sometimes + endDate = new Date().toISOString().slice(0, -5) + 'Z' + } + const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'sizeCount') const upgradeUrl = getUpgradeUrl(projectRef, subscription) const isFreeTier = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.FREE const isProTier = subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.PRO - const exceededLimitStyle = isFreeTier || isProTier ? 'text-red-900' : 'text-amber-900' + const usageBasedBilling = !isFreeTier && !isProTier + const exceededLimitStyle = !usageBasedBilling ? 'text-red-900' : 'text-amber-900' const { data: dbSizeData, isLoading: isLoadingDbSizeData } = useDailyStatsQuery({ projectRef, @@ -103,25 +110,14 @@ const SizeAndCounts = ({ projectRef }: SizeAndCountsProps) => { const chartData = chartMeta[attribute.key]?.data ?? [] - // [Joshen] Ideally this should come from the API imo, foresee some discrepancies - const lastZeroValue = chartData.find( - (x: any) => x.loopId > 0 && x[attribute.attribute] === 0 - ) - const lastKnownValue = - lastZeroValue !== undefined && !chartMeta[attribute.key]?.hasNoData - ? dayjs(lastZeroValue.period_start) - .subtract(1, 'day') - .format('DD MMM YYYY, HH:mma (ZZ)') - : undefined - return (
- +

{attribute.name} quota usage

- {usageRatio >= 1 ? ( + {!usageBasedBilling && usageRatio >= 1 ? (
{ />

Exceeded limit

- ) : usageRatio >= USAGE_APPROACHING_THRESHOLD ? ( + ) : !usageBasedBilling && usageRatio >= USAGE_APPROACHING_THRESHOLD ? (

Approaching limit

) : null}
- {isFreeTier && ( + + {!usageBasedBilling && usageRatio >= USAGE_APPROACHING_THRESHOLD && (
) : null} @@ -140,7 +146,7 @@ const Usage = () => { onClick={() => scrollTo(category.key)} className="flex items-center opacity-50 space-x-2 py-3 hover:opacity-100 transition cursor-pointer" > - {status === USAGE_STATUS.NORMAL ? ( + {status === USAGE_STATUS.NORMAL || usageBillingEnabled ? ( ) : status === USAGE_STATUS.APPROACHING ? ( @@ -155,8 +161,8 @@ const Usage = () => {
- {/* - [Joshen] Could potentially run a map here based on USAGE_CATEGORIES, rather than defining each section + {/* + [Joshen] Could potentially run a map here based on USAGE_CATEGORIES, rather than defining each section but thinking it's gonna "cover up" too much details and make it harder to add attribute specific components e.g for database size, we also need to show disk volume size. Not to mention that are little nuances across each attribute RE formatting (bytes vs locale string) diff --git a/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts b/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts index d4286819f02..99f6092748f 100644 --- a/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts +++ b/studio/components/interfaces/BillingV2/Usage/Usage.utils.ts @@ -4,6 +4,7 @@ import { USAGE_APPROACHING_THRESHOLD } from '../Billing.constants' import { CategoryAttribute, USAGE_STATUS } from './Usage.constants' import { StripeSubscription } from 'components/interfaces/Billing' import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants' +import { formatBytes } from 'lib/helpers' // [Joshen] This is just for development to generate some test data for chart rendering export const generateUsageData = (attribute: string, days: number): DataPoint[] => { @@ -46,3 +47,20 @@ export const getUpgradeUrl = (projectRef: string, subscription?: StripeSubscript ? `/project/${projectRef}/settings/billing/update` : `/project/${projectRef}/settings/billing/update/pro` } + +const compactNumberFormatter = new Intl.NumberFormat('en-US', { + notation: 'compact', + compactDisplay: 'short', +}) + +export const ChartYFormatterCompactNumber = (number: number | string, unit: string) => { + if (typeof number === 'string') return number + + if (unit === 'bytes') { + const formattedBytes = formatBytes(number, 0).replace(/\s/g, '') + + return formattedBytes === '0bytes' ? '0' : formattedBytes + } else { + return compactNumberFormatter.format(number) + } +} diff --git a/studio/components/interfaces/BillingV2/Usage/UsageBarChart.tsx b/studio/components/interfaces/BillingV2/Usage/UsageBarChart.tsx index 2e2dbddae43..efe0289794d 100644 --- a/studio/components/interfaces/BillingV2/Usage/UsageBarChart.tsx +++ b/studio/components/interfaces/BillingV2/Usage/UsageBarChart.tsx @@ -58,7 +58,7 @@ const UsageBarChart = ({ ( } as TData } }, + staleTime: 1000 * 60 * 60, // default good for an hour for now ...options, } ) diff --git a/studio/data/analytics/keys.ts b/studio/data/analytics/keys.ts index ad9e94223b1..fc12cf728fc 100644 --- a/studio/data/analytics/keys.ts +++ b/studio/data/analytics/keys.ts @@ -1,5 +1,3 @@ -import { LogsEndpointParams } from 'components/interfaces/Settings/Logs' - export const analyticsKeys = { functionsInvStats: ( projectRef: string | undefined, @@ -14,7 +12,17 @@ export const analyticsKeys = { interval, }: { attribute?: string; startDate?: string; endDate?: string; interval?: string } ) => - ['projects', projectRef, 'daily-stats', { attribute, startDate, endDate, interval }] as const, + [ + 'projects', + projectRef, + 'daily-stats', + { + attribute, + startDate: isoDateStringToDate(startDate), + endDate: isoDateStringToDate(endDate), + interval, + }, + ] as const, infraMonitoring: ( projectRef: string | undefined, { @@ -34,3 +42,8 @@ export const analyticsKeys = { ['projects', projectRef, 'usage.api-counts', interval] as const, } +function isoDateStringToDate(isoDateString: string | undefined): string | undefined { + if (!isoDateString) return isoDateString + + return isoDateString.split('T')[0] +}