Перейти к содержанию

Примеры 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)