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
60 changes: 48 additions & 12 deletions app/components/Package/TrendsChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ function formatXyDataset(
color: accent.value,
temperatureColors,
useArea: true,
dashIndices: dataset
.map((item, index) => (item.hasAnomaly ? index : -1))
.filter(index => index !== -1),
}

if (selectedGranularity === 'weekly' && isWeeklyDataset(dataset)) {
Expand Down Expand Up @@ -275,19 +278,26 @@ function formatXyDataset(
function extractSeriesPoints(
selectedGranularity: ChartTimeGranularity,
dataset: EvolutionData,
): Array<{ timestamp: number; value: number }> {
): Array<{ timestamp: number; value: number; hasAnomaly: boolean }> {
if (selectedGranularity === 'weekly' && isWeeklyDataset(dataset)) {
return dataset.map(d => ({ timestamp: d.timestampEnd, value: d.value }))
return dataset.map(d => ({
timestamp: d.timestampEnd,
value: d.value,
hasAnomaly: !!d.hasAnomaly,
}))
}
if (
(selectedGranularity === 'daily' && isDailyDataset(dataset)) ||
(selectedGranularity === 'monthly' && isMonthlyDataset(dataset)) ||
(selectedGranularity === 'yearly' && isYearlyDataset(dataset))
) {
return (dataset as Array<{ timestamp: number; value: number }>).map(d => ({
timestamp: d.timestamp,
value: d.value,
}))
return (dataset as Array<{ timestamp: number; value: number; hasAnomaly?: boolean }>).map(
d => ({
timestamp: d.timestamp,
value: d.value,
hasAnomaly: !!d.hasAnomaly,
}),
)
}
return []
}
Expand Down Expand Up @@ -380,6 +390,11 @@ const isEstimationGranularity = computed(
const supportsEstimation = computed(
() => isEstimationGranularity.value && selectedMetric.value !== 'contributors',
)

const hasDownloadAnomalies = computed(() =>
normalisedDataset.value?.some(datapoint => !!datapoint.dashIndices.length),
)

const shouldRenderEstimationOverlay = computed(() => !pending.value && supportsEstimation.value)

const startDate = usePermalink<string>('start', '', {
Expand Down Expand Up @@ -955,11 +970,13 @@ const effectiveDataSingle = computed<EvolutionData>(() => {
granularity: displayedGranularity.value,
})
}

return applyDataCorrection(
data as Array<{ value: number }>,
settings.value.chartFilter,
) as EvolutionData
}

return data
})

Expand Down Expand Up @@ -991,7 +1008,10 @@ const chartData = computed<{
const granularity = displayedGranularity.value

const timestampSet = new Set<number>()
const pointsByPackage = new Map<string, Array<{ timestamp: number; value: number }>>()
const pointsByPackage = new Map<
string,
Array<{ timestamp: number; value: number; hasAnomaly?: boolean }>
>()

for (const pkg of names) {
let data = state.evolutionsByPackage[pkg] ?? []
Expand All @@ -1005,6 +1025,7 @@ const chartData = computed<{
) as EvolutionData
}
const points = extractSeriesPoints(granularity, data)

pointsByPackage.set(pkg, points)
for (const p of points) timestampSet.add(p.timestamp)
}
Expand All @@ -1014,15 +1035,23 @@ const chartData = computed<{

const dataset: VueUiXyDatasetItem[] = names.map(pkg => {
const points = pointsByPackage.get(pkg) ?? []
const map = new Map<number, number>()
for (const p of points) map.set(p.timestamp, p.value)
const valueByTimestamp = new Map<number, number>()
const anomalyTimestamps = new Set<number>()
for (const p of points) {
valueByTimestamp.set(p.timestamp, p.value)
if (p.hasAnomaly) anomalyTimestamps.add(p.timestamp)
}

const series = dates.map(t => map.get(t) ?? 0)
const series = dates.map(t => valueByTimestamp.get(t) ?? 0)
const dashIndices = dates
.map((t, index) => (anomalyTimestamps.has(t) ? index : -1))
.filter(index => index !== -1)

const item: VueUiXyDatasetItem = {
name: pkg,
type: 'line',
series,
dashIndices,
} as VueUiXyDatasetItem

if (isListedFramework(pkg)) {
Expand All @@ -1045,6 +1074,7 @@ const normalisedDataset = computed(() => {
return {
...d,
series: [...d.series.slice(0, -1), projectedLastValue],
dashIndices: d.dashIndices ?? [],
}
})
})
Expand Down Expand Up @@ -1408,7 +1438,10 @@ function drawSvgPrintLegend(svg: Record<string, any>) {
})

// Inject the estimation legend item when necessary
if (supportsEstimation.value && !isEndDateOnPeriodEnd.value && !isZoomed.value) {
if (
(supportsEstimation.value && !isEndDateOnPeriodEnd.value && !isZoomed.value) ||
hasDownloadAnomalies.value
) {
seriesNames.push(`
<line
x1="${svg.drawingArea.left + 12}"
Expand Down Expand Up @@ -1955,7 +1988,10 @@ watch(selectedMetric, value => {
</template>

<!-- Estimation extra legend item -->
<div class="flex gap-1 place-items-center" v-if="supportsEstimation">
<div
class="flex gap-1 place-items-center"
v-if="supportsEstimation || hasDownloadAnomalies"
>
<svg viewBox="0 0 20 2" width="20">
<line
x1="0"
Expand Down
17 changes: 14 additions & 3 deletions app/types/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,28 @@ export type DateRangeFields = {
endDate?: string
}

export type DailyDataPoint = { value: number; day: string; timestamp: number }
export type DailyDataPoint = { value: number; day: string; timestamp: number; hasAnomaly?: boolean }
export type WeeklyDataPoint = {
value: number
weekKey: string
weekStart: string
weekEnd: string
timestampStart: number
timestampEnd: number
hasAnomaly?: boolean
}
export type MonthlyDataPoint = {
value: number
month: string
timestamp: number
hasAnomaly?: boolean
}
export type YearlyDataPoint = {
value: number
year: string
timestamp: number
hasAnomaly?: boolean
}
export type MonthlyDataPoint = { value: number; month: string; timestamp: number }
export type YearlyDataPoint = { value: number; year: string; timestamp: number }

export type EvolutionData =
| DailyDataPoint[]
Expand Down
2 changes: 1 addition & 1 deletion app/utils/download-anomalies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ export function applyBlocklistCorrection(opts: {
for (let i = 0; i < count; i++) {
const t = (i + 1) / (count + 1)
result[affectedIndices[i]!]!.value = Math.round(startVal + t * (endVal - startVal))
result[affectedIndices[i]!]!.hasAnomaly = true
}
}

return result as EvolutionData
}
Loading