From 5f91a44c114595f20b1afa585888bb8c03ec0800 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Mar 2026 23:58:00 +0000 Subject: [PATCH 1/3] Initial plan From ea8184cef9b9fa9b4c6bb78ae1378e990655f33a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Mar 2026 00:07:40 +0000 Subject: [PATCH 2/3] Fix: update POC objective instead of creating duplicate when condition is moved Agent-Logs-Url: https://github.com/canonical/dashboard/sessions/2c26ce3b-f7d0-4e80-a193-f0df5e5f0201 Co-authored-by: dwilding <1141260+dwilding@users.noreply.github.com> --- dashboard/framework/models.py | 35 +++++++++++++++++++------ dashboard/framework/test_models.py | 41 ++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/dashboard/framework/models.py b/dashboard/framework/models.py index f2741cc..34dcd4f 100644 --- a/dashboard/framework/models.py +++ b/dashboard/framework/models.py @@ -168,16 +168,35 @@ def save(self, *args, **kwargs): ProjectObjectiveCondition, ) # avoids circular import - super().save(*args, **kwargs) + # Check if the objective is being changed for an existing condition + old_objective_id = None + if self.pk: + try: + old_objective_id = Condition.objects.values_list( + "objective_id", flat=True + ).get(pk=self.pk) + except Condition.DoesNotExist: + pass - projectobjectives = ProjectObjective.objects.filter(objective=self.objective) + super().save(*args, **kwargs) - for projectobjective in projectobjectives: - ProjectObjectiveCondition.objects.get_or_create( - project=projectobjective.project, - objective=projectobjective.objective, - condition=self, - ) + if old_objective_id is not None and old_objective_id != self.objective_id: + # Condition moved to a new objective: update existing POCs to the new + # objective instead of creating new ones, so that status is preserved + ProjectObjectiveCondition.objects.filter( + condition=self, objective_id=old_objective_id + ).update(objective=self.objective) + else: + # New condition or objective unchanged: propagate to all existing + # ProjectObjectives + projectobjectives = ProjectObjective.objects.filter(objective=self.objective) + + for projectobjective in projectobjectives: + ProjectObjectiveCondition.objects.get_or_create( + project=projectobjective.project, + objective=projectobjective.objective, + condition=self, + ) class Meta: ordering = ["objective__name", "level__value"] diff --git a/dashboard/framework/test_models.py b/dashboard/framework/test_models.py index 0647fff..a8942a1 100644 --- a/dashboard/framework/test_models.py +++ b/dashboard/framework/test_models.py @@ -153,3 +153,44 @@ def test_new_objective_means_new_commitments( assert project.commitment_set.count() == 2 assert work_cycle.commitment_set.count() == 2 + + +@pytest.mark.django_db +def test_moving_condition_updates_poc_not_creates_duplicate( + project, objective, objective_group, level +): + """Moving a condition to a different objective should update the existing POC, + preserving the status, rather than creating a duplicate POC.""" + + objective_b = Objective.objects.create( + name="test_objective_b", group=objective_group, weight=1 + ) + + condition = Condition.objects.create( + name="test_condition", level=level, objective=objective + ) + + # Set the POC's status to "DO" before moving + poc = ProjectObjectiveCondition.objects.get( + project=project, objective=objective, condition=condition + ) + poc.status = "DO" + poc.save() + + # Move the condition to objective_b + condition.objective = objective_b + condition.save() + + # There should be exactly one POC for this condition across all projects + all_pocs = ProjectObjectiveCondition.objects.filter(condition=condition) + assert all_pocs.count() == 1, ( + f"Expected 1 POC but got {all_pocs.count()}. " + "Moving a condition should update the existing POC, not create a duplicate." + ) + + # The POC should point to objective_b + updated_poc = all_pocs.first() + assert updated_poc.objective == objective_b + + # The POC should retain its completion status + assert updated_poc.status == "DO" From 8c07a3725e33831ddcec37fb5d1eb0552e74e33f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 05:54:15 +0000 Subject: [PATCH 3/3] Incorporate next-release changes: Commitment backfill and backfill test Agent-Logs-Url: https://github.com/canonical/dashboard/sessions/e1835332-0813-480b-9f68-b60b5a4b90f1 Co-authored-by: dwilding <1141260+dwilding@users.noreply.github.com> --- dashboard/framework/models.py | 8 ++++++++ dashboard/framework/test_models.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/dashboard/framework/models.py b/dashboard/framework/models.py index 34dcd4f..0c09bf5 100644 --- a/dashboard/framework/models.py +++ b/dashboard/framework/models.py @@ -166,6 +166,7 @@ def save(self, *args, **kwargs): from projects.models import ( ProjectObjective, ProjectObjectiveCondition, + Commitment ) # avoids circular import # Check if the objective is being changed for an existing condition @@ -197,6 +198,13 @@ def save(self, *args, **kwargs): objective=projectobjective.objective, condition=self, ) + for work_cycle in WorkCycle.objects.all(): + Commitment.objects.get_or_create( + work_cycle=work_cycle, + project=projectobjective.project, + objective=projectobjective.objective, + level=self.level, + ) class Meta: ordering = ["objective__name", "level__value"] diff --git a/dashboard/framework/test_models.py b/dashboard/framework/test_models.py index a8942a1..167b0ea 100644 --- a/dashboard/framework/test_models.py +++ b/dashboard/framework/test_models.py @@ -155,6 +155,35 @@ def test_new_objective_means_new_commitments( assert work_cycle.commitment_set.count() == 2 +@pytest.mark.django_db +def test_new_condition_with_new_level_backfills_commitment( + project, objective, condition, work_cycle +): + # A new condition at a new level should create a matching commitment + # for existing rows. + + assert ( + Commitment.objects.filter( + project=project, objective=objective, work_cycle=work_cycle + ).count() + == 1 + ) + + new_level = Level.objects.create(name="test_level_2", value=2) + Condition.objects.create( + name="test_condition_2", objective=objective, level=new_level + ) + + # Expected behaviour: creating a new condition/level backfills + # commitments. + assert Commitment.objects.filter( + project=project, + objective=objective, + work_cycle=work_cycle, + level=new_level, + ).exists() + + @pytest.mark.django_db def test_moving_condition_updates_poc_not_creates_duplicate( project, objective, objective_group, level