Skip to content

Commit

Permalink
quickfix: allow to import logs from any document in the project (#286)
Browse files Browse the repository at this point in the history
Also fixes the issue where importing logs sometimes did not work
  • Loading branch information
geclos authored Sep 26, 2024
1 parent ae4e463 commit 8fad1a4
Show file tree
Hide file tree
Showing 12 changed files with 326 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .tmuxinator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
name: latitude-llm
windows:
- web: cd apps/web
- apps: pnpm dev --filter='./apps/*' --filter='./packages/**'
- apps: pnpm dev --filter='./apps/*'
- docker: docker compose up
- studio: cd packages/core && pnpm db:studio
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { Badge, Icon, Input, Text } from '@latitude-data/web-ui'
import { convertParams } from '$/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/_components/DocumentEditor/Editor/Playground'
import { ROUTES } from '$/services/routes'
import useProviderLogs from '$/stores/providerLogs'
import { useProviderLog } from '$/stores/providerLogs'
import Link from 'next/link'
import { useSearchParams } from 'next/navigation'

Expand All @@ -34,11 +34,9 @@ export default function Playground({
)
const parameters = useMemo(() => convertParams(inputs), [inputs])
const searchParams = useSearchParams()
const providerLogUuid = searchParams.get('providerLogUuid')
const { data } = useProviderLogs()
const providerLog = useMemo(
() => data?.find((log) => log.uuid === providerLogUuid),
[data, providerLogUuid],
const providerLogId = searchParams.get('providerLogId')
const { data: providerLog } = useProviderLog(
providerLogId ? Number(providerLogId) : undefined,
)

const setInput = useCallback((param: string, value: string) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useState } from 'react'
import { capitalize } from 'lodash-es'

import { MessageContent, TextContent } from '@latitude-data/compiler'
import { HEAD_COMMIT, ProviderLogDto } from '@latitude-data/core/browser'
import { ProviderLogDto } from '@latitude-data/core/browser'
import {
Badge,
Button,
Expand All @@ -23,7 +23,7 @@ import {
import { useNavigate } from '$/hooks/useNavigate'
import { relativeTime } from '$/lib/relativeTime'
import { ROUTES } from '$/services/routes'
import useDocumentVersions from '$/stores/documentVersions'
import useDocumentsForImport from '$/stores/documentsForImport'
import useProjects from '$/stores/projects'
import useProviderLogs from '$/stores/providerLogs'

Expand All @@ -35,7 +35,7 @@ export default function ImportLogs({
const navigate = useNavigate()
const [projectId, setProjectId] = useState<number | undefined>()
const [documentUuid, setDocumentUuid] = useState<string | undefined>()
const [providerLogUuid, setProviderLogUuid] = useState<string | undefined>()
const [providerLogId, setProviderLogId] = useState<number | undefined>()

const onChangeProjectId = (value: string) => {
setProjectId(Number(value))
Expand All @@ -45,10 +45,7 @@ export default function ImportLogs({
setDocumentUuid(value)
}
const { data: projects } = useProjects()
const { data: documents } = useDocumentVersions({
commitUuid: HEAD_COMMIT,
projectId,
})
const { data: documents } = useDocumentsForImport({ projectId })

return (
<Modal
Expand All @@ -66,10 +63,10 @@ export default function ImportLogs({
<CloseTrigger />
<Button
fancy
disabled={!providerLogUuid}
disabled={!providerLogId}
onClick={() => {
navigate.push(
`${ROUTES.evaluations.detail({ uuid: evaluationUuid }).editor.root}?providerLogUuid=${providerLogUuid}`,
`${ROUTES.evaluations.detail({ uuid: evaluationUuid }).editor.root}?providerLogId=${providerLogId}`,
)
}}
>
Expand Down Expand Up @@ -102,13 +99,13 @@ export default function ImportLogs({
{documentUuid && (
<ProviderLogsTable
documentUuid={documentUuid}
setProviderLogUuid={setProviderLogUuid}
setProviderLogId={setProviderLogId}
/>
)}
{documentUuid && (
<ProviderLogMessages
documentUuid={documentUuid}
providerLogUuid={providerLogUuid}
providerLogId={providerLogId}
/>
)}
</div>
Expand All @@ -118,10 +115,10 @@ export default function ImportLogs({

const ProviderLogsTable = ({
documentUuid,
setProviderLogUuid: setProviderLogId,
setProviderLogId: setProviderLogId,
}: {
documentUuid: string
setProviderLogUuid: (id: string) => void
setProviderLogId: (id: number) => void
}) => {
const { data = [] } = useProviderLogs({ documentUuid })
if (!data.length) {
Expand Down Expand Up @@ -158,7 +155,7 @@ const ProviderLogsTable = ({
{data?.map((log) => (
<TableRow
key={log.id}
onClick={() => setProviderLogId(log.uuid)}
onClick={() => setProviderLogId(log.id)}
className='cursor-pointer'
role='button'
>
Expand Down Expand Up @@ -187,14 +184,14 @@ const ProviderLogsTable = ({

const ProviderLogMessages = ({
documentUuid,
providerLogUuid,
providerLogId,
}: {
documentUuid?: string
providerLogUuid?: string
providerLogId?: number
}) => {
const { data } = useProviderLogs({ documentUuid })
const providerLog = data?.find(
(log) => log.uuid === providerLogUuid,
(log) => log.id === providerLogId,
) as ProviderLogDto
if (!providerLog) {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {
DocumentVersion,
Providers,
User,
Workspace,
} from '@latitude-data/core/browser'
import { createProject, helpers } from '@latitude-data/core/factories'
import { NextRequest } from 'next/server'
import { beforeEach, describe, expect, it, vi } from 'vitest'

import { GET } from './route'

const mocks = vi.hoisted(() => {
return {
getSession: vi.fn(),
}
})
vi.mock('$/services/auth/getSession', () => ({
getSession: mocks.getSession,
}))

describe('GET handler for documents/[projectId]/for-import', () => {
let mockRequest: NextRequest
let mockParams: { projectId: string }
let mockWorkspace: Workspace
let mockDocuments: DocumentVersion[]
let mockUser: User

beforeEach(async () => {
mockRequest = new NextRequest('http://localhost:3000')
const { workspace, documents, project, user } = await createProject({
providers: [{ type: Providers.OpenAI, name: 'openai' }],
documents: {
foo: {
content: helpers.createPrompt({ provider: 'openai', content: 'foo' }),
},
},
})
mockParams = { projectId: String(project.id) }

mockUser = user
mockWorkspace = workspace
mockDocuments = documents
})

describe('unauthorized', () => {
it('should return 401 if user is not authenticated', async () => {
const response = await GET(mockRequest, {
params: mockParams,
workspace: mockWorkspace,
} as any)

expect(response.status).toBe(401)
expect(await response.json()).toEqual({
message: 'Unauthorized',
})
})
})

describe('authorized', () => {
beforeEach(async () => {
mocks.getSession.mockReturnValue({ user: mockUser })
})

it('should return 400 if projectId is missing', async () => {
const response = await GET(mockRequest, {
params: {},
workspace: mockWorkspace,
} as any)

expect(response.status).toBe(400)
expect(await response.json()).toEqual({
message: 'Project ID is required',
details: {},
})
})

it('should return documents for import when valid params are provided', async () => {
const response = await GET(mockRequest, {
// @ts-expect-error
params: mockParams,
workspace: mockWorkspace,
})

expect(response.status).toBe(200)
expect(await response.json()).toEqual([
{
documentUuid: mockDocuments[0]!.documentUuid,
path: mockDocuments[0]!.path,
},
])
})
})
})
34 changes: 34 additions & 0 deletions apps/web/src/app/api/documents/[projectId]/for-import/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Workspace } from '@latitude-data/core/browser'
import { BadRequestError } from '@latitude-data/core/lib/errors'
import { DocumentVersionsRepository } from '@latitude-data/core/repositories'
import { authHandler } from '$/middlewares/authHandler'
import { errorHandler } from '$/middlewares/errorHandler'
import { NextRequest, NextResponse } from 'next/server'

export const GET = errorHandler(
authHandler(
async (
_: NextRequest,
{
params,
workspace,
}: {
params: { projectId: string }
workspace: Workspace
},
) => {
const { projectId } = params

if (!projectId) {
throw new BadRequestError('Project ID is required')
}

const docsScope = new DocumentVersionsRepository(workspace.id)
const documents = await docsScope
.getDocumentsForImport(Number(projectId))
.then((r) => r.unwrap())

return NextResponse.json(documents, { status: 200 })
},
),
)
5 changes: 1 addition & 4 deletions apps/web/src/middlewares/authHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ export function authHandler(handler: any) {
user = uzer
workspace = workzpace
} catch (error) {
return NextResponse.json(
{ message: (error as Error).message },
{ status: 401 },
)
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 })
}

return await handler(req, { ...rest, user, workspace })
Expand Down
47 changes: 47 additions & 0 deletions apps/web/src/stores/documentsForImport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useCallback } from 'react'

import { useToast } from '@latitude-data/web-ui'
import useSWR, { SWRConfiguration } from 'swr'

interface DocumentForImport {
documentUuid: string
path: string
}

export default function useDocumentsForImport(
{ projectId }: { projectId?: number },
opts?: SWRConfiguration,
) {
const { toast } = useToast()
const fetcher = useCallback(async () => {
if (!projectId) return []

const response = await fetch(`/api/documents/${projectId}/for-import`, {
credentials: 'include',
})

if (!response.ok) {
const error = await response.json()
toast({
title: 'Error fetching documents for import',
description: error.message || 'An error occurred',
variant: 'destructive',
})
return []
}

return response.json()
}, [projectId, toast])

const {
data = [],
mutate,
...rest
} = useSWR<DocumentForImport[]>(
['api/documents/for-import', projectId],
fetcher,
opts,
)

return { data, mutate, ...rest }
}
Loading

0 comments on commit 8fad1a4

Please sign in to comment.