Перейти к содержанию

Подготовка редакционной подборки

Маршрутизация прошлого урока дала каждой заявке понятный исход. Теперь редактору нужна компактная подборка: несколько карточек с сохранёнными идентификаторами, заголовками и очередями. Рассмотрим узел Aggregate и подготовим ожидаемый объект с массивом records. Число items изменится, но связь полей внутри каждой карточки должна сохраниться.

На вход этого шага подаём собственный образец из трёх уже размеченных карточек. Он описан в prepared-items.json и не получен запуском. Мы не объявляем, что Switch автоматически собрал свои разные выходы обратно: подготовка набора и объединение веток являются отдельными задачами. В уроке изучаем агрегацию одного известного входного потока.

Собственные файлы этого шага доступны в архиве fixtures и контрактов серии, папка lesson-06. Это приложения к объяснению, а не готовый экспорт workflow.

Карточка перед объединением

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

Проекция полей выполняется до объединения. Если агрегировать весь HTTP-вход, подборка получит лишние технические сведения и станет зависеть от способа доставки. Если убрать поле reason, карточка P-003 окажется в ручной очереди без объяснения. Выбор набора полей определяется задачей человека, который будет читать результат, а не количеством доступной информации.

Для P-001 сохраняем тему web, очередь web_editorial и очищенный заголовок. Для P-002 сохраняем SEO-направление. Для P-003 сохраняем web3, manual_review и unknown_topic. Подготовка не заменяет неизвестную тему другой строкой и не объединяет карточки только потому, что две из них попадут к одному редактору.

Один item с массивом records

Aggregate объединяет отдельные items. Вариант All Item Data собирает их данные в один список, а параметр Put Output in Field задаёт имя массива. Выберем records и заранее подготовленные поля. Документация Aggregate описывает этот режим и отличие от объединения отдельных полей.

{
  "records": [
    {"id": "P-001", "title": "Проверка старых URL", "queue": "web_editorial", "approved": false},
    {"id": "P-002", "title": "Страница товара без запаса", "queue": "seo_editorial", "approved": false},
    {"id": "P-003", "title": "Разбор web3-приложения", "queue": "manual_review", "approved": false}
  ]
}

Фрагмент сокращён для чтения: полный образец сохраняет все восемь полей карточки. На входе находятся три items, на выходе ожидается один item, внутри которого массив из трёх объектов. Выражение $json.title после такого изменения больше не относится к заголовку карточки верхнего уровня. Для конкретной записи нужен путь внутри records или новое раскрытие массива.

Состояние Число items Где находится идентификатор Что означает число
До Aggregate 3 В каждом json.id Три предложения
После Aggregate 1 В json.records Одна подборка
Массив records Не отдельный поток В каждом объекте списка Три связанные карточки

Один выходной item не означает, что осталось одно предложение. Это лишь новая форма передачи. Если следующая операция создаёт одну задачу на каждый item, она могла бы создать одну задачу на всю подборку. Чтобы создавать отдельные задачи, потребовалось бы раскрыть records. В этой серии внешнего действия нет, поэтому ограничиваемся правильным чтением структуры.

Для отдельной схемы агрегации добавьте Manual Trigger и Code с именем Fixture prepared records. У Code выберите Run Once for All Items и вставьте полный prepared-fixture.js: он возвращает три карточки из приложения, а не читает выходы других workflow. Соедините его со следующим узлом Aggregate, названным Editorial batch. В Aggregate выберите All Item Data, поле выхода records, включение All fields, без бинарных данных. Провода образуют одну линию: Manual Trigger → Fixture prepared records → Editorial batch.

В ожидаемом выходе агрегатора прочитайте сначала число items, затем массив records. Для проверки связи второй карточки выражение имеет вид {{ $json.records[1].id }} и ожидаемое значение P-002; её заголовок находится в {{ $json.records[1].title }}. Индекс здесь нужен для конкретного учебного разбора. В прикладной работе поиск нужного предложения следует выполнять по идентификатору, поскольку порядок подборки может измениться.

Почему не два параллельных массива

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

В нашем результате идентификатор и заголовок остаются внутри одного объекта. Добавление четвёртого поля не требует поддерживать ещё одну параллельную последовательность. Для P-003 причина ручной проверки хранится рядом с её очередью, а не в общем списке исключений без идентификатора. Такая форма облегчает последующее сравнение и объясняет, почему выбирается All Item Data.

Представим, что у P-002 исчез заголовок. Корректная подготовка должна отметить нарушение раньше, а подборка — сохранить объяснимый ручной исход. Не следует скрывать проблему автоматическим объединением строк или заменять пропущенный заголовок соседним. Ключевое требование не «массив выглядит полным», а «каждое предложение сохраняет свои данные и причину решения».

Отдельные ветки и общий набор

В прошлом уроке Switch показал тематические выходы. Aggregate, поставленный после одного выхода, соберёт только items этой ветки. Он не читает другие провода автоматически. Чтобы получить общий итог настоящего разветвлённого workflow, пришлось бы отдельно описать согласование веток или хранение результатов. В нашем приложении используется явный общий fixture подготовленных карточек, поэтому такого механизма не заявлено.

Есть и другой проектный вариант: сначала вычислить очередь как поле в общей цепочке, подготовить подборку, а тематическое разделение выполнять только там, где оно требуется. Тогда общий отчёт и диспетчеризация имеют разные выходные результаты. Какой вариант выбрать, зависит от того, нужен ли человеку общий список или несколько независимых тематических подборок. Важно описать это до размещения узлов.

Также определим пустой вход. Если ни одной заявки нет, не нужно придумывать предложение P-000. Для внешнего отчёта можно было бы подготовить отдельное сообщение «нет новых записей», но такое сообщение не является заявкой. В приложении фиксируем ожидаемый пустой редакционный список отдельно от поведения конкретного узла на пустом потоке: последнее требует проверки в выбранной версии n8n.

Учебный webhook и рабочий адрес

В дальнейшем общий набор мог бы формироваться из нескольких событий, а не трёх локальных объектов. Узел Webhook различает тестовый и производственный URL: тестовый связан с режимом ожидания события, производственный регистрируется при публикации workflow. Производственные данные просматривают через историю Executions. Документация Webhook объясняет эту границу.

Однако один webhook обычно представляет одну попытку доставки, а не автоматически накопленный недельный список. Для накопления потребовалось бы внешнее хранилище и правило границ подборки. Наш массив из трёх заявок задан как fixture, а не как результат работающей очереди. Это ограничение предотвращает неверное ожидание, что Aggregate сам хранит события между разными выполнениями.

После ручного запуска этой отдельной учебной линии ожидается один item с массивом records из трёх карточек. Откройте JSON узла Editorial batch и сравните содержимое с expected-batch.json, учитывая, что его внешние поля fictional и example_execution описывают файл-образец, а реальная ожидаемая полезная часть находится в expected_output_items. Такое сравнение отделяет происхождение документа от структуры выхода узла.

Файлы prepared-items.json и expected-batch.json показывают ожидаемый переход формы. Сопоставьте все три идентификатора и их очереди, затем прочитайте заголовок второй карточки через массив. Если после объединения ищете title на верхнем уровне, вернитесь к таблице структуры. Далее разберём ошибки процесса и подготовим отдельное сообщение о сбое без отправки уведомлений.