chore: minor changes

This commit is contained in:
Ephraim Atta-Duncan
2025-08-14 10:17:45 +00:00
parent 28f1948a5e
commit 020c0497ba
11 changed files with 187 additions and 141 deletions

View File

@ -1,5 +1,6 @@
import type { DocumentStatus } from '@prisma/client';
import type { DateRange } from '@documenso/lib/types/search-params';
import { kyselyPrisma, sql } from '@documenso/prisma';
export type OrganisationSummary = {
@ -50,7 +51,7 @@ export type GetOrganisationDetailedInsightsOptions = {
organisationId: string;
page?: number;
perPage?: number;
dateRange?: 'last30days' | 'last90days' | 'lastYear' | 'allTime';
dateRange?: DateRange;
view: 'teams' | 'users' | 'documents';
};
@ -63,42 +64,38 @@ export async function getOrganisationDetailedInsights({
}: GetOrganisationDetailedInsightsOptions): Promise<OrganisationDetailedInsights> {
const offset = Math.max(page - 1, 0) * perPage;
let dateFilter = sql``;
const now = new Date();
let createdAtFrom: Date | null = null;
switch (dateRange) {
case 'last30days': {
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
dateFilter = sql`AND d."createdAt" >= ${thirtyDaysAgo}`;
createdAtFrom = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
break;
}
case 'last90days': {
const ninetyDaysAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000);
dateFilter = sql`AND d."createdAt" >= ${ninetyDaysAgo}`;
createdAtFrom = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000);
break;
}
case 'lastYear': {
const oneYearAgo = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate());
dateFilter = sql`AND d."createdAt" >= ${oneYearAgo}`;
createdAtFrom = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate());
break;
}
case 'allTime':
default:
dateFilter = sql``;
createdAtFrom = null;
break;
}
// Get organisation summary metrics
const summaryData = await getOrganisationSummary(organisationId, dateFilter);
const summaryData = await getOrganisationSummary(organisationId, createdAtFrom);
const viewData = await (async () => {
switch (view) {
case 'teams':
return await getTeamInsights(organisationId, offset, perPage, dateFilter);
return await getTeamInsights(organisationId, offset, perPage, createdAtFrom);
case 'users':
return await getUserInsights(organisationId, offset, perPage, dateFilter);
return await getUserInsights(organisationId, offset, perPage, createdAtFrom);
case 'documents':
return await getDocumentInsights(organisationId, offset, perPage, dateFilter);
return await getDocumentInsights(organisationId, offset, perPage, createdAtFrom);
default:
throw new Error(`Invalid view: ${view}`);
}
@ -114,8 +111,7 @@ async function getTeamInsights(
organisationId: string,
offset: number,
perPage: number,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
dateFilter: any,
createdAtFrom: Date | null,
): Promise<OrganisationDetailedInsights> {
const teamsQuery = kyselyPrisma.$kysely
.selectFrom('Team as t')
@ -132,9 +128,10 @@ async function getTeamInsights(
't.name as name',
't.createdAt as createdAt',
sql<number>`COUNT(DISTINCT om."userId")`.as('memberCount'),
sql<number>`COUNT(DISTINCT CASE WHEN d.id IS NOT NULL ${dateFilter} THEN d.id END)`.as(
'documentCount',
),
(createdAtFrom
? sql<number>`COUNT(DISTINCT CASE WHEN d.id IS NOT NULL AND d."createdAt" >= ${createdAtFrom} THEN d.id END)`
: sql<number>`COUNT(DISTINCT d.id)`
).as('documentCount'),
])
.groupBy(['t.id', 't.name', 't.createdAt'])
.orderBy('documentCount', 'desc')
@ -161,26 +158,42 @@ async function getUserInsights(
organisationId: string,
offset: number,
perPage: number,
// eslint-disable-next-line @typescript-eslint/no-explicit-any, unused-imports/no-unused-vars
_dateFilter: any,
createdAtFrom: Date | null,
): Promise<OrganisationDetailedInsights> {
const usersQuery = kyselyPrisma.$kysely
const usersBase = kyselyPrisma.$kysely
.selectFrom('OrganisationMember as om')
.innerJoin('User as u', 'u.id', 'om.userId')
.where('om.organisationId', '=', organisationId)
.leftJoin('Document as d', (join) =>
join.onRef('d.userId', '=', 'u.id').on('d.deletedAt', 'is', null),
)
.leftJoin('Team as td', (join) =>
join.onRef('td.id', '=', 'd.teamId').on('td.organisationId', '=', organisationId),
)
.leftJoin('Recipient as r', (join) =>
join.onRef('r.email', '=', 'u.email').on('r.signedAt', 'is not', null),
)
.where('om.organisationId', '=', organisationId)
.leftJoin('Document as sd', (join) =>
join.onRef('sd.id', '=', 'r.documentId').on('sd.deletedAt', 'is', null),
)
.leftJoin('Team as ts', (join) =>
join.onRef('ts.id', '=', 'sd.teamId').on('ts.organisationId', '=', organisationId),
);
const usersQuery = usersBase
.select([
'u.id as id',
'u.name as name',
'u.email as email',
'u.createdAt as createdAt',
sql<number>`COUNT(DISTINCT d.id)`.as('documentCount'),
sql<number>`COUNT(DISTINCT r.id)`.as('signedDocumentCount'),
(createdAtFrom
? sql<number>`COUNT(DISTINCT CASE WHEN d.id IS NOT NULL AND td.id IS NOT NULL AND d."createdAt" >= ${createdAtFrom} THEN d.id END)`
: sql<number>`COUNT(DISTINCT CASE WHEN td.id IS NOT NULL THEN d.id END)`
).as('documentCount'),
(createdAtFrom
? sql<number>`COUNT(DISTINCT CASE WHEN r.id IS NOT NULL AND ts.id IS NOT NULL AND r."signedAt" >= ${createdAtFrom} AND r.role = 'SIGNER'::"RecipientRole" THEN r.id END)`
: sql<number>`COUNT(DISTINCT CASE WHEN ts.id IS NOT NULL AND r.role = 'SIGNER'::"RecipientRole" THEN r.id END)`
).as('signedDocumentCount'),
])
.groupBy(['u.id', 'u.name', 'u.email', 'u.createdAt'])
.orderBy('u.createdAt', 'desc')
@ -208,8 +221,7 @@ async function getDocumentInsights(
organisationId: string,
offset: number,
perPage: number,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
dateFilter: any,
createdAtFrom: Date | null,
): Promise<OrganisationDetailedInsights> {
let documentsQuery = kyselyPrisma.$kysely
.selectFrom('Document as d')
@ -217,9 +229,8 @@ async function getDocumentInsights(
.where('t.organisationId', '=', organisationId)
.where('d.deletedAt', 'is', null);
// Apply date filter if it's not empty (which means all time)
if (dateFilter && dateFilter.sql && dateFilter.sql !== '') {
documentsQuery = documentsQuery.where(sql`${dateFilter}`);
if (createdAtFrom) {
documentsQuery = documentsQuery.where('d.createdAt', '>=', createdAtFrom);
}
documentsQuery = documentsQuery
@ -241,9 +252,8 @@ async function getDocumentInsights(
.where('t.organisationId', '=', organisationId)
.where('d.deletedAt', 'is', null);
// Apply same date filter to count query
if (dateFilter && dateFilter.sql && dateFilter.sql !== '') {
countQuery = countQuery.where(sql`${dateFilter}`);
if (createdAtFrom) {
countQuery = countQuery.where('d.createdAt', '>=', createdAtFrom);
}
countQuery = countQuery.select(({ fn }) => [fn.countAll().as('count')]);
@ -268,39 +278,67 @@ async function getDocumentInsights(
async function getOrganisationSummary(
organisationId: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
dateFilter: any,
createdAtFrom: Date | null,
): Promise<OrganisationSummary> {
const summaryQuery = kyselyPrisma.$kysely
.selectFrom('Organisation as o')
.leftJoin('Team as t', 'o.id', 't.organisationId')
.leftJoin('OrganisationMember as om', 'o.id', 'om.organisationId')
.leftJoin('Document as d', (join) =>
join.onRef('t.id', '=', 'd.teamId').on('d.deletedAt', 'is', null),
)
.where('o.id', '=', organisationId)
.select([
sql<number>`COUNT(DISTINCT t.id)`.as('totalTeams'),
sql<number>`COUNT(DISTINCT om."userId")`.as('totalMembers'),
sql<number>`COUNT(DISTINCT d.id)`.as('totalDocuments'),
sql<number>`COUNT(DISTINCT CASE WHEN d.status IN ('DRAFT', 'PENDING') THEN d.id END)`.as(
'activeDocuments',
sql<number>`(SELECT COUNT(DISTINCT t2.id) FROM "Team" AS t2 WHERE t2."organisationId" = o.id)`.as(
'totalTeams',
),
sql<number>`COUNT(DISTINCT CASE WHEN d.status = 'COMPLETED' THEN d.id END)`.as(
'completedDocuments',
),
sql<number>`COUNT(DISTINCT CASE WHEN d.id IS NOT NULL AND d.status = 'COMPLETED' ${dateFilter} THEN d.id END)`.as(
'volumeThisPeriod',
),
sql<number>`COUNT(DISTINCT CASE WHEN d.status = 'COMPLETED' THEN d.id END)`.as(
'volumeAllTime',
sql<number>`(SELECT COUNT(DISTINCT om2."userId") FROM "OrganisationMember" AS om2 WHERE om2."organisationId" = o.id)`.as(
'totalMembers',
),
sql<number>`(
SELECT COUNT(DISTINCT d2.id)
FROM "Document" AS d2
INNER JOIN "Team" AS t2 ON t2.id = d2."teamId"
WHERE t2."organisationId" = o.id AND d2."deletedAt" IS NULL
)`.as('totalDocuments'),
sql<number>`(
SELECT COUNT(DISTINCT d2.id)
FROM "Document" AS d2
INNER JOIN "Team" AS t2 ON t2.id = d2."teamId"
WHERE t2."organisationId" = o.id AND d2."deletedAt" IS NULL AND d2.status IN ('DRAFT', 'PENDING')
)`.as('activeDocuments'),
sql<number>`(
SELECT COUNT(DISTINCT d2.id)
FROM "Document" AS d2
INNER JOIN "Team" AS t2 ON t2.id = d2."teamId"
WHERE t2."organisationId" = o.id AND d2."deletedAt" IS NULL AND d2.status = 'COMPLETED'
)`.as('completedDocuments'),
(createdAtFrom
? sql<number>`(
SELECT COUNT(DISTINCT d2.id)
FROM "Document" AS d2
INNER JOIN "Team" AS t2 ON t2.id = d2."teamId"
WHERE t2."organisationId" = o.id
AND d2."deletedAt" IS NULL
AND d2.status = 'COMPLETED'
AND d2."createdAt" >= ${createdAtFrom}
)`
: sql<number>`(
SELECT COUNT(DISTINCT d2.id)
FROM "Document" AS d2
INNER JOIN "Team" AS t2 ON t2.id = d2."teamId"
WHERE t2."organisationId" = o.id
AND d2."deletedAt" IS NULL
AND d2.status = 'COMPLETED'
)`
).as('volumeThisPeriod'),
sql<number>`(
SELECT COUNT(DISTINCT d2.id)
FROM "Document" AS d2
INNER JOIN "Team" AS t2 ON t2.id = d2."teamId"
WHERE t2."organisationId" = o.id AND d2."deletedAt" IS NULL AND d2.status = 'COMPLETED'
)`.as('volumeAllTime'),
]);
const result = await summaryQuery.executeTakeFirst();
return {
totalTeams: Math.max(Number(result?.totalTeams || 0), 1),
totalTeams: Number(result?.totalTeams || 0),
totalMembers: Number(result?.totalMembers || 0),
totalDocuments: Number(result?.totalDocuments || 0),
activeDocuments: Number(result?.activeDocuments || 0),