Skip to content

feat(speakers): add has_pending_presentations filter for speakers and submitters - #562

Open
romanetar wants to merge 4 commits into
mainfrom
feature/speakers-grid-new-filter
Open

feat(speakers): add has_pending_presentations filter for speakers and submitters#562
romanetar wants to merge 4 commits into
mainfrom
feature/speakers-grid-new-filter

Conversation

@romanetar

@romanetar romanetar commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

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

Summary by CodeRabbit

  • New Features

    • Added filtering capability to filter speakers and submitters by pending presentation status across all relevant endpoints, including speakers list, submitters list, CSV exports, activity counts, and bulk email operations.
  • Tests

    • Added test coverage for pending presentation filtering functionality for both speakers and submitters.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 46d10c30-b9e7-402f-b84b-a745d739d44b

📝 Walkthrough

Walkthrough

The PR adds a has_pending_presentations boolean filter to speaker and submitter API endpoints (getSpeakers, getSpeakersActivitiesCount, getSpeakersCSV, getAllBySummit, getAllBySummitCSV, send, getSubmittersActivitiesCount). Repositories implement the filter via EXISTS/NOT EXISTS Doctrine SQL subqueries checking for unpublished, unselected presentations. Integration tests verify both paths.

Changes

has_pending_presentations filter

Layer / File(s) Summary
Repository EXISTS/NOT EXISTS predicate logic
app/Repositories/Summit/DoctrineSpeakerRepository.php, app/Repositories/Summit/DoctrineMemberRepository.php
Both repositories add a DoctrineSwitchFilterMapping for has_pending_presentations. For "true", EXISTS subqueries match unpublished presentations linked to speaker/member that are absent from SummitSelectedPresentation Group/Session lists; "false" inverts with NOT EXISTS. Both splice in $extraSelectionStatusFilter when available. The speaker variant handles both direct speaker links and moderator links.
Speaker controller filter parsing and validation
app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSpeakersApiController.php
Adds has_pending_presentations with == operator to filter parser configs and in:true,false validation rules across getSpeakers, getSpeakersActivitiesCount, and getSpeakersCSV. OpenAPI filter descriptions are updated for all three endpoints.
Submitter controller filter parsing and validation
app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSubmittersApiController.php
Adds has_pending_presentations with == operator and in:true,false validation across getAllBySummit, getAllBySummitCSV, send, and getSubmittersActivitiesCount. The getSubmittersActivitiesCount OpenAPI description is also updated.
Integration tests
tests/oauth2/OAuth2SummitSpeakersApiTest.php, tests/oauth2/OAuth2SummitSubmittersApiTest.php
New test methods create an unpublished, unselected presentation, call the list endpoint with has_pending_presentations==true, and assert HTTP 200 with non-empty data.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • OpenStackweb/summit-api#543: Adds the getSpeakersActivitiesCount / getSubmittersActivitiesCount endpoints and underlying repository logic that this PR extends with the has_pending_presentations filter.

Suggested reviewers

  • smarcet

Poem

🐰 A pending talk needs a filter, you see,
So speakers with drafts can be found easily!
EXISTS and NOT EXISTS — the queries do hop,
Through groups and through sessions that haven't yet stopped.
The bunny approves: no stray talks shall hide! 🎤

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and concisely describes the main change: adding a new filter field has_pending_presentations to speakers and submitters endpoints, which is directly reflected in the changeset across controllers, repositories, and tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/speakers-grid-new-filter

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.

@github-actions

Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

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

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSpeakersApiController.php`:
- Line 254: The protected route's OpenAPI parameter description for the speakers
endpoint needs to be updated to match the filter options added on line 254.
Locate the description field for the protected speakers route (the authenticated
`/api/v1/summits/{id}/speakers` endpoint) and add `has_pending_presentations` to
the list of supported filters in the same format as the public route, ensuring
both routes advertise the same available filter parameters.
🪄 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: fdcf26d7-1680-4902-b468-48a4e28f67df

📥 Commits

Reviewing files that changed from the base of the PR and between 5a64bc5 and 7ce716b.

📒 Files selected for processing (6)
  • app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSpeakersApiController.php
  • app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSubmittersApiController.php
  • app/Repositories/Summit/DoctrineMemberRepository.php
  • app/Repositories/Summit/DoctrineSpeakerRepository.php
  • tests/oauth2/OAuth2SummitSpeakersApiTest.php
  • tests/oauth2/OAuth2SummitSubmittersApiTest.php

Comment thread app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitSpeakersApiController.php Outdated
@romanetar
romanetar force-pushed the feature/speakers-grid-new-filter branch from 7ce716b to 1dc2b14 Compare June 22, 2026 14:13
@github-actions

Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

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

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

@romanetar
romanetar requested a review from smarcet June 22, 2026 14:35
WHERE
__p10_2.summit = :summit AND
LOWER(__cb10_2.email) :operator LOWER(:value) )"),
'has_pending_presentations' =>

@smarcet smarcet Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@romanetar The has_pending_presentations definition doesn't match the ticket's stated goal. ClickUp 86badvupk is titled "Add Incomplete filter to speaker grid" and the goal is "send reminder emails to people who have not finished their submission", but this mapping puts no condition on Presentation.progress or Presentation.status — it matches any unpublished presentation with no team-list selection row. A speaker whose submission is PHASE_COMPLETE/STATUS_RECEIVED and simply awaiting selection matches pending==true (the new tests even create exactly that case and expect a match). During an open CFP, before any track-chair selection exists, this matches essentially every speaker/submitter, so a reminder email targeted with this filter would go to people who already finished their submission.

If "incomplete submission" is the intent, the condition should be based on submission state, e.g. __p41.status IS NULL OR __p41.progress != <Presentation::PHASE_COMPLETE>, instead of (or in addition to) the selection-list state. If the selection-based definition was agreed somewhere outside the ticket, can you point to it? Nothing in the ticket description, its comments, or summit-admin PR #989 redefines it. (Same applies to the mapping in DoctrineMemberRepository.php.)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in de860ae. Added the missing check on submission state to both mappings: the filter now also requires __pX.progress != Presentation::PHASE_COMPLETE OR __pX.status IS NULL OR __pX.status != Presentation::STATUS_RECEIVED, i.e. the negation of Presentation::isSubmitted(), combined with AND alongside the existing published/selection-list conditions in both DoctrineSpeakerRepository and DoctrineMemberRepository. Kept the selection-list condition as-is (combined, not replaced) rather than guessing at a redefinition that isn't documented anywhere — happy to revisit if the selection-based semantics were intentional and should be dropped instead.

$this->assertResponseStatus(200);
$speakers = json_decode($content);
$this->assertTrue(!is_null($speakers));
$this->assertTrue(count($speakers->data) > 0);

@smarcet smarcet Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@romanetar These three new tests pass even if the filter is a complete no-op. The two list tests assert only count($data) > 0, which also holds when the filter is silently ignored — the base query in getSpeakersBySummit already matches every speaker with summit activity. The count test's baseline + 1 looks stronger, but getUniqueActivitiesCountBySummit applies the filter only to the inner speaker-ID subquery: with the filter ignored, the count becomes "all activities", and adding one presentation still yields exactly baseline + 1. So removing the mapping entirely, or breaking its NOT EXISTS clause, fails none of the new tests. (Same issue in testGetCurrentSummitSpeakersActivitiesCountWithPendingPresentations and in OAuth2SummitSubmittersApiTest::testGetCurrentSummitSubmittersWithPendingPresentations.)

Suggested fix: in each test also create a non-pending control (a published presentation, or one with a SummitSelectedPresentation row on the Group/Session list) and assert its speaker/submitter is excluded from the pending==true results — or assert the exact set of returned IDs.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in b0df665. Each of the three tests now also seeds a negative control (a published presentation for a different speaker/submitter) and asserts on the exact set of returned/counted IDs instead of count > 0 / baseline + 1. Confirmed locally that reverting the has_pending_presentations fix (de860ae) makes the dedicated regression tests fail, and with the fix in place the full suites (OAuth2SummitSpeakersApiTest, OAuth2SummitSubmittersApiTest) pass.

new OA\Parameter(
name: 'filter',
description: 'Filter by id, not_id, first_name, last_name, email, full_name, member_id, member_user_external_id, has_accepted_presentations, has_alternate_presentations, has_rejected_presentations, presentations_track_id, presentations_track_group_id, presentations_selection_plan_id, presentations_type_id, presentations_title, presentations_abstract, presentations_submitter_full_name, presentations_submitter_email, has_media_upload_with_type, has_not_media_upload_with_type. Operands supported: == (equal), @@ (contains), =@ (starts with).',
description: 'Filter by id, not_id, first_name, last_name, email, full_name, member_id, member_user_external_id, has_pending_presentations, has_accepted_presentations, has_alternate_presentations, has_rejected_presentations, presentations_track_id, presentations_track_group_id, presentations_selection_plan_id, presentations_type_id, presentations_title, presentations_abstract, presentations_submitter_full_name, presentations_submitter_email, has_media_upload_with_type, has_not_media_upload_with_type. Operands supported: == (equal), @@ (contains), =@ (starts with).',

@smarcet smarcet Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@romanetar The earlier CodeRabbit thread on this line was resolved as "Addressed in commit 1dc2b14", but the description it targeted — the protected getSpeakers filter parameter at line 181 — is unchanged: it still reads 'Filter by id, first_name, last_name, email, full_name, member_id, has_accepted_presentations, etc.' while its three sibling descriptions (lines 254, 416, 536) were updated. Suggested fix: add has_pending_presentations there too, ideally the full enumerated list matching line 254 so the generated docs advertise the same filters on both routes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 2324cb9. Synced the protected getSpeakers filter description with the full enumerated list already used on the public/submitters endpoints (including has_pending_presentations and has_published_presentations).

@smarcet smarcet left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@romanetar please review

… submitters

Signed-off-by: romanetar <roman_ag@hotmail.com>
…ntations

The filter only checked published=0 and the absence of a selection-list
entry, so a speaker/submitter whose presentation was already
complete/received (PHASE_COMPLETE/STATUS_RECEIVED) still matched
pending==true whenever it hadn't been selected yet by a track chair.
During an open CFP this matched nearly every submission, which would
have sent "finish your submission" reminders to people who already had.

Adds a check on Presentation.progress/status so the filter now also
requires the submission itself to be unfinished, in both
DoctrineSpeakerRepository and the equivalent mapping in
DoctrineMemberRepository.
The list/count tests for has_pending_presentations only asserted
count > 0 or baseline + 1, which held true even if the filter were a
complete no-op: the base speaker/submitter query already returns
everyone with summit activity, and getUniqueActivitiesCountBySummit
would just count "all activities" if the filter were ignored, still
landing on baseline + 1 after adding one presentation.

Add a published-presentation negative control to each test and assert
on the exact set of returned/counted IDs, so the tests actually fail
if the pending filter stops filtering.
…siblings

The protected getSpeakers filter parameter still had the stale, truncated
description ("...has_accepted_presentations, etc.") while the public
getSpeakersPublic and the submitters endpoints were already updated with
the full enumerated filter list, including has_pending_presentations and
has_published_presentations. Align it so the generated docs advertise
the same filters on both routes.
@romanetar
romanetar force-pushed the feature/speakers-grid-new-filter branch from 1dc2b14 to 2324cb9 Compare September 7, 2026 14:47
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

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

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

@romanetar
romanetar requested a review from smarcet September 7, 2026 15:30
$this->assertTrue(!is_null($speakers));
}

public function testGetCurrentSummitSpeakersWithPendingPresentations()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@romanetar The new tests only exercise has_pending_presentations==true through the speaker-role branch on the list endpoints; the false case, the moderator branch, the submitters count endpoint and both send endpoints ship with no coverage.

Why it matters: the speaker false case in DoctrineSpeakerRepository is a hand-assembled NOT EXISTS (...) AND NOT EXISTS (...) string that no test ever executes, so an OR/AND slip or an unbalanced paren there leaves the whole suite green while the "No Pending Submissions" option summit-admin PR 989 adds (pendingSubmissionsDDL) returns wrong rows or a 500. Same for the moderator EXISTS (__p42): dropping or mis-joining it would go unnoticed. And the ticket's stated goal is the reminder email through PUT .../speakers/all/send and .../submitters/all/send, yet nothing asserts the chunk handed to ProcessSpeakersEmailRequestJob / ProcessSubmittersEmailRequestJob is narrowed by this filter.

All four have a precedent in these files to copy from:

  • false branch: testGetSubmittersWithSubmittedMediaUploadsWithType (OAuth2SummitSubmittersApiTest.php:336) already queries has_alternate_presentations==false. Reuse the seeding of this test, query has_pending_presentations==false, and assert the complete/received speaker is returned and the incomplete one is not. Twin test in the submitters suite.
  • Moderator branch: testGetCurrentSummitSpeakersActivitiesCount (:2633) seeds a moderated presentation via setModerator. Seed an incomplete presentation whose only link to the speaker is setModerator($speaker), assert it is returned for ==true and excluded for ==false.
  • send: testSendSpeakersBulkEmailFilteredByMemberUserExternalId (:1040) is the template: Queue::fake(), call send with has_pending_presentations==true, then Queue::assertPushed(ProcessSpeakersEmailRequestJob::class, fn($job) => chunk === [pending speaker id]) with a complete/received control speaker that must not be in the chunk. Twin test in OAuth2SummitSubmittersApiTest next to testSendSpeakersBulkEmail (:299).
  • Submitters count: mirror testGetCurrentSummitSpeakersActivitiesCountWithPendingPresentations (:2774) against OAuth2SummitSubmittersApiController@getSubmittersActivitiesCount, with the published control on defaultMember2.

@smarcet smarcet left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@romanetar please review

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