Журнал технических изменений

Версия 6.11.2

Уведомление состояния записи

  • Изменился формат сообщения meta-driven, уведомления о вставке и удалении.

Версия 6.11

Автопрокрутка к новому правилу в наборе правил качества

platform/model-form/ModelCollectionPanel.tsx

Добавлен новый пропс isAutoScrollToNewItem: boolean для включения прокрутки.

Добавлена переменная cardRefs: ObservableMap<number, HTMLElement | null>.

В конструкторе добавлена MobX реакция на изменения в cardRefs для прокрутки к последнему добавленному элементу.

Преобразования в единицах измерения

  • Для эндпоинта /v2/meta/measurement появился новый параметр "reverseConversionFunction": "String" в measurementUnits.

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

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

Переименование "Модель данных" на "Структура данных"

Frontend:

Изменено название категории "Управление моделью данных" на "Управление структурой данных".

  • platform/app/assets/locale

Переименование ресурсов безопасности

  • Ресурс безопасности org.unidata.mdm.data.security.resource.model.management переименован на org.unidata.mdm.core.security.resource.model.management.

Скрытие объемных строк в JSON-сравнении

В окне JSON-сравнения (например, в сравнении ревизий модели данных) реализован механизм сворачивания ячеек, если в ней содержится более 200 символов.

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

API получения списка классификаторов

Добавлен новый необязательный параметр сортировки sort [] в API получения списка классификаторов (GET api/v1/classifiers/model/classifier).

Параметр имеет тип "Массив строк". Сортировка доступна по следующим полям классификатора: name, displayName, createDate.

Пример запроса с новым параметром:

api/v1/classifiers/model/classifier?sort[]=createDate:desc&sort[]=name:asc

Также добавлен необязательный параметр сортировки sort [] (GET api/v2/data/model/nested-entities).

Параметр имеет тип "Массив строк". Сортировка доступна по двум полям классификатора: name, displayName.

Пример запроса с новым параметром:

api/v2/data/model/nested-entities?draftId=0&sort[]=displayName:asc&sort[]=name:desc

Валидация при объединении записи

Добавлена валидация при объединении записи на предмет прохождения записью бизнес-процесса.

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

Аудит событий для черновиков

Backend:

  • Новые события в журнале аудита:

    • Создание черновика модели данных;

    • Редактирование черновика модели данных;

    • Публикация черновика модели данных;

    • Удаление черновика модели данных.

  • Удаленные неактивные события из журнала аудита:

    • Вставка черновика;

    • Применение черновика;

    • Удаление черновика.

  • Измененные пайплайны:

    • org.unidata.mdm.data[MODEL_DRAFT_UPSERT_START]

    • org.unidata.mdm.data[MODEL_DRAFT_PUBLISH_START]

  • Теперь последовательность сегментов следующая:

    • MODEL_DRAFT_UPSERT_START → MODEL_DRAFT_AUDIT_EVENT → MODEL_DRAFT_UPSERT_FINISH → MODEL_DRAFT_AUDIT_FALLBACK

    • MODEL_DRAFT_PUBLISH_START → MODEL_DRAFT_AUDIT_EVENT → MODEL_DRAFT_PUBLISH_FINISH → MODEL_DRAFT_AUDIT_FALLBACK

  • Новый пайплайн:

    • org.unidata.mdm.data[MODEL_DRAFT_REMOVE_START]

    • MODEL_DRAFT_REMOVE_START → MODEL_DRAFT_AUDIT_EVENT → MODEL_DRAFT_REMOVE_FINISH → MODEL_DRAFT_AUDIT_FALLBACK

Операция сбора статистики по данным

Перед обновлением на новую версию Юниверс MDM необходимо переключиться на версию контейнера базы данных, содержащую timescaledb extension, иначе сборка не соберется. Есть версия установки с Docker и без.

Сборка образа

Содержимое dockerfile
#Используйте базовый образ PostgreSQL версии 16.3
FROM postgres:16.3

#Устанавливайте необходимые пакеты
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
wget \
gnupg \
lsb-release \
software-properties-common \
ca-certificates

#Добавляйте ключ GPG и репозиторий TimescaleDB без использования apt-key
RUN wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | gpg --dearmor -o /usr/share/keyrings/timescaledb-archive-keyring.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/timescaledb-archive-keyring.gpg] https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -cs) main" > /etc/apt/sources.list.d/timescaledb.list

#Устанавливайте TimescaleDB
RUN apt-get update \
&& apt-get install -y --no-install-recommends timescaledb-2-oss-postgresql-16

#Конфигурируйте PostgreSQL для загрузки расширения TimescaleDB
RUN echo "shared_preload_libraries = 'timescaledb'" >> /usr/share/postgresql/postgresql.conf.sample

#Очищайте кэш apt, чтобы уменьшить размер образа
RUN apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

Сборка самого образа:

docker build --no-cache -t docker.universe-data.ru/mirror/postgres16.3-tsdb2.15.2.

Итоговый образ:

docker.universe-data.ru/mirror/postgres16.3-tsdb2.15.2

Docker version

Для этого запустите docker-compose с измененной конфигурацией контейнера postgres-mdm:

postgres-mdm:
image: docker.universe-data.ru/mirror/postgres16.3-tsdb2.15.2
restart: always
environment:
  POSTGRES_DB: ${MDM_POSTGRES_DB_NAME}
  POSTGRES_USER: postgres
  POSTGRES_PASSWORD: postgres
  MDM_POSTGRES_USER: ${MDM_POSTGRES_USER}
  MDM_POSTGRES_PASSWORD: ${MDM_POSTGRES_PASSWORD}
  TZ: ${TIMEZONE:-UTC}
ports:
  - ${POSTGRES_OUTER_PORT}:5432
networks:
  - mdm_network
volumes:
  - ./init-db.sh:/docker-entrypoint-initdb.d/initdb.sh
  - mdm-postgres-data:/var/lib/postgresql/data
command: postgres -c max_prepared_transactions=300 -c shared_preload_libraries='timescaledb'
healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres -d $MDM_POSTGRES_DB_NAME"]
  interval: 10s
  timeout: 1s
  retries: 20

No docker version

$ apt-get update \
&& apt-get install -y --no-install-recommends \
wget \
gnupg \
lsb-release \
software-properties-common \
ca-certificates

$ wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | gpg --dearmor -o /usr/share/keyrings/timescaledb-archive-keyring.gpg \
&& echo "deb [signed-by=/usr/share/keyrings/timescaledb-archive-keyring.gpg] https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -cs) main" > /etc/apt/sources.list.d/timescaledb.list

$ apt-get update \
&& apt-get install -y --no-install-recommends timescaledb-2-oss-postgresql-16


$ echo "shared_preload_libraries = 'timescaledb'" >> /usr/share/postgresql/postgresql.conf.sample

После перезагрузите бд и выполните:

psql -U postgres -d postgres -c "CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;"

Примечания:

  • timescaledb-2-oss-postgresql-16 - версия для image: postgres:16.

  • Если версия image postgres отличается, необходимо установить соответствующую версию timescaledb.

  • Путь к postgresql.conf тоже может отличаться.

Отправка записи в системы-потребители

Новые REST:

GET /v2/data/records-send-notifications/domains - получает список доступных систем-потребителей с доступными типами.

{
 "domains": [
   {
     "id": "string",
     "displayName": "string",
     "description": "string",
     "types": [
       {
         "id": "string",
         "displayName": "string",
         "description": "string",
         "headers": [
           {
             "name": "string",
             "displayName": "string",
             "description": "string",
             "type": "string"
           }
         ]
       }
     ]
   }
 ]
}

POST /v2/data/records-send-notifications/send - отправляет сообщение в выбранный домен.

{
  "etalonId": "string",
  "externalId": "string",
  "domains": [
     {
        "id": "string",
        "types": [
        "string"
        ]
     }
  ]
}

Новый ресурс "Ручная отправка в системы-потребители" на вкладке Менеджмент записей - предоставляет права на:

  • GET /v2/data/records-send-notifications/domains

  • POST /v2/data/records-send-notifications/send

Новая система-потребитель:

Очередь доступна по адресу http://server_url:8161/admin/.

Прекращение сборки редакции SE

Редакции SE и EE определяются лицензией на продукт.

Frontend:

  • Все модули @universe-se были перемещены в каталог ЕЕ и переименованы в @universe-ee.

  • Соединены модули @universe-se/workflow и @universe-ee/workflow.

  • Файлы из @universe-ee/data перенесены в @universe-ee/data-classifier.

Backend:

  • Поле лицензии «Ограничить кластер операции "Переиндексация данных"» перенесено из редакции SE в EE (значение по умолчанию – "нет"), ранее это поле было только в SE редакции.

Возврат к исторической версии модели реестра/справочника

Множественная вставка

Создан новый API для вставки множества реестров, справочников, вложенных объектов.

POST:

/v2/data/model/upsert

В теле запроса передается список реестров, список справочников и список вложенных объектов для вставки.

{
"registerEntities": [/*Объекты вставляемых/обновляемых реестров*/],
"lookupEntities": [/*Объекты вставляемых/обновляемых справочников*/],
"nestedEntities": [/*Объекты вставляемых/обновляемых комплексных атрибутов*/]
}

Откат к указанной ревизии

Создан новый API для отката указанных реестров, справочников, связей, вложенных объектов к указанной ревизии.

POST:

/v2/data/model/rollback

Данный API откатывает указанные реестры, справочники, вложенные объекты к указанной ревизии. При необходимости восстанавливает состояние групп для указанных сущностей.

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

{
 "revision": 242, /*Номер ревизии*/
 "registerEntitiesNames": ["versionRegister"], /*Список имен откатываемых реестров*/
 "lookupEntitiesNames": ["versLookup"], /*Список имен откатываемых справочников*/
 "nestedEntitiesNames": ["versComl"], /*Список имен откатываемых вложенных объектов*/
 "rollbackGroups": true /*Нужно ли откатывать группы для указанных сущностей*/
}

Если rollbackGroups == true, то будет восстановлена полная иерархия до указанной сущности. Все группы, в которые была вложена сущность будут восстановлены и переименованы в то состояние в котором они были в ревизии к которой совершается откат. Так же если в данной ревизии были удалены группы и в последней ревизии они не содержат элементов - такие группы удаляются (удаление пустых групп).

Сортировка значений параметров операций

  • В platformParametersForm.tsx и platform/ParameterItem.tsx добавлен новый props для передачи функции сортировки: props.onSortOptions?: (parameters: IParameterDescriptor) => IOption[].

  • В module/JobForm.tsx реализована функция сортировки с сохранением элемента с ключом ALL на первом месте. Создана константа DEFAULT_VALUE='ALL'.

Отображение системного имени функции

  • В paltform/uikit в CardPanel.tsx добавлен новый props headerAutoHeight?: boolean для выставления высоты хедера = auto.

Разлогинивание по бездействию юзера (ФСТЭК)

  • Обратная совместимость не нарушена.

  • Для активации функции необходимо изменить параметра системы. Подробнее см Параметры системы.

Упорядоченность списка операций по алфавиту

В модуль module/job/PropertiesForm.tsx добавлена сортировка списка типов операций в метод formattedJobName.

Дополнительные кнопки в шапке карточке записи

  • Добавлена новая точка расширения DataCardButton для отображения кнопок в шапке карточки записи.

Подсветка автоматически обогащенных атрибутов

  • Изменены возвращаемые типы для метода enrich в AbstractRecordEntityStore.ts и IInnerRecordCardStore.ts с undefined на Set<string> | undefined. Реализация с undefined помечена как deprecated. Метод enrich должен возвращать сеты dataPath, которые были обогащены.

  • Во всех input-компонентах добавлен параметр isHighlighted, подсвечивающий поле ввода.

  • Добавлена новая точка расширения AttributeViewProperties, задающая параметры отображения полей ввода значений атрибутов. Данная точка используется в DataAttributeValueFactory.tsx и передается в компоненты атрибутов посредством контекста DataAttributeViewContext.

Удалены устаревшие API

Из сборки исключены следующие API-модули:

  • UserRestService#read;

  • UserRestService#readInfo;

  • UserRestService#readAll(GetUsersRequestRO);

  • UserRestService#readAll(GetUsersByLoginsRequestRO.

А также соответствующие им endpoint-ы (были помечены как deprecated в релизе 6.9):

  • /v2/core/security/user/{login}

  • /v2/core/security/user/logins

  • /v2/core/security/user/query

  • /v2/core/security/user/user-info/{login}

Пул соединений и менеджер транзакций в операциях

  • Вместо PlatformTransactionManager в операциях теперь используется ResourcelessTransactionManager, не запускающий реальных транзакций для пула соединений операций.

  • org.apache.tomcat.jdbc.pool.DataSource заменен на HikariCP последней версии, что дает также возможность делать сборки системы на других сервлет-контейнерах, а не только на Tomcat.

В текущей реализации поддерживаются следующие конфигурационные свойства пула соединений операций (org.unidata.mdm.core.job.datasource.*):

"minPoolSize",        дефолтное значение "10"
"maxPoolSize",        дефолтное значение "30"
"maxIdleTime",        дефолтное значение "600000"   # миллисекунды
"maxLifeTime",        дефолтное значение "1800000"  # миллисекунды
"validationInterval", дефолтное значение "0"        # не использовать
"suspectTimeout",     дефолтное значение "0"        # не использовать
"isolationLevel",     дефолтное значение null       # READ_COMMITED
"uniqueName"                                        # будет использоваться как имя пула в JMX консоли

Добавлены параметры для внешней конфигурации:

org.unidata.mdm.core.job.datasource.minPoolSize=${CORE_JOB_DATASOURCE_MINPOOLSIZE:3} org.unidata.mdm.core.job.datasource.maxPoolSize=${CORE_JOB_DATASOURCE_MAXPOOLSIZE:10} org.unidata.mdm.core.job.datasource.maxIdleTime=${CORE_JOB_DATASOURCE_MAXIDLETIME:600000} org.unidata.mdm.core.job.datasource.maxLifeTime=${CORE_JOB_DATASOURCE_MAXLIFETIME:1800000} org.unidata.mdm.core.job.datasource.validationInterval=${CORE_JOB_DATASOURCE_VALIDATIONINTERVAL:0} org.unidata.mdm.core.job.datasource.suspectTimeout=${CORE_JOB_DATASOURCE_SUSPECTTIMEOUT:0} org.unidata.mdm.core.job.datasource.uniqueName=Unidata-Core-Jobs

Улучшение отображения ошибок правил качества

  • Изменены стили в:

    • platform/record/errorsBadge.module.scss

    • platform/record/errorsRow.module.scss

    • platform/uikit/icon.m.scss

  • В module/dq-base/PortConstantField.tsx для констант <Input> заменен на <Input.TextArea>.

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

  • В module-ee/workflow/Reassign.tsx добавлен элемент <Input>, а также 2 метода onSearch и get filteredAcceptedUserList.

Сортировка типов операций

  • В module/job/PropertiesForm.tsx: в метод formattedJobName добавлена сортировка перечня типов операций.

Цветовая индикация связей на графе

RelationGraphStore.ts:

  • Добавлены новые переменные для доп. параметров. colorIncomingProperty: string = 'clustergraph_incoming_color' и colorIncomingProperty: string = 'clustergraph_color'.

  • Добавлена проверка направления связи в метод getLinkDisplayConfig. Заданы цвета: исходящая связь - оранжевая, входящая - голубая.

  • Добавлен метод graphDisplayConfig, где задается вид графа displayConfig.layout = GraphLayouts.Tree.

GraphD3:

  • В displayConfig добавлен новый параметр isLinkStraight.

  • В метод linkArc добавлена проверка isLinkStraight. Рекурсивные связи теперь отображаются как: исходящая связь - прямая линия, входящая - изогнутая.

Отображение информации об исполнителе в задачах и процессах

  • Добавлен текст локализации "Исполнитель" / "Assignee".

  • В окне просмотра задачи ProcessCard.tsx и в окне просмотра подробностей процесса TaskOverview.tsx добавлен элемент <UserView/> для отображения имени пользователя и всплывающая подсказка с подробной информацией о пользователе.

Раскрытие токена авторизации

  • GET-запросы, в которых имело место раскрытие токена в параметрах, заменены на POST-запросы с передачей токена в теле запроса.

  • Измененные POST-запросы теперь принимают данные только в формате application/x-www-form-urlencoded.

  • Изменена логика проверки токена авторизации для поддержки передачи токена в теле запроса.

  • Убрана возможность передачи токена авторизации в параметрах запроса.

Новая логика обработки токена авторизации:

Изменена логика обработки токена в методе SystemSecurityDataSourceComponent#handleRequest. Сначала заголовки запроса проверяются на наличие в запросе на наличие в запросе типа данных application/x-www-form-urlencoded.

  • При наличии в запросе типа данных application/x-www-form-urlencoded производится попытка извлечь токен авторизации из строки тела запроса. Если токен не найден, то токен ищется в Authorization header запроса.

  • При отсутствии в запросе типа данных application/x-www-form-urlencoded логика осталась прежней, токен ищется в Authorization header запроса.

Измененные методы:

  • LargeObjectRestService#fetchBlob

  • LargeObjectRestService#fetchClob

  • DataModelRestService#exportDataModel

  • QualityModelRestService#dump

  • MatchingModelRestService#dump

  • EnumerationRestService#export

  • MeasurementUnitsRestService#export

  • SourceSystemRestService#exportSourceSystems

  • ClassifiersModelRestService#dump

  • WorkflowModelRestService#dump

  • UtilityRestService#logs

В перечисленных методах теперь указан единственный разрешенный формат принимаемых данных: application/x-www-form-urlencoded.

Также в указанных методах изменен тип с GET на POST.

Все параметры, которые передавались как параметры запроса, теперь передаются как параметры формы (тела запроса).

Пример измененного запроса:

  • Было: GET /v1/classifiers/model/export/book_classifier?token=f3b61f0b-5ec4-4a9e-b61f-0b5ec43a9ea1

  • Стало: POST /v1/classifiers/model/export/book_classifier Content-Type: application/x-www-form-urlencoded token=f3b61f0b-5ec4-4a9e-b61f-0b5ec43a9ea1

Соответствующие методам endpoint-ы:

  • /v2/core/lob/blob/{id}

  • /v2/core/lob/clob/{id}

  • /v2/data/model/export

  • /v2/data-quality/model/export

  • /v2/matching/model/export

  • /v2/meta/enumeratuions/export

  • /v2/meta/measurement/export

  • /v2/meta/source-system/export

  • /v1/classifiers/model/export/{classifierName}

  • /v2/workflow/model/export

  • /v2/core/utility/logs

Во всех этих эндпоинтах токен теперь ожидается в теле POST-запроса вместо GET.

Запуск бизнес-процессов при удалении записи

Backend:

  • В WorkflowRecordDraftTriggers добавлены триггеры DELETE_RECORD, DELETE_PERIOD;

  • В OpenSearch добавлен индекс $workflow_trigger;

  • В WorkflowDataStorageIds добавлена константа SID_PROCESS_TRIGGER, при помощи которой в SystemStorage храниться триггер БП;

  • В пайплайн [RECORD_DRAFT_PUBLISH_WORKFLOW_SELECTOR] добавлен новый point [RECORD_DRAFT_PUBLISH_BEFORE_START_WORKFLOW], в котором осуществляется проверка существования процесса на удаление записи. Проверка срабатывает при попытке опубликовать черновик на удаление записи. Если процесс уже существует, появляется ошибка PlatformValidationException, которая прерывает выполнение публикации черновика;

  • В WorkflowValidationComponent добавлена проверка на существование незавершенного БП, привязанного к данному черновику. В случае нахождения такого БП отобразится ошибка WorkflowRuntimeException.

  • Добавлен новый API, возвращающий количество БП данной записи, сгруппированных по типу триггера БП. WorkflowProcessInstanceRestService#getProcessCount endpoint: v2/workflow/process/count-by-trigger.

Frontend:

  • mdm/DataCardStore.ts:

    • В методе handleDelete добавлено создание нового черновика.

    • В методе deleteData:

      • В deleteRecordPayload был добавлен draftId;

      • Добавлена публикация черновика для запуска бизнес-процесса. Если публикация не удалась, то отобразится ошибка валидации и удалится созданный черновик, т.к. невалидные черновики не хранятся.

  • platform/networkResponseException.ts:

    • Добавлен метод getErrorWithDomain для поиска поиска ошибку по необходимому domain

  • platform/record/AbstractCardStore.ts

    • Добавлен необязательный параметр draftId в следующие методы deleteData и handleDelete.

Изменение логики отключения route-ов

  • RouterStore:

    • routerStore.disableRoute и routerStore.undisableRoute помечены как deprecated и будут удалены в universe-platform/router v2.0.0. Вместо устаревших методов добавлены routerStore.addRouteDisableCondition и routerStore.removeRouteDisableCondition.

    • routerStore.addRouteDisableCondition принимает имя роута и функцию, результат выполнения которой должен вернуть boolean значение, означающее отключен роут или нет.

    • После добавления условий отключения через routerStore.addRouteDisableCondition необходимо выполнить routerStore.reloadRoutes для перерасчета активных роутов.

  • Unidata.ts:

    • Метод getDisabledRoutes помечен как deprecated и будет удален в universe-platform/app v2.0.0. Вместо устаревшего метода добавлен manageDisabledRoutes, внутри которого необходимо использовать методы routerStore.addRouteDisableCondition и routerStore.removeRouteDisableCondition.

    • Дополнительно вызывать метод routerStore.reloadRoutes не требуется. Он будет вызван после manageDisabledRoutes.

Проверка прав на rout-ы

В RouterStore реализован метод hasRouteAccess:

public hasRouteAccess<T extends UniverseRouter.RouteKeys> (name: T, params: RouteDescriptorParams<T>)

Метод использует функцию routeAccessor из IRouteMeta и возвращает boolean значение доступности роута текущему пользователю. Если у роута не задан routeAccessor - функция вернет true.

routeAccessor принимает те же параметры, что и routeFunc:

RouteAccessor extends ((...args: Parameters<RouteFunc>) => boolean) = ((...args: Parameters<RouteFunc>) => boolean)

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

Копирование данных из окна о системе

  • universe-platform/security

  • Добавлен экспорт из пакета типа BUILD_VERSION.

  • universe-platform/app

    • В зависимости добавлена библиотека copy-to-clipboard.

    • В модальное окно с информацией о системе добавлена кнопка "Скопировать текст".

Исправление срабатываний анализатора Svacer

Классы AbstractBulkOperationContext, RoleDeleteContext, RoleGetContext, RoleUpsertContext, UserGetContext, UserUpsertContext, AbstractImportContext, AbstractModelChangeContext, AbstractModelGetContext, AbstractModelRefreshContext, AbstractModelRemoveContext, AbstractModelSourceContext, AuthenticationRequestContext, DataImportInputContext, DataImportTemplateContext, DeleteLargeObjectContext, FetchLargeObjectContext, RefreshModelContext, UpsertLargeObjectContext, UpsertUserEventRequestContext, PasswordPO, RolePO, SecurityLabelAttributePO, SecurityLabelPO, UserPO, GetDataModelContext, SourceDataModelContext, UpsertDataModelContext, AbstractRecordIdentityContext, AbstractRelationIdentityContext, AbstractRelationsFromRequestContext, AbstractRelationsFromToRequestContext, CloneFromRelationRequestContext, CloneRecordContext, CloneRelationsContext, CloneToRelationRequestContext, DeleteRelationRequestContext, DeleteRelationsRequestContext, DeleteRequestContext, GetMultipleRequestContext, GetRecordTimelineRequestContext, GetRecordsTimelinesRequestContext, GetRelationRequestContext, GetRelationTimelineRequestContext, GetRelationsDigestRequestContext, GetRelationsRequestContext, GetRelationsTimelineRequestContext, GetRequestContext, JoinRequestContext, MergeFromRelationRequestContext, MergeRelationsRequestContext, MergeRequestContext, MergeToRelationRequestContext, PreviewRequestContext, RecordHistoryContext, RelationFromPreviewContext, RestoreFromRelationRequestContext, RestoreRecordRequestContext, RestoreRelationsRequestContext, RestoreToRelationRequestContext, SplitRecordRequestContext, UnmergeFromRelationContext, UnmergeRecordContext, UnmergeToRelationContext, UpsertRelationRequestContext, UpsertRelationsRequestContext, UpsertRequestContext, DataQualityContext, GetQualityModelContext, SourceQualityModelContext, UpsertQualityModelContext, RecordQualityContext, StateCheckContext, AbstractDraftDataContext, AbstractDraftFieldsContext, DraftGetContext, DraftPublishContext, DraftQueryContext, DraftRemoveContext, DraftUpsertContext, MatchingModelGetContext, MatchingModelUpsertContext, DataMatchingContext, RecordMatchingCloneContext, RecordMatchingDeleteContext, RecordMatchingMergeContext, RecordMatchingRestoreContext, RecordMatchingUnmergeContext, RecordMatchingUpsertContext, ApplyUploadedModelRequestContext, ExportModelRequestContext, GetEnumerationsContext, GetMeasurementUnitsContext, GetSourceSystemsContext, SourceEnumerationsContext, SourceMeasurementUnitsContext, SourceSourceSystemsContext, UpsertEnumerationsContext, UpsertMeasurementUnitsContext, UpsertSourceSystemsContext, AbstractCompositeRequestContext, CommonRequestContext, RenderingContext, TimelogDeleteContext, TimelogQueryContext, GetTransformModelContext, SourceTransformModelContext, UpsertTransformModelContext, TransformContext, AbstractCommentContext, AbstractScoreContext, CommentDeleteContext, CommentQueryContext, CommentUpsertContext, ScoreDeleteContext, ScoreQueryContext, ScoreUpsertContext, TagDeleteContext, TagQueryContext, TagSearchContext, TagUpsertContext, ExportRecordsXLSXBulkOperationContext, RemoveRecordsBulkOperationContext, AbstractClassificationIdentityContext, AbstractClassificationsContext, AbstractClassifierModelChangeContext, AbstractClassifierModelGetContext, AbstractClassifierModelRemoveContext, ClassificationCloneContext, ClassificationDeleteContext, ClassificationGetContext, ClassificationMergeContext, ClassificationPreviewContext, ClassificationRestoreContext, ClassificationTimelineQueryContext, ClassificationUnmergeContext, ClassificationUpsertContext, ClassificationsCloneContext, ClassificationsDeleteContext, ClassificationsGetContext, ClassificationsMergeContext, ClassificationsPreviewContext, ClassificationsRestoreContext, ClassificationsTimelineQueryContext, ClassificationsUpsertContext, GetClassifierModelContext, GetNodeModelContext, GetVersionModelContext, RemoveClassifierModelContext, RemoveNodeModelContext, RemoveVersionModelContext, SourceDataModelContext, UpsertClassifierModelContext, UpsertNodeModelContext, UpsertVersionModelContext, DataStatusModelGetContext, DataStatusModelSourceContext, DataStatusModelUpsertContext, WorkflowModelGetContext, WorkflowModelUpsertContext и ModifyRecordsBulkOperationContext больше не являются Serializable.

Переименование колонок таблиц сопоставления

Переименованы системные колонки в таблицах сопоставления на _$x, _$id, _$namespace, _$type_name, _$subject_id, _$valid_from, _$valid_to для исключения конфликтов с пользовательскими колонками. Добавились новые индексы и изменились настройки нечеткого алгоритма, поэтому таблицы сопоставления необходимо пересоздать.

Чтение данных из AD

Поскольку имя и фамилия в режиме sgi читались из неверных атрибутов, исправлено (sn - фамилия, givenName - имя).

Режим sgi заменен на gsi: название - это первые буквы атрибутов-источников имени; режим displayName оставлен без изменений.

Добавлен режим gsDisplayMix - имя и фамилия берутся из атрибутов, отчество вычисляется из атрибута displayName.

При режиме gsDisplayMix если displayName не содержит в себе корректных атрибутов, то отчество будет незаполненным, имя и фамилия будут взяты из атрибутов givenName и sn.

Валидация атрибутов с поисковыми параметрами

Добавлена валидация атрибутов с параметрами поиска, которые не являются поисковыми.

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

Из-за большого количества таких ошибок может не быть возможности сохранить модель через интерфейс. В таком случае необходимо:

  1. Экспортировать модель данных.

  2. Открыть файл модели .xml в любом редакторе.

  3. Найти элемент в котором произошла ошибка (<register>, <lookup>, <nested>) по имени (атрибут name).

  4. Найти атрибут в котором произошла ошибка (<simpleAttribute>, <arrayAttribute>) по имени (атрибут name).

    • Далее либо сделать атрибут поисковым (searchable="true");

    • Либо убрать у него параметры поиска (searchMorphologically="false" searchCaseInsensitive="false" searchWithTransliteration="false" searchWithSynonyms="false").

  5. После изменений необходимо произвести реиндекс модели с обновлением маппингов.

Для классификаторов шаги аналогичны - отличается только структура .xml и шаг 3: необходимо найти узел (<node>) в котором произошла ошибка по имени (атрибут name).

Валидация отображаемых атрибутов связей

Добавлена валидация отображаемых атрибутов связей на backend; frontend не отображает в списке невалидные атрибуты.

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

Из-за большого количества таких ошибок может не быть возможности сохранить модель через интерфейс. В таком случае необходимо:

  1. Экспортировать модель данных и открыть файл модели .xml в любом редакторе.

  2. Найти связи с ошибками, стереть в них строки <toEntityDefaultDisplayAttributes>*имя атрибута*</toEntityDefaultDisplayAttributes>.

  3. Сохранить файл и импортировать модель.

Публикация черновика модели данных в последнюю ревизию

  • В DataModelValidationComponent добавлена валидация merge черновика и последней ревизии модели данных: DataModelValidationComponent#mergeValidation. Эта валидация используется в DataModelComponent#upsert.

  • В DataModelComponent убрана ошибка, бросаемая, если версия модели данных черновика не равна следующей ревизии.

  • В методе DataModelComponent#merge распаковка модели данных вынесена в методы класса DataModelUtils.

  • В методе DataModelComponent#merge при merge EntitiesGroup удаление несуществующих сущностей из дерева производится всегда, а не только при обновлении состояния дерева.

  • В DataModelDiffProvider добавлены статические методы для получения значений входных параметров метода DataModelDiffProvider#compare. Метод DataModelDiffProvider#revision возвращает интерпретацию ревизии.

  • В ModelDraftPublishFinishExecutor#publish в зависимости от флага isForce создаются разные контексты вставки модели данных.

    • Если при публикации черновика isForce == false, то вызывается diffService.compare, который сравнивает состояние модели данных черновика и последней ревизии. На основе этих результатов формируется `UpsertDataModelContext`, содержащий только измененные сущности. Если при публикации черновика isForce == false, то UpsertDataModelContext имеет upsertType == ModelChangeType.MERGE.

    • Если при публикации черновика isForce == true`, то контекст заполняется текущим состоянием черновика. Если при публикации черновика isForce == true, то UpsertDataModelContext имеет upsertType == ModelChangeType.FULL.

  • Для удобства работы с результатом DiffService#compare создан вспомогательный класс DiffElements.

  • Добавлено версионирование групп сущностей. Новая версия присваивается при создании, переименовании и перемещении.

  • Создана обертка над узлом дерева групп MergedGroupedEntity. Данная обертка помимо узла хранит имя родителя, расстояния до узлов находящихся левее/правее вливаемого узла и ссылки на обертки детей.

  • В UpsertDataModelContext добавлено поле entitiesGroupsMerge. В данном поле хранится список поддеревьев из оберток MergedGroupedEntity. В DataModelComponent данное поддерево будет использоваться для merge групп сущностей в текущую модель данных.

  • Создан метод слияния деревьев сущностей EntitiesGroup#merge(EntitiesGroup root, List<MergedGroupedEntity> mergedEntities).

Поиск процессов и задач по полям статусов

Теперь у процессов и статусов задач на согласование есть поля статусов $process_status/$task_status и $linked_subject_display_value, по которым можно производить поиск и сортировку.

Значения полей не сохраняются в БД, а вычисляются перед индексацией. $task_status вычисляется по $completed и наличию $assignee, $process_status вычисляется по $finished и наличию активных/завершенных задач-потомков, $linked_subject_display_value — по $linked_type_name и $linked_subject_id.

$process_status/$task_status хранятся в числовом виде для упрощения сортировки, в ответе на поисковый запрос REST API числовые значения дополняются человеко-читаемым displayValue.

Примечание

Поскольку маппинг поля $linked_subject_display_value у задач/процессов бизнес-процессов был изменен - необходима переиндексация бизнес-процессов с ремаппингом.

Пример ответа с $process_status
{
   "id": "Process_36181b30-edbf-11ee-9271-031d8add40d5-41864",
   "score": 1.0,
   "status": null,
   "preview": [
      {
            "field": "$initiator_full_name",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [
               "Root2 Admin2"
            ],
            "extendedValues": [
               {
                  "value": "Root2 Admin2",
                  "displayValue": null,
                  "linkedEtalonId": null,
                  "linkedTypeName": null
               }
            ],
            "complexValues": null
      },
      {
            "field": "$definition_id",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [
               "Process_36181b30-edbf-11ee-9271-031d8add40d5:1:41707"
            ],
            "extendedValues": [
               {
                  "value": "Process_36181b30-edbf-11ee-9271-031d8add40d5:1:41707",
                  "displayValue": null,
                  "linkedEtalonId": null,
                  "linkedTypeName": null
               }
            ],
            "complexValues": null
      },
      {
            "field": "$definition_display_name",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [
               "test"
            ],
            "extendedValues": [
               {
                  "value": "test",
                  "displayValue": null,
                  "linkedEtalonId": null,
                  "linkedTypeName": null
               }
            ],
            "complexValues": null
      },
      {
            "field": "$end_event_name",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [],
            "extendedValues": null,
            "complexValues": null
      },
      {
            "field": "$finished",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [
               false
            ],
            "extendedValues": [
               {
                  "value": false,
                  "displayValue": null,
                  "linkedEtalonId": null,
                  "linkedTypeName": null
               }
            ],
            "complexValues": null
      },
      {
            "field": "$linked_subject_id",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [
               "676773a5-edc4-11ee-a295-a91ddb030bdd"
            ],
            "extendedValues": [
               {
                  "value": "676773a5-edc4-11ee-a295-a91ddb030bdd",
                  "displayValue": null,
                  "linkedEtalonId": null,
                  "linkedTypeName": null
               }
            ],
            "complexValues": null
      },
      {
            "field": "$start_date",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [
               "2024-03-29T12:03:54.631Z"
            ],
            "extendedValues": [
               {
                  "value": "2024-03-29T12:03:54.631Z",
                  "displayValue": null,
                  "linkedEtalonId": null,
                  "linkedTypeName": null
               }
            ],
            "complexValues": null
      },
      {
            "field": "$linked_type_name",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [
               "register:UN-25529-test"
            ],
            "extendedValues": [
               {
                  "value": "register:UN-25529-test",
                  "displayValue": null,
                  "linkedEtalonId": null,
                  "linkedTypeName": null
               }
            ],
            "complexValues": null
      },
      {
            "field": "$process_status",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [
               "В РАБОТЕ"
            ],
            "extendedValues": [
               {
                  "value": 1,
                  "displayValue": "В РАБОТЕ",
                  "linkedEtalonId": null,
                  "linkedTypeName": null
               }
            ],
            "complexValues": null
      },
      {
            "field": "$definition_key",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [
               "Process_36181b30-edbf-11ee-9271-031d8add40d5"
            ],
            "extendedValues": [
               {
                  "value": "Process_36181b30-edbf-11ee-9271-031d8add40d5",
                  "displayValue": null,
                  "linkedEtalonId": null,
                  "linkedTypeName": null
               }
            ],
            "complexValues": null
      },
      {
            "field": "$initiator",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [
               "admin"
            ],
            "extendedValues": [
               {
                  "value": "admin",
                  "displayValue": null,
                  "linkedEtalonId": null,
                  "linkedTypeName": null
               }
            ],
            "complexValues": null
      },
      {
            "field": "$end_event_id",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [],
            "extendedValues": null,
            "complexValues": null
      },
      {
            "field": "$business_key",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [
               "register:UN-25529-test:draftId:751"
            ],
            "extendedValues": [
               {
                  "value": "register:UN-25529-test:draftId:751",
                  "displayValue": null,
                  "linkedEtalonId": null,
                  "linkedTypeName": null
               }
            ],
            "complexValues": null
      },
      {
            "field": "$finish_date",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [],
            "extendedValues": null,
            "complexValues": null
      },
      {
            "field": "$initiator_email",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [
               "mail@example.com"
            ],
            "extendedValues": [
               {
                  "value": "mail@example.com",
                  "displayValue": null,
                  "linkedEtalonId": null,
                  "linkedTypeName": null
               }
            ],
            "complexValues": null
      },
      {
            "field": "$id",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [
               "41864"
            ],
            "extendedValues": [
               {
                  "value": "41864",
                  "displayValue": null,
                  "linkedEtalonId": null,
                  "linkedTypeName": null
               }
            ],
            "complexValues": null
      },
      {
            "field": "$etalon_id",
            "fieldDisplayName": null,
            "fieldValueType": null,
            "values": [],
            "extendedValues": null,
            "complexValues": null
      }
   ],
   "source": null
}

Поиск процессов по полям связанных задач

Добавлена возможность искать процессы по полям связанных задач на согласование (supplementary поисковые запросы).

Пример запроса, возвращающего все процессы
{
"payload": {
   "com.unidata.mdm.rest.v2.workflow.core": {
      "countOnly": false,
      "formFields": [],
      "formGroups": [],
      "searchFields": [
      "$id",
      "$definition_display_name",
      "$finished"
      ],
      "returnFields": [
      "$business_key",
      "$id",
      "$definition_id",
      "$definition_key",
      "$definition_display_name",
      "$start_date",
      "$finish_date",
      "$initiator",
      "$initiator_full_name",
      "$initiator_email",
      "$finished",
      "$end_event_id",
      "$end_event_name",
      "$linked_type_name",
      "$linked_subject_id"
      ],
      "fetchAll": true,
      "entity": "",
      "typedVariablesSearch": false,
      "searchWorkflowType": "PROCESS",
      "totalCount": true,
      "page": 1,
      "count": 20,
      "start": 0,
      "sortFields": []
   },
   "org.unidata.mdm.rest.v2.dq.data": {
      "setNames": [],
      "ruleNames": [],
      "functionNames": [],
      "message": null,
      "severity": null,
      "category": null,
      "totalScore": 0,
      "matchAll": false,
      "negate": false
   },
   "com.unidata.mdm.rest.v1.classifiers.multiple": null
}
}
Пример запроса, возвращающего задачи с исполнителем "admin"
{
"payload": {
   "org.unidata.mdm.rest.v2.dq.data": {
      "setNames": [],
      "ruleNames": [],
      "functionNames": [],
      "message": null,
      "severity": null,
      "category": null,
      "totalScore": 0,
      "matchAll": false,
      "negate": false
   },
   "com.unidata.mdm.rest.v1.classifiers.multiple": null,
   "com.unidata.mdm.rest.v2.workflow.core": {
      "countOnly": false,
      "formFields": [
      {
         "name": "$assignee",
         "type": "STRING",
         "searchType": "EXACT",
         "inverted": false,
         "value": "admin"
      }
      ],
      "formGroups": [],
      "searchFields": [
      "$definition_display_name",
      "$id",
      "$assignee",
      "$completed",
      "$process_definition_display_name"
      ],
      "returnFields": [
      "$id",
      "$definition_key",
      "$definition_display_name",
      "$create_date",
      "$due_date",
      "$complete_date",
      "$completed",
      "$assignee",
      "$assignee_full_name",
      "$candidate_groups.$name",
      "$candidate_users.$login",
      "$variables",
      "$initiator",
      "$linked_subject_id",
      "$linked_type_name",
      "$business_key",
      "$process_instance_id",
      "$process_definition_id",
      "$process_definition_key",
      "$process_definition_display_name",
      "$process_start_date",
      "$process_finish_date",
      "$linked_type_name",
      "$linked_subject_id"
      ],
      "fetchAll": false,
      "entity": "",
      "typedVariablesSearch": false,
      "searchWorkflowType": "TASK",
      "totalCount": true,
      "page": 1,
      "count": 20,
      "start": 0,
      "sortFields": []
   }
}
}

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

Пример запроса поиска процессов по полям задач
{
"payload": {
   "com.unidata.mdm.rest.v2.workflow.core": {
      "countOnly": false,
      "formFields": [],
      "formGroups": [],
      "searchFields": [
      "$id",
      "$definition_display_name",
      "$finished"
      ],
      "returnFields": [
      "$business_key",
      "$id",
      "$definition_id",
      "$definition_key",
      "$definition_display_name",
      "$start_date",
      "$finish_date",
      "$initiator",
      "$initiator_full_name",
      "$initiator_email",
      "$finished",
      "$end_event_id",
      "$end_event_name",
      "$linked_type_name",
      "$linked_subject_id"
      ],
      "fetchAll": true,
      "entity": "",
      "typedVariablesSearch": false,
      "searchWorkflowType": "PROCESS",
      "totalCount": true,
      "page": 1,
      "count": 20,
      "start": 0,
      "sortFields": [],
      "supplementary": [
      {
         "countOnly": false,
         "formFields": [
            {
            "name": "$assignee",
            "type": "STRING",
            "searchType": "EXACT",
            "inverted": false,
            "value": "admin"
            }
         ],
         "formGroups": [],
         "searchFields": [
            "$definition_display_name",
            "$id",
            "$assignee",
            "$completed",
            "$process_definition_display_name"
         ],
         "returnFields": [
            "$id",
            "$definition_key",
            "$definition_display_name",
            "$create_date",
            "$due_date",
            "$complete_date",
            "$completed",
            "$assignee",
            "$assignee_full_name",
            "$candidate_groups.$name",
            "$candidate_users.$login",
            "$variables",
            "$initiator",
            "$linked_subject_id",
            "$linked_type_name",
            "$business_key",
            "$process_instance_id",
            "$process_definition_id",
            "$process_definition_key",
            "$process_definition_display_name",
            "$process_start_date",
            "$process_finish_date",
            "$linked_type_name",
            "$linked_subject_id"
         ],
         "fetchAll": false,
         "entity": "",
         "typedVariablesSearch": false,
         "searchWorkflowType": "TASK",
         "totalCount": true,
         "page": 1,
         "count": 20,
         "start": 0,
         "sortFields": []
      }
      ]
   },
   "org.unidata.mdm.rest.v2.dq.data": {
      "setNames": [],
      "ruleNames": [],
      "functionNames": [],
      "message": null,
      "severity": null,
      "category": null,
      "totalScore": 0,
      "matchAll": false,
      "negate": false
   },
   "com.unidata.mdm.rest.v1.classifiers.multiple": null
}
}