Примеры Lua-скриптов¶
Базовые запросы и действия:
Объявление глобальной переменной, работает в любой части кода
local TASK_ID = CONTEXT["Id"]
Объявление переменной, в которой выполняется SQL-запрос, возвращает таблицу
local dbData = SQL:query("select top 3 TaskId, description from tasks", {})
Вывод результата
var_dump(dbData)
--Полученный результат: Вывод: [{"description":"Test Demo","TaskId":2},{"description":"","TaskId":3},{"description":"","TaskId":4}]
Цикл на основании полученного результата (построчная обработка полученных данных)
for key, val in pairs(dbData) do
-- Объявление локальной переменной, которая будет работать и будет видима только в рамках данного цикла, ЗА ним (после end) данной переменной не существует
local res
if val["description"] == ''
-- Присваивание переменной res значения в зависимости от выполнения/невыполнения условия
then res = '123456'
elseif val["description"] == nil
then res = '87654'
-- Объединение значений в одно производится через две точки
else res = '123 ' .. val["description"]
end
end
Объявление таблицы
local params = {};
params['Task'] = '365522';
params['ChangingUser'] = '3';
params['TaskText'] = 'TEST';
params['BreakTaskLinkForAll'] = 'true';
--Вывод таблицы
--Вывод: {"ChangingUser":"3","TaskText":"TEST","Task":"365522","BreakTaskLinkForAll":"true"}
Объявление глобальных переменных
local COMMENT_CONTENT = "По Вашему сценарию снят спринт"
local ROBOT_ID = 3
local STATE_ID = 2
local TASK_ID = CONTEXT["Id"]
local OWNER_ID = CONTEXT["OwnerId"]
Выполняем действие по отправке комментария
SMART:execute_action('PostComment', TASK_ID, 'task', {
CommentAuthor = ROBOT_ID,
CommentText = COMMENT_CONTENT,
Recipients = OWNER_ID,
RecipCopies = nil,
CommentType = 3,
Task = TASK_ID,
ForcedEmail = false,
CommentSMS = false,
TextAsHTML = false,
NoSubscription = false,
MarkAsQuestion = false,
CommentVisibleOnlyToRealRecipients = true
});
Работа с ДП:
local USER_ID = 3;
local TASK_ID = CONTEXT["Id"]
--EXTPARAM_ID = 99;
-- Единичное изменение ДП
-- Объявление функции
function update_extparam (task, extparam, value)
SMART:execute_action(
'ChangeExtParamValue', task, 'task',
{
Task = task,
User = USER_ID,
ExtParam = extparam,
Value = value,
WriteCommentOnChange = false
}
);
return
end
-- Вызов функции изменения ДП
update_extparam(683059, 1238, '123')
-- Массовое изменение ДП
-- Функция массового обновления ДП
function massUpdateExtParams(task, value)
SMART:execute_action('MassChangeExtParamValue', task, 'task', {
User = 3,
Dest = task,
DestExtParams = value,
WriteCommentOnChange = false
})
end
-- Объявляем переменную (таблица), куда записываем ID ДП и значение для этого ДП
local MassChangeExtParamValue = {}
MassChangeExtParamValue =
{
{ExtParamId = 765, FixedValue = post.Impressions},
{ExtParamId = 768, FixedValue = post.Spent},
{ExtParamId = 764, FixedValue = post.Reposts},
{ExtParamId = 726, FixedValue = round(post.CTR, 2)},
{ExtParamId = 1792, FixedValue = post.Hide},
{ExtParamId = 1793, FixedValue = post.Report},
{ExtParamId = 1794, FixedValue = post.Links},
{ExtParamId = 1795, FixedValue = post.Clicks}
}
-- Вызов функции с передачей параметров
massUpdateExtParams(post_info.posttask, MassChangeExtParamValue)
-- Изменение ДП типа "Таблица"
-- Сбор данных, которыми будем заполнять таблицу
-- Вызываем процедуру расчета calculateMain. Она возвращает столбцы factHrs, cost и profit, а также служебный столбец rowId с номерами строк
local resource_data = SQL:query(
[[
exec [dbo].[calculateMain] @TaskId = @TaskId
]],
{
TaskId = TASK_ID
}
);
-- Записываем полученные значения в таблицу
for key, val in pairs(resource_data) do
local result = string.format(
[[={"First":%s,"Second":{"11":{"First":%s},"22":{"First":%s},"33":{"First":%s}}}]],
val.rowID, val.factHrs, val.cost, val.profit
);
update_extparam(TASK_ID, EXTPARAM_ID, result);
end
Заполнение Таблицы с колонкой "Мультифайл":
local TASK_ID = CONTEXT['Id']
-- Действие "Сгенерировать файл по шаблону"
function create_file(name, shablonid, konvert)
SMART:execute_action('GenerateByTemplate', TASK_ID, 'task', {
UploadingUserName = 3,
TargetFile = name,
TargetExternalParameter = 1212, -- ДП, в который вкладываем шаблон после формирования
TemplateId = shablonid, -- ID шаблона из вкладки "Дизайн — шаблоны файлов"
SmartParameter_DocxMode = true,
SmartParameter_ConvertFileTo = konvert -- Конвертация сгенерированного файла
})
return
end
function update_extparam (task, value)
SMART:execute_action(
'ChangeExtParamValue',
task,
'task',
{
Task = task,
User = 3,
ExtParam = 2819,
Value = value,
WriteCommentOnChange = false
}
);
return
end
--Формируем претензию
local pret_tab = SQL:query(
[[
select top 1 t.tasktext as [FileName],
t.taskid as ShablonTaskId,
123456 as shablonid,
'1' as rowid,
'pdf' as ras
from tasks t with (nolock)
where t.taskid = @contextid
]],
{
contextid = TASK_ID
}
)
--Создаем претензии
for key, val in pairs(pret_tab) do
create_file(val.FileName, val.shablonid, val.ras)
local file_id = SQL:scalar(
[[
select
top 1 f.fileid
from FileStorageFileToExtParamLinks f with(nolock)
join tasks t with(nolock) on t.taskid = f.TaskId
where f.extparamid = 1212
and t.taskid = @contextid and isdeleted = 0
order by f.versionid desc
]],
{
contextid = TASK_ID
}
)
--Добавляем файлы в таблицу
str = string.format(
'={"First":"%s","Second":{1175:{"First":"[{\\\"Name\\\":\\\"%s.%s\\\",\\\"FileId\\\":%s,\\\"PreuploadId\\\":null,\\\"Version\\\":null}"]}}',
val.RowID,
val.FileName,
val.Ras,
file_id
)
update_extparam(TASK_ID, str)
end
Обработка исключений и ошибок выполнения LUA-скриптов:
Для безопасного выполнения кода, который может вызвать ошибки, используйте функцию pcall. Оберните критические участки кода в pcall, а затем настройте логирование ошибок, например, запись в файл или добавление комментария в задачу.
Пример поиска ошибок при создании задачи:
local IdQ = CONTEXT.Id
local params = UTILS:json_decode(CONTEXT.Data)
local logId = math.floor(SQL:scalar([[
INSERT INTO [dbo].[cm_GATELog]
([PERIOD_DATE]
,[DOC_ID]
,[DEPARTMENT_ID]
,[DATE_BEGIN]
,[DATE_END]
,[tabstring]
,[mqID])
VALUES
(
@PERIOD_DATE
,@DOC_ID
,@DEPARTMENT_ID
,@DATE_BEGIN
,@DATE_END
,@tabstring
,@mqID)
SELECT IDENT_CURRENT('dbo.cm_GATELog')
]],
{
PERIOD_DATE = params.ep88931,
DOC_ID = params.ep89001,
DEPARTMENT_ID = params.ep89011,
DATE_BEGIN = params.ep89021,
DATE_END = params.ep89031,
tabstring = params.tabstring,
mqID = IdQ
} ))
function createTask (performerId, newtaskextparams, dateto, text)
res = SMART:execute_action("CreateTask", nil, "task",
{
Owner = 3,
Subcat = 39651,
TaskText = text,
CreateLink = false,
CreateSubtask = true,
Performers = performerId,
DueTime = dateto,
TaskStartTime = nil,
ExtParams = newtaskextparams,
NewTaskCopySubscribers = false,
CreateCopyFiles = false,
CopyParentText = false,
Priority = 1,
Notify = nil,
UsersToSubscribe = nil,
CreateCopiesForEachPerformer = false,
LinkFiles = nil,
AssignLetterWithTask = nil,
Confidentiality = nil,
LinkAsUser = nil
}
)
return res
end
local epString =
{
{ExtParamId = 88931, FixedValue = params.ep88931},
{ExtParamId = 89001, FixedValue = params.ep89001},
{ExtParamId = 89011, FixedValue = params.ep89011},
{ExtParamId = 89021, FixedValue = params.ep89021},
{ExtParamId = 89031, FixedValue = params.ep89031},
{ExtParamId = 88981, FixedValue = params.tabstring}
}
local success = true
local returnValue = ''
success, returnValue = pcall(createTask, 3, epString, nil, nil)
if not success then
SQL:scalar(
[[
update [cm_GATELog]
set
ErrorMsg = @error
where ID = @logId
]],
{logId=logId, error = tostring(returnValue.InnerException)})
else
SQL:scalar(
[[
update [cm_GATELog]
set
TaskID = @TaskID
where ID = @logId
]],
{logId=logId, TaskID = returnValue[0]})
end
Конструкция try...catch:
Функция pcall (f, arg1, ...) вызывает запрошенную функцию в защищенном режиме. Если какая-то ошибка возникает в функции f, она не выдает ошибку, а возвращает статус ошибки. Возвращается два значения: status (true — если функция f выполнилась без ошибок, false — если с ошибкой) и retval (результат функции f или сообщение об ошибке)
function updateExtParam(arg1, arg2, ...)
...
end
local status, retval = pcall( updateExtParam, arg1, arg2, ... )
if status then
var_dump("success");
var_dump(retval);
else
var_dump("failure");
var_dump(retval.InnerException.Message);
end;
HTTP запросы:
-- Заголовки запроса
local headers = {}
headers['Accept'] = "application/json"
headers['Content-Type'] = "application/json"
-- Параметры запроса
local params = {}
params['param1'] = "123"
params['param2'] = "строка"
-- строка body
local body = "текст тела сообщения"
--Запрос GET с заголовками и передачей параметров в URL
local response = HTTP:send_http_request("get", "адрес", params, headers, nil, nil)
local content = response['HttpResponse']['ResponseContent']
--Запрос POST с заголовками и телом запроса
local response = HTTP:send_http_request("post", "адрес", nil, headers, body, nil)
--Запрос POST с NTLM аутентификацией
local options = {
domain = '...',
password = '...',
userName = '...',
authType = 'NTLM'
}
local response = HTTP:send_http_request("post", "адрес", nil, nil, nil, options)
Примечание. Автоматический fallback на HTTP/1.1. Начиная с версии 2.267, HTTP-клиент для Lua-скриптов автоматически понижает версию протокола с HTTP/2 до HTTP/1.1, если целевой сервер или промежуточный прокси не поддерживает HTTP/2. Это устраняет ситуации, когда запрос возвращал пустой ответ при работе через корпоративные балансировщики. Ручная передача options с таймаутом для обхода проблемы больше не требуется.
Добавление событий в очередь:
--ContextId — контекст задачи
SMART:execute_action('AddNewEventToQueue', ContextId , 'task',
{
Type = 'Custom', --Тип
Flow = 3, --Поток (id)
SystemAction = nil, --Событие
CustomActions = 2, --Кастомное событие (id)
InternalID = nil, --Внутренний ID, колонка ContextId в MessageQueue
ExternalID = nil, --Внешний ID, колонка ContextExternalId в MessageQueue
Data =
{
{ParameterName = "parameter",FixedValue = "value"},
{...}
} --Параметры, колонка Data в MessageQueue, для дополнительных параметров
})
Создание задач на основании SQL-выборки:
-- Функция создания задачи, возвращает таблицу с номером созданной задачи
-- Список параметров задается по необходимости
function createTask (newtaskextparams, subcatid)
local result = SMART:execute_action("CreateTask", ContextId, 'task',
{
Owner = 3,
Subcat = subcatid,
TaskText = nil,
CreateLink = false,
CreateSubtask = false,
Performers = nil,
DueTime = nil,
TaskStartTime = nil,
ExtParams = newtaskextparams,
NewTaskCopySubscribers = false,
CreateCopyFiles = false,
CopyParentText = false,
Priority = 1,
Notify = nil,
UsersToSubscribe = nil,
CreateCopiesForEachPerformer = false,
LinkFiles = nil,
AssignLetterWithTask = nil,
Confidentiality = nil,
LinkAsUser = nil
}
)
return result
end
-- Выборка для формирования доп параметров
local table_data = SQL:query('select top 10 * from Tasks t (nolock)', {});
-- Цикл по выборке, формирование таблицы доп параметров и создание задачи
for key, val in pairs(table_data) do
newtaskextparams =
{
'{"ExtParamId": 1792, "FixedValue": "' .. val.TaskId .. '"}',
'{"ExtParamId": 4099, "FixedValue": "' .. val.UserId .. '"}',
'{"ExtParamId": 1608, "FixedValue": "' .. val.CreatedTime ..'"}',
'{"ExtParamId": 9762, "FixedValue": "' .. val.EndTime .. '"}'
}
local createdTask = createTask(newtaskextparams, 123)
var_dump(createdTask[0]) -- id созданной задачи
end
| Параметр | Тип данных | Описание |
|---|---|---|
| Owner * | int | Заказчик |
| Subcat * | int | Категория |
| TaskText | string | Текст |
| CreateLink * | bool | Сделать связанной |
| CreateSubtask * | bool | Сделать подзадачей |
| Performers | int[] | Исполнители |
| DueTime | DateTime | Срок |
| TaskStartTime | DateTime | Дата начала работ |
| ExtParams | ExtParamValueForNewTask[] | Доп параметры |
| NewTaskCopySubscribers * | bool | Копировать подписчиков из родительской |
| CreateCopyFiles * | bool | Копировать вложения |
| CopyParentText * | bool | Включать текст исходной |
| Priority * | byte | Приоритет |
| Notify | int[] | Уведомить |
| UsersToSubscribe | int[] | Подписать |
| CreateCopiesForEachPerformer * | bool | Каждому исполнителю отдельную копию |
| LinkFiles | bool | Создать ссылки на вложения |
| AssignLetterWithTask | bool | Вложить письмо файлом |
| Confidentiality | bool | Конфиденциальность |
| LinkAsUser | int | Связать от имени |
| SessionUser | int | От чьего имени совершается действие |
| return | Int32[] |
Подсчет трудозатрат + отправка комментария:
--[[ GENERAL ]]
COMMENT_TASK = 416665;--365522;
COMMENT_USER = 3;
local function isempty(s)
return s == nil or s == ''
end
--[[ GET STORY INFO ]]
function get_story_info()
local res = SQL:query(
[[
SELECT TOP 1
td.ExtParam12133Value AS type,
td2.TaskID AS storyId,
td2.TaskText AS storyDescr,
team.UserID AS teamLeadUserId
FROM TasksInSubcat5641Denormalized td
JOIN TasksInSubcat5574Denormalized td2 ON td.ExtParam11980NativeValue = td2.TaskID
OUTER APPLY (
SELECT TOP 1 UserId
FROM ExtParamSelectUsersValues euv
WHERE euv.TaskID = td2.ExtParam10435NativeValue AND euv.ExtParamID = 10433
) team
WHERE td.TaskId = @TaskId
]],
{ TaskId = CONTEXT['Id'] }
);
local task = res[0]
local short_task = string.sub(task.storyDescr, 0, utf8.offset(task.storyDescr, 60));
task.storyShortText ='#' .. tostring(task.storyId) .. ' (' .. short_task .. ')';
return task;
end;
info = get_story_info();
--[[ GET PLAN / FACT ]]
function get_plan_fact()
return SQL:query(
"EXEC sp_cm_ustoryGetPlanFact NULL, @TaskId",
{ TaskId = CONTEXT['Id'] }
);
end;
--[[ SEND COMMENT FUNC ]]
function send_comment(comment)
local recipients;
local recipients_in_copy;
if not isempty(EVENTPARAMS.PerformerUserId) then
recipients = EVENTPARAMS.PerformerUserId.Id;
--table.insert(recipients, EVENTPARAMS.PerformerUserId.Id)
end;
if not isempty(info.teamLeadUserId) then
recipients_in_copy = info.teamLeadUserId;
end;
SMART:execute_action('PostComment', null, 'task', {
CommentAuthor = COMMENT_USER,
CommentText = comment,
Recipients = recipients,
RecipCopies = recipients_in_copy,
CommentType = 3,
Task = COMMENT_TASK,
ForcedEmail = false,
CommentSMS = false,
TextAsHTML = false,
NoSubscription = false,
MarkAsQuestion = true,
CommentVisibleOnlyToRealRecipients = true
});
end
--[[ MAIN ]]
function main()
local plan_fact = get_plan_fact();
local plan_amount = plan_fact[0]['planAmount']
local fact_amount = plan_fact[0]['factAmount']
-- если плана нет
if plan_amount == 0 then
if fact_amount > 1 then
send_comment('Внесены трудозатраты по сценарию без плана ' .. info.storyShortText);
end;
return;
end;
local plan_fact_overage = (fact_amount * 100 / plan_amount) - 100;
local plan_fact_overage_limit = 20;
-- если план превышен
if plan_fact_overage > plan_fact_overage_limit then
return send_comment(
'Превышение плана по сценарию ' .. info.storyShortText
.. ' на ' .. string.format('%.f', plan_fact_overage) .. '% '
.. '(факт: ' .. string.format('%.f', fact_amount) .. 'ч)'
);
end;
print('everything is ok!');
end;
--[[ INIT ]]
if (info['type'] == 'Разработка') then
main();
end;
Парсинг XML:
"Первая Форма" поддерживает следующие утилиты для преобразования XML-файлов в lua-таблицу: - UTILS:xml_to_json() — Принимает xml-строку, возвращает json-строку - UTILS:json_to_xml() — Принимает json-строку, возвращает xml-строку - UTILS:xml_serialize() — Принимает lua-таблицу, отдает xml-строку (внутри делает json_encode -> json_to_xml) - UTILS:xml_deserialize() — Принимает xml-строку, отдает lua-таблицу (внутри делает xml_to_json -> json_decode) - FILES:get_file_content(fileId, versionId) — Получить контент файла - FILES:get_file_content_string(fileId, versionId, encoding) — Получить контент файла в виде строки в кодировке (по умолчанию UTF-8)
Пример работы парсинга xml:
-- получаем содержимое файла как utf-8 строку
local xml = FILES:get_file_content_string(2193619, nil, "utf-8")
-- парсим json в lua-таблицу
local tbl = UTILS:xml_deserialize(xml)
-- меняем в таблице значение
tbl['wsdl:definitions']['wsdl:message'][1]['@name'] = 'NGDO_ReplacementOperationRequest2'
local val = tbl['wsdl:definitions']['wsdl:message'][1]['@name']
-- обратно из lua-таблицы в xml. (в идеале должно быть xml == xml2, но зависит от библиотеки Newtonsoft)
local xml2 = UTILS:xml_serialize(tbl)
RESULT = xml2
Пример POST HTTP-запроса с файлами¶
Функция HTTP:post_files выполняет HTTP POST-запрос с типом содержимого multipart/form-data, позволяя прикреплять один или несколько файлов из базы данных системы. Список файлов может быть передан любым образом. Пример отправки документа во внешний сервис с передачей дополнительных параметров:
t = CONTEXT.Id;
fileID = SQL:scalar([[
SELECT top(1) ff.FileId
FROM FileStorageFileToExtParamLinks ff
WHERE ff.TaskId = @t AND ff.IsDeleted <> 1
ORDER BY ff.id desc
]], {t = t});
local url = 'https://httpbin.org/post';
local files = {file = fileID};
local params = {document_type = 'commercial_invoice'};
local headers = {};
req = HTTP:post_files( url, files, params, headers, nil );
x = req["HttpResponse"]["ResponseContent"]
x = UTILS:json_decode(x);
var_dump(x);
RESULT = x;
Пример 2:
local addComment = function(taskID,userID,commentText,isQuestion)
SMART:execute_action("PostComment", taskID, "task",
{
CommentAuthor = userID,
CommentText = commentText,
Recipients = nil,
RecipCopies = nil,
CommentType = 3,
Task = taskID,
ForcedEmail = false,
CommentSMS = false,
TextAsHTML = false,
NoSubscription = false,
MarkAsQuestion = isQuestion,
CommentVisibleOnlyToRealRecipients = false,
SilentComment = true
}
)
end
function myerrorhandler( err )
addComment(НОМЕР_ЗАДАЧИ,3,string.format("ERROR:%s",err ),0)
end
function wrapper_post_files(prm)
local req = HTTP:post_files(
prm.url,
prm.files,
prm.parameters,
prm.headers,
nil
)
return req
end
MessageQueueData = UTILS:json_decode(CONTEXT["Data"])
--addComment(НОМЕР_ЗАДАЧИ,3,CONTEXT["Data"],0)
if MessageQueueData ~= nil then
TaskID = MessageQueueData["ParamTaskID"]
FileID = MessageQueueData["ParamFileID"]
else
RESULT = false
return
end
local Headers = {}
-- Headers['Accept-Language'] = 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7'
-- Headers['Accept'] = '*/*'
-- --Headers['1formaauth'] = 'ТОКЕН_АВТОРИЗАЦИИ'
-- Headers['X-Atlassian-Token'] = 'nocheck'
--URL = 'https://ВАШ_АДРЕС_1Формы/UploadFilesAsync.ashx'
local bot = {}
bot.id = ""
bot.token = ""
bot.host = "api.telegram.org"
URL = "https://"..bot.host.."/bot"..bot.id..":"..bot.token.."/sendDocument?chat_id=ИД_ЧАТА"
--var_dump(URL)
--if 1 then return end
Files = {document = {tonumber(FileID)}}
--Files = {file = {tonumber(FileID)}}
local prm = {
url = URL,
files = Files,
parameters = {},
headers = Headers
}
--var_dump(prm)
--if 1==1 then return end
local st, result = pcall(wrapper_post_files,prm)
var_dump(st)
if st then
var_dump("done");
var_dump(result)
else
addComment(TaskID,3,"Ошибка отправки файла",0)
end
RESULT = true
Пример POST HTTP-запроса с комбинацией файлов и текстовых параметров в одном теле¶
Функция HTTP:post_multipart выполняет HTTP POST-запрос в формате multipart/form-data с комбинацией файлов и текстовых параметров в одном теле. Он позволяет передавать в одном запросе как текстовые параметры (например, chatId или caption), так и файлы, указанные по их идентификаторам (fileId). Метод принимает таблицу parts, где каждый элемент гибко описывает, что отправлять: обычное поле, файл из системы или файл с предзагрузкой. Для файлов можно задать имя поля в форме, имя самого файла и тип содержимого.
Пример вызова:
local parts = {
{ name = "param1", value = "value1" }, -- строковый параметр в multipart
{ name = "param2", value = "value2" }, -- еще один
{ fileId = 2325190 }, -- простой fileId
{ fileId = "2325190.2" }, -- fileId с версией
{ fileId = 2325190, name = "file0", fileName = "Name-2325190" }, -- переопределить name и fileName в соотв. секции multipart запроса
{ fileId = -706713, fileName = "Name-2325190" }, -- preuploadedFileId (отрицательный id)
{ fileId = "preupload:706713", fileName = "Name-2325190" }, -- preuploadedFileId через нотацию строковым ключем
{ fileId = 2325190, fileName = "Name-2325190", contentType = "text/html" } -- переопределить contentType (иначе будет взят из файла)
}
local res = HTTP:post_multipart(
url,
parts,
params,
headers)