multiple breakpoints

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-06-20 23:25:18 +02:00
parent c07f0d302c
commit cf8617e809
48 changed files with 908 additions and 432 deletions

View File

@@ -16,7 +16,16 @@ import type { IInterval } from '@openpanel/validation';
// Define the data structure
export interface ISerieDataItem {
label: string | null | undefined;
label_0: string | null | undefined;
label_1?: string | null | undefined;
label_2?: string | null | undefined;
label_3?: string | null | undefined;
count: number;
date: string;
}
export interface ISerieDataItemComplete {
labels: string[];
count: number;
date: string;
}
@@ -37,6 +46,39 @@ function roundDate(date: Date, interval: IInterval): Date {
}
}
function filterFalsyAfterTruthy(array: (string | undefined | null)[]) {
let foundTruthy = false;
const filtered = array.filter((item) => {
if (foundTruthy) {
// After a truthy, filter out falsy values
return !!item;
}
if (item) {
// Mark when the first truthy is encountered
foundTruthy = true;
}
// Return all elements until the first truthy is found
return true;
});
if (filtered.some((item) => !!item)) {
return filtered;
}
return [null];
}
function concatLabels(entry: ISerieDataItem): string {
return filterFalsyAfterTruthy([
entry.label_0,
entry.label_1,
entry.label_2,
entry.label_3,
])
.map((label) => label || NOT_SET_VALUE)
.join(':::');
}
// Function to complete the timeline for each label
export function completeSerie(
data: ISerieDataItem[],
@@ -51,23 +93,23 @@ export function completeSerie(
data.forEach((entry) => {
const roundedDate = roundDate(parseISO(entry.date), interval);
const dateKey = format(roundedDate, 'yyyy-MM-dd HH:mm:ss');
const label = entry.label || NOT_SET_VALUE;
const label = concatLabels(entry) || NOT_SET_VALUE;
if (!labelsMap.has(label)) {
labelsMap.set(label, new Map());
}
const labelData = labelsMap.get(label);
labelData?.set(dateKey, (labelData.get(dateKey) || 0) + (entry.count || 0));
const labelData = labelsMap.get(label)!;
labelData.set(dateKey, (labelData.get(dateKey) || 0) + (entry.count || 0));
});
// Complete the timeline for each label
const result: Record<string, ISerieDataItem[]> = {};
const result: Record<string, ISerieDataItemComplete[]> = {};
labelsMap.forEach((counts, label) => {
let currentDate = roundDate(startDate, interval);
result[label] = [];
while (currentDate <= endDate) {
const dateKey = format(currentDate, 'yyyy-MM-dd HH:mm:ss');
result[label]!.push({
label: label,
labels: label.split(':::'),
date: dateKey,
count: counts.get(dateKey) || 0,
});

View File

@@ -15,7 +15,7 @@ export function toDots(
return {
...acc,
[`${path}${key}`]: value,
[`${path}${key}`]: typeof value === 'string' ? value.trim() : value,
};
}, {});
}

View File

@@ -24,10 +24,10 @@ export function getChartSql({
sb.where.projectId = `project_id = ${escape(projectId)}`;
if (event.name !== '*') {
sb.select.label = `${escape(event.name)} as label`;
sb.select.label_0 = `${escape(event.name)} as label_0`;
sb.where.eventName = `name = ${escape(event.name)}`;
} else {
sb.select.label = `'*' as label`;
sb.select.label_0 = `'*' as label_0`;
}
sb.select.count = `count(*) as count`;
@@ -60,11 +60,11 @@ export function getChartSql({
}
breakdowns.forEach((breakdown, index) => {
const key = index === 0 ? 'label' : `label_${index}`;
const key = `label_${index}`;
const value = breakdown.name.startsWith('properties.')
? `mapValues(mapExtractKeyLike(properties, ${escape(
? `arrayMap(x -> trim(x), mapValues(mapExtractKeyLike(properties, ${escape(
breakdown.name.replace(/^properties\./, '').replace('.*.', '.%.')
)}))`
)})))`
: escape(breakdown.name);
sb.select[key] = breakdown.name.startsWith('properties.')
? `arrayElement(${value}, 1) as ${key}`
@@ -125,9 +125,9 @@ export function getEventFiltersWhereClause(filters: IChartEventFilter[]) {
}
if (name.startsWith('properties.')) {
const whereFrom = `mapValues(mapExtractKeyLike(properties, ${escape(
const whereFrom = `arrayMap(x -> trim(x), mapValues(mapExtractKeyLike(properties, ${escape(
name.replace(/^properties\./, '').replace('.*.', '.%.')
)}))`;
)})))`;
switch (operator) {
case 'is': {

View File

@@ -458,14 +458,17 @@ export async function getChartSerie(payload: IGetChartDataInput) {
completeSerie(data, payload.startDate, payload.endDate, payload.interval)
)
.then((series) => {
return Object.keys(series).map((label) => {
return Object.keys(series).map((key) => {
const firstDataItem = series[key]![0]!;
const isBreakdown =
payload.breakdowns.length && !alphabetIds.includes(label as 'A');
const serieLabel = isBreakdown ? label : getEventLegend(payload.event);
payload.breakdowns.length && firstDataItem.labels.length;
const serieLabel = isBreakdown
? firstDataItem.labels
: [getEventLegend(payload.event)];
return {
name: serieLabel,
event: payload.event,
data: series[label]!.map((item) => ({
data: series[key]!.map((item) => ({
...item,
date: toDynamicISODateWithTZ(
item.date,
@@ -523,7 +526,7 @@ export async function getChart(input: IChartInput) {
const final: FinalChart = {
series: series.map((serie) => {
const previousSerie = previousSeries?.find(
(item) => item.name === serie.name
(item) => item.name.join('-') === serie.name.join('-')
);
const metrics = {
sum: sum(serie.data.map((item) => item.count)),
@@ -533,8 +536,8 @@ export async function getChart(input: IChartInput) {
};
return {
id: slug(serie.name),
name: serie.name,
id: slug(serie.name.join('-')),
names: serie.name,
event: {
id: serie.event.id!,
name: serie.event.displayName ?? serie.event.name,

View File

@@ -102,9 +102,9 @@ export const chartRouter = createTRPCRouter({
sb.where.event = `name = ${escape(event)}`;
}
if (property.startsWith('properties.')) {
sb.select.values = `distinct mapValues(mapExtractKeyLike(properties, ${escape(
sb.select.values = `distinct arrayMap(x -> trim(x), mapValues(mapExtractKeyLike(properties, ${escape(
property.replace(/^properties\./, '').replace('.*.', '.%.')
)})) as values`;
)}))) as values`;
} else {
sb.select.values = `distinct ${property} as values`;
}

View File

@@ -40,9 +40,9 @@ export const profileRouter = createTRPCRouter({
sb.from = 'profiles';
sb.where.project_id = `project_id = ${escape(projectId)}`;
if (property.startsWith('properties.')) {
sb.select.values = `distinct mapValues(mapExtractKeyLike(properties, ${escape(
sb.select.values = `distinct arrayMap(x -> trim(x), mapValues(mapExtractKeyLike(properties, ${escape(
property.replace(/^properties\./, '').replace('.*.', '.%.')
)})) as values`;
)}))) as values`;
} else {
sb.select.values = `${property} as values`;
}

View File

@@ -65,7 +65,7 @@ export type Metrics = {
export type IChartSerie = {
id: string;
name: string;
names: string[];
event: {
id: string;
name: string;