No GLPI padrão não existe registro de leitura: o histórico do chamado guarda o que mudou, nunca quem apenas abriu e leu. Na sustentação de parques GLPI de clientes, isso vira o clássico impasse do "o técnico nem olhou o chamado" contra "eu li, sim" - e sem um dado objetivo é a palavra de um contra a do outro. O módulo gratuito Ticket Tracker fecha essa lacuna registrando cada visualização de ticket. Este guia mostra como ele grava os dados, como configurá-lo sem inflar contadores e três consultas SQL que respondem "quem leu o quê" na hora.
Por que o GLPI não sabe quem leu um chamado
O histórico nativo (glpi_logs) só registra alterações: criação, mudança de status, novo acompanhamento, troca de técnico. Abrir o formulário e ler não muda nenhum campo, então não gera linha nenhuma. Não há, no núcleo do GLPI, um evento de "visualização". O Ticket Tracker resolve isso enganchando no ponto em que o formulário do ticket é renderizado (o hook postShowItem do Ticket): toda vez que uma sessão autenticada exibe um chamado, o módulo grava a visualização. Sem ação manual, sem campo novo no formulário.
As duas camadas de registro: resumo e log
O módulo grava em duas tabelas com propósitos distintos. A de resumo responde "quantas vezes e quando"; a de log responde "cada acesso, um a um":
-- Resumo por (ticket, usuário): uma linha, atualizada a cada acesso.
CREATE TABLE `glpi_plugin_nextool_tickettracker_views` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`tickets_id` int unsigned NOT NULL,
`users_id` int unsigned NOT NULL,
`view_count` int unsigned NOT NULL DEFAULT 1,
`first_view` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_view` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_ticket_user` (`tickets_id`, `users_id`)
);
-- Log detalhado: uma linha por acesso individual.
CREATE TABLE `glpi_plugin_nextool_tickettracker_viewlog` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`tickets_id` int unsigned NOT NULL,
`users_id` int unsigned NOT NULL,
`view_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
A chave UNIQUE (tickets_id, users_id) é o coração da tabela de resumo: cada par ticket/usuário tem exatamente uma linha. A gravação usa INSERT ... ON DUPLICATE KEY UPDATE view_count = view_count + 1, um upsert atômico - dois acessos simultâneos do mesmo usuário não criam linha duplicada nem perdem contagem. A aba "Visualizações" no ticket lê o resumo (tabela com todos os visualizadores, contagem, primeira e última leitura) e oferece uma cascata expansível que puxa do log cada acesso individual, com data e hora.
Ativação e checklist de configuração
O módulo é gratuito e liga no catálogo NexTool. Depois de ativado, a configuração fica em Módulos > Ticket Tracker, aba Funcionalidades:
- Módulo ativo - o mesmo toggle da página de Módulos. Desligado, nada é rastreado.
- Tracking ativo (tracking_enabled) - liga a gravação das visualizações. Pode ficar ligado com a aba escondida, se você quiser coletar sem expor.
- Exibir aba no ticket (show_tab_on_ticket) - mostra ou oculta a aba "Visualizações" no formulário do chamado.
- Intervalo mínimo (s) (min_interval_seconds, padrão 60) - janela de debounce: acessos do mesmo usuário ao mesmo ticket dentro desse intervalo contam como um só.
- Ignorados (aba Permissões adicionais) - usuários, perfis ou grupos que visualizam sem serem rastreados. Tudo em branco = todos os acessos contam.
O erro de campo que infla (ou zera) os contadores
O erro comum é zerar o intervalo mínimo achando que "quanto mais granular, melhor". O gancho dispara a cada render do formulário - inclusive em recarregamentos, navegação de volta e reaberturas da aba. Com min_interval_seconds = 0, um técnico que deixa o chamado aberto e dá três F5 vira "três leituras", e o view_count perde o sentido de "quantas vezes ele de fato voltou ao assunto". O padrão de 60 segundos existe justamente para colapsar a rajada de recarregamentos numa leitura só. Use 0 apenas em diagnóstico pontual e volte ao padrão depois.
O segundo erro é mais sutil e nós batemos nele em cliente: até a versão 1.2.0 do módulo, a gravação dependia da permissão de ver a aba de Visualizações - ou seja, só perfis administrativos eram contados. O requerente e os observadores abriam o chamado e nada era registrado, então a base subnotificava exatamente quem mais interessa numa disputa de SLA. Desacoplamos o registro da permissão de ver a aba: hoje qualquer sessão autenticada que renderiza o ticket é contabilizada - requerente, observador ou técnico - independente do perfil. Se você opera uma versão anterior, atualize antes de confiar nos números.
Matriz de decisão: intervalo e ignorados por cenário
O ajuste certo depende do que você quer provar. A matriz que usamos no desenho:
| Cenário de operação | Tracking | Intervalo mínimo (s) | Quem colocar em "ignorados" |
|---|---|---|---|
| Auditoria de leitura / SLA (padrão) | Ligado | 60 | Ninguém |
| Parque com refresh automático ou painel de parede | Ligado | 180 a 300 | Contas de integração / robôs |
| Supervisão que não deve inflar o contador | Ligado | 60 | Perfil de gestores / auditores |
| Diagnóstico pontual (contar cada acesso) | Ligado | 0 (temporário) | Ninguém |
Três consultas SQL que respondem na hora
A aba resolve o caso a caso; para visão de parque, vá direto ao banco. Chamados abertos atribuídos que o técnico responsável ainda não abriu (type = 2 em glpi_tickets_users é o técnico atribuído):
-- Chamados abertos atribuídos que o técnico responsável NUNCA visualizou.
SELECT t.id AS chamado, t.name AS titulo, u.name AS tecnico, t.date AS aberto_em
FROM glpi_tickets t
JOIN glpi_tickets_users tu ON tu.tickets_id = t.id AND tu.type = 2
JOIN glpi_users u ON u.id = tu.users_id
LEFT JOIN glpi_plugin_nextool_tickettracker_views v
ON v.tickets_id = t.id AND v.users_id = tu.users_id
WHERE t.is_deleted = 0
AND t.status IN (1, 2, 3, 4) -- novo, atribuído, planejado, pendente
AND v.id IS NULL -- nenhuma leitura registrada pelo técnico
ORDER BY t.date ASC;
Latência de leitura - quantos minutos o chamado ficou aberto até a primeira leitura do técnico atribuído. É a métrica que antecede a primeira resposta e costuma explicar SLA estourado:
-- Minutos entre a abertura e a 1a leitura pelo técnico atribuído.
SELECT t.id AS chamado,
TIMESTAMPDIFF(MINUTE, t.date, v.first_view) AS minutos_ate_1a_leitura
FROM glpi_tickets t
JOIN glpi_tickets_users tu ON tu.tickets_id = t.id AND tu.type = 2
JOIN glpi_plugin_nextool_tickettracker_views v
ON v.tickets_id = t.id AND v.users_id = tu.users_id
WHERE t.is_deleted = 0
ORDER BY minutos_ate_1a_leitura DESC;
Os visualizadores mais ativos de um chamado específico, do resumo, para saber quem realmente acompanha (troque 12345 pelo ID do ticket):
-- Quem mais abriu o chamado 12345, do mais recorrente ao menos.
SELECT u.name AS usuario, v.view_count, v.first_view, v.last_view
FROM glpi_plugin_nextool_tickettracker_views v
JOIN glpi_users u ON u.id = v.users_id
WHERE v.tickets_id = 12345
ORDER BY v.view_count DESC;
Leitura não é resposta: o que o contador prova
O dado é poderoso, mas tem um limite honesto: ele prova que o formulário foi renderizado para aquele usuário, não que a pessoa leu com atenção nem que agiu. Um view_count alto sem acompanhamento é sinal amarelo - alguém está abrindo e fechando sem resolver. Um chamado com zero visualização do responsável e o SLA correndo é sinal vermelho - ninguém sequer olhou. Na sustentação, o valor real não é vigiar técnico, é fechar a lacuna probatória: quando o cliente pergunta "por que esse chamado ficou parado três dias?", a resposta deixa de ser opinião e passa a ter carimbo de data e hora.
Precisa de visibilidade real sobre o atendimento no seu GLPI, com os módulos certos ativados e ajustados ao seu fluxo? Conheça a sustentação de GLPI da NexTool.
Este conteúdo foi produzido com auxílio de inteligência artificial e revisado pela equipe Nextool Solutions.