Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/Glpi/Dashboard/Grid.php
Original file line number Diff line number Diff line change
Expand Up @@ -1647,6 +1647,15 @@ public function getAllDasboardCards($force = false): array
'filters' => Filter::getAppliableFilters(\Computer::getTable()),
];

$cards["ticket_by_group_and_status"] = [
Comment thread
Herafia marked this conversation as resolved.
Outdated
'widgettype' => ['hBars', 'stackedHBars'],
'itemtype' => "\\Ticket",
'group' => __('Assistance'),
'label' => __("Number of opened and closed tickets by group"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'label' => __("Number of opened and closed tickets by group"),
'label' => __("Number of opened and solved tickets by group"),

From the code, the "closed tickets" part is actually solved + closed tickets: $closed_statuses = implode(',', [Ticket::SOLVED, Ticket::CLOSED]);.

With that in mind, I think "solved" would be a more precise label here.

'provider' => "Glpi\\Dashboard\\Provider::ticketsByGroupAndStatus",
'filters' => Filter::getAppliableFilters(Ticket::getTable()),
];

$cards["RemindersList"] = [
'widgettype' => ["articleList"],
'label' => sprintf(__('List of %s'), Reminder::getTypeName(Session::getPluralNumber())),
Expand Down
93 changes: 93 additions & 0 deletions src/Glpi/Dashboard/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,99 @@ public static function computersByOperatingSystem(array $params = []): array
];
}

/**
* count number of opened and closed tickets grouped by their assigned group
* @param array<string, mixed> $params
* @return array<string, mixed>
*/
public static function ticketsByGroupAndStatus(array $params = []): array
{
$DB = DBConnection::getReadConnection();

$default_params = [
'label' => "",
'icon' => Ticket::getIcon(),
'apply_filters' => [],
];
$params = array_merge($default_params, $params);

$ticket_table = Ticket::getTable();
$group_ticket_table = Group_Ticket::getTable();
$group_table = Group::getTable();

$opened_statuses = implode(',', [Ticket::INCOMING, Ticket::ASSIGNED, Ticket::PLANNED, Ticket::WAITING]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$opened_statuses = implode(',', [Ticket::INCOMING, Ticket::ASSIGNED, Ticket::PLANNED, Ticket::WAITING]);
$opened_statuses = implode(',', [Ticket::getNotSolvedStatusArray()]);

I think you forget Ticket::APPROVAL (unless it is missing on purpose?), if that is the case you can use getNotSolvedStatusArray() so you are sure to not miss anything ;)

$closed_statuses = implode(',', [Ticket::SOLVED, Ticket::CLOSED]);

Profiler::getInstance()->start(__METHOD__ . ' build SQL criteria');
$criteria = array_merge_recursive(
[
'SELECT' => [
"$group_table.name AS group_name",
"$group_table.id AS group_id",
Comment thread
Herafia marked this conversation as resolved.
Outdated
new QueryExpression("COUNT(CASE WHEN $ticket_table.status IN ($opened_statuses) THEN $ticket_table.id END) AS opened"),
new QueryExpression("COUNT(CASE WHEN $ticket_table.status IN ($closed_statuses) THEN $ticket_table.id END) AS closed"),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
new QueryExpression("COUNT(CASE WHEN $ticket_table.status IN ($opened_statuses) THEN $ticket_table.id END) AS opened"),
new QueryExpression("COUNT(CASE WHEN $ticket_table.status IN ($closed_statuses) THEN $ticket_table.id END) AS closed"),
new QueryExpression("COUNT(DISTINCT CASE WHEN $ticket_table.status IN ($opened_statuses) THEN $ticket_table.id END) AS opened"),
new QueryExpression("COUNT(DISTINCT CASE WHEN $ticket_table.status IN ($closed_statuses) THEN $ticket_table.id END) AS closed"),

I have some duplicated results when testing, adding UNIQUE seems to fix them.

For example, in a database with only one ticket attached to two groups and using these groups as a filter I would get 2 results:
Image

Image

],
'FROM' => $ticket_table,
'INNER JOIN' => [
$group_ticket_table => [
'ON' => [
$group_ticket_table => 'tickets_id',
$ticket_table => 'id',
[
'AND' => [
"$group_ticket_table.type" => Group_Ticket::ASSIGN,
],
],
],
],
$group_table => [
'ON' => [
$group_table => 'id',
$group_ticket_table => 'groups_id',
],
],
],
'WHERE' => [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we need something to keep only the groups requested by the filters?

If I filter for "Group A", all groups still shows:

Image

"$ticket_table.is_deleted" => 0,
] + getEntitiesRestrictCriteria($ticket_table),
'GROUPBY' => "$group_table.id",
Comment thread
Herafia marked this conversation as resolved.
],
Ticket::getCriteriaFromProfile(),
self::getFiltersCriteria($ticket_table, $params['apply_filters'])
);
$iterator = $DB->request($criteria);
Profiler::getInstance()->stop(__METHOD__ . ' build SQL criteria');

$data = [
'labels' => [],
'series' => [
['name' => __('Opened'), 'data' => []],
['name' => __('Closed'), 'data' => []],
Comment on lines +1350 to +1351

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how to fix it but if you only have one type of tickets, the numbers overlap and become unreadable:

Image

],
];
foreach ($iterator as $result) {
$group_name = $result['group_name'] ?? null;
Comment thread
Herafia marked this conversation as resolved.
Outdated

if (!$group_name) {
continue;
}

$data['labels'][] = $group_name;
$data['series'][0]['data'][] = (int) $result['opened'];
$data['series'][1]['data'][] = (int) $result['closed'];
}

if (count($data['labels']) === 0) {
$data['nodata'] = true;
}

return [
'data' => $data,
'label' => $params['label'],
'icon' => $params['icon'],
];
}

/**
* Get a list of article for an compatible item (with date,name,text fields)
*
Expand Down
68 changes: 68 additions & 0 deletions tests/functional/Glpi/Dashboard/ProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1068,4 +1068,72 @@ public function testComputersByAge()

$this->assertGreaterThan(0, $nb_items);
}

public function testTicketsByGroupAndStatus()
{
$this->login();
$self = getItemByTypeName(User::class, TU_USER);

$group1 = $this->createItem(\Group::class, [
'name' => 'test dashboard group 1 for tickets by status',
]);
$group2 = $this->createItem(\Group::class, [
'name' => 'test dashboard group 2 for tickets by status',
]);

$this->createItem(\Group_User::class, [
'groups_id' => $group1->getID(),
'users_id' => $self->getID(),
]);
$this->createItem(\Group_User::class, [
'groups_id' => $group2->getID(),
'users_id' => $self->getID(),
]);

\Session::loadGroups();

$this->attachTicketToGroup('group1 opened 1', \Ticket::ASSIGNED, $group1);
$this->attachTicketToGroup('group2 opened', \Ticket::ASSIGNED, $group2);
$this->attachTicketToGroup('group1 opened 2', \Ticket::INCOMING, $group1);
$this->attachTicketToGroup('group1 closed', \Ticket::CLOSED, $group1);
$this->attachTicketToGroup('group2 closed/solved 1', \Ticket::SOLVED, $group2);
$this->attachTicketToGroup('group2 closed 2', \Ticket::CLOSED, $group2);

$result = Provider::ticketsByGroupAndStatus();
$this->assertArrayHasKey('data', $result);
$this->assertArrayHasKey('label', $result);
$this->assertArrayHasKey('icon', $result);

$this->assertArrayHasKey('labels', $result['data']);
$this->assertArrayHasKey('series', $result['data']);
$this->assertCount(2, $result['data']['series']);

$group1_index = array_search($group1->fields['name'], $result['data']['labels'], true);
$group2_index = array_search($group2->fields['name'], $result['data']['labels'], true);
$this->assertNotFalse($group1_index, 'group1 must be included in report labels');
$this->assertNotFalse($group2_index, 'group2 must be included in report labels');
$this->assertNotSame($group1_index, $group2_index, 'each group must have its own row');

$this->assertSame(2, $result['data']['series'][0]['data'][$group1_index], 'group1: 2 opened tickets expected');
$this->assertSame(1, $result['data']['series'][1]['data'][$group1_index], 'group1: 1 closed ticket expected');
$this->assertSame(1, $result['data']['series'][0]['data'][$group2_index], 'group2: 1 opened ticket expected');
$this->assertSame(2, $result['data']['series'][1]['data'][$group2_index], 'group2: 2 closed tickets expected');
}

private function attachTicketToGroup(string $name, int $status, \Group $group): \Ticket
{
$ticket = $this->createItem(\Ticket::class, [
'name' => "test dashboard $name",
'content' => 'blablabla',
'status' => $status,
'entities_id' => $this->getTestRootEntity(true),
]);
$this->createItem(\Group_Ticket::class, [
'tickets_id' => $ticket->getID(),
'groups_id' => $group->getID(),
'type' => \Group_Ticket::ASSIGN,
]);

return $ticket;
}
}
Loading