378 lines
14 KiB
PHP
378 lines
14 KiB
PHP
<?php
|
|
use Respect\Validation\Validator as DataValidator;
|
|
use RedBeanPHP\Facade as RedBean;
|
|
|
|
/**
|
|
* @api {post} /system/get-stats Get overall stats
|
|
* @apiVersion 4.9.0
|
|
*
|
|
* @apiName Stats
|
|
*
|
|
* @apiGroup System
|
|
*
|
|
* @apiDescription This path retrieves the last stats.
|
|
*
|
|
* @apiPermission staff1
|
|
*
|
|
* @apiParam {Number} staffId Id of the current staff.
|
|
* @apiParam {Number[]} tags The ids of the tags for the custom stats.
|
|
* @apiParam {Number[]} dateRange The array with start and end date of the range for the custom stats.
|
|
* @apiParam {Number[]} departments The ids of the departments for the custom stats.
|
|
* @apiParam {Number[]} owners The ids of the owners for the custom stats.
|
|
*
|
|
* @apiUse NO_PERMISSION
|
|
* @apiUse INVALID_PERIOD
|
|
* @apiUse INVALID_DEPARTMENT_FILTER
|
|
* @apiUse INVALID_DATE_RANGE_FILTER
|
|
* @apiUse INVALID_OWNER_FILTER
|
|
*
|
|
* @apiSuccess {[StatList](#api-Data_Structures-ObjectStatlist)[]} data Array of the stats
|
|
*
|
|
*/
|
|
|
|
class GetStatsController extends Controller {
|
|
const PATH = '/get-stats';
|
|
const METHOD = 'POST';
|
|
private $table;
|
|
private $groupBy;
|
|
|
|
private $dateRangeFilter;
|
|
private $departmentsFilter;
|
|
private $tagsFilter;
|
|
private $ownersFilter;
|
|
|
|
public function validations() {
|
|
return [
|
|
'permission' => 'staff_1',
|
|
'requestData' => [
|
|
'dateRange' => [
|
|
'validation' => DataValidator::oneOf(DataValidator::validDateRange(), DataValidator::nullType()),
|
|
'error' => ERRORS::INVALID_DATE_RANGE_FILTER
|
|
],
|
|
'departments' => [
|
|
'validation' => DataValidator::oneOf(DataValidator::validDepartmentsId(), DataValidator::nullType()),
|
|
'error' => ERRORS::INVALID_DEPARTMENT_FILTER
|
|
],
|
|
'tags' => [
|
|
'validation' => DataValidator::oneOf(DataValidator::validTagsId(), DataValidator::nullType()),
|
|
'error' => ERRORS::INVALID_TAG_FILTER
|
|
],
|
|
'owners' => [
|
|
'validation' => DataValidator::oneOf(DataValidator::validOwnersId(), DataValidator::nullType()),
|
|
'error' => ERRORS::INVALID_OWNER_FILTER
|
|
],
|
|
]
|
|
];
|
|
}
|
|
|
|
public function handler() {
|
|
$this->table = " ticket LEFT JOIN ticketevent ON ticket.id = ticketevent.ticket_id
|
|
LEFT JOIN tag_ticket ON ticket.id = tag_ticket.ticket_id ";
|
|
$this->groupBy = " GROUP BY ticket.id ";
|
|
$this->dateRangeFilter = $this->addDateRangeFilter();
|
|
$this->departmentsFilter = $this->addDepartmentsFilter();
|
|
$this->tagsFilter = $this->addTagsFilter();
|
|
$this->ownersFilter = $this->addOwnersFilter();
|
|
|
|
Response::respondSuccess([
|
|
'created' => $this->getNumberOfCreatedTickets(),
|
|
'open' => $this->getNumberOfOpenTickets(),
|
|
'closed' => $this->getNumberOfClosedTickets(),
|
|
'instant' => $this->getNumberOfInstantTickets(),
|
|
'reopened' => $this->getNumberOfReopenedTickets(),
|
|
'created_by_hour' => $this->getNumberOfCreatedTicketsByHour(),
|
|
'created_by_weekday' => $this->getNumberOfCreatedTicketsByWeekday(),
|
|
'average_first_reply' => $this->getAverageFirstReply(),
|
|
'average_first_closed' => $this->getAverageFirstClosed(),
|
|
'average_last_closed' => $this->getAverageLastClosed(),
|
|
'average_department_hops' => $this->getAverageDepartmentHops(),
|
|
'average_staff_hops' => $this->getAverageStaffHops()
|
|
]);
|
|
}
|
|
|
|
// This function assumes there is a previous condition
|
|
private function addDateRangeFilter() {
|
|
$dateRange = json_decode(Controller::request('dateRange'));
|
|
if ($dateRange === NULL) return " ";
|
|
$sql = " AND ticket.date >= {$dateRange[0]} AND ticket.date <= {$dateRange[1]} ";
|
|
return $sql;
|
|
}
|
|
|
|
// This function assumes there is a previous condition
|
|
private function addDepartmentsFilter() {
|
|
$departments = json_decode(Controller::request('departments'));
|
|
if ($departments === NULL || empty($departments)) return " ";
|
|
$sql = " AND ";
|
|
for ($i = 0; $i < count($departments); ++$i) {
|
|
$departmentId = $departments[$i];
|
|
$departments[$i] = " ticket.department_id={$departmentId} ";
|
|
}
|
|
$sql .= '(' . join(" OR ", $departments) . ')';
|
|
return $sql;
|
|
}
|
|
|
|
// This function assumes there is a previous condition
|
|
private function addTagsFilter() {
|
|
$tags = json_decode(Controller::request('tags'));
|
|
if ($tags === NULL || empty($tags)) return " ";
|
|
$sql = " AND ";
|
|
for ($i = 0; $i < count($tags); ++$i) {
|
|
$tagId = $tags[$i];
|
|
$tags[$i] = " tag_ticket.tag_id={$tagId} ";
|
|
}
|
|
$sql .= '(' . join(" OR ", $tags) . ')';
|
|
return $sql;
|
|
}
|
|
|
|
// This function assumes there is a previous condition
|
|
private function addOwnersFilter() {
|
|
$owners = json_decode(Controller::request('owners'));
|
|
if ($owners === NULL || empty($owners)) return " ";
|
|
$sql = " AND ";
|
|
for ($i = 0; $i < count($owners); ++$i) {
|
|
$ownerId = $owners[$i];
|
|
$owners[$i] = " ticket.owner_id={$ownerId} ";
|
|
}
|
|
$sql .= '(' . join(" OR ", $owners) . ')';
|
|
return $sql;
|
|
}
|
|
|
|
public function getNumberOfCreatedTickets() {
|
|
return (int) RedBean::getCell("
|
|
SELECT
|
|
COUNT(*)
|
|
FROM
|
|
(SELECT COUNT(*) FROM {$this->table} WHERE 1=1
|
|
{$this->dateRangeFilter}
|
|
{$this->departmentsFilter}
|
|
{$this->tagsFilter}
|
|
{$this->ownersFilter}
|
|
{$this->groupBy}) AS Z;
|
|
");
|
|
}
|
|
|
|
public function getNumberOfOpenTickets() {
|
|
return (int) RedBean::getCell("
|
|
SELECT
|
|
COUNT(*)
|
|
FROM
|
|
(SELECT COUNT(*) FROM {$this->table} WHERE closed=0
|
|
{$this->dateRangeFilter}
|
|
{$this->departmentsFilter}
|
|
{$this->tagsFilter}
|
|
{$this->ownersFilter}
|
|
{$this->groupBy}) AS Z;
|
|
");
|
|
}
|
|
|
|
public function getNumberOfClosedTickets() {
|
|
return (int) RedBean::getCell("
|
|
SELECT
|
|
COUNT(*)
|
|
FROM
|
|
(SELECT COUNT(*) FROM {$this->table} WHERE closed=1
|
|
{$this->dateRangeFilter}
|
|
{$this->departmentsFilter}
|
|
{$this->tagsFilter}
|
|
{$this->ownersFilter}
|
|
{$this->groupBy}) AS Z;
|
|
");
|
|
}
|
|
|
|
public function getNumberOfInstantTickets() {
|
|
return (int) RedBean::getCell("
|
|
SELECT
|
|
COUNT(*)
|
|
FROM
|
|
(SELECT
|
|
COUNT(*)
|
|
FROM
|
|
{$this->table}
|
|
WHERE
|
|
ticketevent.type = 'COMMENT'
|
|
AND ticketevent.author_staff_id
|
|
AND private = 0
|
|
AND closed = 1
|
|
{$this->dateRangeFilter}
|
|
{$this->departmentsFilter}
|
|
{$this->tagsFilter}
|
|
{$this->ownersFilter}
|
|
{$this->groupBy}
|
|
HAVING COUNT(*) = 1) AS Z;
|
|
");
|
|
}
|
|
|
|
public function getNumberOfReopenedTickets() {
|
|
return (int) RedBean::getCell("
|
|
SELECT
|
|
COUNT(*)
|
|
FROM
|
|
(SELECT COUNT(*) FROM {$this->table} WHERE reopened=1
|
|
{$this->dateRangeFilter}
|
|
{$this->departmentsFilter}
|
|
{$this->tagsFilter}
|
|
{$this->ownersFilter}
|
|
{$this->groupBy}) AS Z;
|
|
");
|
|
}
|
|
|
|
// Returns an array of size 24 with the number of tickets created for each hour
|
|
public function getNumberOfCreatedTicketsByHour() {
|
|
$result = RedBean::getAll("
|
|
SELECT
|
|
VALID_TICKETS.HOUR_DAY, COUNT(RAW_CNT) AS CNT
|
|
FROM
|
|
(SELECT
|
|
COUNT(*) AS RAW_CNT,
|
|
ticket.date % 10000 DIV 100 AS HOUR_DAY
|
|
FROM {$this->table} WHERE 1=1
|
|
{$this->dateRangeFilter}
|
|
{$this->departmentsFilter}
|
|
{$this->tagsFilter}
|
|
{$this->ownersFilter}
|
|
{$this->groupBy}) AS VALID_TICKETS
|
|
GROUP BY VALID_TICKETS.HOUR_DAY;
|
|
");
|
|
|
|
$ans = array_fill(0, 24, 0);
|
|
for ($i = 0; $i < count($result); ++$i) {
|
|
$hour = (int)$result[$i]["HOUR_DAY"];
|
|
$cnt = (int)$result[$i]["CNT"];
|
|
$ans[$hour] = $cnt;
|
|
}
|
|
return $ans;
|
|
}
|
|
|
|
// Returns an array of size 7 with the number of tickets created by weekday (0 - monday, ..., 6 - sunday)
|
|
public function getNumberOfCreatedTicketsByWeekday() {
|
|
$result = RedBean::getAll("
|
|
SELECT
|
|
VALID_TICKETS.WEEK_DAY, COUNT(RAW_CNT) AS CNT
|
|
FROM
|
|
(SELECT
|
|
COUNT(*) AS RAW_CNT, WEEKDAY(STR_TO_DATE(CONVERT(ticket.date, CHAR), '%Y%m%d%H%i')) AS WEEK_DAY
|
|
FROM {$this->table} WHERE 1 = 1
|
|
{$this->dateRangeFilter}
|
|
{$this->departmentsFilter}
|
|
{$this->tagsFilter}
|
|
{$this->ownersFilter}
|
|
{$this->groupBy}) AS VALID_TICKETS
|
|
GROUP BY VALID_TICKETS.WEEK_DAY;
|
|
");
|
|
|
|
$ans = array_fill(0, 7, 0);
|
|
for ($i = 0; $i < count($result); ++$i) {
|
|
$hour = (int)$result[$i]["WEEK_DAY"];
|
|
$cnt = (int)$result[$i]["CNT"];
|
|
$ans[$hour] = $cnt;
|
|
}
|
|
|
|
return $ans;
|
|
}
|
|
|
|
// Returns the number of seconds (as int), on average, that a ticket waits for the first reply of a staff
|
|
public function getAverageFirstReply() {
|
|
return (int) RedBean::getCell("
|
|
SELECT AVG(SECS)
|
|
FROM (
|
|
SELECT
|
|
ticket.id,
|
|
UNIX_TIMESTAMP(STR_TO_DATE(CONVERT(MIN(ticketevent.date), CHAR), '%Y%m%d%H%i')) -
|
|
UNIX_TIMESTAMP(STR_TO_DATE(CONVERT(ticket.date, CHAR), '%Y%m%d%H%i')) AS SECS
|
|
FROM
|
|
{$this->table}
|
|
WHERE
|
|
ticketevent.type = 'COMMENT'
|
|
AND ticketevent.author_staff_id
|
|
AND private = 0
|
|
{$this->dateRangeFilter}
|
|
{$this->departmentsFilter}
|
|
{$this->tagsFilter}
|
|
{$this->ownersFilter}
|
|
{$this->groupBy}) AS Z;
|
|
");
|
|
}
|
|
|
|
// Returns the number of seconds (as int), on average, that a ticket waits until it's first closed
|
|
public function getAverageFirstClosed() {
|
|
return (int) RedBean::getCell("
|
|
SELECT
|
|
AVG(SECS)
|
|
FROM
|
|
(SELECT
|
|
ticket.id,
|
|
UNIX_TIMESTAMP(STR_TO_DATE(CONVERT( MIN(ticket.first_closed_at) , CHAR), '%Y%m%d%H%i')) - UNIX_TIMESTAMP(STR_TO_DATE(CONVERT( ticket.date , CHAR), '%Y%m%d%H%i')) AS SECS
|
|
FROM
|
|
{$this->table}
|
|
WHERE
|
|
first_closed_at IS NOT NULL
|
|
{$this->dateRangeFilter}
|
|
{$this->departmentsFilter}
|
|
{$this->tagsFilter}
|
|
{$this->ownersFilter}
|
|
{$this->groupBy}) AS Z;
|
|
");
|
|
}
|
|
|
|
// Returns the number of seconds (as int), on average, that a ticket waits until it's closed for the last time
|
|
public function getAverageLastClosed() {
|
|
return (int) RedBean::getCell("
|
|
SELECT
|
|
AVG(SECS)
|
|
FROM
|
|
(SELECT
|
|
ticket.id,
|
|
UNIX_TIMESTAMP(STR_TO_DATE(CONVERT( MIN(ticket.last_closed_at) , CHAR), '%Y%m%d%H%i')) - UNIX_TIMESTAMP(STR_TO_DATE(CONVERT( ticket.date , CHAR), '%Y%m%d%H%i')) AS SECS
|
|
FROM
|
|
{$this->table}
|
|
WHERE
|
|
first_closed_at IS NOT NULL
|
|
{$this->dateRangeFilter}
|
|
{$this->departmentsFilter}
|
|
{$this->tagsFilter}
|
|
{$this->ownersFilter}
|
|
{$this->groupBy}) AS Z;
|
|
");
|
|
}
|
|
|
|
// Returns the average number of departments (as double) a ticket has been in
|
|
public function getAverageDepartmentHops() {
|
|
return (double) RedBean::getCell("
|
|
SELECT
|
|
AVG(CNT)
|
|
FROM
|
|
(SELECT
|
|
ticket.id, ticket.total_departments AS CNT
|
|
FROM
|
|
{$this->table}
|
|
WHERE
|
|
1 = 1
|
|
{$this->dateRangeFilter}
|
|
{$this->departmentsFilter}
|
|
{$this->tagsFilter}
|
|
{$this->ownersFilter}
|
|
{$this->groupBy}) AS Z;
|
|
");
|
|
}
|
|
|
|
// Returns the average number of staff members a ticket has been assigned to
|
|
public function getAverageStaffHops() {
|
|
return (double) RedBean::getCell("
|
|
SELECT
|
|
AVG(CNT)
|
|
FROM
|
|
(SELECT
|
|
ticket.id, ticket.total_owners AS CNT
|
|
FROM
|
|
{$this->table}
|
|
WHERE
|
|
1 = 1
|
|
{$this->dateRangeFilter}
|
|
{$this->departmentsFilter}
|
|
{$this->tagsFilter}
|
|
{$this->ownersFilter}
|
|
{$this->groupBy}) AS Z;
|
|
");
|
|
}
|
|
}
|