Skip to content

feat(tickets): getMyCommentedTicketsForRange for Progress "Supported"#3649

Merged
marcelfolaron merged 9 commits into
masterfrom
feat/commented-tickets-range
Jul 20, 2026
Merged

feat(tickets): getMyCommentedTicketsForRange for Progress "Supported"#3649
marcelfolaron merged 9 commits into
masterfrom
feat/commented-tickets-range

Conversation

@gloriafolaron

@gloriafolaron gloriafolaron commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Adds a new Tickets service/repository pathway to support Mobile Progress → "Supported" — tickets a user commented on within a date range that they don't own.

What

  • Repo getTicketIdsCommentedByUser(userId, from, to) — distinct ticket ids the user commented on in the range (zp_comment, module=ticket), via pluck.
  • Repo getTicketsByIdsWithinProjects(ids, projectIds) — ticket rows for those ids, constrained to the given projects, with an explicit deterministic ORDER BY.
  • Service getMyCommentedTicketsForRange(userId, from, to) (@api, VIEW) — resolves the commented ids, scopes them to the projects the user can access (getProjectsUserHasAccessTo), then drops tickets the user is the editor of ("your work", not support).

Access / security

  • Scoping by accessible projects (not editor/collaborator) is deliberate: you comment on others' work without being its editor or collaborator, so an editor/collaborator filter silently dropped most supported tickets. Project access is the correct, still-safe boundary — a historical comment can't surface a ticket in a project you no longer see.
  • IDOR guard: a non-admin passing someone else's userId is forced back to the session user (mirrors Projects::getProjectsUserHasAccessTo()).

Notes

  • Returns raw ticket rows (id, headline, projectName, editorId, status, …) — they do not carry the resolved statusLabel/statusClass/statusType that getAll*UserTickets attach; the docblock says so.
  • Unit tests cover ownership exclusion, project scoping, and the empty-access short-circuit. Pint + phpstan + unit green.

🤖 Generated with Claude Code

Mobile Progress "Supported" honors invisible/presence labor — work you did on
someone else's arc. Commenting on others' tickets is one of its clearest
signals, and there was no way to query it (comments are only fetchable per
ticket). There is no zp_watchers table, so "watching" isn't available; comments
are.

- Repo: getTicketIdsCommentedByUser(userId, from, to) — distinct ticket ids the
  user commented on in the range (zp_comment, module=ticket). Access-agnostic on
  its own; the caller MUST intersect with an access-scoped set.
- Service: getMyCommentedTicketsForRange(userId, from, to) (@api, VIEW) —
  intersects those ids with simpleTicketQuery's access-scoped universe (project
  access + collaborator clause), then drops tickets the user is the editor of
  ("your work", not support). So a historical comment can never surface a ticket
  the user can no longer see (no IDOR), and only genuinely-others' work returns.

Returns the same ticket-row shape as the other user-ticket queries. Pint +
php -l clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NtfhjPwUEEHJb4Jf7714eR
Copilot AI review requested due to automatic review settings July 14, 2026 16:00
@gloriafolaron
gloriafolaron requested a review from a team as a code owner July 14, 2026 16:00
@gloriafolaron
gloriafolaron requested review from marcelfolaron and removed request for a team July 14, 2026 16:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a new Tickets service/repository pathway to support Mobile Progress → “Supported” by returning tickets a user commented on within a date range, while attempting to keep results access-safe by intersecting with an access-scoped ticket set and excluding tickets the user “owns”.

Changes:

  • Add repository query to fetch distinct ticket IDs the user commented on within an inclusive [from, to] date range.
  • Add service API getMyCommentedTicketsForRange to intersect commented ticket IDs with an access-scoped ticket universe and exclude editor-owned tickets.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
app/Domain/Tickets/Services/Tickets.php Adds getMyCommentedTicketsForRange API to compute “Supported” tickets from comments + access-scoped intersection + ownership exclusion.
app/Domain/Tickets/Repositories/Tickets.php Adds getTicketIdsCommentedByUser primitive to retrieve distinct commented ticket IDs for a user within a date range.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/Domain/Tickets/Services/Tickets.php Outdated
Comment on lines +2496 to +2501
// Access-scoped universe — never returns a ticket outside the user's
// project access, so intersecting against it makes the comment lookup
// safe regardless of what was commented on historically.
$accessible = $this->ticketRepository->simpleTicketQuery($userId, null);
if (! is_array($accessible) || empty($accessible)) {
return [];
Comment thread app/Domain/Tickets/Services/Tickets.php Outdated
Comment on lines +2475 to +2476
* @return array<int, array<string, mixed>> ticket rows (id, headline,
* projectName, …), same shape as the other user-ticket queries.
Comment on lines +1896 to +1904
$rows = $this->connection->table('zp_comment')
->select('moduleId')
->distinct()
->where('module', 'ticket')
->where('userId', $userId)
->whereBetween('date', [$fromDate.' 00:00:00', $toDate.' 23:59:59'])
->get();

return array_values(array_unique(array_map(fn ($row) => (int) $row->moduleId, $rows->all())));
Comment on lines +2478 to +2489
#[RequiresPermission(TicketsPermissions::VIEW)]
public function getMyCommentedTicketsForRange(?int $userId = null, ?string $from = null, ?string $to = null): array
{
$userId = $userId ?? (int) session('userdata.id');
if ($userId === 0) {
return [];
}
$from = $from ?: date('Y-m-d');
$to = $to ?: date('Y-m-d');
if ($from > $to) {
[$from, $to] = [$to, $from];
}
@marcelfolaron

Copy link
Copy Markdown
Collaborator

Maintainer review (advisory — merge authority stays with @marcelfolaron / @broskees) · diff read in full at head 374163e

1. Intent: Adds getMyCommentedTicketsForRange(?userId, ?from, ?to) (service) + getTicketIdsCommentedByUser(userId, from, to) (repo), returning tickets the user commented on but does not own within a date range — powering mobile Progress "Supported" (invisible/presence labor on others' arcs). Pairs with #3647.

2. Change requests (the access-scoping design is deliberately careful — these are confirm-before-merge; access-control code raises the bar):

  1. The IDOR guard rests entirely on simpleTicketQuery being the access-scoped universe — this is the load-bearing line, so it must have a test (getMyCommentedTicketsForRange, the intersect against $accessible). The repo method getTicketIdsCommentedByUser is explicitly access-agnostic (comment on any ticket id), so the only thing preventing a historical comment from surfacing a now-inaccessible ticket is the intersection with simpleTicketQuery($userId, null). A test proving user A does not get a ticket in a project they've lost access to (but once commented on) is the required guard — php -l clean is not sufficient for this class.
  2. simpleTicketQuery($userId, null) may be an expensive full access-scan run on every "Supported" call (getMyCommentedTicketsForRange). It pulls the user's entire accessible ticket universe, then intersects in PHP against the (usually small) commented-id set. For a user with access to thousands of tickets this loads them all to filter down to a handful. Consider passing the commented ids down into the query as a whereIn so the DB does the intersection, rather than materializing the whole accessible set in PHP. Correctness is fine; this is a scale flag.
  3. editorId ownership drop — confirm the field is always present on the simpleTicketQuery row shape (if ((string)($ticket['editorId'] ?? '') === (string)$userId)). The ?? '' guard means a row missing editorId is treated as "not yours" and kept. Confirm simpleTicketQuery always selects editorId so a genuinely-owned ticket can't slip into "Supported" because the column wasn't hydrated.
  4. date column filter uses whereBetween('date', [from 00:00:00, to 23:59:59]) (getTicketIdsCommentedByUser) — same lexical Y-m-d assumption as feat(tickets): getMyClosedTicketsForRange for period close-lists #3647. Fine given the callers, worth the same normalization note.

3. Acceptance checklist (must pass before merge):

  1. Cross-user isolation test (blocker): a comment on a ticket in a project the user can no longer access does NOT appear in the result; a comment on an accessible ticket the user does NOT own DOES appear; a comment on the user's OWN ticket does NOT appear (it's "your work," not support).
  2. Confirm simpleTicketQuery selects editorId (ownership filter correctness) and is a LEFT/consistent access scope — the whole safety argument depends on it being the same scoping the open/closed user-ticket lists use.
  3. CI green: Pint + PHPStan level 5 + full unit suite. No schema/API change (additive methods), no migration.

4. CI status: ⚠️ Can't read check-run status this cycle — confirm green on 374163e in the UI; treat any red/skipped job as blocking. This touches ticket visibility across users — missing automated cross-user isolation test = blocker (checklist #1), not optional. The design avoids the getUser-style leak class by construction (intersect-with-access-scope), which is the right instinct — lock it with the test so a future refactor of simpleTicketQuery can't silently break the guard.

Merge-order note: edits the same Tickets/Services/Tickets.php region as #3647 (just below getMyClosedTicketsForRange). No logical conflict, but the second to merge needs a trivial rebase — don't merge the two blind in parallel.

Advisory review only — not an approval, and I am not marking this ready or merging. Merge authority stays with @marcelfolaron / @broskees.

…ator

Copilot review (#3649): intersecting commented ids against simpleTicketQuery
constrained results to tickets the user EDITS or COLLABORATES on — but you
comment on others' work without being either, so most supported tickets were
silently dropped (could return empty even with comments). Now scope commented
ids to the user's accessible PROJECTS (getProjectsUserHasAccessTo) — the correct,
still-safe boundary — then exclude editor-owned tickets. Also: docblock no longer
claims identical shape to getAll*UserTickets (raw rows, no statusLabel), and
getTicketIdsCommentedByUser uses pluck() (drops redundant array_unique).

New repo method getTicketsByIdsWithinProjects fetches the rows by id within the
access-scoped projects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NtfhjPwUEEHJb4Jf7714eR
Copilot AI review requested due to automatic review settings July 14, 2026 20:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

Comment thread app/Domain/Tickets/Services/Tickets.php Outdated
Comment on lines +2486 to +2489
$userId = $userId ?? (int) session('userdata.id');
if ($userId === 0) {
return [];
}
Comment on lines +2468 to +2473
* Access safety: commented ticket ids are constrained to the PROJECTS the
* user can access (getProjectsUserHasAccessTo) — NOT to tickets they edit
* or collaborate on. Commenting on a ticket rarely makes you its editor or
* a collaborator, so scoping by those (as an earlier version did via
* simpleTicketQuery) silently dropped most supported work — it collapsed to
* "commented AND collaborator" and could return empty even when comments
Comment on lines +1919 to +1933
$rows = $this->connection->table('zp_tickets')
->leftJoin('zp_projects', 'zp_tickets.projectId', '=', 'zp_projects.id')
->select(
'zp_tickets.id',
'zp_tickets.headline',
'zp_tickets.projectId',
'zp_tickets.editorId',
'zp_tickets.userId',
'zp_tickets.status',
'zp_tickets.dateToFinish',
'zp_projects.name as projectName',
)
->whereIn('zp_tickets.id', $ticketIds)
->whereIn('zp_tickets.projectId', $projectIds)
->get();
Comment on lines +2483 to +2485
#[RequiresPermission(TicketsPermissions::VIEW)]
public function getMyCommentedTicketsForRange(?int $userId = null, ?string $from = null, ?string $to = null): array
{
… scope

Copilot review (#3649): add unit coverage — editor-owned tickets are excluded,
tickets outside the accessible-project fetch don't appear, and no project access
short-circuits to empty.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NtfhjPwUEEHJb4Jf7714eR
Copilot AI review requested due to automatic review settings July 14, 2026 20:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment on lines +2463 to +2465
* Tickets the user COMMENTED on within [$from, $to] that they do NOT own
* (they're not the assignee/editor) — i.e. work they supported by weighing
* in on someone else's arc. Powers the mobile Progress "Supported" section
@marcelfolaron

Copy link
Copy Markdown
Collaborator

Maintainer re-review (advisory — merge authority stays with @marcelfolaron / @broskees) · re-reviewed at head a68e9d12 (advanced past 374163e) · full diff read

1. Intent: Adds getMyCommentedTicketsForRange (+ repo getTicketIdsCommentedByUser and new getTicketsByIdsWithinProjects) returning tickets the user commented on but does not own, for mobile Progress "Supported."

What changed since my last review — the access model was rewritten, and tests were added. Two substantive changes:

  • Access boundary moved from intersect-with-simpleTicketQuery (editor/collaborator scope) to project-access scoping: getProjectsUserHasAccessTo($userId)getTicketsByIdsWithinProjects($commentedIds, $projectIds), which whereIns both id and projectId. The docblock rationale is sound: scoping by simpleTicketQuery collapsed to "commented and collaborator" and silently dropped most supported work.
  • editorId now explicitly selected in getTicketsByIdsWithinProjects — resolves my prior CR on ownership-column presence.
  • Two tests added: test_commented_tickets_range_excludes_owned_and_scopes_by_projects and test_commented_tickets_range_empty_without_project_access.

2. Change requests (this is access-control code, so these are confirm-before-merge):

  1. Confirm getProjectsUserHasAccessTo is the same-or-tighter access universe as the open/closed user-ticket lists (simpleTicketQuery). The IDOR guard now rests on project-level access, not ticket-level. It's the safer direction — a comment in a project the user has lost access to drops out — but confirm (a) project access never surfaces a ticket the viewer shouldn't see, and (b) dropping ticket-level-only access (a collaborator on an individual ticket without project access) is acceptable as a false-negative rather than a leak.
  2. The load-bearing IDOR guard — the repo-level whereIn('projectId', …) — is mocked, not exercised. test_commented_tickets_range_excludes_owned_and_scopes_by_projects stubs getTicketsByIdsWithinProjects to return 10+20, so it proves the service drops editor-owned tickets and trusts the repo filter, but never runs the actual project-scoping SQL. Add a repository-level test asserting a commented ticket in a non-accessible project is filtered out — that's the assertion the safety argument depends on.
  3. ⛔ Divergence with feat(reports): stakeholder report — Phase 1 shell (deck, header, navigation) #3648. feat(reports): stakeholder report — Phase 1 shell (deck, header, navigation) #3648's feat/stakeholder-report branch carries an older version of this same method using the simpleTicketQuery-intersection approach (and a different getTicketIdsCommentedByUser body). When both land, Tickets/Services/Tickets.php conflicts and the two access models differ semantically — reconcile which implementation wins before merging either; do not merge them blind in parallel.
  4. Merge-order: edits the same Tickets/Services/Tickets.php region as feat(tickets): getMyClosedTicketsForRange for period close-lists #3647 (inserts right after getMyClosedTicketsForRange) — trivial rebase for the second to merge.

3. Acceptance checklist (must pass before merge):

  1. Cross-user isolation: a comment in a now-inaccessible project is absent; an accessible non-owned ticket is present; the user's own ticket is absent. Service-level tests ✔ — add the repo-level project-scoping assertion (CR Gitignore #2).
  2. getProjectsUserHasAccessTo scope parity confirmed against the open/closed lists (CR Documentation #1).
  3. CI green: Pint + PHPStan level 5 + full unit suite; no schema/API change, no migration. feat(reports): stakeholder report — Phase 1 shell (deck, header, navigation) #3648 divergence reconciled (CR Create Email Notification Class (including user settings to opt out) #3).

4. CI status: ⚠️ Can't read check-run status this cycle — confirm green on a68e9d12 in the UI; treat any red/skipped job as blocking. This touches ticket visibility across users — the service tests clear my prior blocker, but the repo-level project-scoping (the actual guard) still needs a test (CR #2).

Advisory review only — not an approval, and I am not marking this ready or merging. Merge authority stays with @marcelfolaron / @broskees.

…eview)

Copilot re-review (#3649):
- IDOR: a non-admin passing someone else's userId could read that user's
  comment activity (scoped to the caller's projects). Force userId to the
  session user unless admin — mirrors Projects::getProjectsUserHasAccessTo().
- Resolve today's date once so a run across midnight can't disagree on bounds.
- getTicketsByIdsWithinProjects now has an explicit ORDER BY (DB default order
  is unspecified/nondeterministic).
- Docstring: 'editor', not 'assignee/editor' (the filter is editorId only).
- Test stubs take variadic args.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NtfhjPwUEEHJb4Jf7714eR
Copilot AI review requested due to automatic review settings July 16, 2026 12:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment on lines +1933 to +1936
// Stable, deterministic order (DB default order is unspecified).
->orderBy('zp_tickets.dateToFinish')
->orderBy('zp_tickets.id')
->get();
Comment on lines +610 to +621
$ticketRepository = $this->make(TicketRepository::class, [
'getTicketIdsCommentedByUser' => fn (...$args) => [10],
'getTicketsByIdsWithinProjects' => fn (...$args) => [['id' => 10, 'editorId' => '99']],
]);
$projectService = $this->make(ProjectService::class, [
'getProjectsUserHasAccessTo' => fn (...$args) => false,
]);

$service = $this->buildServiceWithTicketRepoAndProjectService($ticketRepository, $projectService);

$this->assertSame([], $service->getMyCommentedTicketsForRange(1, '2026-07-01', '2026-07-07'));
}
@marcelfolaron

Copy link
Copy Markdown
Collaborator

Maintainer re-review (advisory — merge authority stays with @marcelfolaron / @broskees) · re-reviewed at head 896c3570 (advanced past my a68e9d12 review — 4th commit on branch, rebased onto master f5a06e2f) · full diff read

1. Intent: Adds getMyCommentedTicketsForRange (+ repo getTicketIdsCommentedByUser and getTicketsByIdsWithinProjects) returning tickets the user commented on but does not own, for mobile Progress "Supported."

What changed since my last review: the code at head is stable relative to a68e9d12 — project-access scoping (getProjectsUserHasAccessTogetTicketsByIdsWithinProjects with whereIn on both id and projectId), editorId explicitly selected in the repo method, and the two service tests are all present and unchanged (Repositories/Tickets.php + Services/Tickets.php, +210, 3 files). The advance is a branch update, not a logic change.

2. Change requests (this is access-control code — these are confirm-before-merge; #2 is the one I'd treat as the gate):

  1. Confirm getProjectsUserHasAccessTo is the same-or-tighter access universe as the open/closed user-ticket lists (getMyCommentedTicketsForRange). The IDOR guard now rests on project-level access, not ticket-level — safer direction, but confirm (a) project access never surfaces a ticket the viewer shouldn't see, and (b) dropping ticket-level-only access (collaborator on an individual ticket without project access) is acceptable as a false-negative rather than a leak.
  2. The load-bearing IDOR guard — the repo-level whereIn('zp_tickets.projectId', $projectIds) — is still mocked, not exercised (test_commented_tickets_range_excludes_owned_and_scopes_by_projects stubs getTicketsByIdsWithinProjects to return 10+20). The service test proves editor-owned tickets are dropped and trusts the repo filter, but the actual project-scoping SQL is never run. Add a repository-level test asserting a commented ticket in a non-accessible project is filtered out — that is the exact assertion the safety argument depends on. For cross-user visibility code, a mocked guard is not a tested guard.
  3. editorId ownership dropif ((string)($ticket['editorId'] ?? '') === (string)$userId) keeps a row missing editorId. getTicketsByIdsWithinProjects now selects editorId explicitly ✔, so this is covered at head; keep the ?? '' fallback in mind if the select ever changes.

3. Acceptance checklist (must pass before merge):

  1. Repository-level cross-user isolation test (blocker): a comment on a ticket in a project the user can no longer access does NOT appear (real whereIn projectId SQL exercised, not mocked); a comment on an accessible ticket the user does NOT own DOES appear; a comment on the user's OWN ticket does NOT appear.
  2. Confirm getProjectsUserHasAccessTo scoping is same-or-tighter than the existing user-ticket lists (CR Documentation #1).
  3. CI green: Pint + PHPStan level 5 + full unit suite. No schema/API change (additive methods), no migration.

4. CI status: ⚠️ Can't read check-run status this cycle — confirm green on 896c3570 in the UI; treat any red/skipped/flaky job as blocking. This touches ticket visibility across users — the missing repository-level isolation test (checklist #1) is the merge blocker, not optional. The design avoids the getUser-style leak class by construction (scope-then-fetch); lock it with the real-SQL test so a future refactor can't silently break the guard.

Merge-order: edits the same Tickets/Services/Tickets.php region as #3647 (immediately after getMyClosedTicketsForRange). No logical conflict, but the second to merge needs a trivial rebase — don't merge the two blind in parallel.

Advisory review only — not an approval, and I am not marking this ready or merging. Merge authority stays with @marcelfolaron / @broskees.

…mentedTicketsForRange (#3649 review)

Copilot asked for coverage of the access-sensitive branches. Add:
- IDOR guard: a non-admin caller supplying another user's id is forced to the
  session user before the query runs.
- Reversed range is normalized earliest-first.
- An empty commented set short-circuits and never calls getTicketsByIdsWithinProjects
  (asserted explicitly, so the short-circuit can't silently regress).

TicketsServiceTest green (27 tests / 55 assertions).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NtfhjPwUEEHJb4Jf7714eR
Copilot AI review requested due to automatic review settings July 19, 2026 03:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment on lines +2499 to +2502
// Resolve "today" once so a run across midnight can't disagree on bounds.
$today = date('Y-m-d');
$from = $from ?: $today;
$to = $to ?: $today;
Resolve the TicketsServiceTest conflict by keeping BOTH suites — master's
closed-range tests (#3647/#3650) and this branch's commented-range tests.
Tickets service/repo auto-merged cleanly. Full suite green (30 tests / 62
assertions), Pint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NtfhjPwUEEHJb4Jf7714eR
Copilot AI review requested due to automatic review settings July 19, 2026 15:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

tests/Unit/app/Domain/Tickets/Services/TicketsServiceTest.php:706

  • This test’s docblock says it should return empty without ever fetching tickets, but it currently doesn’t assert that getTicketsByIdsWithinProjects() was not called. Add a flag/assertion so the test will fail if the implementation regresses and performs the ticket fetch anyway.
    public function test_commented_tickets_range_empty_without_project_access(): void
    {
        session(['userdata' => ['id' => 1]]);

        $ticketRepository = $this->make(TicketRepository::class, [
            'getTicketIdsCommentedByUser' => fn (...$args) => [10],
            'getTicketsByIdsWithinProjects' => fn (...$args) => [['id' => 10, 'editorId' => '99']],
        ]);

Comment on lines +1914 to +1921
return $this->connection->table('zp_comment')
->where('module', 'ticket')
->where('userId', $userId)
->whereBetween('date', [$fromDate.' 00:00:00', $toDate.' 23:59:59'])
->distinct()
->pluck('moduleId')
->map(fn ($id) => (int) $id)
->all();
…review)

Copilot: zp_comment.moduleId is nullable, so (int) NULL cast a bogus id 0 into the
commented-ids list, which then reached the downstream whereIn(). Filter NULLs at
the query level (whereNotNull) plus a defensive ->filter() to drop any 0 after
the cast, so only real ticket ids flow to getTicketsByIdsWithinProjects.

TicketsServiceTest green (30 tests / 62 assertions); Pint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NtfhjPwUEEHJb4Jf7714eR
Copilot AI review requested due to automatic review settings July 19, 2026 19:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment on lines +1954 to +1956
->whereIn('zp_tickets.id', $ticketIds)
->whereIn('zp_tickets.projectId', $projectIds)
// Stable, deterministic order (DB default order is unspecified).
…3649 review)

Copilot: a comment on a milestone would surface a milestone row in the 'Supported'
list, inconsistent with the other mobile user-ticket queries which exclude
milestones. Add the same 'type <> milestone' guard so Supported stays a task surface.

TicketsServiceTest green (30 tests / 62 assertions); Pint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NtfhjPwUEEHJb4Jf7714eR
Copilot AI review requested due to automatic review settings July 19, 2026 20:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread app/Domain/Tickets/Services/Tickets.php Outdated
Comment on lines +2536 to +2538
* @return array<int, array<string, mixed>> raw ticket rows (id, headline,
* projectName, editorId, status, …). NOTE: these do NOT carry the resolved
* statusLabel/statusClass/statusType that getAll*UserTickets attach.
…ts own lines (#3649 review)

Copilot readability nit — the NOTE was crammed onto the @return type line. Split
the type/description from the status-fields note. Comment-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NtfhjPwUEEHJb4Jf7714eR
Copilot AI review requested due to automatic review settings July 19, 2026 21:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment on lines +2567 to +2583
// Scope to the projects the user can see — the correct access boundary
// for "tickets I commented on" (you comment on others' work without
// being its editor/collaborator, so filtering by those would drop it).
$projects = $this->projectService->getProjectsUserHasAccessTo($userId);
if (! is_array($projects) || empty($projects)) {
return [];
}
$projectIds = array_values(array_filter(array_map(
fn ($p) => (int) ($p['id'] ?? 0),
$projects
)));
if (empty($projectIds)) {
return [];
}

$tickets = $this->ticketRepository->getTicketsByIdsWithinProjects($commentedIds, $projectIds);

Comment on lines +645 to +663
private function buildServiceWithTicketRepoAndProjectService(
TicketRepository $ticketRepository,
ProjectService $projectService
): TicketsService {
return new TicketsService(
language: $this->make(LanguageCore::class),
ticketRepository: $ticketRepository,
timesheetsRepo: $this->make(TimesheetRepository::class),
settingsRepo: $this->make(SettingRepository::class),
projectService: $projectService,
timesheetService: $this->make(TimesheetService::class),
sprintService: $this->make(SprintService::class),
ticketHistoryRepo: $this->make(TicketHistory::class),
goalcanvasService: $this->make(Goalcanvas::class),
dateTimeHelper: $this->make(DateTimeHelper::class),
commentService: $this->make(CommentService::class),
clientService: $this->make(ClientService::class)
);
}
@marcelfolaron
marcelfolaron merged commit d518c81 into master Jul 20, 2026
12 of 13 checks passed
@marcelfolaron
marcelfolaron deleted the feat/commented-tickets-range branch July 20, 2026 00:01
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.

3 participants