Usage.utils.ts 4.3 KB

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