diff --git a/docs/_resources/Архитектура - общая схема.png b/docs/_resources/Архитектура - общая схема.png new file mode 100644 index 0000000..7b93035 Binary files /dev/null and b/docs/_resources/Архитектура - общая схема.png differ diff --git a/docs/_resources/Архитектура - основной процесс.png b/docs/_resources/Архитектура - основной процесс.png new file mode 100644 index 0000000..f1f14e4 Binary files /dev/null and b/docs/_resources/Архитектура - основной процесс.png differ diff --git a/docs/_resources/Архитектура.svg b/docs/_resources/Архитектура.svg new file mode 100644 index 0000000..3fc7e39 --- /dev/null +++ b/docs/_resources/Архитектура.svg @@ -0,0 +1,1430 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + redmine issue event emitter + сохранение задачи в кеш + анализ задачи + сохранение событий в журнал + подписка на источники событий по email, через cron, rss + + + + + + + + события - это обнаружение факта внесения какого-либо изменения в задачу + + + + + + + + анализ изменений в задаче + + + + + websocketwebhook + chat bot + + точка для расширения: можно добавить специальные обработчики для данных + + + + Ядро проекта Pinkmine(redmine issue event emitter) + + + + redmine issue event emitter + сохранение задачи в кеш + преобразование задачи + сохранение событий в журнал + подписка на источники событий по email, через cron, rss + + + + + + + + анализ изменений в задаче + + + + + websocketwebhook + chat bot + Ядро проекта Pinkmine(redmine issue event emitter) + + + + + + Что такое Pinkmine? + Предыстория проекта. Что это такое? + + Какие проблемы есть у пользователей Redmine? + + Архитектура приложения Pinkmine + + + + + + + + + + Ядро redmine issue event emitter + + NestJS framework + сервисы, контроллеры, чтение конфигурационных файлов, доступ к хранилищу данных CouchDB и многое другое + http api + + webhook-и,websocket-ы + telegram-бот + + frontend react + dashboard-ы на любых react компонентах + + + cli скрипты (wget, curl), +использование других языков программирования (python) + Автоматизация и интеграция сторонними средствами + + Автоматизация и интеграция с помощью lowcode платформы n8n + другие чат-боты + frontendserver-side + frontend на простой шаблонизации + cron-task-и + Планировщик встроенный в NestJS + + Стратегии подписки на обновления задач из Redmine + По email + Через cron-таски + Redmine отправит уведомления об изменениях в email + Pinkmine прочитает письмо, определит номер задачи, положит в очередь в свой основной процесс + Эта стратегия позволяет оперативно подгружать данные в Pinkmine + Pinkmine с помощью cron-task-и запустит процедуру определения обновлений в redmine + Pinkmine загрузит из redmine csv из сохранённых запросов и корневые задачи со всеми подзадачами + Pinkmine определит какие из задач пора обновить по полю "Обновлено" и положит в очередь в свой основной процесс + Эта стратегия позволяет догружать данные, которые не пришли по email рассылке: закрытые задачи, shady-mode, изменение структуры дерева при смене привязки к родительской задаче + Для развития + * Прямой доступ к БД redmine +* Реализация webhook-ов на стороне redmine + + + diff --git a/docs/_resources/Папка в почте с уведомлениями из Redmine.png b/docs/_resources/Папка в почте с уведомлениями из Redmine.png new file mode 100644 index 0000000..a0092a5 Binary files /dev/null and b/docs/_resources/Папка в почте с уведомлениями из Redmine.png differ diff --git a/docs/_resources/Преднастроенный фильтр в Redmine.png b/docs/_resources/Преднастроенный фильтр в Redmine.png new file mode 100644 index 0000000..bb17895 Binary files /dev/null and b/docs/_resources/Преднастроенный фильтр в Redmine.png differ diff --git a/docs/_resources/Преобразования задач.png b/docs/_resources/Преобразования задач.png new file mode 100644 index 0000000..1f0e61c Binary files /dev/null and b/docs/_resources/Преобразования задач.png differ diff --git a/docs/_resources/Преобразования задач.svg b/docs/_resources/Преобразования задач.svg new file mode 100644 index 0000000..2c6762e --- /dev/null +++ b/docs/_resources/Преобразования задач.svg @@ -0,0 +1,252 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + Чтение данных из redmine через http api + + + Указание коллекции функций-преобразователей при инициализии приложения + + Выполнение всех функций-преобразователей + + + + + Дальнейшая работа с данными (сохранение в CouchDB) + + + + diff --git a/docs/_resources/Сохранение в CouchDB.png b/docs/_resources/Сохранение в CouchDB.png new file mode 100644 index 0000000..fc30a36 Binary files /dev/null and b/docs/_resources/Сохранение в CouchDB.png differ diff --git a/docs/_resources/Сохранение в CouchDB.svg b/docs/_resources/Сохранение в CouchDB.svg new file mode 100644 index 0000000..c74878a --- /dev/null +++ b/docs/_resources/Сохранение в CouchDB.svg @@ -0,0 +1,462 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + { "_id": "8ccc", "_rev": "0-abcd", "field_a": "value_a"} + Поток 1 + Чтение документа из коллекции + Поток 2 + { + id: 8ccc, + rev: 0-abcd, + field_a: value_a +} + + Чтение документа из коллекции + + Запись новой версии документа в коллекцию + { + id: 8ccc, + rev: 0-abcd, + field_a: success_value +} + + Успех. Документу внутри CouchDB будет проставлена новая ревизия - 1-bcde + Исходный документ: + Получили: + { + id: 8ccc, + rev: 1-bcde, + field_a: success_value +} + Запись новой версии документа в коллекцию + { + id: 8ccc, + rev: 0-abcd, + field_a: fail_value +} + + Fail. Ревизия не совпадает с предыдущим значением: +"0-abcd" != "1-bcde". +Документ не будет обновлён. + { + id: 8ccc, + rev: 0-abcd, + field_a: value_a +} + Получили: + Время + + + diff --git a/docs/Архитектура.md b/docs/Архитектура.md new file mode 100644 index 0000000..6338b05 --- /dev/null +++ b/docs/Архитектура.md @@ -0,0 +1,52 @@ +# Общая структура + +![](_resources/Архитектура%20-%20общая%20схема.png) + +Основной код приложения написан на TypeScript и фреймворке NestJS (это такой Angular для backend-а) + +Монолитное приложение включает в своём составе: + +- основная функциональность +- кеш и промежуточный коллектор для данных на couchdb +- http api для внешних интеграций +- webhook-и и websocket-ы +- telegram-бот - есть возможность для реализации новых интеграций с другими чат-ботами +- cron-task-и для выполнения повторяющихся задач +- frontend server-side на простой шаблонизации +- frontend на react +- специальный плагин для lowcode платформы n8n + +# Основной процесс + +![](_resources/Архитектура%20-%20основной%20процесс.png) + +Двигателем приложения служит redmine-issue-event-emitter - это основной процесс поступления данных в Pinkmine. + +1. Подписка на источники событий - по email, через cron-таски, через rss. Позволяет обнаруживать факты изменений в задачах в redmine. ([подробнее](Как%20это%20работает/Как%20работают%20стратегии%20синхронизации%20redmine%20и%20pinkmine.md)) +2. Redmine Issue Event Emitter - это очередь для синхронизации данных из redmine. На выход кладётся номер задачи, на выходе данные полученные с помощью redmine api. Очередь обеспечивает бережное отношение к боевому redmine, так как позволяет выполнять выгрузки не выходя за выставленный лимит обращений. ([подробнее про работу очереди](Как%20это%20работает/Как%20работает%20очередь%20загрузки%20задач.md) и [подробнее про загрузку задач из redmine](Как%20это%20работает/Как%20происходит%20загрузка%20задачи%20из%20Redmine.md)) +3. Преобразование задачи - это набор вспомогательных обработчиков. Позволяет при получении данных произвести попутно полезные изменения, например, привести текстовые теги к массиву, определить текущего ответственного за задачу, обнаружить в описании или в комментариях ссылки на gitlab на MR-ы, в т.ч. можно реализовать специализированный обработчик. ([подбронее](./Как%20это%20работает/Как%20происходит%20преобразование%20задачи.md)) +4. Сохранение задачи в кеш в CouchDB ([подробнее](./Как%20это%20работает/Сохранение%20задачи%20в%20кеш%20CouchDB.md)) +5. Анализ изменений в задаче - это сравнение предыдущего состояния задачи с новым. Позволяет организовать процессы связанные с жизненным циклом самой задачи и, например, разослать уведомления с учётом происходящих изменений ([подробнее](./Как%20это%20работает/Анализ%20изменений%20в%20задаче.md)) +6. Сохранение событий изменения в журнал - вспомогательная коллекция данных ([подробнее](./Как%20это%20работает/Анализ%20изменений%20в%20задаче.md)) + +# Точки расширения + +Платформа Pinkmine предполагает в т.ч. реализацию конкретных задач в рамках основного кода. Допускается реализация специализированных внутренних сервисов. + +В рамках ядра redmine-issue-event-emitter: + +- новые стратегии получения задач из Redmine +- специализированные обработчики задач для отдельных проектов +- интеграция событий в новые процессы - чат-боты или простая передача данных с помощью webhook-ов и websocket-ов + +В рамках расширенной платформы pinkmine: + +Благодаря CouchDB стало возможным предоставить api для поиска задач с более гибкими условиями для выборки чем во встроенном в redmine api. Можно использовать в выборках комбинированные выражения через "и"/"или". Богатый набор операторов - "есть значение", "нет значения", "больше", "меньше", "равно", "поиск по регулярному выражению". Для поиска можно пользоваться расширенными данными. + +CouchDB позволяет выгружать для работы и анализа больший объём данных чем основной api redmine. + +Webhook-и и websocket-ы могут сделать api ещё более гибким. + +В рамках реализованного telegram chat бота можно дописывать логику работы под узкоспециализированные задачи. + +С помощью фреймворка NestJS можно сделать сервисы для решения узких задач. Например, сделать cron-task-у с аналитической выгрузкой с сохранением промежуточных результатов во вспомогательную коллекцию в CouchDB и предостатить уже к ней доступ через http api и сделать человеко-читаемое представление с помощью шаблона. diff --git a/docs/Как это работает/Анализ изменений в задаче.md b/docs/Как это работает/Анализ изменений в задаче.md new file mode 100644 index 0000000..4715832 --- /dev/null +++ b/docs/Как это работает/Анализ изменений в задаче.md @@ -0,0 +1,64 @@ +Как было описано в документе [Сохранение задачи в кеш CouchDB](./Сохранение%20задачи%20в%20кеш%20CouchDB.md) специфика работы с данными из issue такова что есть два состояния - предыдущее и текущее состояние. + +Значит есть возможность сравнения двух состояний и построение дополнительной логики на основе сравнения. + +Это позволило реализовать рассылку уведомлений при обнаружении в задачах изменений. + +Функции сохранения данных в CouchDB с вычислением изменений реализована в `libs/event-emitter/src/issue-cache-writer/redmine-issues-cache-writer.service.ts`. + +Затем с помощью пайплайнов rxjs реализован дополнительный анализ изменений: + +1. `src/notifications/personal-notifications.service.ts` - поиск упоминаний пользователей redmine в комментариях для пересылки личных уведомлений в чат-бот +2. `src/notifications/status-change-notifications.service.ts` - поиск изменений статусов для рассылки уведомлений согласно изменениям ответственного за дальнейшую работу над задачей +3. `src/changes-cache-writer/changes-cache-writer.service.ts` - сохранение изменений в дополнительный журнал активностей в коллекцию в CouchDB + +Сам пайплайн rxjs определяется в инициализирующей функции в `src/app.module.ts`. + +Для правильности работы функций анализа нужно выполнить правильно конфигугирование. + +Статусы используемые в вашем экземпляре redmine определяются в конфигурационном файле `configs/redmine-statuses-config.jsonc`. Они имеет следующий вид: + +```json +[ + { + "id": 1, + "name": "New" + }, + { + "id": 2, + "name": "In Progress" + }, + { + "id": 3, + "name": "Closed", + "is_closed": true + } + // ... +] +``` + +Идентификаторы и name должны соответствовать статусам настроенным в основном redmine. + +Правила для определения изменений в статусах определяются с помощью конфигурационного файла `configs/redmine-status-changes-config.jsonc`. Он имеет следующий вид: + +```json +[ + { + "default": false, + "from": "New", + "to": "In Progress", + "messages": [ + { + "recipient": "", + // Handlebars - template engine + "changes_message": "{{qa.name}} got issue #{{issue_id}} after development {{dev.name}}", + "notification_message": "{{ issue_tracker }} #{{ issue_id }} {{ issue_subject }}:\n{{dev.name}} finished development. You can test issue.\n\n{{journal_note}}" + } + // ... + ] + } + // ... +] +``` + +Это набор правил для формирования уведомлений и сообщений для журнала изменений. Поля "from" и "to" - указывают на возможные статусы задачи. "messages" - это набор сообщений. Тут можно определить варианты сообщений для различных получателей задаваемых полем "recipient", а тексты сообщений с помощью шаблонизатора [Handlebars](https://handlebarsjs.com). \ No newline at end of file diff --git a/docs/Как это работает/Как происходит загрузка задачи из Redmine.md b/docs/Как это работает/Как происходит загрузка задачи из Redmine.md new file mode 100644 index 0000000..aec8ab9 --- /dev/null +++ b/docs/Как это работает/Как происходит загрузка задачи из Redmine.md @@ -0,0 +1,6 @@ +Это обращение к стандартной функции через http api по следующему шаблону: + +`/issues/.json?include=children,journals,relations` + +Подробное описание параметров api можно найти в официальной фокументации на странице [Rest_Issues](https://www.redmine.org/projects/redmine/wiki/Rest_Issues) + diff --git a/docs/Как это работает/Как происходит преобразование задачи.md b/docs/Как это работает/Как происходит преобразование задачи.md new file mode 100644 index 0000000..8e55f51 --- /dev/null +++ b/docs/Как это работает/Как происходит преобразование задачи.md @@ -0,0 +1,41 @@ +Для разрешения некоторых проблем напрашивалось попутно после получения данных об issue из redmine делать преобразования + +Примеры: + +1. Преобразовать дату+вермя из текстового формата в числовой для возможности фильтрации данных с помощью сравнивающих операторов - больше и меньше. +2. Привести текстовое значение тегов к массиву для фильтрации данных с помощью оператора "$in". +3. Можно извлечь из описания и комментариев ссылки на MR-ы в gitlab-е. +4. Определить текущего ответственного за задачу по правилам соответствия статусу одному из полей - "Назначено", "Code Reviewer", "Quality Assurance". + +Через конфигурационный файл уже не решить настройку, т.к. за преобразования задач отвечают функции - а если точнее то классы с реализацией интерфейса `IssueEnhancerInterface`. Это делается в коде проекта Pinkmine. Примеры реализаций в файлах: + +- `libs/event-emitter/src/issue-enhancers/timestamps-enhancer.ts` - преобразует дату+время из текстового формата в числовой +- `libs/event-emitter/src/issue-enhancers` - в папке ещё несколько универсальных примеров (не зависящих от экземпляра redmine) +- `src/issue-enhancers` - в папке лежат дополнительные примеры с функциями уже привязанными к рабочему redmine и специфичные для рабочих проектов + +Чтобы эти функции отрабатывали налету после получения данных из redmine и перед сохранением в кеш, то нужно при начальной инициализации приложения (`src/app.module.ts` > `AppModule.onModuleInit`) указать набор реализаций `IssueEnhancerInterface`. Упрощённый код, отражающий суть: + +```ts +export class AppModule { + // ... + onModuleInit() { + // ... + this.enhancerService.addEnhancer([ + this.timestampEnhancer, + this.customFieldsEnhancer, + this.currentUserEnhancer, + this.issueUrlEnhancer, + this.categoryMergeToTagsEnhancer, + this.calendarEnhancer, + ]); + // ... + } + // ... +} +``` + +Схематически процесс можно проиллюстрировать так: + +![Преобразования задач](../_resources/Преобразования%20задач.png) + +Выполнение преобразований данных до сохранения в кеш в CouchDB позволяет в последствии использовать дополнительные данные для выборки задач \ No newline at end of file diff --git a/docs/Как это работает/Как работает очередь загрузки задач.md b/docs/Как это работает/Как работает очередь загрузки задач.md new file mode 100644 index 0000000..9fb8324 --- /dev/null +++ b/docs/Как это работает/Как работает очередь загрузки задач.md @@ -0,0 +1,22 @@ +Это стандартный паттерн: + +1. берём максимум N номеров задач раз в M секунд, +2. передаём их в функцию для синхронизаицации + 1. выгружаем данные о задаче из redmine api + 2. пишем полученные данные во внутренний кеш + +Очередь нужна для бережного отношения к боевому redmine, потому что не загрузит CPU на сервере сотнями одновременных параллельных запросов. + +Настройка очереди производится в конфигурационном файле `configs/issue-event-emitter-config.jsonc` в секции `issueChangesQueue`: + +```json +{ + // ... + "issueChangesQueue": { + "updateInterval": 5000, // 5 sec + "itemsLimit": 3 + } + // ... +} +``` + diff --git a/docs/Как это работает/Как работают стратегии синхронизации redmine и pinkmine.md b/docs/Как это работает/Как работают стратегии синхронизации redmine и pinkmine.md new file mode 100644 index 0000000..5d4c0d7 --- /dev/null +++ b/docs/Как это работает/Как работают стратегии синхронизации redmine и pinkmine.md @@ -0,0 +1,106 @@ +# Стратегия по email + +![](../_resources/Папка%20в%20почте%20с%20уведомлениями%20из%20Redmine.png) + +1. В задаче в redmine делается любое изменение - смена статуса, обновление описания, добавление нового комментария. +2. Redmine отправляет сообщение о факте обновления задачи по email +3. Pinkmine читает новые письма протоколу imap, находит в заголовках номер задачи с помощью регулярного выражения +4. Pinkmine помещает номер задачи в очередь для выполнения обновления + +Параметры работы стратегии задаются в конфигурационном файле `configs/issue-event-emitter-config.jsonc` в секции `mailListener`: + +```json +{ + + // ... + + "mailListener": { + + // регулярное выражение для определения номера задачи в заголовке письма: + "issueNumberParser": "\\b(?<=#)\\d+\\b", + + // параметры для доступа к почте через протокол imap: + "imapSimpleConfig": { + "imap": { + "user": "", + "password": "", + "host": "", + "port": 143, + // tls: true, + "autotls": "always", + "authTimeout": 5000 + } + }, + + // интервал через который происходит проверка почты: + "updateInterval": 180000, // 3 min + + // имя папки в которой следует искать входящие письма + "boxName": "INBOX" + }, + + // ... + +} +``` + +# Стратегия через cron-таски + +![Преднастроенный фильтр в Redmine](../_resources/Преднастроенный%20фильтр%20в%20Redmine.png) + +1. Pinkmine загружает данные из преднастроенных query-запросов в redmine в формате csv. Выбирает данные по двум полям - id задачи и "обновлено" (дата+время обновления задачи) +2. Pinkmine проверяет какие задачи в своём кеше (CouchDB) уже успели устареть +3. Номера устаревших задач кладёт в очередь для дальнейшего обновления + +Параметры работы стратегии задаются в конфигурационном файле `configs/issue-event-emitter-config.jsonc` в секциях `csvListener` и `rootIssueListener`: + +```json +{ + + // ... + + // таски для синхронизации по преднастроенным запросам: + "csvListener": { + "tasks": [ + { + // расписание для выполнения задачи (синтаксис crontab) + "schedule": "* * * * *", + + // поле в csv-файле с датой и временем обновления задачи + "updatedAtFieldName": "Обновлено", + + // формат даты и времени + "dateTimeFormat": "dd.MM.yyyy HH:mm", + + // ссылки на предстароенные запросы выборки задач в формате csv + "csvLinks": [ + "https:///projects/proj/issues.csv?utf8=%E2%9C%93&query_id=2" + ] + } + ] + }, + + // таски для синхронизации корневых и суммирующих задач: + "rootIssueListener": { + "tasks": [ + { + // расписание для выполнения задачи (синтаксис crontab) + "schedule": "15 6,12 * * *", + + // номера задач + "rootIssues": [ + 1, + 3, + 7, + 11 + ] + } + ] + }, + + // ... + +} +``` + +Эта стратегия позволяет догружать данные, которые не пришли по email рассылке: закрытые задачи, shady-mode, изменение структуры дерева при смене привязки к родительской задаче и т.п. \ No newline at end of file diff --git a/docs/Как это работает/Сохранение задачи в кеш CouchDB.md b/docs/Как это работает/Сохранение задачи в кеш CouchDB.md new file mode 100644 index 0000000..7b1cde8 --- /dev/null +++ b/docs/Как это работает/Сохранение задачи в кеш CouchDB.md @@ -0,0 +1,32 @@ +CouchDB - это nosql субд. Основная концепция CouchDB - это коллекции данных (json) с возможностью гибкого поиска по ним. + +Важная особенность CouchDB - это отсутствие механизма транзакций. Гарантия согласованности записи обеспечивается с помощью контроля ревизии. + +Документы в CouchDB имеют версионирование, аналогичное тому, как это было бы в обычной системе контроля версий, такой как Subversion. Если вы хотите изменить значение в документе, вы создаете полностью новую версию этого документа и сохраняете ее поверх старой. После выполнения этого вы получите две версии одного и того же документа, одну старую и одну новую. + +Как это обеспечивает улучшение по сравнению с блокировками? Рассмотрим набор запросов, желающих получить доступ к документу. Первый запрос считывает документ. Пока он обрабатывается, второй запрос изменяет документ. Поскольку второй запрос включает совершенно новую версию документа, CouchDB может просто добавить его в базу данных, не дожидаясь завершения запроса на чтение. + +Когда третий запрос захочет прочитать тот же документ, CouchDB укажет ему на новую версию, которая только что была написана. В течение всего этого процесса первый запрос все еще может читать исходную версию. + +Запрос на чтение всегда будет отображать самый последний снимок вашей базы данных на момент начала запроса. + +![Сохранение в CouchDB](../_resources/Сохранение%20в%20CouchDB.png) + +Redmine Issue Event Emitter всегда для записи нового состояния задачи должен прочитать предыдущее состояние. Это обязательно нужно для корректного определения последней ревизии. Одновременно для дальнейшего анализа станет доступно два состояния - предыдущее и текущее. Благодаря этой особенности работы с CouchDB можно проводить дополнительный [анализ произошедших в задаче изменений](./Анализ%20изменений%20в%20задаче.md). + +Настройка доступа к CouchDB делается в конфигурационном файле `configs/issue-event-emitter-config.jsonc` в секции `couchDb`: + +```json +{ + // ... + "couchDb": { + "url": "http://admin:password@localhost:5984", + "dbs": { + "users": "redmine_users", + "issues": "redmine_issues", + "dashboards": "dashboards" + } + } + // ... +} +``` \ No newline at end of file