Files
tessera-ctl/.planning/quick/260723-lvg-jetzt-abrufen-button-fuer-ausschreibungs/260723-lvg-SUMMARY.md
T
schalli 51501aa7ee
Tessera CI/CD / Lint & Type Check (push) Successful in 1m13s
Tessera CI/CD / Tests (push) Successful in 1m5s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m52s
docs(260723-lvg): complete "Jetzt abrufen" quick task summary
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 15:55:23 +02:00

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 TenderIngestionService as the LAST constructor param on TendersController (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 in tenders.module.ts).
  • Added pollNow() handler: @Post('poll-now'), @Roles(Role.ADMIN, Role.SUPER_ADMIN), declared immediately after getSourceConfig() — well before @Get(':id') (route-order Pitfall 5). Body: awaits pollDueSources(), 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 @Roles metadata via Reflect.getMetadata(ROLES_KEY, TendersController.prototype.pollNow).
  • Added a route-declaration-order regression test (pollNow index < getTender index) to the existing "route declaration order" describe block.
  • Commit: 7502f97

Task 2: Frontend PollNowButton, pollNow client, refetch wiring + i18n

  • pollNow() client in tender-radar-api.ts — POST /modules/tender-radar/poll-now, credentials: 'include', throws on non-2xx, mirrors DKV's checkNow client shape.
  • New PollNowButton.tsx (self-contained, 'use client'): copies the DKV SpinnerIcon, renders the primary button with the DKV opacity/disabled states, shows a dismissible destructive-text error line on failure. Props: { onPolled?: () => void }.
  • page.tsx: added refreshKey state, wrapped the settings gear <Link> and <PollNowButton onPolled={...}> in a right-side flex cluster inside the existing header row, passed refreshKey into <ResultsList>.
  • ResultsList.tsx: accepts an optional refreshKey prop ({ refreshKey = 0 }: { refreshKey?: number } = {}), added to the load useCallback dependency array alongside paramsKey.
  • de.json/en.json: added tenderRadar.page.pollNow, pollNowTitle, polling, pollError, pollErrorDismiss to 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 --noEmit after the vitest run passed
  • Issue: The plan's approach of appending TenderIngestionService as 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-arg new TendersController(...) call in tenders.controller.spec.ts (36 call sites) — vitest itself doesn't type-check so the test run was green, but tsc --noEmit caught it (TS2554: Expected 8 arguments, but got 7).
  • Fix: Declared the param optional (tenderIngestionService?: TenderIngestionService) and used a non-null assertion (this .tenderIngestionService!.pollDueSources()) inside pollNow() — safe because NestJS DI always resolves and injects this provider in production (already registered in tenders.module.ts); only unit tests that never call pollNow() 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.tsx
    • tenderRadar-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 — FOUND
  • apps/api/src/tenders/tenders.controller.spec.ts — FOUND
  • apps/web/src/lib/tender-radar-api.ts — FOUND
  • apps/web/src/app/(portal)/modules/tender-radar/page.tsx — FOUND
  • apps/web/src/app/(portal)/modules/tender-radar/components/ResultsList.tsx — FOUND
  • apps/web/src/app/(portal)/modules/tender-radar/components/PollNowButton.tsx — FOUND
  • apps/web/src/app/(portal)/modules/tender-radar/components/PollNowButton.test.tsx — FOUND
  • apps/web/src/messages/de.json — FOUND
  • apps/web/src/messages/en.json — FOUND
  • commit 7502f97 — FOUND
  • commit bdaf154 — FOUND