Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply new progress evaluation bar design #465

Merged
merged 1 commit into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
'use client'

import {
CommitStatus,
DocumentVersion,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type EvaluationResultWithMetadataAndErrors,
} from '@latitude-data/core/repositories'
import { Button, Icon, Modal, ReactStateDispatch } from '@latitude-data/web-ui'
import useFetcher from '$/hooks/useFetcher'
import { ROUTES } from '$/services/routes'
import useProviderLogs from '$/stores/providerLogs'
import useSWR from 'swr'
Expand All @@ -18,27 +19,16 @@ import { EvaluationResultMetadata } from './Metadata'
type MaybeDocumentLog = number | null | undefined

function useFetchDocumentLog({ documentLogId }: { documentLogId: number }) {
const fetcher = useFetcher(
ROUTES.api.documentLogs.detail({ id: documentLogId }).root,
)
const {
data: documentLog,
isLoading,
error,
} = useSWR<DocumentLogWithMetadataAndError>(
['documentLogs', documentLogId],
useCallback(async () => {
const response = await fetch(
ROUTES.api.documentLogs.detail({ id: documentLogId }).root,
{
credentials: 'include',
},
)

if (!response.ok) {
const error = await response.json()
throw new Error(error.message)
}

return response.json()
}, [documentLogId]),
fetcher,
)
return { documentLog, isLoading, error }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,36 @@

import { useEffect, useRef, useState } from 'react'

import { ProgressIndicator } from '@latitude-data/web-ui'
import { Badge, Text } from '@latitude-data/web-ui'
import { type EventArgs } from '$/components/Providers/WebsocketsProvider/useSockets'

import { isEvaluationRunDone } from '../../../_lib/isEvaluationRunDone'
import { useEvaluationStatusEvent } from '../../../_lib/useEvaluationStatusEvent'

const DISAPERING_IN_MS = 5000

function BatchIndicator({ job }: { job: EventArgs<'evaluationStatus'> }) {
const isDone = isEvaluationRunDone(job)
const badgeLabel = isDone ? 'Finished' : 'Running'
const doneRuns = job.completed + job.errors
return (
<div className='flex flex-row items-center gap-x-4'>
<Badge variant={isDone ? 'muted' : 'accent'}>{badgeLabel}</Badge>
<div className='flex flex-row items-center gap-x-2'>
<Text.H5>{`${doneRuns} of ${job.total} generated`}</Text.H5>
{job.errors > 0 ? (
<>
<Text.H5 color='foregroundMuted'>·</Text.H5>
<Text.H5 color='destructiveMutedForeground'>
{job.errors} Errors
</Text.H5>
</>
) : null}
</div>
</div>
)
}

export function EvaluationStatusBanner({
documentUuid,
evaluationId,
Expand Down Expand Up @@ -59,27 +81,11 @@ export function EvaluationStatusBanner({
return (
<>
{jobs.map((job) => (
<div key={job.batchId} className='flex flex-col gap-4'>
{!isEvaluationRunDone(job) && (
<ProgressIndicator state='running'>
{`Running batch evaluation ${job.completed}/${job.total}`}
</ProgressIndicator>
)}
{job.errors > 0 && (
<ProgressIndicator state='error'>
Some evaluations failed to run. We won't retry them automatically
to avoid increasing provider costs. Total errors:{' '}
<strong>{job.errors}</strong>
</ProgressIndicator>
)}
{isEvaluationRunDone(job) && (
<ProgressIndicator state='completed'>
Batch evaluation completed! Total evaluations:{' '}
<strong>{job.total}</strong> · Total errors:{' '}
<strong>{job.errors}</strong> · Total completed:{' '}
<strong>{job.completed}</strong>
</ProgressIndicator>
)}
<div
key={job.batchId}
className='flex flex-col gap-4 p-4 rounded-lg border border-border'
>
<BatchIndicator job={job} />
</div>
))}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,39 @@ describe('runEvaluationJob', () => {
})
})

it('increment error counter when response has an error', async () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We were missing updating the counter of errors on an evaluation when the error happens in the response. We were checking only the result of the run.

Errors can happen:

  1. Before runChain occours. Errors on runEvaluation
  2. Errors on runChain which are inside the run.response

runChainResponse = Result.error(
new ChainError({
code: RunErrorCodes.EvaluationRunResponseJsonFormatError,
message: 'malformed json response',
}),
)
runEvaluationSpy.mockResolvedValueOnce(
Result.ok({
stream,
response: new Promise((resolve) => resolve(runChainResponse)),
resolvedContent: 'chain resolved text',
errorableUuid: FAKE_ERRORABLE_UUID,
duration: new Promise((resolve) => resolve(1000)),
}),
)

await runEvaluationJob(jobData)

expect(websockets.WebsocketClient.getSocket).toHaveBeenCalledTimes(1)
expect(incrementErrorsSpy).toHaveBeenCalledTimes(1)
expect(mockEmit).toHaveBeenCalledWith('evaluationStatus', {
workspaceId: workspace.id,
data: {
batchId: 'batch-123',
evaluationId: evaluation.id,
documentUuid,
completed: 1,
total: 1,
},
})
})

it('throws an error if runEvaluation fails', async () => {
runEvaluationSpy.mockRejectedValue(new Error('Some error'))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ export type RunEvaluationJobData = {
batchId: string
}

async function isSuccessful(run: Awaited<ReturnType<typeof runEvaluation>>) {
if (run.error) return { ok: false, error: run.error }

const response = await run.value.response
if (response.error) return { ok: false, error: response.error }

return { ok: true, error: undefined }
}

export async function runEvaluationJob(job: Job<RunEvaluationJobData>) {
const { workspaceId, batchId, documentUuid, documentLogUuid, evaluationId } =
job.data
Expand All @@ -72,13 +81,15 @@ export async function runEvaluationJob(job: Job<RunEvaluationJobData>) {
documentLogUuid,
}).then((r) => r.unwrap())

const result = await runEvaluation({
const run = await runEvaluation({
documentLog,
evaluation,
documentUuid,
})

if (result.ok) {
const { ok, error } = await isSuccessful(run)

if (ok) {
await progressTracker.incrementCompleted()
} else {
await progressTracker.incrementErrors()
Expand All @@ -96,5 +107,5 @@ export async function runEvaluationJob(job: Job<RunEvaluationJobData>) {
},
})

throwIfUnknownError(result.error)
throwIfUnknownError(error)
}
38 changes: 1 addition & 37 deletions packages/web-ui/src/ds/atoms/Alert/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ReactNode } from 'react'

import { cn } from '../../../lib/utils'
import { Icon, IconName } from '../Icons'
import { Icon } from '../Icons'
import {
AlertDescription,
AlertProps,
Expand Down Expand Up @@ -36,38 +35,3 @@ export function Alert({
</AlertRoot>
)
}

type ProgressState = 'completed' | 'error' | 'running'
const ICON_NAME: Record<ProgressState, IconName> = {
completed: 'check',
error: 'alert',
running: 'refresh',
}
const VARIANT_PROGRESS: Record<ProgressState, AlertProps['variant']> = {
completed: 'success',
error: 'destructive',
running: 'default',
}

type ProgressIndicatorProps = {
children: ReactNode
state?: ProgressState
}
export function ProgressIndicator({
children,
state = 'completed',
}: ProgressIndicatorProps) {
const running = state === 'running'
const variant = VARIANT_PROGRESS[state]
return (
<AlertRoot variant={variant}>
<div className='flex flex-row items-center gap-x-2'>
<Icon
name={ICON_NAME[state]}
className={cn({ 'animate-spin': running })}
/>
<div>{children}</div>
</div>
</AlertRoot>
)
}
Loading