6.8 KiB
task: quick-260723-lvg title: "Jetzt abrufen"-Button für Ausschreibungs-Radar (manueller Poll-Trigger) status: complete one-liner: Admin-gated POST /modules/tender-radar/poll-now delegating to TenderIngestionService.pollDueSources(), plus a header PollNowButton (DKV spinner/disabled pattern) that refetches ResultsList on success. key-files: created: - apps/web/src/app/(portal)/modules/tender-radar/components/PollNowButton.tsx - apps/web/src/app/(portal)/modules/tender-radar/components/PollNowButton.test.tsx modified: - apps/api/src/tenders/tenders.controller.ts - apps/api/src/tenders/tenders.controller.spec.ts - apps/web/src/lib/tender-radar-api.ts - apps/web/src/app/(portal)/modules/tender-radar/page.tsx - apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx - apps/web/src/messages/de.json - apps/web/src/messages/en.json metrics: duration: "~35 min" completed: 2026-07-23
Quick 260723-lvg: "Jetzt abrufen"-Button für Ausschreibungs-Radar Summary
Added a manual "Jetzt abrufen" poll trigger to the Ausschreibungs-Radar module,
analogous to DKV's "Jetzt prüfen"/check-now. Backend: one admin-gated
POST /modules/tender-radar/poll-now endpoint that runs
TenderIngestionService.pollDueSources() immediately. Frontend: a
PollNowButton in the tender-radar page header next to the settings gear,
using the DKV spinner/disabled button pattern, refetching the result list on
success via a refreshKey bump, with i18n error handling on failure.
Tasks Completed
Task 1: Backend admin-gated POST /poll-now endpoint + controller spec
- Injected
TenderIngestionServiceas the LAST constructor param onTendersController(declared optional?:in TypeScript so all 7-arg existing spec call sites still type-check; NestJS DI always resolves it in production since it's already a provider intenders.module.ts). - Added
pollNow()handler:@Post('poll-now'),@Roles(Role.ADMIN, Role.SUPER_ADMIN), declared immediately aftergetSourceConfig()— well before@Get(':id')(route-order Pitfall 5). Body: awaitspollDueSources(), returns{ ok: true }. No try/catch — the service never throws (catch-and-log per tick + per source). - Added
makeFakeIngestionService()helper + a new describe block asserting delegation, return shape, and@Rolesmetadata viaReflect.getMetadata(ROLES_KEY, TendersController.prototype.pollNow). - Added a route-declaration-order regression test (
pollNowindex <getTenderindex) to the existing "route declaration order" describe block. - Commit:
7502f97
Task 2: Frontend PollNowButton, pollNow client, refetch wiring + i18n
pollNow()client intender-radar-api.ts—POST /modules/tender-radar/poll-now,credentials: 'include', throws on non-2xx, mirrors DKV'scheckNowclient shape.- New
PollNowButton.tsx(self-contained,'use client'): copies the DKVSpinnerIcon, renders the primary button with the DKV opacity/disabled states, shows a dismissible destructive-text error line on failure. Props:{ onPolled?: () => void }. page.tsx: addedrefreshKeystate, wrapped the settings gear<Link>and<PollNowButton onPolled={...}>in a right-side flex cluster inside the existing header row, passedrefreshKeyinto<ResultsList>.ResultsList.tsx: accepts an optionalrefreshKeyprop ({ refreshKey = 0 }: { refreshKey?: number } = {}), added to theloaduseCallbackdependency array alongsideparamsKey.de.json/en.json: addedtenderRadar.page.pollNow,pollNowTitle,polling,pollError,pollErrorDismissto both locales (parity spec green).- New
PollNowButton.test.tsx: render/label test, click→pollNow→onPolled test, pending-spinner/disabled test (deferred promise), rejection→error copy→no-onPolled test. - Commit:
bdaf154
Semantic Honesty
No force parameter was added to pollDueSources() — out of scope per the
plan. The button labels honestly communicate "fällige Quellen jetzt abrufen"
(fetch DUE sources now): pollNowTitle / EN Fetch due sources now. A
'day'-granularity source (e.g. doe-opendata) that already ingested
today's batch is a structural no-op on this click, by design.
Deviations from Plan
Auto-fixed Issues
1. [Rule 3 - Blocking issue] Made tenderIngestionService constructor param optional
- Found during: Task 1,
npx tsc --noEmitafter the vitest run passed - Issue: The plan's approach of appending
TenderIngestionServiceas a required (non-optional) 8th constructor param compiles fine at the type level for new call sites, but breaks TypeScript compilation for every pre-existing 7-argnew TendersController(...)call intenders.controller.spec.ts(36 call sites) — vitest itself doesn't type-check so the test run was green, buttsc --noEmitcaught it (TS2554: Expected 8 arguments, but got 7). - Fix: Declared the param optional (
tenderIngestionService?: TenderIngestionService) and used a non-null assertion (this .tenderIngestionService!.pollDueSources()) insidepollNow()— safe because NestJS DI always resolves and injects this provider in production (already registered intenders.module.ts); only unit tests that never callpollNow()omit it. - Files modified:
apps/api/src/tenders/tenders.controller.ts - Commit:
7502f97
No other deviations — plan executed as written otherwise.
Test Results
- API:
cd apps/api && npx vitest run→ 393/393 passed (was 390/390; +3 new: delegation, roles metadata, route-order guard). - API:
cd apps/api && npx tsc --noEmit -p tsconfig.json→ clean. - Web:
cd apps/web && npx vitest run→ 155/155 passed (was 151/151; +4 new PollNowButton tests). - Web:
cd apps/web && npx tsc --noEmit→ clean. - Targeted verify command (
PollNowButton.test.tsx+ResultsList.test.tsxtenderRadar-parity.spec.ts): 15/15 passed.
Known Stubs
None.
Threat Flags
None beyond the plan's own threat register (T-lvg-01/02/03), which is fully
addressed: @Roles(ADMIN, SUPER_ADMIN) gates the new route, no per-body
privilege input, generic i18n error copy on 403.
Self-Check: PASSED
apps/api/src/tenders/tenders.controller.ts— FOUNDapps/api/src/tenders/tenders.controller.spec.ts— FOUNDapps/web/src/lib/tender-radar-api.ts— FOUNDapps/web/src/app/(portal)/modules/tender-radar/page.tsx— FOUNDapps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx— FOUNDapps/web/src/app/(portal)/modules/tender-radar/components/PollNowButton.tsx— FOUNDapps/web/src/app/(portal)/modules/tender-radar/components/PollNowButton.test.tsx— FOUNDapps/web/src/messages/de.json— FOUNDapps/web/src/messages/en.json— FOUND- commit
7502f97— FOUND - commit
bdaf154— FOUND