Typescript SDK

Typescript SDK это библиотека для работы с системой QuickBPM из пользовательских сценариев.

Что такое сценарии

Сценарии QuickBPM это мощный и гибкий инструмент настройки системы под нужды компании. С помощью сценариев можно имплементировать сложную логику работы с объектами системы. Сценарий представляется собой набор функций, написанных на языке Typescript. Каждый сценарий имеет контекст запуска, а также доступ к другим глобальным константам.

Где можно использовать сценарии

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

Серверные

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

Внимание! Исполнение серверных сценариев ограничено по времени. Если сценарий не завершился в течении минуты, то он прерывается.

Клиентские

Клиентские сценарии на данный момент доступны при создании страниц с помощью конструктора.

Что можно сделать с помощью сценариев

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

async function createOrder() {
    // Корзина заказа
    const items = await Context.fields.items.fetchAll();
    // Стоимость корзины
    const total = items
        .map(item => item.price)
        .reduce((acc, price) => acc.add(price));
    // Создадим заказ
    const order = Context.fields.order.app.create();
    order.data.agent = Context.data.__createdBy;
    // Применим скидку
    order.data.total = total.multiply(0.75);
    // Сохраним заказ
    await order.save();
    Context.data.order = order;
    // Отправим идентификатор заказа на сайт
    await fetch(`https://my-store.ru/orders/${ Context.data.storeId }`, {
        method: 'PATCH',
        headers: {
            Authorization: 'bearer MY-SECRET-TOKEN',
        }
        body: JSON.stringify({
            QBPMOrderId: order.data.__id,
        }),
    });
}

Принципы работы

Сценарий представляет из себя файл с Typescript кодом. Работа над сценарием производится во встроенном редакторе, основанном на Monaco, в котором добавляются файлы определения Typescript SDK для автодополнения. При публикации процесса или виджета сценарии валидируются и транспилируются в javascript, который и будет исполняться в дальнейшем.

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

Async / await

Важным моментом при работе сценариев является то, что многие операции являются асинхронными: получение элемента по ссылке, сохранение элемента, работа с внешними сервисом. Такие методы возвращают Promise. Promise (в переводе с англ. «Обещание»), является обещанием того, что мы получим результат нашей операции. Если же нам необходим результат нашей операции для совершения других действий, то нужно дождаться этого «обещания». Для лучшей читаемости кода, мы рекомендуем использовать синтаксис async/await.

Синтаксис:

  • async - обозначает, что в функции будут происходить асинхронные операции
  • await - гарантирует, что promise("Обещание"), будет выполнено и мы получим результат нашей асинхронной операции

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

// Ставим перед нашей функцией async обозначая, что в ней будут происходить асинхронные операции
async function getUsers(): Promise<UserItem> {
    // метод fetchAll является асинхронным методом, поэтому ставим await, чтобы дождаться результата
    // если не дождаться результата, то скрипт будет выполняться дальше 
    // и вернет нам объект типа Promise, а не массив пользователей
    const users = await Context.fields.users.fetchAll();
    const currentUser = users.find((user: UserItem) => user.data.email === Context.data.email);
    return currentUser;
}

Promise.all

Promise.all преобразует массив «обещаний» в «обещание» массива результатов, другими словами позволяет дождаться пока все promise, переданные в эту конструкцию, разрешатся и возвращает массив результатов их выполнения. Допустим нужно скачать несколько файлов со сторонних ресурсов:

async function loadFiles(): Promise<Array<ApplicationItem<T>>> {
    // Получаем массив праметров для загрузки файлов типа приложение 
    const filesParams = await Context.fields.filesParams!.fetchAll();
    // посредством метода массива map получаем массив промисов
    const promises = filesParams.map(async (fileParams: Array<ApplicationItem<T>>) => {
        // в соответствующие переменные записываем соответствующие значения массива,
        // которые возвращает Promise.all
        const [name, link] = Promise.all([
            await fileParams.data.name!.fetch(),
            await fileParams.data.link!.fetch()
        ]);
        // создает файл в контексте процесса с именем по индексу
        Context.fields.file.createFromLink(`${ name }`, `${ link }`);
    });
    // для получения загруженных файлов в конструкцию Promise.all помещаем полученный массив промисов
    // дожидаемся получения результата
    const files = await Promise.all(promises);
    // возвращаем массив загруженных файлов для дальнейшего использования
    return files;
}

Non-Null Assertion Operator (!)

Это постфикс оператор, который в дословном переводе означает «оператор ненулевого утверждения», то есть при установке данного оператора после выражения, мы утверждаем, что данное выражение не может быть null и undefined. В связи с этим, пользоваться "!" при моделировании решения нужно осторожно, зная, что к моменту выполнения скрипта поле точно не будет null или undefined, иначе скрипт упадет на этой строчке.

// Посмотрим на стандартный пример получения элемента приложения.
// Конструкцию app! ознаает, app не может быть null или undefined и можно ее использовать    
const app = await Context.data.app!.fetch();
app.data.file = Context.data.file;
await app.save();
// Если есть вероятность, что поле будет null или undefined к моменту выполнения скрипта,
// лучше добавлять проверки с помощью условий
deal.data.outstanding = (deal.data.contract_amount || new Money(0, 'RUB').add(deal.data.received_payments.multiply(-1));
// Без проверки, такая строчка была бы написана вот так:
deal.data.outstanding = new Money(deal.data.contract_amount!.asFloat() - deal.data.received_payments.asFloat(), 'RUB' );

Структура справки

Справка состоит из следующих разделов:

  • Типы объектов — раздел, в котором описываются интерфейсы объектов, доступные из сценариев.
  • Типы данных — раздел, посвящённый базовым типам системы QuickBPM.
  • Глобальные константы — список доступных глобальных констант, доступных в рамках сценариев.
  • Работа с приложениями — раздел, в котором рассматриваются способы работы с элементами приложений, такие как создание, поиск, изменение и удаление.
  • Работа с внешними сервисами — описание функции fetch, с помощью которой можно отправлять HTTP-запросы во внешние системы.
  • Примеры — раздел форума с примерами и ответами на вопросы.