Files
AstralRinth/apps/frontend/src/components/analytics-dashboard/breakdown.ts
T
Truman Gao 8371ff641a fix: analytics post release bugs (#6291)
* fix: previous period data was included in the table

* fix: revenue displaying stale data when viewing it from different metric and grouped by 6 hour or 1 hour

* fix: remove staletime on analytics query so switching tabs does not refersh query

* feat: add monetization alert

* fix-small: missing space in tooltip

* fix: incorrect y-axis formatting for trailing decimal 0s

* fix: switching tabs resets table series selection due to other refetches

* fix: always show month first in chart tooltip

* fix: change all time start date to be project published date

* fix: increase length on project name column

* fix: unknown download source data points not showing for download source breakdown

* fix: double unknown for loader

* fix: no data on country labeling incorrectly as "Unknown" instead of "Other"

* fix: date picker number inputs showing arrows

* fix: stat card showing enormous percentage for prev period by switching it to absolute value difference after 1000%

* fix: decimal values for playtime being rounded badly, resulting in 0.04 becoming 0.0

* fix: chips having stroke

* refactor: pnpm prepr

* fix: spacing in annoucement link

* fix: legend scroll shadow on top of event tooltip
2026-06-03 18:27:31 +00:00

111 lines
3.6 KiB
TypeScript

import type { Labrinth } from '@modrinth/api-client'
import type { AnalyticsBreakdownPreset } from '~/providers/analytics/analytics'
import { formatAnalyticsDownloadSourceLabel, type FormatMessage } from './analytics-messages'
export const ALL_BREAKDOWN_VALUE = '__all__'
export const UNKNOWN_BREAKDOWN_VALUE = '__unknown__'
export const COMBINED_BREAKDOWN_LABEL_SEPARATOR = ' + '
export const COMBINED_BREAKDOWN_DATASET_ID_PREFIX = 'breakdowns:'
export function getAnalyticsBreakdownValue(
point: Labrinth.Analytics.v3.ProjectAnalytics,
selectedBreakdown: AnalyticsBreakdownPreset,
formatMessage: FormatMessage,
): string {
switch (selectedBreakdown) {
case 'none':
return ALL_BREAKDOWN_VALUE
case 'project':
return normalizeBreakdownValue('source_project' in point ? point.source_project : undefined)
case 'country':
return normalizeBreakdownValue('country' in point ? point.country?.toUpperCase() : undefined)
case 'monetization': {
if ('monetized' in point && typeof point.monetized === 'boolean') {
return point.monetized ? 'monetized' : 'unmonetized'
}
return ALL_BREAKDOWN_VALUE
}
case 'user_agent': {
const downloadSource = normalizeBreakdownValue(
'user_agent' in point ? point.user_agent : undefined,
UNKNOWN_BREAKDOWN_VALUE,
)
return downloadSource === UNKNOWN_BREAKDOWN_VALUE
? UNKNOWN_BREAKDOWN_VALUE
: getDownloadSourceLabel(downloadSource, formatMessage)
}
case 'download_reason':
return normalizeBreakdownValue(
'reason' in point ? point.reason : undefined,
UNKNOWN_BREAKDOWN_VALUE,
)
case 'version_id':
return normalizeBreakdownValue('version_id' in point ? point.version_id : undefined)
case 'loader':
return normalizeBreakdownValue(
'loader' in point ? point.loader : undefined,
UNKNOWN_BREAKDOWN_VALUE,
)
case 'game_version':
return normalizeBreakdownValue(
'game_version' in point ? point.game_version : undefined,
UNKNOWN_BREAKDOWN_VALUE,
)
default:
return ALL_BREAKDOWN_VALUE
}
}
export function getAnalyticsBreakdownValues(
point: Labrinth.Analytics.v3.ProjectAnalytics,
selectedBreakdowns: readonly AnalyticsBreakdownPreset[],
formatMessage: FormatMessage,
): string[] {
return selectedBreakdowns
.filter((breakdown) => breakdown !== 'none')
.map((breakdown) => getAnalyticsBreakdownValue(point, breakdown, formatMessage))
}
export function getAnalyticsBreakdownKey(values: readonly string[]): string {
return values.map((value) => encodeURIComponent(value)).join('+')
}
export function getAnalyticsBreakdownDatasetId(
values: readonly string[],
selectedBreakdowns: readonly AnalyticsBreakdownPreset[],
): string {
const normalizedBreakdowns = selectedBreakdowns.filter((breakdown) => breakdown !== 'none')
if (normalizedBreakdowns.length === 0) {
return 'all'
}
if (normalizedBreakdowns.length === 1) {
if (normalizedBreakdowns[0] === 'project') {
return values[0] ?? 'all'
}
return `breakdown:${values[0] ?? 'all'}`
}
return `${COMBINED_BREAKDOWN_DATASET_ID_PREFIX}${getAnalyticsBreakdownKey(values)}`
}
export function getDownloadSourceLabel(value: string, formatMessage: FormatMessage): string {
return formatAnalyticsDownloadSourceLabel(value, formatMessage)
}
function normalizeBreakdownValue(
value: string | undefined,
fallback = ALL_BREAKDOWN_VALUE,
): string {
const normalized = value?.trim()
const normalizedLowercase = normalized?.toLowerCase()
if (
fallback === UNKNOWN_BREAKDOWN_VALUE &&
(normalizedLowercase === 'unknown' || normalizedLowercase === 'other')
) {
return fallback
}
return normalized && normalized.length > 0 ? normalized : fallback
}