fix: show current month data and add caching (#2573)

### Summary

- Add Cache-Control headers to all route responses (1h s-maxage, 2h
stale-while-revalidate)
- Append current month to chart data so graphs stay up-to-date
(cumulative carries forward, else zero)
- Remove `.limit(12)` from growth queries for full history
- Pass isCumulative flag through addZeroMonth
- Deduplicate TransformedData type, remove transformRepoStats
This commit is contained in:
Ephraim Duncan
2026-03-06 02:30:31 +00:00
committed by GitHub
parent c63b4ca3cc
commit 7e2cbe46c0
20 changed files with 50 additions and 49 deletions
@@ -12,6 +12,7 @@ export async function GET(request: Request) {
status: 200,
headers: {
'content-type': 'application/json',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
},
}),
);
@@ -12,6 +12,7 @@ export async function GET(request: Request) {
status: 200,
headers: {
'content-type': 'application/json',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
},
}),
);
@@ -12,6 +12,7 @@ export async function GET(request: Request) {
status: 200,
headers: {
'content-type': 'application/json',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
},
}),
);
@@ -12,6 +12,7 @@ export async function GET(request: Request) {
status: 200,
headers: {
'content-type': 'application/json',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
},
}),
);
@@ -10,6 +10,7 @@ export async function GET(request: Request) {
status: 200,
headers: {
'content-type': 'application/json',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
},
}),
);
@@ -12,6 +12,7 @@ export async function GET(request: Request) {
status: 200,
headers: {
'content-type': 'application/json',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
},
}),
);
@@ -12,6 +12,7 @@ export async function GET(request: Request) {
status: 200,
headers: {
'content-type': 'application/json',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
},
}),
);
@@ -10,6 +10,7 @@ export async function GET(request: Request) {
status: 200,
headers: {
'content-type': 'application/json',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
},
}),
);
@@ -10,6 +10,7 @@ export async function GET(request: Request) {
status: 200,
headers: {
'content-type': 'application/json',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
},
}),
);
@@ -10,6 +10,7 @@ export async function GET(request: Request) {
status: 200,
headers: {
'content-type': 'application/json',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
},
}),
);
@@ -10,6 +10,7 @@ export async function GET(request: Request) {
status: 200,
headers: {
'content-type': 'application/json',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
},
}),
);
@@ -10,6 +10,7 @@ export async function GET(request: Request) {
status: 200,
headers: {
'content-type': 'application/json',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
},
}),
);
@@ -16,6 +16,7 @@ export async function GET(request: Request) {
status: 200,
headers: {
'content-type': 'application/json',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
},
}),
);
@@ -10,6 +10,7 @@ export async function GET(request: Request) {
status: 200,
headers: {
'content-type': 'application/json',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
},
}),
);
@@ -10,6 +10,7 @@ export async function GET(request: Request) {
status: 200,
headers: {
'content-type': 'application/json',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200',
},
}),
);
+27 -28
View File
@@ -1,15 +1,20 @@
import { DateTime } from 'luxon';
export interface TransformedData {
export type TransformedData = {
labels: string[];
datasets: Array<{
label: string;
data: number[];
}>;
}
};
export function addZeroMonth(transformedData: TransformedData): TransformedData {
const result = {
const FORMAT = 'MMM yyyy';
export const addZeroMonth = (
transformedData: TransformedData,
isCumulative = false,
): TransformedData => {
const result: TransformedData = {
labels: [...transformedData.labels],
datasets: transformedData.datasets.map((dataset) => ({
label: dataset.label,
@@ -21,34 +26,28 @@ export function addZeroMonth(transformedData: TransformedData): TransformedData
return result;
}
if (result.datasets.every((dataset) => dataset.data[0] === 0)) {
return result;
}
try {
let firstMonth = DateTime.fromFormat(result.labels[0], 'MMM yyyy');
if (!result.datasets.every((dataset) => dataset.data[0] === 0)) {
const firstMonth = DateTime.fromFormat(result.labels[0], FORMAT);
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;
}
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.labels.unshift(firstMonth.minus({ months: 1 }).toFormat(FORMAT));
result.datasets.forEach((dataset) => {
dataset.data.unshift(0);
});
return result;
} catch (error) {
return transformedData;
}
}
const now = DateTime.now().startOf('month');
const lastMonth = DateTime.fromFormat(result.labels[result.labels.length - 1], FORMAT);
if (lastMonth.isValid && lastMonth.startOf('month') < now) {
result.labels.push(now.toFormat(FORMAT));
result.datasets.forEach((dataset) => {
dataset.data.push(isCumulative ? dataset.data[dataset.data.length - 1] : 0);
});
}
return result;
};
@@ -21,8 +21,7 @@ export const getCompletedDocumentsMonthly = async (type: 'count' | 'cumulative'
.where(() => sql`"Envelope"."status" = ${DocumentStatus.COMPLETED}::"DocumentStatus"`)
.where(() => sql`"Envelope"."type" = ${EnvelopeType.DOCUMENT}::"EnvelopeType"`)
.groupBy('month')
.orderBy('month', 'desc')
.limit(12);
.orderBy('month', 'desc');
const result = await qb.execute();
@@ -38,7 +37,7 @@ export const getCompletedDocumentsMonthly = async (type: 'count' | 'cumulative'
],
};
return addZeroMonth(transformedData);
return addZeroMonth(transformedData, type === 'cumulative');
};
export type GetCompletedDocumentsMonthlyResult = Awaited<
@@ -36,7 +36,7 @@ export const getSignerConversionMonthly = async (type: 'count' | 'cumulative' =
],
};
return addZeroMonth(transformedData);
return addZeroMonth(transformedData, type === 'cumulative');
};
export type GetSignerConversionMonthlyResult = Awaited<
@@ -17,8 +17,7 @@ export const getUserMonthlyGrowth = async (type: 'count' | 'cumulative' = 'count
.as('cume_count'),
])
.groupBy('month')
.orderBy('month', 'desc')
.limit(12);
.orderBy('month', 'desc');
const result = await qb.execute();
@@ -34,7 +33,7 @@ export const getUserMonthlyGrowth = async (type: 'count' | 'cumulative' = 'count
],
};
return addZeroMonth(transformedData);
return addZeroMonth(transformedData, type === 'cumulative');
};
export type GetUserMonthlyGrowthResult = Awaited<ReturnType<typeof getUserMonthlyGrowth>>;
+3 -14
View File
@@ -1,6 +1,6 @@
import { DateTime } from 'luxon';
import { addZeroMonth } from './add-zero-month';
import { type TransformedData, addZeroMonth } from './add-zero-month';
type MetricKeys = {
stars: number;
@@ -14,14 +14,6 @@ type DataEntry = {
[key: string]: MetricKeys;
};
type TransformData = {
labels: string[];
datasets: {
label: string;
data: number[];
}[];
};
type MetricKey = keyof MetricKeys;
const FRIENDLY_METRIC_NAMES: { [key in MetricKey]: string } = {
@@ -38,7 +30,7 @@ export function transformData({
}: {
data: DataEntry;
metric: MetricKey;
}): TransformData {
}): TransformedData {
try {
if (!data || Object.keys(data).length === 0) {
return {
@@ -103,7 +95,7 @@ export function transformData({
],
};
return addZeroMonth(transformedData);
return addZeroMonth(transformedData, true);
} catch (error) {
return {
labels: [],
@@ -111,6 +103,3 @@ export function transformData({
};
}
}
// To be on the safer side
export const transformRepoStats = transformData;