Skip to content

Inbox Filter and My Tickets Developer Guide

Ed Mozley edited this page Sep 24, 2026 · 1 revision

Just my tickets, or no closed ones - developer guide

User-facing page: Just my tickets, or no closed ones. Asked for in #149.


Why a filter, not a "My tickets" folder everywhere

The first idea was a pinned My tickets folder in every grouping. In Department view that folder would need a department tree of its own underneath it - a second copy of the whole pane. A filter narrows the tree that is already there. So "My tickets" is a folder only in Analyst grouping, where the pane is already one folder per person, and a filter everywhere else.

πŸ”‘ One helper for the counts AND the list

includes/inbox_view_filter.php is the only place "mine" and "closed" are defined:

inboxViewFilterFromRequest()                    // ['mine' => bool, 'hide_closed' => bool] from ?mine=1&hide_closed=1
inboxViewFilterSql($filter, $analystId, 't')    // the SQL fragment

Both get_ticket_counts.php and get_emails.php call it. If they each spelled the rule out, a badge would say 12 over a list of 11 the first time one of them was edited - the count-disagrees-with-list bug this pane has had twice.

In the counts endpoint the fragment is appended to the shared $ttSql, so every count obeys it. $ttSql lands in LEFT JOIN ... ON clauses as well as WHERE, and relies on binding no parameters, so the fragment is placeholder-free: the analyst id is an (int) cast, never input.

The two definitions

  • Mine = t.assigned_analyst_id, the column the Analyst folders count. Not owner_id: the two are stored separately and disagree on most rows, so "Mine" must match the number on your own analyst folder.
  • Closed = any status with is_closed = 1, via NOT EXISTS. NOT IN against a ticket with a NULL status_id evaluates to NULL and would silently drop it.

Trash and Snoozed are exempt on both sides: their counts build their own predicate, and get_emails.php skips the filter for trashed / snoozed, so they agree.

The front end (assets/js/inbox.js)

  • State is viewFilter = { mine, hide_closed }, saved as one JSON preference, tickets_view_filter.
  • viewFilterParams() builds the query string for both fetches. It drops mine in Analyst grouping (viewFilterMineActive()), which is why setFolderGrouping() re-fetches the counts when mine is on.
  • Counts and list wait for the preference. DOMContentLoaded loads the grouping and filter preferences together and only then fetches, or the list would paint unfiltered and change a moment later. updateViewFilterUI() runs again once both are in, because they can arrive in either order.
  • In Analyst grouping the Show section is hidden, not disabled. It was disabled at first, with a hint; Ed tried to click it and reported it broken. A greyed-out control reads as broken.
  • My tickets is the current analyst's own folder moved to the top (window.CURRENT_ANALYST_ID), keeping its data-analyst-id, so drag-to-assign, status children and highlighting needed no changes. analystFolderLabel() names it for the row and the list title only. The toast after a drag still uses the real name.

Search: include_closed

A separate flag, on by default and independent of the inbox filter. Absent means included, so no other caller changes.

  • search_tickets.php adds the same NOT EXISTS clause.
  • search_content.php passes exclude_closed in the search scope, and searchCorpusQuery() applies it inside the query, next to include_deleted, on the tickets join it already has. Filtering the rows afterwards would print "62 found" above a page that had lost four. Measured: the total went 62 β†’ 58 and the page still filled to 25.

Verified

Against a real database, every combination of filters gave the same number in the folder badge and in the list (109 / 4 / 101 / 4), Trash ignored the filter, and the page was driven headless with no console errors.


See also

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally