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
This commit is contained in:
Truman Gao
2026-06-03 18:27:31 +00:00
committed by GitHub
parent b1cd16f966
commit 8371ff641a
22 changed files with 343 additions and 87 deletions
@@ -17,7 +17,7 @@ import type {
const ANALYTICS_START_TIMESTAMP = '2023-01-01T00:00:00.000Z'
export const ANALYTICS_START_DATE_INPUT_VALUE = ANALYTICS_START_TIMESTAMP.slice(0, 10)
const ANALYTICS_START_TIME = new Date(ANALYTICS_START_TIMESTAMP).getTime()
export const ANALYTICS_START_TIME = new Date(ANALYTICS_START_TIMESTAMP).getTime()
export const REVENUE_MIN_TIMEFRAME_MS = 1 * 24 * 60 * 60 * 1000 // need at least 1 day in timeframe range to show revenue
const ANALYTICS_DAY_MS = 24 * 60 * 60 * 1000
const ANALYTICS_MAX_TIME_SLICES = 256 // controls granularity allowed in "group by" for timeframe ranges
@@ -32,6 +32,7 @@ function isProjectAnalyticsPoint(
export function buildComparisonFetchRequest(
fetchRequest: Labrinth.Analytics.v3.FetchRequest | null,
minStartTime = ANALYTICS_START_TIME,
): AnalyticsProjectFetchRequest | null {
if (!isAnalyticsFetchRequestReady(fetchRequest)) {
return null
@@ -47,7 +48,7 @@ export function buildComparisonFetchRequest(
const previousStart = new Date(startTimestamp - duration)
if (previousStart.getTime() < ANALYTICS_START_TIME) {
if (previousStart.getTime() < minStartTime) {
return null
}
@@ -93,8 +94,12 @@ function getAnalyticsTimeSliceCount(
export function splitAnalyticsTimeSlices(
timeSlices: Labrinth.Analytics.v3.TimeSlice[],
fetchRequest: Labrinth.Analytics.v3.FetchRequest | null,
minStartTime = ANALYTICS_START_TIME,
): AnalyticsTimeSliceSplit {
if (!isAnalyticsFetchRequestReady(fetchRequest) || !buildComparisonFetchRequest(fetchRequest)) {
if (
!isAnalyticsFetchRequestReady(fetchRequest) ||
!buildComparisonFetchRequest(fetchRequest, minStartTime)
) {
return {
currentTimeSlices: timeSlices,
previousTimeSlices: [],
@@ -339,6 +344,7 @@ export function getAnalyticsTimeframeDurationMs({
customStartDate,
customEndDate,
nowTimestamp,
allTimeStartTimestamp = ANALYTICS_START_TIME,
}: {
mode: AnalyticsTimeframeMode
preset: AnalyticsTimeframePreset
@@ -347,6 +353,7 @@ export function getAnalyticsTimeframeDurationMs({
customStartDate: string
customEndDate: string
nowTimestamp: number
allTimeStartTimestamp?: number
}): number {
if (mode === 'preset') {
switch (preset) {
@@ -370,7 +377,7 @@ export function getAnalyticsTimeframeDurationMs({
return now.getTime() - yearStart.getTime()
}
case 'all_time': {
const allTimeDurationMs = nowTimestamp - ANALYTICS_START_TIME
const allTimeDurationMs = nowTimestamp - allTimeStartTimestamp
return Math.max(0, allTimeDurationMs)
}
}
@@ -46,6 +46,7 @@ export function toAnalyticsDashboardProject(
iconUrl: project.icon_url ?? undefined,
downloads: project.downloads ?? 0,
status: getProjectStatusFilterValue(project.status),
publishedAt: project.published ?? undefined,
}
}
@@ -108,6 +108,7 @@ export type AnalyticsDashboardProjectSource = ProjectTypeMetadata & {
icon_url?: string | null
downloads?: number | null
status?: string | null
published?: string | null
}
export type AnalyticsProjectVersionSource = {
@@ -121,6 +122,7 @@ export interface AnalyticsDashboardProject {
iconUrl?: string
downloads: number
status: ProjectStatusFilterValue
publishedAt?: string
}
export interface AnalyticsDashboardProjectGroup {
@@ -36,6 +36,7 @@ import type { OrganizationContext } from '../organization-context'
import {
addVersionIdsFromTimeSlices,
addVersionProjectNamesFromTimeSlices,
ANALYTICS_START_TIME,
areAnalyticsFetchRequestsEqual,
buildAnalyticsCurrentTimeSlicesQueryKey,
buildAnalyticsFacetsRequest,
@@ -68,6 +69,7 @@ import {
sortStringValues,
} from './analytics-filter-utils'
import {
getProjectIdsMatchingStatusFilter,
getProjectOrganizationId,
getSingleQueryValue,
getUniqueAnalyticsDashboardProjects,
@@ -131,6 +133,17 @@ const ANALYTICS_TIME_SLICES_GC_TIME_MS = 30 * 1000
const ANALYTICS_PREFETCH_GC_TIME_MS = 15 * 1000
const ANALYTICS_FILTER_OPTIONS_GC_TIME_MS = 60 * 1000
const ANALYTICS_MOBILE_LAYOUT_QUERY = '(pointer: coarse), (max-width: 800px)'
const ANALYTICS_ALL_TIME_START_OFFSET_MONTHS = 2
function subtractAnalyticsCalendarMonths(date: Date, months: number): Date {
const nextDate = new Date(date)
const day = nextDate.getDate()
nextDate.setDate(1)
nextDate.setMonth(nextDate.getMonth() - months)
const daysInMonth = new Date(nextDate.getFullYear(), nextDate.getMonth() + 1, 0).getDate()
nextDate.setDate(Math.min(day, daysInMonth))
return nextDate
}
function getAnalyticsFetchErrorMessage(error: unknown): string {
if (error && typeof error === 'object') {
@@ -164,6 +177,7 @@ export interface AnalyticsDashboardContextValue {
selectedCustomTimeframeStartDate: Ref<string>
selectedCustomTimeframeEndDate: Ref<string>
selectedGroupBy: Ref<AnalyticsGroupByPreset>
analyticsAllTimeStartDate: ComputedRef<Date>
selectedBreakdowns: Ref<AnalyticsSelectedBreakdowns>
selectedFilters: Ref<AnalyticsSelectedFilters>
queryRefreshTimestamp: Ref<number>
@@ -368,6 +382,7 @@ export function createAnalyticsDashboardContext(
},
enabled: computed(() => shouldFetchEffectiveUser.value && hasCompletedAnalyticsLoading.value),
placeholderData: null,
refetchOnWindowFocus: false,
})
const effectiveUsername = computed(() => {
if (effectiveUserId.value === options.auth.value.user?.id) {
@@ -407,6 +422,7 @@ export function createAnalyticsDashboardContext(
}
},
enabled: shouldFetchDashboardAllProjects,
refetchOnWindowFocus: false,
})
const areProjectsLoaded = computed(() => {
@@ -547,6 +563,63 @@ export function createAnalyticsDashboardContext(
return getAnalyticsVersionIdsFromProjects(projects, sortedSelectedProjectIds.value)
})
const { data: filterOptionProjectVersions, isFetched: hasFetchedFilterOptionProjectVersions } =
useQuery({
queryKey: computed(() => [
'analytics',
'dashboard',
analyticsQueryUserId.value,
'filter-options',
'versions',
filterOptionVersionIds.value,
]),
queryFn: () =>
fetchAnalyticsVersionMetadataByIds(filterOptionVersionIds.value, (ids) =>
client.labrinth.versions_v3.getVersions(ids),
),
enabled: computed(
() =>
filterOptionProjectSources.value !== null && sortedSelectedProjectIds.value.length > 0,
),
placeholderData: [],
gcTime: ANALYTICS_FILTER_OPTIONS_GC_TIME_MS,
refetchOnWindowFocus: false,
})
const projectsById = computed(
() => new Map(projects.value.map((project) => [project.id, project])),
)
const analyticsAllTimeStartDate = computed(() => {
const fallbackStartDate = new Date(ANALYTICS_START_TIME)
const filteredProjectIds = getProjectIdsMatchingStatusFilter(
selectedProjectIds.value.length > 0 ? selectedProjectIds.value : availableProjectIds.value,
projectStatusById.value,
selectedFilters.value,
)
let startTime = Number.POSITIVE_INFINITY
for (const projectId of filteredProjectIds) {
const publishedAt = projectsById.value.get(projectId)?.publishedAt
if (!publishedAt) {
continue
}
const projectStartTime = new Date(publishedAt).getTime()
if (Number.isFinite(projectStartTime)) {
startTime = Math.min(startTime, projectStartTime)
}
}
if (!Number.isFinite(startTime)) {
return fallbackStartDate
}
const offsetStartDate = subtractAnalyticsCalendarMonths(
new Date(startTime),
ANALYTICS_ALL_TIME_START_OFFSET_MONTHS,
)
return new Date(Math.max(offsetStartDate.getTime(), ANALYTICS_START_TIME))
})
const hasExplicitProjectSelectionQuery = computed(() =>
hasAnalyticsProjectSelectionQuery(route.query),
)
@@ -598,6 +671,7 @@ export function createAnalyticsDashboardContext(
customStartDate: selectedCustomTimeframeStartDate.value,
customEndDate: selectedCustomTimeframeEndDate.value,
nowTimestamp: queryRefreshTimestamp.value,
allTimeStartTimestamp: analyticsAllTimeStartDate.value.getTime(),
}) > REVENUE_MIN_TIMEFRAME_MS,
)
@@ -867,7 +941,17 @@ export function createAnalyticsDashboardContext(
{ deep: true },
)
const comparisonFetchRequest = computed(() => buildComparisonFetchRequest(fetchRequest.value))
const analyticsComparisonStartTime = computed(() => {
if (selectedTimeframeMode.value === 'preset' && selectedTimeframe.value === 'all_time') {
const fetchRequestStart = fetchRequest.value?.time_range.start
return new Date(fetchRequestStart ?? analyticsAllTimeStartDate.value).getTime()
}
return ANALYTICS_START_TIME
})
const comparisonFetchRequest = computed(() =>
buildComparisonFetchRequest(fetchRequest.value, analyticsComparisonStartTime.value),
)
const analyticsTimeSlicesFetchRequest = computed(
() => comparisonFetchRequest.value ?? fetchRequest.value,
)
@@ -901,6 +985,7 @@ export function createAnalyticsDashboardContext(
)
},
enabled: computed(() => isAnalyticsFetchRequestReady(analyticsTimeSlicesFetchRequest.value)),
refetchOnWindowFocus: false,
gcTime: ANALYTICS_TIME_SLICES_GC_TIME_MS,
})
watch(currentAnalyticsError, (error) => {
@@ -956,7 +1041,10 @@ export function createAnalyticsDashboardContext(
}
const dailyFetchRequest = buildDailyAnalyticsFetchRequest(fetchRequest.value)
return buildComparisonFetchRequest(dailyFetchRequest) ?? dailyFetchRequest
return (
buildComparisonFetchRequest(dailyFetchRequest, analyticsComparisonStartTime.value) ??
dailyFetchRequest
)
})
watch(
@@ -984,7 +1072,7 @@ export function createAnalyticsDashboardContext(
nextQueryRefreshTimestamp,
),
queryFn: () =>
fetchAnalyticsTimeSlices(nextFetchRequest, (request) =>
fetchAnalyticsData(nextFetchRequest, (request) =>
client.labrinth.analytics_v3.fetch(request),
),
gcTime: ANALYTICS_PREFETCH_GC_TIME_MS,
@@ -1057,6 +1145,7 @@ export function createAnalyticsDashboardContext(
isAnalyticsFetchRequestReady(analyticsFacetsRequest.value),
),
gcTime: ANALYTICS_FILTER_OPTIONS_GC_TIME_MS,
refetchOnWindowFocus: false,
})
const { data: analyticsDownloadCountTimeSlices } = useQuery({
@@ -1086,30 +1175,9 @@ export function createAnalyticsDashboardContext(
),
placeholderData: [],
gcTime: ANALYTICS_FILTER_OPTIONS_GC_TIME_MS,
refetchOnWindowFocus: false,
})
const { data: filterOptionProjectVersions, isFetched: hasFetchedFilterOptionProjectVersions } =
useQuery({
queryKey: computed(() => [
'analytics',
'dashboard',
analyticsQueryUserId.value,
'filter-options',
'versions',
filterOptionVersionIds.value,
]),
queryFn: () =>
fetchAnalyticsVersionMetadataByIds(filterOptionVersionIds.value, (ids) =>
client.labrinth.versions_v3.getVersions(ids),
),
enabled: computed(
() =>
filterOptionProjectSources.value !== null && sortedSelectedProjectIds.value.length > 0,
),
placeholderData: [],
gcTime: ANALYTICS_FILTER_OPTIONS_GC_TIME_MS,
})
const analyticsFacetsFilterOptionSummary = computed(() =>
getAnalyticsFacetsFilterOptionSummary(analyticsFacetsData.value?.facets),
)
@@ -1213,6 +1281,7 @@ export function createAnalyticsDashboardContext(
const splitTimeSlices = splitAnalyticsTimeSlices(
nextAnalyticsData.metrics,
fetchRequest.value,
analyticsComparisonStartTime.value,
)
timeSlices.value = splitTimeSlices.currentTimeSlices
previousTimeSlices.value = splitTimeSlices.previousTimeSlices
@@ -1269,6 +1338,7 @@ export function createAnalyticsDashboardContext(
enabled: computed(() => analyticsVersionIds.value.length > 0),
placeholderData: [],
gcTime: ANALYTICS_FILTER_OPTIONS_GC_TIME_MS,
refetchOnWindowFocus: false,
})
const allVersionMetadata = computed(() => {
@@ -1488,6 +1558,7 @@ export function createAnalyticsDashboardContext(
selectedCustomTimeframeStartDate,
selectedCustomTimeframeEndDate,
selectedGroupBy,
analyticsAllTimeStartDate,
selectedBreakdowns,
selectedFilters,
queryRefreshTimestamp,