0 AND $filter GROUP BY k"); while ($row = mysqli_fetch_assoc($sql)) { $rows[(string) $row['k']] = reportSlaFigures($row); } return $rows; }; $buckets = reportBuckets($period); if ($buckets['unit'] === 'day') { // A day's handful of tickets makes a jumpy line - weeks read better $buckets = reportBuckets(['start' => $period['start'], 'end' => $period['end'], 'days' => 60]); } $by_bucket = $grouped(reportBucketSql('ticket_created_at', $buckets['unit'])); $by_priority = $grouped('ticket_priority'); $by_agent = $grouped('ticket_assigned_to'); $priority_rows = []; foreach (['Urgent', 'High', 'Medium', 'Low'] as $priority) { if (isset($by_priority[$priority])) { $priority_rows[] = ['priority' => $priority] + $by_priority[$priority]; } } $agent_rows = []; $names = []; $sql = mysqli_query($mysqli, "SELECT user_id, user_name, user_avatar FROM users WHERE user_type = 1"); while ($row = mysqli_fetch_assoc($sql)) { $names[intval($row['user_id'])] = $row; } foreach ($by_agent as $user_id => $figures) { $user_id = intval($user_id); $agent_rows[] = ['user_id' => $user_id, 'agent' => $names[$user_id]['user_name'] ?? '', 'avatar' => $names[$user_id]['user_avatar'] ?? ''] + $figures; } usort($agent_rows, function ($a, $b) { return $b['tickets'] <=> $a['tickets']; }); // On screen each share carries its late count; the export keeps every count apart $figure_columns = [ 'tickets' => ['label' => 'Tickets', 'type' => 'count'], 'response_met' => ['label' => 'Replied in time', 'type' => 'count', 'screen' => false], 'response_missed' => ['label' => 'Replied late', 'type' => 'count', 'screen' => false], 'response_percent' => ['label' => 'Response', 'type' => 'percent', 'title' => 'First replies in time, of those judged', 'html' => function ($row) { return reportRateHtml($row['response_percent']) . reportLateHtml($row['response_missed']); }], 'resolution_met' => ['label' => 'Resolved in time', 'type' => 'count', 'screen' => false], 'resolution_missed' => ['label' => 'Resolved late', 'type' => 'count', 'screen' => false], 'resolution_percent' => ['label' => 'Resolution', 'type' => 'percent', 'title' => 'Resolved in time, of those judged', 'html' => function ($row) { return reportRateHtml($row['resolution_percent']) . reportLateHtml($row['resolution_missed']); }], 'resolution_pending' => ['label' => 'Still running', 'type' => 'count', 'class' => 'text-body-secondary', 'title' => 'Resolution clock still running'], ]; $priority_columns = ['priority' => ['label' => 'Priority', 'html' => function ($row) { return reportPriorityDot($row['priority']) . escapeHtml($row['priority']); }]] + $figure_columns; $agent_columns = ['agent' => ['label' => 'Assigned to', 'csv' => function ($row) { return $row['user_id'] ? $row['agent'] : 'Unassigned'; }, 'html' => function ($row) { return formatAssignee($row['user_id'], $row['agent'], $row['avatar'], true); }]] + $figure_columns; $month_rows = []; foreach ($buckets['keys'] as $i => $key) { if (isset($by_bucket[$key])) { $month_rows[] = ['when' => $buckets['labels'][$i]] + $by_bucket[$key]; } } $month_columns = ['when' => ['label' => ucfirst($buckets['unit']), 'sort' => false]] + $figure_columns; if (reportExporting('agents')) { reportCsv('SLA by Agent', $agent_columns, $agent_rows); } if (reportExporting('priorities')) { reportCsv('SLA by Priority', $priority_columns, $priority_rows); } if (reportExporting()) { reportCsv('SLA by ' . ucfirst($buckets['unit']), $month_columns, $month_rows, ['when' => 'Total'] + $overall); } require_once "includes/inc_report_layout.php"; $has_sla = $overall['tickets'] > 0; ?>
$period, 'years' => reportPastYears("SELECT DISTINCT YEAR(ticket_created_at) AS y FROM tickets WHERE ticket_sla_id > 0"), 'client' => $client_id, 'export' => ['months' => 'By ' . $buckets['unit'], 'priorities' => 'By priority', 'agents' => 'By agent'], ]); ?> 'Tickets with an SLA', 'value' => reportCount($overall['tickets']), 'sub' => 'Raised in the period'], ['label' => 'First reply in time', 'value_html' => reportRateHtml($overall['response_percent']), 'sub' => $previous['response_percent'] !== null ? 'Was ' . reportPercent($previous['response_percent']) . ' ' . escapeHtml($period['compare']) . '' : ''], ['label' => 'Resolved in time', 'value_html' => reportRateHtml($overall['resolution_percent']), 'sub' => $previous['resolution_percent'] !== null ? 'Was ' . reportPercent($previous['resolution_percent']) . ' ' . escapeHtml($period['compare']) . '' : ''], ['label' => 'Breached and still open', 'value' => reportCount($breached_open), 'tone' => $breached_open ? 'danger' : 'success', 'url' => '/agent/tickets.php?sla=breached' . ($client_id ? "&client_id=$client_id" : ''), 'sub' => 'Whenever they were raised'], ]) ?>
SLA to start measuring them.', 'fa-stopwatch', 'secondary') ?>
'bar', 'labels' => $buckets['labels'], 'series' => [ ['label' => 'Tickets with an SLA', 'data' => array_map(function ($k) use ($by_bucket) { return $by_bucket[$k]['tickets'] ?? 0; }, $keys), 'tone' => 'faint'], ['label' => 'First reply in time', 'data' => array_map(function ($k) use ($by_bucket) { return $by_bucket[$k]['response_percent'] ?? null; }, $keys), 'tone' => 'accent', 'type' => 'line', 'axis' => 'y2'], ['label' => 'Resolved in time', 'data' => array_map(function ($k) use ($by_bucket) { return $by_bucket[$k]['resolution_percent'] ?? null; }, $keys), 'tone' => 'success', 'type' => 'line', 'axis' => 'y2'], ], 'format' => 'count', 'format2' => 'percent', 'max2' => 100, ], '', 'SLA tickets and the share of targets met over the period'); ?>
false]) ?> false, 'totals' => ['when' => 'Total'] + $overall, 'totals_html' => ['response_percent' => reportRateHtml($overall['response_percent']) . reportLateHtml($overall['response_missed']), 'resolution_percent' => reportRateHtml($overall['resolution_percent']) . reportLateHtml($overall['resolution_missed'])]]) ?>