feat: various analytics updates (#6330)

* feat: add button to view user analytics

* feat: add "Your projects" preset selection

* feat: fix revenue rounding for values under 1 and show full values for all statcards with tooltip

* fix: sum rounded value instead of raw value for tooltip total if it's under 1

* fix: show decimal in playtime statcard if under 1 hrs

* feat: disable playtime statcard for purely plugin projects

* refactor: pnpm prepr
This commit is contained in:
Truman Gao
2026-06-08 16:03:28 -06:00
committed by GitHub
parent a92b5b08df
commit 2c9bf58d1f
12 changed files with 491 additions and 51 deletions
@@ -168,6 +168,14 @@ const {
onRangeSelected: (start, end, groupBy) => emit('range-select', start, end, groupBy),
})
function getTooltipTotalMetricValue(value: number): number {
if (props.activeStat === 'revenue' && Math.abs(value) < 1) {
return Math.round(value * 100) / 100
}
return value
}
const hoverTotalValue = computed(() => {
if (hoverState.sliceIndex === null) return 0
const sliceIndex = hoverState.sliceIndex
@@ -176,7 +184,7 @@ const hoverTotalValue = computed(() => {
return props.currentLegendEntries.reduce((sum, legendEntry) => {
if (legendEntry.hidden) return sum
const dataset = props.chartDatasetById.get(legendEntry.id)
return sum + (dataset?.data[sliceIndex] ?? 0)
return sum + getTooltipTotalMetricValue(dataset?.data[sliceIndex] ?? 0)
}, 0)
})
@@ -39,6 +39,14 @@ export const analyticsMessages = defineMessages({
id: 'analytics.project.all',
defaultMessage: 'All projects',
},
yourProjects: {
id: 'analytics.project.your',
defaultMessage: 'Your projects',
},
userProjects: {
id: 'analytics.project.user',
defaultMessage: "{username}'s projects",
},
selectProjects: {
id: 'analytics.project.select',
defaultMessage: 'Select projects',
@@ -146,6 +146,7 @@ const QUERY_KEY_TABLE_SORT = 'a_table_sort'
const QUERY_KEY_TABLE_SORT_DIRECTION = 'a_table_sort_direction'
const QUERY_KEY_LEGACY_GRAPH_TOP_BREAKDOWN_FILTER = 'a_top_breakdown'
const QUERY_KEY_LEGACY_GRAPH_LEGEND_EXPANSION = 'a_legend_expanded'
const PROJECT_SELECTION_ALL_QUERY_VALUE = 'all'
const URL_FILTER_CATEGORIES: Exclude<AnalyticsQueryFilterCategory, 'project'>[] = [
'project_status',
@@ -405,9 +406,10 @@ export function buildDefaultAnalyticsGraphState(
export function buildDefaultAnalyticsQueryBuilderState(
availableProjectIds: string[],
defaultProjectIds: string[] = availableProjectIds,
): AnalyticsQueryBuilderState {
return {
selectedProjectIds: [...availableProjectIds],
selectedProjectIds: [...defaultProjectIds],
selectedTimeframeMode: DEFAULT_TIMEFRAME_MODE,
selectedTimeframe: DEFAULT_TIMEFRAME_PRESET,
selectedLastTimeframeAmount: DEFAULT_LAST_TIMEFRAME_AMOUNT,
@@ -415,7 +417,7 @@ export function buildDefaultAnalyticsQueryBuilderState(
selectedCustomTimeframeStartDate: getDefaultCustomStartDate(),
selectedCustomTimeframeEndDate: getDefaultCustomEndDate(),
selectedGroupBy: DEFAULT_GROUP_BY_PRESET,
selectedBreakdowns: getDefaultAnalyticsBreakdownPresets(availableProjectIds),
selectedBreakdowns: getDefaultAnalyticsBreakdownPresets(defaultProjectIds),
selectedFilters: buildEmptySelectedFilters(),
}
}
@@ -475,12 +477,16 @@ export function getAnalyticsBreakdownPresetForProjectSelection(
export function isAnalyticsQueryBuilderStateDefault(
state: AnalyticsQueryBuilderState,
availableProjectIds: string[],
defaultProjectIds: string[] = availableProjectIds,
): boolean {
const defaultState = buildDefaultAnalyticsQueryBuilderState(availableProjectIds)
const defaultState = buildDefaultAnalyticsQueryBuilderState(
availableProjectIds,
defaultProjectIds,
)
const areDefaultProjectsSelected =
availableProjectIds.length === 0
defaultProjectIds.length === 0
? state.selectedProjectIds.length === 0
: areAllProjectsSelected(state.selectedProjectIds, availableProjectIds)
: areAllProjectsSelected(state.selectedProjectIds, defaultProjectIds)
return (
areDefaultProjectsSelected &&
@@ -666,13 +672,19 @@ export function readAnalyticsTableSortState(
export function readAnalyticsQueryBuilderState(
query: LocationQuery,
availableProjectIds: string[],
defaultProjectIds: string[] = availableProjectIds,
): AnalyticsQueryBuilderState {
const defaultState = buildDefaultAnalyticsQueryBuilderState(availableProjectIds)
const defaultState = buildDefaultAnalyticsQueryBuilderState(
availableProjectIds,
defaultProjectIds,
)
const selectedProjectIdsFromQuery = parseListQueryValue(query[QUERY_KEY_PROJECT_IDS])
const selectedProjectIds =
selectedProjectIdsFromQuery.length > 0
? selectedProjectIdsFromQuery
: defaultState.selectedProjectIds
let selectedProjectIds = defaultState.selectedProjectIds
if (selectedProjectIdsFromQuery.includes(PROJECT_SELECTION_ALL_QUERY_VALUE)) {
selectedProjectIds = [...availableProjectIds]
} else if (selectedProjectIdsFromQuery.length > 0) {
selectedProjectIds = selectedProjectIdsFromQuery
}
const selectedFilters = buildEmptySelectedFilters()
for (const category of URL_FILTER_CATEGORIES) {
@@ -779,14 +791,17 @@ export function buildAnalyticsQueryBuilderRouteQuery(
state: AnalyticsQueryBuilderState,
availableProjectIds: string[],
graphState?: AnalyticsGraphState,
defaultProjectIds: string[] = availableProjectIds,
): MutableRouteQuery {
const nextRouteQuery = {
...currentRouteQuery,
} as MutableRouteQuery
const projectIdsQueryValue = areAllProjectsSelected(state.selectedProjectIds, availableProjectIds)
const projectIdsQueryValue = areAllProjectsSelected(state.selectedProjectIds, defaultProjectIds)
? undefined
: serializeListQueryValue(state.selectedProjectIds)
: areAllProjectsSelected(state.selectedProjectIds, availableProjectIds)
? PROJECT_SELECTION_ALL_QUERY_VALUE
: serializeListQueryValue(state.selectedProjectIds)
const isCustomTimeframeMode =
state.selectedTimeframeMode === 'custom_range' ||
state.selectedTimeframeMode === 'custom_datetime_range'
@@ -69,6 +69,29 @@
<template v-if="hasProjectOptions" #top>
<div>
<button
v-if="showProjectPresets"
type="button"
class="flex w-full cursor-pointer items-center gap-1.5 border-0 bg-surface-4 px-4 py-3 text-left shadow-none transition-all duration-150 hover:brightness-[115%] focus:brightness-[115%]"
:aria-selected="isUserProjectsOptionSelected"
:class="isUserProjectsOptionSelected ? 'text-contrast' : 'text-primary'"
role="option"
@click="selectUserProjectsMode"
@keydown.enter.stop
@keydown.space.stop
>
<LayersIcon
class="h-5 w-5 shrink-0 text-primary"
:class="isUserProjectsOptionSelected ? 'text-contrast' : 'text-primary'"
/>
<span class="min-w-0 flex-1 font-semibold leading-tight">
{{ userProjectsLabel }}
</span>
<span class="flex shrink-0 items-center justify-center text-brand">
<CheckIcon v-if="isUserProjectsOptionSelected" aria-hidden="true" class="size-5" />
</span>
</button>
<button
v-if="!showProjectPresets || showAllProjectsPreset"
type="button"
class="flex w-full cursor-pointer items-center gap-1.5 border-0 bg-surface-4 px-4 py-3 text-left shadow-none transition-all duration-150 hover:brightness-[115%] focus:brightness-[115%]"
:aria-selected="isAllProjectsOptionSelected"
@@ -210,7 +233,11 @@
decoding="async"
/>
<LayersIcon
v-else-if="isAllProjectsOptionSelected || areAllProjectsSelected"
v-else-if="
isUserProjectsOptionSelected ||
isAllProjectsOptionSelected ||
areAllProjectRowsSelected
"
class="size-5 shrink-0 text-primary"
/>
<BoxIcon v-else class="size-5 shrink-0 text-primary" />
@@ -253,6 +280,33 @@
<template v-if="hasProjectOptions" #top>
<div>
<button
v-if="showProjectPresets"
type="button"
class="flex w-full cursor-pointer items-center gap-2 border-0 bg-surface-4 px-4 py-3 text-left shadow-none transition-all duration-150 hover:brightness-[115%] focus:brightness-[115%]"
:aria-selected="isUserProjectsOptionSelected"
:class="isUserProjectsOptionSelected ? 'text-contrast' : 'text-primary'"
role="option"
@click="selectUserProjectsMode"
@keydown.enter.stop
@keydown.space.stop
>
<LayersIcon
class="h-5 w-5 shrink-0 text-primary"
:class="isUserProjectsOptionSelected ? 'text-contrast' : 'text-primary'"
/>
<span class="min-w-0 flex-1 font-semibold leading-tight">
{{ userProjectsLabel }}
</span>
<span class="flex shrink-0 items-center justify-center text-brand">
<CheckIcon
v-if="isUserProjectsOptionSelected"
aria-hidden="true"
class="size-5"
/>
</span>
</button>
<button
v-if="!showProjectPresets || showAllProjectsPreset"
type="button"
class="flex w-full cursor-pointer items-center gap-2 border-0 bg-surface-4 px-4 py-3 text-left shadow-none transition-all duration-150 hover:brightness-[115%] focus:brightness-[115%]"
:aria-selected="isAllProjectsOptionSelected"
@@ -449,11 +503,17 @@ const QUERY_BUILDER_DROPDOWN_MIN_WIDTH = '12rem'
const analyticsQueryChipTriggerClass = 'h-10 '
const analyticsQueryAddFilterButtonClass = '!h-10 max-w-full !w-max !px-3.5 flex !gap-2'
const projectOptionCollator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' })
type ProjectSelectionPreset = 'user' | 'all'
const {
hasProjectContext,
projectGroups,
projects,
dashboardUserProjectIds,
dashboardOrganizationProjectIds,
defaultProjectIds,
isUsingDashboardUserOverride,
dashboardProjectUserName,
selectedProjectIds,
selectedTimeframeMode,
selectedTimeframe,
@@ -467,7 +527,7 @@ const {
activeStat,
showPreviousPeriod,
projectStatusById,
projectDownloadsById,
availableProjectDownloadsById,
queryResetToken,
refreshAnalyticsQuery,
setFetchRequest,
@@ -535,12 +595,25 @@ const projectSelectOptions = computed<MultiSelectItem<string>[]>(() => {
const allProjectIds = computed(() => projectOptions.value.map((project) => project.value))
const hasProjectOptions = computed(() => projectOptions.value.length > 0)
const userProjectIds = computed(() =>
dashboardOrganizationProjectIds.value.length > 0
? dashboardUserProjectIds.value
: defaultProjectIds.value,
)
const showProjectPresets = computed(
() =>
hasProjectOptions.value &&
dashboardUserProjectIds.value.length > 0 &&
dashboardOrganizationProjectIds.value.length > 0,
)
const showAllProjectsPreset = computed(() => dashboardOrganizationProjectIds.value.length > 0)
const noProjectsMessage = computed(() =>
hasProjectContext.value
? formatMessage(analyticsMessages.noDataAvailableForAnalytics)
: formatMessage(analyticsMessages.noProjectsAvailable),
)
const isProjectSelectOpen = ref(false)
const draftProjectSelectionPreset = ref<ProjectSelectionPreset | null>(null)
const draftSelectedProjectIds = ref<string[]>([...selectedProjectIds.value])
const projectDownloadsThreshold = ref<number | null>(null)
const projectDownloadsThresholdProjectIds = ref<string[] | null>(null)
@@ -558,15 +631,48 @@ function normalizeProjectSelection(projectIds: string[]) {
return projectIds.length > 0 ? [...projectIds] : [...allProjectIds.value]
}
function getProjectSelectionPreset(projectIds: string[]): ProjectSelectionPreset | null {
if (!showProjectPresets.value) {
return null
}
if (isSameProjectSelection(projectIds, userProjectIds.value)) {
return 'user'
}
if (isSameProjectSelection(projectIds, allProjectIds.value)) {
return 'all'
}
return null
}
function setDraftProjectSelection(projectIds: string[]) {
const preset = getProjectSelectionPreset(projectIds)
draftProjectSelectionPreset.value = preset
if (preset) {
draftSelectedProjectIds.value = []
return
}
draftSelectedProjectIds.value = isSameProjectSelection(projectIds, allProjectIds.value)
? []
: [...projectIds]
}
watch(selectedProjectIds, (nextSelectedProjectIds) => {
if (isProjectSelectOpen.value) {
return
}
draftSelectedProjectIds.value = [...nextSelectedProjectIds]
setDraftProjectSelection(nextSelectedProjectIds)
})
watch(draftSelectedProjectIds, (nextSelectedProjectIds) => {
if (draftProjectSelectionPreset.value && nextSelectedProjectIds.length > 0) {
draftProjectSelectionPreset.value = null
}
if (projectDownloadsThreshold.value === null) {
return
}
@@ -587,25 +693,40 @@ watch(queryResetToken, () => {
isBreakdownSelectOpen.value = false
draftSelectedBreakdowns.value = [...selectedBreakdowns.value]
clearProjectDownloadsThreshold()
draftSelectedProjectIds.value = isSameProjectSelection(
selectedProjectIds.value,
allProjectIds.value,
)
? []
: [...selectedProjectIds.value]
setDraftProjectSelection(selectedProjectIds.value)
})
const areAllProjectsSelected = computed(() => {
const areAllProjectRowsSelected = computed(() => {
return isSameProjectSelection(draftSelectedProjectIds.value, allProjectIds.value)
})
const isAllProjectsOptionSelected = computed(() => draftSelectedProjectIds.value.length === 0)
const isAllProjectsOptionSelected = computed(() =>
showProjectPresets.value
? draftProjectSelectionPreset.value === 'all'
: draftSelectedProjectIds.value.length === 0,
)
const isUserProjectsOptionSelected = computed(() => {
return showProjectPresets.value && draftProjectSelectionPreset.value === 'user'
})
const userProjectsLabel = computed(() => {
if (isUsingDashboardUserOverride.value) {
return formatMessage(analyticsMessages.userProjects, {
username: dashboardProjectUserName.value,
})
}
return formatMessage(analyticsMessages.yourProjects)
})
const selectedProjectLabel = computed(() => {
if (!hasProjectOptions.value) {
return noProjectsMessage.value
}
if (isAllProjectsOptionSelected.value || areAllProjectsSelected.value) {
if (isUserProjectsOptionSelected.value) {
return userProjectsLabel.value
}
if (isAllProjectsOptionSelected.value || areAllProjectRowsSelected.value) {
return formatMessage(analyticsMessages.allProjects)
}
@@ -623,8 +744,9 @@ const selectedProjectLabel = computed(() => {
const selectedProjectIconUrl = computed(() => {
if (
isUserProjectsOptionSelected.value ||
isAllProjectsOptionSelected.value ||
areAllProjectsSelected.value ||
areAllProjectRowsSelected.value ||
draftSelectedProjectIds.value.length !== 1
) {
return undefined
@@ -639,12 +761,7 @@ function getProjectIconUrl(projectId: string): string | undefined {
function handleProjectSelectOpen() {
isProjectSelectOpen.value = true
draftSelectedProjectIds.value = isSameProjectSelection(
selectedProjectIds.value,
allProjectIds.value,
)
? []
: [...selectedProjectIds.value]
setDraftProjectSelection(selectedProjectIds.value)
}
function handleProjectSelectClose(
@@ -657,9 +774,14 @@ function handleProjectSelectClose(
function commitDraftSelectedProjects(
nextSelectedProjectIds: string[] = draftSelectedProjectIds.value,
) {
const nextProjectIds = normalizeProjectSelection(nextSelectedProjectIds)
const nextProjectIds =
draftProjectSelectionPreset.value === 'user'
? [...userProjectIds.value]
: draftProjectSelectionPreset.value === 'all'
? [...allProjectIds.value]
: normalizeProjectSelection(nextSelectedProjectIds)
draftSelectedProjectIds.value = [...nextProjectIds]
setDraftProjectSelection(nextProjectIds)
if (!isSameProjectSelection(selectedProjectIds.value, nextProjectIds)) {
if (isSameProjectSelection(nextProjectIds, allProjectIds.value)) {
showPreviousPeriod.value = false
@@ -670,6 +792,17 @@ function commitDraftSelectedProjects(
function selectAllProjectsMode() {
clearProjectDownloadsThreshold()
if (showProjectPresets.value) {
draftProjectSelectionPreset.value = 'all'
} else {
draftProjectSelectionPreset.value = null
}
draftSelectedProjectIds.value = []
}
function selectUserProjectsMode() {
clearProjectDownloadsThreshold()
draftProjectSelectionPreset.value = 'user'
draftSelectedProjectIds.value = []
}
@@ -753,9 +886,10 @@ function applyProjectDownloadsThreshold(threshold: number | null) {
}
const projectIds = projects.value
.filter((project) => (projectDownloadsById.value.get(project.id) ?? 0) > threshold)
.filter((project) => (availableProjectDownloadsById.value.get(project.id) ?? 0) > threshold)
.map((project) => project.id)
draftProjectSelectionPreset.value = null
projectDownloadsThresholdProjectIds.value = projectIds
draftSelectedProjectIds.value = projectIds
}
@@ -38,7 +38,8 @@
<div class="flex flex-col gap-2.5">
<div
class="text-2xl font-semibold leading-none md:text-4xl"
v-tooltip="!disabled ? statTooltip : undefined"
class="w-fit text-2xl font-semibold leading-none md:text-4xl"
:class="{
'text-primary': disabled,
'text-contrast': !disabled,
@@ -114,6 +115,7 @@ import { analyticsStatCardMessages } from '../analytics-messages'
const props = defineProps<{
label: string
statLabel: string
statTooltip?: string
vsPrevPeriodPercent: string | null
icon: string
active?: boolean
@@ -26,6 +26,7 @@
:key="card.key"
:label="card.label"
:stat-label="card.statLabel"
:stat-tooltip="card.statTooltip"
:vs-prev-period-percent="card.vsPrevPeriodPercent"
:icon="card.icon"
:active="activeStat === card.key"
@@ -47,6 +48,7 @@ import {
} from '~/providers/analytics/analytics'
import { analyticsStatCardMessages, formatAnalyticsStatLabel } from '../analytics-messages.ts'
import { formatAnalyticsTableFullPlaytime } from '../analytics-table/analytics-table-formatting.ts'
import StatCard from './StatCard.vue'
const MONETIZATION_BANNER_DISMISSED_KEY = 'analytics-monetization-banner-dismissed'
@@ -77,6 +79,38 @@ const compactNumberFormatter = computed(
}),
)
const underDollarRevenueFormatter = computed(
() =>
new Intl.NumberFormat(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
)
const preciseRevenueFormatter = computed(
() =>
new Intl.NumberFormat(undefined, {
minimumFractionDigits: 5,
maximumFractionDigits: 5,
}),
)
const tooltipRevenueFormatter = computed(
() =>
new Intl.NumberFormat(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
)
const underHourPlaytimeFormatter = computed(
() =>
new Intl.NumberFormat(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}),
)
function formatStatNumber(value: number): string {
const rounded = Math.round(value)
@@ -87,6 +121,45 @@ function formatStatNumber(value: number): string {
return formatNumber(rounded)
}
function formatFullStatNumber(value: number): string {
return formatNumber(Math.round(value))
}
function formatRevenueNumber(value: number): string {
if (Math.abs(value) > 0 && Math.abs(value) < 1) {
return underDollarRevenueFormatter.value.format(value)
}
return formatStatNumber(value)
}
function formatRevenueValue(value: number): string {
return formatMessage(analyticsStatCardMessages.revenueValue, {
value: formatRevenueNumber(value),
})
}
function formatPreciseRevenueValue(value: number): string {
return formatMessage(analyticsStatCardMessages.revenueValue, {
value:
Math.abs(value) < 1
? preciseRevenueFormatter.value.format(value)
: tooltipRevenueFormatter.value.format(value),
})
}
function formatPlaytimeTooltip(value: number): string {
return formatAnalyticsTableFullPlaytime(value, formatMessage)
}
function formatPlaytimeNumber(value: number): string {
if (Math.abs(value) > 0 && Math.abs(value) < 1) {
return underHourPlaytimeFormatter.value.format(value)
}
return formatStatNumber(value)
}
function formatPercent(value: number): string {
const rounded = Math.round(value * 10) / 10
if (rounded === 0) {
@@ -105,7 +178,7 @@ function formatSignedStatNumber(value: number): string {
function formatSignedRevenue(value: number): string {
const signPrefix = value > 0 ? '+' : value < 0 ? '-' : ''
return `${signPrefix}${formatMessage(analyticsStatCardMessages.revenueValue, {
value: formatStatNumber(Math.abs(value)),
value: formatRevenueNumber(Math.abs(value)),
})}`
}
@@ -169,6 +242,7 @@ const statCards = computed<
key: AnalyticsDashboardStat
label: string
statLabel: string
statTooltip?: string
vsPrevPeriodPercent: string | null
icon: string
disabled: boolean
@@ -178,6 +252,7 @@ const statCards = computed<
key: 'views',
label: formatAnalyticsStatLabel('views', formatMessage),
statLabel: formatStatNumber(currentTotals.value.views),
statTooltip: formatFullStatNumber(currentTotals.value.views),
vsPrevPeriodPercent: formatPreviousPeriodComparison(
'views',
percentChanges.value.views,
@@ -191,6 +266,7 @@ const statCards = computed<
key: 'downloads',
label: formatAnalyticsStatLabel('downloads', formatMessage),
statLabel: formatStatNumber(currentTotals.value.downloads),
statTooltip: formatFullStatNumber(currentTotals.value.downloads),
vsPrevPeriodPercent: formatPreviousPeriodComparison(
'downloads',
percentChanges.value.downloads,
@@ -203,9 +279,8 @@ const statCards = computed<
{
key: 'revenue',
label: formatAnalyticsStatLabel('revenue', formatMessage),
statLabel: formatMessage(analyticsStatCardMessages.revenueValue, {
value: formatStatNumber(currentTotals.value.revenue),
}),
statLabel: formatRevenueValue(currentTotals.value.revenue),
statTooltip: formatPreciseRevenueValue(currentTotals.value.revenue),
vsPrevPeriodPercent: formatPreviousPeriodComparison(
'revenue',
percentChanges.value.revenue,
@@ -219,8 +294,9 @@ const statCards = computed<
key: 'playtime',
label: formatAnalyticsStatLabel('playtime', formatMessage),
statLabel: formatMessage(analyticsStatCardMessages.playtimeHours, {
hours: formatStatNumber(currentTotals.value.playtime / 3600),
hours: formatPlaytimeNumber(currentTotals.value.playtime / 3600),
}),
statTooltip: formatPlaytimeTooltip(currentTotals.value.playtime),
vsPrevPeriodPercent: formatPreviousPeriodComparison(
'playtime',
percentChanges.value.playtime,
@@ -53,6 +53,7 @@ export interface UseAnalyticsRouteSyncOptions {
queryBuilder: AnalyticsQueryBuilderRefs
graph: AnalyticsGraphRefs
availableProjectIds: Ref<string[]>
defaultProjectIds: Ref<string[]>
sanitizeSelectedFilters: (
breakdowns: readonly AnalyticsBreakdownPreset[],
filters: AnalyticsSelectedFilters,
@@ -60,7 +61,8 @@ export interface UseAnalyticsRouteSyncOptions {
}
export function useAnalyticsRouteSync(options: UseAnalyticsRouteSyncOptions) {
const { queryBuilder, graph, availableProjectIds, sanitizeSelectedFilters } = options
const { queryBuilder, graph, availableProjectIds, defaultProjectIds, sanitizeSelectedFilters } =
options
const route = useRoute()
const router = useRouter()
@@ -116,6 +118,7 @@ export function useAnalyticsRouteSync(options: UseAnalyticsRouteSyncOptions) {
getSelectedAnalyticsQueryBuilderState(),
availableProjectIds.value,
getSelectedAnalyticsGraphState(),
defaultProjectIds.value,
)
const hasAnalyticsQueryChange = hasAnalyticsQueryBuilderRouteChange(route.query, nextRouteQuery)
@@ -144,7 +147,11 @@ export function useAnalyticsRouteSync(options: UseAnalyticsRouteSyncOptions) {
}
function applyRouteQueryToState(nextQuery: LocationQuery) {
const nextQueryState = readAnalyticsQueryBuilderState(nextQuery, availableProjectIds.value)
const nextQueryState = readAnalyticsQueryBuilderState(
nextQuery,
availableProjectIds.value,
defaultProjectIds.value,
)
const availableProjectIdSet = new Set(availableProjectIds.value)
const nextSelectedProjectIds = nextQueryState.selectedProjectIds.filter((projectId) =>
availableProjectIdSet.has(projectId),