GetMyBot-Wissensdatenbank

Практика: каталог, списки и заявки

Шесть упражнений: поиск занятия, связанные записи, обработка HTTP-списка, итоги, подтверждение заявки, команды в диалоге и составление пар. С настройками и ожидаемыми результатами.

Auf dieser Seite

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

Работайте в учебном проекте. Первые четыре урока используют свои коллекции и не требуют платного внешнего сервиса. Во втором уроке для получения списка понадобится HTTP-источник; саму обработку можно сначала проверить на приведённом JSON в редакторе.

Что хотите сделатьС какого урока начать
Найти занятие по названию и предложить вариантыУрок 1: record_search, branch_result, record_related
Отобрать и отсортировать список из HTTP-ответаУрок 2: list_transform
Посчитать число занятий и стоимость по категориямУрок 3: list_aggregate
Подтвердить заявку, только пока она новаяУрок 4: record_update_if, branch_value
Дать человеку выйти из анкеты командойУрок 5: escape_on_command и проверка кнопок
Разделить участников на парыУрок 6: формула PAIRUP

В таблицах ниже куда сохранить означает имя временного результата текущего запуска, например search. В следующем действии пишите это имя без ctx. и без скобок. В тексте сообщения используйте подстановку, например {{ctx.search.first.name}}. Постоянные данные пользователя сохраняются отдельно в параметрах или коллекциях.

Урок 1. Найти занятие и показать варианты

Результат: человек пишет «акварель», бот находит занятие, предлагает дату и после нажатия читает актуальную запись.

Подготовьте данные

В разделе Данные создайте коллекцию classes. Поля code, name, aliases имеют тип Текст.

codenamealiases
wcАкварельРисование водой
clayЛепкаКерамика

Создайте вторую коллекцию sessions: class_code, title, state типа Текст, price типа Число.

class_codetitlestateprice
wcАкварель в субботуopen1500
wcАкварель в воскресеньеopen1800
clayЛепка в субботуopen2000

code=wc – ваш постоянный код занятия. _record_id – внутренний ID конкретной строки коллекции. В этом уроке коллекции связаны через code и class_code; для выбора конкретной даты используем _record_id строки sessions.

Настройте поиск и выбор ветки

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

Добавьте Поиск по коллекции (record_search):

НастройкаЗначение
Коллекцияclasses
Запрос{{text}}
Поля поискаname
Поля с альтернативными названиямиaliases
Режимauto
Учитывать опечаткиВключено
Максимум строк20
Куда сохранитьsearch

Следующим действием добавьте Выбрать следующий шаг (branch_result). Источник – search. Ветки проверяются сверху вниз; сработает первая подходящая.

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

ПорядокУсловиеЧто делает целевая реакция
1Неполная выдача (truncated)Просит уточнить запрос
2Ничего не найдено (empty)Предлагает другое название
3Нечёткое совпадение (fuzzy)Показывает «Вы имели в виду…?» и просит подтвердить выбор
4Единственный результат (single)Получает даты найденного занятия
5Несколько результатов (multiple)Показывает найденные занятия кнопками
ИначеЗапасной переходПросит уточнить запрос

Нечёткое совпадение ставим раньше единственного результата: одна строка с похожим названием ещё не означает, что пользователь выбрал именно её. Точное совпадение тоже можно выделить отдельной веткой, но оно само по себе не означает единственность записи.

Для нескольких занятий используйте помощник Кнопки из результата: источник search, подпись по name, ключ выбора по code. Каждая кнопка должна передавать selected_code своего занятия. Для единственного результата передайте в следующее действие {{ctx.search.first.code}}.

В реакции получения дат добавьте Найти связанные записи (record_related): коллекция sessions, поле связи class_code, значение выбранного code, дополнительный фильтр state=open, результат sessions_result. Для ветки с кнопками значение берётся из {{ctx.selected_code}}.

После чтения снова используйте branch_result: неполная выдача, пустой результат, одна дата, несколько дат. Из sessions_result создайте кнопки с названием title. Передавайте selected_id из _record_id.

Обработчик даты заново читает коллекцию sessions через Найти связанные записи: поле _record_id, значение {{ctx.selected_id}}, результат selected_session. Лишь после проверки результата покажите {{ctx.selected_session.first.title}} и {{ctx.selected_session.first.price}}.

Проверьте сценарий

  • Акварель должно привести к двум датам.
  • Рисование водой должно найти то же занятие по альтернативному названию.
  • Акварел должно предложить подтвердить похожее название; не оформляйте заявку сразу по опечатке.
  • Астрономия должно привести к сообщению о пустом результате.
  • Для проверки неполной выдачи временно поставьте максимум связанных строк 1: две даты не должны превратиться в «единственную доступную дату».
  • Отправьте каталог, поменяйте цену даты в коллекции и нажмите старую кнопку. Карточка должна показать свежую цену. Если строку удалили, обработайте пустой результат вместо показа старых данных.

Если не получается: проверьте, что имена коллекций и полей совпадают; в связанных записях сравниваются одинаковые коды. В Проверить поиск вводите конкретный текст, а не {{text}}: предварительная проверка читает данные, но не запускает следующую реакцию.

Подробности: коллекции, динамические кнопки и Carry.

Урок 2. Оставить доступные занятия и убрать повторы

Результат: из пяти строк получатся два самых дешёвых доступных занятия без повторов. При этом бот сохранит информацию, что есть ещё один результат.

Пусть HTTP-сервис возвращает объект с полем items:

{
  "items": [
    {"id":"c1","category":"watercolor","title":"Акварель","state":"open","price":1500},
    {"id":"c2","category":"clay","title":"Лепка","state":"open","price":2000},
    {"id":"c1","category":"watercolor","title":"Акварель","state":"open","price":1500},
    {"id":"c3","category":"sketch","title":"Скетчинг","state":"closed","price":900},
    {"id":"c4","category":"watercolor","title":"Акварель вечером","state":"open","price":1800}
  ]
}
  1. В действии Запрос к источнику выберите JSONPath items и сохраните результат под именем catalog. Нужен массив из пяти объектов, а не весь объект ответа.
  2. Добавьте Обработать список (list_transform).
  3. Укажите настройки ниже и сохраните результат как selection.
  4. Для кнопок используйте selection.rows. Поле выбора здесь id: это обычные данные внешнего сервиса, у них нет обязательного _record_id коллекции.
{
  "source": "catalog",
  "filters": [{"field":"state","operator":"eq","value":"open"}],
  "sort": [{"field":"price","direction":"asc","mode":"number"}],
  "distinct_by": ["id"],
  "offset": 0,
  "limit": 2,
  "save_to_ctx": "selection"
}

В редакторе эти параметры задаются отдельными полями. Для предварительной проверки вставьте сам массив items, без внешнего объекта. Проверка не отправляет сообщения и не меняет записи.

Ожидается: selection.rows содержит c1 за 1500 и c4 за 1800, в этом порядке. count=2, total=3, truncated=true: три занятия прошли отбор, но показаны два. Повтор c1 исчез, закрытый c3 отфильтрован.

Чтобы показать оставшуюся строку в том же исходном списке, поставьте offset=2: останется c2. Для нового пользовательского запроса сначала снова получите данные. Само окно offset/limit не хранит состояние пагинации и не создаёт кнопку «Далее».

Если не получается: сортировка 10, 2 вместо 2, 10 означает, что выбран текстовый режим. Ошибка источника часто означает, что в catalog попал весь объект с items. Неверное число вроде "цена неизвестна" не превращается в ноль: исправьте данные или отберите такие строки до числовой операции.

Как показать полученные строки кнопками: динамические кнопки и передача параметров.

Урок 3. Посчитать итоги по категориям

Результат: бот посчитает количество и стоимость доступных занятий по каждой категории.

  1. Возьмите catalog из прошлого урока.
  2. Повторите обработку списка с фильтром state=open и удалением повторов по id, но поставьте limit=100 и сохраните результат как available_classes. Для нашего примера это все три подходящие строки.
  3. Добавьте Группы и итоги (list_aggregate). Источник available_classes, группировка по category, результат totals.
  4. Добавьте три метрики: class_count с операцией count без поля; price_sum с sum по price; price_avg с avg по price.
{
  "source": "available_classes",
  "group_by": ["category"],
  "metrics": [
    {"as":"class_count","operator":"count"},
    {"as":"price_sum","operator":"sum","field":"price"},
    {"as":"price_avg","operator":"avg","field":"price"}
  ],
  "save_to_ctx": "totals"
}

Ожидается: в totals.rows две группы.

categoryclass_countprice_sumprice_avg
watercolor233001650
clay120002000

Уберите группировку и выполните действие заново: общий итог должен содержать class_count=3 и price_sum=5300. totals.count при этом равно 1, потому что в результате одна строка итога. Число занятий берётся из вашей метрики totals.first.class_count, а не из числа строк результата.

Для сообщения используйте {{ctx.totals.first.class_count}} и {{ctx.totals.first.price_sum}}. Для нескольких групп можно пройти по totals.rows в for_each.

Проверьте ошибочный вариант: передайте в подсчёт selection с лимитом 2 из предыдущего урока. Сумма будет 3300 только по видимым строкам, а не 5300. Полнота исходного результата не восстанавливается после подсчёта. Надпись «Итого по всей базе» здесь будет неверной.

Для пустого списка без группировки count и sum дают ноль, avg, min, max – null. Не показывайте null пользователю как среднюю цену: добавьте отдельный ответ для пустого набора.

Урок 4. Подтвердить заявку один раз

Результат: первый запуск подтвердит новую заявку, повторный получит конфликт и не покажет сообщение об успешном подтверждении.

Создайте коллекцию practice_bookings: поля guest_id и state типа Текст. Добавьте учебную строку с вашим user_id и state=new. Передайте её внутренний _record_id в selected_id кнопки. Это должен быть ID строки коллекции, а не код занятия wc.

В обработчике кнопки добавьте Изменить запись при условии (record_update_if):

{
  "collection": "practice_bookings",
  "record_id": "{{ctx.selected_id}}",
  "expected": {"state":"new","guest_id":"{{user_id}}"},
  "values": {"state":"confirmed"},
  "save_to_ctx": "transition"
}

Проверка владельца и статуса выполняется вместе с изменением записи. Если два запуска одновременно увидели новую заявку, только один сможет изменить её из new в confirmed.

Далее добавьте Выбрать шаг по значению (branch_value). Значение – {{ctx.transition.status}}. Создайте отдельные целевые реакции и выберите их в редакторе:

УсловиеОтвет целевой реакции
Равно updated«Заявка подтверждена»
Равно conflict«Заявка уже изменилась. Откройте её заново»
Равно not_found«Заявка больше недоступна»
Иначе«Не удалось подтвердить заявку. Попробуйте открыть её заново»

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

Эту же развилку можно настроить прямо в исходах условного изменения: updated, conflict, not_found, error. Тогда отдельное действие branch_value не понадобится. Выбранный исход завершает текущую цепочку; сообщение об успехе по-прежнему остаётся только в реакции updated. Пошаговый пример с исходами.

Для проверки временно оставьте учебную кнопку многоразовой и выполните два нажатия. Сначала ожидается updated, затем conflict. Удалите строку и нажмите кнопку ещё раз: ожидается not_found. Отдельно проверьте строку с другим guest_id: изменения не должны сохраниться, результат будет conflict.

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

Подробнее о выборе и изменении записи: работа с коллекциями. Условное изменение добавляет к фильтру проверку текущего состояния.

Урок 5. Проверить команды и старые кнопки

Результат: человек сможет выйти из вопроса по /start, если вы явно разрешили это в настройках.

  1. Создайте учебную реакцию с вопросом «Как вас зовут?» и включённым ожиданием текстового ответа.
  2. Проверьте, что отдельная активная реакция отвечает на /start.
  3. Пока Команды бота прерывают ожидание выключено, отправьте /start в ответ на вопрос. Это остаётся входным ответом и проходит проверку шага. Например, шаг без проверки может сохранить строку /start как имя.
  4. Включите Команды бота (например, /start) прерывают ожидание (escape_on_command) и сохраните реакцию.
  5. Снова запустите вопрос и отправьте /start. Ожидание должно сняться, после чего команда обрабатывается обычными реакциями. Если реакции на неё нет, сама настройка не создаст приветствие.
  6. Отдельно проверьте /cancel или «отмена»: они снимают ожидание и используют настроенную реакцию отмены либо стандартный ответ.

Поведение кнопок проверяйте в каждом подключённом канале. Возможность оставить клавиатуру, убрать её или изменить подпись после нажатия зависит от канала; доступные варианты показывает редактор. Реакция на кнопку, срок действия Carry и защита записи решают разные задачи.

Проверка старого сообщения: отправьте каталог, измените название или цену, затем нажмите старую кнопку. Carry хранит данные момента отправки. Для актуальной карточки передавайте ID и перечитывайте запись после нажатия, как в уроке 1.

Подробнее: ожидание ответа, Carry и поведение кнопок. Пример перечитывания записи по ID разобран в уроке 1 выше.

Урок 6. Разделить участников на пары

Результат: одни и те же участники с одним ключом раунда дают одно и то же распределение. Сменив ключ раунда, можно получить другое распределение.

Создайте учебную коллекцию practice_participants с текстовым полем participant_id и четырьмя строками: u1, u2, u3, u4. В реакции прочитайте её действием Найти записи (record_query) в ключ participants, с лимитом 100. Это простой список строк; не подставляйте сюда целый результат нового поиска с его служебными полями.

Добавьте действие Вычисление и выберите режим Расширенная формула (formula, язык DSL). У каждой входной строки должно быть непустое уникальное поле participant_id. Используйте:

TOJSON(PAIRUP(FROMJSON({participants}); "participant_id"; "practice-round-1"))

Не заменяйте эту формулу выражением {{= ...}}: это другой движок. Буквальная проверка формулы не подставляет контекст вашей реакции, поэтому пример с {participants} проверяйте запуском учебной реакции после чтения коллекции.

Сохраните результат вычисления в pairs. Ожидается JSON-массив групп ID. Для четырёх разных участников это две пары; для пяти – одна пара и одна тройка. Проверьте, что каждый исходный ID встречается один раз. Повторите с теми же данными и ключом раунда: результат должен совпасть.

Для следующего упражнения добавьте текстовое поле tags. Укажите |team-a| у u1 и u2, |team-b| у u3 и u4. Необязательный четвёртый аргумент задаёт имя поля с тегами людей, которых нежелательно соединять, например из одного отдела:

TOJSON(PAIRUP(FROMJSON({participants}); "participant_id"; "practice-round-2"; "tags"))

Алгоритм старается избежать пересекающихся тегов, но это не гарантия отсутствия конфликтов. Если такое соседство недопустимо, дополнительно проверьте состав групп перед отправкой.

Поддерживается до 10 000 участников, но лимит отправок обычной реакции остаётся 50 на событие. Цикл по большому списку не превращается в массовую рассылку. Для уведомления участников используйте рассылки, а не тысячи отправок в одном for_each.

Подробный синтаксис и ограничения: формулы, лимиты.

Что собрать дальше

Как проверить всю цепочку

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

Для всей несохранённой цепочки используйте проверку черновика. В отчёте сообщения и изменения показываются как предполагаемые действия на заданных учебных данных. Успешная проверка не подтверждает подключение внешнего сервиса или доставку сообщения.

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

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