Usage.utils.ts143 lines · main
1import dayjs from 'dayjs'
2import { groupBy } from 'lodash'
3
4import { DataPoint } from '@/data/analytics/constants'
5import type { OrgDailyUsageResponse, PricingMetric } from '@/data/analytics/org-daily-stats-query'
6import type { OrgSubscription } from '@/data/subscriptions/types'
7import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
8
9// [Joshen] This is just for development to generate some test data for chart rendering
10export const generateUsageData = (attribute: string, days: number): DataPoint[] => {
11 const tempArray = new Array(days).fill(0)
12 return tempArray.map((_x, idx) => {
13 return {
14 loopId: (idx + 1).toString(),
15 period_start: `${idx + 1}`,
16 [attribute]: Math.floor(Math.random() * 100).toString(),
17 }
18 })
19}
20
21export function useGetUpgradeUrl(slug: string, subscription?: OrgSubscription, source?: string) {
22 const { billingAll } = useIsFeatureEnabled(['billing:all'])
23
24 if (!billingAll) {
25 const subject = `Enquiry to upgrade plan for organization`
26 const message = `Organization Slug: ${slug}\nRequested plan: <Specify which plan to upgrade to: Pro | Team | Enterprise | Platform>`
27
28 return `/support/new?orgSlug=${slug}&projectRef=no-project&category=Plan_upgrade&subject=${subject}&message=${encodeURIComponent(message)}`
29 }
30
31 if (!subscription) {
32 return `/org/${slug}/billing`
33 }
34
35 return subscription?.plan?.id === 'pro' && subscription?.usage_billing_enabled === false
36 ? `/org/${slug}/billing#cost-control`
37 : `/org/${slug}/billing?panel=subscriptionPlan&source=usage${source}`
38}
39
40const compactNumberFormatter = new Intl.NumberFormat('en-US', {
41 notation: 'compact',
42 compactDisplay: 'short',
43})
44
45/**
46 * For the y-axis, we don't need to be as precise, to avoid showing 58.597MB.
47 */
48export const ChartYFormatterCompactNumber = (number: number | string, unit: string) => {
49 if (typeof number === 'string') return number
50
51 if (unit === 'bytes') {
52 const formattedBytes = formatBytesCompact(number).replace(/\s/g, '')
53
54 return formattedBytes === '0bytes' ? '0' : formattedBytes
55 } else if (unit === 'gigabytes') {
56 return compactNumberFormatter.format(number) + 'GB'
57 } else {
58 return compactNumberFormatter.format(number)
59 }
60}
61
62/**
63 * For the chart tooltip, we want to be more precise and show more decimals.
64 */
65export const ChartTooltipValueFormatter = (number: number | string, unit: string) => {
66 if (typeof number === 'string') return number
67
68 if (unit === 'bytes') {
69 const formattedBytes = formatBytesPrecision(number).replace(/\s/g, '')
70
71 return formattedBytes === '0bytes' ? '0' : formattedBytes
72 } else if (unit === 'gigabytes') {
73 return compactNumberFormatter.format(number) + 'GB'
74 } else {
75 return compactNumberFormatter.format(number)
76 }
77}
78
79const sizes = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
80
81const formatBytesCompact = (bytes: number) => {
82 if (bytes === 0 || bytes === undefined) return '0 bytes'
83
84 const k = 1024
85 const i = Math.floor(Math.log(bytes) / Math.log(k))
86
87 const unit = sizes[i]
88
89 let dm = 2
90 if (['bytes', 'KB', 'MB'].includes(unit)) {
91 dm = 0
92 } else if (['GB'].includes(unit)) {
93 dm = 1
94 }
95
96 return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + unit
97}
98
99const formatBytesPrecision = (bytes: any) => {
100 if (bytes === 0 || bytes === undefined) return '0 bytes'
101
102 const k = 1024
103 const i = Math.floor(Math.log(bytes) / Math.log(k))
104
105 const unit = sizes[i]
106
107 return parseFloat((bytes / Math.pow(k, i)).toFixed(3)) + ' ' + unit
108}
109
110export function dailyUsageToDataPoints(
111 dailyUsage: OrgDailyUsageResponse | undefined,
112 includeMetric: (metric: PricingMetric) => boolean
113): DataPoint[] {
114 if (!dailyUsage || !dailyUsage.usages.length) return []
115
116 const groupedByDate = groupBy(
117 dailyUsage.usages.filter((it) => includeMetric(it.metric as PricingMetric)),
118 'date'
119 )
120
121 const dataPoints: DataPoint[] = []
122
123 Object.entries(groupedByDate).forEach(([date, usages]) => {
124 const dataPoint: DataPoint = {
125 period_start: date,
126 periodStartFormatted: dayjs(date).format('DD MMM'),
127 }
128
129 for (const usage of usages) {
130 dataPoint[usage.metric.toLowerCase()] = usage.usage_original
131
132 if (usage.breakdown) {
133 for (const [key, value] of Object.entries(usage.breakdown)) {
134 dataPoint[key.toLowerCase()] = value
135 }
136 }
137 }
138
139 dataPoints.push(dataPoint)
140 })
141
142 return dataPoints
143}