The Unassigned queue stops offering merged tickets as work (#1054)
The count and the list were the same question spelled two ways in one method: the sidebar counts scope by $closedSlugs, read from ticketing_statuses, while the `unassigned` and `mine` list branches carried a hand-written ["resolved", "closed"] that predates the `merged` status. So a correctly merged ticket — status merged, merged_into_id set, its own Show page already calling it read-only — stayed in the queue agents pull new work from, and the page contradicted itself on screen: "Unassigned 4" beside five rendered rows. Both queues now scope by $closedSlugs, so they exclude exactly what their own counts exclude. The scoping is by STATUS and not by merged_into_id on purpose: #938 rows (pointer set, status still open) are live, editable work until a human repairs them, and dropping them out of the queue would hide the rows that most need one. There is a NEGCTRL test for that. Also, the secondary finding: ?filter=closed was accepted, silently returned the All-Open set and was echoed back in props.filters as though a Closed view existed. The sidebar has six views and that is not one of them, so an unrecognised filter normalises to `all` and says so. The list is now a constant that savePrefs() validates against too, rather than the same six slugs written out twice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,13 @@ use Inertia\Response;
|
||||
|
||||
class TicketController extends Controller
|
||||
{
|
||||
/**
|
||||
* The sidebar's views, and the only values `?filter=` means anything as.
|
||||
* Index.vue's `visibleViews` draws exactly these; `savePrefs()` validates
|
||||
* against them; `index()` normalises anything else to `all` (#1054).
|
||||
*/
|
||||
private const VIEW_FILTERS = ['all', 'mine', 'unassigned', 'pending', 'resolved', 'merged'];
|
||||
|
||||
private function agentGroupIds(): array
|
||||
{
|
||||
return TicketingAgentAccess::where('user_id', Auth::id())
|
||||
@@ -112,6 +119,18 @@ class TicketController extends Controller
|
||||
$request->merge(['filter' => $pref['filter']]);
|
||||
}
|
||||
|
||||
// #1054 — a `filter` the sidebar has no view for used to fall through to
|
||||
// the All Open branch and then be echoed straight back in `props.filters`,
|
||||
// so `?filter=closed` looked like a real view that just happened to be
|
||||
// showing open tickets. There is no Closed view anywhere in the UI;
|
||||
// anything unrecognised is All Open, and now says so. The list is the
|
||||
// one `savePrefs()` validates against and the one Index.vue draws.
|
||||
// Only rewrites a filter that was actually asked for: an absent one keeps
|
||||
// meaning "no view chosen", which is what leaves `filter` out of the URL.
|
||||
if ($request->filled('filter') && ! in_array($request->filter, self::VIEW_FILTERS, true)) {
|
||||
$request->merge(['filter' => 'all']);
|
||||
}
|
||||
|
||||
// If no groups exist at all, render a first-run / bootstrap state
|
||||
$totalGroups = TicketingGroup::count();
|
||||
if ($totalGroups === 0) {
|
||||
@@ -181,10 +200,20 @@ class TicketController extends Controller
|
||||
if ($request->filled('project_id')) {
|
||||
$query->where('project_id', $request->project_id);
|
||||
}
|
||||
// #1054 — the work queues scope by `$closedSlugs`, the same list the
|
||||
// counts above use. They used to spell it `['resolved', 'closed']`
|
||||
// by hand, which predates the `merged` status: a correctly merged
|
||||
// ticket stayed in Unassigned as pickable work while the sidebar
|
||||
// count next to it — already reading `$closedSlugs` — excluded it,
|
||||
// so the view disagreed with its own header (4 vs 5). Scoping by
|
||||
// STATUS and not by `merged_into_id` is deliberate: #938's
|
||||
// inconsistent rows (pointer set, status still open) are live work
|
||||
// until somebody repairs them, and hiding them from the queue would
|
||||
// hide the rows that most need a human.
|
||||
if ($request->filter === 'mine') {
|
||||
$query->where('assigned_to', $user->id)->whereNotIn('status', ['resolved', 'closed']);
|
||||
$query->where('assigned_to', $user->id)->whereNotIn('status', $closedSlugs);
|
||||
} elseif ($request->filter === 'unassigned') {
|
||||
$query->whereNull('assigned_to')->whereNotIn('status', ['resolved', 'closed']);
|
||||
$query->whereNull('assigned_to')->whereNotIn('status', $closedSlugs);
|
||||
} elseif ($request->filter === 'pending') {
|
||||
$query->where('status', 'pending');
|
||||
} elseif ($request->filter === 'resolved') {
|
||||
@@ -630,7 +659,7 @@ class TicketController extends Controller
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'view_mode' => 'sometimes|in:list,kanban',
|
||||
'filter' => 'sometimes|in:all,mine,unassigned,pending,resolved,merged',
|
||||
'filter' => 'sometimes|in:'.implode(',', self::VIEW_FILTERS),
|
||||
]);
|
||||
|
||||
if (empty($validated)) {
|
||||
|
||||
Reference in New Issue
Block a user