-
-
Notifications
You must be signed in to change notification settings - Fork 28
Saved Table Views Developer Guide
How saved views are put together, and the decisions worth not re-litigating.
The user page is Saved table views. Related: Assets Β· Roles and Permissions
Built for discussion #96 (dschipfel), expanded by Ed.
| File | What it does |
|---|---|
includes/table_views.php |
The visibility rules. Every read and write goes through it |
api/table-views/list.php |
Views on one table, plus the reader's teams and their default |
api/table-views/save.php |
Create or update |
api/table-views/delete.php |
Delete |
api/table-views/use.php |
Stamp last-used, and set or clear the personal default |
assets/js/data-table.js |
Capture, apply, the library and the editor |
includes/data-table-skeleton.php |
The Views and Save view toolbar buttons |
assets/css/data-table.css |
The library, the editor, the buttons |
tests/table-views.php |
31 assertions, touches the database |
asset-management/table.php is a thin page over assets/js/data-table.js, and so are tasks/table, calendar/table and change-management/table. Views are built into the engine, so all four have them.
A module opts in with one line in its createDataTable() config:
viewsKey: 'assets', // 'assets' | 'tasks' | 'calendar' | 'changes'A table with no viewsKey hides both buttons rather than showing ones that do nothing.
β οΈ viewsApiis derived fromprefApi, not set again. The four host pages sit at different depths (../api/β¦vs../../api/β¦), and a second copy of that path is a second chance to get it wrong β which would present as a Views button that silently does nothing.
table_views
id, table_key, name, description
owner_id -- FK analysts, ON DELETE SET NULL
visibility -- 'private' | 'team' | 'public'
team_id -- FK teams, ON DELETE SET NULL
config -- MEDIUMTEXT, the engine's own state as JSON
created_datetime, updated_datetime, last_used_datetimeconfig is opaque to the database on purpose: the engine owns that shape, and a column per setting would need a migration every time it gained one. It is validated as JSON on the way in β a malformed one would break the table for everybody it is shared with, and the moment to find that out is at save.
owner_id is SET NULL rather than CASCADE, so a team or public view survives the person who wrote it leaving. A private view with no owner then matches nobody: unreachable rather than exposed.
No tenant_id, and none is needed. A view is a way of looking at rows; its filters are applied to rows the reader was already allowed to load.
Everything reads through:
function tableViewVisibleClause(PDO $conn, int $analystId): arrayThree answers β mine, my team's, everyone's β and a fourth that must never happen: somebody else's private view. Four endpoints each writing their own clause is four chances to get that wrong.
β οΈ It returns a BRACKETED group. Callers append it to aWHEREthat already carriestable_key, and an unbracketed set of alternatives would bind that condition to the last branch only β a tasks view would appear on the asset table. There is an assertion for exactly this, because it is a one-character mistake.
Analysts belong to many teams. analyst_teams is a join table; there is no analysts.team_id. So "their team" was ambiguous and Ed chose: sharing names a specific team, and tableViewSave() refuses a team the saver does not belong to β otherwise anybody could share into any team by posting its id.
An analyst in no teams is a real case: the clause is built from their teams, so with none it has to fall back to owner-or-public rather than producing broken SQL. Asserted.
Write access is owner-only. can_edit is computed server-side and returned with each row, so the browser shows the same answer the write path enforces rather than working it out from owner_id itself.
-
The default is a row in
user_preferences(dtview_default_<table_key>). It is a fact about the reader: two people can have different defaults pointing at the same shared view, and one changing theirs must not change the other's. - Last used is one timestamp on the view, not one per reader. It answers "is anybody still using this?", which is what decides whether it can be deleted. "When did I last use it?" would need a row per person per view, and nobody has asked for that.
captureViewConfig() reads columnState, sort and filters. Filters are held as a Set per column, so they are unpacked to arrays going out and repacked coming back.
β οΈ searchTermis deliberately excluded. A filter is how you like to look at things; a search is a question you asked once.
applyColumnConfig() is one function shared by preferences and views β both restore the same thing, and two copies would drift the first time a column was added. Its merge is what makes a view survive a changing table: unknown keys dropped, columns the config never mentioned appended at their defaultOrder.
That merge is why a view naming six columns can render eight, which is correct and worth knowing before somebody "fixes" it.
applyDefaultView() runs after loadPreferences(), so a chosen view beats the loose column state β picking a view is the more deliberate act. A default pointing at a deleted or now-unreachable view silently falls back to the table's own defaults rather than erroring every morning.
-
.dt-btnis scoped.dt-toolbar .dt-btn. The modal footer buttons shipped with no padding, border or radius, rendering as bare text on a coloured block..dt-modal .dt-btnnow defines them. Third time in one day a class was used outside the scope it is defined in. -
The English fallback must interpolate.
vt()fell back to raw strings without substituting, so before the strings existed the library renderedCreated {d}andby {name}literally. A safety net that produces visible nonsense is not a safety net. -
Save viewfetches the list before opening the editor. The teams an analyst can share into arrive with the view list, so opening from a cold toolbar would show "A team" disabled the first time and working the second.
php tests/table-views.php # 31 assertions
Everything it makes is named ZZTV and removed in a finally. It picks real analysts with differing team membership rather than creating them, because the whole feature turns on analyst_teams being what it says it is β and skips with a message if analyst 1 is not in two teams, rather than passing vacuously.
Every visibility answer is checked from the side that must be refused as well as the side that must work, with positive controls: somebody who can see a public view cannot update or delete it, and can_edit says so before they try.
Beyond the unit tests, the library was driven in a real browser on the asset table: applying a saved view took it from 10 columns and 597 rows to 4 columns and 14 rows, with the sort applied and the button marked active.
FreeITSM β an open-source IT Service Management platform Β· github.com/edmozley/freeitsm Β· MIT licence
- Installation
- β° Scheduled tasks (cron jobs)
- Architecture
- π§ͺ Developer tests
- AI Providers
- Internationalisation (i18n)
- Timezones & Time Handling
- π Date & Time Formats
- Theming & Dark Mode
- ποΈ Recent β getting back to what you were doing
- β¨οΈ Command palette (βK)
- π Searching inside tickets
- π Attached documents
-
MobileβFriendly
- β³ π« Mobile: Tickets
- β³ π» Mobile: Assets
- β³ π Mobile: Calendar
- β³ π Mobile: Knowledge
- β³ π¦ Mobile: Service Status
- β³ πΌ Mobile: Watchtower
- β³ π§© Mobile: Problem Management
- β³ π Mobile: Change Management
- β³ πΏ Mobile: Software
- β³ β Mobile: Tasks
- β³ π Mobile: Forms
- β³ π Mobile: Contracts
- β³ π Mobile: Domains
- β³ π Mobile: People
- β³ π Mobile: LMS
- β³ πΊοΈ Mobile: CMDB
- β³ πΊοΈ Mobile: Network Mapper
- β³ π§ Mobile: Process Mapper
- β³ βοΈ Mobile: Workflow
- β³ π₯οΈ Mobile: System
- β³ π Mobile: Reporting
- β³ π Mobile: System Wiki
- β³ π Mobile: Self-Service Portal
- β³ π§° Mobile: Techniques & Tricks
-
Security
- Layer 1 β which modules you can enter
- β³ π§© Module Access Control
- β³ π οΈ Module Access β Developer Guide
- Layer 2 β what you can administer
- β³ π Roles & Permissions
- β³ π οΈ Roles β Developer Guide
- β³ π€ Why capabilities are constants
- Layer 3 β the System module
- β³ π Admin Access Control
- Hardening
- β³ π Security review response 2026-08
- β³ π‘οΈ Security hardening 2026-08
- β³ π οΈ Security hardening 2026-08 β Developer Guide
- β³ π‘οΈ Round three β plain English
- β³ π οΈ Round three β Developer Guide
- β³ π‘οΈ CSRF protection (S4) β Developer Guide
- Single Sign-On (SSO)
- ποΈ LDAP & Active Directory
- π CardDAV contact sync
- Browser Extension
- API Reference
-
π REST API β how it works
- β³ π« REST API: Tickets
- β³ π» REST API: Assets
- β³ π΄ REST API: Problems
- β³ π REST API: Changes
- β³ π REST API: Knowledge
- β³ β REST API: Tasks
- β³ ποΈ REST API: CMDB
- β³ π REST API: Contracts
- β³ ποΈ REST API: Calendar
- β³ πΏ REST API: Software
- β³ π REST API: Domains
- β³ π¦ REST API: Service Status
- β³ βοΈ REST API: Morning Checks
- β³ π REST API: Forms
- β³ βοΈ REST API: Workflow
- β³ π·οΈ REST API: Cost centres
- β³ πΊοΈ REST API: Network Mapper
- β³ π§ Using the API docs page
- β³ π OpenAPI specification
- β³ β OpenAPI: kept correct
- β³ π οΈ Maintaining the catalogue
- Watchtower
-
Tickets
- β³ π Rota copy and paste β Developer Deep Dive
- β³ β Checklists & SOPs
- β³ βοΈ Mandatory fields
- β³ π·οΈ Ticket categories
- β³ π₯ Assigning tickets to a team, and escalation
- β³ π’ One board across every company
- β³ Mailbox Authentication
- β³ π€ Email send log
- β³ Basic IMAP mailboxes
- β³ Email rendering & images
- β³ SLA Management
- β³ WhatsApp channel
-
β³
βοΈ Telegram channel - β³ β CSAT company scope and filters β Developer Guide
- β³ π₯ Microsoft Teams channel
- β³ π¨οΈ Mattermost channel
- β³ π¬ Web chat channel
- β³ π£ Slack channel
- β³ π Linking tickets
- β³ β Record previews
- β³ π Ticket notes: internal or shared
- β³ ποΈ Canned responses
- β³ βοΈ Limiting replies to particular senders
- β³ π¨ Telling the analyst a ticket is theirs
- β³ βοΈ Email signatures
- β³ π The public web address
- β³ π’ Ticket numbering
- β³ π Raising a ticket for someone else
- β³ π Merging tickets
- β³ π Confidential tickets
- β³ π₯ Portal managers
- β³ π Who has seen a ticket
- β³ π Reading long tickets
- β³ β Splitting tickets
- β³ β Selecting several tickets
- β³ ποΈ The folder pane
- β³ π½ Just my tickets, or no closed ones
- β³ π οΈ Snoozing tickets β Developer Guide
- β³ π₯ Collision detection
- β³ β±οΈ Time tracking
- β³ π Scheduled work in your own calendar
- Problem Management
- Tasks
-
Assets
- β³ π’ Moving an asset between companies
- β³ π Shared asset locations
- β³ π§βπΌ Assigning assets to analysts
- β³ π Warranty and lease alerts
- β³ π Saved table views
- β³ π¨οΈ Recording anything, and importing it
- β³ π·οΈ QR asset labels
- β³ π Who holds what, and handover documents
- β³ π₯οΈ The inventory agent (PowerShell)
- β³ ποΈ Proxmox VE servers
- β³ βοΈ VMware Cloud Director servers
- β³ π Linking equipment to tickets
- β³ βοΈ Follow-up tasks on a ticket
- Knowledge
- Change Management
- Calendar
- Morning Checks
- Reporting
- Software
-
Forms
- β³ π¨ The form designer β Developer Guide
- β³ π Layout & the grid β Developer Guide
- β³ ποΈ Collections β grouping submissions
- β³ π Submissions as PDFs
- β³ β‘ What happens next β a form's own actions
- β³ π οΈ Sections & conditional logic β Developer Guide
- β³ π οΈ Lookup fields β Developer Guide
- β³ π‘οΈ Catalogue request approvals
- People
- Domains
- Contracts
- Service Status
- π Notifications
- π¨ War Room
- Self-Service Portal
- LMS
- Process Mapper
- CMDB
- Network Mapper
- Workflows
- Issue trackers (Jira, Azure DevOps)
- System
-
Overview
- β³ π Progress tracker
- β³ Concepts & vocabulary
- β³ Email routing & mailboxes
- β³ Settings: global vs per-company
- β³ Users & self-service
- β³ Staff cross-company access
- β³ π’ One board across every company
- β³ Worked examples
- β³ Pitfalls & gotchas
- β³ Scope: what it's for
- β³ π οΈ Developer Guide (make a module multi-company)
- β³ ποΈ Case study: CMDB (a linked graph)
- β³ π§ͺ Test harness (prove it's isolated)
- What this is
-
π Bugs resolved
- β³ π’ Chat tickets ignored your ticket numbering
- β³ π Dates shown as a dash, or in server time
- β³ π Assets β Users showed people from other companies
- β³ π Restricted analysts could read other modules' data
- β³ πΌοΈ Replies with a picture in the thread failed to send
- β³ π Reply attachments never reached the customer
- β³ π οΈ Outbound email attachments β Developer Guide
- β³ π A global SSO provider was missing from the portal
- β³ π Behind a proxy, the SSO redirect said http
- β³ βοΈ The portal tagline moved when you saved it
- β³ π¨ The portal settings screen forgot what you saved
- β³ π‘οΈ The approvals inbox said "Error" and nothing else
- β³ π A table's answers were missing from the PDF
- β³ β A single-select column let you tick every option
- β³ π The portal ignored a form's field widths
- β³ π The tasks board stopped taking clicks
- β³ ποΈ #121 The index list is out of date after upgrading
- β³ π #133 The calendar subscription was empty
- β³ π #131 Tasks always reopened on the board
- β³ π₯ #129 Every page returned HTTP 500 after upgrading
- β³ π³ #127 A PHP warning above the System page
- β³ π #126 Notes stamped with the server's clock
- β³ π Storing every date in UTC
- β³ πͺ The portal was down for everyone signed in
- β³ βοΈ #120 Workflow notes could never be written
- β³ βοΈ #123 Three errors when running Database Verification
- β³ π #122 The description box was a stub in the corner
- β³ π£ Demo data deleted real accounts
- β³ π #117 Sign-in redirected to the wrong address
- β³ π¨ #108 The priority dot was invisible
- β³ β±οΈ #116 Time logged from the right-click menu
- β³ π #114 API keys refused by our own guard
- β³ ποΈ #110 Assigning a task told nobody
- β³ πͺ #107 Signed out while still working
- β³ π #103 "Share with Requester" reached nobody
- β³ π #102 Search found nothing for hyphens
- β³ πͺ #101 Source code editor opened behind
- β³ βοΈ #88 Subtasks could not be ticked off
- β³ π» #84 Asset deep link selected nothing
- β³ π« #79 A new ticket arrived with no status
- β³ π§ #79 A ticket from email did not say so
- β³ π #78 Bell opened to nothing
- β³ π¬ #77 Mail only collected from Inbox
- β³ π #74 The default password could not be changed
- β³ π¦ #70 Renaming an impact level
- β³ π€ #67 App-only mailboxes could not send
- β³ π #45 Verify only ever worked for Microsoft
- β³ π #45 IMAP reported as not authenticated
- β³ βοΈ An email template stopped escaping itself
- β³ π The portal dashboard showed the wrong time
- β³ π’ The folder said 99 and the list showed 96