fix: new get stats query

This commit is contained in:
Ephraim Atta-Duncan
2024-12-11 00:40:42 +00:00
parent b19b57dbc9
commit 5103477e7b
9 changed files with 313 additions and 205 deletions

View File

@ -6,8 +6,8 @@ import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session'; import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import type { PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents'; import type { PeriodSelectorValue } from '@documenso/lib/server-only/document/find-documents';
import { findDocuments } from '@documenso/lib/server-only/document/find-documents'; import { findDocuments } from '@documenso/lib/server-only/document/find-documents';
import type { GetStatsInput } from '@documenso/lib/server-only/document/get-stats'; import type { GetStatsInput } from '@documenso/lib/server-only/document/get-stats-new';
import { getStats } from '@documenso/lib/server-only/document/get-stats'; import { getStats } from '@documenso/lib/server-only/document/get-stats-new';
import { parseToIntegerArray } from '@documenso/lib/utils/params'; import { parseToIntegerArray } from '@documenso/lib/utils/params';
import { formatDocumentsPath } from '@documenso/lib/utils/teams'; import { formatDocumentsPath } from '@documenso/lib/utils/teams';
import type { Team, TeamEmail, TeamMemberRole } from '@documenso/prisma/client'; import type { Team, TeamEmail, TeamMemberRole } from '@documenso/prisma/client';
@ -35,7 +35,7 @@ export interface DocumentsPageViewProps {
senderIds?: string; senderIds?: string;
search?: string; search?: string;
}; };
team?: Team & { teamEmail?: TeamEmail | null } & { currentTeamMember?: { role: TeamMemberRole } }; team?: Team & { teamEmail: TeamEmail | null } & { currentTeamMember?: { role: TeamMemberRole } };
} }
export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPageViewProps) => { export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPageViewProps) => {
@ -50,25 +50,14 @@ export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPa
const currentTeam = team const currentTeam = team
? { id: team.id, url: team.url, teamEmail: team.teamEmail?.email } ? { id: team.id, url: team.url, teamEmail: team.teamEmail?.email }
: undefined; : undefined;
const currentTeamMemberRole = team?.currentTeamMember?.role;
const getStatOptions: GetStatsInput = { const getStatOptions: GetStatsInput = {
user, user,
period, period,
team,
search, search,
}; };
if (team) {
getStatOptions.team = {
teamId: team.id,
teamEmail: team.teamEmail?.email,
senderIds,
currentTeamMemberRole,
currentUserEmail: user.email,
userId: user.id,
};
}
const stats = await getStats(getStatOptions); const stats = await getStats(getStatOptions);
const results = await findDocuments({ const results = await findDocuments({

View File

@ -16,8 +16,8 @@ ENV_FILES.forEach((file) => {
export default defineConfig({ export default defineConfig({
testDir: './e2e', testDir: './e2e',
/* Run tests in files in parallel */ /* Run tests in files in parallel */
fullyParallel: false, fullyParallel: true,
workers: 1, workers: '50%',
/* Fail the build on CI if you accidentally left test.only in the source code. */ /* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI, forbidOnly: !!process.env.CI,
/* Retry on CI only */ /* Retry on CI only */

View File

@ -2,9 +2,8 @@ import { DateTime } from 'luxon';
import { P, match } from 'ts-pattern'; import { P, match } from 'ts-pattern';
import { prisma } from '@documenso/prisma'; import { prisma } from '@documenso/prisma';
import { RecipientRole, SigningStatus, TeamMemberRole } from '@documenso/prisma/client';
import type { Document, DocumentSource, Team, TeamEmail, User } from '@documenso/prisma/client'; import type { Document, DocumentSource, Team, TeamEmail, User } from '@documenso/prisma/client';
import { Prisma } from '@documenso/prisma/client'; import { Prisma, RecipientRole, SigningStatus, TeamMemberRole } from '@documenso/prisma/client';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import { DocumentVisibility } from '../../types/document-visibility'; import { DocumentVisibility } from '../../types/document-visibility';
@ -133,6 +132,8 @@ export const findDocuments = async ({
let filters: Prisma.DocumentWhereInput | null = findDocumentsFilter(status, user); let filters: Prisma.DocumentWhereInput | null = findDocumentsFilter(status, user);
console.log('find documets team', team);
if (team) { if (team) {
filters = findTeamDocumentsFilter(status, team, visibilityFilters); filters = findTeamDocumentsFilter(status, team, visibilityFilters);
} }
@ -285,7 +286,7 @@ export const findDocuments = async ({
} satisfies FindResultSet<typeof data>; } satisfies FindResultSet<typeof data>;
}; };
const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => { export const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
return match<ExtendedDocumentStatus, Prisma.DocumentWhereInput>(status) return match<ExtendedDocumentStatus, Prisma.DocumentWhereInput>(status)
.with(ExtendedDocumentStatus.ALL, () => ({ .with(ExtendedDocumentStatus.ALL, () => ({
OR: [ OR: [
@ -443,7 +444,7 @@ const findDocumentsFilter = (status: ExtendedDocumentStatus, user: User) => {
* @param team The team to find the documents for. * @param team The team to find the documents for.
* @returns A filter which can be applied to the Prisma Document schema. * @returns A filter which can be applied to the Prisma Document schema.
*/ */
const findTeamDocumentsFilter = ( export const findTeamDocumentsFilter = (
status: ExtendedDocumentStatus, status: ExtendedDocumentStatus,
team: Team & { teamEmail: TeamEmail | null }, team: Team & { teamEmail: TeamEmail | null },
visibilityFilters: Prisma.DocumentWhereInput[], visibilityFilters: Prisma.DocumentWhereInput[],

View File

@ -0,0 +1,118 @@
import { DateTime } from 'luxon';
import { match } from 'ts-pattern';
import {
type PeriodSelectorValue,
findDocumentsFilter,
findTeamDocumentsFilter,
} from '@documenso/lib/server-only/document/find-documents';
import { prisma } from '@documenso/prisma';
import type { Prisma, Team, TeamEmail, User } from '@documenso/prisma/client';
import { DocumentVisibility, TeamMemberRole } from '@documenso/prisma/client';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
export type GetStatsInput = {
user: User;
team?: Team & { teamEmail: TeamEmail | null } & { currentTeamMember?: { role: TeamMemberRole } };
period?: PeriodSelectorValue;
search?: string;
};
export const getStats = async ({ user, period, search, ...options }: GetStatsInput) => {
let createdAt: Prisma.DocumentWhereInput['createdAt'];
if (period) {
const daysAgo = parseInt(period.replace(/d$/, ''), 10);
const startOfPeriod = DateTime.now().minus({ days: daysAgo }).startOf('day');
createdAt = {
gte: startOfPeriod.toJSDate(),
};
}
const stats: Record<ExtendedDocumentStatus, number> = {
[ExtendedDocumentStatus.DRAFT]: 0,
[ExtendedDocumentStatus.PENDING]: 0,
[ExtendedDocumentStatus.COMPLETED]: 0,
[ExtendedDocumentStatus.INBOX]: 0,
[ExtendedDocumentStatus.ALL]: 0,
[ExtendedDocumentStatus.BIN]: 0,
};
const searchFilter: Prisma.DocumentWhereInput = search
? {
OR: [
{ title: { contains: search, mode: 'insensitive' } },
{ Recipient: { some: { name: { contains: search, mode: 'insensitive' } } } },
{ Recipient: { some: { email: { contains: search, mode: 'insensitive' } } } },
],
}
: {};
const visibilityFilters = [
match(options.team?.currentTeamMember?.role)
.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: DocumentVisibility.EVERYONE })),
];
const statusCounts = await Promise.all(
Object.values(ExtendedDocumentStatus).map(async (status) => {
if (status === ExtendedDocumentStatus.ALL) {
return;
}
const filter = options.team
? findTeamDocumentsFilter(status, options.team, visibilityFilters)
: findDocumentsFilter(status, user);
if (filter === null) {
return { status, count: 0 };
}
const whereClause = {
...filter,
...(createdAt && { createdAt }),
...searchFilter,
};
const count = await prisma.document.count({
where: whereClause,
});
return { status, count };
}),
);
statusCounts.forEach((result) => {
if (result) {
stats[result.status] = result.count;
if (
result.status !== ExtendedDocumentStatus.BIN &&
[
ExtendedDocumentStatus.DRAFT,
ExtendedDocumentStatus.PENDING,
ExtendedDocumentStatus.COMPLETED,
ExtendedDocumentStatus.INBOX,
].includes(result.status)
) {
stats[ExtendedDocumentStatus.ALL] += result.count;
}
}
});
return stats;
};

View File

@ -114,16 +114,9 @@ const getCounts = async ({ user, createdAt, search }: GetCountsOption) => {
deletedAt: null, deletedAt: null,
}, },
{ {
status: ExtendedDocumentStatus.COMPLETED, status: {
Recipient: { not: ExtendedDocumentStatus.DRAFT,
some: {
email: user.email,
documentDeletedAt: null,
}, },
},
},
{
status: ExtendedDocumentStatus.PENDING,
Recipient: { Recipient: {
some: { some: {
email: user.email, email: user.email,
@ -151,7 +144,7 @@ const getCounts = async ({ user, createdAt, search }: GetCountsOption) => {
email: user.email, email: user.email,
signingStatus: SigningStatus.NOT_SIGNED, signingStatus: SigningStatus.NOT_SIGNED,
role: { role: {
not: 'CC', not: RecipientRole.CC,
}, },
documentDeletedAt: null, documentDeletedAt: null,
}, },
@ -181,7 +174,7 @@ const getCounts = async ({ user, createdAt, search }: GetCountsOption) => {
email: user.email, email: user.email,
signingStatus: SigningStatus.SIGNED, signingStatus: SigningStatus.SIGNED,
role: { role: {
not: 'CC', not: RecipientRole.CC,
}, },
documentDeletedAt: null, documentDeletedAt: null,
}, },
@ -302,10 +295,12 @@ const getTeamCounts = async (options: GetTeamCountsOption) => {
.otherwise(() => ({ visibility: DocumentVisibility.EVERYONE })), .otherwise(() => ({ visibility: DocumentVisibility.EVERYONE })),
]; ];
return Promise.all([
// Owner counts (ALL) // Owner counts (ALL)
const ownerCountsWhereInput: Prisma.DocumentWhereInput = { prisma.document.groupBy({
userId: userIdWhereClause, by: ['status'],
createdAt, _count: { _all: true },
where: {
OR: [ OR: [
{ {
teamId, teamId,
@ -321,6 +316,7 @@ const getTeamCounts = async (options: GetTeamCountsOption) => {
Recipient: { Recipient: {
some: { some: {
email: teamEmail, email: teamEmail,
documentDeletedAt: null,
}, },
}, },
deletedAt: null, deletedAt: null,
@ -336,11 +332,17 @@ const getTeamCounts = async (options: GetTeamCountsOption) => {
] ]
: []), : []),
], ],
userId: userIdWhereClause,
createdAt,
...searchFilter, ...searchFilter,
}; },
}),
// Not signed counts (INBOX) // Not signed counts (INBOX)
const notSignedCountsWhereInput: Prisma.DocumentWhereInput = teamEmail prisma.document.groupBy({
by: ['status'],
_count: { _all: true },
where: teamEmail
? { ? {
userId: userIdWhereClause, userId: userIdWhereClause,
createdAt, createdAt,
@ -369,10 +371,14 @@ const getTeamCounts = async (options: GetTeamCountsOption) => {
}, },
searchFilter, searchFilter,
], ],
}; },
}),
// Has signed counts (PENDING + COMPLETED) // Has signed counts (PENDING + COMPLETED)
const hasSignedCountsWhereInput: Prisma.DocumentWhereInput = { prisma.document.groupBy({
by: ['status'],
_count: { _all: true },
where: {
userId: userIdWhereClause, userId: userIdWhereClause,
createdAt, createdAt,
OR: [ OR: [
@ -401,6 +407,7 @@ const getTeamCounts = async (options: GetTeamCountsOption) => {
role: { role: {
not: RecipientRole.CC, not: RecipientRole.CC,
}, },
documentDeletedAt: null,
}, },
}, },
OR: visibilityFilters, OR: visibilityFilters,
@ -421,6 +428,7 @@ const getTeamCounts = async (options: GetTeamCountsOption) => {
Recipient: { Recipient: {
some: { some: {
email: teamEmail, email: teamEmail,
documentDeletedAt: null,
}, },
}, },
OR: visibilityFilters, OR: visibilityFilters,
@ -438,9 +446,14 @@ const getTeamCounts = async (options: GetTeamCountsOption) => {
: []), : []),
], ],
...searchFilter, ...searchFilter,
}; },
}),
const deletedCountsWhereInput: Prisma.DocumentWhereInput = { // Deleted counts (BIN)
prisma.document.groupBy({
by: ['status'],
_count: { _all: true },
where: {
OR: [ OR: [
{ {
teamId, teamId,
@ -450,6 +463,14 @@ const getTeamCounts = async (options: GetTeamCountsOption) => {
}, },
...(teamEmail ...(teamEmail
? [ ? [
{
User: {
email: teamEmail,
},
deletedAt: {
gte: DateTime.now().minus({ days: 30 }).startOf('day').toJSDate(),
},
},
{ {
Recipient: { Recipient: {
some: { some: {
@ -464,28 +485,7 @@ const getTeamCounts = async (options: GetTeamCountsOption) => {
: []), : []),
], ],
...searchFilter, ...searchFilter,
}; },
return Promise.all([
prisma.document.groupBy({
by: ['status'],
_count: { _all: true },
where: ownerCountsWhereInput,
}),
prisma.document.groupBy({
by: ['status'],
_count: { _all: true },
where: notSignedCountsWhereInput,
}),
prisma.document.groupBy({
by: ['status'],
_count: { _all: true },
where: hasSignedCountsWhereInput,
}),
prisma.document.groupBy({
by: ['status'],
_count: { _all: true },
where: deletedCountsWhereInput,
}), }),
]); ]);
}; };

View File

@ -1619,7 +1619,7 @@ msgstr "Dokument wird dauerhaft gelöscht"
#: apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx:109 #: apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx:109
#: apps/web/src/app/(dashboard)/documents/[id]/loading.tsx:16 #: apps/web/src/app/(dashboard)/documents/[id]/loading.tsx:16
#: apps/web/src/app/(dashboard)/documents/[id]/sent/page.tsx:15 #: apps/web/src/app/(dashboard)/documents/[id]/sent/page.tsx:15
#: apps/web/src/app/(dashboard)/documents/documents-page-view.tsx:119 #: apps/web/src/app/(dashboard)/documents/documents-page-view.tsx:108
#: apps/web/src/app/(profile)/p/[url]/page.tsx:166 #: apps/web/src/app/(profile)/p/[url]/page.tsx:166
#: apps/web/src/app/not-found.tsx:21 #: apps/web/src/app/not-found.tsx:21
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:205 #: apps/web/src/components/(dashboard)/common/command-menu.tsx:205

View File

@ -1614,7 +1614,7 @@ msgstr "Document will be permanently deleted"
#: apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx:109 #: apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx:109
#: apps/web/src/app/(dashboard)/documents/[id]/loading.tsx:16 #: apps/web/src/app/(dashboard)/documents/[id]/loading.tsx:16
#: apps/web/src/app/(dashboard)/documents/[id]/sent/page.tsx:15 #: apps/web/src/app/(dashboard)/documents/[id]/sent/page.tsx:15
#: apps/web/src/app/(dashboard)/documents/documents-page-view.tsx:119 #: apps/web/src/app/(dashboard)/documents/documents-page-view.tsx:108
#: apps/web/src/app/(profile)/p/[url]/page.tsx:166 #: apps/web/src/app/(profile)/p/[url]/page.tsx:166
#: apps/web/src/app/not-found.tsx:21 #: apps/web/src/app/not-found.tsx:21
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:205 #: apps/web/src/components/(dashboard)/common/command-menu.tsx:205

View File

@ -1619,7 +1619,7 @@ msgstr "El documento será eliminado permanentemente"
#: apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx:109 #: apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx:109
#: apps/web/src/app/(dashboard)/documents/[id]/loading.tsx:16 #: apps/web/src/app/(dashboard)/documents/[id]/loading.tsx:16
#: apps/web/src/app/(dashboard)/documents/[id]/sent/page.tsx:15 #: apps/web/src/app/(dashboard)/documents/[id]/sent/page.tsx:15
#: apps/web/src/app/(dashboard)/documents/documents-page-view.tsx:119 #: apps/web/src/app/(dashboard)/documents/documents-page-view.tsx:108
#: apps/web/src/app/(profile)/p/[url]/page.tsx:166 #: apps/web/src/app/(profile)/p/[url]/page.tsx:166
#: apps/web/src/app/not-found.tsx:21 #: apps/web/src/app/not-found.tsx:21
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:205 #: apps/web/src/components/(dashboard)/common/command-menu.tsx:205

View File

@ -1619,7 +1619,7 @@ msgstr "Le document sera supprimé de manière permanente"
#: apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx:109 #: apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx:109
#: apps/web/src/app/(dashboard)/documents/[id]/loading.tsx:16 #: apps/web/src/app/(dashboard)/documents/[id]/loading.tsx:16
#: apps/web/src/app/(dashboard)/documents/[id]/sent/page.tsx:15 #: apps/web/src/app/(dashboard)/documents/[id]/sent/page.tsx:15
#: apps/web/src/app/(dashboard)/documents/documents-page-view.tsx:119 #: apps/web/src/app/(dashboard)/documents/documents-page-view.tsx:108
#: apps/web/src/app/(profile)/p/[url]/page.tsx:166 #: apps/web/src/app/(profile)/p/[url]/page.tsx:166
#: apps/web/src/app/not-found.tsx:21 #: apps/web/src/app/not-found.tsx:21
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:205 #: apps/web/src/components/(dashboard)/common/command-menu.tsx:205