Repository navigation
Check first-round imports before importing; save a first round with its files (#510, #509, #447) - #656
Merged
Conversation
- utils: ImportSourceInvalid/CheckBlocked/CheckExpired/CheckRequired (400 with error_type); config key import_check_path (MONTAGE_IMPORT_CHECK_PATH) - labs: one query text for lookups by name and by file_id; get_files_info_by_ids - loaders: fetch_source_text, parse_source_rows (filename > img_name, optional file_id, BOM/case-insensitive headers), parse_name_list, lookups; old full-CSV/name-list loaders removed (#509, #517); only gist links are rewritten (#208) Refs #510, #509, #208 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- POST /admin/campaign/<id>/import/check, GET .../import/check/<token>/download - import_entries requires check_token for csv/gistcsv/category/selected and imports exactly the checked list; blocked check -> 400 before writes - add_checked_entries replaces the three DAO loaders; file_id lookup in every campaign (FILE_ID_LOOKUP_CUTOFF removed) - app refuses to start on beta/prod/devlabs without MONTAGE_IMPORT_CHECK_PATH - no 'all disqualified' warning for an empty import (#208) - tests import through check + token Refs #510, #208 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Check button + ImportCheckResult panel (text only), download link - Save only after a non-blocking check; source change clears the check; stale check responses ignored; source locked while checking - the round is created once; a failed import is retried into it, with the round settings locked - i18n (en + qqq) for the new texts and the import warning dialog - Cypress: check before Add Round (not run) Refs #510 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ll (#447) A first round was created by one request and filled by a second one; when the import brought no files (an empty category, a CSV with no valid names) or failed, an empty round stayed behind and blocked creating another one. - add_round takes an optional import {import_method, check_token}: the round and its files are saved in one transaction; an import that fails, is blocked or brings no files (import_empty) rolls the round back - the round form sends the checked import with the round and keeps Save disabled when the check found no importable file; on an error nothing was saved and the form stays as entered (the locked-settings retry of #510 is no longer needed and is removed) Based on feat/import-check-510. Fixes #447 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- check folder: cleanup deletes only Montage's own check files (token- named, .tmp-), at most MAX_CHECK_FILES kept; an existing folder keeps its mode; startup refuses a relative, unwritable or shared folder and any unconfigured folder outside dev/devtest; warns outside /data/project - source links: https only, every address of the host (and of each of at most 3 redirects) must be public; 20 MB and 100,000-row caps; source fields must be text - same-name keys: database keys on mysql/mariadb, the test-only stand-in only for SQLite in dev/tests, otherwise an error; NULL keys refused - file names canonical like MediaWiki File: titles (prefix in any case, runs of spaces/underscores, first letter upper case) - round sources store a check fingerprint (check_id), not the token, and a new check of the same source reuses the round source - issues report download (every row that is not ok) - add_round accepts only checked import methods - checked imports skip add_entries' lower-case dedupe (matched by file_id); Entry.file_id declared indexed - tools/admin.py create_round: source and advance params - unused imports and cutoff names removed; _ImportError renamed Refs #510, #447 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- deploy.sh stops before building when a required envvar is missing - deployment.md: $HOME-based commands, folder of its own, cleanup rules, troubleshooting (missing variable, timeout, old files) and rollback - docs/api.md: import/check, download, issues, add_round's import, the new error types Refs #510, #447 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- while a round is saved: source fields and Cancel disabled, Save says what it is doing; Cancel reloads the campaign - save errors handled by type: blocked -> check again, no files -> nothing to import, expired / stale page -> check again or reload, other errors -> a message next to Save, no answer -> reload the campaign and say the round may exist - a check older than 7 days is refused before saving - the warnings dialog closed with X/Escape closes the form too (dialogService onClose); dialog text keeps its line breaks - screen readers: a live region announces check results, errors and a cleared check; focus moves to the result; Save is described by the reason it is disabled; the issues table is a named, focusable region with a caption; blocking rows are marked in text; server English is marked lang=en; the pair separator is hidden - category checks: own summary, no 'row' labels, same-name rows not shown as blocking; a typed but unpicked category gets a hint - issues report link; download links use the download attribute - i18n: no concatenated strings; no plurals (none in the project) - Cypress: the save is intercepted, its body asserted, a double click saves once, a refused import keeps the form (not run) Refs #510, #447 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… days Files are renamed and deleted on Commons all the time, and the check and the save happen in one sitting, so a check can be imported for 1 hour (was 7 days). The check file and its downloads (upload file, issues report) stay at most 7 days: every check, import, download and the app start delete older files, so they also go after a quiet spell. Refs #510 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…help texts The File List box read every line as a file name, so a pasted CSV (e.g. the check's download, meant to be the next source) came back as "not on Commons" line by line. Now: - a first line that is a header with filename, img_name or file_id makes the box read as a CSV, exactly like a File List URL source; - a line of digits only is a file_id (a Commons name always has an extension), so a plain list of file_ids works, mixed with names; - a CSV may have only a file_id column (names come from Commons). The same-name rule and the file_id lookup apply as for every list. Help texts: file list explains names, file_ids and pasted CSVs; File List URL names the columns and the sharing setting; the download says it can be pasted into File List; category example is 2026. Found while testing on montage-dev (2026-10-07). #510 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esult - Status "Same name as another file" -> "... inside this import": the check compares names within one import, not with files already in Montage. - The visually hidden "and" between two same-name files had no spaces (Vue drops whitespace between elements across a line break), so a screen reader read "andName.jpg" as one word. Found while testing on montage-dev (2026-10-07). #510 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… clearer download
- "(row N)" is its own message and never breaks across lines; the
combined "{0} (row {1})" message is replaced by montage-round-check-row-ref.
- Commons names (no spaces) may wrap anywhere; on narrow screens each file
of a same-name pair gets its own line.
- When the check blocks, the download link says it holds only the files
without problems and can be pasted into File List; otherwise "Download
the files that will be imported (CSV)".
Found while testing on montage-dev (2026-10-07). #510
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h a reason Commons shows page IDs (Page information) and revision IDs (oldid=...), not file_ids. A bare number in a list (no name) was taken as a file_id: a revision ID came back as "not on Commons" without saying why, and a page ID that happened to be another file's file_id imported that other file silently. Bare numbers are now also looked up as the page ID and revision IDs of File: pages (main replica, same batches): - page_id / revision_id: blocks, "N is a revision ID (one edit of the page File:X), not a file_id; ... write the file name instead: X"; - ambiguous_id: the file_id of one file and the page/revision ID of another: blocks, asks for the file name; - a page ID of the same file, or a row with a name, is unaffected. Live-replica test (xfail off Toolforge) for the new query. #510 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… focus, phones From a three-viewpoint UI review (coordinator, consistency, screen reader + phone), 2026-10-08. Translated reasons: every checked row and every source error now carries reason_code + reason_params; the form shows montage-round-check-reason-* / montage-round-check-error-* and falls back to the English server text (marked lang="en") only without a translation. The dialog after saving is built from the check's rows, translated, instead of server English. Texts (one glossary: problems, file ID, "check the source again", "can be imported", Commons, group): - a check with only warnings now says the round can be saved and that rows are left out or changed; - the hints next to Save no longer point to rows "below" (they are above); Save after a blocking check no longer says "check first"; - same-name: "names Montage cannot tell apart", explains why (case, accents, е/ё), "groups" instead of "pairs", no advice to rename; - labels match the help text (file names or file IDs; link to the sheet or file list); expiry says nothing was saved and why; - the partial-download link is short, the steps follow as text; - blocked-save detail: no raw status codes, singular "1 row". Screen reader and phone: - focus moves to the check result or the save error after a check, an expired check or a refused save (Save loses focus while disabled); the result panel is a named region instead of being announced twice; the live region is outside the aria-busy container; - Save is disabled while a check runs; - buttons and the button row wrap instead of cutting labels off; the problems table scrolls sideways on phones (no inner vertical scroll); row numbers are row headers; file names in <bdi> for right-to-left names; dialog text wraps long names. Tests: reason codes per row and on source errors; wording updates. #510 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…heck Where entries.name has a unique index (beta, installs built from the model), a new file whose name the database treats as equal to that of a different file already in Montage cannot be stored: saving a round with the WLM 2026 Russia category on beta failed with IntegrityError 1062 on "Челябинск_2026,_пр._Ленина_71А.jpg" (beta already had "...71а.jpg"), 2026-10-10. The check compared names only within one import. The check now also looks the names up in entries (the database's own comparison, through that unique index) and marks the clashes as same_name_existing: left out of a category with a warning, blocking a list. The same file (same file_id) and an old row of exactly this name without a file_id are reused by the import, as before, and are not clashes. Production has no index on entries.name (#650): there both rows are stored, so the lookup (a full table scan there) is skipped. New status label and reasons (translated); tests for the app-level case, the database comparison and the skip without a unique index. #510, #645 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Testing on beta (2026-10-10) showed the "ambiguous id" check blocking an
ordinary file ID: almost every file_id is also the page ID of some other
file page (Commons API: 4 of 4 file_ids from our tests), so a bare
number cannot be told apart and the check blocked nearly every list of
file IDs.
Now a bare number that is a file_id is taken as one, and shown as
"Found by file ID" with the file's name (status by_file_id, imported),
so a wrong number is seen before saving. Only a number that is no
file_id is looked up as a page ID / revision ID of a file page, as a
courtesy in the error message ("... is the page ID of File:X, not its
file ID"). The ambiguous_id status is gone.
The help texts and errors now say plainly that a file ID is the number
of the file itself (the file_id column), not the page ID shown under
Page information.
#510
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…file ID The yellow message after a check with only warnings said rows were "left out or changed". Rows found by a bare file ID are also listed there and are neither: they are imported, and shown so a wrong number is seen. The message now reads "You can save the round. Check the rows listed below: some are left out, changed, or found by file ID." The qqq entry explains the three kinds. #510 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #510, closes #509, closes #447, closes #517, closes #146. Fixes the link part of #208. Prevents #645 within one import, and against files already in Montage where
entries.namehas a unique index. Related: #513, #635, #645, #650, #654.What changes
First-round imports become two steps: check, then save the round together with exactly what was checked.
Check (
POST /v1/admin/campaign/<id>/import/check, campaign coordinators)filename,img_nameorfile_id, the box is read as a CSV, so the check's own download can be pasted back in.file_idif the row has one, otherwise by name. The lookup byfile_idis a new wikireplica query,file.file_id IN (...), 500 per query, with the same joins as the name lookup.filename, or elseimg_name; header names are case-insensitive and a BOM is ignored) and an optionalfile_idcolumn are read. A CSV may have only afile_idcolumn. Commons supplies all metadata (Entry metadata should be identical regardless of import method #509).File:/Image:prefix is removed, in any case;Row results:
file_idknown, same nameokfile_idknown, other namerenamedfile_idby_file_idduplicatefile_id)okfile_id)unknown_namefile_idunknown, or not a plain whole numberunknown_file_id,malformed_file_idfile_idbut the page ID or a revision ID of a file pagepage_id,revision_idsame_namesame_name_existingsame_name. Only whereentries.namehas a unique index (beta, installs built from the model); production has none (#650), so the lookup is skipped thereA file ID is the number of the file itself (the
file_idcolumn), not the page ID shown under Page information. Almost everyfile_idis also the page ID of some other file page, so a bare number is always taken as afile_id. Page and revision IDs are looked up only for numbers that are nofile_id, to explain the error.The same-name rule is the database's own:
WEIGHT_STRING(CONVERT(name USING utf8mb4) COLLATE utf8mb4_unicode_ci), batched and without reading any table.import_check.SAME_NAME_CHECK.Translated reasons: every checked row and every source error carries
reason_codeandreason_params. The form shows them through i18n keys (montage-round-check-reason-*,montage-round-check-error-*) and falls back to the English server text (markedlang="en") only when a key is missing. The dialog after saving is built from the check's rows, translated.Check file (
MONTAGE_IMPORT_CHECK_PATH)Downloads
filename(Commons' current name) andfile_id, one row per file the import would take. Uploading it again, or pasting it into File List, passes the check unchanged. When the check blocks, the link says the file holds only the rows without problems, and the steps follow as text.', which Montage removes again when reading.Saving a first round (
POST /v1/admin/campaign/<id>/add_roundwithimport: {import_method, check_token}, #447)import_empty), nothing is saved (no round). The coordinator fixes the source (or checks it again) and saves again.POST /v1/admin/round/<id>/importalso needscheck_token(for imports into an existing round). Without one it returnsimport_check_required.import_method: round) is unchanged.Matching existing entries: by
file_idin every campaign. #654's 2026-06-01 cutoff is removed, because every checked file has afile_id.Entry.file_idis declared indexed (production already hasix_entries_file_id).Round form
<bdi>for right-to-left names.Source links
gist.github.comlinks are rewritten to/raw; other links are fetched as given (Montage File List URL image load interface is broken #208).Round sources store a fingerprint of the check (
check_id), not the token; a new check of the same source reuses the round source.Deploy:
deploy.shstops before building if a required envvar is missing. deployment.md has the folder rules, troubleshooting and rollback notes, and docs/api.md the new endpoints.Behaviour changes
file_id.file_idin a number format (1.49673E+08,149,673,020) block until the column holds plain whole numbers.Choices made in this PR (easy to change)
entries.namehas a unique index. Without the index both rows can be stored, and the lookup would be a full table scan.Open questions (not changed here)
add_roundwithoutimportstill creates an empty round, for API and script use only (the form always sendsimport). Requireimportwhen the campaign has no rounds yet?entries.namefrom the database instead of assumingutf8mb4_unicode_ci?Known limits
entries.name(Category imports time out on production: every file-name lookup reads the whole entries table #650). A file stored before 2026-05-31 (nofile_id) gets a secondentriesrow when imported again. This is accepted until the backfill (Backfill file_id for entries imported before #505 migration #513)./v1/entry/<name>fails for names stored twice.file_idof a deleted file with the same name (thefiletable keeps one row per name; a deleted file keeps its row, markedfile_deleted) [not verified]. A storedfile_idwould then point to a different image after a delete and re-upload under the same name.MONTAGE_ENV=dev, lookups through production's public utils endpoint) there is no lookup byfile_id. Rows with afile_idare compared by name, and renames are not detected.Tests
pytest montage/tests/gives 158 passed, 3 xfailed (master: 56 passed, 1 xfailed). New or extended:test_import_check.py,test_loaders.py,test_import_entries.py. A pr-check panel review (2026-10-06) led to fixes, each with a failing test first: 24 of the 28 tests added for them fail without the fix; the other 4 guard behaviour that was already correct. Later tests cover reason codes per row and per source error, the clash with existing names (app level, the database comparison, and the skip without a unique index), bare file IDs, and a live-replica test of the page/revision ID query (xfail off Toolforge).prettier --check src/,eslint src/andnpm run buildpass. The Cypress spec asserts the save request, a double click saving once, and a refused import keeping the form open. It has not been run (not in CI).utf8mb4_unicode_ci, е/ё, upper/lower case, accents and ß/ss each compare equal, and so do names that differ only in an emoji (this collation gives every emoji the same weight). Different names stay different.…71Аvs…71а); that led to thesame_name_existingcheck. A File List of file IDs was blocked as "ambiguous" before dcc90b1; after it, a quick test worked.After deploy: checks on montage-beta
filename,file_id, one id changed: Check shows the row as blocking and Save is disabled. Fix it, check again, Save: the round exists with its files, and the entries carry the ids.file_id) shows "Renamed on Commons"; a file new to Montage is stored under its current name.Церковь_Богоявления,_Вышний_Волочек.jpgandЦерковь_Богоявления,_Вышний_Волочёк.jpg→ both rows block, side by side. Then a category containing both: the first is imported, the others left out with a warning, no 500.filenamecolumn and other columns imports by name.Deploying to production
MONTAGE_IMPORT_CHECK_PATHmust exist on themontagetool before this runs there: Montage refuses to start without it (production went down on 2026-10-09 when this branch was deployed by mistake without it). Steps are in deployment.md.Written with Claude (Claude Code) on behalf of @lgelauff.