-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feature: live evaluations * chore: fix broken test
- Loading branch information
Showing
30 changed files
with
3,038 additions
and
55 deletions.
There are no files selected for viewing
121 changes: 121 additions & 0 deletions
121
apps/web/src/actions/connectedEvaluations/update.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { Providers } from '@latitude-data/core/browser' | ||
import * as factories from '@latitude-data/core/factories' | ||
import { beforeEach, describe, expect, it, vi } from 'vitest' | ||
|
||
import { updateConnectedEvaluationAction } from './update' | ||
|
||
const mocks = vi.hoisted(() => { | ||
return { | ||
getSession: vi.fn(), | ||
} | ||
}) | ||
|
||
vi.mock('$/services/auth/getSession', () => ({ | ||
getSession: mocks.getSession, | ||
})) | ||
|
||
describe('updateConnectedEvaluationAction', () => { | ||
let workspace: any | ||
let project: any | ||
let user: any | ||
let connectedEvaluation: any | ||
|
||
beforeEach(async () => { | ||
const prompt = factories.helpers.createPrompt({ | ||
provider: 'Latitude', | ||
model: 'gpt-4o', | ||
}) | ||
const setup = await factories.createProject({ | ||
providers: [{ type: Providers.OpenAI, name: 'Latitude' }], | ||
name: 'Default Project', | ||
documents: { | ||
foo: { | ||
content: prompt, | ||
}, | ||
}, | ||
}) | ||
workspace = setup.workspace | ||
project = setup.project | ||
user = setup.user | ||
|
||
// Create a connected evaluation using a factory | ||
const evaluation = await factories.createLlmAsJudgeEvaluation({ workspace }) | ||
const { commit } = await factories.createDraft({ project, user }) | ||
const { documentLog } = await factories.createDocumentLog({ | ||
document: setup.documents[0]!, | ||
commit, | ||
}) | ||
|
||
connectedEvaluation = await factories.createConnectedEvaluation({ | ||
workspace, | ||
evaluationUuid: evaluation.uuid, | ||
documentUuid: documentLog.documentUuid, | ||
}) | ||
}) | ||
|
||
describe('unauthorized', () => { | ||
it('errors when the user is not authenticated', async () => { | ||
mocks.getSession.mockReturnValue(null) | ||
|
||
const [_, error] = await updateConnectedEvaluationAction({ | ||
id: connectedEvaluation.id, | ||
data: { live: true }, | ||
}) | ||
|
||
expect(error!.name).toEqual('UnauthorizedError') | ||
}) | ||
}) | ||
|
||
describe('authorized', () => { | ||
beforeEach(() => { | ||
mocks.getSession.mockReturnValue({ | ||
user, | ||
workspace: { id: workspace.id, name: workspace.name }, | ||
}) | ||
}) | ||
|
||
it('successfully updates a connected evaluation', async () => { | ||
const [data, error] = await updateConnectedEvaluationAction({ | ||
id: connectedEvaluation.id, | ||
data: { live: true }, | ||
}) | ||
|
||
expect(error).toBeNull() | ||
expect(data).toBeDefined() | ||
expect(data!.id).toEqual(connectedEvaluation.id) | ||
expect(data!.live).toEqual(true) | ||
}) | ||
|
||
it('returns an error when the connected evaluation is not found', async () => { | ||
const [_, error] = await updateConnectedEvaluationAction({ | ||
id: 9999, // Non-existent ID | ||
data: { live: true }, | ||
}) | ||
|
||
expect(error).toBeDefined() | ||
expect(error!.name).toEqual('NotFoundError') | ||
}) | ||
|
||
it('does not update fields that are not provided', async () => { | ||
const [data, _] = await updateConnectedEvaluationAction({ | ||
id: connectedEvaluation.id, | ||
data: { live: connectedEvaluation.live }, // Provide the required 'live' field | ||
}) | ||
|
||
expect(data).toBeDefined() | ||
expect(data!.id).toEqual(connectedEvaluation.id) | ||
expect(data!.live).toEqual(connectedEvaluation.live) // Should remain unchanged | ||
}) | ||
|
||
it('handles invalid input data', async () => { | ||
const [_, error] = await updateConnectedEvaluationAction({ | ||
id: connectedEvaluation.id, | ||
// @ts-expect-error - Testing invalid input | ||
data: { live: 'not a boolean' }, | ||
}) | ||
|
||
expect(error).toBeDefined() | ||
expect(error!.name).toEqual('ZodError') | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
'use server' | ||
|
||
import { ConnectedEvaluationsRepository } from '@latitude-data/core/repositories' | ||
import { updateConnectedEvaluation } from '@latitude-data/core/services/connectedEvaluations/update' | ||
import { z } from 'zod' | ||
|
||
import { authProcedure } from '../procedures' | ||
|
||
export const updateConnectedEvaluationAction = authProcedure | ||
.createServerAction() | ||
.input( | ||
z.object({ | ||
id: z.number(), | ||
data: z.object({ | ||
live: z.boolean(), | ||
}), | ||
}), | ||
) | ||
.handler(async ({ input, ctx }) => { | ||
const connectedEvaluationsScope = new ConnectedEvaluationsRepository( | ||
ctx.workspace.id, | ||
) | ||
const connectedEvaluation = await connectedEvaluationsScope | ||
.find(input.id) | ||
.then((r) => r.unwrap()) | ||
|
||
const result = await updateConnectedEvaluation({ | ||
connectedEvaluation, | ||
data: input.data, | ||
}) | ||
|
||
return result.unwrap() | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
...cumentUuid]/evaluations/[evaluationId]/_components/Actions/LiveEvaluationToggle/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { useCallback } from 'react' | ||
|
||
import { EvaluationDto } from '@latitude-data/core/browser' | ||
import { | ||
Label, | ||
SwitchToogle, | ||
useCurrentCommit, | ||
useCurrentProject, | ||
useToast, | ||
} from '@latitude-data/web-ui' | ||
import useConnectedEvaluations from '$/stores/connectedEvaluations' | ||
|
||
export default function LiveEvaluationToggle({ | ||
documentUuid, | ||
evaluation, | ||
}: { | ||
documentUuid: string | ||
evaluation: EvaluationDto | ||
}) { | ||
const { toast } = useToast() | ||
const { commit } = useCurrentCommit() | ||
const { project } = useCurrentProject() | ||
const { data, update, isUpdating } = useConnectedEvaluations({ | ||
documentUuid, | ||
projectId: project.id, | ||
commitUuid: commit.uuid, | ||
}) | ||
const connectedEvaluation = data.find( | ||
(ev) => ev.evaluationId === evaluation.id, | ||
) | ||
const toggleLive = useCallback(async () => { | ||
if (!connectedEvaluation) return | ||
|
||
const live = !connectedEvaluation.live | ||
const [_, error] = await update({ | ||
id: connectedEvaluation.id, | ||
data: { live }, | ||
}) | ||
if (error) return | ||
|
||
toast({ | ||
title: 'Successfully updated evaluation', | ||
description: live ? 'Evaluation is now live' : 'Evaluation is now paused', | ||
}) | ||
}, [connectedEvaluation, update]) | ||
if (!connectedEvaluation) return null | ||
|
||
return ( | ||
<div className='flex flex-row gap-2 items-center'> | ||
<Label>Evaluate live logs</Label> | ||
<SwitchToogle | ||
disabled={isUpdating} | ||
checked={connectedEvaluation.live} | ||
onCheckedChange={toggleLive} | ||
/> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
...b/src/app/api/documents/[projectId]/[commitUuid]/[documentUuid]/evaluations/route.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import { Providers } from '@latitude-data/core/browser' | ||
import * as factories from '@latitude-data/core/factories' | ||
import { Result } from '@latitude-data/core/lib/Result' | ||
import { ConnectedEvaluationsRepository } from '@latitude-data/core/repositories' | ||
import { NextRequest, NextResponse } from 'next/server' | ||
import { beforeEach, describe, expect, it, vi } from 'vitest' | ||
|
||
import { GET } from './route' | ||
|
||
vi.mock('@latitude-data/core/repositories', async (importOriginal) => { | ||
const original = await importOriginal() | ||
return { | ||
// @ts-expect-error | ||
...original, | ||
ConnectedEvaluationsRepository: vi.fn(), | ||
} | ||
}) | ||
vi.mock('$/middlewares/authHandler', () => ({ | ||
authHandler: (fn: Function) => fn, | ||
})) | ||
|
||
describe('GET /api/documents/[projectId]/[commitUuid]/[documentUuid]/evaluations', async () => { | ||
let workspace: any | ||
let documents: any | ||
let mockEvaluations: any | ||
|
||
beforeEach(async () => { | ||
vi.resetAllMocks() | ||
|
||
const setup = await factories.createProject({ | ||
providers: [{ type: Providers.OpenAI, name: 'openai' }], | ||
documents: { | ||
doc1: factories.helpers.createPrompt({ | ||
provider: 'openai', | ||
content: 'foo', | ||
}), | ||
doc2: factories.helpers.createPrompt({ | ||
provider: 'openai', | ||
content: 'bar', | ||
}), | ||
}, | ||
}) | ||
|
||
workspace = setup.workspace | ||
documents = setup.documents | ||
|
||
mockEvaluations = [ | ||
await factories.createConnectedEvaluation({ | ||
workspace, | ||
documentUuid: documents[0]!.documentUuid, | ||
}), | ||
await factories.createConnectedEvaluation({ | ||
workspace, | ||
documentUuid: documents[1]!.documentUuid, | ||
}), | ||
] | ||
}) | ||
|
||
it('should return evaluations for the given document UUID', async () => { | ||
const mockFilterByDocumentUuid = vi | ||
.fn() | ||
.mockResolvedValue(Result.ok(mockEvaluations)) | ||
vi.mocked(ConnectedEvaluationsRepository).mockImplementation( | ||
() => | ||
({ | ||
filterByDocumentUuid: mockFilterByDocumentUuid, | ||
}) as any, | ||
) | ||
|
||
const request = new NextRequest( | ||
'http://localhost/api/documents/test-project/test-commit/test-document-uuid/evaluations', | ||
) | ||
const response = await GET(request, { | ||
// @ts-expect-error | ||
params: { documentUuid: documents[0]!.documentUuid }, | ||
workspace, | ||
}) | ||
|
||
expect(ConnectedEvaluationsRepository).toHaveBeenCalledWith(workspace.id) | ||
expect(mockFilterByDocumentUuid).toHaveBeenCalledWith( | ||
documents[0]!.documentUuid, | ||
) | ||
expect(response).toBeInstanceOf(NextResponse) | ||
// @ts-expect-error | ||
expect((await response.json()).map((ev) => ev.id)).toEqual( | ||
// @ts-expect-error | ||
mockEvaluations.map((ev) => ev.id), | ||
) | ||
expect(response.status).toBe(200) | ||
}) | ||
|
||
it('should handle errors when fetching evaluations fails', async () => { | ||
const mockError = new Error('Failed to fetch evaluations') | ||
const mockFilterByDocumentUuid = vi | ||
.fn() | ||
.mockResolvedValue(Result.error(mockError)) | ||
vi.mocked(ConnectedEvaluationsRepository).mockImplementation( | ||
() => | ||
({ | ||
filterByDocumentUuid: mockFilterByDocumentUuid, | ||
}) as any, | ||
) | ||
|
||
const request = new NextRequest( | ||
'http://localhost/api/documents/test-project/test-commit/test-document-uuid/evaluations', | ||
) | ||
const response = await GET(request, { | ||
// @ts-expect-error | ||
params: { documentUuid: documents[0]!.documentUuid }, | ||
workspace, | ||
}) | ||
|
||
expect(ConnectedEvaluationsRepository).toHaveBeenCalledWith(workspace.id) | ||
expect(mockFilterByDocumentUuid).toHaveBeenCalledWith( | ||
documents[0]!.documentUuid, | ||
) | ||
expect(response).toBeInstanceOf(NextResponse) | ||
expect(response.status).toBe(500) | ||
expect(await response.json()).toEqual({ | ||
message: 'Internal Server Error', | ||
}) | ||
}) | ||
}) |
30 changes: 30 additions & 0 deletions
30
apps/web/src/app/api/documents/[projectId]/[commitUuid]/[documentUuid]/evaluations/route.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Workspace } from '@latitude-data/core/browser' | ||
import { ConnectedEvaluationsRepository } 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: { | ||
documentUuid: string | ||
} | ||
workspace: Workspace | ||
}, | ||
) => { | ||
const { documentUuid } = params | ||
const scope = new ConnectedEvaluationsRepository(workspace.id) | ||
const evaluations = await scope | ||
.filterByDocumentUuid(documentUuid) | ||
.then((r) => r.unwrap()) | ||
|
||
return NextResponse.json(evaluations, { status: 200 }) | ||
}, | ||
), | ||
) |
Oops, something went wrong.