You've already forked AstralRinth
fix: download threshold (#6242)
* fix: download threshold * fix: download threshold for projects select * refactor: pnpm prepr * feat: handle facets not adding count * feat: remove getting facets download count field entirely * feat: update facets to match new backend shape
This commit is contained in:
@@ -232,7 +232,13 @@ import {
|
||||
type AnalyticsFilterValueCategory = Exclude<AnalyticsQueryFilterCategory, 'project'>
|
||||
type GameVersionType = 'release' | 'all'
|
||||
type SetDropdownFilterValues = (values: string[]) => void
|
||||
type ApplyDownloadsThreshold = (setSelectedValues: SetDropdownFilterValues) => void
|
||||
type DownloadsThresholdSelection = {
|
||||
categoryKey: DownloadsThresholdFilterCategory
|
||||
selectedValues: string[]
|
||||
}
|
||||
type ApplyDownloadsThreshold = (
|
||||
setSelectedValues: SetDropdownFilterValues,
|
||||
) => DownloadsThresholdSelection | null
|
||||
type CloseDownloadsThresholdMenu = (event?: Event) => void
|
||||
|
||||
const props = withDefaults(
|
||||
@@ -763,48 +769,46 @@ function compareOptionalDateStringsDescending(
|
||||
function applyGameVersionDownloadsThreshold(setSelectedValues: SetDropdownFilterValues) {
|
||||
const threshold = gameVersionDownloadsThreshold.value
|
||||
if (threshold === null) {
|
||||
return
|
||||
return null
|
||||
}
|
||||
|
||||
const selectedValues = gameVersionFilterOptions.value
|
||||
.filter((gameVersion) => {
|
||||
return (gameVersionDownloadsByVersion.value.get(gameVersion.value) ?? 0) >= threshold
|
||||
return (gameVersionDownloadsByVersion.value.get(gameVersion.value) ?? 0) > threshold
|
||||
})
|
||||
.map((gameVersion) => gameVersion.value)
|
||||
|
||||
setDownloadsThresholdSelectedValues('game_version', selectedValues, setSelectedValues)
|
||||
return setDownloadsThresholdSelectedValues('game_version', selectedValues, setSelectedValues)
|
||||
}
|
||||
|
||||
function applyCountryDownloadsThreshold(setSelectedValues: SetDropdownFilterValues) {
|
||||
const threshold = countryDownloadsThreshold.value
|
||||
if (threshold === null) {
|
||||
return
|
||||
return null
|
||||
}
|
||||
|
||||
const selectedValues = countryFilterOptions.value
|
||||
.filter((country) => {
|
||||
return (
|
||||
(countryDownloadsByCode.value.get(country.value.trim().toUpperCase()) ?? 0) >= threshold
|
||||
)
|
||||
return (countryDownloadsByCode.value.get(country.value.trim().toUpperCase()) ?? 0) > threshold
|
||||
})
|
||||
.map((country) => country.value)
|
||||
|
||||
setDownloadsThresholdSelectedValues('country', selectedValues, setSelectedValues)
|
||||
return setDownloadsThresholdSelectedValues('country', selectedValues, setSelectedValues)
|
||||
}
|
||||
|
||||
function applyProjectVersionDownloadsThreshold(setSelectedValues: SetDropdownFilterValues) {
|
||||
const threshold = projectVersionDownloadsThreshold.value
|
||||
if (threshold === null) {
|
||||
return
|
||||
return null
|
||||
}
|
||||
|
||||
const selectedValues = projectVersionFilterOptions.value
|
||||
.filter((version) => {
|
||||
return (projectVersionDownloadsById.value.get(version.value) ?? 0) >= threshold
|
||||
return (projectVersionDownloadsById.value.get(version.value) ?? 0) > threshold
|
||||
})
|
||||
.map((version) => version.value)
|
||||
|
||||
setDownloadsThresholdSelectedValues('version_id', selectedValues, setSelectedValues)
|
||||
return setDownloadsThresholdSelectedValues('version_id', selectedValues, setSelectedValues)
|
||||
}
|
||||
|
||||
function setCountryDownloadsThreshold(
|
||||
@@ -876,12 +880,18 @@ function setDownloadsThresholdSelectedValues(
|
||||
categoryKey: DownloadsThresholdFilterCategory,
|
||||
selectedValues: string[],
|
||||
setSelectedValues: SetDropdownFilterValues,
|
||||
) {
|
||||
): DownloadsThresholdSelection {
|
||||
const normalizedSelectedValues = normalizeSelectedFilterValues(categoryKey, selectedValues, [])
|
||||
downloadsThresholdSelections.value = {
|
||||
...downloadsThresholdSelections.value,
|
||||
[categoryKey]: normalizeSelectedFilterValues(categoryKey, selectedValues, []),
|
||||
[categoryKey]: normalizedSelectedValues,
|
||||
}
|
||||
setSelectedValues(selectedValues)
|
||||
|
||||
return {
|
||||
categoryKey,
|
||||
selectedValues: normalizedSelectedValues,
|
||||
}
|
||||
}
|
||||
|
||||
function clearDownloadsThreshold(categoryKey: DownloadsThresholdFilterCategory) {
|
||||
@@ -923,8 +933,13 @@ async function runDownloadsThresholdQuery(
|
||||
closeMenu: CloseDownloadsThresholdMenu,
|
||||
event?: KeyboardEvent,
|
||||
) {
|
||||
applyDownloadsThreshold(setSelectedValues)
|
||||
const selection = applyDownloadsThreshold(setSelectedValues)
|
||||
closeMenu(event)
|
||||
if (selection) {
|
||||
const nextFilters = cloneSelectedFilters(draftSelectedFilters.value)
|
||||
nextFilters[selection.categoryKey] = selection.selectedValues
|
||||
draftSelectedFilters.value = nextFilters
|
||||
}
|
||||
await scheduleSelectedFiltersCommit()
|
||||
await refreshAnalyticsQuery()
|
||||
}
|
||||
|
||||
@@ -753,7 +753,7 @@ function applyProjectDownloadsThreshold(threshold: number | null) {
|
||||
}
|
||||
|
||||
const projectIds = projects.value
|
||||
.filter((project) => (projectDownloadsById.value.get(project.id) ?? 0) >= threshold)
|
||||
.filter((project) => (projectDownloadsById.value.get(project.id) ?? 0) > threshold)
|
||||
.map((project) => project.id)
|
||||
|
||||
projectDownloadsThresholdProjectIds.value = projectIds
|
||||
|
||||
@@ -476,6 +476,85 @@ export function computeTotals(
|
||||
return totals
|
||||
}
|
||||
|
||||
export function getProjectDownloadsByIdFromTimeSlices(
|
||||
timeSlices: Labrinth.Analytics.v3.TimeSlice[],
|
||||
): Map<string, number> {
|
||||
const projectDownloadsById = new Map<string, number>()
|
||||
|
||||
for (const timeSlice of timeSlices) {
|
||||
for (const dataPoint of timeSlice) {
|
||||
if (!isProjectAnalyticsPoint(dataPoint) || dataPoint.metric_kind !== 'downloads') {
|
||||
continue
|
||||
}
|
||||
|
||||
projectDownloadsById.set(
|
||||
dataPoint.source_project,
|
||||
(projectDownloadsById.get(dataPoint.source_project) ?? 0) + dataPoint.downloads,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return projectDownloadsById
|
||||
}
|
||||
|
||||
function getDownloadFieldCountsFromTimeSlices(
|
||||
timeSlices: Labrinth.Analytics.v3.TimeSlice[],
|
||||
getKey: (
|
||||
dataPoint: Extract<Labrinth.Analytics.v3.ProjectMetrics, { metric_kind: 'downloads' }>,
|
||||
) => string | null | undefined,
|
||||
): Map<string, number> {
|
||||
const downloadsByValue = new Map<string, number>()
|
||||
|
||||
for (const timeSlice of timeSlices) {
|
||||
for (const dataPoint of timeSlice) {
|
||||
if (!isProjectAnalyticsPoint(dataPoint) || dataPoint.metric_kind !== 'downloads') {
|
||||
continue
|
||||
}
|
||||
|
||||
const key = getKey(dataPoint)?.trim()
|
||||
if (!key) {
|
||||
continue
|
||||
}
|
||||
|
||||
downloadsByValue.set(key, (downloadsByValue.get(key) ?? 0) + dataPoint.downloads)
|
||||
}
|
||||
}
|
||||
|
||||
return downloadsByValue
|
||||
}
|
||||
|
||||
export function getProjectVersionDownloadsByIdFromTimeSlices(
|
||||
timeSlices: Labrinth.Analytics.v3.TimeSlice[],
|
||||
): Map<string, number> {
|
||||
return getDownloadFieldCountsFromTimeSlices(timeSlices, (dataPoint) => dataPoint.version_id)
|
||||
}
|
||||
|
||||
export function getGameVersionDownloadsByVersionFromTimeSlices(
|
||||
timeSlices: Labrinth.Analytics.v3.TimeSlice[],
|
||||
): Map<string, number> {
|
||||
return getDownloadFieldCountsFromTimeSlices(timeSlices, (dataPoint) => dataPoint.game_version)
|
||||
}
|
||||
|
||||
export function getCountryDownloadsByCodeFromTimeSlices(
|
||||
timeSlices: Labrinth.Analytics.v3.TimeSlice[],
|
||||
): Map<string, number> {
|
||||
const countryDownloadsByCode = new Map<string, number>()
|
||||
const downloadsByCountry = getDownloadFieldCountsFromTimeSlices(
|
||||
timeSlices,
|
||||
(dataPoint) => dataPoint.country,
|
||||
)
|
||||
|
||||
for (const [country, downloads] of downloadsByCountry.entries()) {
|
||||
const countryCode = country.toUpperCase()
|
||||
countryDownloadsByCode.set(
|
||||
countryCode,
|
||||
(countryDownloadsByCode.get(countryCode) ?? 0) + downloads,
|
||||
)
|
||||
}
|
||||
|
||||
return countryDownloadsByCode
|
||||
}
|
||||
|
||||
export function cloneAnalyticsFetchRequest(
|
||||
fetchRequest: Labrinth.Analytics.v3.FetchRequest | null,
|
||||
): Labrinth.Analytics.v3.FetchRequest | null {
|
||||
|
||||
@@ -174,28 +174,8 @@ function getEmptyAnalyticsFacetsFilterOptionSummary(): AnalyticsFacetsFilterOpti
|
||||
}
|
||||
}
|
||||
|
||||
function getAnalyticsFacetValues<T>(
|
||||
facets: Labrinth.Analytics.v3.AnalyticsFacet<T>[] | null | undefined,
|
||||
): T[] {
|
||||
return facets?.map((facet) => facet.value) ?? []
|
||||
}
|
||||
|
||||
function getAnalyticsFacetDownloadsByValue<T>(
|
||||
facets: Labrinth.Analytics.v3.AnalyticsFacet<T>[] | null | undefined,
|
||||
getKey: (value: T) => string,
|
||||
): Map<string, number> {
|
||||
const downloadsByValue = new Map<string, number>()
|
||||
for (const facet of facets ?? []) {
|
||||
const key = getKey(facet.value)
|
||||
if (key.length === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
const downloads = Number.isFinite(facet.downloads) ? facet.downloads : 0
|
||||
downloadsByValue.set(key, (downloadsByValue.get(key) ?? 0) + downloads)
|
||||
}
|
||||
|
||||
return downloadsByValue
|
||||
function getAnalyticsFacetValues<T>(facets: T[] | null | undefined): T[] {
|
||||
return facets ? [...facets] : []
|
||||
}
|
||||
|
||||
export function getAnalyticsFacetsFilterOptionSummary(
|
||||
@@ -205,15 +185,18 @@ export function getAnalyticsFacetsFilterOptionSummary(
|
||||
return getEmptyAnalyticsFacetsFilterOptionSummary()
|
||||
}
|
||||
|
||||
const downloadCountries = getAnalyticsFacetValues(facets.project_downloads.country)
|
||||
const downloadGameVersions = getAnalyticsFacetValues(facets.project_downloads.game_version)
|
||||
const downloadLoaders = getAnalyticsFacetValues(facets.project_downloads.loader)
|
||||
const downloadVersionIds = getAnalyticsFacetValues(facets.project_downloads.version_id)
|
||||
const viewCountries = getAnalyticsFacetValues(facets.project_views.country)
|
||||
const playtimeCountries = getAnalyticsFacetValues(facets.project_playtime.country)
|
||||
const playtimeGameVersions = getAnalyticsFacetValues(facets.project_playtime.game_version)
|
||||
const playtimeLoaders = getAnalyticsFacetValues(facets.project_playtime.loader)
|
||||
const playtimeVersionIds = getAnalyticsFacetValues(facets.project_playtime.version_id)
|
||||
const projectDownloadFacets = facets.project_downloads
|
||||
const projectViewFacets = facets.project_views
|
||||
const projectPlaytimeFacets = facets.project_playtime
|
||||
const downloadCountries = getAnalyticsFacetValues(projectDownloadFacets?.country)
|
||||
const downloadGameVersions = getAnalyticsFacetValues(projectDownloadFacets?.game_version)
|
||||
const downloadLoaders = getAnalyticsFacetValues(projectDownloadFacets?.loader)
|
||||
const downloadVersionIds = getAnalyticsFacetValues(projectDownloadFacets?.version_id)
|
||||
const viewCountries = getAnalyticsFacetValues(projectViewFacets?.country)
|
||||
const playtimeCountries = getAnalyticsFacetValues(projectPlaytimeFacets?.country)
|
||||
const playtimeGameVersions = getAnalyticsFacetValues(projectPlaytimeFacets?.game_version)
|
||||
const playtimeLoaders = getAnalyticsFacetValues(projectPlaytimeFacets?.loader)
|
||||
const playtimeVersionIds = getAnalyticsFacetValues(projectPlaytimeFacets?.version_id)
|
||||
const countries = new Set([...viewCountries, ...downloadCountries, ...playtimeCountries])
|
||||
const gameVersions = new Set([...downloadGameVersions, ...playtimeGameVersions])
|
||||
const loaderTypes = new Set<string>()
|
||||
@@ -230,8 +213,8 @@ export function getAnalyticsFacetsFilterOptionSummary(
|
||||
.map((country) => country.trim().toUpperCase())
|
||||
.filter((country) => country.length > 0),
|
||||
),
|
||||
downloadSources: sortStringValues(getAnalyticsFacetValues(facets.project_downloads.user_agent)),
|
||||
downloadReasons: sortStringValues(getAnalyticsFacetValues(facets.project_downloads.reason)),
|
||||
downloadSources: sortStringValues(getAnalyticsFacetValues(projectDownloadFacets?.user_agent)),
|
||||
downloadReasons: sortStringValues(getAnalyticsFacetValues(projectDownloadFacets?.reason)),
|
||||
gameVersions: sortStringValues(
|
||||
[...gameVersions]
|
||||
.map((gameVersion) => gameVersion.trim())
|
||||
@@ -239,22 +222,10 @@ export function getAnalyticsFacetsFilterOptionSummary(
|
||||
),
|
||||
loaderTypes: sortStringValues([...loaderTypes]),
|
||||
versionIds: sortStringValues([...new Set([...downloadVersionIds, ...playtimeVersionIds])]),
|
||||
projectDownloadsById: getAnalyticsFacetDownloadsByValue(
|
||||
facets.project_downloads.project_id,
|
||||
(projectId) => projectId.trim(),
|
||||
),
|
||||
projectVersionDownloadsById: getAnalyticsFacetDownloadsByValue(
|
||||
facets.project_downloads.version_id,
|
||||
(versionId) => versionId.trim(),
|
||||
),
|
||||
gameVersionDownloadsByVersion: getAnalyticsFacetDownloadsByValue(
|
||||
facets.project_downloads.game_version,
|
||||
(gameVersion) => gameVersion.trim(),
|
||||
),
|
||||
countryDownloadsByCode: getAnalyticsFacetDownloadsByValue(
|
||||
facets.project_downloads.country,
|
||||
(country) => country.trim().toUpperCase(),
|
||||
),
|
||||
projectDownloadsById: new Map(),
|
||||
projectVersionDownloadsById: new Map(),
|
||||
gameVersionDownloadsByVersion: new Map(),
|
||||
countryDownloadsByCode: new Map(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,11 @@ import {
|
||||
fetchAnalyticsTimeSlices,
|
||||
getAnalyticsProjectEventsInTimeRange,
|
||||
getAnalyticsTimeframeDurationMs,
|
||||
getCountryDownloadsByCodeFromTimeSlices,
|
||||
getGameVersionDownloadsByVersionFromTimeSlices,
|
||||
getPercentChange,
|
||||
getProjectDownloadsByIdFromTimeSlices,
|
||||
getProjectVersionDownloadsByIdFromTimeSlices,
|
||||
isAnalyticsFetchRequestReady,
|
||||
isRevenueHourlyGroupBy,
|
||||
REVENUE_MIN_TIMEFRAME_MS,
|
||||
@@ -1017,6 +1021,35 @@ export function createAnalyticsDashboardContext(
|
||||
gcTime: ANALYTICS_FILTER_OPTIONS_GC_TIME_MS,
|
||||
})
|
||||
|
||||
const { data: analyticsDownloadCountTimeSlices } = useQuery({
|
||||
queryKey: computed(() => [
|
||||
'analytics',
|
||||
'dashboard',
|
||||
analyticsQueryUserId.value,
|
||||
'filter-options',
|
||||
'download-counts-fallback',
|
||||
analyticsFacetsRequest.value,
|
||||
queryRefreshTimestamp.value,
|
||||
]),
|
||||
queryFn: () => {
|
||||
const nextRequest = analyticsFacetsRequest.value
|
||||
if (!isAnalyticsFetchRequestReady(nextRequest)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return fetchAnalyticsTimeSlices(nextRequest, (request) =>
|
||||
client.labrinth.analytics_v3.fetch(request),
|
||||
)
|
||||
},
|
||||
enabled: computed(
|
||||
() =>
|
||||
hasFetchedAnalyticsFilterOptions.value &&
|
||||
isAnalyticsFetchRequestReady(analyticsFacetsRequest.value),
|
||||
),
|
||||
placeholderData: [],
|
||||
gcTime: ANALYTICS_FILTER_OPTIONS_GC_TIME_MS,
|
||||
})
|
||||
|
||||
const { data: filterOptionProjectVersions, isFetched: hasFetchedFilterOptionProjectVersions } =
|
||||
useQuery({
|
||||
queryKey: computed(() => [
|
||||
@@ -1245,17 +1278,21 @@ export function createAnalyticsDashboardContext(
|
||||
}
|
||||
return versionProjectIconUrls
|
||||
})
|
||||
const projectDownloadsById = computed(
|
||||
() => analyticsFacetsFilterOptionSummary.value.projectDownloadsById,
|
||||
const downloadCountTimeSlices = computed(() => {
|
||||
const countTimeSlices = analyticsDownloadCountTimeSlices.value ?? []
|
||||
return countTimeSlices.length > 0 ? countTimeSlices : timeSlices.value
|
||||
})
|
||||
const projectDownloadsById = computed(() =>
|
||||
getProjectDownloadsByIdFromTimeSlices(downloadCountTimeSlices.value),
|
||||
)
|
||||
const projectVersionDownloadsById = computed(
|
||||
() => analyticsFacetsFilterOptionSummary.value.projectVersionDownloadsById,
|
||||
const projectVersionDownloadsById = computed(() =>
|
||||
getProjectVersionDownloadsByIdFromTimeSlices(downloadCountTimeSlices.value),
|
||||
)
|
||||
const countryDownloadsByCode = computed(
|
||||
() => analyticsFacetsFilterOptionSummary.value.countryDownloadsByCode,
|
||||
const countryDownloadsByCode = computed(() =>
|
||||
getCountryDownloadsByCodeFromTimeSlices(downloadCountTimeSlices.value),
|
||||
)
|
||||
const gameVersionDownloadsByVersion = computed(
|
||||
() => analyticsFacetsFilterOptionSummary.value.gameVersionDownloadsByVersion,
|
||||
const gameVersionDownloadsByVersion = computed(() =>
|
||||
getGameVersionDownloadsByVersionFromTimeSlices(downloadCountTimeSlices.value),
|
||||
)
|
||||
|
||||
const selectedProjectIdSet = computed(() => new Set(selectedProjectIds.value))
|
||||
|
||||
@@ -375,41 +375,36 @@ export namespace Labrinth {
|
||||
facets: AnalyticsFacets
|
||||
}
|
||||
|
||||
export type AnalyticsFacet<T> = {
|
||||
value: T
|
||||
downloads: number
|
||||
}
|
||||
|
||||
export type AnalyticsFacets = {
|
||||
project_views: ProjectViewsFacets
|
||||
project_downloads: ProjectDownloadsFacets
|
||||
project_playtime: ProjectPlaytimeFacets
|
||||
project_views?: Partial<ProjectViewsFacets>
|
||||
project_downloads?: Partial<ProjectDownloadsFacets>
|
||||
project_playtime?: Partial<ProjectPlaytimeFacets>
|
||||
}
|
||||
|
||||
export type ProjectViewsFacets = {
|
||||
domain: AnalyticsFacet<string>[]
|
||||
site_path: AnalyticsFacet<string>[]
|
||||
monetized: AnalyticsFacet<boolean>[]
|
||||
country: AnalyticsFacet<string>[]
|
||||
domain: string[]
|
||||
site_path: string[]
|
||||
monetized: boolean[]
|
||||
country: string[]
|
||||
}
|
||||
|
||||
export type ProjectDownloadsFacets = {
|
||||
project_id: AnalyticsFacet<string>[]
|
||||
domain: AnalyticsFacet<string>[]
|
||||
user_agent: AnalyticsFacet<string>[]
|
||||
version_id: AnalyticsFacet<string>[]
|
||||
monetized: AnalyticsFacet<boolean>[]
|
||||
country: AnalyticsFacet<string>[]
|
||||
reason: AnalyticsFacet<DownloadReason>[]
|
||||
game_version: AnalyticsFacet<string>[]
|
||||
loader: AnalyticsFacet<string>[]
|
||||
project_id: string[]
|
||||
domain: string[]
|
||||
user_agent: string[]
|
||||
version_id: string[]
|
||||
monetized: boolean[]
|
||||
country: string[]
|
||||
reason: DownloadReason[]
|
||||
game_version: string[]
|
||||
loader: string[]
|
||||
}
|
||||
|
||||
export type ProjectPlaytimeFacets = {
|
||||
version_id: AnalyticsFacet<string>[]
|
||||
loader: AnalyticsFacet<string>[]
|
||||
game_version: AnalyticsFacet<string>[]
|
||||
country: AnalyticsFacet<string>[]
|
||||
version_id: string[]
|
||||
loader: string[]
|
||||
game_version: string[]
|
||||
country: string[]
|
||||
}
|
||||
|
||||
export type TimeSlice = AnalyticsData[]
|
||||
|
||||
Reference in New Issue
Block a user