refactor(complianceCheckTab): split — DOCUMENT_TYPES + Storage + Polling out
CI / detect-changes (push) Successful in 7s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / build-sha-integrity (push) Failing after 4s
CI / validate-canonical-controls (push) Successful in 10s
CI / loc-budget (push) Successful in 14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m21s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
CI / detect-changes (push) Successful in 7s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / build-sha-integrity (push) Failing after 4s
CI / validate-canonical-controls (push) Successful in 10s
CI / loc-budget (push) Successful in 14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m21s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
ComplianceCheckTab.tsx war 519 LOC und blockte jeden weiteren Edit
(500-LOC-Hard-Cap). Drei Concerns ausgelagert:
- _document_types.ts: DOCUMENT_TYPES + DocTypeId (inkl. news doc_type)
- _compliance_storage.ts: STORAGE_KEY_*, DocState/HistoryEntry types,
emptyDocState/initState helpers, countWords
- _useCompliancePolling.ts: Resume-Polling-Hook (importierbar,
Inline-Polling bleibt für Stabilität)
ComplianceCheckTab.tsx ist jetzt 461 LOC (-58).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,71 +5,13 @@ import { ChecklistView } from './ChecklistView'
|
||||
import { DocumentRow } from './DocumentRow'
|
||||
import { MigrationPanel } from './MigrationPanel'
|
||||
import { PreScanWizard, useScanContext, isContextComplete } from './PreScanWizard'
|
||||
import { DOCUMENT_TYPES, type DocTypeId } from './_document_types'
|
||||
import {
|
||||
STORAGE_KEY_STATE, STORAGE_KEY_RESULTS, STORAGE_KEY_HISTORY,
|
||||
STORAGE_KEY_CHECK_ID, countWords, initState,
|
||||
type DocState, type DocsState, type HistoryEntry,
|
||||
} from './_compliance_storage'
|
||||
|
||||
const DOCUMENT_TYPES = [
|
||||
{ id: 'dse', label: 'DSI (Datenschutzinformation)', required: true },
|
||||
{ id: 'impressum', label: 'Impressum', required: true },
|
||||
{ id: 'social_media', label: 'Social Media DSE', required: false },
|
||||
{ id: 'cookie', label: 'Cookie-Richtlinie', required: false },
|
||||
{ id: 'agb', label: 'AGB', required: false },
|
||||
{ id: 'nutzungsbedingungen', label: 'Nutzungsbedingungen', required: false },
|
||||
{ id: 'widerruf', label: 'Widerrufsbelehrung', required: false },
|
||||
{ id: 'dsb', label: 'DSB-Kontakt', required: false },
|
||||
] as const
|
||||
|
||||
type DocTypeId = typeof DOCUMENT_TYPES[number]['id']
|
||||
|
||||
interface DocState {
|
||||
url: string
|
||||
text: string
|
||||
loading: boolean
|
||||
error: string | null
|
||||
}
|
||||
|
||||
type DocsState = Record<DocTypeId, DocState>
|
||||
|
||||
const STORAGE_KEY_STATE = 'compliance-check-state'
|
||||
const STORAGE_KEY_RESULTS = 'compliance-check-results'
|
||||
const STORAGE_KEY_HISTORY = 'compliance-check-history'
|
||||
const STORAGE_KEY_CHECK_ID = 'compliance-check-active-id'
|
||||
|
||||
function emptyDocState(): DocState {
|
||||
return { url: '', text: '', loading: false, error: null }
|
||||
}
|
||||
|
||||
function initState(): DocsState {
|
||||
if (typeof window === 'undefined') {
|
||||
return Object.fromEntries(DOCUMENT_TYPES.map(d => [d.id, emptyDocState()])) as DocsState
|
||||
}
|
||||
try {
|
||||
const saved = localStorage.getItem(STORAGE_KEY_STATE)
|
||||
if (saved) {
|
||||
const parsed = JSON.parse(saved) as Record<string, { url?: string; text?: string }>
|
||||
return Object.fromEntries(
|
||||
DOCUMENT_TYPES.map(d => [d.id, {
|
||||
url: parsed[d.id]?.url || '',
|
||||
text: parsed[d.id]?.text || '',
|
||||
loading: false,
|
||||
error: null,
|
||||
}])
|
||||
) as DocsState
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
return Object.fromEntries(DOCUMENT_TYPES.map(d => [d.id, emptyDocState()])) as DocsState
|
||||
}
|
||||
|
||||
function countWords(text: string): number {
|
||||
if (!text.trim()) return 0
|
||||
return text.trim().split(/\s+/).length
|
||||
}
|
||||
|
||||
interface HistoryEntry {
|
||||
date: string
|
||||
docCount: number
|
||||
findings: number
|
||||
resultKey: string
|
||||
checkId?: string
|
||||
}
|
||||
|
||||
export function ComplianceCheckTab() {
|
||||
const [docs, setDocs] = useState<DocsState>(initState)
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Storage-Helfer für ComplianceCheckTab.
|
||||
*
|
||||
* Extrahiert aus ComplianceCheckTab.tsx (P11-Tech-Debt-Sprint) damit
|
||||
* die zentrale UI unter der 500-LOC-Hard-Cap bleibt.
|
||||
*/
|
||||
|
||||
import { DOCUMENT_TYPES, type DocTypeId } from './_document_types'
|
||||
|
||||
export const STORAGE_KEY_STATE = 'compliance-check-state'
|
||||
export const STORAGE_KEY_RESULTS = 'compliance-check-results'
|
||||
export const STORAGE_KEY_HISTORY = 'compliance-check-history'
|
||||
export const STORAGE_KEY_CHECK_ID = 'compliance-check-active-id'
|
||||
|
||||
export interface DocState {
|
||||
url: string
|
||||
text: string
|
||||
loading: boolean
|
||||
error: string | null
|
||||
}
|
||||
|
||||
export type DocsState = Record<DocTypeId, DocState>
|
||||
|
||||
export interface HistoryEntry {
|
||||
date: string
|
||||
docCount: number
|
||||
findings: number
|
||||
resultKey: string
|
||||
checkId?: string
|
||||
}
|
||||
|
||||
export function emptyDocState(): DocState {
|
||||
return { url: '', text: '', loading: false, error: null }
|
||||
}
|
||||
|
||||
export function initState(): DocsState {
|
||||
if (typeof window === 'undefined') {
|
||||
return Object.fromEntries(
|
||||
DOCUMENT_TYPES.map(d => [d.id, emptyDocState()]),
|
||||
) as DocsState
|
||||
}
|
||||
try {
|
||||
const saved = localStorage.getItem(STORAGE_KEY_STATE)
|
||||
if (saved) {
|
||||
const parsed = JSON.parse(saved) as Record<
|
||||
string, { url?: string; text?: string }
|
||||
>
|
||||
return Object.fromEntries(
|
||||
DOCUMENT_TYPES.map(d => [d.id, {
|
||||
url: parsed[d.id]?.url || '',
|
||||
text: parsed[d.id]?.text || '',
|
||||
loading: false,
|
||||
error: null,
|
||||
}]),
|
||||
) as DocsState
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
return Object.fromEntries(
|
||||
DOCUMENT_TYPES.map(d => [d.id, emptyDocState()]),
|
||||
) as DocsState
|
||||
}
|
||||
|
||||
export function readResultsFromStorage(): unknown | null {
|
||||
if (typeof window === 'undefined') return null
|
||||
try {
|
||||
const s = localStorage.getItem(STORAGE_KEY_RESULTS)
|
||||
return s ? JSON.parse(s) : null
|
||||
} catch { return null }
|
||||
}
|
||||
|
||||
export function readHistoryFromStorage(): HistoryEntry[] {
|
||||
if (typeof window === 'undefined') return []
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(STORAGE_KEY_HISTORY) || '[]')
|
||||
} catch { return [] }
|
||||
}
|
||||
|
||||
export function readActiveCheckId(): string {
|
||||
if (typeof window === 'undefined') return ''
|
||||
return localStorage.getItem(STORAGE_KEY_CHECK_ID) || ''
|
||||
}
|
||||
|
||||
export function countWords(text: string): number {
|
||||
if (!text.trim()) return 0
|
||||
return text.trim().split(/\s+/).length
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Custom hook: resume-polling für eine laufende Compliance-Check-Pruefung.
|
||||
*
|
||||
* Beim Mount: wenn localStorage eine `STORAGE_KEY_CHECK_ID` enthaelt aber
|
||||
* noch kein Result da ist, pollt der Hook alle 3s den Status. Setzt
|
||||
* Result, Progress, Error oder cleared den active-check-id beim
|
||||
* Abschluss.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import {
|
||||
STORAGE_KEY_CHECK_ID, STORAGE_KEY_RESULTS,
|
||||
} from './_compliance_storage'
|
||||
|
||||
interface ResumePollingArgs {
|
||||
activeCheckId: string
|
||||
results: unknown | null
|
||||
setLoading: (b: boolean) => void
|
||||
setProgress: (s: string) => void
|
||||
setProgressPct: (n: number) => void
|
||||
setResults: (r: unknown) => void
|
||||
setActiveCheckId: (s: string) => void
|
||||
setError: (s: string | null) => void
|
||||
}
|
||||
|
||||
export function useCompliancePollingResume({
|
||||
activeCheckId, results, setLoading, setProgress, setProgressPct,
|
||||
setResults, setActiveCheckId, setError,
|
||||
}: ResumePollingArgs) {
|
||||
useEffect(() => {
|
||||
if (!activeCheckId || results) return
|
||||
let cancelled = false
|
||||
setLoading(true)
|
||||
setProgress('Pruefung laeuft noch...')
|
||||
const poll = async () => {
|
||||
while (!cancelled) {
|
||||
await new Promise(r => setTimeout(r, 3000))
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/sdk/v1/agent/compliance-check?check_id=${activeCheckId}`,
|
||||
)
|
||||
if (!res.ok) continue
|
||||
const data = await res.json()
|
||||
if (data.progress) setProgress(data.progress)
|
||||
if (typeof data.progress_pct === 'number') {
|
||||
setProgressPct(data.progress_pct)
|
||||
}
|
||||
if (data.status === 'completed' && data.result) {
|
||||
setResults(data.result)
|
||||
setProgress('')
|
||||
setProgressPct(0)
|
||||
setLoading(false)
|
||||
localStorage.setItem(
|
||||
STORAGE_KEY_RESULTS, JSON.stringify(data.result),
|
||||
)
|
||||
localStorage.removeItem(STORAGE_KEY_CHECK_ID)
|
||||
setActiveCheckId('')
|
||||
return
|
||||
}
|
||||
if (['failed', 'not_found', 'skipped_tdm'].includes(data.status)) {
|
||||
if (data.status !== 'not_found') {
|
||||
setError(
|
||||
data.error
|
||||
|| (data.status === 'skipped_tdm'
|
||||
? 'TDM-Vorbehalt erkannt — Crawl uebersprungen'
|
||||
: 'Pruefung fehlgeschlagen'),
|
||||
)
|
||||
}
|
||||
setProgress('')
|
||||
setProgressPct(0)
|
||||
setLoading(false)
|
||||
localStorage.removeItem(STORAGE_KEY_CHECK_ID)
|
||||
setActiveCheckId('')
|
||||
return
|
||||
}
|
||||
} catch { /* retry */ }
|
||||
}
|
||||
}
|
||||
poll()
|
||||
return () => { cancelled = true }
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
}
|
||||
Reference in New Issue
Block a user