ym88659208ym87991671
Фреймворк Action | Документация SmartMarket
Skip to main content

Action

Action — одна из сущностей DSL, которая представляет собой реакцию на какое-либо событие. Например, отправка сообщения, запрос данных у внешней системы, изменение состояния и т.д.

Actions, которые будут использоваться многократно, хранятся в static/references/actions.

Ниже описаны основные Actions, которые передаются в поле type:

Виды Actions

Actions по умолчанию

ТипОписание
None

EmptyAction — если при использовании actions разработчик забыл указать тип или указал тип null, то сработает EmptyAction, который запишет в лог "EmptyAction.run: action do nothing".

{ "type": null, }

Составные Actions

Ниже представлено описание Actions, которые комбинируют другие Actions.

ТипОписание
requirement

RequirementActionэто action, который позволяет выполнить action только при выполнении условия в requirement.

Используемые параметры:

  • requirement — dictionary. Условие, которое будет выполнено. 
  • action — dictionary. Действие, которое будет выполнено в случае успеха проверки requirement.
{ "type": "requirement", "action": { "type": "do_nothing", "command": "ANSWER_TO_USER", "nodes": { "text": "Номер указан не верно, введите еще раз." } }, "requirement": { "type": "external", "requirement": "number_is_wrong" } }
else

ElseAction — это action, который позволяет выполнить action при выполнении requirement. Иначе будет выполнен else_action. Использование else_action необязательно, ключ может быть пропущен.

Используемые параметры:

  • requirement — dictionary. Условие, которое будет выполнено. 
  • action — dictionary. Действие, которое будет выполнено в случае успешной проверки requirement.
  • else_action — dictionary. Действие, которое будет выполнено в случае не прохождения проверки requirement.
{ "type": "else", "requirement": { "type": "external", "requirement": "callcenter_white_list" }, "action": { "type": "external", "action": "nothing_found" }, "else_action": { "type": "external", "action": "just_callcenter_action" } }
choice

ChoiceAction — это action, который позволяет выполнить actions при их доступности. При запуске по порядку проверяется requirement:

  • если он вернул True, то выполняется action. Остальные requirement_action в этом случае не выполняются;
  • если он вернул False, то идет переход на следующий requirement. 

Если не выполнен ни один из requirement_action и при этом указан else_action, то будет выполнен else_action (его использование необязательно, ключ может быть пропущен).

Используемые параметры:

  • requirement_actions — list[dictionary]. Список словарей вида RequirementAction
{ "type": "choice", "requirement_actions": [ { "requirement": { "type": "external", "requirement": "number_is_ok" }, "action": { "nodes": { "answer": [["Ваш номер {{main_form_phone_number}} введен верно"]] } } }, { "requirement": { "type": "external", "requirement": "number_is_wrong" }, "action": { "nodes": { "answer": [["Номер указан не верно, введите еще раз"]] } } } ], "else_action": { "nodes": { "answer": [["Ваш номер и не ok, и не ok. Странно это"]] } } }
external

ExternalAction — это action, который позволяет вызвать action, описанный в references/actions/actions.json. По данному пути описываются actions, которые являются уникальными для конкретного смартапа и которые предполагают многократное использование.

Используемые параметры:

  • action — string. Название action, описанный по пути references/actions/actions.json
{ "type": "external", "action": "approve" }
composite

CompositeAction — это action, который позволяет выполнить список actions.

Используемые параметры:

  • actions — list[dictionary].  Список словарей, в которых описываются последовательно выполняемые actions.
{ "type": "composite", "actions": [ { "nodes": { "answer": [ ["Сейчас переведём вас на оператора"]] } }, { "type": "external", "action": "callcenter_action" } ] }

Actions для управления сценарием

Ниже представлено описание Actions, которые позволяют управлять сценарием и его компонентами.

ТипОписание
clear_form_by_id

ClearFormAction — это action, который позволяет очистить форму.

Используемые параметры:

  • form — string. Название формы.
    { "type": "clear_form_by_id", "form": "hello_app_form" }
clear_inner_form_by_id

ClearInnerFormAction — это action, который позволяет очистить вложенную форму (форма, входящая в композитную форму). 

Используемые параметры:

  • form — string. Название композитной формы.
  • inner_form — string. Название вложенной формы.
    { "type": "clear_form_by_id", "form": "hello_app_form" "inner_form": "inner_hello_app_form" }
break_scenario

BreakScenarioAction — это action, который позволяет завершить обработку сценария. На момент вызова этой команды отправляются только те сообщения, который уже были сгенерированы к этому моменту.

По умолчанию форма сценария не очищается. Для этого необходимо использовать action clear_current_scenario.

{ "type": "break_scenario" }
remove_form_field

RemoveFormFieldAction — это action, который позволяет очистить значения из конкретного поля в форме.

Используемые параметры:

  • form — string. Название композитной формы.
  • field — string. Название поля в форме.
{ "type": "remove_form_field", "form": "form_name", "field": "field_name" }
remove_composite_form_field

RemoveCompositeFormFieldAction — это action, который позволяет очистить значения из конкретного поля в композитной форме.

Используемые параметры:

  • form — string. Название композитной формы.
  • inner_form — string. Название вложенной формы.
  • field — string. Название поля в форме
{ "type": "remove_composite_form_field", "form": "form_name", "inner_form": "inner_form_name", "field": "field_name" }
run_scenario

RunScenarioAction — это action, который позволяет запустить конкретный сценарий. При запуске сценария на вход подается входящее сообщение.

Используемые параметры:

  • scenario — string. Название сценария.
{ "type": "run_scenario", "scenario": "hello_app_scenario" }
fill_field

FillFieldAction — это action, который позволяет заполнить поле формы  данными.

Используемые параметры:

  • form — string. Название композитной формы.
  • field — string. Название поля в форме.
  • data_path — string. Данные для заполнения формы. В качестве данных можно указать конкретное значение (число, строка и т.д.) или использовать обращение через Jinja шаблоны.
{ "type": "fill_field", "form": "form_name", "field": "field_name", "data_path": "data" }
composite_fill_field

CompositeFillFieldAction — это action, который позволяет заполнить поле формы, которая входит в композитную форму данными.

Используемые параметры:

  • form — string. Название композитной формы.
  • inner_form — string. Название вложенной формы.
  • field — string. Название поля в форме.
  • data_path — string. Данные для заполнения формы. 

В качестве данных в data_path можно указать конкретное значение (число, строка и т.д.) или использовать обращение через Jinja шаблоны.

{ "type": "composite_fill_field", "form": "form_name", "internal_form": "internal_form_name", "field": "field_name", "data_path": "data" }
clear_scenario_by_id

ClearScenarioByIdAction — это action, который позволяет удалить сценарий из списка активный сценариев (LastScenarios). Это также приводит к удалению формы, которая соответствует данному сценарию.

Используемые параметры:

  • scenario_id — string. Название сценария.
{ "type": "clear_scenario_by_id", "scenario_id": "SF_Vacations" }
run_last_scenario

RunLastScenario — это action, который запускает последний сценарий, который содержится в списке активных сценариев (LastScenarios). Параметр позволяет продолжить сценарий после получения ответа от внешней системы.

{ "type": "run_last_scenario" }

reset_current_node

ResetCurrentNodeAction — это action, который меняет активную ноду. Актуально для древовидных сценариев.

Используемые параметры:

  • node_id — string. Название ноды.
{ "type": "reset_current_node", "node_id": "your_node_id" }
clear_current_scenario

ClearCurrentScenarioAction — это action, который удаляет форму текущего сценария и сам сценарий из списка активных сценариев (LastScenarios).

{ "type": "clear_current_scenario" }
clear_current_scenario_form

ClearCurrentScenarioFormAction — это action, который очищает форму текущего сценария.

{ "type": "clear_current_scenario_form" }
choice_scenario

ChoiceScenarioAction — это action, который итерируется по списку сценариев в поле scenarios и проверяет, выполняется ли условие в поле requirement на запуск конкретного сценария. Если условие выполняется, сценарий запускается, и остальные сценарии не рассматриваются. Если условие не выполняется, тогда запускается action из поля else_action. Это опциональное поле. ChoiceScenarioAction – универсальный action для выбора сценария.

{ "type": "choice_scenario", "scenarios": [ { "scenario": {...}, "requirement": {...} }, ... { "scenario": {...}, "requirement": {...} } ], "else_action": {} }

Actions для интеграции с внешними системами

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

НазваниеОписание
save_behavior

SaveBehaviorAction —  это action, который создает обработчик исходящего запроса во внешнюю систему — Behavior. 

Используемые параметры:

  • check_scenario — bool. Включение проверки на совпадение сценария в момент отправки запроса и получения ответа. Является необязательным. По умолчанию имеет значение true. Влияет на состояние параметра behavior.
  • behavior — string. Название behavior, который определен по пути references/behaviors/behaviors.json.
{ "type": "save_behavior", "check_scenario": true, "behavior": "behavior_name" }
self_service_with_state

SelfServiceActionithState — это action, который позволяет создать запрос во внешнюю систему и добавить к этому запросу behavior. Используется, если интеграция с внешними системами происходит по kafka.

Используемые параметры:

  • behavior — string. Название behavior, который определен по пути references/behaviors/behaviors.json.
  • command_action — dictionary. Словарь с отправляемыми данными — message_name, тип соединения, топик kafka.
  • command — string. Message_name, который будет отправляться во внешнюю систему.
  • request_type — string. Тип интеграции (kafka). 
  • request_data — dictionary. Словарь с описанием топиков kafka.
  • topic_key — string. Ключ, по которому берется топик kafka из настроек publisher.
  • kafka_key — string. Опциональный параметр. Позволяет выбрать набор настроек publishers, в котором нужно искать topic_key.
  • nodes — dictionary. Словарь с параметрами, которые будут отправлены в поле payload. 
              "type": "self_service_with_state", "behavior": "common_behavior", "command_action": { "command": "INTEGRATIO_REQUEST", "request_type": "kafka", "request_data": { "topic_key": "integration" }, "nodes": { "uniqueId": "test123" } }
proccess_behavior

ProcessBehavior — это action, который позволяет запустить обработку behavior. Callback_id, по которому выбирается behavior, берется из входящего сообщения.

{ "type": "process_behavior" }
http_request

HTTPRequestAction — это action, который позволяет выполнить http запросы к внешним системам. Выполняется синхронно. 

Используемые параметры:

  • params — dictionary. Параметры для запроса.
  • method — string. Метод запроса get / post / etc.
  • url — string. Адрес для запроса. Здесь также можно указать произвольные параметры, которые будут переданы при запросе.
  • store — string. Название переменной, по которой будет сохранен результат запроса — можно получить через user.variables.
  • behavior — string. Название behavior, который будет выполнен после запроса.
{ "type": "http_request", "behavior": "some_behavior", "params": { "url": "http://127.0.0.1", "method": "post", "json": { "type": "unified_template", "template": "{{data|to_json}}" }, "some_request_param": 123 }, "store": "some_variable" }

Actions для взаимодействия с пользователем

Ниже представлено описание Actions, которые позволяют взаимодействовать с пользователем.

ТипОписание
string

StringAction — это action, который используется для отправки ответов на запрос пользователя.

Используемые параметры:

  • command — string. Название команды, которая будет отправлена пользователю в качестве ответа. Например: "ANSWER_TO_USER".
  • nodes — dictionary. Словарь с параметрами, которые будут отправлены в поле payload. Значения полей трактуются как Jinja шаблоны.
  • no_empty_nodes — bool. Если флаг == True, то поля, которые равны пустой строке, будут удалены из payload.
  • support_templates — dictionary. Значения полей трактуются как Jinja шаблоны.

Описание в DSL

    { "type": "string", "command": "ANSWER_TO_USER", "nodes": { "pronounceText": "", "items": [ { "bubble": { "text": "Чтобы прослушивать музыку, нужно авторизоваться в приложении Сбер" } } ], "finished": true } }

Результат выполнения

[ { "message_name": "RECHARGE_MOBILE", "payload": { "phone": "+79173213232", "amount": 100 } } ]
ask_againAskAgainAction — это action, который позволяет повторно задать вопрос пользователю из последнего сценария и поля, к которому был задан последний вопрос.

Описание в DSL

{ "type": "ask_again" }
sdk_answer

SDKAnswer — это action, который позволяет сформировать ответ для пользователя, где значения полей pronounceText, items и suggestions выбираются случайным образом. 
Внутри items поля bubble и card выбираются так же случайным образом. 

Используемые параметры:

  • command — string. Название команды, которая будет отправлена пользователю в качестве ответа. Например: "ANSWER_TO_USER"
  • nodes — dictionary. Словарь с параметрами, которые будут отправлены в поле payload. Значения полей трактуются как Jinja шаблоны.
  • no_empty_nodes — bool. Если флаг True, то поля, которые равны пустой строке, будут удалены из payload.
  • support_templates — dictionary. Значения полей трактуются как Jinja шаблоны.

Описание в DSL

{ "type": "sdk_answer", "pronounceText": [ "Вот.", "Смотрите." ], "items": [ { "bubble": { "text": [ "Вот.", "Смотрите.", "Пожалуйста." ] } }, { "card": { "type": "unified_template", "template": "{{ main_form.start.cards_to_show|tojson }}", "loader": "json" } } ], "suggestions": { "buttons": [ { "title": [ "Почему по карте минус" ], "action": { "text": "Почему по карте минус", "type": "text" } } ] } }
do_nothing

DoingNothingAction — это action, который формирует ответ пользователю, при этом никак не обрабатывая поле nodes.

Используемые параметры:

  • command — string. Название команды, которая будет отправлена пользователю в качестве ответа. Например: "ANSWER_TO_USER"
  • nodes — dictionary. Словарь с параметрами, которые будут отправлены в поле payload. Значения полей трактуются как Jinja шаблоны.

Описание в DSL

{ "type": "do_nothing", "command": "ANSWER_TO_USER", "nodes": { "text": "Попробуй еще" } }

Результат выполнения

[ { "message_name": "ANSWER_TO_USER", "payload": { "text": "Попробуй еще" } } ]
sdk_answer_to_user

SDKAnswerToUser - это action, который формирует ответ пользователю,

Используемые параметры:

  • static — dictionary. Словарь с произвольными данными, которые можно использовать внутри root, items, suggestions.
  • items — list. Список, который может содержать в себе следующие вложения:
    • bubble_text:
      • text — отображаемый текст;
      • markdown — необязательное поле, по умолчанию True.  Показывает, нужна ли обработка разметки markdown;
      • requirement — необязательное поле. Отвечает за отображение элемента массивов ответа
    • item_card:
      • text — содержимое карточки
  • root — list. Список который может содержать в себе следующие вложения:
    • pronounce_text:
      • text — произносимый текст
  • suggestions — list. Список словарей, который содержит в себе следующие вложения:
    • suggest_text:
      • title — заголовок suggest;
      • text — текст, который вводится при нажатии на suggest
    • suggest_deeplink:
      • title — заголовок suggest;
      • deep_link — диплинк, который откроется при нажатии на suggest
  • random_choice — опциональный массив. Состоит из набора словарей с переменными. Значения в словарях считаются jinja шаблонами. Итоговый словарь переменных — это словарь static, объединенный со случайно выбранным словарем из  random_choice

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

Пример запроса

{ "type": "sdk_answer_to_user", "static": { "ios_card": { "type": "list_card", "cells": [ { "ios_params": "ios" } ] }, "android_card": { "type": "list_card", "cells": [ { "android_params": "android" } ] }, "static_sg_dl": "www.www.www", "static_sg_text": "static suggest text" }, "random_choice": [ { "random_choice_greeting": "random text1" }, { "random_choice_greeting": "random text2" } ], "root": [ { "type": "pronounce_text", "text": "random_choice_greeting" } ], "items": [ { "type": "item_card", "text": "ios_card", "requirement": { "type": "external", "requirement": "OCTOBER_iOS" } }, { "type": "item_card", "text": "android_card", "requirement": { "type": "external", "requirement": "OCTOBER_android" } }, { "type": "bubble_text", "text": "random_choice_greeting" } ], "suggestions": [ { "type": "suggest_text", "text": "static_sg_text", "title": "tittle1" }, { "type": "suggest_deeplink", "title": "title2", "deep_link": "static_sg_dl" } ] }

Результат запроса

{ 'items': [ { 'card': { 'type': 'list_card', 'cells': [ { 'ios_params': 'ios' } ] } }, { 'bubble': { 'text': 'random text2', 'markdown': True } } ], 'suggestions': { 'buttons': [ { 'title': 'tittle1', 'action': { 'text': 'static suggest text', 'type': 'text' } }, { 'title': 'title2', 'action': { 'deep_link': 'www.www.www', 'type': 'deep_link' } } ] }, 'pronounceText': 'random text2' }
push

PushAction — это action, который позволяет отправлять push-уведомления на устройства пользователей.

Используемые параметры:

  • surface — поверхность, на которую будет передано уведомление. Необязательное поле. По умолчанию COMPANION. Возможные значения:
    • COMPANION — приложение Салют.
    • SBERBOX — устройство SberBox.
  • push_data — объект с данными уведомления.

    • notificationHeader — строка с заголовком уведомления. Может содержать Jinja-шаблон.
    • fullText — строка с текстом уведомления. Может содержать Jinja-шаблон.
    • mobileAppParameters — объект с параметрами для мобильных устройств.
      • DeeplinkAndroid — диплинк для Android-устройств.
      • DeeplinkIos — диплинк для iOS-устройств.
      • Logo — ссылка на логотип смартапа.

Описание в DSL

{ "type": "push", "surface": "COMPANION", "push_data": { "notificationHeader": "{% if day_time = 'morning' %}Время завтракать!{% else %}Хотите что нибудь заказать?{% endif %}", "fullText": "В нашем магазине большой ассортимент{% if day_time = 'evening' %}. Успей заказать!{% endif %}", "mobileAppParameters": { "DeeplinkAndroid": "deep_link_url", "DeeplinkIos": "deep_link_url", "Logo": "icon_url" } } }

Actions для взаимодействия с счетчиками

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

ТипОписание
counter_increment

CounterIncrementAction — это action, который позволяет увеличить значения счетчика key на значение value и задать время его жизни. Если счетчик key не существует, то с вызовом этого Action он будет создан.

Используемые параметры:

  • key — string. Название счетчика.
  • value — integer. Значение, на которое будет увеличен счетчик. По умолчанию равен 1.
  • lifetime — integer. Время жизни счетчика, выраженное в секундах.  
{ "type": "counter_increment", "key": "greeting" }
counter_decrement

CounterDecrementAction — это action, который позволяет уменьшить значения счетчика key на значение value и задать время его жизни. Если счетчик key не существует, то с вызовом этого Action он будет создан.

Используемые параметры:

  • key — string. Название счетчика.
  • value — integer. Значение, на которое будет увеличен счетчик. По умолчанию равен 1.
  • lifetime — integer. Время жизни счетчика, выраженное в секундах.  
{ "type": "counter_decrement", "key": "greeting" }
counter_clear

CounterClearAction — это action, который позволяет удалить счетчика по ключу key.

Используемые параметры:

  • key — string. Название счетчика.
{ "type": "counter_clear", "key": "greeting" }
counter_set

CounterSetAction — это action, который позволяет произвести настройку счетчика.

Используемые параметры:

  • key — string. Название счетчика.
  • value — integer. Значение, на которое необходимо изменить значение счетчика key. Опциональный параметр. 
  • time_shift — integer. Время, на которое необходимо изменить значение времени счетчика key. Опциональный параметр, по умолчанию 0. Значение указывается в секундах.
  • reset_time — bool. Необходимо ли изменить время создания счетчика на значение time с применением time_shift. Опциональный параметр, по умолчанию false.
{ "type": "counter_set", "key": "msgs_failed_before_operator_switch", "value": 1, "time_shift": 0, "reset_time": false }
counter_copy

CounterCopyAction — это action, который позволяет создать копию уже существующего счетчика.

Используемые параметры:

  • source — string. Название счетчика для копирования.
  • destination — string. Название нового счетчика.
  • time_shift — integer. Время, на которое необходимо изменить значение времени счетчика key. Опциональный параметр, по умолчанию 0. Значение указывается в секундах.
  • reset_time — bool. Необходимо ли изменить время создания счетчика на значение time с применением time_shift. Опциональный параметр, по умолчанию false.
{ "type": "counter_copy", "source": "counter_name_1", "destination": "counter_name_2", "time_shift": 0, "reset_time": false }

Actions для взаимодействия с переменными

Ниже представлено описание Actions, которые позволяют взаимодействовать с переменными.

ТипОписание
set_variable

SetVariableAction — это action, который позволяет сохранить в поле user.variables переменную с произвольным значением и временем жизни.

Используемые параметры:

  • key — string. Ключ, под которым будет храниться переменная.
  • value — string. Произвольное значение, которое будет храниться по ключу. Может быть передано в виде словаря типа "unified_template", для использования jinja.
  • loader — string. Может принимать значение json, float, int. Используется для приведения значения к определенному типу.
  • ttl — string. Время жизни переменной, выраженное в секундах. По умолчанию равно 86400 сек.
{ "type": "set_variable", "key": "greeting", "value": "Привет!" }
delete_variable

DeleteVariableAction — это action, который позволяет удалить переменную по ключу.

Используемые параметры:

  • key — string. Ключ, по которому будет произведено удаление.
{ "type": "delete_variable", "key": "greeting" }
clear_variables

ClearVariablesAction — это action,  который позволяет удалить все переменные.

{ "type": "clear_variables" }
set_local_variable

set_local_variable — это action, который позволяет сохранить в поле user.local_vars переменную с произвольным значением, доступную только в рамках текущего messageID.

Используемые параметры:

  • key — string. Ключ, под которым будет храниться переменная.
  • value — string. Произвольное значение, которое будет храниться по ключу. Может быть передано в виде словаря типа "unified_template" для использования jinja.
  • loader — string. Может принимать значение json, float, int. Используется для приведения значения к определенному типу.
{ "type": "set_local_variable", "loader": "float", "key": "x", "value": "5" }

Как передавать элементы интерфейса в Actions

Чтобы в ответ на запрос пользователя передать текст или карточки, используйте StringAction и команду ANSWER_TO_USER в Actions для взаимодействия с пользователем.

Пример Action для отправки текста:

"action": {
"type": "string",
"command": "ANSWER_TO_USER",
"nodes": {
"pronounceText": "Пример отправки текста",
"items": [
{
"bubble": {
"text": "*Пример текста",
"markdown": true,
"expand_policy": "auto_expand"
}
}
]
}
}

Пример Action для отправки карточек с помощью jinja-шаблона и без него:

"action": {
"type": "string",
"command": "ANSWER_TO_USER",
"nodes": {
"pronounceText": "Пример отправки карточек с помощью jinja-шаблона и без него",
"items": [
{
"card": {
"type": "unified_template",
"template": "{{main_form.start.card|tojson}}",
"loader": "json"
}
},
{
"card": {
"type": "list_card",
"cells": [
{
"type": "left_right_cell_view",
"paddings": {
"left": "8x",
"top": "8x",
"right": "8x",
"bottom": "8x"
},
"left": {
"type": "simple_left_view",
"texts": {
"title": {
"text": "Рассказать о возможностях смартаппа",
"typeface": "body1",
"text_color": "default"
},
"subtitle": {
"text": "Помощь и инструкции",
"typeface": "footnote1",
"text_color": "secondary",
"max_lines": 0,
"margins": {
"top": "2x"
}
}
}
},
"right": {
"type": "disclosure_right_view"
},
"actions": [
{
"type": "text",
"text": "Возможности смартаппа"
}
]
}
]
}
}
]
}
}
note

Описание формата объектов bubble и card ищите в разделе Карточки и тексты.

Как добавить свой Action

Для добавления своего Action необходимо:

  1. Реализовать новый Action, производный от класса core.basic_models.actions.basic_actions.Action. При этом необходимо реализовать метод run, который будет вызван при запуске:
    def run(self, user: BaseUser, text_preprocessing_result: BaseTextPreprocessingResult,
    params: Optional[Dict[str, Union[str, float, int]]] = None) -> Optional[List[Command]]:
  2. Разместить новый Action в app/basic_entities/actions.py своего смартапа, либо создать новый модуль в удобном месте.
  3. Зарегистрировать Action в классе CustomAppResources для вызова по названию:
    CustomAppResources по пути /app/resources/custom_app_resources.py внутри метода init_actions
    class CustomAppResources(SmartAppResources):
    def init_actions(self):
    super(CustomAppResourses, self).init_actions()
    actions["simple_action"] = SamlpeAction
  4. Добавить unit-test на новый Action.
Обновлено 15 июня 2022

Заметили ошибку?

Выделите текст и нажмите Ctrl + Enter, чтобы сообщить нам о ней