Tag import: parent errors name their row and report every cycle as a cycle
Maintainers usually reply within 1 day
@ufedaseyeuconsultant is already working on this.
Since Oct 20, 2026.
Assessment
This issue has not been assessed yet.
Description
User Story
As a Platform Administrator re-importing a competency taxonomy through the Studio import wizard, I want each parent error at the Plan step to name the row it comes from and the real problem, in order to correct my file in one pass instead of hunting for the row or adding a row the file already has.
Acceptance Criteria
Every import below is a full replace import, as the Studio wizard always runs it. Every scenario holds whatever the order of the rows in the file. No scenario shows an "Unknown parent tag" error for a tag that has a row in the file.
Scenario: An unknown parent error names its row, not its step in the Plan
Given a taxonomy with tags that the file omits, so the Plan lists their delete steps before the steps for the file's rows
And the taxonomy has no tag "tag_100"
When the admin imports a file whose fourth row has parent_id "tag_100", and no row has the id "tag_100"
Then the Plan step rejects the import with an error containing "Row 4: Unknown parent tag (tag_100). Add a row for the parent to your file."
And the admin cannot proceed
And an export afterward shows the taxonomy unchanged
Scenario: Rows that name the same unknown parent are told apart
Given a taxonomy that has no tag "tag_100"
When the admin imports a file in which row 1 creates a new tag, row 2 moves an existing tag, and row 3 renames a tag's id with previous_id, and all three rows have parent_id "tag_100"
Then the Plan step shows three "Unknown parent tag (tag_100)" errors in the one error block, naming "Row 1", "Row 2" and "Row 3"
Scenario Outline: A cycle made only of new tags gets one parent cycle error
Given a taxonomy that has none of the tags in the file
When the admin imports a file with the rows <rows>
Then the Plan step rejects the import with exactly one error, "<error>"
And the admin cannot proceed
And an export afterward shows the taxonomy unchanged
Examples:
| rows | error |
| new_a with parent_id new_a | Row 1's parent_id (new_a) creates a parent cycle. |
| new_a with parent_id new_b; new_b with parent_id new_a | Row 1's parent_id (new_b) creates a parent cycle. |
| new_b with parent_id new_a; new_a with parent_id new_b | Row 1's parent_id (new_a) creates a parent cycle. |
| new_a with parent_id new_b; new_b with parent_id new_c; new_c with parent_id new_a | Row 1's parent_id (new_b) creates a parent cycle. |
Scenario Outline: A renamed tag and a new tag that name each other get one parent cycle error
Given a taxonomy with tag_1
When the file renames tag_1 to "tag_50" with parent_id "new_x" and creates new_x with parent_id "tag_50", with <first row> first
Then the Plan step rejects the import with exactly one error, "<error>"
Examples:
| first row | error |
| the rename row | Row 1's parent_id (new_x) creates a parent cycle. |
| new_x's row | Row 1's parent_id (tag_50) creates a parent cycle. |
Scenario: An existing tag and a new tag that name each other get one parent cycle error
Given a taxonomy with tag_1
When the file keeps tag_1 with parent_id "new_x" and creates new_x with parent_id "tag_1"
Then the Plan step rejects the import with exactly one error, the parent cycle error on the first of the two rows in the file
Scenario: A row outside the cycle that names a missing parent still gets the unknown parent error
Given a taxonomy that has none of the tags in the file and no tag "tag_100"
When the file's rows 1 and 2 are new_a with parent_id "new_b" and new_b with parent_id "new_a", and row 3 is new_q with parent_id "tag_100"
Then the Plan step shows exactly two errors: "Row 1's parent_id (new_b) creates a parent cycle." and an error containing "Row 3: Unknown parent tag (tag_100). Add a row for the parent to your file."
Scenario: Other parent errors on a cycle's rows are still reported
Given a taxonomy with tag_1, tag_2 under tag_1, and tag_3
When the file swaps the ids of tag_1 and tag_3, the row that gives tag_3 the id "tag_1" says parent_id "tag_2", and tag_2's row still says parent_id "tag_1"
Then the Plan step shows the parent cycle error and, for tag_2's row, the error from #858 saying that tag_1 is renamed to tag_3 and to use tag_3 as the parent_id
Description
"Row N" is counted as in #858: the N-th data row of the file, counting from 1, so in a CSV file it is spreadsheet line N + 1. The #N that the error already shows is the step's number in the Plan, which can differ from the row.
This ticket replaces two behaviors that #858 specifies in its "Additional fix 3":
- A cycle made only of new tags gets one "Unknown parent tag" error.
- A renamed tag and a new tag that name each other get an extra "Unknown parent tag" error when the new tag's row comes first.
After this change, every parent cycle gets the parent cycle error, and "Unknown parent tag" appears only for a parent that has neither a row nor an existing tag.
Technical Details
This section is background and a suggested approach, not the source of truth. The User Story and Acceptance Criteria are what must be true when the work is done.
In short
Naming the row in the unknown-parent error. The plan gives each action it builds from a row that row's 1-based position among the file's data rows. The stale-parent error from #858 already uses that position for its "Row N: " prefix, but the "Unknown parent tag" error, built in the same parent validation, ignores it. The change applies the "Row N: " prefix once, at the end of that validation, to whichever of the two parent errors it returns, so the two errors cannot drift apart again. Steps that no row describes (deletes, the "move to the root first" steps, and the delete phase's promotions to the root) have no row and never validate a parent, so they are unaffected.
Reporting every cycle as a cycle. Before it builds any row's action, the plan computes the tag tree as it will be after the import and walks that tree to find parent cycles. Today the walk deliberately skips a cycle made only of new tags and leaves it to the per-row "Unknown parent tag" check. The change removes that exception, so every cycle that has a row in the file gets the existing parent cycle error once, on the cycle's first row in the file.
Not reporting a cycle's rows as unknown parents. The plan builds each row after the row that creates or renames-in its parent, so that the per-row check finds the parent among the actions built so far. A cycle has no such order: one of its rows is always built before the row that creates its parent. When that parent is a new tag, the per-row check doesn't find it and reports "Unknown parent tag", although the file has a row for it. A renamed-in parent is found anyway, because the plan records every rename's new id up front, and an existing parent is found in the database. That is why only cycles through a new tag show the extra error. The fix has the cycle check record the rows of every cycle it reports, and has the per-row check skip the "Unknown parent tag" error for those rows. Skipping it hides no real problem, because every parent on a cycle is a tag of the final tree. Only that one error is skipped: the stale-parent error from #858 is still reported for a row on a cycle.
Implementation specifics
- Row prefix in
ImportAction._validate_parent(src/openedx_tagging/import_export/actions.py): build either message, then prefixRow {row}:whenself.row is not None, at a single return. The unknown-parent text after the prefix staysUnknown parent tag ({parent_id}). Add a row for the parent to your file. - Every cycle in
TagImportPlan._validate_hierarchy(src/openedx_tagging/import_export/import_plan.py): remove theany(pk_of[key] != NEW_TAG for key in cycle)condition but keep therowsguard, so a cycle with no row in the file is still not reported. - Cycle rows recorded: add each reported cycle's rows, as 1-based numbers like
ImportAction.row, to a set stored inself.indexed_actions["_rows_on_a_cycle"], the same channel_build_staging_actionsuses for_vacated_pksand_renamed_to_ids._validate_hierarchyruns before the row actions are built, so the set is complete when_validate_parentreads it. - Skip in
_validate_parent: return None instead of the unknown-parent error whenself.row in indexed_actions.get("_rows_on_a_cycle", set()). The.getdefault keeps the unit tests that pass a hand-builtindexed_actionsworking. - Drop the
pk_ofparameter of_validate_hierarchyand update its call ingenerate_actions: nothing in the method reads it after the change, and pylint would flag it as unused.NEW_TAGstays, because_end_stateand_build_detach_actionsstill use it. - Docstrings:
_validate_hierarchycurrently says that a cycle made only of new tags is left to_validate_parent. Make it say that every cycle is reported once, on its first row, and that its rows are recorded so_validate_parentdoesn't also report them as unknown parents. Add one sentence about the skip to_validate_parent. - Contract:
import_tagskeeps its signature and return value. A file that was rejected before is still rejected; only the error text differs. No migration. - Two existing tests assert the old behavior and change, both in
tests/openedx_tagging/import_export/test_import_plan.py:- In the first data set of
test_plan, the expected line becomesAction error in 'update_parent' (#5): Row 4: Unknown parent tag (tag_100). Add a row for the parent to your file. - Delete
test_generate_actions_cycle_of_new_tags_reports_an_unknown_parent_onceand move its rows intotest_generate_actions_parent_cycle_rejected_once, expectingRow 1's parent_id (new_b). Add data sets to that test for a new tag that names itself, a three-tag cycle of new tags, and the renamed-plus-new cycle in both row orders.
- In the first data set of
- New unit tests in
test_import_plan.py:- A file whose create row, move row and rename row all name
tag_100gets three errors, prefixedRow 1:,Row 2:andRow 3:. - A two-tag cycle of new tags plus a third row naming
tag_100gets exactly two errors, the cycle error and theRow 3:unknown-parent error. - The tag_1/tag_3 swap with a cycle through tag_2 gets the cycle error and the stale-parent error.
- A file whose create row, move row and rename row all name
- New unit test in
tests/openedx_tagging/import_export/test_actions.py: a_validate_parentcase withrowset that asserts the prefix. The existing case there builds its action without a row, so its expected text stays the same. - Acceptance tests: add one test per scenario to
TestImportAcceptanceScenariosintests/openedx_tagging/import_export/test_api.py, using_assert_rejected(..., error_count=...)across_orders. - Out of scope: the duplicate-value errors, which also name no row, and the deleted-parent check in #893.
Files to create and modify
Modified files
| File | Nature of modification |
|---|---|
| src/openedx_tagging/import_export/actions.py | _validate_parent: row prefix for both parent errors, skip for cycle rows |
| src/openedx_tagging/import_export/import_plan.py | _validate_hierarchy: report every cycle, record cycle rows, drop pk_of, docstring |
| tests/openedx_tagging/import_export/test_import_plan.py | update test_plan and the cycle test; new unit tests |
| tests/openedx_tagging/import_export/test_actions.py | _validate_parent case with row set |
| tests/openedx_tagging/import_export/test_api.py | acceptance scenarios in TestImportAcceptanceScenarios |
Context
- Builds on the #858 pull request; start after it merges.
- Land this ticket before #893, which follows this ticket's "Row N:" form and edits the same
_validate_hierarchydocstring. - Prior art:
TagImportPlan._end_state,_validate_hierarchyand_order_rows_parent_firstinsrc/openedx_tagging/import_export/import_plan.py;ParentCycleErrorinsrc/openedx_tagging/import_export/exceptions.py. - ADR:
docs/openedx_tagging/decisions/0010-mutable-tag-external-id.rst. - #673 (previous_id); epic #720.
- Dominant language
- Python
- Stars
- 10
- Forks
- 33
- Avg merge
- 2d 23h
- Merged PRs (30d)
- 22
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from openedx/openedx-core
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
openedx/openedx-core#831 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
openedx/openedx-core#827 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 20/100
openedx/openedx-core#895 ·
Maintainers usually reply within 1 day
-
[Draft] Refactor: Consolidate taxonomy, tag and object-tag writes into one modulePossibly taken @jesperhodge claimed this today. Open
openedx/openedx-core#894 · 1 assignee ·
Maintainers usually reply within 1 day
-
Tag import: reject a parent_id that names a tag the full replace import deletesPossibly taken @ufedaseyeuconsultant claimed this today. Open
openedx/openedx-core#893 · 1 assignee ·
Maintainers usually reply within 1 day
All issues in openedx/openedx-core
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 67/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
pgvector/pgvector-python#169 ·
-
ChaosEngine: backup-remote doctor row passes while consumer mode hides bot memory from gitPossibly taken @MohabMohie claimed this today. Openagentic
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
ShaftHQ/SHAFT_ENGINE#7263 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 87/100
huggingface/smolagents#3045 ·
Maintainers usually reply within 1 day