Upgrade async GPU analysis and workbench UX
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
import { act, renderHook } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { JobRead, SegmentationRead, SegmentationRunRead } from '../types'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
listModels: vi.fn(),
|
||||
runAsync: vi.fn(),
|
||||
listRuns: vi.fn(),
|
||||
getRun: vi.fn(),
|
||||
listSegmentations: vi.fn(),
|
||||
getRunGeoJson: vi.fn(),
|
||||
compareWithReference: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('../services/api', () => ({
|
||||
segmentationApi: {
|
||||
listModels: mocks.listModels,
|
||||
runAsync: mocks.runAsync,
|
||||
listRuns: mocks.listRuns,
|
||||
getRun: mocks.getRun,
|
||||
listSegmentations: mocks.listSegmentations,
|
||||
getRunGeoJson: mocks.getRunGeoJson,
|
||||
compareWithReference: mocks.compareWithReference,
|
||||
},
|
||||
}))
|
||||
|
||||
import { useSegmentationWorkflow } from './useSegmentationWorkflow'
|
||||
|
||||
const projectId = 'project-1'
|
||||
const datasetId = 'dataset-1'
|
||||
const jobId = 'job-1'
|
||||
const analysisRunId = 'run-1'
|
||||
|
||||
const completedJob: JobRead = {
|
||||
id: jobId,
|
||||
job_type: 'segmentation.run',
|
||||
status: 'success',
|
||||
project_id: projectId,
|
||||
dataset_id: datasetId,
|
||||
parameters_json: {},
|
||||
result_json: { analysis_run_id: analysisRunId, segmentation_count: 2 },
|
||||
}
|
||||
|
||||
const persistedRun: SegmentationRunRead = {
|
||||
id: analysisRunId,
|
||||
project_id: projectId,
|
||||
dataset_id: datasetId,
|
||||
job_id: jobId,
|
||||
analysis_type: 'segmentation',
|
||||
status: 'success',
|
||||
model_name: 'yolo-seg-configured',
|
||||
parameters_json: {},
|
||||
result_json: { segmentation_count: 2 },
|
||||
}
|
||||
|
||||
function renderWorkflow(selectedProjectId = projectId) {
|
||||
const loadProjectData = vi.fn().mockResolvedValue(undefined)
|
||||
const loadQualityChecks = vi.fn().mockResolvedValue([])
|
||||
const view = renderHook(() => useSegmentationWorkflow({
|
||||
selectedProjectId,
|
||||
rasterDatasets: [],
|
||||
qaIouThreshold: 0.5,
|
||||
loadProjectData,
|
||||
loadQualityChecks,
|
||||
}))
|
||||
return { ...view, loadProjectData }
|
||||
}
|
||||
|
||||
describe('useSegmentationWorkflow GPU execution', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mocks.listModels.mockResolvedValue({
|
||||
models: [{
|
||||
model_id: 'yolo-seg-configured',
|
||||
display_name: 'YOLO segmentatie',
|
||||
framework: 'ultralytics/pytorch',
|
||||
task_type: 'segmentation',
|
||||
supported_classes: ['building'],
|
||||
configured: true,
|
||||
status: 'configured',
|
||||
limitation_message: '',
|
||||
operator_review_required: true,
|
||||
}],
|
||||
})
|
||||
mocks.runAsync.mockResolvedValue(completedJob)
|
||||
mocks.listRuns.mockResolvedValue({ items: [persistedRun], total: 1 })
|
||||
mocks.getRun.mockResolvedValue(persistedRun)
|
||||
mocks.listSegmentations.mockResolvedValue({ items: [], total: 0, truncated: false })
|
||||
mocks.getRunGeoJson.mockResolvedValue({ type: 'FeatureCollection', features: [] })
|
||||
})
|
||||
|
||||
it('queues, follows and reconciles a persisted segmentation result', async () => {
|
||||
const { result, loadProjectData } = renderWorkflow()
|
||||
await act(async () => { await result.current.loadSegmentationModels() })
|
||||
act(() => {
|
||||
result.current.setSelectedSegmentationDatasetId(datasetId)
|
||||
result.current.setSegmentationTileManifestPath('/tiles/manifest.json')
|
||||
})
|
||||
|
||||
await act(async () => { await result.current.runSegmentation() })
|
||||
|
||||
expect(mocks.runAsync).toHaveBeenCalledWith(expect.objectContaining({
|
||||
project_id: projectId,
|
||||
dataset_id: datasetId,
|
||||
model_id: 'yolo-seg-configured',
|
||||
tile_manifest_path: '/tiles/manifest.json',
|
||||
}))
|
||||
expect(mocks.getRun).toHaveBeenCalledWith(analysisRunId, projectId)
|
||||
expect(result.current.segmentationRunResult).toMatchObject({
|
||||
analysis_run_id: analysisRunId,
|
||||
job_id: jobId,
|
||||
segmentation_count: 2,
|
||||
status: 'success',
|
||||
})
|
||||
expect(result.current.segmentationRunError).toBeNull()
|
||||
expect(result.current.segmentationTotal).toBe(0)
|
||||
expect(result.current.segmentationTruncated).toBe(false)
|
||||
expect(loadProjectData).toHaveBeenCalledWith(projectId)
|
||||
})
|
||||
|
||||
it('does not queue a configured model without a tile manifest', async () => {
|
||||
const { result } = renderWorkflow()
|
||||
await act(async () => { await result.current.loadSegmentationModels() })
|
||||
act(() => { result.current.setSelectedSegmentationDatasetId(datasetId) })
|
||||
|
||||
await act(async () => { await result.current.runSegmentation() })
|
||||
|
||||
expect(mocks.runAsync).not.toHaveBeenCalled()
|
||||
expect(result.current.segmentationRunError).toContain('beeldtegelmanifest')
|
||||
})
|
||||
|
||||
it('ignores a late run list after the active project changes', async () => {
|
||||
let resolveOlder!: (value: { items: SegmentationRunRead[]; total: number }) => void
|
||||
let resolveNewer!: (value: { items: SegmentationRunRead[]; total: number }) => void
|
||||
mocks.listRuns
|
||||
.mockReturnValueOnce(new Promise((resolve) => { resolveOlder = resolve }))
|
||||
.mockReturnValueOnce(new Promise((resolve) => { resolveNewer = resolve }))
|
||||
const loadProjectData = vi.fn().mockResolvedValue(undefined)
|
||||
const loadQualityChecks = vi.fn().mockResolvedValue([])
|
||||
const { result, rerender } = renderHook(
|
||||
({ selectedProjectId }) => useSegmentationWorkflow({
|
||||
selectedProjectId,
|
||||
rasterDatasets: [],
|
||||
qaIouThreshold: 0.5,
|
||||
loadProjectData,
|
||||
loadQualityChecks,
|
||||
}),
|
||||
{ initialProps: { selectedProjectId: 'project-1' } },
|
||||
)
|
||||
|
||||
let olderRequest!: Promise<void>
|
||||
let newerRequest!: Promise<void>
|
||||
act(() => { olderRequest = result.current.loadSegmentationRuns('project-1') })
|
||||
rerender({ selectedProjectId: 'project-2' })
|
||||
act(() => { newerRequest = result.current.loadSegmentationRuns('project-2') })
|
||||
const projectTwoRun = { ...persistedRun, id: 'run-2', project_id: 'project-2' }
|
||||
await act(async () => {
|
||||
resolveNewer({ items: [projectTwoRun], total: 1 })
|
||||
await newerRequest
|
||||
})
|
||||
await act(async () => {
|
||||
resolveOlder({ items: [persistedRun], total: 1 })
|
||||
await olderRequest
|
||||
})
|
||||
|
||||
expect(result.current.segmentationRuns).toEqual([projectTwoRun])
|
||||
expect(result.current.selectedSegmentationRunId).toBe('run-2')
|
||||
})
|
||||
|
||||
it('ignores late polygons from another project and clears an empty selection loader', async () => {
|
||||
let resolveOlderList!: (value: { items: SegmentationRead[]; total: number }) => void
|
||||
let resolveNewerList!: (value: { items: SegmentationRead[]; total: number }) => void
|
||||
let resolveOlderGeo!: (value: GeoJSON.FeatureCollection) => void
|
||||
let resolveNewerGeo!: (value: GeoJSON.FeatureCollection) => void
|
||||
mocks.listSegmentations
|
||||
.mockReturnValueOnce(new Promise((resolve) => { resolveOlderList = resolve }))
|
||||
.mockReturnValueOnce(new Promise((resolve) => { resolveNewerList = resolve }))
|
||||
mocks.getRunGeoJson
|
||||
.mockReturnValueOnce(new Promise((resolve) => { resolveOlderGeo = resolve }))
|
||||
.mockReturnValueOnce(new Promise((resolve) => { resolveNewerGeo = resolve }))
|
||||
const loadProjectData = vi.fn().mockResolvedValue(undefined)
|
||||
const loadQualityChecks = vi.fn().mockResolvedValue([])
|
||||
const { result, rerender } = renderHook(
|
||||
({ selectedProjectId }) => useSegmentationWorkflow({
|
||||
selectedProjectId,
|
||||
rasterDatasets: [],
|
||||
qaIouThreshold: 0.5,
|
||||
loadProjectData,
|
||||
loadQualityChecks,
|
||||
}),
|
||||
{ initialProps: { selectedProjectId: 'project-1' } },
|
||||
)
|
||||
const oldItem: SegmentationRead = {
|
||||
id: 'segment-1', project_id: 'project-1', analysis_run_id: 'run-1', model_name: 'model', class_name: 'building',
|
||||
}
|
||||
const newItem: SegmentationRead = {
|
||||
id: 'segment-2', project_id: 'project-2', analysis_run_id: 'run-2', model_name: 'model', class_name: 'building',
|
||||
}
|
||||
let olderRequest!: Promise<void>
|
||||
let newerRequest!: Promise<void>
|
||||
act(() => { olderRequest = result.current.loadSegmentationResults('run-1') })
|
||||
rerender({ selectedProjectId: 'project-2' })
|
||||
act(() => { newerRequest = result.current.loadSegmentationResults('run-2') })
|
||||
await act(async () => {
|
||||
resolveNewerList({ items: [newItem], total: 1 })
|
||||
resolveNewerGeo({ type: 'FeatureCollection', features: [] })
|
||||
await newerRequest
|
||||
})
|
||||
await act(async () => {
|
||||
resolveOlderList({ items: [oldItem], total: 1 })
|
||||
resolveOlderGeo({ type: 'FeatureCollection', features: [] })
|
||||
await olderRequest
|
||||
})
|
||||
|
||||
expect(result.current.segmentationItems).toEqual([newItem])
|
||||
await act(async () => { await result.current.loadSegmentationResults('') })
|
||||
expect(result.current.loadingSegmentationResults).toBe(false)
|
||||
expect(result.current.segmentationItems).toEqual([])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user