Skip to content

feat: remove selection plan ref from presentation when a category is removed from a category group#522

Open
romanetar wants to merge 2 commits intomainfrom
feature/validate-category-removal-from-group
Open

feat: remove selection plan ref from presentation when a category is removed from a category group#522
romanetar wants to merge 2 commits intomainfrom
feature/validate-category-removal-from-group

Conversation

@romanetar
Copy link
Copy Markdown
Collaborator

@romanetar romanetar commented Mar 31, 2026

ref https://app.clickup.com/t/86b8rp3vh

Summary by CodeRabbit

  • Bug Fixes

    • Presentations now have their selection-plan association cleared only when a track is truly no longer covered by any category group.
  • Refactor

    • Category group ↔ selection plan relationships now synchronize more reliably to prevent inconsistent associations.
  • Tests

    • Added/updated tests verifying selection-plan cleanup on disassociation and preservation when another group still covers the track.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

Adds a bidirectional ManyToMany between SelectionPlan and PresentationCategoryGroup, a PresentationCategory helper to fetch presentations by selection-plan IDs, service logic to conditionally clear presentations' selection plans when disassociating tracks, and tests covering both clearing and preservation cases.

Changes

Cohort / File(s) Summary
Bidirectional relationship & sync
app/Models/Foundation/Summit/SelectionPlan.php, app/Models/Foundation/Summit/Events/Presentations/PresentationCategoryGroup.php
Made SelectionPlan⇄PresentationCategoryGroup association bidirectional (inversedBy: 'selection_plans'). Added selection_plans collection and helper methods (getSelectionPlanIds(), addSelectionPlan(), removeSelectionPlan()); SelectionPlan now updates the inverse side in addTrackGroup()/removeTrackGroup().
Presentation query helper
app/Models/Foundation/Summit/Events/Presentations/PresentationCategory.php
Added getPresentationsBySelectionPlanIds(array $selection_plan_ids): array returning presentations for this category filtered by provided selection plan IDs (returns empty array for empty input).
Service logic change
app/Services/Model/Imp/PresentationCategoryGroupService.php
disassociateTrack2TrackGroup() now fetches presentations for the group's selection plan IDs and clears each presentation's selection plan only if no other category group in the same selection plan still contains the track, before removing the category from the group.
Tests (clear & preserve cases)
tests/oauth2/OAuth2TrackGroupsApiTest.php
Updated testDisassociateTrack2TrackGroup() to assert presentations are cleared after disassociation and added testDisassociateTrackFromTrackGroupPreservesSelectionPlanWhenTrackReachableViaAnotherGroup() to verify selection plans are preserved when another group still covers the track.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Service as PresentationCategoryGroupService
    participant Category as PresentationCategory
    participant Repo as DoctrineRepo
    participant Presentation

    Client->>Service: disassociateTrack2TrackGroup(groupId, trackId)
    Service->>Category: getPresentationsBySelectionPlanIds(selectionPlanIds)
    Category->>Repo: execute DQL (filter by category and selection_plan IN ids)
    Repo-->>Category: presentations[]
    Category-->>Service: presentations[]
    
    loop for each presentation
        Service->>Presentation: inspect presentation.selectionPlan
        Service->>Service: check other groups for same track within that selection plan
        alt no other group covers track
            Service->>Presentation: clearSelectionPlan()
            Presentation-->>Service: selection plan cleared
        else other group covers track
            Presentation-->>Service: left intact
        end
    end

    Service->>Service: removeCategory(track) from group
    Service-->>Client: disassociation complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I nibble at mappings, neat and small,
I link the plans and groups in a gentle sprawl,
Presentations checked, some set free,
Others stay snug where they must be,
A happy hop — relations all.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main change: removing selection plan references from presentations when categories are removed from groups, which aligns with the core functionality added across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/validate-category-removal-from-group

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

➡️ https://OpenStackweb.github.io/summit-api/openapi/pr-522/

This page is automatically updated on each push to this PR.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/Services/Model/Imp/PresentationCategoryGroupService.php`:
- Around line 255-261: Check whether the track is actually in the group first
using $track_group->hasCategory($track); only proceed to removeCategory($track)
when true, then fetch presentations by the selection plan ids and for each
presentation verify its current selection plan still contains the track (e.g.,
$presentation->getSelectionPlan()->hasTrack($track)); call
$presentation->clearSelectionPlan() only for presentations whose current plan no
longer has the track, and finally call $track_group->removeCategory($track) (or
call removeCategory before checking presentations if you prefer but still gate
the clearing by hasTrack on each presentation's current plan).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9331089e-20a7-4123-9994-f1a4a80493de

📥 Commits

Reviewing files that changed from the base of the PR and between b2e5f45 and f0235fc.

📒 Files selected for processing (5)
  • app/Models/Foundation/Summit/Events/Presentations/PresentationCategory.php
  • app/Models/Foundation/Summit/Events/Presentations/PresentationCategoryGroup.php
  • app/Models/Foundation/Summit/SelectionPlan.php
  • app/Services/Model/Imp/PresentationCategoryGroupService.php
  • tests/oauth2/OAuth2TrackGroupsApiTest.php

…removed from a category group

Signed-off-by: romanetar <roman_ag@hotmail.com>
@romanetar romanetar force-pushed the feature/validate-category-removal-from-group branch from f0235fc to d2bd7d4 Compare March 31, 2026 17:55
@github-actions
Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

➡️ https://OpenStackweb.github.io/summit-api/openapi/pr-522/

This page is automatically updated on each push to this PR.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/Services/Model/Imp/PresentationCategoryGroupService.php (1)

265-270: Prefix unused parameter with underscore to suppress warning.

The $key parameter is required by Doctrine's exists() callback signature but is unused. Use $_ or $_key to signal intent.

♻️ Suggested fix
-                $track_reachable_via_another_group = $selection_plan->getCategoryGroups()->exists(
-                    function ($key, $group) use ($track, $track_group) {
+                $track_reachable_via_another_group = $selection_plan->getCategoryGroups()->exists(
+                    function ($_, $group) use ($track, $track_group) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Services/Model/Imp/PresentationCategoryGroupService.php` around lines 265
- 270, The callback passed to selection_plan->getCategoryGroups()->exists is
using an unused $key parameter which triggers a warning; rename it to $_ or
$_key (e.g., change function ($key, $group) to function ($_key, $group) or
function ($_ , $group)) to indicate it's intentionally unused and suppress the
warning while keeping the existing logic that checks $group->getId() !==
$track_group->getId() && $group->hasCategory($track->getId()).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/Services/Model/Imp/PresentationCategoryGroupService.php`:
- Around line 265-270: The callback passed to
selection_plan->getCategoryGroups()->exists is using an unused $key parameter
which triggers a warning; rename it to $_ or $_key (e.g., change function ($key,
$group) to function ($_key, $group) or function ($_ , $group)) to indicate it's
intentionally unused and suppress the warning while keeping the existing logic
that checks $group->getId() !== $track_group->getId() &&
$group->hasCategory($track->getId()).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6aee6a81-9a01-4434-80fd-a70a7722c5f4

📥 Commits

Reviewing files that changed from the base of the PR and between f0235fc and d2bd7d4.

📒 Files selected for processing (5)
  • app/Models/Foundation/Summit/Events/Presentations/PresentationCategory.php
  • app/Models/Foundation/Summit/Events/Presentations/PresentationCategoryGroup.php
  • app/Models/Foundation/Summit/SelectionPlan.php
  • app/Services/Model/Imp/PresentationCategoryGroupService.php
  • tests/oauth2/OAuth2TrackGroupsApiTest.php
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/oauth2/OAuth2TrackGroupsApiTest.php
  • app/Models/Foundation/Summit/Events/Presentations/PresentationCategory.php
  • app/Models/Foundation/Summit/SelectionPlan.php
  • app/Models/Foundation/Summit/Events/Presentations/PresentationCategoryGroup.php

Copy link
Copy Markdown
Collaborator

@smarcet smarcet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@romanetar please review

Signed-off-by: romanetar <roman_ag@hotmail.com>
@romanetar romanetar requested a review from smarcet April 10, 2026 18:59
@github-actions
Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

➡️ https://OpenStackweb.github.io/summit-api/openapi/pr-522/

This page is automatically updated on each push to this PR.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
app/Services/Model/Imp/PresentationCategoryGroupService.php (1)

255-277: ⚠️ Potential issue | 🟠 Major

Guard disassociation side effects when the track is not in the target group.

This block clears presentation selection plans before confirming the track is actually part of $track_group. If the track was never in that group, this can still clear plans incorrectly.

Proposed fix
-            $presentations = $track->getPresentationsBySelectionPlanIds($track_group->getSelectionPlanIds());
-
-            // Only clear the selection plan if no other category group in that plan
-            // still contains $track. A selection plan can span multiple groups, so
-            // removing $track from $track_group does not necessarily invalidate the
-            // plan for presentations under $track — another group may still cover it.
-            // We exclude $track_group from the check because removeCategory has not
-            // been called yet at this point.
-            foreach ($presentations as $presentation) {
-                $selection_plan = $presentation->getSelectionPlan();
-                if (is_null($selection_plan)) continue;
-                $track_reachable_via_another_group = $selection_plan->getCategoryGroups()->exists(
-                    function ($key, $group) use ($track, $track_group) {
-                        return $group->getId() !== $track_group->getId()
-                            && $group->hasCategory($track->getId());
-                    }
-                );
-                if (!$track_reachable_via_another_group) {
-                    $presentation->clearSelectionPlan();
-                }
-            }
-
-            $track_group->removeCategory($track);
+            if ($track_group->hasCategory($track->getId())) {
+                $presentations = $track->getPresentationsBySelectionPlanIds($track_group->getSelectionPlanIds());
+
+                // Only clear the selection plan if no other category group in that plan
+                // still contains $track. A selection plan can span multiple groups, so
+                // removing $track from $track_group does not necessarily invalidate the
+                // plan for presentations under $track — another group may still cover it.
+                // We exclude $track_group from the check because removeCategory has not
+                // been called yet at this point.
+                foreach ($presentations as $presentation) {
+                    $selection_plan = $presentation->getSelectionPlan();
+                    if (is_null($selection_plan)) continue;
+                    $track_reachable_via_another_group = $selection_plan->getCategoryGroups()->exists(
+                        function ($key, $group) use ($track, $track_group) {
+                            return $group->getId() !== $track_group->getId()
+                                && $group->hasCategory($track->getId());
+                        }
+                    );
+                    if (!$track_reachable_via_another_group) {
+                        $presentation->clearSelectionPlan();
+                    }
+                }
+
+                $track_group->removeCategory($track);
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/Services/Model/Imp/PresentationCategoryGroupService.php` around lines 255
- 277, Before iterating presentations, verify the track is actually part of the
target group to avoid clearing selection plans when it never belonged there: add
a guard that checks $track_group->hasCategory($track->getId()) (or equivalent)
and return/skip the disassociation logic if false; keep the existing logic using
$track->getPresentationsBySelectionPlanIds(...), the loop that checks
$selection_plan->getCategoryGroups()->exists(...), and the final
$track_group->removeCategory($track) unchanged but only executed after the
membership guard passes.
🧹 Nitpick comments (1)
tests/oauth2/OAuth2TrackGroupsApiTest.php (1)

419-429: Drop unused $response assignment in preserve-path test.

$response is assigned but never used; removing it keeps the test cleaner without changing behavior.

Proposed cleanup
-        $response = $this->action(
+        $this->action(
             "DELETE",
             "OAuth2PresentationCategoryGroupController@disassociateTrack2TrackGroup",
             $params,
             [],
             [],
             [],
             $headers
         );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/oauth2/OAuth2TrackGroupsApiTest.php` around lines 419 - 429, The test
assigns $response from the call to action(...) in
OAuth2TrackGroupsApiTest::preserve-path test but never uses it; remove the
unused $response variable and invoke action(...) directly (or just keep the call
without assignment) for the DELETE to
OAuth2PresentationCategoryGroupController@disassociateTrack2TrackGroup, leaving
the subsequent $this->assertResponseStatus(204) intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@app/Services/Model/Imp/PresentationCategoryGroupService.php`:
- Around line 255-277: Before iterating presentations, verify the track is
actually part of the target group to avoid clearing selection plans when it
never belonged there: add a guard that checks
$track_group->hasCategory($track->getId()) (or equivalent) and return/skip the
disassociation logic if false; keep the existing logic using
$track->getPresentationsBySelectionPlanIds(...), the loop that checks
$selection_plan->getCategoryGroups()->exists(...), and the final
$track_group->removeCategory($track) unchanged but only executed after the
membership guard passes.

---

Nitpick comments:
In `@tests/oauth2/OAuth2TrackGroupsApiTest.php`:
- Around line 419-429: The test assigns $response from the call to action(...)
in OAuth2TrackGroupsApiTest::preserve-path test but never uses it; remove the
unused $response variable and invoke action(...) directly (or just keep the call
without assignment) for the DELETE to
OAuth2PresentationCategoryGroupController@disassociateTrack2TrackGroup, leaving
the subsequent $this->assertResponseStatus(204) intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 411e0f77-a95e-4e69-8a09-eae205d2f8cc

📥 Commits

Reviewing files that changed from the base of the PR and between d2bd7d4 and f77b1d4.

📒 Files selected for processing (2)
  • app/Services/Model/Imp/PresentationCategoryGroupService.php
  • tests/oauth2/OAuth2TrackGroupsApiTest.php

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants