add bar chart support and other fixes

This commit is contained in:
Carl-Gerhard Lindesvärd
2023-10-27 20:37:08 +02:00
parent d8c587ef90
commit ed7ed2ab24
26 changed files with 713 additions and 226 deletions

View File

@@ -1,7 +1,7 @@
import { z } from "zod";
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import { db } from "@/server/db";
import { pipe, sort, uniq } from "ramda";
import { last, pipe, sort, uniq } from "ramda";
import { toDots } from "@/utils/object";
import { zChartInput } from "@/utils/validation";
import { type IChartInput, type IChartEvent } from "@/types";
@@ -13,15 +13,14 @@ export const config = {
};
export const chartRouter = createTRPCRouter({
events: protectedProcedure
.query(async () => {
const events = await db.event.findMany({
take: 500,
distinct: ["name"],
});
events: protectedProcedure.query(async () => {
const events = await db.event.findMany({
take: 500,
distinct: ["name"],
});
return events;
}),
return events;
}),
properties: protectedProcedure
.input(z.object({ event: z.string() }).optional())
@@ -106,12 +105,37 @@ export const chartRouter = createTRPCRouter({
);
}
return {
series: series.sort((a, b) => {
const sorted = [...series].sort((a, b) => {
if (input.chartType === "linear") {
const sumA = a.data.reduce((acc, item) => acc + item.count, 0);
const sumB = b.data.reduce((acc, item) => acc + item.count, 0);
return sumB - sumA;
}),
} else {
return b.totalCount - a.totalCount;
}
});
const meta = {
highest: sorted[0]?.totalCount ?? 0,
lowest: last(sorted)?.totalCount ?? 0,
};
return {
events: Object.entries(series.reduce((acc, item) => {
if(acc[item.event.id]) {
acc[item.event.id] += item.totalCount;
} else {
acc[item.event.id] = item.totalCount;
}
return acc
}, {} as Record<typeof series[number]['event']['id'], number>)).map(([id, count]) => ({
count,
...events.find((event) => event.id === id)!,
})),
series: sorted.map((item) => ({
...item,
meta,
})),
};
}),
});
@@ -169,7 +193,7 @@ async function getChartData({
endDate,
}: {
event: IChartEvent;
} & Omit<IChartInput, 'events'>) {
} & Omit<IChartInput, "events">) {
const select = [];
const where = [];
const groupBy = [];
@@ -259,30 +283,34 @@ async function getChartData({
}
if (startDate) {
where.push(`"createdAt" >= '${startDate.toISOString()}'`);
where.push(`"createdAt" >= '${startDate}'`);
}
if (endDate) {
where.push(`"createdAt" <= '${endDate.toISOString()}'`);
where.push(`"createdAt" <= '${endDate}'`);
}
const sql = `
SELECT ${select.join(", ")}
FROM events
WHERE ${where.join(" AND ")}
GROUP BY ${groupBy.join(", ")}
ORDER BY ${orderBy.join(", ")}
`;
const sql = [
`SELECT ${select.join(", ")}`,
`FROM events`,
`WHERE ${where.join(" AND ")}`,
];
const result = await db.$queryRawUnsafe<ResultItem[]>(sql);
if (groupBy.length) {
sql.push(`GROUP BY ${groupBy.join(", ")}`);
}
if (orderBy.length) {
sql.push(`ORDER BY ${orderBy.join(", ")}`);
}
const result = await db.$queryRawUnsafe<ResultItem[]>(sql.join("\n"));
// group by sql label
const series = result.reduce(
(acc, item) => {
// item.label can be null when using breakdowns on a property
// that doesn't exist on all events
// fallback on event legend
const label = item.label?.trim() ?? getEventLegend(event);
const label = item.label?.trim() ?? event.id;
if (label) {
if (acc[label]) {
acc[label]?.push(item);
@@ -301,18 +329,26 @@ async function getChartData({
return Object.keys(series).map((key) => {
const legend = breakdowns.length ? key : getEventLegend(event);
const data = series[key] ?? [];
return {
name: legend,
event: {
id: event.id,
name: event.name,
},
totalCount: getTotalCount(data),
data: fillEmptySpotsInTimeline(data, interval, startDate, endDate).map(
(item) => {
return {
label: legend,
count: item.count,
date: new Date(item.date).toISOString(),
};
},
),
data:
chartType === "linear"
? fillEmptySpotsInTimeline(data, interval, startDate, endDate).map(
(item) => {
return {
label: legend,
count: item.count,
date: new Date(item.date).toISOString(),
};
},
)
: [],
};
});
}
@@ -320,8 +356,8 @@ async function getChartData({
function fillEmptySpotsInTimeline(
items: ResultItem[],
interval: string,
startDate: Date,
endDate: Date,
startDate: string,
endDate: string,
) {
const result = [];
const clonedStartDate = new Date(startDate);

View File

@@ -8,21 +8,42 @@ import {
type IChartInput,
type IChartBreakdown,
type IChartEvent,
type IChartEventFilter,
} from "@/types";
import { type Report as DbReport } from "@prisma/client";
import { getProjectBySlug } from "@/server/services/project.service";
import { getDashboardBySlug } from "@/server/services/dashboard.service";
import { alphabetIds } from "@/utils/constants";
function transform(report: DbReport): IChartInput & { id: string } {
function transformFilter(filter: Partial<IChartEventFilter>, index: number): IChartEventFilter {
return {
id: filter.id ?? alphabetIds[index]!,
name: filter.name ?? 'Unknown Filter',
operator: filter.operator ?? 'is',
value: typeof filter.value === 'string' ? [filter.value] : filter.value ?? [],
}
}
function transformEvent(event: Partial<IChartEvent>, index: number): IChartEvent {
return {
segment: event.segment ?? 'event',
filters: (event.filters ?? []).map(transformFilter),
id: event.id ?? alphabetIds[index]!,
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
name: event.name || 'Untitled',
}
}
function transformReport(report: DbReport): IChartInput & { id: string } {
return {
id: report.id,
events: report.events as IChartEvent[],
events: (report.events as IChartEvent[]).map(transformEvent),
breakdowns: report.breakdowns as IChartBreakdown[],
startDate: getDaysOldDate(report.range),
endDate: new Date(),
startDate: getDaysOldDate(report.range).toISOString(),
endDate: new Date().toISOString(),
chartType: report.chart_type,
interval: report.interval,
name: report.name,
name: report.name || 'Untitled',
};
}
@@ -40,7 +61,7 @@ export const reportRouter = createTRPCRouter({
id,
},
})
.then(transform);
.then(transformReport);
}),
list: protectedProcedure
.input(
@@ -60,7 +81,7 @@ export const reportRouter = createTRPCRouter({
});
return {
reports: reports.map(transform),
reports: reports.map(transformReport),
dashboard,
}
}),
@@ -82,7 +103,7 @@ export const reportRouter = createTRPCRouter({
interval: report.interval,
breakdowns: report.breakdowns,
chart_type: report.chartType,
range: dateDifferanceInDays(report.endDate, report.startDate),
range: dateDifferanceInDays(new Date(report.endDate), new Date(report.startDate)),
},
});
}),
@@ -108,7 +129,7 @@ export const reportRouter = createTRPCRouter({
interval: report.interval,
breakdowns: report.breakdowns,
chart_type: report.chartType,
range: dateDifferanceInDays(report.endDate, report.startDate),
range: dateDifferanceInDays(new Date(report.endDate), new Date(report.startDate)),
},
});
}),