Вопросы по созданию кастомных визуализаций

Коллеги, в рамках работ по созданию кастомных визуализаций, просьба пояснить следующие вопросы:

  1. Просьба уточнить принцип развертывания кастомных скриптов на сервере Luxms. Как я понимаю, в результате деплоя, скрипты должны быть помещены в таблицу _resources соответствующей схемы. Так же нужно понимать в каких случаях скрипты нужно помещать в схему ds_res. Так же нужно понимать какие файлы подлежат деплою (как я понимаю это не все файлы из проекта react).
  2. В документации упоминается проект luxmsbi-web-resources (руководство разработчика). Как я понимаю он на гитхабе? И можно ли к нему подключить для деплоя наши проекты?
  3. Просьба прислать инфу, где на сервере (или в БД) находятся библиотеки: echarts, react, typescript и прочие библиотеки JavaScript. Можно ли туда добавлять еще библиотеки?
  4. Уточнить в каких случаях целесообразно использовать Internal, а в каких External view_class.
  5. Просьба, указать какие есть служебные наименования для файлов со скриптами. Что выполняются при загрузке дашборда без использования Internal и External классов.
  6. Просьба на примере пояснить работу Observable сервисов. Для обоих ли классов (Internal и External) они используются? В целом просьба, указать какие есть в luxms ваши собственные библиотеки/классы что используются в кастомной разработке дашбордов. Где они находятся, описание их методов или примеры использования.
  7. Просьба на примере показать как встраивать кастомный дэш в дашборд, где остальные дэши из коробки. В том числе показать взаимодействие (взаимное влияние) между кастомным и дэшем из коробки. Просьба рабочий пример передать нам для установки на наш стенд.

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

Будет серия постов, потому наберитесь терпения, пожалуйста)

Немного о подходе,концепции и истории :

За работу веб-клиента (далее просто Клиент или Обвязка) как итогового собранного webpack-ом React-приложения отвечает проект luxmsbi-web-client - закрытый проект ДСП Luxms , который поставляет “коробочный” функционал.

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

Изначально, на ранних этапах развития платформы, под кастомизацией мы понимали именно создание новых визуализаций для дешлетов. Однако, с течением времени концепция значительно изменилась.

Так вот, частью коробочного функционала является возможность вставлять на место дешлета вместо предустановленных типов визуализаций один из специфических типов визуализаций: External (Внешний) и Internal (Внутренний). Они сами по себе не имеют готовой итоговой визуализации, но имеют каждый свой собственный набор необходимого интерфейса и событийной системы и ждут от пользователя указания ссылки на ресурс какого-либо атласа. В первом случае это ссылка на html файл, которая по итогу приводит к встраиванию iframe со ссылкой на указанный html, а во втором - на js файл собранного вебпаком реакт-компонента с указанием export default внутри и который нативным образом маунтится в итоговое дерево компонентов React, являясь такой же частью страницы, как и коробочные компоненты с .

Под ресурсом мы понимаем в первую очередь запись в таблице имя_схемы_датасета.resources (например ds_51.resources), так же это файл, который понимается браузером, а стало быть имеет один из валидных MIME-типов.

  'image/png',
  'image/jpeg',
  'image/svg+xml',
  'text/xml',
  'text/plain',
  'text/html',
  'text/css',
  'text/javascript',
  'application/javascript',
  'application/x-javascript',
  'application/sql',
  'application/json',
  'text/markdown',

Вы можете загрузить любой файл при помощи drag’n’drop через административный интерфейс или по адресу например https://site.ru/#/ds/ds_51/resources (покажет вам раздел с ресурсами датасета ds_51)

Коснемся вопроса из пункта 1: Об атласе ds_res и его роли. Возможность использовать тот или иной файл (понимай “ресурс”) зависит от того, имеет ли ваш пользователь доступ к атласу, который этот ресурс хранит, или нет.

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

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

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

Обвязка поставляет коробочную версию стандартным образом. Однако в ее коде и настройках вебпака существует логика, которая проверяет наличие компонентов из раздела ресурсы датасета. Это распространяется как на структурные компоненты, формирующие внешний вид того или иного раздела BI, так и конкретно на логику и отображения дешлета типа Внутренний (internal). Разве что во втором случае название и расположение компонента вы выбираете сами и указываете это в специальном поле конфига дешлета “url” (об этом позднее), а в первом - его имя фиксировано и должно совпадать с тем именем компонента , что используется внутри обвязки (об этом в серии про internal), и располагается или в ресурсах ds_res или в ресурсах текущего атласа, на котором находимся.

  1. Итоговый внешний вид веб-клиента Luxms BI зависит от того, какие файлы хранятся в ресурсах и какие атласы доступны текущему пользователю.
  2. По итогу уровни бизнес-логики и внешнего отображения содержат в смысле фронтенда два уровня: основной шаблон (обвязка), поставленный по умолчанию и слой, который формируется ресурсами.
  3. Уникальную для вас бизнес-логику, которой нет в коробочной версии, вы будете хранить в ваших ресурсах и имеете право именно этот набор файлов считать кодом вашего приложения, т.к. при должном терпении вы можете ресурсами качественно изменить внешний вид и поведение всего фронтенда Luxms BI.
  4. Взаимосвязь между обвязкой и слоем ресурсов происходит через специальные встроенные сервисы и пакет экспортируемых обвязкой модулей, доступных для использования в кастомных React-компонентах (об этом чуть позже).

Вспомогательный проект bi-magic-resources

Данный проект является публичной версией закрытого проекта luxmsbi-web-resources. Оба проекта по функционалу идентичны и решают одни и те же задачи:

  1. Позволяют управлять ресурсами всех атласов, а в общем случае умеет получать в виде json настройки атласа, дешбордов и дешлетов и изменять их прямо в проекте, с последующей выгрузкой на сервер.
  2. Позволяет использовать git и версионировать разработку.
  3. Позволяет настроить CI/СD для управления ресурсами на деве, тесте, проде и т.д.
  4. Позволяет работать над ресурсами проекта команде разработчиков.
  5. Позволяет вносить изменения в поведение и внешний вид итогового веб-клиента без привлечения devops а лишь силами встроенных методов и скриптов
  6. Можно массово загружать все ресурсы на сервер (или же только специфических атласов, если требуется), проверяя перед этим, новый ли это файл и его нужно на сервере создать, файл которого больше нет и его надо удалить на сервере, или существующий, который д.б. перезаписан.

Работа с проектом выглядит так:

  1. Зайдите на GitHub - luxms/bi-magic-resources
  2. Ознакомьтесь с документацией в GitHub - luxms/bi-magic-resources
  3. Сделайте форк данного проекта в свой github аккаунт и сделайте git clone себе на компьютер.
    Если у вас есть команда - то пусть это лучше сначала форкает тимлид, а вы потом клоните его версию, но это опционально. Идея только в том, чтобы вы использовали единый репозиторий по понятным причинам.
    Этот проект создаст необходимое окружение.
  4. Вызовите npm install (или yarn, лично мы больше любим его).
  5. В корне проекта найдите файл config.json и добавь в поле server адрес инстанса Luxms BI (“server”: “https//mysite.ru/”).
    Т.е. тот урл адрес, под которым у вас открываются дашборды, но только до символа “#”
  6. Создайте в папке src папку “ds_res” (проект по умолчанию пустой и без файлов и папок, потому хотя бы одну папку нужно создать для запуска)
    Создайте в корне проекта файл authConfig.json с содержимым
{
  "master": {
     "username": "ваш логин",
     "password": "ваш пароль"
   }
}

Этот файл находится в .gitignore и нужен для того, чтобы вам не приходилось вводить данные пользователя каждый раз, когда используете один из скриптов проекта для массовой загрузки/выгрузки ресурсов с сервера или старта приложения.

P.S. Вы можете, вообще говоря, и указать просто

{
      "username": "ваш логин",
      "password": "ваш пароль"
}

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

Концепция:

Каждая ветка данного проекта - это потенциально отдельный проект, который может содержать свой набор модулей в package.json, свой адрес сервера в config.json и свой набор ресурсов и логин-пароль в authConfig.json.

Каждая папка внутри src данного проекта, которая начинается с префикса ds_ - это папка, отображающая разделы ресурсов одноименного атласа. Вы можете создавать и простые папки типа components, utils, services и прочие, но есть разница:

В папках произвольного именования файлы хранятся как есть и с ними ничего не происходит, если только они не являются частью импорта в папках ds_. Однако в папках с ds_ и их подпапках при старте приложения и предварительном билдовании перед отправкой на сервер все компоненты, если для них это имеет место будут билдиться вебпаком. Т.е. ваши MyComponent.tsx превратятся в MyComponent.js и MyComponent.js.map и именно эта пара уйдет на сервер при соотв. команде скрипта.

Воркфлоу

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

У вас есть два способа: через external и internal. Т.е. или вы пишете хардкорный html с подключением стилей, скриптов, вспомогательной библиотеки bixel.js (следующий пост) или иными библиотеками для визуализации (D3.js например) и разрабатываете с учетом событийной системы, создаваемой данной библиотечкой или пишете React компоненты и полностью используете только реакт-подход.
Лично мы пропагандируем использование internal как более гибкого способа разработки, плюс таким образом вы получаете по итогу цельное реакт-приложение + вам доступны ряд методов, которых нет и не может появиться в external.

Допустим, что у вас есть ваши собственные UI-компоненты ,которые будут использоваться в более чем одном компоненте.
Вы можете хранить их например в папке src/components с любым деревом папок, которое вам удобно и подключать в вашем компоненте, который вы например только что создали в папке ds_51.

  1. Итак, вы указали адрес сервера в config.json, логин пароль в authConfig.json
  2. Создали файл MyComponent.tsx, MyComponent.scss и ряд еще каких-то файлов, которые вам понадобятся для разработки на сегодня. Я для примера считаю, что вы будете встраивать react компонент.
  3. Перезапустите проект, чтобы webpack проекта подхватил ваши файлы и следил за изменениями в дальнейшем.
  4. Открываете localhost с нужным портом, который можно поменять в config.json в ключе port
  5. Идете в соответствующий атлас, где хотите увидеть работу вашего нового компонента. Подразумевается ,что вы уже оформили его как простейший код компонента React, например
import React from 'react';

export class Test extends React.Component<any> {
  constructor(props) {
    super(props);
  }
  public render() {
    return (
      <div>Hello world</div>
    );
  }
}


export default Test;

Или функциональный его вариант, без разницы.
6. В режиме редактирования дешборда выбираете нужный дешлет, указываете тип визуализации Внутренний и в блоке Файл указываете в качестве файла MyComponent.js ибо именно в таком виде вы его увидите. Сохраните работу и выйдите из режима редактирования.
Уже к этому моменту вы увидите результат рендера вашего компонента. В моем случае “Hello world”. Если вы глянете на дешлет через инспектор браузера, то никаких фреймов вы не увидите, потому что обвязка посчитает этот компонент нативной частью себя.
7. Вы ведете разработку компонента ,пишете стили и т.д. Закончили, увидели удовлетворяющий вас результат на локали и готовитесь залить это на дев и пойти домой
8. Коммитите это как обычно при работе с git, пулите и затем пушите . Я бы на всякий случай напомнил вам, что master лучше использовать как финальный результат сборки или вообще от нее отказаться, чтобы вас она не смущала. И ветками задать нужный вам уровень этапов деплоя, который вас устроит.
9. Все, вы сохранили свой прогресс в git. Теперь будем отправлять ресурсы на сервер. Убедитесь, что перед этим, вы сделали *git pull *и у вас актуальная версия проекта (ветки) без неразрешенных мерж-конфликтов.
10. Набираете команду yarn push (npm run push) и видите в консоли идет подключение к серверу, который вы указали в конфиге с логином и паролем из ауф-конфига. Затем идет прогрессбар сравнения вашей версии файлов и того, что на сервере.
Затем вы увидите итоговый диалог, где проект подскажет вам статус файлов и запросит подтверждения на продолжение. Набрали Y и видим прогрессбар загрузки на сервер ресурсов.
11. Прогрессбар завершился, ошибок нет. Идем на сервер по боевому урлу и проверяем ,что по ссылке на дешборд данного атласа не с локали вы видите то же, что и на локали.
12. Вы восхитительны, пишите вашему руководителю “Я сделяль” и идите домой)

NB: Перед пушем ресурсов через yarn push (npm run push) обязательно делайте git pull. Ибо вы должны помнить о том, что вы зальете ресурсы на сервер переписывая контент на тот, что у вас. Потому. работая в команде, вы должны доверять только тому, что есть в git, а не на сервере.

Ибо в скриптах есть например такой хитрый скрипт yarn pull (npm run pull) который стоит использовать очень ограниченно и осторожно, в идеале разово при первичном наполнении ветки проекта или тогда, когда вы указываете в config.json ключ

"dashboards": true

который, при использовании npm run pull и после в итоге npm run push позволит вам править в проекте конфиги дешлетов, дешбордов и датасетов, которые будет храниться в специфических папках, которые у вас автоматически заведутся для каждой папки типа ds_ . Тогда вы сможете хранить конфиги в git.

Этот скрипт pull сам создаст все папки атласов, которые в данный момент присутствуют на сервере и скачает все файлы и рассортирует их по папкам, включая вложенные. Единственный его минус: он не умеет собирать из js и js.map пары итоговый tsx например. Простим ему) он и не должен, ибо ваши файлы в первую очередь хранятся в git, а не на сервере.

Помните, что вы не обязаны в проекте хранить полный перечень папок с атласами ,которые есть на сервере. Это не нужно. Храните только те, с которыми работает ваша команда сейчас. Те атласы, которые в вашем проекте не присутствуют явно, просто не будут участвовать в итоговом массовом обновлении при команде push. То есть вы можете например создать папку ds_res и в ней хранить ровно тот набор компонентов, который хотите сейчас и не смотреть на ресурсы других атласов. Они останутся нетронутыми.

И еще: подразумевается ,что у вас открытый контур, в ином случае, для установки депенденси вам потребуется доп.настройка от ваших девопсов репозитория с npm пакетами, к которому вы будете подключаться. И тогда проблем в закрытом контуре не будет. Это уже проверено нашими клиентами и работает.

External

External был первой нашей пробой пера, еще до того, как Обвязка стала полностью реакт-приложением, и ее логика сводилась к тому, чтобы просто вставить iframe, занимающий все доступной пространство дешлета (за вычетом высоты хидера с названием дешлета) и в качестве src атрибута указать адрес, который вы прописали конфиге дешлета.

В режиме JSON config вы даже можете сделать ссылку на внешний ресурс в интернете, если необходимо, однако такой внешний дешлет будет декоративным и не будет реагировать на события Обвязки (т.е. на любые ваши действия с данными или в интерфейсе).
В режиме Editor вам такая возможность по умолчанию недоступна (потому что слишком специфична для обывателя) и вы, при попытке указать файл для указанных выше типов визуализаций, обозреваете ресурсы всех атласов, которые доступны вашему пользователю. Это выглядит как путешествие по папкам с названиями атласов, где список файлов - есть все ,что хранится в соответствующем разделе Ресурсы выбранного атласа. По итогу вашего выбора файла вы получаете в конфиге дешлета “url” ключ в виде например таком:

"url": "res:MyExternalComponent.html"

Такая запись означает, что веб-клиент должен пойти в раздел ресурсы текущего атласа (пусть это будет тот же ds_51), на котором мы находимся и асинхронно (via Promise) загрузить файл (или его контент, если это Internal) с именем, указанным после res:.
По итогу файл будет запрашиваться по адресу

/srv/resources/ds_51/MyExternalComponent.html

и эта ссылка вставиться в src у iframe. То, что отрендерится в результате такого подключения зависит от скриптов внутри MyExternalComponent.html
Вы можете писать там любой код, как при рисовке обычной веб-страницы. Можете даже подключать любые библиотеки и фреймворки, если сочтете нужным. Однако, у этого типа дешлета есть ряд ограничений:

Вам будет необходимо подключить данный файл в качестве скрипта bixel.js - Google Диск

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

Страница этой библиотеки тут GitHub - luxms/bixel: BI eXternal ELement . К сожалению, она не содержит в Readme.md информации о том,как взаимодействовать с кубами, ибо туда пока просто не добавили эту инфу, но сам файл, что я вам указал в ссылке содержит все необходимое и считайте, что у вас тут GitHub - luxms/bixel: BI eXternal ELement есть еще отдельный блок с

const xs = axis.getXs() // вернет объекты, лежащие на оси Х
const ys = axis.getYs() // вернет объекты, лежащие на оси Y
const z = axis.getZs()[0] // вернет объект, лежащий на оси Z

Так вот, сделав поведение вашего компонента зависящим от событий, которые генерирует эта библиотечка вы получите кастомный компонент, который умеет взаимодействовать с Обвязкой и делать широкий круг вещей .однако ему будут недоступны многие модули и сервисы Обвязки, либо доступны опосредованно через их сохраненные в объекте window версии. Например window.parent.__koobFiltersService - сохраненный инстанс сервиса, который хранит в себе информацию о выбранных вами фильтрах для кубов.
Пример:

const service = window.parent.__koobFiltersService;
service.subscribeUpdatesAndNotify(myCallback);

const myCallback = (model) => {
// тут логика работы с объектом текущей модели сервиса, например с его ключом filters, где указаны выбранные фильтры для кубов
}
const onClick = (e) => {
service.setFilter("", "age", [">=", 50]) // При клике обратится к фильтру и выставит фильтр на дименшн age (фиксированный или из e.target например)
}

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

Причины для использования этого:

  1. Для него требуется сравнительно невысокий входной порог по навыкам
  2. Не нужно билдить, при особом желании можно править код прямо в разделе ресурсы на сервере, без проекта.
  3. Поскольку это фрейм, то вы можете подгружать любые библиотеки. То есть добавить компонент, рендерящий Angular или Vue компонент внутри реакт-приложения. Сомнительная инициатива, но запретить ее мы не можем.
  4. Вставить визуал с другого инстанса BI пользуясь логикой токенов для безпарольного входа (для этого есть отдельная дока по запросу).

Об ограничениях писал выше.

Пример такого файла на external


<!DOCTYPE html>
<html>
<head>
  <script>window.Promise || document.write('<scr'+'ipt src="es6-promise.auto.js"></sc'+'ript>');</script>
  <script src="bixel.js"></script>

</head>
<style>
  html, body {
    margin: 0;
    font-size: 13px;
    min-height: 100%;
  }
  #wrapper {
    overflow-y: auto;
    overflow-x: hidden;
    width: 100%;
    min-height: 100%;
    padding: 1rem;
    box-sizing: border-box;
  }
  .elements {
    widrh: 100%;
    display: flex;
    align-items: center;
    flex-direction: column;
    padding: 0.25rem;
  }
  .element {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 0.5rem;
  }
  .element:last-child{
    margin-bottom: 0;
  }
  .element .title {
    flex: 0 0 auto;
    max-width: 70%;
    width: 100%;
  }
  .element .value {
    flex: 0 0 auto;
    max-width: 30%;
    padding: 0.5rem 0.25rem;
    width: 100%;
    border: 1px solid #7ba7db;
    text-align:center;
  }
</style>

<body>
<main id="wrapper">
  <div class="elements" id="target"></div>
</main>

<script>
  let dashlet, config, ms, ls, ps, M, L, P, data, axes, xs, ys, zs;
// Первоначальный инит библиотеки. Обычно нужен для разовой предварительной настройки компонента и чтении конфига дешлета 
  bixel.init({
    zsCount: 1,
  }).then(function(d) {dashlet = d; config = dashlet.config || {}});

// Данных нет, или не указаны важные ключи в конфиге дешлета. Нужен для заглушки типа Нет данных
  bixel.on('no-data', function(axes) {
    // no data
  });
// Событие триггерится каждый раз, когда вы выставляете фильтр вручную или через управляющий деш. И когда данные есть
  bixel.on('load', function (d, a) {
    data = d;
    axes = a;
    xs = axes.getXs();
    ys = axes.getYs();
    zs = axes.getZs();
// Просто функция, которая отвечает за основной рендер. Учитывайте, что событие load может происходить часто, потому не забывайте очищать контейнер перед вставкой в DOM
    _render();
  });

  function _render() {
    const y = ys[0]
    const z = zs[0];
    console.log(xs, ys, zs, data);

    console.log(data.getValue(xs[0], y, z));
  }

  document.addEventListener('DOMContentLoaded', function(){ // Аналог $(document).ready(function(){
    document.getElementById('wrapper').addEventListener('click', function(event) {
      console.log(config);
      if (config.hasOwnProperty('onClickDataPoint')) {
        // Прокидываем обработчик встроенной логики клика по элементу графика onClickDataPoint
        bixel.invoke('CLICK_DATA_POINT', {
          x_id: xs[0].id,
          y_id: ys[0].id,
          z_id: zs[0].id,
          event: { pageX: event.pageX, pageY: event.pageY },
        });
      }
    });
  });
</script>
</body>
</html>

Коллеги, скажите пожалуйста.
Вопрос про деплой компонентов на сервер Luxms с помощью команды yarn push (п.10 в разделе Воркфлоу).
Как я понимаю будут доплоиться все компоненты, что находятся в проекте на github.
Допустимо ли, что в проекте на github в разделе src будет находится не все ds_…, что созданы на стенде Luxms?
Например на стенде Luxms большой объем ds_ созданных коробочным решением и их нет проекте на github. Они останутся без изменений?
Тот же вопрос по каталогу ds_res. Например в проекте на github я разработал только один файл, а на стенде Luxms в ds_res находятся другие файлы. Они останутся без изменений?

И нужно ли как-то контролировать уникальность идентификаторов ds_? Что будет если в проекте на github будет использован идентификатор ds_ и этот же идентификатор будет использоваться ранее созданным другим датасетом с дашами из коробки?

Здравствуйте!

  1. отправляться на сервер будут ТОЛЬКО компоненты, которые находятся в папках ds_ и их подпапках. все остальные папки внутри src проекта на сервер не уйдут.
    Еще раз: ваша задача по итогу получить список компонентов, которые вы отправите на сервер в рамках текущей задачи. А не тащить все, что есть в bi-magic-resources. Он лишь помогает вам в достижении цели.
  2. Если вы не заводите папку ds_15 для одноименного атласа - то его ресурсы и не будут ни скачаны, ни изменены, не станут храниться в гите.
    Однако ничего не мешает вам завести в проекте папки для всех атласов и далее при помощи настроек тут GitHub - luxms/bi-magic-resources

Указать например

noRemove: true 

что не даст проекту удалять файлы при пуше ресурсов на сервер. Будут только добавления новых и изменения старых

include: "ds_res|ds_51|ds_679" 

эта директива означает, что только те папки типа ds_ которые удовлетворяют регулярному выражению что указано в строке будут участвовать в сборке и отправке результата на сервер.

Антоним этой директивы -

exclude 

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

  1. Да, вам стоит придумать схему для именования атласов и тем самым избежать ситуаций, при которых вы в одноименные атласы но разных стендов заливаете разные ресурсы. Обычно такое возникает при переносах БД с контура на контур. Но если боитесь - всегда можно завести отдельную ветку гита bi-magic-resources с разными адресами серверов, доступами и ресурсами и тем самым разделить ресурсы навсегда. Да, придется иногда заниматься мержем разных веток, чтобы не дублировать действия, но вы хотя бы контролируете этот процесс. Плюс не факт, что вам такое понадобится
    пример от одного из наших клиентов: ds_кластер_наименованиепроекта_номератласа: ds_brd_gradient_7. Я бы добавил по необходимости префиксы для теста, прода и дева, но это опционально.

Учитывайте, пожалуйста, что если ваш девопс переименует атласы в БД, то ресурсы автоматически будут подтягиваться из “новых” своих атласов. Но в проекте bi-magic-resources вам придется следить за этим вручную. И вручную же переименовывать папки. Благо это происходит обычно очень редко

Observable сервисы

Это инстансы класса, являющегося наследником базового класса BaseService, который вы получаете из обвязки (из модуля “bi-internal/core” если точнее).

Он сам по себе реализует паттерн, при котором вы формируете наблюдаемую модель (некий объект сродни реактовскому state), изменения которого происходят публичными методами этого сервиса и который наследует механизм уведомления компонентов-подписчиков данного сервиса. Чаще всего обязательными ключами такой модели являются булев loading и строковый error (по ним вы можете отслеживать, готова ли модель для работы или еще в процессе загрузки, а также не содержит ли она ошибок). Чаще всего дефолтным значением для них является

loading: true,
error: ""

То есть мы изначально ожидаем, что ошибок нет, а модель грузится. Когда целевое действие в инициализации сделано (например вы загрузили с сервера важные структуры данных и как-то организовали их хранение), то вы выставляете

loading: false

и если имеет место - указываете текст ошибки. Работа с сервисом начнется именно с момента loading: false

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

Сами по себе сервисы могут реализовывать как Singleton подход (т.е. конструктор такого класса пустой), так и быть Factory и зависеть от неких входных параметров (например идентификатора куба, имени схемы атласа и чего угодно, что для вас имеет смысл)

Основная цель observable сервиса - предоставить вам механизмы взаимосвязи между как компонентами в разных контекстах, так и экономного запрашивания данных, которые “дорого” запрашивать часто, не кешируя.

Так же он позволит вам выступать в качестве портала, через который можно передавать данные, реализовывать собственную событийную систему. Или в качестве некой смеси Model-Controller из MVC архитектуры для фронтенда.

UrlState и работа с урлом приложения

Самым первым примером такого сервиса выступает UrlState из пакета “bi-internal/core”. Это Singleton, который следит за состояние url SPA-приложения luxmsbi-web-client (Обвязки).

import {UrlState} from 'bi-internal/core';

Его модель выглядит так:
Ряд ключей уже устарели и являются необходимыми только для MLP кубов (нашей более старой версии хранения данных в атласе (тогда это звалось датасет)), потому я позволил себе их убрать из списка:

dash: null // строка, хранящая идентификатор текущего выделенного дешлета (раскрытого на весь экран) 
dboard: "4" // идентификатор текущего дешборда
path: ( ['ds', 'ds_5142', 'dashboards'] // полный путь к текущему разделу
route: "#dashboards" // идентификатор роута текущего раздела. В данном случае мы на странице с дешбордами
segment: "ds" // идентификатор плагина (сегмента) в общем случае или одного из нескольких разделов ,
              //  которые вы видите на стандартной разводящей странице после авторизации
segmentId: "ds_5142" // идентификатор элемента раздела (сегмента)
slide: null // идентификатор слайда (актуально при предпросмотре презентации)
f: {} // опциональный ключ, который по синтаксису идентичен тому, что вы пишете в блоке filters конфига дешлета
      // c той разницей, что туда нельзя писать значение true, только идентификатор дименшна и массив 
      // с оператором и операндами. Если там появится что-то вроде sex: ["=", "Мужской"] то вы таким образом 
      // наложите сохраняющийся при перезагрузке страницы фильтр на дименшн sex. Потому этот способ 
      // используется, чтобы передать кому-то ссылку на страницу с предустановленными фильтрами (будет get-параметр &f.sex=Мужской)
_koobFilters: {} // хранит фильтры из сервиса фильтров, которые вы не хотите показывать в урле (например потому, что урл не резиновый, а строка фильтра может быть огромной)

У данной модели есть особенности: все ключи здесь являются частью стандартного интерфейса IUrlState. И не все из них вы видите в итоговом урле приложения, потому что не все из них на данный момент времени могут иметь значение или оправданы разделом. Не являются частью стандартного интерфейса “f” и “koobFilters" они добавляются программно сервисом по фильтрации данных например.
Так вот постулируется следующее: все ключи в модели UrlState, которые содержат "
” в начале являются скрытыми и явно в урле не видны. Например вы хотите сохранить в модели UrlState объект, который хранит какую-то нужную вам информацию на другом дешборде или на другом атласе. Вы можете сохранить его как раз в ключе с “_”. А если хотите сделать новый get-параметр - укажите обычный ключ, без префикса.
Этот сервис предоставляет ряд методов, которые можно использовать:

UrlState.navigate({dboard: "3"}) 

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

UrlState.updateModel({_myData: {key1: 4546, data: [124, 5757, 575468]}})

И последующее получение

UrlState.getModel()._myData

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

Подобная возможность хранения в данной модели произвольных данных связана с функционалом презентаций, когда вы путешествуете по разделам BI и добавляете интересующую вас страницу (например ту, где вы выбрали какой-то фильтр или сделали действие ,которое достигается вашим кастомным сервисом ). Добавление страницы в конкретный шаблон презентации под капотом есть ни что иное как добавление в список ее полной модели UrlState.

Ибо на сервере при генерации презентаций работает headless chrome, который получает объект модели urlState, восстанавливает страницу, исходя из модели и результат рендерит и сохраняет картинку в pdf или pptx (и так для каждого слайда).

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

Рассмотрим примеры сервисов

Давайте рассмотрим пример observable сервиса, который вы можете использовать в своих компонентах. Предлагаю хранить в папке src/services и иметь в имени класса слово “Service”. Данный сервис есть ни что иное как локальная версия коробочного сервиса по управлению фильтрами KoobFiltersService (назовем файл KoobFiltersService.ts). И допустим мы хотим его как-то поменять и в дальнейшем в компонентах использовать эту версию сервиса, а не ту, что приходит нам из обвязки

import { BaseService, UrlState } from 'bi-internal/core';
import { throttle } from 'lodash';

const throttleTimeout = 0;  // можно ставить достаточно большим
                                                // повторные фильтры будут срабатывать в течение этого времени
export interface IKoobFiltersModel {
  loading?: boolean;
  error?: string;
  query?: string;
  result: any;
  filters: any;
  pendingFilters: any;
}

export class KoobFiltersService extends BaseService<IKoobFiltersModel> {
  private constructor() {
    super({
      loading: false,
      error: null,
      query: undefined,
      result: {},
      filters: {},
      pendingFilters: {},
    });
   // Хотим, чтобы каждый раз ,когда в модели 
    UrlState.subscribeAndNotify('_koobFilters f', this._onUrlStateUpdated);
  }

  protected _dispose() {
    UrlState.unsubscribe(this._onUrlStateUpdated);
    super._dispose();
  }

  private _onUrlStateUpdated = (url) => {
    this._updateWithData({filters: {...url._koobFilters, ...url.f}});
  }

  public setFilter(koob: string, dimension: string, filter?: any[]) {
    let filters = this._model?.pendingFilters;
    if (filter) {
      let arr: string[] | undefined = filter?.slice(0);
      filters = {...filters, [dimension]: arr};
    } else {
      filters = {...filters, [dimension]: undefined};
    }
    this._updateModel({pendingFilters: filters});
    this._applyAllFilters();
  }

  public setFilters(koob: string, newFilters: any) {
    let filters = this._model.pendingFilters;
    for (let dimension in newFilters) {
      let filter = newFilters[dimension];
      if (filter) {
        let arr: string[] | undefined = filter?.slice(0);
        filters = {...filters, [dimension]: arr};
      } else {
        filters = {...filters, [dimension]: undefined};
      }
    }
    this._updateModel({pendingFilters: filters});
    this._applyAllFilters();
  }

  public applyPeriodsFilter(dimension: string, lodate: string | number, hidate: string | number) {
    const filters = this._model.pendingFilters;
    const _filters = {...filters, [dimension]: ['between', lodate, hidate]};
    this._updateModel({pendingFilters: _filters});
    this._applyAllFilters();
  }

  private _applyAllFilters = throttle(() => {
    const filters = {...this._model.filters, ...this._model.pendingFilters};
    this._updateModel({pendingFilters: {}});

    const url = UrlState.getInstance().getModel();
    let publicKeys = Object.keys(url.f || {});                                                      // Раскидываем ключи фильтров на две части - публичную и скрытую
    const publicFilters = {}, privateFilters = {};                                                  // в публичную попадают ключи, которые уже есть в url
    for (let key in filters) {                                                                // Может быть стоит добавить какое-то более остроумное условие
      if (publicKeys.includes(key)) {
        publicFilters[key] = filters[key];
      } else {
        privateFilters[key] = filters[key];
      }
    }

    UrlState.getInstance().updateModel({f: publicFilters, _koobFilters: privateFilters});
  }, throttleTimeout);

  public static getInstance = () => {
    if (!(window.__koobFiltersService)) {
      window.__koobFiltersService = new KoobFiltersService();
    }
    return window.__koobFiltersService;
  };
}

KoobFiltersService.getInstance();

KoobFiltersService - singleton, который аггрегирует в себе информацию обо всех фильтрах, которые были сделаны пользователем на текущий момент. По умолчанию, на фильтр, добавляемый в модель такого сервиса реагируют не все деши, а лишь те, для которых в блоке filters прописано true на него

filters: {
    category: ["=", "Clothes", "Shoes", "Scarfs", "Bags"],
    example: true
}

то есть если я каким-то образом (через упр.деш или программно) выставлю фильтр на example - дешлет обновится и перезапросит данные.

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

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

Рассмотрим сервис выше далее.
От предка сервис наследует два самых часто используемых метода:

subscribeAndNotify("строка с интересующими ключами из модели через пробел", "колбек, который надо вызвать")
subscribeUpdatesAndNotify("колбек, который надо вызвать")

Эти методы вы будете использовать только в классовых компонентах, ибо в функциональных методы работы с ней немного проще.

Первый метод будет вызвать колбек только тогда, когда в сервисе поменялись один или несколько ключей ,которые были указаны через пробел (в нашем примере это “_koobFilters” и “f”).

Колбек есть функция написанная в компоненте и которая совершает какие-то действия с его state (вызывает setState). При указании нее вы автоматически передаете this как ссылку на компонент сервису. Благодаря ей он и вызывает нужный колбек у нужного компонента, дирижируя состояниями компонентов на странице(-цах).

Второй метод (subscribeUpdatesAndNotify) - менее избирателен и будет вызывать колбек, если любое поле модели было изменено.

В затроттленном методе applyAllFilters мы видим как раз ту самую ситуацию, что мы хотим выставить важные для нас параметры в урл (явно и неявно)

UrlState.getInstance().updateModel({f: publicFilters, _koobFilters: privateFilters});

Это позволит подхватить выставленные фильтры в тех же презентациях или где бы то ни было.

Еще один метод внутри сервиса: выставление новых значений модели:

this._updateWithData({filters: {...url._koobFilters, ...url.f}});

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

Метод выше поменяет только те ключи, что мы указали, но у метода есть собратья в виде

_updateWithLoading() // выставит error: null и loading: true
_updateWithError(error: string) // выставит loading: false, error: error

Просто для удобства.
и последнее

 UrlState.unsubscribe(this._onUrlStateUpdated);

unsubscribe - метод, который очищает память и удаляет инстанс. На тот случай. если вы не хотите доверять тому, что есть в кеше и хотите инициировать его заново при определенных условиях. Чаще всего вызывается на unmount реакт-компонента, если это необходимо (в большинстве случаев нет)

Если вы хотите создавать инстансы для каждого из переданных идентификаторов, то вот пример метода createInstance

// Тут пример конструктора

export class DatePickerService extends BaseService<IDatePickerModel> {
  private readonly id: string | number;
  private constructor(koobId: string) {
      super({
         loading: false,
         error: null,
         data: [],
       });
      this.id = koobId;
  }
 // Тут какие-то методы, которые вам понадобятся

// ляляля

// Пример createInstance

  public static createInstance (id: string | number) : DatePickerService {
    if (!(window.__datePickerService)) {
      window.__datePickerService = {};
    }
    if (!window.__datePickerService.hasOwnProperty(String(id))) {
      window.__datePickerService[String(id)] = new DatePickerService(String(id));
    }
   
    return window.__datePickerService[String(id)];
  };

Итого:
Вы можете использовать пример с сервисами выше как шаблон. К чему все сводится:

  1. Пишете метод, который создает инстанс и прихранивает его в переменной где-то в window
  2. В конструкторе прописываете логику инициализации (или вызываете функцию инита, которую в классе и пропишете)
  3. Создаете публичные методы, которые будете дергать из компонентов-подписчиков, и которые меняют модель сервиса одним из методов типа _updateWith
Подписка на сервисы

Мы уже видели в примеров функционального компонента MyComponent подписку на сервис KoobFiltersService

Она достигалась методами useService, useServiceItself.

Так вот, такая запись

const koobFiltersService = useServiceItself<KoobFiltersService>(KoobFiltersService);

или такая

const koobFiltersServiceModel = useService<KoobFiltersService>(KoobFiltersService);

В обоих случаях приведет к тому, что модель сервиса, хранящаяся в переменной koobFiltersService ( через .getModel()) или koobFiltersServiceModel будет всегда актуальной. и когда бы вы не обратились к этим переменной - они, при условии, что loading: false дадут вам свою актуальную модель

Достаточно в ключевых местах (но только ниже всех useEffect, иначе ошибка минифицированного реакта будет) вызвать блок

if (koobFiltersServiceModel.loading || koobFiltersServiceModel.error) return;

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

Примеры для сиглтон и не-синглтон сервисов


const koobFiltersService = useServiceItself<KoobFiltersService>(KoobFiltersService)
const datePickerService = useServiceItself<DatePickerService >(DatePickerService, "luxmsbi.myKoob")

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

Рассмотрим пример

import React from "react";
import './DatePickers.scss';

// Подключили кастомный сервис
import {DatePickerService} from "../services/ds/DatePickerService";

export default class DatePickers extends React.Component<any> {
  private _datePickerService: DatePickerService = null; // подготовили переменную для хранения инстанса сервиса внутри текущего компонента.

  public state: {
    koob: string;
    data: any;
    error: string,
  };

  public constructor(props) {
    super(props);
    this.state = {
      koob: "",
      data: [],
      error: ""
    };
  }
  public componentDidMount(): void {
    const { cfg } = this.props;
    const koob = cfg.getRaw().koob;

   // Пусть сервис зависит от идентификатора куба
    this._datePickerService = DatePickerService.createInstance(koob);
    this._datePickerService.subscribeUpdatesAndNotify(this._onSvcUpdated); // подписка на все изменения модели
  }


  private _onSvcUpdated = (model) => {
    if (model.loading || model.error) return; // проверяем, готов ли к работе сервис и устанавливаем стейт из данных модели
    this.setState({
      data: model.data,
    });
  }

  public onSubmitClick = () => {
 // прим какого-то целевого действия, вызывающего метод, меняющий модель сервиса
      this._datePickerService?.setFilter(here_some_arguments);
  }
  
  public render() {
    const {data} = this.state;
    return (
      <div className="DatePickers">
        {/* что-то делаем с data */}
          <div className="DatePickers__SelectButtons">
            <div className="DatePickers__SelectButton active" onClick={this.onSubmitClick}>Применить</div>
          </div>
        </div>
      </div>
    );
  }
}

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

Коллеги, добрый день.
На основании указанной выше информации, запустил команду npm install. Но она завершилась с ошибками. Команды npm i -force и legacy-peer-deps так же идут с ошибками. Подскажите плиз что здесь не так. Лог с ошибками приложил ниже.
у меня версия react 18.2.0

npm resolution error report

While resolving: @luxms/bi-resources@8.8.1
Found: react@16.14.0
node_modules/react
dev react@“^16.5.0” from the root project

Could not resolve dependency:
peer react@“>=18.0” from @react-three/fiber@8.12.0
node_modules/@react-three/fiber
peer @react-three/fiber@“>=6.0” from @react-three/drei@8.10.6
node_modules/@react-three/drei
@react-three/drei@“^8.10.6” from the root project

Fix the upstream dependency conflict, or retry
this command with --force or --legacy-peer-deps
to accept an incorrect (and potentially broken) dependency resolution.

Как вы можете сами прочитать в тексте ошибки - вы получили конфликт депенденси, потому что подняли версию реакта выше той .что приходит из обвязки. Это работает на понижение, но не работает на повышение, потому что так постулирует node package manager. Если бы вам не приходили доп.модули типа библиотек three.js выше, то все даже бы завелось, хоть и c warning.

Потому вы можете сделать две вещи:

  1. Вернуть версию реакта до ^16.5.0
  2. И/или удалить @react-three/fiber и @react-three/drei

Ибо для работы проекта они не обязательны, но чувствительны к тому, какая версия реакта у вас сейчас. Если бы вы поставили их себе нативно, то оно бы сработало, а так как вам и реакт и пакеты приходят извне. то встречая локальную версию реакта - некоторые компоненты могут конфликтовать.

Коллеги, спасибо за пост. Свои компоненты уже получается делать.
Еще просьба по возможности приложить пример компонента(internal), который реагирует на изменения в управляющем дэше (из коробки).
То есть нужно чтобы при изменении фильтра в управляющем дэше, выполнялась фильтрация в кастомном дэше (internal).
На работающих примерах намного быстрее идет освоение материала.

Здравствуйте. А вы точно пробовали примеры, которые я указывал выше?

я писал компонент такого рода

// react и echarts на самом деле тянутся из обвязки "на лету"
import React, { useEffect, useState } from "react";
import  * as echarts from 'echarts';
// KoobFiltersService - Observable сервис, управляющий фильтрами для кубов (по умолчанию его использует упр.дешлет)
// useService, useServiceItself - специальные хуки для получения только модели или всего инстанса какого-либо
// Observable сервиса. Принимает по умолчанию класс нужного сервиса и если это не singleton то еще и идентификатор (через запятую)
import {KoobFiltersService, useService, useServiceItself} from 'bi-internal/services'; 

import './MyComponent.scss';

const MyComponent = (props) => {
  const { cfg, subspace, dp } = props;
  const [data, setData] = useState<any>([]); 
  const [clickedPointName, setClickedPointName] = useState<string>(""); // для наглядности покажем на какую точку (Y,X) кликнули
  const koobFiltersService = useServiceItself<KoobFiltersService>(KoobFiltersService); // Получили инстанс сервиса фильтров
  // через метод koobFiltersService.getModel() можем получить его модель,
  // а через метод koobFiltersService.setFilter(koobId, dimensionId, valueArray) // ("", "category", ["=", "Beer"])
  // можем фильтровать данные дешлетов, которые подписаны в своих блоках filters на изменение этого дименшна (содержат "category": true)

  // Храним реф-ссылку на контейнер для графика, сам инстанс Echarts и опции, которые ему передаем
  let containerRef = null; 
  let chart = null;
  let options = {};

  // Обрабатываем клик по точке графика. Проверяем, есть ли в конфиге дешлета свойство onClickDataPoint, отвечающая
  // в коробке за логику клика на точку по умолчанию
  // если нет - просто реализована произвольная логика выставления текущего значения дименшна в фильтр + для наглядности
  // показываем в интерфейсе строчку с "координатами" кликнутой точки

  const onChartClick = (params): void => {
// о том, что входит в params можно подглядеть тут https://echarts.apache.org/en/api.html#events.Mouse%20events
    if (cfg.getRaw().hasOwnProperty('onClickDataPoint')) {
      // Формируем объект информации о точке для встроенного контроллера обработки клика по точке
      const vcpv = {m: undefined, l: undefined, p: undefined, z: undefined, y: params.data.y, x: params.data.x, v: params.value};
      cfg.controller.handleVCPClick(params.event, vcpv)
    } else {
      const koobFiltersModel = koobFiltersService.getModel();
      if (koobFiltersModel.loading || koobFiltersModel.error) return;
      koobFiltersService.setFilter('', params.data.x.axisIds[0], ["=",params.name]);
    }
    setClickedPointName(`${params.data.y.title} ${params.data.x.title}`);
  }
  // На инит рефа создаем с нуля или обновляем существующий инстанс Echarts и подаем ему опции на вход
  // конфигурацию графиков Echarts смотрите тут https://echarts.apache.org/en/option.html#title
  const onChartCreated = (container) => {
    if (container && data.length) {
      if (!containerRef) {
        containerRef = container;
        chart = echarts.init(containerRef, null, {renderer: 'svg'});
      }
      options = {
        title: {
          show: false
        },
        tooltip: {
          trigger: 'item',
          appendToBody: true,
          show: true
        },
        xAxis: {
          type: 'category',
          data: subspace.xs.map(x => x.title)
        },
        yAxis: {
          type: 'value'
        },
        series: subspace.ys.map((y, yIndex) => ({
          data: subspace.xs.map((x, xIndex) => ({
            name: x.title,
            itemStyle: {
              // контроллер, который получает информацию о цвете автоматически, исходя из контекста
              color: cfg.getColor(y, null, yIndex),
            },
            x,
            y,
            value:  data[yIndex][xIndex] // мы получали матрицу YX
          })),
          name: y.title,
          type: 'bar', // я задал этот тип явно, но это можно прочитать из конфига дешлета 
                       //как переменную cfg.getRaw().chartType например
          showBackground: true,
        })),
        legend: {
          show: true,
          data: subspace.ys.map((y, yIndex) => ({
            name: y.title,
            icon: 'circle',
            itemStyle: {
             // контроллер, который получает информацию о цвете автоматически, исходя из контекста
              color: cfg.getColor(y, null, yIndex),
            },
          }))
        },
      };
      chart.setOption(options);
      chart.resize(); // принудительно заставляем расшириться на весь контейнер
      chart.on('click', 'series', onChartClick); // Обрабатываем клик по серии, если нужно
    }
  }

  useEffect(() => {
    // Получаем полное декартово произведение для указанного конфига в дешлете
    // ожидаем матрицу [subspace.ys.length][subspace.xs.length] 

    dp.getMatrixYX(subspace).then(dataArr => {
      setData(dataArr || []);
    });
  },[]);

  return (
    <div className="MyComponent">
      {clickedPointName != "" && <div className="MyComponent__onClickText">Вы кликнули на {clickedPointName}</div>}
      <div ref={onChartCreated} className="MyComponent__graphic"></div>
    </div>
  );
}
export default MyComponent;

В блоке useEffect происходит прямое обращение к текущему subspace, которое, при условии, что в конфиге дешлета в блоке filters вы пропишете mydimensionid: true будет автоматически вызвано уже с учетом ваших фильтров.
Чуть более прямое управление фильтрацией и загрузкой данных можно сделать примерно так (чуть изменив логику у компонента выше):

import {KoobDataService} from 'bi-internal/services'; // получаем сервис по работе с данными
const {koobDataRequest3} = KoobDataService; // получаем функцию для запроса данных
const koobFiltersServiceModel = useService<KoobFiltersService>(KoobFiltersService); // Получили модель сервиса фильтров
const [myData, setMyData] = useState([]);
const PAGE_SIZE = 5; //число строк на странице при пейджинге
let page = 1; //  хранит ваш номер текущей страницы при пейджинге

// далее в блоке с useEffect пишем что-то вроде
useEffect(() => {
koobDataRequest3(
cfg.getRaw().dataSource.koob, // id вашего куба
cfg.getRaw().dataSource.dimensions,  // поля, которые вы запрашиваете (по умолчанию сюда указывать список дименшнов)
cfg.getRaw().dataSource.measures, // список межей, если нужно, если указать пустой массив, то будут DISTINCT для дименшнов
koobFiltersServiceModel.filters, //вы здесь берете фильтры напрямую из сервиса фильтров, но не учитываете те фильтры, которые прописаны в конфиге дешлета. Если вам нужно ,чтобы и они учитывались тоже, то вам придется 
писать что-то вроде ```{...cfg.getRaw().dataSource.filters, ...koobFiltersServiceModel.filters}```
 {
      offset: page * PAGE_SIZE, 
      limit: PAGE_SIZE,
      sort: ["+dt"], // сортировка по дименшну dt ASC (DESC при "-")
      options: ['!MemberAll', '!ParallelHierarchyFilters'],
      schema_name, // поле schema_name вашего датасета, например ds_51 или прямо из урла: UrlState.getInstance().getModel().segmentId;
    }, 
`request_for_my_data` // строчка-комментарий, который добавится после знака ? для запроса за вашими данными. Просто для наглядности. Пробелов не завезли
).then(data => {
// пришли данные с сервера - это декартово произведение дименшнов и меж (массив объектов с текущим значение каждой межи и каждого дименшна в данной точке)
// что с этим делать решать вам, зависит от бизнес-логики, можно сделать setMyData(data) и в цикле выводить каждый элемент такого массива как строку, а каждый ключ как ячейку таблицы, коей она и является.
})
}, []);
 
// сюда можно добавить блок
if (koobFiltersServiceModel.loading || koobFiltersServiceModel.error) return null; // гарантируем, что ничего не отрендерится, пока сервис не будет загружен

return (/*тут рендер основанный на использовании myData например*/)