Кастомизация в Luxms BI: программируем под свои желания

Привет! Меня зовут Илья Гурешидзе, занимаюсь разработкой фронтенда в Luxms BI, сопровождением и внедрением кастомных решений на базе данной платформы.

»Я этого чувака знаю это граф Гурешикула»

Жизнь

Наши будни разнообразны и насыщенны. Роадмап обширен и амбициозен, а конкуренты серьезные, что делает соперничество интересным. Мы уже можем настраивать множество функций через новый пользовательский интерфейс, и необходимость привлекать разработчиков уменьшается. Однако для меня платформенность означает прежде всего дружелюбие к командам разработчиков, стремящимся создавать собственные шаблонизируемые решения на базе платформы Luxms BI.

Поэтому пользователям часто приходится самостоятельно реализовывать логику поведения компонентов. Мы не всегда знаем, насколько сложна эта логика. Наша задача — предоставить методы и определить контекст. Итоговая логика вычисляется «на лету». И, как показывает опыт наших пользователей, их креативность иногда удивляет даже нас. Для написания такой логики мы создали специальный язык LPE, который позволяет вносить условную логику практически куда угодно. Количество мест, где при желании можно будет управлять поведением компонента или его внешним видом в зависимости от данных, будет только расти.

Разработка кастомизированных решений — это благодарное занятие с точки зрения удовлетворенных клиентов, но неблагодарное — для тех, кто затем должен поддерживать эти решения. Регулярно сталкиваясь с различными задачами и реализуя кастомные проекты, мы понимаем, что необходимо добавить в платформу как можно скорее. Создавая возможности для гибкой настройки системы, мы учитываем живые кейсы реальных заказчиков. Их разнообразные бизнес-задачи помогают нам лучше понять, как оптимально создать интерфейс и сделать пользовательский опыт максимально комфортным. К сожалению, иногда мы принимаем неправильные решения или вынуждены сделать что-то, что решает конкретную задачу, но вряд ли кому-то еще пригодится. И в последующих версиях меняем это, после осознания, как функционал НА САМОМ ДЕЛЕ должен работать. Издержки быстрого роста и наращивания функционала.

e6105b20baa6df41f99c2c1f193d0dcd.png

Создание новой версии нашей платформы — это как обновление старого автомобиля: мы хотим добавить новые функции, но при этом не хотим, чтобы у вас отвалились колеса!

Количество тестов кратно увеличено, увеличивается покрытие платформы unit-тестами, и есть группы, проверяющие уже созданные кастомизированные стенды, к которым у нас есть доступ, перед выпуском версии. И либо правим версию, либо готовим пакет ресурсов, которые поправят изменения, которые ломают старый функционал. Сложность тестирования в том, что иногда клиентами используется даже то, что по факту баг, или то, что мы сами признаем в будущем нашим архитектурным промахом. И вот тут и приходится проверять все почти вручную, ибо тесты на такое далеко не всегда напишешь.

25a1046824c1ec9db759a6ce935a32cc.png

Что такое кастомизация на нашей платформе для фронтенд-разработчика?

Сейчас кастомизация фронтенда под конкретного клиента строится слоеным пирогом следующим образом:

  • Первым слоем идет «коробка» в виде сборки фронтенд-проекта luxmbi-web-client;

  • Вторым слоем идет логика, тянущаяся из раздела ресурсов атласов. То есть это могут быть файлы темы, локализации и стилей, как общие на инстанс, так и локальные для конкретного атласа (приложения);

  • Выделю все же третий слой искусственно и отдельно: это react-компоненты, которые как просто добавляют новые визуализации на панель редактирования дэшборда, так и переопределяют поведение коробочных визуализаций или вовсе меняют логику ряда строительных компонентов React-приложения, выдаваемого в базовом виде luxmsbi-web-client (он же »веб-клиент» далее);
    Это и страница авторизации (и способ авторизации), и разводящая страница, и страницы в админке, и страница дэшборда или и вовсе создание новых разделов приложения со своими рутовыми компонентами.

Поэтому для того, чтобы повторить уже настроенную логику на новом инстансе, помимо установки там необходимого ПО и функционала коробки, а также источников данных и кубов — требуется еще и перенести ресурсы конкретных атласов. И тогда новая логика перекочует на новый инстанс.

Новогодняя кастомизация в Luxms BI

Новый год мы ходим в баню готовим праздничную визуализацию. В прошлом году это были ёлочки:

544330320a7bf0e2d114796e6d0b8f48.png

А в этом году мы решили создать график, который бы напоминал развешенные ёлочные игрушки: яркие и веселые. Мы любим, когда ярко, весело и привлекательно. Наши дизайнеры и разработчики UI/UX скоры на красивый, удобный функционал и терпеливо ждут, когда-таки до их макетов доберётся бэклог.

Когда дизайнеры представляют свои идеи, разработчики кидаются фразами вроде: «Как же это прекрасно!» — с тем же энтузиазмом, с которым они бы говорили о посещении стоматолога. 

Но вернемся к тому, как же вывести ЯК-42 возможностей кастома на сверхзвук и не оглохнуть. 

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

А для понимания контекста прежде проведем небольшой экскурс по тому, как и где ведётся командная разработка кастомных решений силами наших клиентов.

Для разработки, версионирования и публикации решений на необходимый стенд используется публичный проект Luxms BI bi-magic-resources (далее BMR).

Что он из себя базово представляет? Это вспомогательный проект, позволяющий управлять ресурсами «атласов» («приложений») на одном или нескольких стендах с Luxms BI.

Как он работает:

По факту он занимается тем, что создает development-сервер с hot-reload, который через описанные middleware в проекте проксирует все ключевые запросы, необходимые для авторизации, слушания сокетов и сообщений из NATS, обращения к ресурсам и сущностям инстанса Luxms BI под правами пользователя, которого вы указываете в настройках проекта. Скрипты открыты и при желании и по мере понимания работы проекта и принципов работы Luxms BI вы можете даже наращивать функционал проекта и не стесняться отправлять нам PR и обратную связь. 

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

Так же будет добавлен функционал mock-данных и API для работы с ним такой же, какой обычно используется для реальных кубов. Таким образом разработка может вестись еще до того, как данные будут готовы и по факту всё, в простом случае, сведётся к замене идентификаторов кубов.

По итогу проксирования — мы получаем ровно ту же картинку, что и зайдя на этот сервер с инстансом luxms bi обычным способом этим же пользователем. Но разница в том, что вы получаете сборку приложения на локальном сервере с учетом ваших кастомных компонентов, находящихся на вашем компьютере, которые реагируют и обновляются автоматически, пока вы вносите изменения по всем правилам hot-reload.

Внутри папки /src проекта могут находиться какие угодно папки с вашими файлами и компонентами, стилями и прочим. Но есть поведение, привязанное к папкам, которые именуются так же, как и schema_name соответствующего атласа на вашем стенде. Например, у вас есть атлас c id = 123, его schema_name = «ds_123».

Тогда создав такую папку в проекте и поместив в нее файл с React компонентом MyComponent.tsx, внутри которого будет что-то вроде:

import React from ‘react’;

const MyComponent = (props) => {
   return (
      
test ); }; export default MyComponent;

А затем перезапустив проект (чтобы webpack подтянул новый компонент в сборку), вы сможете использовать данный компонент в качестве визуализации для графика. Всё определяется именем файла и его расположением в проекте. Некоторые имена файлов и структура папок зарезервированы. 

Вы сможете подтянуть этот компонент, выбрав при создании дэшлета тип визуализации «Внутренний» и через указание ссылки на файл через встроенный браузер файлов ресурсов. Вместо своего MyComponent.tsx файла вы увидите в файловом браузере пару MyComponent.js и MyComponent.js.map файлы. Выберите первый из них и увидите рендер с текстом test вашего компонента. Компонент живой и будет реагировать на все события , что вы пропишете в коде.

Почему это произошло и как это работает?

Кастомные компоненты на React, будь то компоненты визуализаций, как в примере выше, или же это компоненты, меняющие шаблон страниц, — все прогоняются через единый контроллер веб-клиента InternalComponentVC. По мере формирования итогового компонента, веб-клиент ему принудительно асинхронно прокидывает ряд модулей, которые впоследствии можно импортировать внутри компонента так, будто бы эти модули находятся у вас в проекте. Версии модулей разумеется те, что установлены в package.json веб-клиента. 

Эти модули:

  • Полноценный модуль React и ReactDOM

    • react (»^18.2.0»)

    • react-dom (»^18.2.0»)

    • react-dom/client

  • Работа с библиотекой LATEX

  • Работа с классами

    • classnames (»2.x»)

  • Старый добрый jquery

    • jquery (»^3.7.1»)

  • Библиотека для запросов

    • axios (»^1.7.9»)

  • Движок графиков под капотом

    • echarts (»^5.5.1»)

Семейство модулей bi-internal, где хранятся методы, сервисы, классы, интерфейсы, модули и ui-компоненты из веб-клиента, которые чаще всего используются при кастомизации или переопределении коробки:

  • bi-internal/core (основные методы и сервисы коробки, базовые классы)

  • bi-internal/face (библиотека готовых компонентов)

  • bi-internal/utils (различные вспомогательные методы, работа с текстом, lpe и т.д.)

  • bi-internal/ui (библиотека готовых компонентов)

  • bi-internal/root (набор методов и компонентов для разводящей страницы и ее подразделов)

  • bi-internal/types (большое количество часто используемых типов и интерфейсов)

  • bi-internal/services (observable сервисы)

  • bi-internal/ds-helpers (методы по работе с атласом и много чего еще)

Конечно, в этих библиотеках только то, что мы посчитали наиболее часто используемым при разработке набором необходимых инструментов. Возможно, что мы где-то были не правы).

Итоговый компонент передается контроллером далее в итоговый компонент-представление, который по итогу маунтится в месте, определяемом контекстом вызова. Как визуализация дэшлета или на место шаблонного компонента текущего раздела BI. С учетом стилей и поведения, разумеется. 

Основное требование к кастомному React-компоненту — иметь у себя export default.

В самом проекте BMR в его webpack.config есть блок:

externals: {
    'react': 'react',
    'react-dom': 'react-dom',
    'react-dom/client': 'react-dom/client',
    'react-latex-next': 'react-latex-next',
    'classnames': 'classnames',
    'jquery': 'jquery',
    'axios': 'axios',
    'echarts': 'echarts',
    'bi-internal': 'bi-internal',
    'bi-internal/font': 'bi-internal/font',
    'bi-internal/core': 'bi-internal/core',
    'bi-internal/face': 'bi-internal/face',
    'bi-internal/root': 'bi-internal/root',
    'bi-internal/types': 'bi-internal/types',
    'bi-internal/shell': 'bi-internal/shell',
    'bi-internal/services': 'bi-internal/services',
    'bi-internal/utils': 'bi-internal/utils',
    'bi-internal/ds-helpers': 'bi-internal/ds-helpers',
    'bi-internal/ui': 'bi-internal/ui',
    'bi-internal/_internal': 'bi-internal/_internal',
  },

Что дает возможность импортировать эти компоненты из веб-клиента и НЕ инжектировать их в итоговую сборку компонентов при билдовании. Что существенно снижает размеры сборки.

Например библиотека echarts вам уже будет доступна для использования и импорта вручную или библиотеками-декораторами типа ReactEcharts, у которых под капотом вызов import * from «echarts», но её код вы не будете помещать в каждый компонент, который ее билдит, как в случае, если бы вы подключили её стандартно через package.json BMR.

У этого есть естественные ограничения:

  • версия компонента фиксирована и зависит от версии в веб-клиенте, свежую версию чего-то из списка придется ждать лишь с новой версией веб-клиента от Luxms BI;

  • Типизация отстаёт от того списка, что есть в самом проекте BMR. Наша команда старается периодически актуализировать её, но к сожалению, это не всегда возможно по времени.

Однако вы можете убрать из externals компонент и самому выбрать версию вашего компонента. Да, у этого будут последствия в виде роста размера бандлов. Но технически, вы решите свою проблему, установив ряд модулей для вебпака, которые могут помочь нивелировать размер до некоего приемлемого.

По окончанию работ по разработке — вы можете получить ту же актуальную картину приложения, что и у вас, выполнив всего одну команду npm run push внутри данного приложения. Она:

  • запустит сборку всех файлов внутри папок, чьи имена начинаются с «ds_» (и если в настройках вы не указали список включаемых и исключаемых регулярками папок),

  • по итогу сформирует готовый для публикации набор файлов,

  • обратится к серверу,  

  • получит список файлов из ресурсов каждого из тех атласов, что выбраны в BMR,  

  • сравнит файлы локальные и их копии на сервере,

  • уведомит о том, кто из файлов новый, кто подлежит удалению, а кто будет перезаписан,

  • попросит подтверждения

  • и затем отправит по API на сервер.

После выполнения такой команды вы увидите, что на сервере теперь выставлены настройки дэшбордов, дэшей и появились необходимые компоненты из вашего проекта BMR. Картинка будет та же, что и у вас на локальном сервере.

Вы великолепны! Идите домой, ваш Дозор окончен.

Так вот, теперь вернемся к новогодним шарам)

От идеи…

Шарики — это не просто картинка, а сетчатая диаграмма, где каждая точка имеет собственный размер и цвет. Дизайнеры добавили задаче немного «магии»: сами шарики должны быть объемными, с тенями и размытием, затемнением исходного цвета и вариациями его прозрачности. И всё это должно отображать данные, работать динамически и выглядеть празднично.

… до реализации

Мы используем библиотеку Apache Echarts для визуализации данных. Она поддерживает кастомные символы через параметр symbol, но есть ограничения:

'path://M30.9,53.2C16.8,53.2,5.3,41.7,5.3,27.6S16.8,2,30.9,2C45,2,56.4,13.5,56.4,27.6S45,53.2,30.9,53.2z M30.9,3.5C17.6,3.5,6.8,14.4,6.8,27.6c0,13.3,10.8,24.1,24.101,24.1C44.2,51.7,55,40.9,55,27.6C54.9,14.4,44.1,3.5,30.9,3.5z M36.9,35.8c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H36c0.5,0,0.9,0.4,0.9,1V35.8z M27.8,35.8 c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H27c0.5,0,0.9,0.4,0.9,1L27.8,35.8L27.8,35.8z'

'image://data:svg/xml;base64,.....'

Подробнее тут.

И это стало проблемой, потому что динамически рассчитать тени, цвета и размытие не получается при таком подходе. В коробке веб-клиента такой случай не обрабатывается (да как они МОГЛИ эти Люксы?!).

А какие праздничные шарики без блеска?

8ff76f9f1f4c59b0adef6fe87e3d0880.png

К тому же добавилась сложность с отрисовкой линий — «ниточек», на которых висят шарики.

В итоге я решил экспортировать «коробочный» компонент «сетчатая диаграмма» в ресурсы, добавив необходимые библиотеки, и уже там разобраться с проблемами символов.

Что сделал

Создал React-компонент с иконкой, которая принимала цвет точки и делала ряд расчетов для итоговой отрисовки:

  • Обрабатывала входной цвет, приводя его к rgba с базовым альфа-каналом = 1 (т.е. полностью непрозрачный);

  • Пересчитывала цвет в его аналог на 30% темнее и использовала его для линий шарика и его кольца, которым он крепится к ниточке;

  • Был отдельный компонент, который возвращал foreignObject, для которого просто передавалось итоговое значение box-shadow для входного цвета, с альфа-каналом, пониженным до 40%.

В итоге у меня получился динамический компонент с полноценной поддержкой всех визуальных эффектов.

После создания SVG с нужными фильтрами, тенью и прочими эффектами, нужно было перевести его в base64, чтобы подключить к графику. Для этого я использовал следующий алгоритм:

symbol: image://data:image/svg+xml;base64,${base64};

Это позволило отобразить кастомные шарики с учётом всех фильтров и эффектов, заложенных в исходный SVG.

Проблема была в том, что оказалось, что наш любимый bi-magic-resources по умолчанию не экспортирует библиотеку 'reactDOM/server' из веб-клиента, что было добавлено в ближайшей версии. А она мне была нужна, ибо в ней есть метод, возвращающий рендер итогового svg компонента, внутри которого есть специфическая логика.

Я решил это, убрав react-dom из блока externals в Webpack и добавив её в package.json. вручную.

Так вот в этой библиотеке 'reactDOM/server' есть метод renderToStaticMarkup, который принимает React-компонент и возвращает результат его рендера. Это как раз то, что мне было нужно, чтобы преобразовать его в base64. В результате иконка встала с нужными фильтрами.

А как быть с ниточками?

Шарик ведь нужно повесить, но сделать это оказалось не так просто — он должен был стать частью компонента label в серии графика типа scatter для Echarts.

93ff3f6fa23266ce6dced52de635cfa8.png

Я решил использовать две серии графиков —- обе разделяют одну пару координат для каждой точки, но:

  • первая — отображает лейблы и не реагирует на взаимодействие

  • вторая — рисует лейблы бесконечно далеко вверху, оставляя лишь линию связи с этим лейблом, которая как раз идеально подходит для роли той самой ниточки.

Это позволило добиться нужного эффекта подвешенности.

Результат новогодней кастомизации в Luxms BI

Визуализация получилась не только праздничной и информативной, но и технически интересной. К тому же, ее может использовать любой пользователь, установив получившийся архив.

a49cd9ca0003bc57ba48db1d57818f47.png

Этот проект еще раз напомнил, насколько гибкой может быть кастомизация в Luxms BI и как важно искать нестандартные решения.

Выводы:

  • я добавил в экспорт веб-клиента reactDom/server;

  • в одной из ближайших версий мы-таки будем уметь обрабатывать символы на графике с помощью кастомных компонентов;

  • и нам будет нужно научить Echarts рендерить реакт-ноды вместо строк у лейблов и символов. Мы уже начали в этом году трогать исходники и останавливаться в этом не собираемся.

А пока можете посмотреть наш новогодний дэшборд, где мы использовали новую визуализацию:

Новогодний дэшборд с результатами 2024 года от Luxms BI

Новогодний дэшборд с результатами 2024 года от Luxms BI

А если вы также хотите добавить немного праздника в систему — вот вам архивы шариков и елочек:

Устанавливайте по инструкции и пользуйтесь.

С наступающим Новым годом! Пусть в ваших проектах и сердцах всегда будет место для праздника.

© Habrahabr.ru