BI-GLPI. Или мой взгляд на CRM в контексте JS
Часть 1. Общее описание подхода
Впервые я столкнулся с CRM-системами совершенно неожиданным для себя образом. Когда я пришел в новую компанию, то обнаружил господствующую в диспетчерской систему учета заявок — GLPI. Никогда ранее я не слышал о ней и не имел дела с системами подобного рода. Однако, спустя какое‑то время, прилетел таск на то, чтобы подумать и воплотить в жизнь некие дашборды или наглядные отчеты по следующим критериям:
общее суточное количество заявок
распределение по отделам
остальные подобные метрики и их производные
GLPI предлагает как дефолтные инструменты репортов, так и возможность установить дополнительные плагины из специального маркетплейса (платно). Но меня все это не впечатлило, так как показалось довольно скучным. К тому же, было интересно решить задачу самостоятельно, не прибегая к готовым продуктам. Я стал думать, чтобы такое придумать и «не изобретать велосипед». Хотелось взаимодействовать с программой по минимуму. Иметь возможность просто и быстро получать данные и делать с ними все, что угодно. А главное, не разбираться в документациях к API и не копаться в PHP. Я хотел использовать имеющиеся у себя знания и именно на их базе создать веб приложение, которое смогло бы выполнять поставленные задачи.
Решение было принято вскоре. Я понял, что мне достаточно иметь возможность взаимодействия исключительно с базой данных GLPI и ее структурой таблиц и их реляций. Стек, который показался мне наиболее подходящим для данной задачи выглядел так:
Node JS
Express
Handlebars
Знакомство с фреймворком Express я советую начинать с книги Итана Брауна — «Веб-разработка с применением NODE Js и Express». Я сам продолжаю по сей день пользоваться ей как проводником и наставником. Применяю Java Script в Arduino с помощью Johnny-Five. Активно применяю JS для решения кейсов на работе. И, конечно, надеюсь, что в обозримом будущем смогу в полной мере использовать накопленный опыт и знания в этой области для того, чтоб еще больше подружить промышленную автоматизацию с JavaScript.
Самое точное на мой взгляд описание фреймворка Express приводится самим Итаном Брауном в книге, упомянутой выше:
… Просто он в меньшей степени становится у вас на пути, позволяя более полно выражать свои идеи. — Itan Braun [1]
В общем виде система файлов и каталогов приложения выглядит так Некоторые файлы не затронуты в статье т.к объяснение их наличия выходит за рамки темы.
Немного описательной части структуры каталогов приложения…
Как правило, каталог src — основной для проекта.»public» — служил на первых парах для хранения файлов css, js, изображений и тестирования. Папка »views» — это про handlebars и систему шаблонов. Также имеется файл »accessMain.log». По его расширению понятна его «цель жизни»:) Так же файл со странным названием »indexV3…2_mine.js» — является основным и самым важным.
Вернемся же к нашему решению, которое будет реализовывать взаимодействие между приложением клиентом и системой GLPI.
За что я люблю Node JS, так это за многообразие NPM пакетов. Так сказать, на все случаи жизни вы можете найти что‑то подходящее для себя среди 1000 NPM — ов. Действительно, практически всегда я нахожу оптимальный пакет для того, чтобы решить очередную задачу. И в этот раз мне не пришлось долго искать нужный, и я взял на вооружение npm «mysql» . Ссылка на пакет.
Довольно простая в использовании библиотека позволяет гибко взаимодействовать с базой данных. В описании пакета подробно приведены основные методы взаимодействия с БД.
В идеале, нам достаточно открыть соединение и получить данные в ответ на цепочку SQL запросов. Так мы и сделаем. Настроим параметры соединения согласно файлу описания пакета:
Пример с офф док:
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'me',
password : 'secret',
database : 'my_db'
});
connection.connect();
connection.query('SELECT 1 + 1 AS solution', function (error, results, fields) {
if (error) throw error;
console.log('The solution is: ', results[0].solution);
});
connection.end();
Для представления данных на стороне клиента мы будем формировать объект и передавать его в некий шаблонизатор для последующего рендеринга HTML в ответ на GET — запрос пользователя.
Скрытый текст
Тоже самое мы будем делать и на POST запрос, однако, с той разницей, что в функцию получения данных мы передадим параметры из формы со странички клиента.
В поставленной передо мной задаче было четко обозначено, что хочет видеть конечный пользователь по итогу выполнения запроса. Необходима информация следующего характера:
Заголовок заявки
Статус заявки
Количество заявок по отделам
Общее количество заявок за выбранный период
Вытащить непосредственно имя заявки, содержание, общее количество заявок — не представляется чем‑то трудным. В то же время, для того, чтобы прогнать полученные данные через скрипты подсчета и статистики, прежде необходимо составить сложный SQL запрос. Изучив немного таблицы GLPI я решил, что для получения данных о принадлежности заявки к определенному отделу я буду сопоставлять id инициатора с id группы к которой он принадлежит в системе пользователей GLPI.
connection.query(`SELECT COUNT(*) FROM glpi_tickets WHERE date BETWEEN '${date[0]}' AND '${date[1]}'`, function (err, rows1) {
if (err) throw err;
connection.query(`SELECT DATE_FORMAT(date, '%d/%m/%Y %H:%i'), name, users_id_recipient, status FROM glpi_tickets WHERE date BETWEEN '${date[0]}' AND '${date[1]}'`, function (err, rows2) {
if (err) throw err;
connection.query(`SELECT * FROM glpi_tickets
INNER JOIN glpi_tickets_users ON glpi_tickets.id=glpi_tickets_users.tickets_id
INNER JOIN glpi_groups_users ON glpi_tickets_users.users_id=glpi_groups_users.users_id
INNER JOIN glpi_groups ON glpi_groups_users.groups_id=glpi_groups.id WHERE date BETWEEN '${date[0]}' AND '${date[1]}'`, function (err, rows3)
{ ...
Как видно из фрагмента кода выше, все запросы сведены в колбэк цепочку, в которой предусмотрено отображение в консоли ошибки в случае ее возникновения на этапе транзакции.
Скрытый текст
Для корректного отображения даты в удобочитаемом формате пришлось прибегнуть к встраиванию в SQL запрос функции конвертации временного формата — DATE_FORMAT (date, '%d/%m/%Y %H:%i');
В ответ на наш настойчивый запрос БД ответит нам данными вида Row Data Packet Object.
Некоторая информация скрыта
После того, как данные получены, нам надо их привести в порядок, что называется. Причесать, «отмыть» и распределить по структуре нашего основного объекта, который будет использовать для процесса представления. Это можно сделать простым заполнением объекта, проходясь в цикле по полученным данным.
По итогу получаем основной структурированный объект, который и будем передавать в шаблонизатор.
Некоторая информация скрыта
Представление на стороне клиента
Как я упоминал выше, для представления данных на стороне клиента я буду использовать подход шаблонизации. Подробнее можно почитать тут. А еще тут.
В целом же, философия шаблонизации представления всегда сводится к одним и тем же намерениям:
не писать повторяющийся код несколько раз
тиражирование правок, вносимых в один компонент
Однако, надо помнить об одной детали такого подхода. Существует много шаблонизаторов с разным уровнем абстракции. То есть уровень того, на сколько мы абстрагируемся или, как мне кажется, лучше сказать — «удаляемся» от классического HTML. Handlebars имеет явное преимущество в это плане. Так же, имеется следующий факт, который я нахожу немаловажным:
Handlebars компилирует шаблоны в функции JavaScript. Это делает выполнение шаблона быстрее, чем у большинства других шаблонизаторов.
— https://handlebarsjs.com
Уместно будет представить моему читателю и способ подключения Handlebars в Express, а также структуру его каталогов.
Этот фрагмент кода располагается в основном файле приложения:
...
/*HANDLEBARS*/
const expressHandlebars = require("express-handlebars").engine;
app.engine('handlebars', expressHandlebars({
defaultLayout: 'main',
}));
app.set('view engine', 'handlebars');
...
Ниже приведена структура каталогов папок и файлов Handlebars.
Содержание файла main.handlebars. Это основной каркас пользовательской страницы. К примеру, в нем из ранее сформированного объекта для представления берется значение поля count и подставляется в ячейку таблицы
GLPI Analytics
GLPIReport [beta]
...
{{#each this.count}}
Заявок всего
{{this}}
{{/each}}
{{{body}}}
Для отрисовки таблицы со всеми заявками необходим второй файл- home copy.handlebars.
{{title}}
{{#each ticketS}}
{{date}}
{{name}}
{{status}}
{{/each}}
{{maintenance}} | {{count}} |
Тут вся магия шаблонизации представления. Данные, переданные в шаблонизатор, как и в первом случае преобразуются в готовый HTML и выдаются при обращении пользователя по определенному маршруту.
По адресу, на котором запущен наш веб-сервер, мы видим следующее:
Некоторая информация скрыта
В целом, такой способ — это не только простая выжимка необходимых данных, но и многочисленные возможности по их представлению и обработке. Основной GLPI продолжает работать на десктопе у хотлайнеров, а наше небольшое и легковесное веб-приложение запущено параллельно, как некий дополнительный сервис. В дальнейшем, я планирую вторую часть статьи. В ней будем говорить о динамизации данных, визуализации и манипулировании DOM элементами. А так же о других аспектах улучшения представлений и пользовательского опыта.