Часть 1. Общее описание подхода

Впервые я столкнулся с CRM-системами совершенно неожиданным для себя образом. Когда я пришел в новую компанию, то обнаружил господствующую в диспетчерской систему учета заявок — GLPI. Никогда ранее я не слышал о ней, однако, спустя какое‑то время, прилетел таск на то, чтобы подумать и воплотить в жизнь некие дашборды или наглядные отчеты по следующим критериям:

  1. общее суточное количество заявок

  2. распределение по отделам

  3. остальные подобные метрики и их производные

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

Тоже самое мы будем делать и на 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 и подставляется в ячейку таблицы <td> с id=count.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GLPI Analytics</title>
</head>
<style>
  ...
</style>
<body>
    <h1>GLPIReport [beta]</h1>
       ...
        <table id="count">
            {{#each this.count}}
            <tr>
                <td>
                    Заявок всего
                </td>
                <h2>
                    <td>
                        {{this}}
                    </td>
                </h2>
                {{/each}}
            </tr>
        </table>
    </div>
    <table cellspacing="0" cellpadding="0" class="layout">
        <tr>
            <td id="left_col">
                {{{body}}}
            </td>
        </tr>
    </table>
</body>
</html>

Для отрисовки таблицы со всеми заявками необходим второй файл- home copy.handlebars.

<h2>{{title}}</h2>
  <div style="overflow: auto; width:1300px; height:900px;">
    <table id="tickets">
      {{#each ticketS}}
      <tr id="someRowTickets">
        <td>
          {{date}}
        </td>
        <td>
          {{name}}
        </td>
        <td id="status">
          {{status}}
        </td>
      </tr>
      {{/each}}
      </td>
    </table>
    </td>
    <td>
  </div>
  <table id="groups">
    {{#each groupCount}}
    <tr>
      <td>
        {{maintenance}}
      </td>
      <td>
        {{count}}
      </td>
    </tr>
    {{/each}}
    </td>
  </table>
  </td>
  </tr>
  </table>

Тут вся магия шаблонизации представления. Данные, переданные в шаблонизатор, как и в первом случае преобразуются в готовый HTML и выдаются при обращении пользователя по определенному маршруту.

По адресу, на котором запущен наш веб-сервер, мы видим следующее:

Некоторая информация скрыта
Некоторая информация скрыта

В целом, такой способ - это не только простая выжимка необходимых данных, но и многочисленные возможности по их представлению и обработке. Основной GLPI продолжает работать на десктопе у хотлайнеров, а наше небольшое и легковесное веб-приложение запущено параллельно, как некий дополнительный сервис.

В дальнейшем, я планирую вторую часть статьи. В ней я продолжу писать о разных способах взаимодействия с GLPI, динамизации данных, визуализации и манипулировании DOM элементами. А так же о других аспектах улучшения представлений и пользовательского опыта веб-приложения.

Комментарии (0)