146 lines
4.2 KiB
TypeScript
146 lines
4.2 KiB
TypeScript
import {
|
|
average,
|
|
getPreviousMetric,
|
|
max,
|
|
min,
|
|
round,
|
|
slug,
|
|
sum,
|
|
} from '@openpanel/common';
|
|
import { alphabetIds } from '@openpanel/constants';
|
|
import type { FinalChart } from '@openpanel/validation';
|
|
import type { ConcreteSeries } from './types';
|
|
|
|
/**
|
|
* Format concrete series into FinalChart format (backward compatible)
|
|
* TODO: Migrate frontend to use cleaner ChartResponse format
|
|
*/
|
|
export function format(
|
|
concreteSeries: ConcreteSeries[],
|
|
definitions: Array<{
|
|
id?: string;
|
|
type: 'event' | 'formula';
|
|
displayName?: string;
|
|
formula?: string;
|
|
name?: string;
|
|
}>,
|
|
includeAlphaIds: boolean,
|
|
previousSeries: ConcreteSeries[] | null = null,
|
|
limit: number | undefined = undefined,
|
|
): FinalChart {
|
|
const series = concreteSeries.map((cs) => {
|
|
// Find definition for this series
|
|
const definition = definitions[cs.definitionIndex];
|
|
const alphaId = includeAlphaIds
|
|
? alphabetIds[cs.definitionIndex]
|
|
: undefined;
|
|
|
|
// Build display name with optional alpha ID
|
|
let displayName: string[];
|
|
|
|
// Replace the first name (which is the event name) with the display name if it exists
|
|
const names = cs.name.slice(0);
|
|
if (cs.definition.displayName) {
|
|
names.splice(0, 1, cs.definition.displayName);
|
|
}
|
|
// Add the alpha ID to the first name if it exists
|
|
if (alphaId) {
|
|
displayName = [`(${alphaId}) ${names[0]}`, ...names.slice(1)];
|
|
} else {
|
|
displayName = names;
|
|
}
|
|
|
|
// Calculate metrics for this series
|
|
const counts = cs.data.map((d) => d.count);
|
|
const metrics = {
|
|
sum: sum(counts),
|
|
average: round(average(counts), 2),
|
|
min: min(counts),
|
|
max: max(counts),
|
|
count: cs.data.find((item) => !!item.total_count)?.total_count,
|
|
};
|
|
|
|
// Build event object for compatibility
|
|
const eventName =
|
|
definition?.type === 'formula'
|
|
? definition.displayName || definition.formula || 'Formula'
|
|
: definition?.name || cs.context.event || 'unknown';
|
|
|
|
// Find matching previous series
|
|
const previousSerie = previousSeries?.find(
|
|
(ps) =>
|
|
ps.definitionIndex === cs.definitionIndex &&
|
|
ps.name.slice(1).join(':::') === cs.name.slice(1).join(':::'),
|
|
);
|
|
|
|
return {
|
|
id: cs.id,
|
|
names: displayName,
|
|
// TODO: Do we need this now?
|
|
event: {
|
|
id: definition?.id,
|
|
name: eventName,
|
|
breakdowns: cs.context.breakdowns,
|
|
},
|
|
metrics: {
|
|
...metrics,
|
|
...(previousSerie
|
|
? {
|
|
previous: {
|
|
sum: getPreviousMetric(
|
|
metrics.sum,
|
|
sum(previousSerie.data.map((d) => d.count)),
|
|
),
|
|
average: getPreviousMetric(
|
|
metrics.average,
|
|
round(average(previousSerie.data.map((d) => d.count)), 2),
|
|
),
|
|
min: getPreviousMetric(
|
|
metrics.min,
|
|
min(previousSerie.data.map((d) => d.count)),
|
|
),
|
|
max: getPreviousMetric(
|
|
metrics.max,
|
|
max(previousSerie.data.map((d) => d.count)),
|
|
),
|
|
count: getPreviousMetric(
|
|
metrics.count ?? 0,
|
|
previousSerie.data.find((item) => !!item.total_count)
|
|
?.total_count ?? null,
|
|
),
|
|
},
|
|
}
|
|
: {}),
|
|
},
|
|
data: cs.data.map((item, index) => ({
|
|
date: item.date,
|
|
count: item.count,
|
|
previous: previousSerie?.data[index]
|
|
? getPreviousMetric(
|
|
item.count,
|
|
previousSerie.data[index]?.count ?? null,
|
|
)
|
|
: undefined,
|
|
})),
|
|
};
|
|
});
|
|
|
|
// Sort series by sum (biggest first)
|
|
series.sort((a, b) => b.metrics.sum - a.metrics.sum);
|
|
|
|
// Calculate global metrics
|
|
const allValues = concreteSeries.flatMap((cs) => cs.data.map((d) => d.count));
|
|
const globalMetrics = {
|
|
sum: sum(allValues),
|
|
average: round(average(allValues), 2),
|
|
min: min(allValues),
|
|
max: max(allValues),
|
|
count: undefined as number | undefined,
|
|
};
|
|
|
|
return {
|
|
series: limit ? series.slice(0, limit) : series,
|
|
metrics: globalMetrics,
|
|
};
|
|
}
|