Lua в АИПЛАН

АИПЛАН — система управления проектами и задачами. В неё встроен движок пользовательских Lua-скриптов: администратор проекта описывает бизнес-правила на Lua, и АИПЛАН проверяет их при каждом изменении задачи.

Как это устроено

  • Скрипт хранится в настройках проекта (поле rules_script, до 10 000 символов) — один скрипт на проект.
  • Движок — gopher-lua (реализация Lua 5.1 на Go). Он работает внутри сервера АИПЛАН: отдельно устанавливать Lua не нужно.
  • При событии АИПЛАН вызывает одноимённую функцию из скрипта. Если функции нет, событие проходит без проверки.
  • Скрипт возвращает таблицу: { status = true } — действие разрешено; { status = false, error = "текст" } — запрещено, текст увидит пользователь.
  • Код выполняется в песочнице: таймаут 10 секунд; отключены os, io, require, loadfile, dofile, debug, coroutine, package, net, socket, lfs, ffi.

События (хуки)

  • BeforeStatusChange(ctx, newstatus) — перед сменой статуса задачи.
  • AfterStatusChange(ctx, newstatus) — после смены статуса.
  • BeforeAssigneesChange(ctx, assignees) — перед изменением исполнителей.
  • BeforeWatchersChange(ctx, watchers) — перед изменением наблюдателей.
  • BeforeLabelsChange(ctx, labels) — перед изменением меток.
  • BeforeIssuePropertyChange(ctx, property) — перед изменением кастомного поля; property — таблица { name, type, old_value, new_value }.

Что доступно в скрипте

Первый аргумент ctx содержит таблицы:

  • ctx.user — кто выполняет действие (email, name и другие поля пользователя);
  • ctx.status — текущий статус задачи (name, group);
  • ctx.project и ctx.space — проект и рабочее пространство;
  • ctx.issue — задача, включая attachment_count — число вложений;
  • ctx.properties — список кастомных полей вида { name, type, value }.

Методы контекста:

  • ctx:getProp("Имя поля") — значение кастомного поля по имени (nil, если поле не заполнено);
  • ctx:compareUserEmail("user@example.com") — сравнить email инициатора;
  • ctx:compareStatusName("Готово") — сравнить текущий статус.

Для списков есть проверки: assignees:checkEmail("...") и watchers:checkEmail("...") — есть ли пользователь с таким email; labels:checkName("...") — есть ли метка с таким именем. Функция print(...) пишет сообщение в журнал правил проекта.

Примеры

Запрет закрывать задачу без вложений:

function BeforeStatusChange(ctx, newstatus)
  if newstatus.group == "completed" and ctx.issue.attachment_count == 0 then
    return { status = false, error = "Нельзя закрыть задачу без вложений" }
  end
  return { status = true }
end

Обязательное кастомное поле при переводе в «Готово»:

function BeforeStatusChange(ctx, newstatus)
  if newstatus.name == "Готово" and ctx:getProp("Резолюция") == nil then
    return { status = false, error = "Заполните поле «Резолюция»" }
  end
  return { status = true }
end

В исполнителях всегда должен оставаться руководитель проекта:

function BeforeAssigneesChange(ctx, assignees)
  if not assignees:checkEmail("pm@example.com") then
    return { status = false, error = "В исполнителях должен быть PM" }
  end
  return { status = true }
end

Как включить

Скрипт задаётся через API АИПЛАН администратором проекта:

PUT /api/auth/workspaces/{workspaceSlug}/projects/{projectId}/rules-script/
Content-Type: application/json

{"rules_script": "function BeforeStatusChange(ctx, newstatus) ... end"}
  • GET — прочитать скрипт, DELETE — удалить;
  • POST /api/auth/workspaces/{workspaceSlug}/projects/{projectId}/rules-log/ — журнал выполнения;
  • доступ — только у администраторов проекта.

В текущем веб-интерфейсе АИПЛАН отдельного редактора правил нет: скрипт задаётся через API. Исходники движка — в репозитории github.com/aisa-it/aiplan, пакет pkg/rules.