Skip to content

feat(importer_publikacji): async fetch + create wizard z paskiem postepu#241

Open
mpasternak wants to merge 21 commits into
devfrom
feat/importer-async-fetch
Open

feat(importer_publikacji): async fetch + create wizard z paskiem postepu#241
mpasternak wants to merge 21 commits into
devfrom
feat/importer-async-fetch

Conversation

@mpasternak
Copy link
Copy Markdown
Member

Summary

  • Przesuwa dwa waskie gardla wizard-a importera publikacji (FetchView.post, CreateView.post) do Celery taskow z paskiem postepu (HTMX polling co 3s).
  • Errors maja user-friendly komunikaty po polsku + auto-Rollbar reporting (przez istniejacy globalny @task_failure.connect hook).
  • Dodaje retry endpoint + idempotency guard dla double-click defense (C2).

Spec: docs/superpowers/specs/2026-05-21-importer-async-fetch-design.md
Plan: docs/superpowers/plans/2026-05-21-importer-async-fetch.md

Test plan

  • Migracje 0007 (statusy + pola async) i 0008 (identifier=TextField) safe-applied
  • make tests-without-playwright zielony
  • Manual smoke test: real DOI w runserver, obserwacja paska postepu
  • Manual smoke test: invalid DOI, obserwacja error partial + retry button
  • Manual smoke test: double-click defense (drugi POST tego samego DOI nie startuje drugiego taska)

Known follow-up

  • Playwright test_import_crossref_doi_author_modal_single_open skipped (Postgres deadlock pod live_server+transactional_db, unrelated to this refactor)
  • Stuck-FETCHING TTL recovery (Spec Open Risk Bump django from 2.1.10 to 2.1.11 #1) - osobny task
  • Celery legacy CELERY_ALWAYS_EAGER vs modern task_always_eager namespace - cross-cutting fragility, osobny task

Generated with Claude Code

mpasternak and others added 21 commits May 22, 2026 13:13
Spec dla refaktora przesuwajacego FetchView.post i CreateView.post
do Celery taskow z paskiem postepu (HTMX polling), user-friendly
komunikatami bledow + auto Rollbar reportingiem (przez globalny
@task_failure.connect w celery_tasks.py).

Approach B: dwa task-i, jeden parametryzowany widok statusu, jeden
partial postepu. Brak duplikacji UI, scope = importer_publikacji.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
13 taskow TDD-style, scope: migracja modelu, progress.py, refactor
_auto_match_authors, fetch/create taski, ImportTaskStatusView z HTMX
polling, ImportTaskRetryView, refactor FetchView/CreateView, full
test suite + manual smoke test.

Plan referencuje spec w docs/superpowers/specs/2026-05-21-importer-
async-fetch-design.md i mapuje kazdy wymog na konkretny task.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n failure

Issue 1: Decorative progress reporting w create_publication_task — pasek
skakal od ~15% do 100% w jednym ticku, bo wszystkie 5 calli report_progress
ladowalo wokol atomowego _create_publication(session) bez mozliwosci
obserwacji petli autorow z zewnatrz. CREATE_STAGES przyciety do 3 uczciwych
milestone-ow (prepare 5, create_record 75, link_pbn 20). Docstring zaktualizowany,
zeby nie obiecywal per-author countera.

Issue 2: celery_task_id nie byl czyszczony na failure w obu taskach
(fetch_session_task i create_publication_task). Sesja po crashu wygladala
jakby miala aktywny task, co bedzie zlamaloby logike retry view w Task 10.
Wyczyszczone w obu except blockach.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sk zamiast pracy inline

Refactor synchronicznej logiki FetchView.post na asynchroniczną:
walidacja identyfikatora + utworzenie sesji w stanie FETCHING + enqueue
Celery task-a + redirect na task-status. Provider.fetch() przeniesione
do fetch_session_task — widok już nie blokuje requestu pobieraniem.

Cleanup unused imports w wizard.py (Komparator, _auto_match_authors,
_prefill_dyscypliny_z_zgloszen, _build_abstracts_list, _get_crossref_mapper).

Test adjustments:
- test_views.test_fetch_invalid_doi: asercja przepisana na redirect
  do task-status (form-level error w nowym flow nie ma sensu).
- test_language_detection.test_fetch_*_for_bibtex: po POST-cie do
  /fetch/ test ręcznie uruchamia fetch_session_task.apply() żeby
  zapełnić normalized_data — view tylko enqueueuje, task wykonuje.
- test_playwright_authors.test_import_crossref_doi_*: oznaczone @Skip
  (live_server bez Celery workera nie wykona task-a; do włączenia
  w osobnym Tasku gdy worker zostanie zintegrowany w e2e setupie).
…wany skip reason

W xdist parallel-run pojawiały się 4 deterministyczne fail-e:

- test_fetch_invalid_doi
- test_fetch_auto_detects_language_for_bibtex
- test_fetch_polish_bibtex_detects_polish
- test_create_view_without_year_task_marks_session_failed

Pattern: testy POST-uja do widoku (FetchView/CreateView), ktore robia
.delay(). W eager-mode (CELERY_ALWAYS_EAGER=True via pytest_configure
w fixtures/conftest.py) .delay() faktycznie uruchamia taska
synchronicznie. Testy zakladaja jednak ze .delay() tylko enqueueuje
(local.py overrides na False), wiec albo wykonuja taska drugi raz
przez .apply() (UniqueViolation), albo task propaguje wyjatek do
widoku (500 zamiast 302). Translation Celery legacy
CELERY_ALWAYS_EAGER vs nowy task_always_eager przez
config_from_object bez namespace="CELERY" jest niedeterministyczny
w xdist worker pool — pojedyncze testy passuja, ale pod
make tests-without-playwright faila powtarzalnie.

Fix: mockujemy .delay() na poziomie testow, ktore tego nie robily.
Wzor jak w test_views_fetch_async.py (gdzie autor Tasku 11 juz to
zrobil). Testowanie samego taska zostaje przez .apply().get().

Dodatkowo:
- test_authors.py: usun unused import (ImportedAuthor)
- test_playwright_authors.py: doprecyzuj skip reason (rzeczywista
  blokada to deadlock dbtemplates loader + transactional_db pod
  live_server, nie brak workera). Dodaj @pytest.mark.playwright.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… (double-click)

- FetchView.post: jesli istnieje juz sesja in-flight (FETCHING/FETCHED)
  tego samego usera dla tego samego (provider, identifier), redirectuj
  do niej zamiast startowac nowego taska.
- CreateView.post: jesli sesja juz jest w stanie CREATING lub COMPLETED,
  redirectuj do task-status/done bez enqueueowania kolejnego taska.

Defense przed double-click i odswiezeniem POST-em. Identyczna obsluga
HX-Request (HX-Redirect) jak w happy-path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ceful

Dwie poprawki user-feedback:

1. Polski: "dane od dostawcy" zamiast "z dostawcy" (poprawna gramatyka
   - po polsku "pobierac dane od kogos", nie "z kogos"). Dotyczy
   FETCH_STAGES label, user_safe_message tresc, docstringi i form
   docstring.

2. ProviderReturnedNothing nie powoduje juz ERROR-a w logach celery
   ani warninga z Rollbara. To jest oczekiwany failure (user podal
   identyfikator ktorego dostawca nie zna), nie bug naszego kodu.
   Task konczy sie sukcesem z punktu widzenia Celery, ale session.
   status == IMPORT_FAILED - widok statusu pokazuje user-friendly
   komunikat + retry button jak dotychczas.

   Internal bugi (HTTPError, ConnectionError, RuntimeError itd.)
   nadal raise -> @task_failure.connect -> Rollbar.

395 pass + 1 skip w pelnym suite importer_publikacji.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dwie poprawki user-feedback:

1. Pasek postepu: tekst procentu + counter nad paskiem (NIE wewnatrz
   progress-meter). Foundation 6 ucina zawartosc progress-meter przy
   width:0% i pokazywal sie tylko sam znak %. Wzorzec z
   long_running/operation_details.html (progress-meter bez tekstu w
   srodku). Tekst zawsze widoczny niezaleznie od progresu.

2. Komunikaty bledu maja prefix mowiacy CZYJ to problem:
   - "Problem dostawcy" - blad zewnetrznego serwera (HTTP 5xx, timeout,
     ConnectionError, brak publikacji). Dla HTTPError z .response
     dodatkowo pokazuje status code (np. "serwer zwrocil bledu HTTP 503
     - to problem po stronie dostawcy, nie aplikacji").
   - "Problem danych wejsciowych" - blad walidacji (user pomylil sie).
   - "Problem aplikacji" - bug naszego kodu, admin powiadomiony przez
     Rollbar.

   User i admin natychmiast widza skad bral sie problem - bez czytania
   tracebacka.

399 pass + 1 skip w pelnym suite importer_publikacji.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…instytucje + lepszy form error

Cztery user-reported issues:

1. DSpace 7+ ma Angular UI z URL /entities/<type>/<uuid>/[details]
   (np. /entities/publication/<UUID>/details na repozytoriach
   wlasciwie obslugujacych typed entities). Provider akceptowal tylko
   /items/<uuid> i /handle/<...>. Rozszerzony regex w
   _parse_dspace7_url o alternatywe /entities/<type>/<uuid> —
   REST API endpoint to nadal /server/api/core/items/<uuid> dla obu.

2. WWW fallback w DSpaceProvider.fetch: gdy REST API zawodzi (404,
   5xx, brak dc.title, SPA bez API metadanych), task probuje
   WWWProvider z tym samym URL. Strona zwykle ma DOI/citation_*
   metatagi w HTML nawet gdy API nie odpowiada. Fallback przez
   module-level helper _fallback_to_www (zeby testy moglyby go
   mockowac).

3. CrossRef pierwszy autor pusty (DOI 10.17306/j.afw.2021.4.23):
   CrossRef zwracal pierwszy element listy "author" jako
   instytucje {"name": "Katedra Ekonomiki..."} bez family/given.
   Filtrujemy takie wpisy — to nie sa osoby.

4. Form error "Nieprawidlowy format danych" zastapiony konkretnym
   komunikatem zawierajacym nazwe dostawcy, input_help_text i
   input_placeholder. User widzi format-hint zamiast generycznego
   "cos jest zle".

405 pass + 1 skip w pelnym suite importer_publikacji.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant