@esvit/tiny-bi
v0.0.1
Published
1. [Overview](#overview) 2. [Request Structure](#request-structure) 3. [Response Structure](#response-structure) 4. [Chart Types](#chart-types) 5. [Server](#server) 6. [Datasource](#datasource) 7. [Filter](#filter) 8. [Query](#query)
Readme
BI Tool
Overview
Складається з двох частин:
- бекенд, який відпрацьовує запити до бази даних та повертає дані у форматі JSON
- фронтенд, який відображає дані у вигляді таблиць та графіків
Бекенд може бути 2 типів:
- опрацювання кастомізованих запитів, які передаються з фронтенду
- захаркожена логіка, яка виконує запити до бази даних та повертає дані у форматі JSON
Схема роботи виглядає наступним чином:
sequenceDiagram
actor User as Користувач
participant Widget as Віджет
participant Server as Сервер
participant Lib as Бібліотека tiny-bi
participant DB as База даних
Note over Server: Реалізовується поза бібліотекою
User->>Widget: Або створює віджет через конфігуратор
User->>Widget: Або відкриває віджет в дашборді
Widget->>Server: Надсилає конфігурацію графіку
Server->>Lib: Передає дані конфігурації (jsonToSql)
Lib->>Lib: Зчитує та валідує дані (схема)
Lib->>Server: Повертає SQL запит
Server->>DB: Сервер виконує SQL запит
DB->>Server: Повертає результати
Server->>Lib: Передає результати для форматування (dataToJson)
Lib->>Server: Повертає відформатовані дані
Server->>Widget: Передає результат у віджет
Widget->>User: Милується результатомПриклад реалізації сервера: example/index.js Приклад реалізації фронта: example/index.html
Request Structure
Запит до бібліотеки tiny-bi для побудови графіків має наступну базову структуру:
{
"type": "chart_type", // тип графіку, наприклад "bar", "line", "kpi" тощо
"source": "table_name", // основний datasource, з якого будуть братися дані
"dimensions": {
"rows": [ // масив полів для рядків
{ "name": "field_name", "source": "table_name" }
],
"columns": [ // масив полів для стовпців
{ "name": "field_name", "source": "table_name" }
]
},
"measures": [ // масив полів для вимірювань
{ "name": "field_name", "source": "table_name", "grouping": "sum" }
],
"filter": { // обʼєкт фільтру, який буде застосовано до запиту
// описано далі по тексту
}
}Response Structure
Відповідь від бібліотеки tiny-bi має наступну структуру:
{
"type": "chart_type", // тип графіку, наприклад "bar", "line", "kpi" тощо
"source": "table_name", // основний datasource, з якого будуть братися дані
// ... інші поля, які були в запиті, повторюються тут щоб фронтенд міг будувати графік знаючи лише відповідь без запиту
// поля, які додаються до відповіді
"data": [ // масив масивів чисел, який містить дані для відображення
[1, 2, 3], // перший рядок даних
[4, 5, 6], // другий рядок даних
],
"meta": { // метадані, які можуть бути корисні для відображення
"page": 1, // номер сторінки
"size": 25, // кількість рядків на сторінці
"total": 100, // кількість рядків всього
}
}Chart Types
KPI
Відображає значення метрики та її зміну відносно попереднього періоду.
Конфігурація для KPI виглядає наступним чином:
{
"type": "kpi",
"measures": {
"value": {
"field": { "name": "amount", "source": "transactions", "grouping": "sum" }
},
"compareValue": {
"field": { "name": "amount", "source": "transactions", "grouping": "sum" },
"relativePeriod": {
"field": { "name": "date", "source": "transactions" },
"interval": "-1 month"
}
}
},
"filter": "-- описано далі по тексту --"
}measures містить поле value, воно обовʼязкове, функція групування для поля обовʼязкова, описує одне поле для відображення значення метрики.
Для доходу за певний місяць запит буде виглядати наступним чином:
{
"type": "kpi",
"source": "transactions",
"measures": [
{ "name": "amount", "grouping": "sum" }
],
"filter": {
"link": "and",
"conditions": [
{
"field": "date",
"operator": "between",
"value": ["2025-01-01", "2025-01-31"]
},
{
"field": "type",
"operator": "equal",
"value": ["income"]
}
]
}
}Сформує SQL запит:
SELECT SUM(transactions.amount) AS value
FROM transactions
WHERE transactions.date BETWEEN '2025-01-01' AND '2025-01-31'
AND transactions.type = 'income';Для порівняння значення з попереднім періодом, наприклад, за місяць, потрібно додати поле compareValue:
{
"type": "kpi",
"source": "transactions",
"measures": {
"value": {
"field": { "name": "amount", "grouping": "sum" }
},
"compareValue": {
"field": { "name": "amount", "grouping": "sum" },
"relativePeriod": {
"field": { "name": "date" },
"interval": "-1 month"
}
}
},
"filter": {
"link": "and",
"conditions": [
{
"field": "date",
"operator": "between",
"value": ["2025-01-01", "2025-01-31"]
},
{
"field": "type",
"operator": "equal",
"value": ["income"]
}
]
}
}Server
Інтерфейс сервера має наступні методи:
async loadDatasources(path: string); // завантажує джерела даних з файлу за вказаним шляхом
async jsonToSql(json: any, tenantPayload?: any); // перетворює JSON запит у SQL запит, у tenantPayload можна передати дані про tenant, які підставляти в запит (ключ-значення)
async dataToJson(type: string, data: any, meta?: any); // перетворює дані з бази даних у JSON формат для відображення на фронтенді, meta - додаткові метадані, які можна передатиDatasource
Datasource - це джерело даних, яке містить інформацію про таблиці, поля та їх типи. Задача datasource - побудувати проєкцію даних для запитів до бази даних. В папці має бути файл index.yaml, який містить масив джерел даних. Кожне джерело даних має наступну структуру:
index.yaml:
datasources:
transactions: transactions.yamltransactions.yaml:
tableName: 'Transactions' # назва таблиці в базі даних
title: # строка або обʼєкт з локалізаціями, те що користувач буде бачити в інтерфейсі
en: "Transactions"
uk: "Транзакції"
condition: "TenantId = {tenantId} AND DeletedAt IS NULL" # умова, яка буде застосована до всіх запитів до цієї таблиці, tenantId буде підставлено з tenantPayload
fields:
- name: "amount" # назва поля в базі даних
title: # строка або обʼєкт з локалізаціями, те що користувач буде бачити в інтерфейсі
en: "Amount"
uk: "Сума"
type: "string" # тип поля, може бути "string", "number", "date", "boolean" тощо
- name: "Date"
title: 'Date'
type: "string"
- name: "Description"
title: 'Comment'
type: "string"
relations: # масив звʼязків з іншими таблицями
- name: 'project' # назва звʼязку
title: 'Project' # заголовок, який буде показано в інтерфейсі
to: "projects" # назва datasource, з якою звʼязок
field: "ProjectId" # поле в цій таблиці, яке є зовнішнім ключем
targetField: "Id" # поле в цільовій таблиці, на яке вказує зовнішній ключ
type: "n:1" # тип звʼязку, може бути "1:1", "1:n", "n:1", "n:n"
- name: 'counterparty'
title: 'Counterparty'
to: "counterparties"
field: "CounterpartyId"
targetField: "Id"
type: "n:1"Як використовувати datasource:
// 1. отримуємо datasource потрібного типу
const datasource = DataSource.getDatasource('transactions');
// 2. отримуємо поле за його назвою і шляхом, тут поле 'Name' буде шукатися в таблиці 'projectCategory', яка повʼязана з таблицею 'project', а 'project' в свою чергу повʼязана з таблицею 'transactions'
const field = datasource.getField('Name', [{ name: 'project' }, { name: 'projectCategory' }], 'sum');
// 3. отримуємо SQL запит для цього поля
const query = new Query({
fields: [field], // сюди передаємо масив полів, які отримали з datasource
});Filter
Перетворює json запит умов у SQL запит. На вхід приймає JSON обʼєкт, який містить поле link з можливими значеннями and, or та масивами фільтрів, які потрібно обʼєднати.
{
"link":"and",
"conditions":[
{
"field": "transactions.date",
"operator": "between",
"value": ["2023-01-01", "2023-12-31"]
},
{
"field": "transactions.amount",
"operator": "greater_than",
"value": 100
}
]
}Обʼєкт з conditions має наступні поля:
field- поле, по якому потрібно фільтруватиoperator- оператор фільтрації, який може бути одним з наступних:equalnot_equalgreater_thanless_thanbetweenis_nullis_not_nullstarts_withends_withcontainsdoes_not_containmatchesbeforeafteris_onlastnext
values- масив значень, по яких потрібно фільтрувати. Для операторівbetweenмає бути масив з 2 значень, для інших операторів - масив з 1 значенням.modifier- лише для операторівnextтаlast, може бути одним з наступних:month- за наступний або попередній місяцьday- за наступний або попередній деньyear- за наступний або попередній рікminute- за наступну або попередню хвилинуhour- за наступну або попередню годинуweek- за наступний або попередній тижденьquarter- за наступний або попередній квартал
Query
Відповідає за побудову SQL запиту на основі JSON обʼєкту, який містить інформацію про джерела даних, поля, фільтри та сортування.
const query = new Query({
tableName: 'FinanceTransactions', // основна таблиця, з якої будуть братися дані
filter: new Filter // фільтр, який буде застосовано до запиту
tableAlias // псевдонім таблиці, який буде використано в запиті
limit // кількість рядків, які потрібно повернути
offset // кількість рядків, які потрібно пропустити
fields: QueryField[];
noGrouping?: boolean;
distinct?: boolean;
fromQuery?: Query;
fromUnions?: Query[];
sorting?: QueryField[];
union?: Query[];
joins?: string[];
});Libs
- Odometer https://github.com/HubSpot/odometer
