Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions routes/taiga_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@
collection_name, delete_key, id, result.deleted_count
)

# When a user story is deleted, also remove all its associated tasks
if event_type == "userstory":
tasks_coll = get_collection(f"taiga_{prj}.tasks")
task_result = tasks_coll.delete_many({"userstory_id": id})
logger.info(
"Cascade-deleted %s task(s) linked to userstory_id=%s in taiga_%s.tasks",
task_result.deleted_count, id, prj,
)

Check warning on line 82 in routes/taiga_routes.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Change this code to not log user-controlled data.

See more on https://sonarcloud.io/project/issues?id=Learning-Dashboard_LD_Connect_Event&issues=AZ2sBVrWQSoJ_LA-Cvhz&open=AZ2sBVrWQSoJ_LA-Cvhz&pullRequest=20

author_login = raw_payload.get("by", {}).get("username", "unknown")
logger.info(
"Notifying LD_EVAL about deleted event: %s for team with external_id: %s with quality_model: %s",
Expand All @@ -81,6 +90,9 @@
)
try:
notify_eval_push(event_type, prj, author_login, quality_model)
# Force a task re-eval when tasks were cascade-deleted with the user story
if event_type == "userstory":
notify_eval_push("task", prj, author_login, quality_model)
except Exception as e:
logger.error("Error notifying LD_EVAL: %s", e)
return jsonify({"error": "Failed to notify LD_EVAL"}), 500
Expand Down
37 changes: 37 additions & 0 deletions tests/test_taiga_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,43 @@ def test_delete_action(self, mock_verify, mock_coll, mock_notify, client):
mock_collection.delete_one.assert_called_once_with({"task_id": 99})
mock_notify.assert_called_once_with("task", "P", "u", None)

@patch("routes.taiga_routes.notify_eval_push")
@patch("routes.taiga_routes.get_collection")
@patch("routes.taiga_routes.verify_taiga_signature", return_value=True)
def test_delete_userstory_cascades_tasks(
self, mock_verify, mock_coll, mock_notify, client
):
payload = {
"type": "userstory",
"action": "delete",
"data": {"id": 55, "project": {"name": "P"}},
"by": {"username": "u"},
}
mock_us_collection = MagicMock()
mock_tasks_collection = MagicMock()

def side_effect(name):
if name == "taiga_P.userstories":
return mock_us_collection
if name == "taiga_P.tasks":
return mock_tasks_collection
return MagicMock()

mock_coll.side_effect = side_effect

resp = client.post(
"/webhook/taiga?prj=P",
data=json.dumps(payload),
content_type="application/json",
headers={"X-TAIGA-WEBHOOK-SIGNATURE": "x"},
)
assert resp.status_code == 200
mock_us_collection.delete_one.assert_called_once_with({"userstory_id": 55})
mock_tasks_collection.delete_many.assert_called_once_with({"userstory_id": 55})
assert mock_notify.call_count == 2
mock_notify.assert_any_call("userstory", "P", "u", None)
mock_notify.assert_any_call("task", "P", "u", None)

@patch("routes.taiga_routes.get_collection")
@patch("routes.taiga_routes.verify_taiga_signature", return_value=True)
def test_delete_no_id_returns_400(self, mock_verify, mock_coll, client):
Expand Down
Loading