You've already forked AstralRinth
11b2b6e6c0
* feat: implement cancel/apply for custom timeframe range picker * feat: implement dot for showing todays date * feat: add max date to be today and show todays date * feat: if ratio mode, dont show total * feat: implement show more batching excess lines into "Other" bucket * refactor: pnpm prepr * feat: add pick and plop for date range start/end dates * feat: implement reset query button * feat: clear button to clear breakdown * feat: more aggressively trim allowed minimum group by option * fix: dont show project status filter when from project settings/analytics * fix: clear selected X above number when appropriate * feat: graph style updates and dont show year in x axis unless more than 2 year timeframe * fix: loading state to include legend in blur * feat: add project icon to project select * feat: filter out draft projects from analytics * feat: implement multiselect sections headers, project select org sections, and project options icons * feat: implement click and drag to select date range * feat: implement windows history for query builder * revert: no longer switch breakdown/filter option if same category * feat: implement showing project for project version breakdown/filter when there are multiple projects * feat: implement modrinth sided events * fix: border radius * feat: implement analytics range highlight * fix: loading state showing empty state text * refactor: pnpm prepr * feat: improve dropdown filter bar and multiselect performance * fix: multiselect keyboard use * fix: graph overflow issues * fix: loading state text on table * feat: implement tooltip scroll * fix: adjust charts event tooltip * feat: shorten time to not repeat am/pm * feat: implement query params for graph component settings * fix: qa * feat: add reset timeframe button * fix: legend colors moving between metric by determining color based on only downloads metric index * feat: implement auto switching temporarily to group by day for renvenue metric and disable revenue metric for time range < 2 days * fix: change to > 1 day * fix: custom timeframe picker * feat: implement big performance improvement for table * feat: implement hover on legend to highlight graph * fix: defer commit in query builder/filter and style fixes * feat: more performance optimization to analytics dashboard state, chart, and table * feat: add tooltip for other item * feat: improve custom time frame range select * feat: implement analytics events admin page * fix: switch column order * pnpm prepr * feat: implement mock analytics events * feat: improve analytics events admin page * feat: focus title input on analytics create event modal * fix: remove labels annoying * feat: hook up analytics events backend * fix: type error * feat: reduce combobox padding * feat: reduce padding on multiselect * feat: add overlay scrollbar for combobox * feat: a bunch of style fixes to combobox, multiselect and dropdown filter bar * feat: MORE PADDING fixes * feat: use user_agent for download source * Revert "feat: use user_agent for download source" This reverts commit d6dc8a99f11f94660872427796cdcf6fc93bb21d. * fix: query filter project version lag and borked virtualization * feat: rename breakdown "none" to "project" * feat: implement right side checkmark for multiselect * feat: keep crossed out legend items still shown in tooltip but also crossed out * fix: focus styles * fix: focus styles pt2 * feat: implement filter by top 8 * fix: preview is incorrect when selecting same date in range date picker * feat (playtest): cross out legend items in tooltip and allow hide/show in tooltip * feat (playtest): table component controls what graph shows * feat: change download source to use user_agent * feat: fix click to cross out in legend * feat: add hover legend item to highlight line in tooltip * fix: export csv to always be dropdown * feat: implement breakdown = none * performance: frontend memory reduction * performance: reduce memory usage from project versions query by keeping only whats necessary * fix: table checked items not in graph if 0 * feat: add shift click to select a range in table * performance: add caching for metric types so switching between them is snappy * performance: batch analytics requests by 15 project ids, with 150 ms delay between, so backend is happy * feat: add analytics table search * refactor: pnpm prepr * fix: query filter options not coming in from analytics fetch * feat: remove breakdown = none when there are multiple projects * feat: improve table sorting * feat: sort projects in project dropdown * fix: getting project name for project versions * fix: add loading state for filter and parallel fetch * performance: use precomputed map for project version options to remove first hover lag * feat: dropdown filter always open on one side and improve styles * fix: custom time range picker being weird * refactor: pnpm prepr * fix: add back in batch with 300ms interval for projects to prevent backend rate limiting * performance: only do queries to populate graph first before other analytics queries * fix: QA polish issues around style and copy * feat: dont show select all when its just one item in section * fix: bugs with ratio mode and hiding chart lines * fix: adjust padding in combobox and multiselect and fix not unfocusing when deselect * fix: small styles * fix: polish admin analytic events * fix: keep scroll position with selection action row appearing when selecting one * feat: add subheading in graph for showing N items from table * feat: add unmonetized explaination tooltip * performance: implement limit on how many lines can be shown in graph * feat: mobile pass * refactor: pnpm prepr * add clear button * feat: add time in analytics event and normalize date/time so its correct to timezones * fix: padding * feat: implement show prev period toggle * feat: extract TimeFramePicker to packages/ui * fix: adjust style * feat: keep table selected persisted in query parameter * fix: style on prev item value in legend * fix: when breakdown switches, reset selected series * fix: tooltip styles * feat: change project selection to reset to show top 8 only if reconciled down to 0 items * feat: implement show top 8 button in graph subheading * fix: rename download type to download reason * fix: formatting label for table * feat: persist table sort by and sort direction * fix: show top 8 button in graph not defaulting to top 8 for other metrics * feat: implement prev period analytics fetch into the same current period fetch by shifting start date * refactor: pnpm prepr * fix: remove number if its just top 1 * fix: brief select items empty state when switch breakdown * feat: implement format table playtime column * feat: update export csv filename * feat: change playtime column to display in hours * refactor: pnpm prepr * fix: still download type in filter * feat: update analytics tooltip * fix: wrong all projects icon * feat: force legend order and graph colour for monetization * refactor: pnpm prepr * fix: multiselect and combobox sizes * fix: chart icon add hover delay * feat: (to playtest) implement multiple breakdowns * fix: couple UX things for multiple breakdown * fix: cannot unpin on page click * fix: multiple breakdown legend and tooltip labels * feat: add right side checkmark for dropdown filtr bar * feat: enabling prev period will cross out prev for current ones already crossed out * feat-mobile: remove drag to select time frame in graph * feat-mobile: dropdown filter to replace dropdown for submenus on small screen * feat-mobile: time frame picker to use different start and end date pickers for mobile * fix-mobile: fix multiselect scroll on mobile * feat: consolidate is mobile ref into context * fix-mobile: combobox and multiselect scroll bug when mobile search bar open, fix timeframe picker mobile pick date, and dropdown filter bar click outside to close * fix-mobile: smaller metric card font * fix: dropdown filter bar scroll while search * feat: implement project side events * feat: implement better mobile view design for query builder * feat: handle events overflow * small: add select none * feat: remove clear project and breakdown * fix: event icon hover color * feat: default hide project events if there are multiple projects, and default show if only 1 project * feat: implement analytics performance updates, including facets, and v3 user projects * feat: grey out dimmed lined on legend item hover * feat-mobile: style fixes * add close on select prop * feat: add close on select for time frame picker mobile * feat: date picker default read only * refactor: pnpm prepr * feat: default to projects breakdown instead of no breakdown with multiple projects * fix-mobile: improve graph touch interactions * small: 2 sig figs on playtime * feat: deduplicate version uploads that have same version number and are uploaded on same day * fix: analytics events grouping causing overflow * feat: improve performance on analytics events grouping * fix: tooltip expanding page width briefly * fix: prevent double tap to zoom on inputs * feat: add click to show chart event for mobile * fix: toggle not having touch manipulation * fix: chart tooltip scroll in mobile * fix: remove project breakdownoption as it is default breakdown when none are selected * fix: dropdown filter bar briefly empty when switching pages in mobile * feat: keep tooltip open after drag in mobile * fix: using plural instead of single for project breakdown * fix: date picker scrolling page after picking date in mobile by suppressing focus * fix: callback to Organization instead of org id * feat: improve chart tooltip date range label formatter to be much more consistent * feat: tap to toggle event tooltip * fix: add user select none on graph and fix zoom into download threshold input * fix: frontend still filtering after backend already filters * feat: fix emptys state height content shift * fix: qa issues * fix: a number of qa issues - Hide project events based on visible project legend/table selection - Filter project status events by end status and add explicit copy for approved, private, and unlisted - Style Modrinth analytics events with blue icon, marker, guide, and range borders - Add scroll fade shadows to analytics chart and event tooltips - Show previous-period date range in the chart tooltip - Make project breakdown conditional on multiple selected projects and allow no breakdown when none are selected - Add breakdown selection actions and fix “Group by day” copy * feat: implement graph controls dropdown * fix date picker typing into time input * fix: styles in events table * small: style * feat: implement using new backend facets route * feat: implement user get all projects * performance: deter non-critical fetches to after analytics is in * fix: refreshing causes multiple projects to do breakdown=none * performance: cache project version options to fix lag on open sub menu * refactor: remove chart event height being controlled by parent * feat: update controls dropdown to have fainter border * fix: loading bar not fading away * fix: cannot click in graph * feat: dont conditionally show multiselect selection actions * fix: z-index and padding issues * fix: project events incorrectly toggling on for first page load * feat: remove show more and show less in legend, always show all * fix: playtime y axis labels * feat: improve y axis formatting for playtime and others * feat: use tabs for game version select, and remove prev period when change breakdown or project selection * refactor: pnpm prepr * feat: change hidden legend items to not contribute to ratio percentages * feat: event icon consume scroll for tooltip panel * feat: remove gap inside chart tooltip * feat: add gap for date picker 2 calendar view * feat: improve analytics events grouping logic for modrinth events to be close to target * pnpm prepr * fix: cant click in gap in toggle * fix: bugs around selected series from table not persisting with timeframe or filter changes * refactor: kabab case * refactor: split up large analytics chart and table component files into smaller components and ts modules * fix: legend is stale after resetting query * refactor: split up giant analytics provider with utils * i18n pass * revert: format number composable change * fix: playtime was choosing y axis ticks in seconds instead of hours * refactor: rename folder that with components to match main component name * refactor: same rename for analytics table for consistency * refactor: name main components to index.vue and keep folder name as component name * refactor: pnpm prepr * refactor: rename types * refactor: move query builder types into types file and move components into components/analytics-dashboard * refactor: colocate query builder url with analytics-dashboard component * refactor: pnpm prepr:frontend * fix: download threshold not width fit * fix: no option to see release/all game versions in selected filter dropdown * fix: game version dropdown width --------- Signed-off-by: Truman Gao <106889354+tdgao@users.noreply.github.com> Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
220 lines
7.3 KiB
Vue
220 lines
7.3 KiB
Vue
<template>
|
|
<div
|
|
ref="chartContainer"
|
|
class="relative -ml-4 h-[460px] select-none"
|
|
@click="onChartClick"
|
|
@wheel.capture="onChartWheel"
|
|
>
|
|
<div :class="['h-full']">
|
|
<div v-if="showEmptyChartState" class="flex h-full items-center justify-center rounded-xl">
|
|
<div v-if="!isDataLoading" class="relative bottom-6 text-base font-normal text-secondary">
|
|
{{ emptyChartMessage }}
|
|
</div>
|
|
</div>
|
|
<template v-else>
|
|
<ClientOnly>
|
|
<AnalyticsChartClient
|
|
:type="chartType"
|
|
:fill="isArea"
|
|
:stacked="isStacked"
|
|
:ratio-mode="isRatioMode"
|
|
:datasets="visibleChartDatasets"
|
|
:labels="chartLabels"
|
|
:x-axis-tick-limit="xAxisTickLimit"
|
|
:active-stat="activeStat"
|
|
:pinned-slice-index="pinnedSliceIndex"
|
|
:highlighted-dataset-id="highlightedChartDatasetId"
|
|
@hover="onChartHover"
|
|
@geometry="onChartGeometry"
|
|
@pinned-drag="onPinnedDrag"
|
|
@range-select="onRangeSelect"
|
|
@touch-drag="onTouchDragEnd"
|
|
/>
|
|
</ClientOnly>
|
|
<AnalyticsChartEvents
|
|
v-if="hasVisibleTimelineEvents"
|
|
:events="visibleTimelineEvents"
|
|
:active-stat="activeStat"
|
|
:group-by="selectedGroupBy"
|
|
:chart-start="chartRangeBounds?.start ?? null"
|
|
:chart-end="chartRangeBounds?.end ?? null"
|
|
:geometry="chartGeometry"
|
|
/>
|
|
<div
|
|
v-if="showHoverGuide"
|
|
aria-hidden="true"
|
|
class="pointer-events-none absolute bottom-0 left-0 top-0 z-10 mb-[1.8rem] mt-2.5 border-0 border-l border-solid border-contrast opacity-25"
|
|
:style="{ transform: `translate(${hoverState.x}px, 0)` }"
|
|
/>
|
|
<div
|
|
v-if="showPinnedGuide"
|
|
aria-hidden="true"
|
|
class="pointer-events-none absolute bottom-0 left-0 top-0 z-10 mb-[1.8rem] mt-2.5 border-0 border-l border-dashed border-green opacity-75"
|
|
:style="{ transform: `translate(${hoverState.x}px, 0)` }"
|
|
/>
|
|
<AnalyticsChartTooltip
|
|
ref="chartTooltip"
|
|
:visible="hoverState.visible"
|
|
:x="hoverState.x"
|
|
:y="hoverState.y"
|
|
:start="hoverBucketRange?.start ?? null"
|
|
:end="hoverBucketRange?.end ?? null"
|
|
:previous-start="previousHoverBucketRange?.start ?? null"
|
|
:previous-end="previousHoverBucketRange?.end ?? null"
|
|
:chart-start="chartRangeBounds?.start ?? null"
|
|
:chart-end="chartRangeBounds?.end ?? null"
|
|
:formatted-total="hoverFormattedTotal"
|
|
:entries="hoverEntries"
|
|
:container-width="containerSize.width"
|
|
:container-height="containerSize.height"
|
|
:pinned="isHoverPinned"
|
|
:ratio-mode="isRatioMode"
|
|
:capitalize-labels="shouldCapitalizeDatasetLabels"
|
|
:shift-key-pressed="isShiftKeyPressed"
|
|
@entry-click="(datasetId, shiftKey) => emit('entry-click', datasetId, shiftKey)"
|
|
@entry-hover="emit('entry-hover', $event)"
|
|
@entry-hover-clear="emit('entry-hover-clear', $event)"
|
|
/>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { Labrinth } from '@modrinth/api-client'
|
|
import { useFormatNumber, useVIntl } from '@modrinth/ui'
|
|
|
|
import type {
|
|
AnalyticsDashboardStat,
|
|
AnalyticsGroupByPreset,
|
|
} from '~/providers/analytics/analytics'
|
|
|
|
import type {
|
|
AnalyticsChartLegendEntry,
|
|
AnalyticsChartRangeBounds,
|
|
} from '../analytics-chart-types.ts'
|
|
import type { ChartDataset } from '../analytics-chart-utils.ts'
|
|
import { formatMetricValue } from '../analytics-chart-utils.ts'
|
|
import AnalyticsChartClient from '../AnalyticsChart.client.vue'
|
|
import AnalyticsChartEvents, { type AnalyticsChartEvent } from './AnalyticsChartEvents.vue'
|
|
import AnalyticsChartTooltip from './AnalyticsChartTooltip.vue'
|
|
import { useAnalyticsChartInteractions } from './use-analytics-chart-interactions.ts'
|
|
|
|
const props = defineProps<{
|
|
chartType: 'line' | 'bar'
|
|
isArea: boolean
|
|
isStacked: boolean
|
|
isRatioMode: boolean
|
|
isDataLoading: boolean
|
|
showEmptyChartState: boolean
|
|
emptyChartMessage: string
|
|
visibleChartDatasets: ChartDataset[]
|
|
chartLabels: string[]
|
|
xAxisTickLimit?: number
|
|
activeStat: AnalyticsDashboardStat
|
|
highlightedChartDatasetId: string | null
|
|
hasVisibleTimelineEvents: boolean
|
|
visibleTimelineEvents: AnalyticsChartEvent[]
|
|
selectedGroupBy: AnalyticsGroupByPreset
|
|
chartRangeBounds: AnalyticsChartRangeBounds | null
|
|
fetchRequest: Labrinth.Analytics.v3.FetchRequest | null
|
|
sliceCount: number
|
|
shouldShowPreviousPeriod: boolean
|
|
allChartDatasets: ChartDataset[]
|
|
currentLegendEntries: AnalyticsChartLegendEntry[]
|
|
legendEntries: AnalyticsChartLegendEntry[]
|
|
chartDatasetById: Map<string, ChartDataset>
|
|
hoverRatioSliceTotals: number[]
|
|
shouldCapitalizeDatasetLabels: boolean
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
'range-select': [start: Date, end: Date, groupBy: AnalyticsGroupByPreset]
|
|
'entry-click': [datasetId: string, shiftKey: boolean]
|
|
'entry-hover': [datasetId: string]
|
|
'entry-hover-clear': [datasetId: string]
|
|
}>()
|
|
|
|
const formatNumber = useFormatNumber()
|
|
const { formatMessage } = useVIntl()
|
|
const {
|
|
chartContainer,
|
|
chartTooltip,
|
|
chartGeometry,
|
|
containerSize,
|
|
hoverState,
|
|
isHoverPinned,
|
|
isShiftKeyPressed,
|
|
onChartHover,
|
|
onPinnedDrag,
|
|
onTouchDragEnd,
|
|
onChartGeometry,
|
|
onRangeSelect,
|
|
onChartClick,
|
|
onChartWheel,
|
|
pinnedSliceIndex,
|
|
showHoverGuide,
|
|
showPinnedGuide,
|
|
hoverBucketRange,
|
|
previousHoverBucketRange,
|
|
} = useAnalyticsChartInteractions({
|
|
isDataLoading: computed(() => props.isDataLoading),
|
|
fetchRequest: computed(() => props.fetchRequest),
|
|
sliceCount: computed(() => props.sliceCount),
|
|
chartLabels: computed(() => props.chartLabels),
|
|
allChartDatasets: computed(() => props.allChartDatasets),
|
|
chartRangeBounds: computed(() => props.chartRangeBounds),
|
|
shouldShowPreviousPeriod: computed(() => props.shouldShowPreviousPeriod),
|
|
onRangeSelected: (start, end, groupBy) => emit('range-select', start, end, groupBy),
|
|
})
|
|
|
|
const hoverTotalValue = computed(() => {
|
|
if (hoverState.sliceIndex === null) return 0
|
|
const sliceIndex = hoverState.sliceIndex
|
|
if (props.isRatioMode) return props.hoverRatioSliceTotals[sliceIndex] ?? 0
|
|
|
|
return props.currentLegendEntries.reduce((sum, legendEntry) => {
|
|
if (legendEntry.hidden) return sum
|
|
const dataset = props.chartDatasetById.get(legendEntry.id)
|
|
return sum + (dataset?.data[sliceIndex] ?? 0)
|
|
}, 0)
|
|
})
|
|
|
|
const hoverFormattedTotal = computed(() => {
|
|
if (props.isRatioMode) {
|
|
return hoverTotalValue.value > 0 ? '100%' : '0%'
|
|
}
|
|
return formatMetricValue(hoverTotalValue.value, props.activeStat, formatNumber, formatMessage)
|
|
})
|
|
|
|
const hoverEntries = computed(() => {
|
|
if (hoverState.sliceIndex === null) return []
|
|
const sliceIndex = hoverState.sliceIndex
|
|
const totalValue = hoverTotalValue.value
|
|
|
|
return props.legendEntries.map((legendEntry) => {
|
|
const dataset = props.chartDatasetById.get(legendEntry.id)
|
|
const value = dataset?.data[sliceIndex] ?? 0
|
|
const ratioValue = legendEntry.hidden || totalValue === 0 ? 0 : (value / totalValue) * 100
|
|
return {
|
|
projectId: legendEntry.id,
|
|
name: legendEntry.name,
|
|
projectName: legendEntry.projectName,
|
|
color: legendEntry.color,
|
|
formattedValue: props.isRatioMode
|
|
? `${ratioValue.toFixed(1)}%`
|
|
: formatMetricValue(value, props.activeStat, formatNumber, formatMessage),
|
|
hidden: legendEntry.hidden,
|
|
toggleDisabled: !legendEntry.hidden && isLegendEntryToggleDisabled(legendEntry),
|
|
isPreviousPeriod: legendEntry.isPreviousPeriod,
|
|
}
|
|
})
|
|
})
|
|
|
|
function isLegendEntryToggleDisabled(legendEntry: AnalyticsChartLegendEntry) {
|
|
if (legendEntry.hidden) return false
|
|
const visibleCount = props.legendEntries.filter((entry) => !entry.hidden).length
|
|
return visibleCount <= 1
|
|
}
|
|
</script>
|