diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-utils.ts b/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-utils.ts index 5a88bc4567..509aa7dfd3 100644 --- a/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-utils.ts +++ b/apps/frontend/src/components/analytics-dashboard/analytics-chart/analytics-chart-utils.ts @@ -143,6 +143,7 @@ export function formatBreakdownLabel( breakdownValue: string, selectedBreakdown: AnalyticsBreakdownPreset, getVersionDisplayName: ((versionId: string) => string) | undefined, + userNamesById: ReadonlyMap | undefined, formatMessage: FormatMessage, ): string { const normalizedValue = breakdownValue.trim() @@ -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 } @@ -187,6 +191,7 @@ export function formatBreakdownLabels( breakdownValues: readonly string[], selectedBreakdowns: readonly AnalyticsBreakdownPreset[], getVersionDisplayName: ((versionId: string) => string) | undefined, + userNamesById: ReadonlyMap | undefined, formatMessage: FormatMessage, ): string { const normalizedBreakdowns = selectedBreakdowns.filter((breakdown) => breakdown !== 'none') @@ -202,6 +207,7 @@ export function formatBreakdownLabels( ) { return formatAnalyticsDependentProjectFallbackLabel( breakdownValues[downloadReasonBreakdownIndex], + userNamesById, formatMessage, ) } @@ -371,6 +377,7 @@ export function buildChartDatasets( selectedFilters: AnalyticsSelectedFilters, dependentProjectTypesById: ReadonlyMap, projectNamesById: ReadonlyMap, + userNamesById: ReadonlyMap, getVersionDisplayName: ((versionId: string) => string) | undefined, getVersionProjectName: ((versionId: string) => string | undefined) | undefined, formatMessage: FormatMessage, @@ -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) diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-chart/use-analytics-chart-datasets.ts b/apps/frontend/src/components/analytics-dashboard/analytics-chart/use-analytics-chart-datasets.ts index b9581782ec..482c879002 100644 --- a/apps/frontend/src/components/analytics-dashboard/analytics-chart/use-analytics-chart-datasets.ts +++ b/apps/frontend/src/components/analytics-dashboard/analytics-chart/use-analytics-chart-datasets.ts @@ -58,6 +58,7 @@ export function useAnalyticsChartDatasets( | 'defaultGraphDatasetIds' | 'topGraphDatasetIds' | 'projectNamesById' + | 'userNamesById' | 'dependentProjectTypesById' | 'getVersionDisplayName' | 'getVersionProjectName' @@ -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, @@ -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, @@ -376,6 +379,7 @@ function buildDatasetsByStat( selectedFilters: AnalyticsSelectedFilters, dependentProjectTypesById: ReadonlyMap, projectNamesById: ReadonlyMap, + userNamesById: ReadonlyMap, getVersionDisplayName: (versionId: string) => string, getVersionProjectName: ((versionId: string) => string | undefined) | undefined, formatMessage: FormatMessage, @@ -392,6 +396,7 @@ function buildDatasetsByStat( selectedFilters, dependentProjectTypesById, projectNamesById, + userNamesById, getVersionDisplayName, getVersionProjectName, formatMessage, diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-messages.ts b/apps/frontend/src/components/analytics-dashboard/analytics-messages.ts index ee35753290..ac4509eeff 100644 --- a/apps/frontend/src/components/analytics-dashboard/analytics-messages.ts +++ b/apps/frontend/src/components/analytics-dashboard/analytics-messages.ts @@ -13,6 +13,7 @@ export type AnalyticsBreakdownItemType = | 'downloadSource' | 'gameVersion' | 'loader' + | 'member' | 'monetization' | 'project' | 'projectVersion' @@ -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...', @@ -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', @@ -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', @@ -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': @@ -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': diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-route-query.ts b/apps/frontend/src/components/analytics-dashboard/analytics-route-query.ts index c840b48aa1..22d5e4bbe0 100644 --- a/apps/frontend/src/components/analytics-dashboard/analytics-route-query.ts +++ b/apps/frontend/src/components/analytics-dashboard/analytics-route-query.ts @@ -73,6 +73,7 @@ const BREAKDOWN_PRESET_VALUES: AnalyticsBreakdownPreset[] = [ 'monetization', 'user_agent', 'download_reason', + 'user_id', 'version_id', 'loader', 'game_version', @@ -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', @@ -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' @@ -159,6 +162,7 @@ const URL_FILTER_CATEGORIES: Exclude[] 'monetization', 'user_agent', 'download_reason', + 'user_id', 'version_id', 'game_version', 'loader_type', @@ -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, @@ -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, @@ -223,6 +229,7 @@ export function buildEmptySelectedFilters(): AnalyticsSelectedFilters { monetization: [], user_agent: [], download_reason: [], + user_id: [], version_id: [], game_version: [], loader_type: [], diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-table/analytics-table-row-builder.ts b/apps/frontend/src/components/analytics-dashboard/analytics-table/analytics-table-row-builder.ts index a67abece6a..a4fe46007c 100644 --- a/apps/frontend/src/components/analytics-dashboard/analytics-table/analytics-table-row-builder.ts +++ b/apps/frontend/src/components/analytics-dashboard/analytics-table/analytics-table-row-builder.ts @@ -49,6 +49,7 @@ type BuildAnalyticsTableRowsOptions = { includeDependentProjectTooltipContext: boolean relevantStats: ReadonlySet projectNamesById: ReadonlyMap + userNamesById: ReadonlyMap getVersionDisplayName: (versionId: string) => string getVersionProjectName: (versionId: string) => string | undefined showTimeInBucketLabel: boolean @@ -67,6 +68,7 @@ export function buildAnalyticsTableRows({ includeDependentProjectTooltipContext, relevantStats, projectNamesById, + userNamesById, getVersionDisplayName, getVersionProjectName, showTimeInBucketLabel, @@ -99,6 +101,7 @@ export function buildAnalyticsTableRows({ breakdownValue, breakdown, projectNamesById, + userNamesById, getVersionDisplayName, formatMessage, ) @@ -366,6 +369,7 @@ function formatAnalyticsTableBreakdownDisplayValue( value: string, breakdown: AnalyticsTableBreakdownPreset, projectNamesById: ReadonlyMap, + userNamesById: ReadonlyMap, getVersionDisplayName: (versionId: string) => string, formatMessage: FormatMessage, ): string { @@ -381,5 +385,5 @@ function formatAnalyticsTableBreakdownDisplayValue( return projectNamesById.get(value) ?? value } - return formatBreakdownLabel(value, breakdown, getVersionDisplayName, formatMessage) + return formatBreakdownLabel(value, breakdown, getVersionDisplayName, userNamesById, formatMessage) } diff --git a/apps/frontend/src/components/analytics-dashboard/analytics-table/index.vue b/apps/frontend/src/components/analytics-dashboard/analytics-table/index.vue index 1b3734728b..714ef36cd4 100644 --- a/apps/frontend/src/components/analytics-dashboard/analytics-table/index.vue +++ b/apps/frontend/src/components/analytics-dashboard/analytics-table/index.vue @@ -77,6 +77,25 @@ +