mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
fix: zero month addition (#1733)
- Add zero month at the begining of each metric on the open page
This commit is contained in:
54
apps/openpage-api/lib/add-zero-month.ts
Normal file
54
apps/openpage-api/lib/add-zero-month.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,6 +3,8 @@ import { DateTime } from 'luxon';
|
|||||||
|
|
||||||
import { kyselyPrisma, sql } from '@documenso/prisma';
|
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { addZeroMonth } from '../add-zero-month';
|
||||||
|
|
||||||
export const getCompletedDocumentsMonthly = async (type: 'count' | 'cumulative' = 'count') => {
|
export const getCompletedDocumentsMonthly = async (type: 'count' | 'cumulative' = 'count') => {
|
||||||
const qb = kyselyPrisma.$kysely
|
const qb = kyselyPrisma.$kysely
|
||||||
.selectFrom('Document')
|
.selectFrom('Document')
|
||||||
@ -35,7 +37,7 @@ export const getCompletedDocumentsMonthly = async (type: 'count' | 'cumulative'
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
return transformedData;
|
return addZeroMonth(transformedData);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GetCompletedDocumentsMonthlyResult = Awaited<
|
export type GetCompletedDocumentsMonthlyResult = Awaited<
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { DateTime } from 'luxon';
|
|||||||
|
|
||||||
import { kyselyPrisma, sql } from '@documenso/prisma';
|
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { addZeroMonth } from '../add-zero-month';
|
||||||
|
|
||||||
export const getSignerConversionMonthly = async (type: 'count' | 'cumulative' = 'count') => {
|
export const getSignerConversionMonthly = async (type: 'count' | 'cumulative' = 'count') => {
|
||||||
const qb = kyselyPrisma.$kysely
|
const qb = kyselyPrisma.$kysely
|
||||||
.selectFrom('Recipient')
|
.selectFrom('Recipient')
|
||||||
@ -34,7 +36,7 @@ export const getSignerConversionMonthly = async (type: 'count' | 'cumulative' =
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
return transformedData;
|
return addZeroMonth(transformedData);
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GetSignerConversionMonthlyResult = Awaited<
|
export type GetSignerConversionMonthlyResult = Awaited<
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { DateTime } from 'luxon';
|
|||||||
|
|
||||||
import { kyselyPrisma, sql } from '@documenso/prisma';
|
import { kyselyPrisma, sql } from '@documenso/prisma';
|
||||||
|
|
||||||
|
import { addZeroMonth } from '../add-zero-month';
|
||||||
|
|
||||||
export const getUserMonthlyGrowth = async (type: 'count' | 'cumulative' = 'count') => {
|
export const getUserMonthlyGrowth = async (type: 'count' | 'cumulative' = 'count') => {
|
||||||
const qb = kyselyPrisma.$kysely
|
const qb = kyselyPrisma.$kysely
|
||||||
.selectFrom('User')
|
.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>>;
|
export type GetUserMonthlyGrowthResult = Awaited<ReturnType<typeof getUserMonthlyGrowth>>;
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
|
import { addZeroMonth } from './add-zero-month';
|
||||||
|
|
||||||
type MetricKeys = {
|
type MetricKeys = {
|
||||||
stars: number;
|
stars: number;
|
||||||
forks: number;
|
forks: number;
|
||||||
@ -37,31 +39,77 @@ export function transformData({
|
|||||||
data: DataEntry;
|
data: DataEntry;
|
||||||
metric: MetricKey;
|
metric: MetricKey;
|
||||||
}): TransformData {
|
}): TransformData {
|
||||||
|
try {
|
||||||
|
if (!data || Object.keys(data).length === 0) {
|
||||||
|
return {
|
||||||
|
labels: [],
|
||||||
|
datasets: [{ label: `Total ${FRIENDLY_METRIC_NAMES[metric]}`, data: [] }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const sortedEntries = Object.entries(data).sort(([dateA], [dateB]) => {
|
const sortedEntries = Object.entries(data).sort(([dateA], [dateB]) => {
|
||||||
|
try {
|
||||||
const [yearA, monthA] = dateA.split('-').map(Number);
|
const [yearA, monthA] = dateA.split('-').map(Number);
|
||||||
const [yearB, monthB] = dateB.split('-').map(Number);
|
const [yearB, monthB] = dateB.split('-').map(Number);
|
||||||
|
|
||||||
|
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();
|
return DateTime.local(yearA, monthA).toMillis() - DateTime.local(yearB, monthB).toMillis();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error sorting entries:', error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const labels = sortedEntries.map(([date]) => {
|
const labels = sortedEntries.map(([date]) => {
|
||||||
|
try {
|
||||||
const [year, month] = date.split('-');
|
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({
|
const dateTime = DateTime.fromObject({
|
||||||
year: Number(year),
|
year: Number(year),
|
||||||
month: Number(month),
|
month: Number(month),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!dateTime.isValid) {
|
||||||
|
console.warn(`Invalid DateTime object for: ${date}`);
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
return dateTime.toFormat('MMM yyyy');
|
return dateTime.toFormat('MMM yyyy');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error formatting date:', error, date);
|
||||||
|
return date;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
const transformedData = {
|
||||||
labels,
|
labels,
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: `Total ${FRIENDLY_METRIC_NAMES[metric]}`,
|
label: `Total ${FRIENDLY_METRIC_NAMES[metric]}`,
|
||||||
data: sortedEntries.map(([_, stats]) => stats[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
|
// To be on the safer side
|
||||||
|
|||||||
Reference in New Issue
Block a user