feat: dont show analytics incomplete current day revenue

This commit is contained in:
tdgao
2026-07-29 13:08:29 -07:00
parent 9ccaab5fad
commit 3808b2fc19
7 changed files with 80 additions and 9 deletions
@@ -27,6 +27,7 @@ import {
LineController, LineController,
LineElement, LineElement,
PointElement, PointElement,
type ScriptableLineSegmentContext,
Tooltip, Tooltip,
} from 'chart.js' } from 'chart.js'
@@ -446,6 +447,12 @@ function buildDatasets() {
pointHoverBackgroundColor: colors.borderColor, pointHoverBackgroundColor: colors.borderColor,
pointHoverBorderWidth: 0, pointHoverBorderWidth: 0,
pointHitRadius: 16, pointHitRadius: 16,
segment: dataset.lastDataPointUnavailable
? {
borderColor: (context: ScriptableLineSegmentContext) =>
context.p1DataIndex === dataset.data.length - 1 ? 'transparent' : undefined,
}
: undefined,
stack: props.stacked ? 'analytics' : undefined, stack: props.stacked ? 'analytics' : undefined,
} }
}) })
@@ -59,7 +59,9 @@
<span class="font-medium text-primary"> <span class="font-medium text-primary">
{{ formatMessage(analyticsChartMessages.total) }} {{ formatMessage(analyticsChartMessages.total) }}
</span> </span>
<span class="font-semibold text-contrast">{{ formattedTotal }}</span> <span class="font-semibold" :class="totalHasData ? 'text-contrast' : 'text-primary'">
{{ formattedTotal }}
</span>
</div> </div>
<div <div
v-for="entry in entries" v-for="entry in entries"
@@ -111,7 +113,11 @@
:class="[ :class="[
'shrink-0', 'shrink-0',
entry.isPreviousPeriod ? 'font-medium text-secondary' : 'font-semibold', entry.isPreviousPeriod ? 'font-medium text-secondary' : 'font-semibold',
entry.hidden ? 'text-primary line-through opacity-70' : 'text-contrast', entry.hidden
? 'text-primary line-through opacity-70'
: entry.noData
? 'text-primary'
: 'text-contrast',
]" ]"
> >
{{ entry.formattedValue }} {{ entry.formattedValue }}
@@ -148,6 +154,7 @@ export type AnalyticsChartTooltipEntry = {
tooltip?: string tooltip?: string
color: string color: string
formattedValue: string formattedValue: string
noData: boolean
hidden: boolean hidden: boolean
toggleDisabled: boolean toggleDisabled: boolean
isPreviousPeriod?: boolean isPreviousPeriod?: boolean
@@ -164,6 +171,7 @@ const props = defineProps<{
chartStart: Date | null chartStart: Date | null
chartEnd: Date | null chartEnd: Date | null
formattedTotal: string formattedTotal: string
totalHasData: boolean
entries: AnalyticsChartTooltipEntry[] entries: AnalyticsChartTooltipEntry[]
containerWidth: number containerWidth: number
containerHeight: number containerHeight: number
@@ -64,6 +64,7 @@
:chart-start="chartRangeBounds?.start ?? null" :chart-start="chartRangeBounds?.start ?? null"
:chart-end="chartRangeBounds?.end ?? null" :chart-end="chartRangeBounds?.end ?? null"
:formatted-total="hoverFormattedTotal" :formatted-total="hoverFormattedTotal"
:total-has-data="hoverHasData"
:entries="hoverEntries" :entries="hoverEntries"
:container-width="containerSize.width" :container-width="containerSize.width"
:container-height="containerSize.height" :container-height="containerSize.height"
@@ -89,6 +90,7 @@ import type {
AnalyticsGroupByPreset, AnalyticsGroupByPreset,
} from '~/providers/analytics/analytics' } from '~/providers/analytics/analytics'
import { analyticsChartMessages } from '../../analytics-messages.ts'
import type { import type {
AnalyticsChartLegendEntry, AnalyticsChartLegendEntry,
AnalyticsChartRangeBounds, AnalyticsChartRangeBounds,
@@ -188,7 +190,26 @@ const hoverTotalValue = computed(() => {
}, 0) }, 0)
}) })
function hasDatasetDataAtSlice(dataset: ChartDataset | undefined, sliceIndex: number) {
if (!dataset) return false
return !dataset.lastDataPointUnavailable || sliceIndex !== dataset.data.length - 1
}
const hoverHasData = computed(() => {
if (hoverState.sliceIndex === null) return false
const sliceIndex = hoverState.sliceIndex
return props.currentLegendEntries.some((legendEntry) => {
if (legendEntry.hidden) return false
const dataset = props.chartDatasetById.get(legendEntry.id)
return hasDatasetDataAtSlice(dataset, sliceIndex)
})
})
const hoverFormattedTotal = computed(() => { const hoverFormattedTotal = computed(() => {
if (!hoverHasData.value) {
return formatMessage(analyticsChartMessages.noData)
}
if (props.isRatioMode) { if (props.isRatioMode) {
return hoverTotalValue.value > 0 ? '100%' : '0%' return hoverTotalValue.value > 0 ? '100%' : '0%'
} }
@@ -203,6 +224,7 @@ const hoverEntries = computed(() => {
return props.legendEntries.map((legendEntry) => { return props.legendEntries.map((legendEntry) => {
const dataset = props.chartDatasetById.get(legendEntry.id) const dataset = props.chartDatasetById.get(legendEntry.id)
const value = dataset?.data[sliceIndex] ?? 0 const value = dataset?.data[sliceIndex] ?? 0
const hasData = hasDatasetDataAtSlice(dataset, sliceIndex)
const ratioValue = legendEntry.hidden || totalValue === 0 ? 0 : (value / totalValue) * 100 const ratioValue = legendEntry.hidden || totalValue === 0 ? 0 : (value / totalValue) * 100
return { return {
projectId: legendEntry.id, projectId: legendEntry.id,
@@ -210,9 +232,12 @@ const hoverEntries = computed(() => {
projectName: legendEntry.projectName, projectName: legendEntry.projectName,
tooltip: legendEntry.tooltip, tooltip: legendEntry.tooltip,
color: legendEntry.color, color: legendEntry.color,
formattedValue: props.isRatioMode formattedValue: hasData
? `${ratioValue.toFixed(1)}%` ? props.isRatioMode
: formatMetricValue(value, props.activeStat, formatNumber, formatMessage), ? `${ratioValue.toFixed(1)}%`
: formatMetricValue(value, props.activeStat, formatNumber, formatMessage)
: formatMessage(analyticsChartMessages.noData),
noData: !hasData,
hidden: legendEntry.hidden, hidden: legendEntry.hidden,
toggleDisabled: !legendEntry.hidden && isLegendEntryToggleDisabled(legendEntry), toggleDisabled: !legendEntry.hidden && isLegendEntryToggleDisabled(legendEntry),
isPreviousPeriod: legendEntry.isPreviousPeriod, isPreviousPeriod: legendEntry.isPreviousPeriod,
@@ -38,6 +38,7 @@ export type ChartDataset = {
projectName?: string projectName?: string
tooltip?: string tooltip?: string
data: number[] data: number[]
lastDataPointUnavailable?: boolean
borderColor: string borderColor: string
backgroundColor: string backgroundColor: string
borderDash?: number[] borderDash?: number[]
@@ -32,6 +32,7 @@ import {
type ChartDataset, type ChartDataset,
getChartDatasetTotal, getChartDatasetTotal,
getShortHourlyAxisTickLimit, getShortHourlyAxisTickLimit,
getSliceBucketRange,
getSliceCount, getSliceCount,
shouldCapitalizeBreakdownLabel, shouldCapitalizeBreakdownLabel,
} from './analytics-chart-utils' } from './analytics-chart-utils'
@@ -161,8 +162,8 @@ export function useAnalyticsChartDatasets(
) )
: undefined : undefined
}) })
const chartDatasetsByStat = computed<Record<AnalyticsDashboardStat, ChartDataset[]>>(() => const chartDatasetsByStat = computed<Record<AnalyticsDashboardStat, ChartDataset[]>>(() => {
buildDatasetsByStat( const datasets = buildDatasetsByStat(
context.displayedTimeSlices.value, context.displayedTimeSlices.value,
selectedProjects.value, selectedProjects.value,
legendPalette.value, legendPalette.value,
@@ -175,8 +176,20 @@ export function useAnalyticsChartDatasets(
showProjectVersionNames.value ? context.getVersionProjectName : undefined, showProjectVersionNames.value ? context.getVersionProjectName : undefined,
formatMessage, formatMessage,
sliceCount.value, sliceCount.value,
), )
) const nextFetchRequest = context.displayedFetchRequest.value
if (
nextFetchRequest &&
context.displayedSelectedGroupBy.value === 'day' &&
isLastBucketCurrentDay(nextFetchRequest.time_range, sliceCount.value)
) {
datasets.revenue = datasets.revenue.map((dataset) => ({
...dataset,
lastDataPointUnavailable: true,
}))
}
return datasets
})
const previousChartDatasetsByStat = computed<Record<AnalyticsDashboardStat, ChartDataset[]>>(() => const previousChartDatasetsByStat = computed<Record<AnalyticsDashboardStat, ChartDataset[]>>(() =>
buildDatasetsByStat( buildDatasetsByStat(
context.displayedPreviousTimeSlices.value, context.displayedPreviousTimeSlices.value,
@@ -406,6 +419,16 @@ function buildDatasetsByStat(
return datasetsByStat return datasetsByStat
} }
function isLastBucketCurrentDay(timeRange: Labrinth.Analytics.v3.TimeRange, sliceCount: number) {
const lastBucket = getSliceBucketRange(timeRange, sliceCount, sliceCount - 1)
const todayStart = new Date()
todayStart.setHours(0, 0, 0, 0)
const tomorrowStart = new Date(todayStart)
tomorrowStart.setDate(tomorrowStart.getDate() + 1)
return lastBucket.start < tomorrowStart && lastBucket.end > todayStart
}
function sortDatasetsByTotal(datasets: ChartDataset[]) { function sortDatasetsByTotal(datasets: ChartDataset[]) {
return [...datasets] return [...datasets]
.sort((a, b) => { .sort((a, b) => {
@@ -688,6 +688,10 @@ export const analyticsChartMessages = defineMessages({
id: 'analytics.chart.tooltip.hide-entry', id: 'analytics.chart.tooltip.hide-entry',
defaultMessage: 'Hide {name} in graph', defaultMessage: 'Hide {name} in graph',
}, },
noData: {
id: 'analytics.chart.tooltip.no-data',
defaultMessage: 'No data',
},
durationDays: { durationDays: {
id: 'analytics.chart.tooltip.duration.days', id: 'analytics.chart.tooltip.duration.days',
defaultMessage: '{count, plural, one {# day} other {# days}}', defaultMessage: '{count, plural, one {# day} other {# days}}',
@@ -191,6 +191,9 @@
"analytics.chart.tooltip.hide-entry": { "analytics.chart.tooltip.hide-entry": {
"message": "Hide {name} in graph" "message": "Hide {name} in graph"
}, },
"analytics.chart.tooltip.no-data": {
"message": "No data"
},
"analytics.chart.tooltip.pinned": { "analytics.chart.tooltip.pinned": {
"message": "Chart tooltip pinned" "message": "Chart tooltip pinned"
}, },