Pull to refresh

Пошаговая настройка веб-сервисов в OTRS 5 в качестве запрашивающего

Reading time5 min
Views8.9K
Ранее я писал как можно настроить 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 момента:

  1. Если у вас не отправляются запросы и в логах “Got no TicketNumber”, то вам нужно на сервере найти файл Test.pm и везде поменять “TicketNumber” на “TicketID”. В моем случае он лежал тут — /opt/otrs/Kernel/GenericInterface/Invoker/Test.
    Спасибо этим ребятам за совет.
  2. 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;
            }
        }


Вот, собственно, и весь секрет. :)
Tags:
Hubs:
Total votes 3: ↑3 and ↓0+3
Comments6

Articles