Ticket Dashboard Developer Guide - edmozley/freeitsm GitHub Wiki
Ticket dashboard - Developer Guide
How Tickets → Dashboard and its Widget Library work underneath: the pages, the shared widget editor, the endpoints and the two tables. For what an analyst sees, see Tickets → Dashboard.
Read this first if you touch
assets/js/widget-editor.js. One script drives every way of making or changing a widget - the cog on the dashboard, and New widget and Edit in the library. An error anywhere in it stops all three at once. That is what happened between 2.5.0 and 4.10.0 (#260).
The shape of it
There are two kinds of thing:
- A widget - one chart definition: what to count, how to draw it, which dates and departments. Widgets live in one shared library for the whole install.
- A dashboard - one analyst's choice of library widgets, in their own order.
So adding, removing and reordering are personal. Editing a widget's definition is not: it changes the library row, and every analyst who has that widget sees the change.
ticket_dashboard_widgets (the library - one row per widget, shared)
▲ widget_id
analyst_ticket_dashboard_widgets (who has which widget, in what order)
Files
| File | What it does |
|---|---|
tickets/dashboard/index.php |
The dashboard. It draws each card with Chart.js, handles drag and drop, the status dropdown on a card, and the cog that opens the edit modal |
tickets/dashboard/library.php |
The Widget Library: table of every widget, New widget, Edit, Duplicate, Delete, Add to dashboard |
tickets/dashboard/includes/widget_edit_form.php |
The edit form's markup. It is included by both pages, so the fields are written once |
assets/js/widget-editor.js |
The form's behaviour, shared by both pages as window.WidgetEditor (below) |
assets/js/chart.min.js |
Chart.js, loaded by the dashboard only |
lang/en/tickets.php → tickets.dashboard.* |
Every string, including the auto-written description's sentence parts (auto_desc.*) |
Both pages load the editor as widget-editor.js?v=N. Bump N on both pages whenever the script changes, or browsers keep the old copy.
widget-editor.js - the shared editor
One IIFE that exposes window.WidgetEditor:
| Function | Called by | What it does |
|---|---|---|
init(apiBase) |
both pages, once on load | Loads departments and wires the form's change handlers |
populateForm(widget) |
the cog, library Edit | Fills the form from a widget, then calls onPropertyChange() |
resetForm() |
library New widget | Clears the form, then calls onPropertyChange() |
onPropertyChange() |
the two above, and the What to count dropdown | Rebuilds the Series and Chart type options and shows or hides Time grouping to suit the property |
collectFormData() / validateForm() |
saveWidget() |
Reads the form into the save payload; a time property must have a grouping |
saveWidget() |
the modal's and the library's Save | Posts to save_ticket_dashboard_widget.php, returns {success, widget} or {success:false, error} |
generateDescription() |
as the form changes | Writes the automatic description as one translated sentence, until the analyst types their own |
The rules that decide what the form offers:
TIME_AGGREGATES=created,closed,created_vs_closed- these need a Time grouping (day,month,year).SERIES_RULES- which breakdown each property allows.statuscannot be broken down by status;first_time_fix,training_providedandcreated_vs_closedtake no series.getValidChartTypes()- with a series or a time property, bar or line; otherwise bar, doughnut or pie.
The server repeats these lists in save_ticket_dashboard_widget.php ($allowed_properties, $allowed_chart_types, $allowed_series, $allowed_date_ranges, $allowed_groupings). Adding a property, range or grouping means changing both, plus get_ticket_widget_data.php which has to know how to count it.
TRAP: the escape helper in this file is
escapeHtml(), notesc(). Many FreeITSM scripts callesc(), so it is the name your fingers type. Here it does not exist, andonPropertyChange()- which every open-the-form path calls - threwesc is not definedand stopped the modal opening (#260).tests/escape-helpers-quotes.phpnow fails any script inassets/js/that callsesc()without defining or importing it.
Opening the edit modal (the cog)
cog button onclick="openWidgetEditModal(id)" tickets/dashboard/index.php
└─ WidgetEditor.populateForm(widget) assets/js/widget-editor.js
└─ onPropertyChange() rebuilds Series / Chart type
└─ #widgetEditModal.classList.add('active') the modal appears
The modal is added after populateForm() returns. So any exception inside populateForm() or onPropertyChange() leaves the page looking as if the click did nothing. The only sign is in the browser console. When the cog "does nothing", open the console first.
On Save, handleWidgetSave() copies the saved fields onto the card's in-memory widget and redraws that one card. It does not reload the page.
Endpoints (api/tickets/)
Every one starts with requireModuleAccessJson('tickets'). There is no admin check: any analyst with Tickets access can create, edit or delete library widgets.
| Endpoint | Table touched | Scope |
|---|---|---|
get_ticket_dashboard.php |
reads both, WHERE analyst_id = ? AND is_active = 1 |
personal |
add_ticket_dashboard_widget.php |
INSERT IGNORE into analyst_ticket_dashboard_widgets |
personal |
remove_ticket_dashboard_widget.php |
DELETE ... WHERE analyst_id = ? AND widget_id = ? |
personal |
reorder_ticket_dashboard_widgets.php |
UPDATE sort_order ... WHERE analyst_id = ? |
personal |
get_ticket_widget_library.php |
reads ticket_dashboard_widgets WHERE is_active = 1 |
shared |
save_ticket_dashboard_widget.php |
UPDATE or INSERT on ticket_dashboard_widgets |
shared - the cog edits everyone's copy |
delete_ticket_dashboard_widget.php |
removes it from every dashboard, then is_active = 0 |
shared, soft delete |
get_ticket_widget_data.php |
reads tickets |
the numbers for one card |
get_ticket_widget_data.php - the numbers
It takes widget_id and an optional status, reads the widget's definition, then builds one WHERE that every chart path shares:
- the date range (
buildDateRangeWhere()), on the created or closed date as the property needs; - the department filter (
buildDepartmentWhere(), the widget's JSON list of ids); - the status, if the widget allows it, otherwise its
default_status; - the company scope -
ticketTenantFilter()for the analyst's active company. It is a no-op with one company; t.deleted_datetime IS NULL, so trashed tickets never count.
Then one of three paths: categorical (getCategoricalData()), categorical with a series (getCategoricalWithSeries()), or time-based, which fills in every day, month or year in the range with getTimeLabels() so empty periods draw as zero rather than vanishing. Time labels are formatted with DateFmt, so they follow the analyst's date format.
Tables
ticket_dashboard_widgets - the library. title (100), description (255), chart_type, aggregate_property, series_property, is_status_filterable, default_status, date_range, department_filter (JSON array of department ids), time_grouping, display_order, is_active (soft delete), is_demo. Seeded with the stock widgets in database/freeitsm.sql.
analyst_ticket_dashboard_widgets - one row per analyst per widget. UNIQUE (analyst_id, widget_id), so adding twice is harmless. sort_order, status_filter, is_demo. It is in the analyst lifecycle map as personal, so the rows go with the analyst.
Known gaps (not bugs, not built)
- Editing is shared, not personal. The help says the cog lets you "customise" a widget, but saving changes the library row for everyone who uses it. To change a widget just for yourself, Duplicate it in the library and add the copy.
- The status dropdown on a card is not remembered. It reloads that card's numbers, but nothing writes
analyst_ticket_dashboard_widgets.status_filter, so after a refresh the card is back to All statuses. The column is read but never written.
Testing a change here
php tests/escape-helpers-quotes.php- includes theesc()check.- In a browser, with the console open: click a cog, then in the library click New widget and Edit. All three go through
onPropertyChange(). Change What to count to a time property and back, and Save. - Bump
widget-editor.js?v=on both pages.
Related
- Tickets → Dashboard
- #260 The dashboard cog did nothing
- Escaped text could leave its attribute - why every helper escapes quotes
- Code traps