Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Tag import: parent errors name their row and report every cycle as a cycle

Open
#892 0 comments 0 reactions 1 assignee View on GitHub

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 prefix Row {row}: when self.row is not None, at a single return. The unknown-parent text after the prefix stays Unknown 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 the any(pk_of[key] != NEW_TAG for key in cycle) condition but keep the rows guard, 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 in self.indexed_actions["_rows_on_a_cycle"], the same channel _build_staging_actions uses for _vacated_pks and _renamed_to_ids. _validate_hierarchy runs before the row actions are built, so the set is complete when _validate_parent reads it.
  • Skip in _validate_parent: return None instead of the unknown-parent error when self.row in indexed_actions.get("_rows_on_a_cycle", set()). The .get default keeps the unit tests that pass a hand-built indexed_actions working.
  • Drop the pk_of parameter of _validate_hierarchy and update its call in generate_actions: nothing in the method reads it after the change, and pylint would flag it as unused. NEW_TAG stays, because _end_state and _build_detach_actions still use it.
  • Docstrings: _validate_hierarchy currently 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_parent doesn't also report them as unknown parents. Add one sentence about the skip to _validate_parent.
  • Contract: import_tags keeps 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 becomes Action 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_once and move its rows into test_generate_actions_parent_cycle_rejected_once, expecting Row 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.
  • New unit tests in test_import_plan.py:
    • A file whose create row, move row and rename row all name tag_100 gets three errors, prefixed Row 1:, Row 2: and Row 3:.
    • A two-tag cycle of new tags plus a third row naming tag_100 gets exactly two errors, the cycle error and the Row 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.
  • New unit test in tests/openedx_tagging/import_export/test_actions.py: a _validate_parent case with row set 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 TestImportAcceptanceScenarios in tests/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_hierarchy docstring.
  • Prior art: TagImportPlan._end_state, _validate_hierarchy and _order_rows_parent_first in src/openedx_tagging/import_export/import_plan.py; ParentCycleError in src/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

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from openedx/openedx-core

All issues in openedx/openedx-core

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.