fix: NaN cmp-info (#5619)

* fix: NaN cmp-info

* fix: ssr

* fix: lint
This commit is contained in:
Calum H.
2026-03-19 15:26:15 +00:00
committed by GitHub
parent 3b604cfdc0
commit 93c81631a9
4 changed files with 110 additions and 73 deletions

View File

@@ -8,7 +8,7 @@
{{ analytics.error.value }} {{ analytics.error.value }}
</div> </div>
</div> </div>
<div v-else-if="!isInitialized || analytics.loading.value" class="universal-card"> <div v-else-if="analytics.loading.value" class="universal-card">
<h2> <h2>
<span class="label__title">Loading analytics...</span> <span class="label__title">Loading analytics...</span>
</h2> </h2>
@@ -315,6 +315,7 @@ import {
Card, Card,
DropdownSelect, DropdownSelect,
useCompactNumber, useCompactNumber,
useDebugLogger,
useFormatMoney, useFormatMoney,
useFormatNumber, useFormatNumber,
} from '@modrinth/ui' } from '@modrinth/ui'
@@ -332,6 +333,7 @@ import {
intToRgba, intToRgba,
} from '~/utils/analytics.js' } from '~/utils/analytics.js'
const debug = useDebugLogger('ChartDisplay')
const formatNumber = useFormatNumber() const formatNumber = useFormatNumber()
const { formatCompactNumber } = useCompactNumber() const { formatCompactNumber } = useCompactNumber()
const formatMoney = useFormatMoney() const formatMoney = useFormatMoney()
@@ -339,6 +341,8 @@ const formatMoney = useFormatMoney()
const router = useNativeRouter() const router = useNativeRouter()
const theme = useTheme() const theme = useTheme()
debug('setup start', { server: import.meta.server, client: import.meta.client })
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
projects?: any[] projects?: any[]
@@ -359,6 +363,11 @@ const props = withDefaults(
const projects = computed(() => props.projects || []) const projects = computed(() => props.projects || [])
debug('projects from props', {
count: projects.value.length,
ids: projects.value.map((p: any) => p.id),
})
// const selectedChart = ref('downloads') // const selectedChart = ref('downloads')
const selectedChart = computed({ const selectedChart = computed({
get: () => { get: () => {
@@ -438,46 +447,47 @@ const isUsingProjectColors = computed({
}, },
}) })
const startDate = ref(dayjs().startOf('day')) const defaultRange = props.ranges.find(
const endDate = ref(dayjs().endOf('day')) (r) => r.getLabel([dayjs(), dayjs()]) === 'Previous 30 days',
const timeResolution = ref(30) )!
const isInitialized = ref(false) const initialDates = defaultRange.getDates(dayjs())
const internalRange: Ref<RangeObject> = ref(defaultRange)
const startDate = ref(initialDates.startDate)
const endDate = ref(initialDates.endDate)
const timeResolution = ref(defaultRange.timeResolution)
debug('default range initialized', {
range: defaultRange.getLabel([dayjs(), dayjs()]),
startDate: startDate.value.toISOString(),
endDate: endDate.value.toISOString(),
timeResolution: timeResolution.value,
})
onBeforeMount(() => { onBeforeMount(() => {
// Load cached data and range from localStorage - cache. debug('onBeforeMount')
if (import.meta.client) { if (import.meta.client) {
const rangeLabel = localStorage.getItem('analyticsSelectedRange') const rangeLabel = localStorage.getItem('analyticsSelectedRange')
debug('localStorage range', { rangeLabel })
if (rangeLabel) { if (rangeLabel) {
const range = props.ranges.find((r) => r.getLabel([dayjs(), dayjs()]) === rangeLabel)! const range = props.ranges.find((r) => r.getLabel([dayjs(), dayjs()]) === rangeLabel)
if (range !== undefined) { if (range) {
internalRange.value = range internalRange.value = range
const ranges = range.getDates(dayjs()) const dates = range.getDates(dayjs())
timeResolution.value = range.timeResolution timeResolution.value = range.timeResolution
startDate.value = ranges.startDate startDate.value = dates.startDate
endDate.value = ranges.endDate endDate.value = dates.endDate
debug('range overridden from localStorage', {
startDate: dates.startDate.toISOString(),
endDate: dates.endDate.toISOString(),
timeResolution: range.timeResolution,
})
} }
} }
} }
}) })
onMounted(() => {
if (internalRange.value === null) {
internalRange.value = props.ranges.find(
(r) => r.getLabel([dayjs(), dayjs()]) === 'Previous 30 days',
)!
}
const ranges = selectedRange.value.getDates(dayjs())
startDate.value = ranges.startDate
endDate.value = ranges.endDate
timeResolution.value = selectedRange.value.timeResolution
isInitialized.value = true
})
const internalRange: Ref<RangeObject> = ref(null as unknown as RangeObject)
const selectedRange = computed({ const selectedRange = computed({
get: () => { get: () => {
return internalRange.value return internalRange.value
@@ -499,6 +509,7 @@ const selectedRange = computed({
}, },
}) })
debug('calling useFetchAllAnalytics')
const analytics = useFetchAllAnalytics( const analytics = useFetchAllAnalytics(
resetCharts, resetCharts,
projects, projects,
@@ -507,9 +518,15 @@ const analytics = useFetchAllAnalytics(
startDate, startDate,
endDate, endDate,
timeResolution, timeResolution,
isInitialized,
) )
debug('awaiting analytics.fetch()')
await analytics.fetch()
debug('analytics.fetch() resolved', {
loading: analytics.loading.value,
error: analytics.error.value,
})
const formattedCategorySubtitle = computed(() => { const formattedCategorySubtitle = computed(() => {
return ( return (
selectedRange.value?.getLabel([dayjs(startDate.value), dayjs(endDate.value)]) ?? 'Loading...' selectedRange.value?.getLabel([dayjs(startDate.value), dayjs(endDate.value)]) ?? 'Loading...'

View File

@@ -1,15 +1,23 @@
<template> <template>
<div> <div>
<ChartDisplay :projects="projects ?? undefined" :personal="true" /> <Suspense>
<ChartDisplay :projects="projects" :personal="true" />
<template #fallback>
<div class="universal-card">
<h2><span class="label__title">Loading analytics...</span></h2>
</div>
</template>
</Suspense>
</div> </div>
</template> </template>
<script setup> <script setup>
import { injectModrinthClient } from '@modrinth/ui' import { injectModrinthClient, useDebugLogger } from '@modrinth/ui'
import { useQuery } from '@tanstack/vue-query'
import ChartDisplay from '~/components/ui/charts/ChartDisplay.vue' import ChartDisplay from '~/components/ui/charts/ChartDisplay.vue'
const debug = useDebugLogger('analytics.vue')
definePageMeta({ definePageMeta({
middleware: 'auth', middleware: 'auth',
}) })
@@ -22,9 +30,9 @@ const auth = await useAuth()
const client = injectModrinthClient() const client = injectModrinthClient()
const id = auth.value?.user?.id const id = auth.value?.user?.id
const { data: projects } = useQuery({ debug('auth resolved', { id })
queryKey: computed(() => ['user', id, 'projects']),
queryFn: () => client.labrinth.users_v2.getProjects(id), const projects = await client.labrinth.users_v2.getProjects(id)
enabled: computed(() => !!id),
}) debug('projects resolved', { count: projects?.length })
</script> </script>

View File

@@ -195,7 +195,7 @@ const { data: transparencyInformation } = useQuery({
queryFn: () => client.labrinth.payouts_v3.getPlatformRevenue(), queryFn: () => client.labrinth.payouts_v3.getPlatformRevenue(),
}) })
const platformRevenue = computed(() => (transparencyInformation.value as any)?.all_time) const platformRevenue = computed(() => Number((transparencyInformation.value as any)?.all_time))
const platformRevenueData = computed( const platformRevenueData = computed(
() => (transparencyInformation.value as any)?.data?.slice(0, 5) ?? [], () => (transparencyInformation.value as any)?.data?.slice(0, 5) ?? [],
) )

View File

@@ -1,4 +1,4 @@
import { injectI18n } from '@modrinth/ui' import { injectI18n, useDebugLogger } from '@modrinth/ui'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
@@ -314,8 +314,15 @@ export const useFetchAllAnalytics = (
startDate = ref(dayjs().subtract(30, 'days')), startDate = ref(dayjs().subtract(30, 'days')),
endDate = ref(dayjs()), endDate = ref(dayjs()),
timeResolution = ref(1440), timeResolution = ref(1440),
isInitialized = ref(false),
) => { ) => {
const debug = useDebugLogger('useFetchAllAnalytics')
debug('init', {
projectCount: projects.value?.length,
personalRevenue,
startDate: startDate.value?.toISOString(),
endDate: endDate.value?.toISOString(),
})
const downloadData = ref(null) const downloadData = ref(null)
const viewData = ref(null) const viewData = ref(null)
const revenueData = ref(null) const revenueData = ref(null)
@@ -340,7 +347,22 @@ export const useFetchAllAnalytics = (
revenue: processRevAnalytics(revenueData.value, projects.value, theme.active), revenue: processRevAnalytics(revenueData.value, projects.value, theme.active),
})) }))
const buildQuery = () => {
const q = {
start_date: startDate.value.toISOString(),
end_date: endDate.value.toISOString(),
resolution_minutes: timeResolution.value,
}
if (projects.value?.length) {
q.project_ids = JSON.stringify(projects.value.map((p) => p.id))
}
return q
}
const fetchData = async (query) => { const fetchData = async (query) => {
debug('fetchData called', { query })
const normalQuery = new URLSearchParams(query) const normalQuery = new URLSearchParams(query)
const revenueQuery = new URLSearchParams(query) const revenueQuery = new URLSearchParams(query)
@@ -355,6 +377,7 @@ export const useFetchAllAnalytics = (
loading.value = true loading.value = true
error.value = null error.value = null
debug('fetching all 5 endpoints...')
const responses = await Promise.all([ const responses = await Promise.all([
useFetchAnalytics(`analytics/downloads?${qs}`), useFetchAnalytics(`analytics/downloads?${qs}`),
useFetchAnalytics(`analytics/views?${qs}`), useFetchAnalytics(`analytics/views?${qs}`),
@@ -362,16 +385,21 @@ export const useFetchAllAnalytics = (
useFetchAnalytics(`analytics/countries/downloads?${qs}`), useFetchAnalytics(`analytics/countries/downloads?${qs}`),
useFetchAnalytics(`analytics/countries/views?${qs}`), useFetchAnalytics(`analytics/countries/views?${qs}`),
]) ])
debug('all 5 endpoints resolved', {
downloads: Object.keys(responses[0] || {}).length,
views: Object.keys(responses[1] || {}).length,
revenue: Object.keys(responses[2] || {}).length,
})
// collect project ids from projects.value into a set
const projectIds = new Set() const projectIds = new Set()
if (projects.value) { if (projects.value) {
projects.value.forEach((p) => projectIds.add(p.id)) projects.value.forEach((p) => projectIds.add(p.id))
} else { } else {
// if projects.value is not set, we assume that we want all project ids
Object.keys(responses[0] || {}).forEach((id) => projectIds.add(id)) Object.keys(responses[0] || {}).forEach((id) => projectIds.add(id))
} }
debug('filtering to projectIds', { count: projectIds.size })
const filterProjectIds = (data) => { const filterProjectIds = (data) => {
const filtered = {} const filtered = {}
Object.entries(data).forEach(([id, values]) => { Object.entries(data).forEach(([id, values]) => {
@@ -389,43 +417,27 @@ export const useFetchAllAnalytics = (
downloadsByCountry.value = responses[3] || {} downloadsByCountry.value = responses[3] || {}
viewsByCountry.value = responses[4] || {} viewsByCountry.value = responses[4] || {}
} catch (e) { } catch (e) {
debug('fetchData error', e)
error.value = e error.value = e
} finally { } finally {
loading.value = false loading.value = false
debug('fetchData done, loading=false')
}
}
const fetch = async () => {
debug('fetch() called', { projectCount: projects.value?.length })
await fetchData(buildQuery())
if (onDataRefresh) {
onDataRefresh()
} }
} }
watch( watch(
[ [() => startDate.value, () => endDate.value, () => timeResolution.value, () => projects.value],
() => startDate.value, (newVals, oldVals) => {
() => endDate.value, debug('watch triggered', { new: newVals, old: oldVals })
() => timeResolution.value, fetch()
() => projects.value,
() => isInitialized.value,
],
async () => {
if (!isInitialized.value) {
return
}
const q = {
start_date: startDate.value.toISOString(),
end_date: endDate.value.toISOString(),
resolution_minutes: timeResolution.value,
}
if (projects.value?.length) {
q.project_ids = JSON.stringify(projects.value.map((p) => p.id))
}
await fetchData(q)
if (onDataRefresh) {
onDataRefresh()
}
},
{
immediate: true,
}, },
) )
@@ -474,6 +486,6 @@ export const useFetchAllAnalytics = (
totalData, totalData,
loading, loading,
error, error,
isInitialized, fetch,
} }
} }