Ранее я писал как можно настроить OTRS в роли провайдера. Это когда любая система может обратиться к OTRS и запросить данные.
Сейчас же я опишу, как произвести настройку OTRS в роли запрашивающего (requester). Когда в OTRS происходит какое-то событие и после этого идет обращение к внешней системе. А так же проблемы, с которыми столкнулся. Если заинтересовало, то прошу под кат.

Итак, установили мы замечательный OTRS, начали в нем работать. Но захотелось от системы большего. В нашем случае — интеграции с телеграм ботом. Кейс следующий: если приходит тикет от ВИП пользователя, то бот должен сразу проинформировать об этом ответственных лиц.
А для этого надо, чтобы система сама могла с ним общаться и сообщать о приходе таких тикетов.
В самом начале нам надо включить invoker. По умолчанию их всего два, и как сделать больше, я не разбирался. Для задачи и одного вполне хватает.
Для этого заходим в “Конфигурация системы” и далее ищем.
Edit Config Settings in GenericInterface -> GenericInterface::Invoker::ModuleRegistration

Просто включаем, ничего менять не надо.
Далее переходим в администрирование — > веб-сервисы.


Создаем новый веб сервис.
Вписываем название интерфейса
Выбираем сетевой транспорт HTTP::REST в блоке «OTRS как запрашивающий».
Жмем “Сохранить”.

После сохранения есть возможность выбрать Invoker и настроить транспорт.
Invoker у нас один, его и выбираем.

После добавления invoker вам сразу предложат его настроить.
Имя — тут все понятно
Дальше две мапы параметров. Я не уверен, что они в принципе работают, поэтому просто настроил пробрасывать как есть.
Триггер события — я выбрал по созданию тикета. Важно — не забудьте нажать плюсик. Только после этого ваш триггер добавится. Я минут 5 разбирался, почему после перезахода в настройки не видел своего триггера.
Триггер может быть синхронным и асинхронным. Если синхронный, то пока OTRS не получит ответ, вы не сможете дальше работать с тикетом. Асинхронный — OTRS отправит запрос в фоне.
Пример проблемы синхронных вызовов:
Настроили триггер на смену сервиса в тикете.
Открываем в тикете окно смены сервиса, меняем сервис.
Пока внешняя система не даст ответ, окно не закроется. Хотя должно закрываться сразу.

Далее настраиваем транспорт

Указываете адрес принимающей системы с портом. В следующей строке имя сервлета и TicketID. Согласно инструкции можно передавать еще и другие параметры от тикета, но у меня это не получилось. Поэтому все что мы имеем — номер тикета. Об этом чуть ниже.

На этом настройки закончены. При наступлении события наша внешняя система получила запрос.

За время настройки всплыло 2 момента:
Если у кого-то есть интересные замечания, комментарии — welcome :)
UP
Спасибо Mexonizator за ценное дополнение.
В 2020 году проблема остаётся актуальной — переменные, кроме TicketID, не резолвятся в строке запроса. Инфы в инете как не было, так и нет — ни в нашем, ни в зарубежном.
1. Прежде всего, как и сказано в комментах, надо добавить поля в Invoker'е:
Функция берёт из входных данных TicketID и делает запрос к базе на предмет нужного нам кастомного поля и отправляет всё это дальше.
2. Затем идём в /opt/otrs/Kernel/GenericInterface/Transport/HTTP/REST.pm. Нас интересует код начиная с:
Как видно, скрипт берёт строку GET-запроса, которую указали в гуе в настройках веб-сервиса, ищет знак вопроса и разбивает на две части. Далее обработка происходит в три этапа:
1) Строка до знака вопроса;
2) После него;
3) И дополнительные параметры, которые добавляются при соблюдении некоторых условий.
Нам здесь важна главным образом регулярка. Она активно используется на первых двух этапах, и именно из-за неё переменные не резолвятся.
Регулярка ожидает на входе не просто :VAR, а :VAR&, :VAR$ и т.п. в зависимости от участка кода. Соответственно, совпадения не происходит и переменная не резолвится.
Лечится изменением регулярки в зависимости от запроса к веб-сервису. Например, при такой регулярке (см. ниже) пройдёт вот такой запрос:
/search?:CustomField-:TicketID
Вот, собственно, и весь секрет. :)
Сейчас же я опишу, как произвести настройку OTRS в роли запрашивающего (requester). Когда в OTRS происходит какое-то событие и после этого идет обращение к внешней системе. А так же проблемы, с которыми столкнулся. Если заинтересовало, то прошу под кат.

Итак, установили мы замечательный OTRS, начали в нем работать. Но захотелось от системы большего. В нашем случае — интеграции с телеграм ботом. Кейс следующий: если приходит тикет от ВИП пользователя, то бот должен сразу проинформировать об этом ответственных лиц.
А для этого надо, чтобы система сама могла с ним общаться и сообщать о приходе таких тикетов.
В самом начале нам надо включить invoker. По умолчанию их всего два, и как сделать больше, я не разбирался. Для задачи и одного вполне хватает.
Для этого заходим в “Конфигурация системы” и далее ищем.
Edit Config Settings in GenericInterface -> GenericInterface::Invoker::ModuleRegistration

Просто включаем, ничего менять не надо.
Далее переходим в администрирование — > веб-сервисы.


Создаем новый веб сервис.
Вписываем название интерфейса
Выбираем сетевой транспорт HTTP::REST в блоке «OTRS как запрашивающий».
Жмем “Сохранить”.

После сохранения есть возможность выбрать Invoker и настроить транспорт.
Invoker у нас один, его и выбираем.

После добавления invoker вам сразу предложат его настроить.
Имя — тут все понятно
Дальше две мапы параметров. Я не уверен, что они в принципе работают, поэтому просто настроил пробрасывать как есть.
Триггер события — я выбрал по созданию тикета. Важно — не забудьте нажать плюсик. Только после этого ваш триггер добавится. Я минут 5 разбирался, почему после перезахода в настройки не видел своего триггера.
Триггер может быть синхронным и асинхронным. Если синхронный, то пока OTRS не получит ответ, вы не сможете дальше работать с тикетом. Асинхронный — OTRS отправит запрос в фоне.
Пример проблемы синхронных вызовов:
Настроили триггер на смену сервиса в тикете.
Открываем в тикете окно смены сервиса, меняем сервис.
Пока внешняя система не даст ответ, окно не закроется. Хотя должно закрываться сразу.

Далее настраиваем транспорт

Указываете адрес принимающей системы с портом. В следующей строке имя сервлета и TicketID. Согласно инструкции можно передавать еще и другие параметры от тикета, но у меня это не получилось. Поэтому все что мы имеем — номер тикета. Об этом чуть ниже.

На этом настройки закончены. При наступлении события наша внешняя система получила запрос.

За время настройки всплыло 2 момента:
- Если у вас не отправляются запросы и в логах “Got no TicketNumber”, то вам нужно на сервере найти файл Test.pm и везде поменять “TicketNumber” на “TicketID”. В моем случае он лежал тут — /opt/otrs/Kernel/GenericInterface/Invoker/Test.
Спасибо этим ребятам за совет. - OTRS по наступлению события отправляет только id тикета. Заявлено, что можно передавать и другие параметры. Однако это так и не удалось. В результате при наступлении события внешняя система получает TicketID, и по нему сама обращается в OTRS за полной информацией. Ребята с форума приблизительно так же и поступили.Это плодит дополнительные обращения, но в нашем случае не критично.
Если у кого-то есть интересные замечания, комментарии — welcome :)
UP
Спасибо Mexonizator за ценное дополнение.
В 2020 году проблема остаётся актуальной — переменные, кроме TicketID, не резолвятся в строке запроса. Инфы в инете как не было, так и нет — ни в нашем, ни в зарубежном.
1. Прежде всего, как и сказано в комментах, надо добавить поля в Invoker'е:
package Kernel::GenericInterface::Invoker::Test::Test;
use strict;
use warnings;
use JSON::XS;
use Kernel::System::VariableCheck qw(IsString IsStringWithData);
use Kernel::System::ObjectManager;
local $Kernel::OM = Kernel::System::ObjectManager->new();
...
sub PrepareRequest {
my ( $Self, %Param ) = @_;
# we need a TicketID
if ( !IsStringWithData( $Param{Data}->{TicketID} ) ) {
return $Self->{DebuggerObject}->Error( Summary => 'Got no TicketID' );
}
my %ReturnData;
my $TicketID = $Param{Data}->{TicketID};
# Getting the fields' objects from OTRS
my $BackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField');
# Getting CustomField field properties
my $CustomFieldFieldConfig = $DynamicFieldObject->DynamicFieldGet(
Name => 'CustomField',
);
# Getting CustomField field value in a given ticket
my $CustomFieldFieldValue = $BackendObject->ValueGet(
DynamicFieldConfig => $CustomFieldFieldConfig,
ObjectID => $TicketID,
UserID => 1,
);
# Data in the request to a web-service
$ReturnData{TicketID} = $TicketID;
$ReturnData{CustomField} = $CustomFieldFieldValue;
Функция берёт из входных данных TicketID и делает запрос к базе на предмет нужного нам кастомного поля и отправляет всё это дальше.
2. Затем идём в /opt/otrs/Kernel/GenericInterface/Transport/HTTP/REST.pm. Нас интересует код начиная с:
my @RequestParam;
my $Controller = $Config->{InvokerControllerMapping}->{ $Param{Operation} }->{Controller};
# Remove any query parameters that might be in the config,
# For example, from the controller: /Ticket/:TicketID/?:UserLogin&:Password
# controller must remain /Ticket/:TicketID/
$Controller =~ s{([^?]+)(.+)?}{$1};
Как видно, скрипт берёт строку GET-запроса, которую указали в гуе в настройках веб-сервиса, ищет знак вопроса и разбивает на две части. Далее обработка происходит в три этапа:
1) Строка до знака вопроса;
2) После него;
3) И дополнительные параметры, которые добавляются при соблюдении некоторых условий.
Нам здесь важна главным образом регулярка. Она активно используется на первых двух этапах, и именно из-за неё переменные не резолвятся.
# Replace any URI params with their actual value.
# for example: from /Ticket/:TicketID/:Other
# to /Ticket/1/2 (considering that $Param{Data} contains TicketID = 1 and Other = 2).
for my $ParamName ( sort keys %{ $Param{Data} } ) {
if ( $Controller =~ m{:$ParamName(?=/|\?|$)}msx ) {
my $ParamValue = $Param{Data}->{$ParamName};
$ParamValue = URI::Escape::uri_escape_utf8($ParamValue);
$Controller =~ s{:$ParamName(?=/|\?|$)}{$ParamValue}msxg;
push @ParamsToDelete, $ParamName;
}
}
if ($QueryParamsStr) {
# Replace any query params with their actual value
# for example: from ?UserLogin:UserLogin&Password=:Password
# to ?UserLogin=user&Password=secret
# (considering that $Param{Data} contains UserLogin = 'user' and Password = 'secret').
my $ReplaceFlag;
for my $ParamName ( sort keys %{ $Param{Data} } ) {
if ( $QueryParamsStr =~ m{:$ParamName(?=&|$)}msx ) {
my $ParamValue = $Param{Data}->{$ParamName};
$ParamValue = URI::Escape::uri_escape_utf8($ParamValue);
$QueryParamsStr =~ s{:$ParamName(?=&|$)}{$ParamValue}msxg;
push @ParamsToDelete, $ParamName;
$ReplaceFlag = 1;
}
}
Регулярка ожидает на входе не просто :VAR, а :VAR&, :VAR$ и т.п. в зависимости от участка кода. Соответственно, совпадения не происходит и переменная не резолвится.
Лечится изменением регулярки в зависимости от запроса к веб-сервису. Например, при такой регулярке (см. ниже) пройдёт вот такой запрос:
/search?:CustomField-:TicketID
if ($QueryParamsStr) {
# Replace any query params with their actual value
# for example: from ?UserLogin:UserLogin&Password=:Password
# to ?UserLogin=user&Password=secret
# (considering that $Param{Data} contains UserLogin = 'user' and Password = 'secret').
my $ReplaceFlag;
for my $ParamName ( sort keys %{ $Param{Data} } ) {
if ( $QueryParamsStr =~ m{:$ParamName}msx ) {
my $ParamValue = $Param{Data}->{$ParamName};
$ParamValue = URI::Escape::uri_escape_utf8($ParamValue);
$QueryParamsStr =~ s{:$ParamName}{$ParamValue}msxg;
push @ParamsToDelete, $ParamName;
$ReplaceFlag = 1;
}
}
Вот, собственно, и весь секрет. :)