Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export function formatBreakdownLabel(
breakdownValue: string,
selectedBreakdown: AnalyticsBreakdownPreset,
getVersionDisplayName: ((versionId: string) => string) | undefined,
userNamesById: ReadonlyMap<string, string> | undefined,
formatMessage: FormatMessage,
): string {
const normalizedValue = breakdownValue.trim()
Expand Down Expand Up @@ -173,6 +174,9 @@ export function formatBreakdownLabel(
}
return breakdownValue
}
if (selectedBreakdown === 'user_id') {
return userNamesById?.get(breakdownValue) ?? breakdownValue
}
if (selectedBreakdown === 'version_id') {
return getVersionDisplayName?.(breakdownValue) ?? breakdownValue
}
Expand All @@ -187,6 +191,7 @@ export function formatBreakdownLabels(
breakdownValues: readonly string[],
selectedBreakdowns: readonly AnalyticsBreakdownPreset[],
getVersionDisplayName: ((versionId: string) => string) | undefined,
userNamesById: ReadonlyMap<string, string> | undefined,
formatMessage: FormatMessage,
): string {
const normalizedBreakdowns = selectedBreakdowns.filter((breakdown) => breakdown !== 'none')
Expand All @@ -202,6 +207,7 @@ export function formatBreakdownLabels(
) {
return formatAnalyticsDependentProjectFallbackLabel(
breakdownValues[downloadReasonBreakdownIndex],
userNamesById,
formatMessage,
)
}
Expand Down Expand Up @@ -371,6 +377,7 @@ export function buildChartDatasets(
selectedFilters: AnalyticsSelectedFilters,
dependentProjectTypesById: ReadonlyMap<string, readonly string[]>,
projectNamesById: ReadonlyMap<string, string>,
userNamesById: ReadonlyMap<string, string>,
getVersionDisplayName: ((versionId: string) => string) | undefined,
getVersionProjectName: ((versionId: string) => string | undefined) | undefined,
formatMessage: FormatMessage,
Expand Down Expand Up @@ -413,7 +420,13 @@ export function buildChartDatasets(
return projectNamesById.get(breakdownValue) ?? breakdownValue
}

return formatBreakdownLabel(breakdownValue, breakdown, getVersionDisplayName, formatMessage)
return formatBreakdownLabel(
breakdownValue,
breakdown,
getVersionDisplayName,
userNamesById,
formatMessage,
)
}),
formatMessage,
).join(COMBINED_BREAKDOWN_LABEL_SEPARATOR)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export function useAnalyticsChartDatasets(
| 'defaultGraphDatasetIds'
| 'topGraphDatasetIds'
| 'projectNamesById'
| 'userNamesById'
| 'dependentProjectTypesById'
| 'getVersionDisplayName'
| 'getVersionProjectName'
Expand Down Expand Up @@ -169,6 +170,7 @@ export function useAnalyticsChartDatasets(
context.displayedSelectedFilters.value,
context.dependentProjectTypesById.value,
context.projectNamesById.value,
context.userNamesById.value,
context.getVersionDisplayName,
showProjectVersionNames.value ? context.getVersionProjectName : undefined,
formatMessage,
Expand All @@ -184,6 +186,7 @@ export function useAnalyticsChartDatasets(
context.displayedSelectedFilters.value,
context.dependentProjectTypesById.value,
context.projectNamesById.value,
context.userNamesById.value,
context.getVersionDisplayName,
showProjectVersionNames.value ? context.getVersionProjectName : undefined,
formatMessage,
Expand Down Expand Up @@ -376,6 +379,7 @@ function buildDatasetsByStat(
selectedFilters: AnalyticsSelectedFilters,
dependentProjectTypesById: ReadonlyMap<string, readonly string[]>,
projectNamesById: ReadonlyMap<string, string>,
userNamesById: ReadonlyMap<string, string>,
getVersionDisplayName: (versionId: string) => string,
getVersionProjectName: ((versionId: string) => string | undefined) | undefined,
formatMessage: FormatMessage,
Expand All @@ -392,6 +396,7 @@ function buildDatasetsByStat(
selectedFilters,
dependentProjectTypesById,
projectNamesById,
userNamesById,
getVersionDisplayName,
getVersionProjectName,
formatMessage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type AnalyticsBreakdownItemType =
| 'downloadSource'
| 'gameVersion'
| 'loader'
| 'member'
| 'monetization'
| 'project'
| 'projectVersion'
Expand Down Expand Up @@ -187,6 +188,10 @@ export const analyticsMessages = defineMessages({
id: 'analytics.filter.search.dependent-projects',
defaultMessage: 'Search projects...',
},
searchMembersPlaceholder: {
id: 'analytics.filter.search.members',
defaultMessage: 'Search members...',
},
searchVersionsPlaceholder: {
id: 'analytics.filter.search.versions',
defaultMessage: 'Search versions...',
Expand Down Expand Up @@ -363,6 +368,10 @@ export const analyticsBreakdownMessages = defineMessages({
id: 'analytics.breakdown.download-reason',
defaultMessage: 'Download reason',
},
members: {
id: 'analytics.breakdown.members',
defaultMessage: 'Members',
},
dependentProjectDownload: {
id: 'analytics.breakdown.dependent-project-download',
defaultMessage: 'Dependent project',
Expand Down Expand Up @@ -552,22 +561,22 @@ export const analyticsChartMessages = defineMessages({
tableSelectionLimited: {
id: 'analytics.chart.table-selection.limited',
defaultMessage:
'Showing {limit} {itemType, select, project {{limit, plural, one {project} other {projects}}} country {{limit, plural, one {country} other {countries}}} monetization {{limit, plural, one {monetization value} other {monetization values}}} downloadSource {{limit, plural, one {download source} other {download sources}}} downloadReason {{limit, plural, one {download reason} other {download reasons}}} projectVersion {{limit, plural, one {project version} other {project versions}}} loader {{limit, plural, one {loader} other {loaders}}} gameVersion {{limit, plural, one {game version} other {game versions}}} other {{limit, plural, one {item} other {items}}}} from table',
'Showing {limit} {itemType, select, project {{limit, plural, one {project} other {projects}}} country {{limit, plural, one {country} other {countries}}} monetization {{limit, plural, one {monetization value} other {monetization values}}} downloadSource {{limit, plural, one {download source} other {download sources}}} downloadReason {{limit, plural, one {download reason} other {download reasons}}} member {{limit, plural, one {member} other {members}}} projectVersion {{limit, plural, one {project version} other {project versions}}} loader {{limit, plural, one {loader} other {loaders}}} gameVersion {{limit, plural, one {game version} other {game versions}}} other {{limit, plural, one {item} other {items}}}} from table',
},
tableSelectionAll: {
id: 'analytics.chart.table-selection.all',
defaultMessage:
'Showing all {itemType, select, project {{count, plural, one {project} other {projects}}} country {{count, plural, one {country} other {countries}}} monetization {{count, plural, one {monetization value} other {monetization values}}} downloadSource {{count, plural, one {download source} other {download sources}}} downloadReason {{count, plural, one {download reason} other {download reasons}}} projectVersion {{count, plural, one {project version} other {project versions}}} loader {{count, plural, one {loader} other {loaders}}} gameVersion {{count, plural, one {game version} other {game versions}}} other {{count, plural, one {item} other {items}}}} from table',
'Showing all {itemType, select, project {{count, plural, one {project} other {projects}}} country {{count, plural, one {country} other {countries}}} monetization {{count, plural, one {monetization value} other {monetization values}}} downloadSource {{count, plural, one {download source} other {download sources}}} downloadReason {{count, plural, one {download reason} other {download reasons}}} member {{count, plural, one {member} other {members}}} projectVersion {{count, plural, one {project version} other {project versions}}} loader {{count, plural, one {loader} other {loaders}}} gameVersion {{count, plural, one {game version} other {game versions}}} other {{count, plural, one {item} other {items}}}} from table',
},
tableSelectionTop: {
id: 'analytics.chart.table-selection.top',
defaultMessage:
'Showing top {count} {itemType, select, project {{count, plural, one {project} other {projects}}} country {{count, plural, one {country} other {countries}}} monetization {{count, plural, one {monetization value} other {monetization values}}} downloadSource {{count, plural, one {download source} other {download sources}}} downloadReason {{count, plural, one {download reason} other {download reasons}}} projectVersion {{count, plural, one {project version} other {project versions}}} loader {{count, plural, one {loader} other {loaders}}} gameVersion {{count, plural, one {game version} other {game versions}}} other {{count, plural, one {item} other {items}}}} from table',
'Showing top {count} {itemType, select, project {{count, plural, one {project} other {projects}}} country {{count, plural, one {country} other {countries}}} monetization {{count, plural, one {monetization value} other {monetization values}}} downloadSource {{count, plural, one {download source} other {download sources}}} downloadReason {{count, plural, one {download reason} other {download reasons}}} member {{count, plural, one {member} other {members}}} projectVersion {{count, plural, one {project version} other {project versions}}} loader {{count, plural, one {loader} other {loaders}}} gameVersion {{count, plural, one {game version} other {game versions}}} other {{count, plural, one {item} other {items}}}} from table',
},
tableSelectionCount: {
id: 'analytics.chart.table-selection.count',
defaultMessage:
'Showing {count} {itemType, select, project {{count, plural, one {project} other {projects}}} country {{count, plural, one {country} other {countries}}} monetization {{count, plural, one {monetization value} other {monetization values}}} downloadSource {{count, plural, one {download source} other {download sources}}} downloadReason {{count, plural, one {download reason} other {download reasons}}} projectVersion {{count, plural, one {project version} other {project versions}}} loader {{count, plural, one {loader} other {loaders}}} gameVersion {{count, plural, one {game version} other {game versions}}} other {{count, plural, one {item} other {items}}}} from table',
'Showing {count} {itemType, select, project {{count, plural, one {project} other {projects}}} country {{count, plural, one {country} other {countries}}} monetization {{count, plural, one {monetization value} other {monetization values}}} downloadSource {{count, plural, one {download source} other {download sources}}} downloadReason {{count, plural, one {download reason} other {download reasons}}} member {{count, plural, one {member} other {members}}} projectVersion {{count, plural, one {project version} other {project versions}}} loader {{count, plural, one {loader} other {loaders}}} gameVersion {{count, plural, one {game version} other {game versions}}} other {{count, plural, one {item} other {items}}}} from table',
},
lineView: {
id: 'analytics.chart.view.line',
Expand Down Expand Up @@ -813,6 +822,8 @@ export function formatAnalyticsBreakdownLabel(
return formatMessage(analyticsBreakdownMessages.userAgent)
case 'download_reason':
return formatMessage(analyticsBreakdownMessages.downloadReason)
case 'user_id':
return formatMessage(analyticsBreakdownMessages.members)
case 'dependent_project_download':
return formatMessage(analyticsBreakdownMessages.dependentProjectDownload)
case 'version_id':
Expand Down Expand Up @@ -844,6 +855,8 @@ export function getAnalyticsBreakdownItemType(
return 'downloadSource'
case 'download_reason':
return 'downloadReason'
case 'user_id':
return 'member'
case 'dependent_project_download':
return 'project'
case 'version_id':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const BREAKDOWN_PRESET_VALUES: AnalyticsBreakdownPreset[] = [
'monetization',
'user_agent',
'download_reason',
'user_id',
'version_id',
'loader',
'game_version',
Expand All @@ -97,6 +98,7 @@ const ANALYTICS_TABLE_SORT_COLUMN_VALUES: AnalyticsTableSortColumn[] = [
'breakdown_monetization',
'breakdown_user_agent',
'breakdown_download_reason',
'breakdown_user_id',
'breakdown_version_id',
'breakdown_loader',
'breakdown_game_version',
Expand Down Expand Up @@ -134,6 +136,7 @@ const QUERY_KEY_FILTER_MONETIZATION = 'a_monetization'
const QUERY_KEY_FILTER_USER_AGENT = 'a_user_agent'
const QUERY_KEY_FILTER_LEGACY_DOWNLOAD_SOURCE = 'a_download_source'
const QUERY_KEY_FILTER_DOWNLOAD_REASON = 'a_download_reason'
const QUERY_KEY_FILTER_USER_ID = 'a_user_id'
const QUERY_KEY_FILTER_VERSION_ID = 'a_version_id'
const QUERY_KEY_FILTER_GAME_VERSION = 'a_game_version'
const QUERY_KEY_FILTER_LOADER_TYPE = 'a_loader_type'
Expand All @@ -159,6 +162,7 @@ const URL_FILTER_CATEGORIES: Exclude<AnalyticsQueryFilterCategory, 'project'>[]
'monetization',
'user_agent',
'download_reason',
'user_id',
'version_id',
'game_version',
'loader_type',
Expand All @@ -175,6 +179,7 @@ const FILTER_QUERY_KEY_BY_CATEGORY: Record<
monetization: QUERY_KEY_FILTER_MONETIZATION,
user_agent: QUERY_KEY_FILTER_USER_AGENT,
download_reason: QUERY_KEY_FILTER_DOWNLOAD_REASON,
user_id: QUERY_KEY_FILTER_USER_ID,
version_id: QUERY_KEY_FILTER_VERSION_ID,
game_version: QUERY_KEY_FILTER_GAME_VERSION,
loader_type: QUERY_KEY_FILTER_LOADER_TYPE,
Expand All @@ -198,6 +203,7 @@ const ANALYTICS_QUERY_KEYS = [
QUERY_KEY_FILTER_USER_AGENT,
QUERY_KEY_FILTER_LEGACY_DOWNLOAD_SOURCE,
QUERY_KEY_FILTER_DOWNLOAD_REASON,
QUERY_KEY_FILTER_USER_ID,
QUERY_KEY_FILTER_VERSION_ID,
QUERY_KEY_FILTER_GAME_VERSION,
QUERY_KEY_FILTER_LOADER_TYPE,
Expand All @@ -223,6 +229,7 @@ export function buildEmptySelectedFilters(): AnalyticsSelectedFilters {
monetization: [],
user_agent: [],
download_reason: [],
user_id: [],
version_id: [],
game_version: [],
loader_type: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type BuildAnalyticsTableRowsOptions = {
includeDependentProjectTooltipContext: boolean
relevantStats: ReadonlySet<AnalyticsDashboardStat>
projectNamesById: ReadonlyMap<string, string>
userNamesById: ReadonlyMap<string, string>
getVersionDisplayName: (versionId: string) => string
getVersionProjectName: (versionId: string) => string | undefined
showTimeInBucketLabel: boolean
Expand All @@ -67,6 +68,7 @@ export function buildAnalyticsTableRows({
includeDependentProjectTooltipContext,
relevantStats,
projectNamesById,
userNamesById,
getVersionDisplayName,
getVersionProjectName,
showTimeInBucketLabel,
Expand Down Expand Up @@ -99,6 +101,7 @@ export function buildAnalyticsTableRows({
breakdownValue,
breakdown,
projectNamesById,
userNamesById,
getVersionDisplayName,
formatMessage,
)
Expand Down Expand Up @@ -366,6 +369,7 @@ function formatAnalyticsTableBreakdownDisplayValue(
value: string,
breakdown: AnalyticsTableBreakdownPreset,
projectNamesById: ReadonlyMap<string, string>,
userNamesById: ReadonlyMap<string, string>,
getVersionDisplayName: (versionId: string) => string,
formatMessage: FormatMessage,
): string {
Expand All @@ -381,5 +385,5 @@ function formatAnalyticsTableBreakdownDisplayValue(

return projectNamesById.get(value) ?? value
}
return formatBreakdownLabel(value, breakdown, getVersionDisplayName, formatMessage)
return formatBreakdownLabel(value, breakdown, getVersionDisplayName, userNamesById, formatMessage)
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,25 @@
<template #cell-breakdown_download_reason="{ value }">
<span class="mr-2.5 text-primary">{{ value }}</span>
</template>
<template #cell-breakdown_user_id="{ row, value }">
<div class="mr-2.5 flex min-w-0 items-center gap-2">
<span
v-tooltip="getUserCellLabel(value)"
class="flex size-6 shrink-0 items-center justify-center overflow-hidden rounded-full text-primary"
>
<img
v-if="getUserAvatarUrl(row.breakdownValues.user_id)"
:src="getUserAvatarUrl(row.breakdownValues.user_id)"
:alt="getUserCellLabel(value)"
class="h-6 w-6 rounded-full object-cover"
/>
<UserIcon v-else class="h-full w-full" />
</span>
<span class="min-w-0 truncate font-semibold leading-tight text-primary">
{{ value }}
</span>
</div>
</template>
<template #cell-breakdown_dependent_project_download="{ row, value }">
<ProjectCell
:label="getProjectCellLabel(value)"
Expand Down Expand Up @@ -164,7 +183,7 @@
</template>

<script setup lang="ts">
import { DownloadIcon, DropdownIcon, SearchIcon } from '@modrinth/assets'
import { DownloadIcon, DropdownIcon, SearchIcon, UserIcon } from '@modrinth/assets'
import {
ButtonStyled,
OverflowMenu,
Expand Down Expand Up @@ -267,6 +286,8 @@ const {
projectIconUrlsById,
projectOrganizationIdsById,
projectOrganizationNamesById,
userNamesById,
userAvatarUrlsById,
dependentProjectTypesById,
getVersionDisplayName,
getVersionProjectName,
Expand Down Expand Up @@ -407,6 +428,7 @@ function buildTableRows(mode: AnalyticsTableMode) {
includeDependentProjectTooltipContext: includeDependentProjectTooltipContext.value,
relevantStats: relevantStats.value,
projectNamesById: projectNamesById.value,
userNamesById: userNamesById.value,
getVersionDisplayName,
getVersionProjectName,
showTimeInBucketLabel: showTimeInBucketLabel.value,
Expand Down Expand Up @@ -441,6 +463,14 @@ function getProjectCellLabel(value: unknown) {
return typeof value === 'string' ? value : String(value ?? '')
}

function getUserAvatarUrl(userId: string | undefined) {
return userId ? userAvatarUrlsById.value.get(userId) : undefined
}

function getUserCellLabel(value: unknown) {
return typeof value === 'string' ? value : String(value ?? '')
}

function getProjectPageHref(projectId: string | undefined) {
return projectId ? `/project/${encodeURIComponent(projectId)}` : undefined
}
Expand Down Expand Up @@ -549,6 +579,7 @@ watch(
projects,
dependentProjectTypesById,
projectNamesById,
userNamesById,
versionNumbersById,
versionProjectNamesById,
],
Expand Down
2 changes: 2 additions & 0 deletions apps/frontend/src/components/analytics-dashboard/breakdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export function getAnalyticsBreakdownValue(
'reason' in point ? point.reason : undefined,
UNKNOWN_BREAKDOWN_VALUE,
)
case 'user_id':
return normalizeBreakdownValue('user_id' in point ? point.user_id : undefined)
case 'dependent_project_download': {
const dependentProjectId = normalizeBreakdownValue(
'dependent_project_id' in point ? point.dependent_project_id : undefined,
Expand Down
Loading
Loading