Бэнды и наборы данных

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

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

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

На рисунке ниже показан пример бэндов, используемых в отчете:

bands 1
Figure 1. Пример бэндов отчета

Бэнд Root в определении отчета предопределен, однако он может не использоваться в шаблоне.

Примеры различных бэндов и наборов данных можно найти в руководстве Report Generation.

Бэнды

Бэнды можно определить во время выполнения на вкладке Bands экрана деталей отчета или во время разработки с помощью аннотации @BandDef.

Вкладка Bands

Вкладка Bands редактора отчетов позволяет определить иерархию бэндов и наборы данных для каждого бэнда.

bands

Бэнд отчета имеет следующие параметры:

  • Name — имя бэнда, уникальное в пределах отчета. Должно содержать только латинские буквы, цифры и знаки подчеркивания.

    Если имя бэнда начинается с header, его данные не будут выводиться при использовании форматера таблиц.
  • Orientation - ориентация бэнда: Horizontal, Vertical или Crosstab.

    • Horizontal бэнды копируются вниз. Они могут содержать подбэнды.

    • Vertical бэнды копируются вправо.

    • Crosstab бэнды копируются вправо и вниз в виде матрицы.

  • Parent - родительский бэнд.

  • Multiple Datasets - установите этот флажок, если хотите определить более одного набора данных для бэнда.

  • Streaming — установите этот флажок, чтобы сделать бэнд потоковым.

  • Dataset type — тип набора данных.

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

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

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

Обратите внимание, что столбец Dataset name, доступный при работе с несколькими наборами данных, предназначен только для удобства пользователя.

Аннотация @BandDef

Аннотация @BandDef позволяет определять бэнды при создании отчета во время разработки. Она имеет следующие атрибуты:

  • name - имя бэнда, уникальное в пределах отчета. Должно содержать только латинские буквы, цифры и знаки подчеркивания.

  • parent - имя родительского бэнда. Может быть опущено только для бэнда Root.

  • root - должно быть установлено в true только для бэнда Root. По умолчанию false.

  • orientation - ориентация бэнда, определяемая перечислением Orientation: HORIZONTAL, VERTICAL, CROSS. По умолчанию HORIZONTAL.

  • streaming - значение true делает бэнд потоковым. По умолчанию false.

  • dataSets - наборы данных, определяемые массивом аннотаций @DataSetDef.

Отчет должен включать определение бэнда Root с атрибутами name = "Root" и root = true.

Порядок аннотаций @BandDef в Java-классе определения отчета важен. Он влияет на порядок вывода бэндов на одном уровне иерархии.

Потоковые бэнды

Начиная с Jmix 3.1

Потоковый бэнд читает строки по одной из курсора базы данных и сразу записывает их в документ XLSX. Механизм отчетов держит в памяти только последние записанные строки, а более ранние сбрасывает во временный файл. Данные остальных бэндов, как обычно, загружаются в память. Чтобы сделать бэнд потоковым, установите флажок Streaming на вкладке Bands или задайте streaming = true в аннотации @BandDef.

Отчет с потоковым бэндом должен соответствовать следующим требованиям:

  • В отчете только один потоковый бэнд.

  • Потоковый бэнд — бэнд первого уровня, то есть прямой потомок бэнда Root, и у него нет дочерних бэндов.

  • У потокового бэнда ровно один набор данных типа SQL или JPQL.

  • Все бэнды отчета горизонтальные.

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

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

Запрос потокового бэнда выполняется в отдельной транзакции только для чтения. Транзакция остается открытой, пока записываются строки бэнда.

Для настройки потокового режима используйте свойства приложения jmix.reports.streaming.fetch-size, jmix.reports.streaming.cursor-clear-interval и jmix.reports.streaming.row-access-window-size.

Наборы данных

Набор данных SQL

SQL набор данных формируется в результате выполнения SQL-запроса. Рекомендуется использовать псевдонимы для полей результата запроса с помощью оператора as. Также рекомендуется заключать псевдонимы в двойные кавычки, чтобы предотвратить возможное преобразование регистра СУБД:

select u.name as "userName", u.login as "userLogin"
from user_ u

В запросе можно использовать входные параметры отчета и поля родительских бэндов. К параметрам следует обращаться по имени, заключенному в ${}, например, ${dateFrom}. К полям родительских бэндов следует обращаться аналогично, добавляя имя бэнда перед именем поля: ${band1.field1}.

Ниже приведен пример SQL-запроса с параметром groupId, полученным из родительского бэнда group, и внешним параметром enabled:

select u.name as "userName", u.login as "userLogin"
from user_ u
where u.group_id = ${group.groupId}
    and u.active = ${active}
    and u.deleted_date is null

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

Выбор хранилища данных

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

Предварительная обработка запросов в бэндах

Если необходимо динамически модифицировать SQL/JPQL запрос в зависимости от входных параметров отчета или значений параметров из родительского бэнда, можно использовать предварительную обработку SQL. Механизм шаблонов позволяет модифицировать SQL/JPQL запросы с помощью Groovy. Для активации установите флажок Preprocess query as Groovy template под редактором бэнда. Итоговый запрос будет обработан GStringTemplateEngine, который получит доступ к:

  • параметрам отчета: ${<имя_параметра>},

  • значениям из родительских бэндов: ${<имя_бэнда>.<имя_параметра>}.

Рассмотрим пример: в зависимости от того, передан ли параметр отчета createTs2, нужно выбрать одно из условий запроса: e.create_ts < ${createTs2} или e.create_ts < current_timestamp.

В этом случае запрос должен выглядеть так:

select e.create_ts, e.id, e.vin from ref_car e
where
e.create_ts >= ${createTs1}
and
<% out << (createTs2 != null  ? 'e.create_ts < ${createTs2}' : 'e.create_ts < current_timestamp')%>

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

select e.create_ts, e.id, e.vin from ref_car e
where
e.create_ts >= ${createTs1}
and
e.create_ts < current_timestamp

Если createTs2 передан, для бэнда будет использован следующий итоговый запрос:

select e.create_ts, e.id, e.vin from ref_car e
where
e.create_ts >= ${createTs1}
and
e.create_ts < ${createTs2}

Набор данных JPQL

Набор данных JPQL формируется в результате выполнения JPQL-запроса. Поля результирующего запроса должны иметь псевдонимы, заданные с помощью оператора as. Аналогично SQL-запросу, в JPQL-запросе можно использовать входные параметры отчета и поля родительских бэндов.

Ниже приведен пример JPQL-запроса с параметром groupId, полученным из родительского бэнда group, и внешним параметром enabled:

select u.name as userName, u.login as userLogin
from User u
where u.group.id = ${group.groupId}
    and u.active = ${active}

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

При использовании JPQL-запроса в качестве источника данных для отчета обратите внимание, что запрос выполняется напрямую к базе данных и обходит DataManager. Это означает, что обычные проверки безопасности доступа к данным (на уровне строк и ресурсов) не применяются автоматически. Вы должны убедиться, что ваши JPQL-запросы извлекают только те данные, к которым пользователь имеет право доступа. Вы несете ответственность за реализацию необходимых проверок безопасности в ваших запросах.

Вы также можете использовать Link field для объединения нескольких наборов данных, использовать Data store для запросов к дополнительному хранилищу данных или активировать предварительную обработку запросов, установив флажок Preprocess query as Groovy template под редактором бэнда.

Набор данных AI-generated JPQL

Начиная с Jmix 3.1

Набор данных AI-generated JPQL формируется в результате выполнения JPQL-запроса. Однако сам запрос генерирует большая языковая модель (LLM) по вашему описанию на естественном языке. Запрос хранится вместе с набором данных. При запуске отчета сохраненный запрос выполняется через DataManager, а LLM не вызывается.

Требования

Запрос генерируется с помощью дополнения AI Tools. Когда оно установлено и его функциональность загрузки данных включена, на экране деталей отчета доступен тип набора данных AI-generated JPQL. Для кнопки Generate query также нужна настроенная модель. Без модели сохраненный запрос можно изменить только вручную.

Отчеты с такими наборами данных можно запускать и без дополнения, например после импорта.

Вместо дополнения AI Tools запросы можно генерировать с помощью собственного бина, реализующего интерфейс io.jmix.reports.llm.LlmDataQueryService.

Генерация запроса

Модель получает метаданные сущностей через инструменты Data Load дополнения AI Tools, поэтому видит только те сущности, которые дополнение делает доступными текущему пользователю. Дополнение проверяет сгенерированный запрос и при необходимости просит модель исправить его столько раз, сколько позволяет свойство jmix.aitools.dataload.max-repair-attempts.

Чтобы создать набор данных этого типа:

  1. Выберите AI-generated JPQL в поле Dataset type.

  2. В поле Prompt опишите данные, которые нужны бэнду, например: Заказы, оформленные начиная с :dateFrom, с именем клиента и суммой заказа, где dateFrom — параметр отчета. Имена параметров описаны в разделе Параметры.

  3. Нажмите Generate query. Максимальное время генерации задается свойством приложения jmix.reports.client.llm-query-generation-timeout.

  4. Проверьте запрос в поле Generated query и поля бэнда в списке Columns.

Список Columns содержит имена, которые используются для полей бэнда в шаблоне отчета, например ${customerName}. Столбцы сопоставляются со значениями предложения select по порядку.

Чтобы изменить запрос и столбцы вручную, нажмите кнопку Edit (значок карандаша) рядом с кнопкой Generate query. Если вы добавляете, удаляете или переставляете значения в предложении select, измените список Columns так же, иначе поля бэнда получат не те значения или останутся пустыми. Экран не проверяет это соответствие. Повторная генерация запроса заменяет ваши изменения.

Параметры

Запрос обращается к значениям через именованные параметры JPQL:

  • Входные параметры отчета доступны по своим псевдонимам, например :dateFrom.

  • Поля родительских бэндов доступны как :<band>_<field>, например :Orders_customerId для поля customerId бэнда Orders. Поля бэнда Root недоступны.

    Модель знает поля родительского бэнда, только если у него есть набор данных AI-generated JPQL с сохраненным запросом. Для других родительских наборов данных, например JPQL или SQL, укажите имена параметров в промпте.

Некоторые параметры требуют особой обработки:

  • Пустой необязательный параметр передается как null. Чтобы такое условие работало, запрос должен проверять параметр, например (:customer is null or o.customer = :customer). Модель получает указание добавлять такие проверки. При сохранении определения отчета экран предупреждает о необязательных параметрах, которые сравниваются без проверки, так как иначе бэнд окажется пустым без сообщения об ошибке.

  • Параметр типа List of entities сопоставляется с помощью in. Если такой параметр пустой, запуск отчета завершается ошибкой.

  • Синтаксис параметров без учета регистра :(?i)name не поддерживается, и запуск отчета завершается ошибкой. Используйте вместо него lower(…​) like :name.

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

В кросс-табличном бэнде набор данных AI-generated JPQL можно использовать для ячеек. Поля наборов данных заголовков столбцов (<band>_dynamic_header) и заголовков строк (<band>_master_data) передаются в запрос как параметры-списки с именами :<набор данных>_<поле>, например :Sales_dynamic_header_month для поля month набора данных Sales_dynamic_header. Чтобы механизм отчетов связал каждую строку результата с ячейкой, запрос должен вернуть столбец с таким же именем для заголовков столбцов и для заголовков строк, например Sales_dynamic_header_month.

Отличия от набора данных JPQL

Синтаксис запроса и поведение отличаются от набора данных JPQL:

Набор данных JPQL Набор данных AI-generated JPQL

Параметр отчета

${dateFrom}

:dateFrom

Поле родительского бэнда

${Orders.customerId}

:Orders_customerId

Имена полей

Псевдонимы после as, при необходимости в двойных кавычках. Перед выполнением псевдонимы удаляются из запроса.

Список Columns. Псевдонимы остаются в запросе, поэтому их нельзя заключать в двойные кавычки, а в качестве псевдонимов нельзя использовать зарезервированные слова JPQL, например count, date или order. Если вы заменяете набор данных JPQL набором данных AI-generated JPQL, переименуйте такие псевдонимы, например count в orderCount, и обновите имена полей в шаблоне.

Предварительная обработка запроса

Шаблон Groovy

Не поддерживается

Хранилище данных

Указывается в поле Data store. По умолчанию используется основное хранилище.

Определяется по запросу: хранилище сущности, к которой относится первое значение в select. Поля Data store нет.

Проверки доступа к данным

Не применяются, так как запрос обходит DataManager

Применяются, см. Проверки доступа к данным

Проверки доступа к данным

В отличие от набора данных JPQL, набор данных AI-generated JPQL применяет проверки доступа к данным текущего пользователя:

  • У пользователя должно быть разрешение на чтение каждой сущности, которую использует запрос, включая присоединенные сущности и подзапросы. Иначе запуск отчета завершается ошибкой доступа.

  • Атрибуты, которые пользователь не может читать, возвращаются как null.

  • JPQL-политики всех сущностей, которые читает запрос, добавляются в запрос.

Чтобы эти проверки нельзя было обойти, запуск отчета завершается ошибкой, если сохраненный запрос:

  • Не является запросом select или использует функции EclipseLink SQL, FUNCTION, FUNC, OPERATOR, COLUMN или TABLE.

  • Выбирает сущности целиком, например select o или select o.customer, а не их атрибуты.

  • Читает сущность, у которой есть предикатная политика для операции READ. Используйте вместо нее JPQL-политику.

  • Читает сущность с политикой уровня строк, которую нельзя применить к запросу: сущность используется только в подзапросе или под несколькими псевдонимами, либо ее политика содержит условие join и это не та сущность, из которой запрос выбирает данные.

Ограничения

  • Бэнд с набором данных AI-generated JPQL не может быть потоковым.

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

Набор данных Groovy

Набор данных Groovy формируется в результате выполнения Groovy-скрипта. Скрипт возвращает объект типа List<Map<String, Object>>. Каждый элемент этого списка - объект типа Map<String, Object> - соответствует одной записи набора данных.

В скрипт передаются следующие объекты:

  • params - карта внешних параметров отчета. Пример получения значения параметра:

    def active = params['active']
  • parentBand - родительский бэнд как объект типа io.jmix.reports.yarg.structure.BandData. Этот объект позволяет получить значение поля родительского бэнда путем вызова метода getParameterValue(), например:

    def groupId = parentBand.getParameterValue('groupId')
  • currentAuthentication - объект типа io.jmix.core.security.CurrentAuthentication, связанный с текущим аутентифицированным пользователем. Например:

    def user = currentAuthentication.getUser()
  • metadata - объект типа io.jmix.core.Metadata, предоставляющий доступ к метаданным приложения. Например:

    def metaClass = metadata.getClass('User')
  • dataManager - объект типа io.jmix.core.DataManager, предоставляющий CRUD-функциональность. Например:

    def book = dataManager.load(Book.class).id(bookId).fetchPlan("book.edit").one()
  • timeSource - объект типа io.jmix.core.TimeSource, используемый для получения текущего времени. Например:

    def currentDate = timeSource.currentTimestamp()
  • applicationContext - объект типа org.springframework.context.ApplicationContext, предоставляющий доступ к управляемым бинам. Например:

    def accountService = applicationContext.getBean(AccountService);

Набор данных Entity

Набор данных Entity состоит из одной строки и формируется с использованием атрибутов одного экземпляра JPA-сущности и связанных с ней сущностей.

Источник данных формируется из внешнего параметра типа Entity, который должен быть описан на вкладке Parameters. Значение в поле Entity parameter name должно совпадать с именем параметра.

Шаблон отчета должен содержать поля с именами атрибутов сущности. Имена атрибутов могут использовать имена из существующего фетч-плана (при установке флажка Use existing fetch plan) или могут быть выбраны кнопкой Select entity attributes.

Поскольку механизм отчетов всегда перезагружает сущности в наборе данных Entity из базы данных, вы не можете использовать его для DTO-сущностей или экземпляров JPA-сущностей, не сохраненных в базе данных. Вместо этого используйте Набор данных Groovy и обращайтесь к сущности, переданной как параметр, следующим образом:

def entity = params['entity']

return [['title': entity.title,
        'description': entity.description]]

Набор данных List of Entities

Набор данных List of entities формируется с использованием списка экземпляров JPA-сущностей.

Источник данных формируется с использованием внешнего параметра типа List of entities, который должен быть описан на вкладке Parameters. Значение в поле List of entities parameter name должно совпадать с псевдонимом параметра.

Шаблон отчета должен содержать поля с именами атрибутов сущности. Имена атрибутов могут использовать имена из существующего фетч-плана (при установке флажка Use existing fetch plan) или могут быть выбраны кнопкой Select entity attributes.

Поскольку механизм отчетов всегда перезагружает сущности в наборе данных List of entities из базы данных, вы не можете использовать его для DTO-сущностей или экземпляров JPA-сущностей, не сохраненных в базе данных. Вместо этого используйте Набор данных Groovy и обращайтесь к списку сущностей, переданному как параметр, следующим образом:

return params['entities'].collect {[
    'title': it.title,
    'description': it.description
]}

Набор данных JSON

Набор данных JSON генерируется из JSON-данных. Эти данные могут быть получены из следующих источников:

  1. Groovy-скрипт

    Предполагается, что скрипт, предоставленный пользователем, возвращает JSON-данные в виде строки.

    Например:

    return ''' 
            {
              "items": [
                {
                  "name": "Java Concurrency in practice",
                  "price": 15000
                },
                {
                  "name": "Clear code",
                  "price": 13000
                },
                {
                  "name": "Scala in action",
                  "price": 12000
                }
              ]
            }
            '''
  2. URL

    Механизм отчетов выполнит HTTP-запрос GET по указанному URL.

    Например:

    https://jsonplaceholder.typicode.com/users
  3. Parameter

    Внешний параметр отчета типа String, содержащий JSON-данные, должен быть описан на вкладке Parameters.

Полученное JSON-дерево запрашивается с помощью JsonPath-запроса. Например, можно использовать JsonPath $.store.book[*] для возврата всех книг из следующего JSON-дерева:

{
    "store": {
    "book": [
            {
                "category": "reference",
                "author": "Nigel Rees",
                "title": "Sayings of the Century",
                "price": 8.95
            },
            {
                "category": "fiction",
                "author": "Evelyn Waugh",
                "title": "Sword of Honour",
                "price": 12.99,
                "isbn": "0-553-21311-3"
            }
    ],
    "bicycle": {
        "color": "red",
        "price": 19.95
    }
}
}

Для получения более подробной информации о выражениях JsonPath смотрите документацию.

Поля, выводимые отчетом, которые имеют типы данных Date, DateTime или Time, не поддерживают формат, определенный java.text.SimpleDateFormat. Чтобы установить правильный формат, следует написать Groovy-скрипт.

Для этого перейдите на вкладку Value formats экрана деталей отчета и откройте диалог форматера. Например, для поля bookPublication.dateTime Groovy-скрипт будет выглядеть так:

import java.text.SimpleDateFormat

def simpleOldDateFormat = new SimpleDateFormat('yyyy-MM-dd HH:mm')
def simpleNewDateFormat = new SimpleDateFormat('dd/MM/yyyy HH:mm')
def oldDate = simpleOldDateFormat.parse(value)

return simpleNewDateFormat.format(oldDate)

Аннотация @DataSetDef

Аннотация @DataSetDef позволяет определять наборы данных при создании отчета во время разработки. Она имеет следующие атрибуты:

  • name - имя набора данных. Используется в наборах данных типа DELEGATE для связи определения набора данных с методом, возвращающим делегат.

    В кросс-табличном бэнде (@BandDef.orientation = Orientation.CROSS) наборы данных должны иметь следующие имена:

    • Заголовки столбцов: ${band_name}_dynamic_header

    • Заголовки строк: ${band_name}_master_data

    • Основное содержимое: ${band_name}

  • type - тип набора данных, определяемый перечислением DataSetType. Значения этого перечисления соответствуют типам наборов данных во время выполнения, описанным выше. Значение LLM (набор данных AI-generated JPQL) нельзя использовать во время разработки.

    Обычно в отчетах, создаваемых во время разработки, используются наборы данных типа DELEGATE. В этом случае они связываются по имени с методами, аннотированными аннотацией @DataSetDelegate.

Аннотация @DataSetDelegate

Аннотация @DataSetDelegate позволяет определить метод, который возвращает делегат для выполнения логики набора данных. Она имеет атрибут name, который должен быть равен имени соответствующего набора данных.

Аннотированный метод не должен иметь параметров и должен возвращать функциональный интерфейс ReportDataLoader.