Inbox Filter and My Tickets Developer Guide - edmozley/freeitsm GitHub Wiki
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. Notowner_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, viaNOT EXISTS.NOT INagainst a ticket with a NULLstatus_idevaluates 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 dropsminein Analyst grouping (viewFilterMineActive()), which is whysetFolderGrouping()re-fetches the counts whenmineis on.- Counts and list wait for the preference.
DOMContentLoadedloads 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 itsdata-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.phpadds the sameNOT EXISTSclause.search_content.phppassesexclude_closedin the search scope, andsearchCorpusQuery()applies it inside the query, next toinclude_deleted, on theticketsjoin 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.