From b29a724c34a132983986eb5542ef3e9bdd2ab7f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= Date: Mon, 30 Sep 2024 08:59:05 +0200 Subject: [PATCH] Document evaluation results table with pagination (#300) * Paginate document evaluation results and refactor pagination Refactor pagination to make it more generic and easy to use in any table we want to add pagination * Fix troll test --- .../commits/fetchCommitsByProjectAction.ts | 23 ++-- .../computeEvaluationResultsWithMetadata.ts | 26 ++-- .../Editor/Playground/Chat/index.tsx | 2 +- .../DocumentEditor/Editor/Playground/Chat.tsx | 2 +- .../_components/DocumentTabs/index.tsx | 4 +- .../DatasetForm/index.tsx | 0 .../CreateBatchEvaluationModal/index.tsx | 25 ++-- .../CreateBatchEvaluationModal/useRunBatch.ts | 0 .../useRunBatchForm.ts | 0 .../_components/Actions/index.tsx | 31 +++-- .../EvaluationResultInfo/Messages.tsx | 3 +- .../EvaluationResultInfo/index.tsx | 39 ++++-- .../EvaluationResultsTable.tsx | 21 +++- .../_components/EvaluationResults/index.tsx | 10 ++ .../_lib/fetchEvaluationCached.ts | 10 ++ .../[evaluationId]/create-batch/page.tsx | 38 ------ .../evaluations/[evaluationId]/layout.tsx | 27 +--- .../evaluations/[evaluationId]/page.tsx | 58 ++++++++- .../DocumentLogs/DocumentLogInfo/Messages.tsx | 2 +- .../DocumentLogs/DocumentLogInfo/index.tsx | 45 +++++-- .../DocumentLogs/DocumentLogsTable.tsx | 22 +++- .../logs/_components/DocumentLogs/index.tsx | 19 ++- .../documents/[documentUuid]/logs/page.tsx | 49 ++++---- .../GoToPageInput/index.tsx | 38 ++++++ .../TablePaginationFooter/index.tsx | 72 +++++++++++ .../src/components/WebPagination/index.tsx | 50 -------- .../components/layouts/AppLayout/index.tsx | 2 +- apps/web/src/hooks/useDynamicHeight.ts | 42 +++++++ .../stores/evaluationResultsWithMetadata.ts | 18 ++- apps/websockets/src/server.ts | 2 + .../handlers/createEvaluationResultJob.ts | 6 +- packages/core/src/lib/buildPagination.ts | 110 ----------------- packages/core/src/lib/index.ts | 1 + .../src/lib/pagination/buildPaginatedUrl.ts | 32 +++++ .../src/lib/pagination/buildPagination.ts | 79 ++++++++++++ packages/core/src/lib/pagination/paginate.ts | 70 +++++++++++ .../commitsRepository/index.test.ts | 36 ++---- .../repositories/commitsRepository/index.ts | 21 ++-- .../documentVersionsRepository/index.test.ts | 3 +- packages/core/src/repositories/repository.ts | 29 +---- .../computeDocumentLogsWithMetadata.test.ts | 14 +-- .../computeDocumentLogsWithMetadata.ts | 9 +- .../computeEvaluationResultsWithMetadata.ts | 14 +-- packages/core/src/websockets/constants.ts | 6 +- packages/web-ui/src/ds/atoms/Input/index.tsx | 17 ++- .../web-ui/src/ds/atoms/SplitPane/index.tsx | 2 +- packages/web-ui/src/ds/atoms/Table/index.tsx | 38 +++--- .../ds/molecules/Chat/MessageList/index.tsx | 41 +------ .../src/ds/molecules/Pagination/index.tsx | 115 ------------------ packages/web-ui/src/ds/molecules/index.ts | 1 - 50 files changed, 702 insertions(+), 622 deletions(-) rename apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/{create-batch/_components => _components/Actions}/CreateBatchEvaluationModal/DatasetForm/index.tsx (100%) rename apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/{create-batch/_components => _components/Actions}/CreateBatchEvaluationModal/index.tsx (80%) rename apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/{create-batch/_components => _components/Actions}/CreateBatchEvaluationModal/useRunBatch.ts (100%) rename apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/{create-batch/_components => _components/Actions}/CreateBatchEvaluationModal/useRunBatchForm.ts (100%) create mode 100644 apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_lib/fetchEvaluationCached.ts delete mode 100644 apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/create-batch/page.tsx create mode 100644 apps/web/src/components/TablePaginationFooter/GoToPageInput/index.tsx create mode 100644 apps/web/src/components/TablePaginationFooter/index.tsx delete mode 100644 apps/web/src/components/WebPagination/index.tsx create mode 100644 apps/web/src/hooks/useDynamicHeight.ts delete mode 100644 packages/core/src/lib/buildPagination.ts create mode 100644 packages/core/src/lib/pagination/buildPaginatedUrl.ts create mode 100644 packages/core/src/lib/pagination/buildPagination.ts create mode 100644 packages/core/src/lib/pagination/paginate.ts delete mode 100644 packages/web-ui/src/ds/molecules/Pagination/index.tsx diff --git a/apps/web/src/actions/commits/fetchCommitsByProjectAction.ts b/apps/web/src/actions/commits/fetchCommitsByProjectAction.ts index 14991a442..f0f7dac8c 100644 --- a/apps/web/src/actions/commits/fetchCommitsByProjectAction.ts +++ b/apps/web/src/actions/commits/fetchCommitsByProjectAction.ts @@ -1,6 +1,7 @@ 'use server' import { CommitStatus } from '@latitude-data/core/browser' +import { paginateQuery } from '@latitude-data/core/lib/index' import { CommitsRepository } from '@latitude-data/core/repositories' import { z } from 'zod' @@ -8,7 +9,6 @@ import { withProject } from '../procedures' // Not really using pagination yet const ULTRA_LARGE_PAGE_SIZE = 1000 - export const fetchCommitsByProjectAction = withProject .createServerAction() .input( @@ -20,12 +20,17 @@ export const fetchCommitsByProjectAction = withProject { type: 'json' }, ) .handler(async ({ input, ctx }) => { - return new CommitsRepository(ctx.workspace.id) - .getCommitsByProject({ - project: ctx.project, - filterByStatus: input.status, - page: input.page ?? 1, - pageSize: input.pageSize ?? ULTRA_LARGE_PAGE_SIZE, - }) - .then((r) => r.unwrap()) + const repo = new CommitsRepository(ctx.workspace.id) + const { rows } = await paginateQuery({ + dynamicQuery: repo + .getCommitsByProjectQuery({ + project: ctx.project, + filterByStatus: input.status, + }) + .$dynamic(), + defaultPaginate: { + pageSize: ULTRA_LARGE_PAGE_SIZE, + }, + }) + return rows }) diff --git a/apps/web/src/actions/evaluations/computeEvaluationResultsWithMetadata.ts b/apps/web/src/actions/evaluations/computeEvaluationResultsWithMetadata.ts index 1520853b9..7fe05fecd 100644 --- a/apps/web/src/actions/evaluations/computeEvaluationResultsWithMetadata.ts +++ b/apps/web/src/actions/evaluations/computeEvaluationResultsWithMetadata.ts @@ -1,10 +1,12 @@ 'use server' +import { paginateQuery } from '@latitude-data/core/lib/index' import { CommitsRepository, + EvaluationResultWithMetadata, EvaluationsRepository, } from '@latitude-data/core/repositories' -import { computeEvaluationResultsWithMetadata } from '@latitude-data/core/services/evaluationResults/computeEvaluationResultsWithMetadata' +import { computeEvaluationResultsWithMetadataQuery } from '@latitude-data/core/services/evaluationResults/computeEvaluationResultsWithMetadata' import { z } from 'zod' import { withProject } from '../procedures' @@ -16,6 +18,8 @@ export const computeEvaluationResultsWithMetadataAction = withProject evaluationId: z.number(), documentUuid: z.string(), commitUuid: z.string(), + page: z.string().nullable(), + pageSize: z.string().nullable(), }), ) .handler(async ({ input, ctx }) => { @@ -29,12 +33,18 @@ export const computeEvaluationResultsWithMetadataAction = withProject const commit = await commitsScope .getCommitByUuid({ projectId: project.id, uuid: input.commitUuid }) .then((r) => r.unwrap()) + const { rows } = await paginateQuery({ + searchParams: { + page: input.page ?? undefined, + pageSize: input.pageSize ?? undefined, + }, + dynamicQuery: computeEvaluationResultsWithMetadataQuery({ + workspaceId: evaluation.workspaceId, + evaluation, + documentUuid, + draft: commit, + }).$dynamic(), + }) - return await computeEvaluationResultsWithMetadata({ - workspaceId: ctx.workspace.id, - evaluation, - documentUuid, - draft: commit, - limit: 1000, - }).then((r) => r.unwrap()) + return rows as EvaluationResultWithMetadata[] }) diff --git a/apps/web/src/app/(private)/evaluations/(evaluation)/[evaluationUuid]/editor/_components/EvaluationEditor/Editor/Playground/Chat/index.tsx b/apps/web/src/app/(private)/evaluations/(evaluation)/[evaluationUuid]/editor/_components/EvaluationEditor/Editor/Playground/Chat/index.tsx index 3dee36dc7..7e6da5a2e 100644 --- a/apps/web/src/app/(private)/evaluations/(evaluation)/[evaluationUuid]/editor/_components/EvaluationEditor/Editor/Playground/Chat/index.tsx +++ b/apps/web/src/app/(private)/evaluations/(evaluation)/[evaluationUuid]/editor/_components/EvaluationEditor/Editor/Playground/Chat/index.tsx @@ -144,7 +144,7 @@ export default function Chat({
Prompt
Prompt
-
- {children} -
+
{children}
) } diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/create-batch/_components/CreateBatchEvaluationModal/DatasetForm/index.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/Actions/CreateBatchEvaluationModal/DatasetForm/index.tsx similarity index 100% rename from apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/create-batch/_components/CreateBatchEvaluationModal/DatasetForm/index.tsx rename to apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/Actions/CreateBatchEvaluationModal/DatasetForm/index.tsx diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/create-batch/_components/CreateBatchEvaluationModal/index.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/Actions/CreateBatchEvaluationModal/index.tsx similarity index 80% rename from apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/create-batch/_components/CreateBatchEvaluationModal/index.tsx rename to apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/Actions/CreateBatchEvaluationModal/index.tsx index c51268be8..c8d660b48 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/create-batch/_components/CreateBatchEvaluationModal/index.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/Actions/CreateBatchEvaluationModal/index.tsx @@ -1,45 +1,34 @@ -'use client' - import { useCallback } from 'react' import { DocumentVersion, EvaluationDto } from '@latitude-data/core/browser' import { Button, CloseTrigger, Modal } from '@latitude-data/web-ui' import { useMetadata } from '$/hooks/useMetadata' -import { useNavigate } from '$/hooks/useNavigate' -import { ROUTES } from '$/services/routes' import DatasetForm from './DatasetForm' import { useRunBatch } from './useRunBatch' import { useRunBatchForm } from './useRunBatchForm' export default function CreateBatchEvaluationModal({ + open, + onClose, document, evaluation, projectId, commitUuid, }: { + open: boolean + onClose: () => void projectId: string commitUuid: string document: DocumentVersion evaluation: EvaluationDto }) { - const navigate = useNavigate() - const documentUuid = document.documentUuid - const goToDetail = useCallback(() => { - navigate.push( - ROUTES.projects - .detail({ id: Number(projectId) }) - .commits.detail({ uuid: commitUuid }) - .documents.detail({ uuid: documentUuid }) - .evaluations.detail(evaluation.id).root, - ) - }, [evaluation.id, projectId, commitUuid, documentUuid]) const { runBatch, errors, isRunningBatch } = useRunBatch({ document, projectId, commitUuid, onSuccess: () => { - goToDetail() + onClose() }, }) @@ -73,11 +62,11 @@ export default function CreateBatchEvaluationModal({ return ( diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/create-batch/_components/CreateBatchEvaluationModal/useRunBatch.ts b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/Actions/CreateBatchEvaluationModal/useRunBatch.ts similarity index 100% rename from apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/create-batch/_components/CreateBatchEvaluationModal/useRunBatch.ts rename to apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/Actions/CreateBatchEvaluationModal/useRunBatch.ts diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/create-batch/_components/CreateBatchEvaluationModal/useRunBatchForm.ts b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/Actions/CreateBatchEvaluationModal/useRunBatchForm.ts similarity index 100% rename from apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/create-batch/_components/CreateBatchEvaluationModal/useRunBatchForm.ts rename to apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/Actions/CreateBatchEvaluationModal/useRunBatchForm.ts diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/Actions/index.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/Actions/index.tsx index 8fedfd3ff..13ef38a46 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/Actions/index.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/Actions/index.tsx @@ -1,10 +1,11 @@ 'use client' +import { useCallback, useState } from 'react' + import { EvaluationDto } from '@latitude-data/core/browser' -import { TableWithHeader } from '@latitude-data/web-ui' -import { ROUTES } from '$/services/routes' -import Link from 'next/link' +import { TableWithHeader, useCurrentDocument } from '@latitude-data/web-ui' +import CreateBatchEvaluationModal from './CreateBatchEvaluationModal' import LiveEvaluationToggle from './LiveEvaluationToggle' export function Actions({ @@ -18,21 +19,27 @@ export function Actions({ commitUuid: string documentUuid: string }) { - const href = ROUTES.projects - .detail({ id: Number(projectId) }) - .commits.detail({ uuid: commitUuid }) - .documents.detail({ uuid: documentUuid }) - .evaluations.detail(evaluation.id).createBatch - + const document = useCurrentDocument() + const [open, setOpen] = useState(false) + const onClose = useCallback(() => setOpen(false), []) + const onOpen = useCallback(() => setOpen(true), []) return (
- - Run batch evaluation - + + Run batch evaluation + +
) } diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/EvaluationResults/EvaluationResultInfo/Messages.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/EvaluationResults/EvaluationResultInfo/Messages.tsx index 0b5ade8d3..22ea585b8 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/EvaluationResults/EvaluationResultInfo/Messages.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/EvaluationResults/EvaluationResultInfo/Messages.tsx @@ -23,9 +23,8 @@ export function EvaluationResultMessages({ if (!providerLog) return null return ( -
+
(null) const [selectedTab, setSelectedTab] = useState('metadata') const [selected, setSelected] = useState(null) const onClickOpen = useCallback(async () => { setSelected(evaluationResult.documentLogId) }, [evaluationResult.documentLogId]) + const height = useDynamicHeight({ ref, paddingBottom: 16 }) return ( <> -
- -
+
+
+
+ +
+
+
{selectedTab === 'metadata' && ( )} +
+
diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/EvaluationResults/EvaluationResultsTable.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/EvaluationResults/EvaluationResultsTable.tsx index c79d6172c..40d67d301 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/EvaluationResults/EvaluationResultsTable.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/EvaluationResults/EvaluationResultsTable.tsx @@ -1,9 +1,11 @@ +import { useRef } from 'react' import { capitalize } from 'lodash-es' import { EvaluationDto, EvaluationResultableType, } from '@latitude-data/core/browser' +import { IPagination } from '@latitude-data/core/lib/pagination/buildPagination' import { EvaluationResultWithMetadata } from '@latitude-data/core/repositories' import { Badge, @@ -18,6 +20,12 @@ import { Text, } from '@latitude-data/web-ui' import { formatCostInMillicents, relativeTime } from '$/app/_lib/formatUtils' +import { TablePaginationFooter } from '$/components/TablePaginationFooter' +import useDynamicHeight from '$/hooks/useDynamicHeight' + +function countLabel(count: number) { + return `${count} evaluation results` +} export const ResultCellContent = ({ evaluation, @@ -55,17 +63,21 @@ type EvaluationResultRow = EvaluationResultWithMetadata & { } export const EvaluationResultsTable = ({ evaluation, + pagination, evaluationResults, selectedResult, setSelectedResult, }: { evaluation: EvaluationDto + pagination: IPagination evaluationResults: EvaluationResultRow[] selectedResult: EvaluationResultRow | undefined setSelectedResult: (log: EvaluationResultWithMetadata | undefined) => void }) => { + const ref = useRef(null) + const height = useDynamicHeight({ ref, paddingBottom: 16 }) return ( - +
Time @@ -76,7 +88,7 @@ export const EvaluationResultsTable = ({ Tokens - + {evaluationResults.map((evaluationResult) => ( ))} +
) } diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/EvaluationResults/index.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/EvaluationResults/index.tsx index 91bb72847..68c405264 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/EvaluationResults/index.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_components/EvaluationResults/index.tsx @@ -3,6 +3,7 @@ import { useState } from 'react' import { EvaluationDto } from '@latitude-data/core/browser' +import { IPagination } from '@latitude-data/core/lib/pagination/buildPagination' import { type EvaluationResultWithMetadata } from '@latitude-data/core/repositories' import { TableBlankSlate, @@ -19,6 +20,7 @@ import { DocumentRoutes, ROUTES } from '$/services/routes' import useEvaluationResultsWithMetadata from '$/stores/evaluationResultsWithMetadata' import { useProviderLog } from '$/stores/providerLogs' import Link from 'next/link' +import { useSearchParams } from 'next/navigation' import { EvaluationResultInfo } from './EvaluationResultInfo' import { EvaluationResultsTable } from './EvaluationResultsTable' @@ -27,9 +29,11 @@ import { EvaluationStatusBanner } from './EvaluationStatusBanner' export function EvaluationResults({ evaluation, evaluationResults: serverData, + pagination, }: { evaluation: EvaluationDto evaluationResults: EvaluationResultWithMetadata[] + pagination: IPagination }) { const { project } = useCurrentProject() const { commit } = useCurrentCommit() @@ -38,12 +42,17 @@ export function EvaluationResults({ EvaluationResultWithMetadata | undefined >(undefined) const { data: providerLog } = useProviderLog(selectedResult?.providerLogId) + const searchParams = useSearchParams() + const page = searchParams.get('page') + const pageSize = searchParams.get('pageSize') const { data: evaluationResults, mutate } = useEvaluationResultsWithMetadata( { evaluationId: evaluation.id, documentUuid: document.documentUuid, commitUuid: commit.uuid, projectId: project.id, + page, + pageSize, }, { fallbackData: serverData, @@ -103,6 +112,7 @@ export function EvaluationResults({ evaluationResults={evaluationResults} selectedResult={selectedResult} setSelectedResult={setSelectedResult} + pagination={pagination} /> )}
diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_lib/fetchEvaluationCached.ts b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_lib/fetchEvaluationCached.ts new file mode 100644 index 000000000..2cb5e40a3 --- /dev/null +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/_lib/fetchEvaluationCached.ts @@ -0,0 +1,10 @@ +import { cache } from 'react' + +import { EvaluationsRepository } from '@latitude-data/core/repositories' +import { getCurrentUser } from '$/services/auth/getCurrentUser' + +export const fetchEvaluationCached = cache(async (id: number) => { + const { workspace } = await getCurrentUser() + const evaluationScope = new EvaluationsRepository(workspace.id) + return evaluationScope.find(id).then((r) => r.unwrap()) +}) diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/create-batch/page.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/create-batch/page.tsx deleted file mode 100644 index 5c5fb5555..000000000 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/create-batch/page.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { EvaluationsRepository } from '@latitude-data/core/repositories' -import { getDocumentByUuidCached } from '$/app/(private)/_data-access' -import { getCurrentUser } from '$/services/auth/getCurrentUser' - -import CreateBatchEvaluationModal from './_components/CreateBatchEvaluationModal' - -export default async function ConnectionEvaluationModal({ - params, -}: { - params: { - projectId: string - commitUuid: string - documentUuid: string - evaluationId: string - } -}) { - const { workspace } = await getCurrentUser() - const evaluationScope = new EvaluationsRepository(workspace.id) - const evaluation = await evaluationScope - .find(params.evaluationId) - .then((r) => r.unwrap()) - const projectId = Number(params.projectId) - const documentUuid = params.documentUuid - const commitUuid = params.commitUuid - const document = await getDocumentByUuidCached({ - projectId, - commitUuid, - documentUuid, - }) - return ( - - ) -} diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/layout.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/layout.tsx index 431925feb..2461aca7a 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/layout.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/layout.tsx @@ -8,8 +8,6 @@ import { EvaluationModalValue, EvaluationResultableType, } from '@latitude-data/core/browser' -import { EvaluationsRepository } from '@latitude-data/core/repositories' -import { computeEvaluationResultsWithMetadata } from '@latitude-data/core/services/evaluationResults/computeEvaluationResultsWithMetadata' import { getEvaluationMeanValueQuery, getEvaluationModalValueQuery, @@ -24,8 +22,8 @@ import { ROUTES } from '$/services/routes' import Link from 'next/link' import { Actions } from './_components/Actions' -import { EvaluationResults } from './_components/EvaluationResults' import { MetricsSummary } from './_components/MetricsSummary' +import { fetchEvaluationCached } from './_lib/fetchEvaluationCached' const TYPE_TEXT: Record = { [EvaluationResultableType.Text]: 'Text', @@ -94,23 +92,11 @@ export default async function ConnectedEvaluationLayout({ } }) { const { workspace } = await getCurrentUser() - const evaluationScope = new EvaluationsRepository(workspace.id) - const evaluation = await evaluationScope - .find(params.evaluationId) - .then((r) => r.unwrap()) - + const evaluation = await fetchEvaluationCached(Number(params.evaluationId)) const commit = await findCommitCached({ projectId: Number(params.projectId), uuid: params.commitUuid, }) - - const evaluationResults = await computeEvaluationResultsWithMetadata({ - workspaceId: evaluation.workspaceId, - evaluation, - documentUuid: params.documentUuid, - draft: commit, - limit: 1000, - }).then((r) => r.unwrap()) const isNumeric = evaluation.configuration.type == EvaluationResultableType.Number const data = await fetchData({ @@ -120,10 +106,8 @@ export default async function ConnectedEvaluationLayout({ isNumeric, commit, }) - return ( -
- {children} +
- + {children}
) } diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/page.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/page.tsx index 603e00efb..468d0ffe2 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/page.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/page.tsx @@ -1,3 +1,57 @@ -export default function ConnectedEvaluationsPage() { - return null // --> layout.tsx +import { paginateQuery } from '@latitude-data/core/lib/index' +import { QueryParams } from '@latitude-data/core/lib/pagination/buildPaginatedUrl' +import { computeEvaluationResultsWithMetadataQuery } from '@latitude-data/core/services/evaluationResults/computeEvaluationResultsWithMetadata' +import { findCommitCached } from '$/app/(private)/_data-access' +import { ROUTES } from '$/services/routes' + +import { EvaluationResults } from './_components/EvaluationResults' +import { fetchEvaluationCached } from './_lib/fetchEvaluationCached' + +function pageUrl(params: { + projectId: string + commitUuid: string + documentUuid: string + evaluationId: string +}) { + return ROUTES.projects + .detail({ id: Number(params.projectId) }) + .commits.detail({ uuid: params.commitUuid }) + .documents.detail({ uuid: params.documentUuid }) + .evaluations.detail(Number(params.evaluationId)).root +} + +export default async function ConnectedEvaluationPage({ + params, + searchParams, +}: { + params: { + projectId: string + commitUuid: string + documentUuid: string + evaluationId: string + } + searchParams: QueryParams +}) { + const evaluation = await fetchEvaluationCached(Number(params.evaluationId)) + const commit = await findCommitCached({ + projectId: Number(params.projectId), + uuid: params.commitUuid, + }) + const { rows, pagination } = await paginateQuery({ + searchParams, + pageUrl: { base: pageUrl(params) }, + dynamicQuery: computeEvaluationResultsWithMetadataQuery({ + workspaceId: evaluation.workspaceId, + evaluation, + documentUuid: params.documentUuid, + draft: commit, + }).$dynamic(), + }) + return ( + + ) } diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogInfo/Messages.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogInfo/Messages.tsx index 02ea269fb..1091b1cff 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogInfo/Messages.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogInfo/Messages.tsx @@ -24,7 +24,7 @@ export function DocumentLogMessages({ if (!providerLogs) return null return ( -
+
('metadata') + const ref = useRef(null) + const height = useDynamicHeight({ ref, paddingBottom: 16 }) return ( - <> - -
+
+
+
+ +
+
+
{isLoading ? ( ) : ( @@ -71,6 +90,6 @@ export function DocumentLogInfo({ )}
- +
) } diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogsTable.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogsTable.tsx index fe180536d..687dd5d19 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogsTable.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/DocumentLogsTable.tsx @@ -1,3 +1,6 @@ +import { useRef } from 'react' + +import { IPagination } from '@latitude-data/core/lib/pagination/buildPagination' import { DocumentLogWithMetadata } from '@latitude-data/core/repositories' import { Badge, @@ -15,18 +18,28 @@ import { formatDuration, relativeTime, } from '$/app/_lib/formatUtils' +import { TablePaginationFooter } from '$/components/TablePaginationFooter' +import useDynamicHeight from '$/hooks/useDynamicHeight' + +function countLabel(count: number) { + return `${count} logs` +} export const DocumentLogsTable = ({ documentLogs, selectedLog, setSelectedLog, + pagination, }: { documentLogs: DocumentLogWithMetadata[] selectedLog: DocumentLogWithMetadata | undefined setSelectedLog: (log: DocumentLogWithMetadata | undefined) => void + pagination: IPagination }) => { + const ref = useRef(null) + const height = useDynamicHeight({ ref, paddingBottom: 16 }) return ( - +
Time @@ -38,7 +51,7 @@ export const DocumentLogsTable = ({ Cost - + {documentLogs.map((documentLog, idx) => ( ))} +
) } diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/index.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/index.tsx index cc10910e9..04d8f3b6a 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/index.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/_components/DocumentLogs/index.tsx @@ -2,9 +2,8 @@ import { useState } from 'react' -import { IPagination } from '@latitude-data/core/lib/buildPagination' +import { IPagination } from '@latitude-data/core/lib/pagination/buildPagination' import { DocumentLogWithMetadata } from '@latitude-data/core/repositories' -import WebPagination from '$/components/WebPagination' import useProviderLogs from '$/stores/providerLogs' import { DocumentLogInfo } from './DocumentLogInfo' @@ -25,23 +24,21 @@ export function DocumentLogs({ }) return ( -
+
-
{selectedLog && ( -
- -
+ )}
) diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/page.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/page.tsx index 12ecc4794..2ee6010df 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/page.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/logs/page.tsx @@ -1,8 +1,6 @@ -import { - buildPagination, - parsePage, -} from '@latitude-data/core/lib/buildPagination' -import { computeDocumentLogsWithMetadata } from '@latitude-data/core/services/documentLogs/computeDocumentLogsWithMetadata' +import { QueryParams } from '@latitude-data/core/lib/pagination/buildPaginatedUrl' +import { paginateQuery } from '@latitude-data/core/lib/pagination/paginate' +import { computeDocumentLogsWithMetadataQuery } from '@latitude-data/core/services/documentLogs/computeDocumentLogsWithMetadata' import { TableBlankSlate, TableWithHeader } from '@latitude-data/web-ui' import { findCommitCached } from '$/app/(private)/_data-access' import { getCurrentUser } from '$/services/auth/getCurrentUser' @@ -10,40 +8,41 @@ import { ROUTES } from '$/services/routes' import { DocumentLogs } from './_components/DocumentLogs' -const PAGE_SIZE = 25 +function pageUrl(params: { + projectId: string + commitUuid: string + documentUuid: string +}) { + return ROUTES.projects + .detail({ id: Number(params.projectId) }) + .commits.detail({ uuid: params.commitUuid }) + .documents.detail({ uuid: params.documentUuid }).logs.root +} + export default async function DocumentPage({ params, searchParams, }: { params: { projectId: string; commitUuid: string; documentUuid: string } - searchParams: { [key: string]: string | string[] | undefined } + searchParams: QueryParams }) { const { workspace } = await getCurrentUser() const projectId = Number(params.projectId) const commitUuid = params.commitUuid const commit = await findCommitCached({ projectId, uuid: commitUuid }) - const page = parsePage(searchParams.page) - const [rows, count] = await computeDocumentLogsWithMetadata({ - workspaceId: workspace.id, - documentUuid: params.documentUuid, - draft: commit, - pagination: { page, pageSize: PAGE_SIZE }, - }) - const baseUrl = ROUTES.projects - .detail({ id: projectId }) - .commits.detail({ uuid: commitUuid }) - .documents.detail({ uuid: params.documentUuid }).logs.root - const pagination = buildPagination({ - baseUrl, - count, - page, - pageSize: PAGE_SIZE, + const { rows, pagination } = await paginateQuery({ + searchParams, + pageUrl: { base: pageUrl(params) }, + dynamicQuery: computeDocumentLogsWithMetadataQuery({ + workspaceId: workspace.id, + documentUuid: params.documentUuid, + draft: commit, + }).$dynamic(), }) - const title = `${pagination.count} logs (page ${pagination.currentPage} of ${pagination.totalPages})` return (
{!rows.length && ( diff --git a/apps/web/src/components/TablePaginationFooter/GoToPageInput/index.tsx b/apps/web/src/components/TablePaginationFooter/GoToPageInput/index.tsx new file mode 100644 index 000000000..5fdfa89c0 --- /dev/null +++ b/apps/web/src/components/TablePaginationFooter/GoToPageInput/index.tsx @@ -0,0 +1,38 @@ +'use client' + +import { FormEvent, useCallback } from 'react' + +import { buildPaginatedUrl } from '@latitude-data/core/lib/pagination/buildPaginatedUrl' +import { IPagination } from '@latitude-data/core/lib/pagination/buildPagination' +import { Input } from '@latitude-data/web-ui' +import { useNavigate } from '$/hooks/useNavigate' + +export function GoToPageInput({ pagination }: { pagination: IPagination }) { + const router = useNavigate() + const onSubmit = useCallback((event: FormEvent) => { + event.preventDefault() + + const goToPage = new FormData(event.currentTarget).get('page') + const queryParams = window.location.search + router.push( + buildPaginatedUrl({ + baseUrl: pagination.baseUrl, + page: Number(goToPage), + pageSize: pagination.pageSize, + queryParams, + }), + ) + }, []) + return ( +
+ +
+ ) +} diff --git a/apps/web/src/components/TablePaginationFooter/index.tsx b/apps/web/src/components/TablePaginationFooter/index.tsx new file mode 100644 index 000000000..6945483ef --- /dev/null +++ b/apps/web/src/components/TablePaginationFooter/index.tsx @@ -0,0 +1,72 @@ +import { IPagination } from '@latitude-data/core/lib/pagination/buildPagination' +import { + Button, + TableCell, + TableFooter, + TableRow, + Text, +} from '@latitude-data/web-ui' +import { GoToPageInput } from '$/components/TablePaginationFooter/GoToPageInput' +import Link from 'next/link' + +function NavLink({ + url, + direction, +}: { + url?: string + direction: 'prev' | 'next' +}) { + const button = ( +
) } diff --git a/apps/web/src/hooks/useDynamicHeight.ts b/apps/web/src/hooks/useDynamicHeight.ts new file mode 100644 index 000000000..6b5231828 --- /dev/null +++ b/apps/web/src/hooks/useDynamicHeight.ts @@ -0,0 +1,42 @@ +import { RefObject, useCallback, useEffect, useState } from 'react' + +function useDynamicHeight({ + ref, + paddingBottom, +}: { + ref: RefObject + paddingBottom?: number +}) { + const [height, setHeight] = useState('auto') + + const calculateHeight = useCallback( + (element: HTMLElement) => { + const topPosition = element.getBoundingClientRect().top + const windowHeight = paddingBottom + ? window.innerHeight - paddingBottom + : window.innerHeight + setHeight(windowHeight - topPosition) + }, + [setHeight, paddingBottom], + ) + + useEffect(() => { + const element = ref.current + if (!element) return + + calculateHeight(element) + + const resizeObserver = new ResizeObserver(() => { + calculateHeight(element) + }) + + resizeObserver.observe(document.body) + return () => { + resizeObserver.disconnect() + } + }, [ref]) + + return height +} + +export default useDynamicHeight diff --git a/apps/web/src/stores/evaluationResultsWithMetadata.ts b/apps/web/src/stores/evaluationResultsWithMetadata.ts index 7381564d6..e12330a7d 100644 --- a/apps/web/src/stores/evaluationResultsWithMetadata.ts +++ b/apps/web/src/stores/evaluationResultsWithMetadata.ts @@ -11,11 +11,15 @@ export default function useEvaluationResultsWithMetadata( documentUuid, commitUuid, projectId, + page, + pageSize, }: { evaluationId: number documentUuid: string commitUuid: string projectId: number + page: string | null + pageSize: string | null }, { fallbackData }: SWRConfiguration = {}, ) { @@ -26,6 +30,8 @@ export default function useEvaluationResultsWithMetadata( documentUuid, commitUuid, projectId, + page, + pageSize, }) if (error) { @@ -38,9 +44,17 @@ export default function useEvaluationResultsWithMetadata( } return data - }, [commitUuid, documentUuid, evaluationId, projectId, toast]) + }, [commitUuid, documentUuid, evaluationId, projectId, toast, page, pageSize]) const { data = EMPTY_ARRAY, mutate } = useSWR( - ['evaluationResults', evaluationId, documentUuid, commitUuid, projectId], + [ + 'evaluationResults', + evaluationId, + documentUuid, + commitUuid, + projectId, + page, + pageSize, + ], fetcher, { fallbackData }, ) diff --git a/apps/websockets/src/server.ts b/apps/websockets/src/server.ts index 8a3ab9f9b..bc0e9b6aa 100644 --- a/apps/websockets/src/server.ts +++ b/apps/websockets/src/server.ts @@ -131,11 +131,13 @@ workers.on('connection', (socket) => { const workspace = buildWorkspaceRoom({ workspaceId }) web.to(workspace).emit('evaluationStatus', data) }) + socket.on('evaluationResultCreated', (args) => { const { workspaceId, data } = args const workspace = buildWorkspaceRoom({ workspaceId }) web.to(workspace).emit('evaluationResultCreated', data) }) + socket.on('documentLogCreated', (args) => { const { workspaceId, data } = args const workspace = buildWorkspaceRoom({ workspaceId }) diff --git a/packages/core/src/events/handlers/createEvaluationResultJob.ts b/packages/core/src/events/handlers/createEvaluationResultJob.ts index c209edf41..133febbf2 100644 --- a/packages/core/src/events/handlers/createEvaluationResultJob.ts +++ b/packages/core/src/events/handlers/createEvaluationResultJob.ts @@ -46,9 +46,7 @@ export const createEvaluationResultJob = async ({ const result = await baseQuery .where(eq(evaluationResultsScope.id, evaluationResult.id)) .limit(1) - - const evaluationResultWithMetadata = result[0]! - + const row = result[0]! websockets.emit('evaluationResultCreated', { workspaceId: evaluation.workspaceId, data: { @@ -56,7 +54,7 @@ export const createEvaluationResultJob = async ({ workspaceId: evaluation.workspaceId, evaluationId: evaluation.id, evaluationResultId: evaluationResult.id, - row: evaluationResultWithMetadata, + row, }, }) } diff --git a/packages/core/src/lib/buildPagination.ts b/packages/core/src/lib/buildPagination.ts deleted file mode 100644 index 6e790e0eb..000000000 --- a/packages/core/src/lib/buildPagination.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { isNumber } from 'lodash-es' - -type ItemType = 'page' | 'ellipsis' -type PageItem = T extends 'page' - ? { type: T; value: number; url: string } - : { type: T; value: '...' } -export type IPagination = { - count: number - totalPages: number - currentPage: number - prevPage?: PageItem<'page'> - pageItems: (PageItem<'page'> | PageItem<'ellipsis'>)[] - nextPage?: PageItem<'page'> -} - -function buildUrl({ - baseUrl, - page, - limit, -}: { - baseUrl: string - page: number - limit: number -}) { - return `${baseUrl}?page=${page}&limit=${limit}` -} - -export function buildPagination({ - baseUrl, - count, - page, - pageSize, - pageItemsCount = 10, -}: { - baseUrl: string - count: number - page: number - pageSize: number - pageItemsCount?: number -}) { - const totalPages = Math.ceil(count / pageSize) - const pagination: IPagination = { - count, - totalPages, - currentPage: page, - prevPage: - page > 1 - ? { - type: 'page', - value: page - 1, - url: buildUrl({ baseUrl, page: page - 1, limit: pageSize }), - } - : undefined, - pageItems: [], - nextPage: - page < totalPages - ? { - type: 'page', - value: page + 1, - url: buildUrl({ baseUrl, page: page + 1, limit: pageSize }), - } - : undefined, - } - - for (let i = page - 1; i >= 1 && pagination.pageItems.length < 2; i--) { - pagination.pageItems.unshift({ - type: 'page', - url: buildUrl({ baseUrl, page: i, limit: pageSize }), - value: i, - }) - } - - const prevItem = pagination.pageItems[0] - const prevPage = isNumber(prevItem?.value) ? prevItem.value : undefined - if (pagination.pageItems.length > 0 && prevPage && prevPage < page - 2) { - pagination.pageItems.push({ type: 'ellipsis', value: '...' }) - } - - pagination.pageItems.push({ - type: 'page', - value: page, - url: buildUrl({ baseUrl, page, limit: pageSize }), - }) - - for ( - let i = page + 1; - i <= totalPages && pagination.pageItems.length < pageItemsCount; - i++ - ) { - pagination.pageItems.push({ - type: 'page', - value: i, - url: buildUrl({ baseUrl, page: i, limit: pageSize }), - }) - } - - const nextItem = pagination.pageItems[pagination.pageItems.length - 1] - const nextPage = isNumber(nextItem?.value) ? nextItem.value : undefined - if (pagination.pageItems.length > 0 && nextPage && nextPage > page + 1) { - pagination.pageItems.push({ type: 'ellipsis', value: '...' }) - } - - return pagination -} - -export function parsePage( - page: string | string[] | number | undefined, -): number { - return typeof page === 'string' ? Number(page) : 1 -} diff --git a/packages/core/src/lib/index.ts b/packages/core/src/lib/index.ts index 79f8940a4..81c3a8f6b 100644 --- a/packages/core/src/lib/index.ts +++ b/packages/core/src/lib/index.ts @@ -4,3 +4,4 @@ export * from './Result' export * from './errors' export * from './commonTypes' export * from './disk' +export * from './pagination/paginate' diff --git a/packages/core/src/lib/pagination/buildPaginatedUrl.ts b/packages/core/src/lib/pagination/buildPaginatedUrl.ts new file mode 100644 index 000000000..376b302d0 --- /dev/null +++ b/packages/core/src/lib/pagination/buildPaginatedUrl.ts @@ -0,0 +1,32 @@ +export type QueryParams = { [key: string]: string | string[] | undefined } + +export function parseSearchParams( + searchParams: QueryParams | string | undefined, +) { + if (typeof searchParams === 'string') { + const searchParamsInstance = new URLSearchParams(searchParams) + return Object.fromEntries(searchParamsInstance) + } + + return searchParams ?? {} +} + +export function buildPaginatedUrl({ + baseUrl, + page, + pageSize, + queryParams, +}: { + baseUrl: string + page: number + pageSize: number + queryParams?: QueryParams | string +}) { + const queryString = new URLSearchParams({ + ...parseSearchParams(queryParams), + page: String(page), + pageSize: String(pageSize), + }).toString() + + return `${baseUrl}?${queryString}` +} diff --git a/packages/core/src/lib/pagination/buildPagination.ts b/packages/core/src/lib/pagination/buildPagination.ts new file mode 100644 index 000000000..98e92ca38 --- /dev/null +++ b/packages/core/src/lib/pagination/buildPagination.ts @@ -0,0 +1,79 @@ +import { + buildPaginatedUrl, + parseSearchParams, + QueryParams, +} from './buildPaginatedUrl' + +export type PaginationArgs = { + page?: number + pageSize?: number +} + +function parsePage(page: string | string[] | number | undefined): number { + return typeof page === 'string' ? Number(page) : 1 +} + +export type IPagination = ReturnType + +export function getPaginationParamsWithDefaults({ + defaultPaginate, + searchParams, +}: { + defaultPaginate?: Exclude + searchParams?: QueryParams | string +}) { + const params = parseSearchParams(searchParams) + return { + page: parsePage(params?.page), + pageSize: params?.pageSize + ? Number(params.pageSize) + : (defaultPaginate?.pageSize ?? 25), + } +} + +export function buildPagination({ + baseUrl, + count, + queryParams, + page, + pageSize, +}: { + baseUrl: string + count: number + queryParams?: QueryParams | string | undefined + page: number + pageSize: number +}) { + const totalPages = Math.ceil(count / pageSize) + return { + page, + baseUrl, + pageSize, + count, + totalPages, + prevPage: + page > 1 + ? { + value: page - 1, + url: buildPaginatedUrl({ + baseUrl, + page: page - 1, + pageSize, + queryParams, + }), + } + : undefined, + nextPage: + page < totalPages + ? { + value: page + 1, + url: buildPaginatedUrl({ + baseUrl, + page: page + 1, + pageSize, + queryParams, + }), + } + : undefined, + } +} diff --git a/packages/core/src/lib/pagination/paginate.ts b/packages/core/src/lib/pagination/paginate.ts new file mode 100644 index 000000000..7850a93dc --- /dev/null +++ b/packages/core/src/lib/pagination/paginate.ts @@ -0,0 +1,70 @@ +import { sql } from 'drizzle-orm' +import type { PgSelect } from 'drizzle-orm/pg-core' + +import { QueryParams } from './buildPaginatedUrl' +import { + buildPagination, + getPaginationParamsWithDefaults, + PaginationArgs, +} from './buildPagination' + +/** + * This use $dynamic() query + * https://orm.drizzle.team/docs/dynamic-query-building + */ +async function paginateQuerySql({ + dynamicQuery, + page = 1, + pageSize = 20, +}: { + dynamicQuery: T +} & PaginationArgs) { + // @ts-ignore + dynamicQuery.config.fields = { + // @ts-ignore + ...dynamicQuery.config.fields, + __count: sql`count(*) over()`, + } + const rows = await dynamicQuery.limit(pageSize).offset((page - 1) * pageSize) + const count = rows[0]?.__count ? Number(rows[0]?.__count) : 0 + return { rows, count } +} + +/** + * TODO: Add tests + */ +export async function paginateQuery({ + dynamicQuery, + pageUrl, + searchParams, + defaultPaginate, +}: { + /** + * IMPORTANT: + * You need to use $dynamic() in your query + * Example: `yourQuery.$dynamic()` + */ + dynamicQuery: T + pageUrl?: { base?: string; queryParams?: Record } + searchParams?: QueryParams | string + defaultPaginate?: Exclude +}) { + const { page, pageSize } = getPaginationParamsWithDefaults({ + defaultPaginate, + searchParams, + }) + const { rows, count } = await paginateQuerySql({ + dynamicQuery, + page, + pageSize, + }) + const pagination = buildPagination({ + baseUrl: pageUrl?.base ?? '', + count, + queryParams: searchParams, + page, + pageSize, + }) + + return { rows, pagination } +} diff --git a/packages/core/src/repositories/commitsRepository/index.test.ts b/packages/core/src/repositories/commitsRepository/index.test.ts index fc662e03e..fac511be8 100644 --- a/packages/core/src/repositories/commitsRepository/index.test.ts +++ b/packages/core/src/repositories/commitsRepository/index.test.ts @@ -49,44 +49,26 @@ describe('Commits by project', () => { }) it('gets all commits', async () => { - const list = await repository - .getCommitsByProject({ project }) - .then((r) => r.unwrap()) + const list = await repository.getCommitsByProjectQuery({ project }) expect(list).toHaveLength(11) }) it('get merged commits', async () => { - const list = await repository - .getCommitsByProject({ - project, - filterByStatus: CommitStatus.Merged, - }) - .then((r) => r.unwrap()) + const list = await repository.getCommitsByProjectQuery({ + project, + filterByStatus: CommitStatus.Merged, + }) expect(list).toHaveLength(3) }) it('get drafts commits', async () => { - const list = await repository - .getCommitsByProject({ - project, - filterByStatus: CommitStatus.Draft, - }) - .then((r) => r.unwrap()) + const list = await repository.getCommitsByProjectQuery({ + project, + filterByStatus: CommitStatus.Draft, + }) expect(list).toHaveLength(8) }) - - it('get first page of drafts commits', async () => { - const list = await repository - .getCommitsByProject({ - project, - filterByStatus: CommitStatus.Draft, - page: 1, - pageSize: 5, - }) - .then((r) => r.unwrap()) - expect(list).toHaveLength(5) - }) }) describe('findAll', () => { diff --git a/packages/core/src/repositories/commitsRepository/index.ts b/packages/core/src/repositories/commitsRepository/index.ts index 3ca01be6e..cdc612090 100644 --- a/packages/core/src/repositories/commitsRepository/index.ts +++ b/packages/core/src/repositories/commitsRepository/index.ts @@ -13,7 +13,7 @@ import { recomputeChanges, RecomputedChanges, } from '../../services/documents/recomputeChanges' -import Repository, { PaginationArgs } from '../repository' +import Repository from '../repository' import { buildCommitsScope, columnSelection } from './utils/buildCommitsScope' import { getHeadCommitForProject } from './utils/getHeadCommit' @@ -129,17 +129,18 @@ export class CommitsRepository extends Repository< return Result.ok(result[0]!) } - async getCommitsByProject({ + getCommitsByProjectQuery({ project, - page = 1, filterByStatus = CommitStatus.All, - pageSize = 20, - }: { project: Project; filterByStatus?: CommitStatus } & PaginationArgs) { + }: { + project: Project + filterByStatus?: CommitStatus + }) { const filter = filterByStatusQuery({ scope: this.scope, status: filterByStatus, }) - const query = this.db + return this.db .select({ id: this.scope.id, uuid: this.scope.uuid, @@ -155,14 +156,6 @@ export class CommitsRepository extends Repository< .from(this.scope) .where(and(eq(this.scope.projectId, project.id), filter)) .orderBy(desc(this.scope.createdAt)) - - const [rows] = await Repository.paginateQuery({ - query: query.$dynamic(), - page, - pageSize, - }) - - return Result.ok(rows) } async getChanges(id: number, tx = database) { diff --git a/packages/core/src/repositories/documentVersionsRepository/index.test.ts b/packages/core/src/repositories/documentVersionsRepository/index.test.ts index aaac1022c..3b347ac75 100644 --- a/packages/core/src/repositories/documentVersionsRepository/index.test.ts +++ b/packages/core/src/repositories/documentVersionsRepository/index.test.ts @@ -47,7 +47,8 @@ describe('DocumentVersionsRepository', () => { const documents = result.unwrap() expect(documents).toHaveLength(2) - expect(documents.map((d) => d.path)).toEqual(['foo', 'bar']) + const paths = documents.map((d) => d.path).sort() + expect(paths).toEqual(['bar', 'foo']) expect(documents.every((d) => d.commitId === commit1Id)).toBe(true) }) diff --git a/packages/core/src/repositories/repository.ts b/packages/core/src/repositories/repository.ts index beaec22f0..cc821984a 100644 --- a/packages/core/src/repositories/repository.ts +++ b/packages/core/src/repositories/repository.ts @@ -1,17 +1,14 @@ -import { ColumnsSelection, eq, sql } from 'drizzle-orm' -import { PgSelect, SubqueryWithSelection } from 'drizzle-orm/pg-core' +import { ColumnsSelection, eq } from 'drizzle-orm' +import { SubqueryWithSelection } from 'drizzle-orm/pg-core' import { database } from '../client' import { NotFoundError, Result } from '../lib' -export type PaginationArgs = { page?: number; pageSize?: number } export type QueryOptions = { limit?: number offset?: number } -type PaginatatedResult = [rows: Awaited, count: number] - export default abstract class Repository< U extends ColumnsSelection, T extends Record, @@ -24,28 +21,6 @@ export default abstract class Repository< this.db = db } - /** - * This use $dynamic() query - * https://orm.drizzle.team/docs/dynamic-query-building - */ - static async paginateQuery({ - query, - page = 1, - pageSize = 20, - }: { - query: T - } & PaginationArgs): Promise> { - // @ts-ignore - query.config.fields = { - // @ts-ignore - ...query.config.fields, - __count: sql`count(*) over()`, - } - const rows = await query.limit(pageSize).offset((page - 1) * pageSize) - const count = rows[0]?.__count ? Number(rows[0]?.__count) : 0 - return [rows, count] - } - abstract get scope(): SubqueryWithSelection async findAll(opts: QueryOptions = {}) { diff --git a/packages/core/src/services/documentLogs/computeDocumentLogsWithMetadata.test.ts b/packages/core/src/services/documentLogs/computeDocumentLogsWithMetadata.test.ts index 5f67c623b..ff54b1652 100644 --- a/packages/core/src/services/documentLogs/computeDocumentLogsWithMetadata.test.ts +++ b/packages/core/src/services/documentLogs/computeDocumentLogsWithMetadata.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest' import { mergeCommit } from '../../services/commits' import { updateDocument } from '../../services/documents' import * as factories from '../../tests/factories' -import { computeDocumentLogsWithMetadata } from './computeDocumentLogsWithMetadata' +import { computeDocumentLogsWithMetadataQuery } from './computeDocumentLogsWithMetadata' describe('getDocumentLogsWithMetadata', () => { it('return all logs from merged commits', async () => { @@ -39,11 +39,10 @@ describe('getDocumentLogsWithMetadata', () => { commit: commit2, }) - const [result] = await computeDocumentLogsWithMetadata({ + const result = await computeDocumentLogsWithMetadataQuery({ workspaceId: project.workspaceId, documentUuid: doc.documentUuid, draft: commit2, - pagination: { page: 1, pageSize: 100 }, }) expect(result.find((l) => l.uuid === log1.uuid)).toBeDefined() @@ -98,11 +97,10 @@ describe('getDocumentLogsWithMetadata', () => { commit: draft, }) - const [result] = await computeDocumentLogsWithMetadata({ + const result = await computeDocumentLogsWithMetadataQuery({ workspaceId: project.workspaceId, documentUuid: doc.documentUuid, draft, - pagination: { page: 1, pageSize: 100 }, }) expect(result.find((l) => l.uuid === log1.uuid)).toBeDefined() @@ -156,11 +154,10 @@ describe('getDocumentLogsWithMetadata', () => { commit: draft2, }) - const [result] = await computeDocumentLogsWithMetadata({ + const result = await computeDocumentLogsWithMetadataQuery({ workspaceId: project.workspaceId, documentUuid: doc.documentUuid, draft: draft1, - pagination: { page: 1, pageSize: 100 }, }) expect(result.find((l) => l.uuid === log1.uuid)).toBeDefined() @@ -186,11 +183,10 @@ describe('getDocumentLogsWithMetadata', () => { commit, }) - const [result] = await computeDocumentLogsWithMetadata({ + const result = await computeDocumentLogsWithMetadataQuery({ workspaceId: project.workspaceId, documentUuid: doc.documentUuid, draft: commit, - pagination: { page: 1, pageSize: 100 }, }) expect(result.find((l) => l.uuid === log.uuid)).toBeDefined() diff --git a/packages/core/src/services/documentLogs/computeDocumentLogsWithMetadata.ts b/packages/core/src/services/documentLogs/computeDocumentLogsWithMetadata.ts index 84c3aa0c0..271b76b28 100644 --- a/packages/core/src/services/documentLogs/computeDocumentLogsWithMetadata.ts +++ b/packages/core/src/services/documentLogs/computeDocumentLogsWithMetadata.ts @@ -2,30 +2,25 @@ import { and, desc, eq } from 'drizzle-orm' import { Commit } from '../../browser' import { database } from '../../client' -import Repository, { PaginationArgs } from '../../repositories/repository' import { createDocumentLogQuery, getCommitFilter, } from './_createDocumentLogQuery' -export async function computeDocumentLogsWithMetadata( +export function computeDocumentLogsWithMetadataQuery( { workspaceId, documentUuid, draft, - pagination, }: { workspaceId: number documentUuid: string draft?: Commit - pagination: PaginationArgs }, db = database, ) { const { scope, baseQuery } = createDocumentLogQuery(workspaceId, db) - const query = baseQuery + return baseQuery .where(and(eq(scope.documentUuid, documentUuid), getCommitFilter(draft))) .orderBy(desc(scope.createdAt)) - - return Repository.paginateQuery({ query: query.$dynamic(), ...pagination }) } diff --git a/packages/core/src/services/evaluationResults/computeEvaluationResultsWithMetadata.ts b/packages/core/src/services/evaluationResults/computeEvaluationResultsWithMetadata.ts index f568c669e..005c7d0ac 100644 --- a/packages/core/src/services/evaluationResults/computeEvaluationResultsWithMetadata.ts +++ b/packages/core/src/services/evaluationResults/computeEvaluationResultsWithMetadata.ts @@ -2,32 +2,28 @@ import { and, desc, eq } from 'drizzle-orm' import { Commit, Evaluation } from '../../browser' import { database } from '../../client' -import { Result, TypedResult } from '../../lib' -import { EvaluationResultWithMetadata } from '../../repositories/evaluationResultsRepository' import { createEvaluationResultQuery, getCommitFilter, } from './_createEvaluationResultQuery' -export async function computeEvaluationResultsWithMetadata( +export function computeEvaluationResultsWithMetadataQuery( { workspaceId, evaluation, documentUuid, draft, - limit, }: { workspaceId: number evaluation: Evaluation documentUuid: string draft?: Commit - limit?: number }, db = database, -): Promise> { +) { const { evaluationResultsScope, documentLogsScope, baseQuery } = createEvaluationResultQuery(workspaceId, db) - const query = baseQuery + return baseQuery .where( and( eq(evaluationResultsScope.evaluationId, evaluation.id), @@ -36,8 +32,4 @@ export async function computeEvaluationResultsWithMetadata( ), ) .orderBy(desc(evaluationResultsScope.createdAt)) - - const result = await (limit ? query.limit(limit) : query) - - return Result.ok(result) } diff --git a/packages/core/src/websockets/constants.ts b/packages/core/src/websockets/constants.ts index d8dd71cbc..51bed14b9 100644 --- a/packages/core/src/websockets/constants.ts +++ b/packages/core/src/websockets/constants.ts @@ -38,7 +38,7 @@ type EvaluationStatusArgs = { enqueued: number } -type evaluationResultCreatedArgs = { +type EvaluationResultCreatedArgs = { workspaceId: number evaluationId: number documentUuid: string @@ -55,7 +55,7 @@ type DocumentLogCreatedArgs = { export type WebServerToClientEvents = { evaluationStatus: (args: EvaluationStatusArgs) => void - evaluationResultCreated: (args: evaluationResultCreatedArgs) => void + evaluationResultCreated: (args: EvaluationResultCreatedArgs) => void joinWorkspace: (args: { workspaceId: number; userId: string }) => void documentLogCreated: (args: DocumentLogCreatedArgs) => void } @@ -71,7 +71,7 @@ export type WorkersClientToServerEvents = { }) => void evaluationResultCreated: (args: { workspaceId: number - data: evaluationResultCreatedArgs + data: EvaluationResultCreatedArgs }) => void documentLogCreated: (args: { workspaceId: number diff --git a/packages/web-ui/src/ds/atoms/Input/index.tsx b/packages/web-ui/src/ds/atoms/Input/index.tsx index c2b0901f5..47299812d 100644 --- a/packages/web-ui/src/ds/atoms/Input/index.tsx +++ b/packages/web-ui/src/ds/atoms/Input/index.tsx @@ -31,9 +31,21 @@ const inputVariants = cva(cn(INPUT_BASE_CLASSES), { export type InputProps = Omit, 'size'> & VariantProps & - Omit + Omit & { + hideNativeAppearance?: boolean + } const Input = forwardRef(function Input( - { className, label, errors, errorStyle, description, type, size, ...props }, + { + className, + label, + errors, + errorStyle, + description, + type, + size, + hideNativeAppearance = false, + ...props + }, ref, ) { const inputComp = ( @@ -43,6 +55,7 @@ const Input = forwardRef(function Input( className={cn(inputVariants({ size }), className, { 'border-red-500 focus-visible:ring-red-500': errors, hidden: !!props.hidden, + 'appearance-none': hideNativeAppearance, })} {...props} /> diff --git a/packages/web-ui/src/ds/atoms/SplitPane/index.tsx b/packages/web-ui/src/ds/atoms/SplitPane/index.tsx index f9e259a13..e00416973 100644 --- a/packages/web-ui/src/ds/atoms/SplitPane/index.tsx +++ b/packages/web-ui/src/ds/atoms/SplitPane/index.tsx @@ -14,7 +14,7 @@ import { ResizableBox, ResizeCallbackData, ResizeHandle } from 'react-resizable' function Pane({ children }: { children: ReactNode }) { return (
-
{children}
+
{children}
) } diff --git a/packages/web-ui/src/ds/atoms/Table/index.tsx b/packages/web-ui/src/ds/atoms/Table/index.tsx index 62b53a7db..c6316ccad 100644 --- a/packages/web-ui/src/ds/atoms/Table/index.tsx +++ b/packages/web-ui/src/ds/atoms/Table/index.tsx @@ -9,14 +9,17 @@ import { cn } from '../../../lib/utils' import Text from '../Text' type TableProps = HTMLAttributes & { - maxHeight?: number + maxHeight?: number | string overflow?: 'overflow-auto' | 'overflow-hidden' } const Table = forwardRef( ({ className, maxHeight, overflow = 'overflow-auto', ...props }, ref) => (
->(({ className, ...props }, ref) => ( - tr]:last:border-b-0', - className, - )} - {...props} - /> -)) +type TableFooterProps = HTMLAttributes & { + sticky?: boolean +} +const TableFooter = forwardRef( + ({ className, sticky = false, ...props }, ref) => ( + tr]:last:border-b-0', + className, + { + 'sticky bottom-0': sticky, + }, + )} + {...props} + /> + ), +) TableFooter.displayName = 'TableFooter' const TableRow = forwardRef< diff --git a/packages/web-ui/src/ds/molecules/Chat/MessageList/index.tsx b/packages/web-ui/src/ds/molecules/Chat/MessageList/index.tsx index c44699c6f..3a9136f18 100644 --- a/packages/web-ui/src/ds/molecules/Chat/MessageList/index.tsx +++ b/packages/web-ui/src/ds/molecules/Chat/MessageList/index.tsx @@ -1,10 +1,9 @@ 'use client' -import { Fragment, useState } from 'react' +import { Fragment } from 'react' import { Message as ConversationMessage } from '@latitude-data/compiler' -import { Button } from '../../../atoms' import { Message, MessageProps } from '../Message' export function MessageList({ @@ -12,46 +11,17 @@ export function MessageList({ variant, messageLayout, separator = false, - collapsable = false, size, }: { messages: ConversationMessage[] variant?: MessageProps['variant'] messageLayout?: MessageProps['layout'] - collapsable?: boolean size?: MessageProps['size'] separator?: boolean }) { - const [isCollapsed, setIsCollapsed] = useState( - collapsable && messages.length > 1, - ) - - const visibleMessages = isCollapsed ? messages.slice(-1) : messages - const hiddenMessagesCount = messages.length - visibleMessages.length - return (
- {isCollapsed && messages.length > 1 && ( -
-
- -
-
-
- -
-
- )} - {visibleMessages.map((message, index) => ( + {messages.map((message, index) => ( {separator && index > 0 && (
@@ -65,13 +35,6 @@ export function MessageList({ /> ))} - {!isCollapsed && collapsable && messages.length > 1 && ( -
- -
- )}
) } diff --git a/packages/web-ui/src/ds/molecules/Pagination/index.tsx b/packages/web-ui/src/ds/molecules/Pagination/index.tsx deleted file mode 100644 index de07d822e..000000000 --- a/packages/web-ui/src/ds/molecules/Pagination/index.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { ComponentProps, forwardRef } from 'react' -import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react' - -import { cn } from '../../../lib/utils' -import { ButtonProps, buttonVariants } from '../../atoms/Button' - -const Pagination = ({ className, ...props }: ComponentProps<'nav'>) => ( -