Files
documenso/packages/lib/server-only/document/get-stats.ts
Ephraim Duncan ffce7a2c81 fix: filter document stats by folder (#2083)
This pull request refactors the filtering logic in the `getTeamCounts`
function within `get-stats.ts` to improve consistency and
maintainability. The main change is the consolidation of multiple filter
conditions into a single `AND` clause, which now includes search
filters, folder filters, and visibility filters. This ensures that all
relevant filters are applied in a unified way for document count
queries.
2025-10-28 21:16:12 +11:00

369 lines
9.8 KiB
TypeScript

import type { Prisma, User } from '@prisma/client';
import { DocumentVisibility, EnvelopeType, SigningStatus, TeamMemberRole } from '@prisma/client';
import { DateTime } from 'luxon';
import { match } from 'ts-pattern';
import type { PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents';
import { prisma } from '@documenso/prisma';
import { isExtendedDocumentStatus } from '@documenso/prisma/guards/is-extended-document-status';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
export type GetStatsInput = {
user: Pick<User, 'id' | 'email'>;
team?: Omit<GetTeamCountsOption, 'createdAt'>;
period?: PeriodSelectorValue;
search?: string;
folderId?: string;
};
export const getStats = async ({
user,
period,
search = '',
folderId,
...options
}: GetStatsInput) => {
let createdAt: Prisma.EnvelopeWhereInput['createdAt'];
if (period) {
const daysAgo = parseInt(period.replace(/d$/, ''), 10);
const startOfPeriod = DateTime.now().minus({ days: daysAgo }).startOf('day');
createdAt = {
gte: startOfPeriod.toJSDate(),
};
}
const [ownerCounts, notSignedCounts, hasSignedCounts] = await (options.team
? getTeamCounts({
...options.team,
createdAt,
currentUserEmail: user.email,
userId: user.id,
search,
folderId,
})
: getCounts({ user, createdAt, search, folderId }));
const stats: Record<ExtendedDocumentStatus, number> = {
[ExtendedDocumentStatus.DRAFT]: 0,
[ExtendedDocumentStatus.PENDING]: 0,
[ExtendedDocumentStatus.COMPLETED]: 0,
[ExtendedDocumentStatus.REJECTED]: 0,
[ExtendedDocumentStatus.INBOX]: 0,
[ExtendedDocumentStatus.ALL]: 0,
};
ownerCounts.forEach((stat) => {
stats[stat.status] = stat._count._all;
});
notSignedCounts.forEach((stat) => {
stats[ExtendedDocumentStatus.INBOX] += stat._count._all;
});
hasSignedCounts.forEach((stat) => {
if (stat.status === ExtendedDocumentStatus.COMPLETED) {
stats[ExtendedDocumentStatus.COMPLETED] += stat._count._all;
}
if (stat.status === ExtendedDocumentStatus.PENDING) {
stats[ExtendedDocumentStatus.PENDING] += stat._count._all;
}
if (stat.status === ExtendedDocumentStatus.REJECTED) {
stats[ExtendedDocumentStatus.REJECTED] += stat._count._all;
}
});
Object.keys(stats).forEach((key) => {
if (key !== ExtendedDocumentStatus.ALL && isExtendedDocumentStatus(key)) {
stats[ExtendedDocumentStatus.ALL] += stats[key];
}
});
return stats;
};
type GetCountsOption = {
user: Pick<User, 'id' | 'email'>;
createdAt: Prisma.EnvelopeWhereInput['createdAt'];
search?: string;
folderId?: string | null;
};
const getCounts = async ({ user, createdAt, search, folderId }: GetCountsOption) => {
const searchFilter: Prisma.EnvelopeWhereInput = {
OR: [
{ title: { contains: search, mode: 'insensitive' } },
{ recipients: { some: { name: { contains: search, mode: 'insensitive' } } } },
{ recipients: { some: { email: { contains: search, mode: 'insensitive' } } } },
],
};
const rootPageFilter = folderId === undefined ? { folderId: null } : {};
return Promise.all([
// Owner counts.
prisma.envelope.groupBy({
by: ['status'],
_count: {
_all: true,
},
where: {
type: EnvelopeType.DOCUMENT,
userId: user.id,
createdAt,
deletedAt: null,
AND: [searchFilter, rootPageFilter, folderId ? { folderId } : {}],
},
}),
// Not signed counts.
prisma.envelope.groupBy({
by: ['status'],
_count: {
_all: true,
},
where: {
type: EnvelopeType.DOCUMENT,
status: ExtendedDocumentStatus.PENDING,
recipients: {
some: {
email: user.email,
signingStatus: SigningStatus.NOT_SIGNED,
documentDeletedAt: null,
},
},
createdAt,
AND: [searchFilter, rootPageFilter, folderId ? { folderId } : {}],
},
}),
// Has signed counts.
prisma.envelope.groupBy({
by: ['status'],
_count: {
_all: true,
},
where: {
type: EnvelopeType.DOCUMENT,
createdAt,
user: {
email: {
not: user.email,
},
},
OR: [
{
status: ExtendedDocumentStatus.PENDING,
recipients: {
some: {
email: user.email,
signingStatus: SigningStatus.SIGNED,
documentDeletedAt: null,
},
},
},
{
status: ExtendedDocumentStatus.COMPLETED,
recipients: {
some: {
email: user.email,
signingStatus: SigningStatus.SIGNED,
documentDeletedAt: null,
},
},
},
],
AND: [searchFilter, rootPageFilter, folderId ? { folderId } : {}],
},
}),
]);
};
type GetTeamCountsOption = {
teamId: number;
teamEmail?: string;
senderIds?: number[];
currentUserEmail: string;
userId: number;
createdAt: Prisma.EnvelopeWhereInput['createdAt'];
currentTeamMemberRole?: TeamMemberRole;
search?: string;
folderId?: string | null;
};
const getTeamCounts = async (options: GetTeamCountsOption) => {
const { createdAt, teamId, teamEmail, folderId } = options;
const senderIds = options.senderIds ?? [];
const userIdWhereClause: Prisma.EnvelopeWhereInput['userId'] =
senderIds.length > 0
? {
in: senderIds,
}
: undefined;
const searchFilter: Prisma.EnvelopeWhereInput = {
OR: [
{ title: { contains: options.search, mode: 'insensitive' } },
{ recipients: { some: { name: { contains: options.search, mode: 'insensitive' } } } },
{ recipients: { some: { email: { contains: options.search, mode: 'insensitive' } } } },
],
};
const rootPageFilter = folderId === undefined ? { folderId: null } : {};
let ownerCountsWhereInput: Prisma.EnvelopeWhereInput = {
type: EnvelopeType.DOCUMENT,
userId: userIdWhereClause,
createdAt,
teamId,
deletedAt: null,
};
let notSignedCountsGroupByArgs = null;
let hasSignedCountsGroupByArgs = null;
const visibilityFiltersWhereInput: Prisma.EnvelopeWhereInput = {
AND: [
{ deletedAt: null },
{
OR: [
match(options.currentTeamMemberRole)
.with(TeamMemberRole.ADMIN, () => ({
visibility: {
in: [
DocumentVisibility.EVERYONE,
DocumentVisibility.MANAGER_AND_ABOVE,
DocumentVisibility.ADMIN,
],
},
}))
.with(TeamMemberRole.MANAGER, () => ({
visibility: {
in: [DocumentVisibility.EVERYONE, DocumentVisibility.MANAGER_AND_ABOVE],
},
}))
.otherwise(() => ({
visibility: {
equals: DocumentVisibility.EVERYONE,
},
})),
{
OR: [
{ userId: options.userId },
{ recipients: { some: { email: options.currentUserEmail } } },
],
},
],
},
],
};
ownerCountsWhereInput = {
...ownerCountsWhereInput,
AND: [
...(Array.isArray(visibilityFiltersWhereInput.AND)
? visibilityFiltersWhereInput.AND
: visibilityFiltersWhereInput.AND
? [visibilityFiltersWhereInput.AND]
: []),
searchFilter,
rootPageFilter,
folderId ? { folderId } : {},
],
};
if (teamEmail) {
ownerCountsWhereInput = {
type: EnvelopeType.DOCUMENT,
userId: userIdWhereClause,
createdAt,
OR: [
{
teamId,
},
{
user: {
email: teamEmail,
},
},
],
deletedAt: null,
AND: [searchFilter, rootPageFilter, folderId ? { folderId } : {}],
};
notSignedCountsGroupByArgs = {
by: ['status'],
_count: {
_all: true,
},
where: {
type: EnvelopeType.DOCUMENT,
userId: userIdWhereClause,
createdAt,
status: ExtendedDocumentStatus.PENDING,
recipients: {
some: {
email: teamEmail,
signingStatus: SigningStatus.NOT_SIGNED,
documentDeletedAt: null,
},
},
deletedAt: null,
AND: [searchFilter, rootPageFilter, folderId ? { folderId } : {}],
},
} satisfies Prisma.EnvelopeGroupByArgs;
hasSignedCountsGroupByArgs = {
by: ['status'],
_count: {
_all: true,
},
where: {
type: EnvelopeType.DOCUMENT,
userId: userIdWhereClause,
createdAt,
OR: [
{
status: ExtendedDocumentStatus.PENDING,
recipients: {
some: {
email: teamEmail,
signingStatus: SigningStatus.SIGNED,
documentDeletedAt: null,
},
},
deletedAt: null,
},
{
status: ExtendedDocumentStatus.COMPLETED,
recipients: {
some: {
email: teamEmail,
signingStatus: SigningStatus.SIGNED,
documentDeletedAt: null,
},
},
},
],
AND: [searchFilter, rootPageFilter, folderId ? { folderId } : {}],
},
} satisfies Prisma.EnvelopeGroupByArgs;
}
return Promise.all([
prisma.envelope.groupBy({
by: ['status'],
_count: {
_all: true,
},
where: ownerCountsWhereInput,
}),
notSignedCountsGroupByArgs ? prisma.envelope.groupBy(notSignedCountsGroupByArgs) : [],
hasSignedCountsGroupByArgs ? prisma.envelope.groupBy(hasSignedCountsGroupByArgs) : [],
]);
};