fix: zero month addition (#1733)

- Add zero month at the begining of each metric on the open page
This commit is contained in:
Ephraim Duncan
2025-04-01 11:12:41 +00:00
committed by GitHub
parent da71613c9f
commit 5e4956f3a2
5 changed files with 132 additions and 24 deletions

View File

@ -0,0 +1,54 @@
import { DateTime } from 'luxon';
export interface TransformedData {
labels: string[];
datasets: Array<{
label: string;
data: number[];
}>;
}
export function addZeroMonth(transformedData: TransformedData): TransformedData {
const result = {
labels: [...transformedData.labels],
datasets: transformedData.datasets.map((dataset) => ({
label: dataset.label,
data: [...dataset.data],
})),
};
if (result.labels.length === 0) {
return result;
}
if (result.datasets.every((dataset) => dataset.data[0] === 0)) {
return result;
}
try {
let firstMonth = DateTime.fromFormat(result.labels[0], 'MMM yyyy');
if (!firstMonth.isValid) {
const formats = ['MMM yyyy', 'MMMM yyyy', 'MM/yyyy', 'yyyy-MM'];
for (const format of formats) {
firstMonth = DateTime.fromFormat(result.labels[0], format);
if (firstMonth.isValid) break;
}
if (!firstMonth.isValid) {
console.warn(`Could not parse date: "${result.labels[0]}"`);
return transformedData;
}
}
const zeroMonth = firstMonth.minus({ months: 1 }).toFormat('MMM yyyy');
result.labels.unshift(zeroMonth);
result.datasets.forEach((dataset) => {
dataset.data.unshift(0);
});
return result;
} catch (error) {
return transformedData;
}
}

View File

@ -3,6 +3,8 @@ import { DateTime } from 'luxon';
import { kyselyPrisma, sql } from '@documenso/prisma';
import { addZeroMonth } from '../add-zero-month';
export const getCompletedDocumentsMonthly = async (type: 'count' | 'cumulative' = 'count') => {
const qb = kyselyPrisma.$kysely
.selectFrom('Document')
@ -35,7 +37,7 @@ export const getCompletedDocumentsMonthly = async (type: 'count' | 'cumulative'
],
};
return transformedData;
return addZeroMonth(transformedData);
};
export type GetCompletedDocumentsMonthlyResult = Awaited<

View File

@ -2,6 +2,8 @@ import { DateTime } from 'luxon';
import { kyselyPrisma, sql } from '@documenso/prisma';
import { addZeroMonth } from '../add-zero-month';
export const getSignerConversionMonthly = async (type: 'count' | 'cumulative' = 'count') => {
const qb = kyselyPrisma.$kysely
.selectFrom('Recipient')
@ -34,7 +36,7 @@ export const getSignerConversionMonthly = async (type: 'count' | 'cumulative' =
],
};
return transformedData;
return addZeroMonth(transformedData);
};
export type GetSignerConversionMonthlyResult = Awaited<

View File

@ -2,6 +2,8 @@ import { DateTime } from 'luxon';
import { kyselyPrisma, sql } from '@documenso/prisma';
import { addZeroMonth } from '../add-zero-month';
export const getUserMonthlyGrowth = async (type: 'count' | 'cumulative' = 'count') => {
const qb = kyselyPrisma.$kysely
.selectFrom('User')
@ -32,7 +34,7 @@ export const getUserMonthlyGrowth = async (type: 'count' | 'cumulative' = 'count
],
};
return transformedData;
return addZeroMonth(transformedData);
};
export type GetUserMonthlyGrowthResult = Awaited<ReturnType<typeof getUserMonthlyGrowth>>;

View File

@ -1,5 +1,7 @@
import { DateTime } from 'luxon';
import { addZeroMonth } from './add-zero-month';
type MetricKeys = {
stars: number;
forks: number;
@ -37,31 +39,77 @@ export function transformData({
data: DataEntry;
metric: MetricKey;
}): TransformData {
const sortedEntries = Object.entries(data).sort(([dateA], [dateB]) => {
const [yearA, monthA] = dateA.split('-').map(Number);
const [yearB, monthB] = dateB.split('-').map(Number);
try {
if (!data || Object.keys(data).length === 0) {
return {
labels: [],
datasets: [{ label: `Total ${FRIENDLY_METRIC_NAMES[metric]}`, data: [] }],
};
}
return DateTime.local(yearA, monthA).toMillis() - DateTime.local(yearB, monthB).toMillis();
});
const sortedEntries = Object.entries(data).sort(([dateA], [dateB]) => {
try {
const [yearA, monthA] = dateA.split('-').map(Number);
const [yearB, monthB] = dateB.split('-').map(Number);
const labels = sortedEntries.map(([date]) => {
const [year, month] = date.split('-');
const dateTime = DateTime.fromObject({
year: Number(year),
month: Number(month),
if (isNaN(yearA) || isNaN(monthA) || isNaN(yearB) || isNaN(monthB)) {
console.warn(`Invalid date format: ${dateA} or ${dateB}`);
return 0;
}
return DateTime.local(yearA, monthA).toMillis() - DateTime.local(yearB, monthB).toMillis();
} catch (error) {
console.error('Error sorting entries:', error);
return 0;
}
});
return dateTime.toFormat('MMM yyyy');
});
return {
labels,
datasets: [
{
label: `Total ${FRIENDLY_METRIC_NAMES[metric]}`,
data: sortedEntries.map(([_, stats]) => stats[metric]),
},
],
};
const labels = sortedEntries.map(([date]) => {
try {
const [year, month] = date.split('-');
if (!year || !month || isNaN(Number(year)) || isNaN(Number(month))) {
console.warn(`Invalid date format: ${date}`);
return date;
}
const dateTime = DateTime.fromObject({
year: Number(year),
month: Number(month),
});
if (!dateTime.isValid) {
console.warn(`Invalid DateTime object for: ${date}`);
return date;
}
return dateTime.toFormat('MMM yyyy');
} catch (error) {
console.error('Error formatting date:', error, date);
return date;
}
});
const transformedData = {
labels,
datasets: [
{
label: `Total ${FRIENDLY_METRIC_NAMES[metric]}`,
data: sortedEntries.map(([_, stats]) => {
const value = stats[metric];
return typeof value === 'number' && !isNaN(value) ? value : 0;
}),
},
],
};
return addZeroMonth(transformedData);
} catch (error) {
return {
labels: [],
datasets: [{ label: `Total ${FRIENDLY_METRIC_NAMES[metric]}`, data: [] }],
};
}
}
// To be on the safer side