docs(260723-lvg): complete "Jetzt abrufen" quick task summary
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+140
@@ -0,0 +1,140 @@
|
||||
---
|
||||
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
|
||||
Reference in New Issue
Block a user