Update
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s

This commit is contained in:
Jens
2026-07-27 23:28:43 +02:00
parent 21015757bd
commit c76a746cd7
39 changed files with 3268 additions and 519 deletions
+14 -5
View File
@@ -4,6 +4,7 @@ import type { DatasetCreateResponse, QualityCheckRead } from '../types'
import { formatError } from '../lib/formatError'
interface DemoWorkflowOptions {
restrictedMode?: boolean
loadProjects: (preferredProjectId?: string | null) => Promise<void>
loadProjectData: (projectId: string) => Promise<{ datasets: DatasetCreateResponse[] } | null>
loadDatasetDetails: (projectId: string, dataset: DatasetCreateResponse) => Promise<void>
@@ -25,6 +26,7 @@ interface DemoWorkflowOptions {
}
export function useDemoWorkflow({
restrictedMode = false,
loadProjects,
loadProjectData,
loadDatasetDetails,
@@ -47,7 +49,7 @@ export function useDemoWorkflow({
const [loadingDemoWorkflow, setLoadingDemoWorkflow] = useState(false)
const [demoWorkflowMessage, setDemoWorkflowMessage] = useState<string | null>(null)
const loadDemoWorkflow = async () => {
const loadDemoWorkflow = async (): Promise<boolean> => {
setLoadingDemoWorkflow(true)
setDemoWorkflowMessage(null)
setErrorMessage(null)
@@ -65,12 +67,17 @@ export function useDemoWorkflow({
setSegmentationReferenceDatasetId(result.reference_dataset_id)
setDemoWorkflowMessage(result.message)
await loadProjects(result.project_id)
const operatorOnlyLoads = restrictedMode
? Promise.resolve()
: Promise.all([
loadDetectionRuns(result.project_id),
loadSegmentationRuns(result.project_id),
loadExports(result.project_id),
]).then(() => undefined)
const [projectData] = await Promise.all([
loadProjectData(result.project_id),
loadDetectionRuns(result.project_id),
loadSegmentationRuns(result.project_id),
loadQualityChecks(result.project_id),
loadExports(result.project_id),
operatorOnlyLoads,
])
const candidateDataset = projectData?.datasets.find((dataset) => dataset.id === result.candidate_dataset_id)
const rasterDataset = projectData?.datasets.find((dataset) => dataset.id === result.raster_dataset_id)
@@ -79,8 +86,10 @@ export function useDemoWorkflow({
} else if (rasterDataset) {
await loadDatasetDetails(result.project_id, rasterDataset)
}
return true
} catch (error) {
setErrorMessage(formatError(error, 'Failed to load demo workflow'))
setErrorMessage(formatError(error, 'De demowerkruimte kon niet worden geladen.'))
return false
} finally {
setLoadingDemoWorkflow(false)
}
+10 -4
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
import { formatError } from '../lib/formatError'
import { formatAuthError } from '../lib/authError'
import { getAuthSession, logout, type AuthSession } from '../services/api/auth'
const signedOutSession: AuthSession = {
@@ -7,6 +7,9 @@ const signedOutSession: AuthSession = {
authenticated: false,
username: null,
expires_at: null,
role: null,
guest_access_enabled: false,
guest_project_id: null,
}
export function useOperatorSession() {
@@ -26,7 +29,7 @@ export function useOperatorSession() {
.catch((error) => {
if (active) {
setSession(signedOutSession)
setSessionError(formatError(error, 'De aanmeldservice is tijdelijk niet bereikbaar.'))
setSessionError(formatAuthError(error, 'De aanmeldservice is tijdelijk niet bereikbaar. Probeer het over enkele ogenblikken opnieuw.'))
}
})
return () => {
@@ -36,7 +39,10 @@ export function useOperatorSession() {
useEffect(() => {
const expireSession = () => {
setSession(signedOutSession)
setSession((current) => ({
...signedOutSession,
guest_access_enabled: current?.guest_access_enabled ?? false,
}))
setSessionError('Uw sessie is verlopen. Meld u opnieuw aan.')
}
window.addEventListener('geointel:session-expired', expireSession)
@@ -49,7 +55,7 @@ export function useOperatorSession() {
setSession(await logout())
setSessionError(null)
} catch (error) {
setSessionError(formatError(error, 'Uitloggen is niet gelukt.'))
setSessionError(formatAuthError(error, 'Uitloggen is niet gelukt. Vernieuw de pagina en probeer opnieuw.'))
} finally {
setLoggingOut(false)
}
@@ -1,4 +1,5 @@
import { renderHook, waitFor } from '@testing-library/react'
import { renderHook } from '@testing-library/react'
import { waitFor } from '@testing-library/dom'
import { describe, expect, it, vi } from 'vitest'
import { useWorkbenchBootstrap } from './useWorkbenchBootstrap'
@@ -63,4 +64,20 @@ describe('useWorkbenchBootstrap', () => {
expect(state.loadQualityChecks).toHaveBeenCalledWith('project-1')
expect(state.loadExports).toHaveBeenCalledWith('project-1')
})
it('keeps the guest bootstrap inside the read-only demo surface', async () => {
const state = { ...options('project-1'), restrictedMode: true }
renderHook(() => useWorkbenchBootstrap(state))
await waitFor(() => expect(state.loadProjectData).toHaveBeenCalledWith('project-1'))
expect(state.loadCapabilities).toHaveBeenCalledOnce()
expect(state.loadQualityChecks).toHaveBeenCalledWith('project-1')
expect(state.loadDetectionModels).not.toHaveBeenCalled()
expect(state.loadSegmentationModels).not.toHaveBeenCalled()
expect(state.loadDetectionRuns).not.toHaveBeenCalled()
expect(state.loadSegmentationRuns).not.toHaveBeenCalled()
expect(state.loadExports).not.toHaveBeenCalled()
expect(state.loadDetectionResults).not.toHaveBeenCalled()
expect(state.loadSegmentationResults).not.toHaveBeenCalled()
})
})
+17 -9
View File
@@ -4,6 +4,7 @@ type AsyncAction = () => Promise<unknown>
type ProjectAction = (projectId: string) => Promise<unknown>
interface WorkbenchBootstrapOptions {
restrictedMode?: boolean
selectedProjectId: string | null
selectedDetectionRunId: string
detectionClassFilter: string
@@ -30,6 +31,7 @@ interface WorkbenchBootstrapOptions {
}
export function useWorkbenchBootstrap({
restrictedMode = false,
selectedProjectId,
selectedDetectionRunId,
detectionClassFilter,
@@ -57,9 +59,11 @@ export function useWorkbenchBootstrap({
useEffect(() => {
loadProjects().catch(() => null)
loadCapabilities().catch(() => null)
loadDetectionModels().catch(() => null)
loadSegmentationModels().catch(() => null)
}, [])
if (!restrictedMode) {
loadDetectionModels().catch(() => null)
loadSegmentationModels().catch(() => null)
}
}, [restrictedMode])
useEffect(() => {
if (!selectedProjectId) {
@@ -76,17 +80,21 @@ export function useWorkbenchBootstrap({
resetSegmentationForProject()
resetExportsForProject()
loadProjectData(selectedProjectId).catch(() => null)
loadDetectionRuns(selectedProjectId).catch(() => null)
loadSegmentationRuns(selectedProjectId).catch(() => null)
loadQualityChecks(selectedProjectId).catch(() => null)
loadExports(selectedProjectId).catch(() => null)
}, [selectedProjectId])
if (!restrictedMode) {
loadDetectionRuns(selectedProjectId).catch(() => null)
loadSegmentationRuns(selectedProjectId).catch(() => null)
loadExports(selectedProjectId).catch(() => null)
}
}, [restrictedMode, selectedProjectId])
useEffect(() => {
if (restrictedMode) return
loadDetectionResults().catch(() => null)
}, [selectedDetectionRunId, detectionClassFilter, detectionMinConfidenceFilter])
}, [restrictedMode, selectedDetectionRunId, detectionClassFilter, detectionMinConfidenceFilter])
useEffect(() => {
if (restrictedMode) return
loadSegmentationResults().catch(() => null)
}, [selectedSegmentationRunId, segmentationClassFilter, segmentationMinConfidenceFilter])
}, [restrictedMode, selectedSegmentationRunId, segmentationClassFilter, segmentationMinConfidenceFilter])
}