mirror of
https://github.com/modrinth/code.git
synced 2026-08-27 01:54:47 +00:00
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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user