Download PDF
Download page Пример отчета о работе отдела технической поддержки.
Пример отчета о работе отдела технической поддержки
Пример отчета о работе отдела технической поддержки
В данном примере мы будем выводить информацию о тикетах, решение которых потребовало больше времени, чем будет запрошено в форме отчета.
Описания отчета
Для этого создадим xml-файл описания отчета: /usr/local/mgr5/etc/xml/billmgr_mod_<name>.xml, где <name> — название отчета (для примера назовём отчет difficulttickets, файл будет называться billmgr_mod_difficulttickets.xml) со следующим содержимым:
<?xml version="1.0" encoding="UTF-8"?> <mgrdata> <handler name="reportdiff.py" type="xml"> <event name="reportlist" after="yes"/> <event name="report.difficulttickets" after="yes" priority='after'/> <func name="report.clienttickets" type="xml"/> </handler> <metadata name="report.difficulttickets" type="report" level="29"> <form> <period name="period" default="currentmonth"/> <field name="interval"> <input name="interval" type="text" save="yes" required="yes" check="int"/> </field> </form> <band name="project" fullwidth="yes"> <query>SELECT id, name FROM project ORDER BY name</query> <col name="name" type="data"/> <band name="difficulttickets" fullwidth="yes"> <query>SELECT DISTINCT t.id, t.name AS messtitle, a.name AS client, t.date_last FROM ticket_message tm JOIN ticket t ON t.id = tm.ticket JOIN account a ON a.id = t.account_client JOIN user e ON e.id = tm.user WHERE TIMESTAMPDIFF(MINUTE, (SELECT tm2.date_post FROM ticket_message tm2 JOIN user u ON u.id = tm2.user WHERE tm2.ticket = tm.ticket AND u.level = 16 AND tm2.id < tm.id ORDER BY tm2.id DESC LIMIT 1),tm.date_post) > [[interval]] AND e.level = 29 AND tm.date_post >= [[periodstart]] AND t.project = [[project.id]] AND tm.date_post <= [[periodend]] +INTERVAL 1 DAY ORDER BY t.id;</query> <col name="id" type="data" sort="digit" nestedreport="ticket_all.edit"/> <col name="messtitle" type="data"/> <col name="client" type="data" nestedreport="report.clienttickets"/> <col name="date_last" type="data"/> </band> </band> </metadata> <metadata name="report.clienttickets" type="report" level="29"> <toolbar view="buttontext"> <toolgrp name="back"> <toolbtn func="report.difficulttickets" name="back" img="t-back" type="back" sprite="yes"/> </toolgrp> </toolbar> <band name="clienttickets" fullwidth="yes"> <col name="id" type="data" sort="digit" nestedreport="ticket_all.edit"/> <col name="name" type="data"/> <col name="status" type="data"/> </band> </metadata> <lang name="ru"> <messages name="project"> <msg name="id">Id</msg> <msg name="name">Провайдер</msg> </messages> <messages name="report.difficulttickets"> <msg name="title">Отчет по сложным тикетам</msg> <msg name="id">Id</msg> <msg name="messtitle">Тема тикета</msg> <msg name="client">Клиент</msg> <msg name="date_last">Дата последнего ответа</msg> <msg name="interval">Интервалы времени</msg> <msg name="report_info">Отчет по тикетам, закрытым за более чем указанное время</msg> <msg name="hint_interval">Интервалы времени в минутах</msg> <msg name="periodstart">Начальная дата диапазона</msg> <msg name="periodend">Конечная дата диапазона</msg> </messages> <messages name="report.clienttickets"> <include name="ticket_all"/> <msg name="title">Тикеты клиента</msg> <msg name="id">Id</msg> <msg name="name">Имя тикета</msg> <msg name="status">Статус тикета</msg> </messages> </lang> </mgrdata>
Объявление обработчиков
<handler name="reportdiff.py" type="xml"> <event name="reportlist" after="yes"/> <event name="report.difficulttickets" after="yes" priority='after'/> <func name="report.clienttickets" type="xml"/> </handler>
- Первое объявление обработчика говорит, о том что после вызова функции reportlist (Отчеты), будет вызван скрипт reportdiff.py добавляющий наш отчет на форму выбора отчетов.
- Второй раз этот обработчик будет вызван для заполнения полей формы нашего отчета с именем report.difficulttickets (Отчет по сложным тикетам) значениями по умолчанию.
Подробнее об обработчиках и плагинах можно прочитать по ссылке: XML
Описание метаданных отчета
<metadata name="report.difficulttickets" type="report" level="29">
Подробнее рассмотрим параметры описания нашего отчета:
- name — имя функции отчета (обязательно должно начинаться на report.);
- type — тип метаданных имеет значение report и указывает, что это отчет;
- level — уровень доступа, указывает, что данный отчет доступен только пользователям с правами администратора;
- super (root) доступ с уровнем 30
- admin доступ с уровнем 29
- user доступ с уровнем 16
Описание формы отчета
<form> <period name="period" default="currentmonth"/> <field name="interval"> <input name="interval" type="text" save="yes" required="yes" check="int"/> </field> </form>
- <period/> — указывает на то что необходимо сгенерировать элемент select с именем period и заполнить его стандартными периодами составления отчетов ("текущий день", "текущий месяц", "текущий день" и т.д.).
- Атрибут default="currentmonth" — значением по умолчанию для данного поля будет выбран "текущий месяц";
- <field>...</field> — стандартное поля описания для поля ввода с именем interval.
Подробнее про описание форм можно узнать по ссылке Описание форм и Валидаторы
Описание структуры данных отчета
Здесь мы описываем первый бэнд и вложенный запрос, которые при формировании отчета создадут нам несколько (по количеству провайдеров) таблиц в отчете с сортировкой тикетов по провайдеру.
<band name="project" fullwidth="yes"> <query>SELECT id, name FROM project ORDER BY name</query> <col name="name" type="data"/
Подробное описание структуры данных отчета можно узнать перейдя по ссылке Описание отчетов .
В данном описании нужно обратить внимание на описание колонки идентификаторов тикетов <col name="id"...>. Атрибут nestedreport="ticket_all.edit" говорит о том что при нажатии на элемент выборки отчета, будет открыт дочерний отчет.
<band name="difficulttickets" fullwidth="yes"> <query>...</query> <col name="id" type="data" sort="digit" nestedreport="ticket_all.edit"/> <col name="client" type="data" nestedreport="report.clienttickets"/> <col name="messtitle" type="data"/> <col name="date_last" type="data"/> </band>
Строкой <include name="ticket_all"/> мы добавляем в основной отчет сообщения из описания отчета "ticket_all", среди них есть описания статусов тикетов.
Описание дочернего отчета
Дочерний или вложенный отчет может быть реализован как через создание отдельного файла xml-описания, так и через описание в основном отчете. Например, нужна информация по тикету, содержащая id, автора и т.д. В первом варианте создадим файл /usr/local/mgr5/etc/xml/billmgr_mod_nested.xml со следующим содержанием:
<?xml version="1.0" encoding="UTF-8"?> <mgrdata> <metadata name="report.difficulttickets.ticketinfo" type="report" level="29"> <toolbar view="buttontext"> <toolgrp name="back"> <toolbtn func="report.difficulttickets" name="back" img="t-back" type="back" sprite="yes"/> </toolgrp> </toolbar> <band name="function" fullwidth="yes"> <query>SELECT DISTINCT t.id, t.name, a.name AS client, IF(u.level = 28, u.name_ru, IFNULL(u.realname_ru, u.name_ru)) AS responsible, (t.date_last) AS last_message, proj.name AS project_name , IF(tf.user IS NOT NULL, 'on', NULL) AS favorite FROM ticket t JOIN account a ON a.id = t.account_client LEFT JOIN ticket2user t2u ON t2u.ticket = t.id LEFT JOIN ticket_favorite tf ON tf.ticket = t.id AND tf.user = '1' LEFT JOIN user u ON u.id = t.responsible LEFT JOIN abuse_task at ON at.ticket = t.id LEFT JOIN project proj ON proj.id = t.project LEFT JOIN user bu ON bu.id = t.user_block LEFT JOIN item i ON i.id = t.item LEFT JOIN pricelist p ON p.id = i.pricelist WHERE t.id=[[elid]];</query> <col name="id" type="data" sort="digit"/> <col name="name" type="data"/> <col name="client" type="data"/> <col name="responsible" type="data"/> <col name="last_message" type="data"/> </band> </metadata> <lang name="ru"> <messages name="report.difficulttickets.ticketinfo"> <msg name="title">Информация по запросу</msg> <msg name="id">Id тикета</msg> <msg name="name">Тема</msg> <msg name="client">Клиент</msg> <msg name="responsible">Ответственный</msg> <msg name="last_message">Последнее сообщение</msg> </messages> </lang> </mgrdata>
Рекомендуем для проверки синтаксиса вашего xml-файла использовать онлайн-сервис XmlGrid или утилиту Xmllint , как правило предустановленную во всех популярных дистрибутивах.
Во втором варианте описание включено в файл основного отчета. Здесь также возможны 2 варианта — с запросом к базе данных, указанным непосредственно в xml (см.выше) или с запросом, вынесенным в обработчик (внешнее подключение). Описываем колонки для формирования таблицы:
<band name="clienttickets" fullwidth="yes"> <col name="id" type="data" sort="digit" nestedreport="ticket_all.edit"/> <col name="name" type="data"/> <col name="status" type="data"/> </band>
Пример такого подключения в описании обработчика ниже.
Переход из отчета на страницу биллинга
Другой вариант использования вложенного отчета nested — указание в нем одной функций биллинга. Например, в нашем основном отчете есть колонка "ID тикета". Если мы хотим сделать так, чтобы при клике на этой колонке открывался определенный тикет с этим же ID, в файле xml-описания основного отчета в строку с описанием колонки ID добавляем параметр ticket_all.edit . Т.е вся строка будет выглядеть:
<col name="id" type="data" sort="digit" nestedreport="ticket_all.edit"/>
В этом случае вам не нужно никакое отдельное xml-описание для этого отчета, так как происходит простой редирект на одну из страниц биллинга.
Описание скрипта обработчика
Обработчик должен находится в директории: /usr/local/mgr5/addon/ и иметь права на выполнение.
Дать права на выполнение можно командой: chmod +x /usr/local/mgr5/addon/reportdiff.py
Представленный обработчик выполняет следующие действия:
1) Добавляет наш отчет на форму "Отчеты"
2) Заполняет форму нашего отчета значениями по умолчанию
3) Выполняет подключение к базе данных панели и запрашивает данные для вложенного (nested) отчета.
Как говорилось выше, для запросов можно использовать элемент <query>...</query> прямо в описании xml, по умолчанию, будет установлено соединение с текущей базой данных. Если вам этот вариант не подходит, используйте для подключения возможности языка, на котором написан ваш обработчик. В Python для этого устанавливаем пакет python-mysqldb (для ОС CentOS — yum install python-mysqldb, для Debian -apt-get install python-mysqldb).
#!/usr/bin/env python # -*- coding: utf-8 -*- import sys import os import subprocess import xml.etree.ElementTree as ET import MySQLdb # Читаем содержимое stdin (stdin содержит xml формы отчета вместе с переданными параметрами) xmlin = sys.stdin.read() ses = ET.fromstring(xmlin) # Словарь пользовательских параметров в данном случае, если в отправленной нам XML нет узла /doc/interval, в поле "interval" будет подставлено значение равное 60 # При наличии нескольких необходимых к заполнению параметров можно указать form_params = {'param0' : 'value0', 'param1' : 'value1', ...} form_params = {'interval' : '60'} # Имя нашего отчета, определенное в XML см. выше report_name = 'difficulttickets' # Описание отчета для отображения на форме "Отчеты" report_desc = 'Отчет по сложным тикетам' # Получаем параметры запроса одной строкой вида "param0=value0¶m1=value1..." query_string = os.getenv('QUERY_STRING', '') # Определяем нажата ли кнопка на обрабатываемой форме. # При нажатии любой кнопки на обрабатываемой форме в параметрах запроса будет указан параметр 'sok=ok' (действие GET) в противном случае его не будет (действие SET). action = 'SET' if 'sok=ok' in query_string else 'GET' #GET only if action == 'GET': # Добавляем наш отчет на форму выбора отчетов if ses.get('func') == 'reportlist': elem_node = ET.SubElement(ses, 'elem'); id_node = ET.SubElement(elem_node, 'id') id_node.text = report_name report_node = ET.SubElement(elem_node, 'report', orig=report_name) report_node.text = report_desc.decode('UTF-8') type_node = ET.SubElement(elem_node, 'type') type_node.text = 'report' # Заполняем форму нашего отчета значениями по умолчанию elif ses.get('func') == 'report.difficulttickets': for key, value in form_params.items(): if ses.find('./' + key) == None: param_node = ET.SubElement(ses, key) param_node.text = value; # Формируем форму дочернего отчета 'report.clienttickets' elif ses.get('func') == 'report.clienttickets' and ses.find('./doc/doc') == None: elid = ses.find('./doc/elid') if elid != None: # Подключаемся к MySQL. Для подключения запрашиваем у панели с помощью функции mgrctl данные о доступах к базе — пользователь, пароль, хост — # ответ получаем в виде xml, из которого выбираем нужные параметры proc = subprocess.Popen('sbin/mgrctl -m billmgr paramlist out=xml', stdout=subprocess.PIPE, shell=True) xml_param = ET.fromstring(proc.stdout.read()) dbhost = xml_param.find('./elem/DBHost').text dbname = xml_param.find('./elem/DBName').text dbpassword = xml_param.find('./elem/DBPassword').text dbuser = xml_param.find('./elem/DBUser').text db = MySQLdb.connect(host=dbhost, user=dbuser, passwd=dbpassword, db=dbname) cursor = db.cursor() # Выполняем запрос на выборку данных из БД cursor.execute("SELECT t.id, t.name, t.status FROM ticket t JOIN account a ON t.account_client = a.id WHERE a.name ='" + elid.text + "'") # Получаем результат выполнения запроса data = cursor.fetchall() # Формируем содержимое формы отчета reportdata_node = ET.SubElement(ses, 'reportdata') # Добавляем узел с именем band-а описанного в xml отчета report_node = ET.SubElement(reportdata_node, 'clienttickets') # Перебираем результаты запроса: for record in data: # Получаем результаты одной строки выборки 't.id', 't.name' и 't.status' t_id, t_name, t_status = record # Добавляем описание одной строки нашего отчета elem_node = ET.SubElement(report_node, 'elem') # Заполняем поле 'id' id_node = ET.SubElement(elem_node, 'id') id_node.text = str(t_id) # Заполняем поле 'name' name_node = ET.SubElement(elem_node, 'name') name_node.text = str(t_name) # Заполняем поле 'status' status_node = ET.SubElement(elem_node, 'status') # Т.к. статус в таблице 'ticket' представлен в цифровой форме, необходимо получить его описание из xml status_node.text = ses.find("./messages/msg[@name='tstatus_" + str(t_status) + "']").text # Заканчиваем работу с БД db.close() # Отправляем отредактированную XML обратно в BILLmanager через stdout sys.stdout.write(ET.tostring(ses, 'UTF-8'))
В примере на скриншоте отчет составлен за произвольный период.
Отчет за произвольный период