Канбан-доска Трелло (Trello) достаточно проста, удобна и популярна, и обладает простым API, позволяющим создать интеграцию с 1С. Также, на 1С уже создано несколько интерфейсов, реализующих канбан-доски. Их объединение позволяет расширить и сделать удобнее возможности постановки, контроля и решения задач, а также статистику по ним, с помощью механизмов Трелло.
Интеграция выполнена в виде отдельных функций на каждое действие, по возможности атомарных, т.е. без взаимных и пакетных вызовов (исключение составляет только работа с досками и чек-листами). Использованы синхронные http-запросы согласно описанию rest-методов и объектов (на сайте Atlassian: документация по методам и описание основных объектов). К сожалению, подробного описания всех объектов схемы данных я не нашёл, поэтому свойства объектов представлены ниже по результатам просмотра при отладке.
Данные передаются в json, всё только средствами платформы. Никакие особенности, связанные с защищённым соединением, сертификацией, SSL и прочим, в явном виде не использовались (всё по умолчанию).
Я применял для http-обмена собственную разработку, ранее уже опубликованную на ИС. Можно использовать свои или БСП-шные механизмы, не суть. Все они лишь обёртка вызовов API и обработка возвращённых json-объектов и, при наличии документации, можно легко "наштамповать" недостающие методы, используя имеющееся как образец. Поэтому, собственно, все-все методы API я реализовывать и не стал.
Механизмы реализации рест-обмена
// При ошибке возвращает Неопределено
Функция ПрочестьJSONизСтроки(рСтрока,рВСоответствие=Ложь) Экспорт
Попытка
Если ПустаяСтрока(рСтрока) Тогда
Сообщить("ПрочестьJSONизСтроки, строка на чтение пуста!");
Возврат Неопределено;
КонецЕсли;
рЧтение=Новый ЧтениеJSON;
рЧтение.УстановитьСтроку(рСтрока);
//
рДанные=ПрочитатьJSON(рЧтение,рВСоответствие);
Если ТипЗнч(рДанные)<>Тип("Структура") и ТипЗнч(рДанные)<>Тип("Соответствие") и ТипЗнч(рДанные)<>Тип("Массив") Тогда
Сообщить("ПрочестьJSONизСтроки, получены данные неверного формата!");
Возврат Неопределено;
Иначе
Возврат рДанные;
КонецЕсли;
Исключение
Сообщить("ПрочестьJSONизСтроки, общая ошибка: "+ОписаниеОшибки());
Возврат Неопределено;
КонецПопытки;
КонецФункции
// Вспомогательная
Процедура СообщитьИВнестиВПротокол(инфо,рПротокол) Экспорт
Если Прав(инфо,3)="!!!" Тогда
рСтатус=СтатусСообщения.ОченьВажное;
ИначеЕсли Прав(инфо,2)="!!" Тогда
рСтатус=СтатусСообщения.Важное;
ИначеЕсли Прав(инфо,1)="!" Тогда
рСтатус=СтатусСообщения.Внимание;
Иначе
рСтатус=СтатусСообщения.Информация;
КонецЕсли;
Сообщить(инфо,рСтатус);
//
Если ТипЗнч(рПротокол)=Тип("ТекстовыйДокумент") Тогда
рПротокол.ДобавитьСтроку(инфо);
КонецЕсли;
КонецПроцедуры
// Выполняет любой запрос к соединению, при необходимости создаёт соединение, заносит в параметры ответ сервиса.
// Возвращает успешность, при ошибке возвращает Ложь.
//
// Параметры:
// рПараметры - структура:
//
// Соединение - HTTP-соединение; заносится в этот ключ, если не было передано (создаётся в функции), и используется из этого ключа без изменений, если было передано;
// если значение ключа Соединение не указано или имеет иной тип, то соединение создаётся согласно значениям указанных ключей:
// Сервер -строка, обязательный (если пусто или не указано, то возвращает ошибку), если начинается с http://, то приводится к правильному виду автоматически;
// Порт - число, обязательный (по умолчанию для незащищённых 80, для защищённых 443);
// Пользователь - строка, необязательный;
// Пароль - строка, необязательный;
// Таймаут - число, необязательный (по умолчанию 0);
// ПроксиПользователь - строка, необязательный (по умолчанию пуста); если не пуста, то это признак использования прокси, в этом случае требуется передача свойств:
// ПроксиПароль - строка, необязательный;
// ПроксиСервер - строка,
// ПроксиПорт - число, необязательный (по умолчанию 0);
// Если Соединение не было указано, то по завершении функции в ключ Соединение вносится созданное в ней;
//
// HTTPЗапрос - запрос с уже установленными свойствами; необязательный, если не указан, то инициализируется в функции согласно значениям указанных ключей:
// АдресРесурса или АдресСкрипта (равнозначны оба ключа) - строка, необязательный; по умолчанию "/";
// ЗаголовкиЗапроса - соответствие или фикс.соответствие;
// ПараметрыЗапроса - структура, соответствие, фикс.соответствие или список значений (где имя ключа - Представление, а значение ключа - Значение),
// список значений используется в случае, когда важен порядок следования параметров, вносит в ком.строку строго по порядку вхождения данных в список;
// ТелоЗапроса - строка или двоичные данные, необязательный;
// ИмяФайлаТелаЗапроса - строка; необязательный;
// указание тела запроса более приоритетно, чем указание имени файла тела запроса;
// Если HTTPЗапрос был указан, то по завершении функции из рПараметры значение с ключом HTTPЗапрос удаляется, во избежание кэширования;
//
// МетодHTTP - строка, необязательный (по умолчанию GET), допустимы все значения, поддерживаемые методами объекта "HTTPСоединение" 1С.
//
// Протокол - текстовый документ, необязательный (по умолчанию пуст и не используется).
//
// По итогам работы функции при её успешном завершении в рПараметры вставляется значение с ключом HTTPОтвет. При ошибке вставляет Неопределено.
//
// Возвращает успешность (булево).
//
Функция HTTPВыполнитьЗапрос(рПараметры) Экспорт
Попытка
рПротокол=?(рПараметры.Свойство("Протокол"),рПараметры.Протокол,Неопределено);
рПояснять=?(рПараметры.Свойство("Пояснять"),рПараметры.Пояснять,Ложь);
// сразу по умолчанию
//рОтвет=Новый HTTPСервисОтвет(0,"Запрос не выполнялся"); // в релизах ниже 8.3.7 не отрабатывает
рОтвет=Неопределено;
рПараметры.Вставить("HTTPОтвет",рОтвет);
#Область Соединение
рКраткийФормат=?(рПараметры.Свойство("КраткийФорматВызова"),рПараметры.КраткийФорматВызова,Ложь);
рЗащищённое=?(рПараметры.Свойство("ЗащищенноеСоединение"),рПараметры.ЗащищенноеСоединение,Ложь);
//
рСоединение=?(рПараметры.Свойство("Соединение"),рПараметры.Соединение,Неопределено);
Если ТипЗнч(рСоединение)<>Тип("HTTPСоединение") Тогда // надо его установить
#Область УстановкаСоединения
Если не рПараметры.Свойство("Сервер") Тогда Возврат Ложь КонецЕсли;
рСервер=СокрЛП(рПараметры.Сервер);
Если ПустаяСтрока(рСервер) Тогда Возврат Ложь КонецЕсли;
рПорт=?(рПараметры.Свойство("Порт"),рПараметры.Порт,0);
рТаймаут=?(рПараметры.Свойство("Таймаут"),рПараметры.Таймаут,0);
рПроксиПользователь=?(рПараметры.Свойство("ПроксиПользователь"),рПараметры.ПроксиПользователь,""); // признак применения прокси именно в этом
//Если рКраткийФормат Тогда рЗащищённое=Ложь; рПроксиПользователь="" КонецЕсли;
//
рПрокси=Неопределено;
Если не ПустаяСтрока(рПроксиПользователь) Тогда
рПрокси=Новый ИнтернетПрокси;
рПрокси.Пользователь=рПроксиПользователь;
рПрокси.Пароль=рПараметры.ПроксиПароль;
Если рПараметры.ПроксиПорт=0 Тогда
рПрокси.Установить("HTTP",рПараметры.ПроксиСервер);
Иначе
рПрокси.Установить("HTTP",рПараметры.ПроксиСервер,рПараметры.ПроксиПорт);
КонецЕсли;
КонецЕсли;
//
Если СтрНачинаетсяС(НРег(рСервер),"http://") Тогда
рСервер=Сред(рСервер,8);
КонецЕсли;
Если рПорт=0 Тогда
рПорт=?(рЗащищённое,443,80);
КонецЕсли;
//
Если рЗащищённое Тогда
рSSL=Новый ЗащищенноеСоединениеOpenSSL(,Новый СертификатыУдостоверяющихЦентровWindows);
// //рSSL=Новый ЗащищенноеСоединениеOpenSSL(Новый СертификатКлиентаWindows(СпособВыбораСертификатаWindows.Выбирать),Новый СертификатыУдостоверяющихЦентровWindows());
// рСпособВыбора=СпособВыбораСертификатаWindows.Авто;
// рСертификатОС=Новый СертификатКлиентаWindows(рСпособВыбора);
// рSSL=Новый ЗащищенноеСоединениеOpenSSL(рСертификатОС);
Иначе
рSSL=Неопределено;
КонецЕсли;
//
Если рКраткийФормат Тогда
рСоединение=Новый HTTPСоединение(рСервер);
Иначе
Если рПрокси=Неопределено Тогда
рСоединение=Новый HTTPСоединение(рСервер,рПорт,рПараметры.Пользователь,рПараметры.Пароль,,рТаймаут,рSSL);
Иначе
рСоединение=Новый HTTPСоединение(рСервер,рПорт,рПараметры.Пользователь,рПараметры.Пароль,рПрокси,рТаймаут,рSSL);
КонецЕсли;
КонецЕсли;
//
Если ТипЗнч(рСоединение)<>Тип("HTTPСоединение") Тогда
СообщитьИВнестиВПротокол("Не удалось создать объект HTTP-соединения!",рПротокол);
КонецЕсли;
//
// вставим на будущее (это иногда имеет смысл кэшировать)
рПараметры.Вставить("Соединение",рСоединение);
#КонецОбласти
Иначе
рСервер=рСоединение.Сервер;
рПорт=рСоединение.Порт;
рТаймаут=рСоединение.Таймаут;
рПроксиПользователь=рСоединение.Прокси.Пользователь;
КонецЕсли;
// а эти всегда берём по итогам установленного соединения
рПроксиСервер=рСоединение.Прокси.Сервер();
рПроксиПорт=рСоединение.Прокси.Порт();
//
// выводим настройки соединения
Если рПояснять Тогда
инфо="Соединение:
|Сервер: "+СокрЛП(рСервер)+", порт: "+Строка(рПорт)+", пользователь: "+СокрЛП(рПараметры.Пользователь)+", таймаут: "+рТаймаут+"
|краткий формат: "+Строка(рКраткийФормат)+", защищённое: "+Строка(рЗащищённое)+Символы.ПС;
Если не ПустаяСтрока(рПроксиПользователь) Тогда
инфо=инфо+"прокси: сервер "+СокрЛП(рПроксиСервер)+", порт "+Строка(рПроксиПорт)+", пользователь "+СокрЛП(рПроксиПользователь);
Иначе
инфо=инфо+"прокси не используется";
КонецЕсли;
СообщитьИВнестиВПротокол(инфо,рПротокол);
КонецЕсли;
//
Если ТипЗнч(рСоединение)<>Тип("HTTPСоединение") Тогда // уже без сообщения, именно выход по ошибке
Возврат Ложь;
КонецЕсли;
#КонецОбласти
#Область Запрос
рЗапрос=?(рПараметры.Свойство("HTTPЗапрос"),рПараметры.HTTPЗапрос,Неопределено);
Если ТипЗнч(рЗапрос)<>Тип("HTTPЗапрос") Тогда
Если рПараметры.Свойство("АдресРесурса") и не ПустаяСтрока(рПараметры.АдресРесурса) Тогда
рАдресРесурса=рПараметры.АдресРесурса;
ИначеЕсли рПараметры.Свойство("АдресСкрипта") и не ПустаяСтрока(рПараметры.АдресСкрипта) Тогда
рАдресРесурса=рПараметры.АдресСкрипта;
Иначе
рАдресРесурса="/";
СообщитьИВнестиВПротокол("Используется пустой адрес ресурса для запроса.",рПротокол);
КонецЕсли;
#Область ЗаголовкиЗапроса
рЗаголовкиЗапроса=Новый Соответствие;
Если рПараметры.Свойство("ЗаголовкиЗапроса") Тогда
Если ТипЗнч(рПараметры.ЗаголовкиЗапроса)=Тип("Соответствие") или ТипЗнч(рПараметры.ЗаголовкиЗапроса)=Тип("ФиксированноеСоответствие") Тогда
рЗаголовкиЗапроса=рПараметры.ЗаголовкиЗапроса;
КонецЕсли;
КонецЕсли;
Если рПояснять Тогда
Если рЗаголовкиЗапроса.Количество()=0 Тогда
инфо="Заголовки запроса не указаны.";
Иначе
инфо="Заголовки запроса:";
Для каждого киз Из рЗаголовкиЗапроса Цикл
инфо=инфо+Символы.ПС+" "+киз.Ключ+" = "+киз.Значение;
КонецЦикла;
КонецЕсли;
СообщитьИВнестиВПротокол(инфо,рПротокол);
КонецЕсли;
#КонецОбласти
#Область ПараметрыЗапроса
рПараметрыЗапроса=Новый Соответствие;
Если рПараметры.Свойство("ПараметрыЗапроса") и рАдресРесурса<>"/" Тогда
Если ТипЗнч(рПараметры.ПараметрыЗапроса)=Тип("Структура")
или ТипЗнч(рПараметры.ПараметрыЗапроса)=Тип("Соответствие")
или ТипЗнч(рПараметры.ПараметрыЗапроса)=Тип("ФиксированноеСоответствие")
Тогда
Для каждого киз Из рПараметры.ПараметрыЗапроса Цикл
рПараметрыЗапроса.Вставить(киз.Ключ,киз.Значение);
КонецЦикла;
ИначеЕсли ТипЗнч(рПараметры.ПараметрыЗапроса)=Тип("СписокЗначений") Тогда
// случай, когда был очень важен порядок параметров, и их разместили осознанно именно так; не сортировать!
Для каждого знч Из рПараметры.ПараметрыЗапроса Цикл
рПараметрыЗапроса.Вставить(СокрЛП(знч.Представление),знч.Значение);
КонецЦикла;
КонецЕсли;
КонецЕсли;
Если рПараметрыЗапроса.Количество()=0 Тогда
инфо="Параметры запроса не указаны.";
Иначе
инфо="Параметры запроса:";
разд="?";
Для каждого киз Из рПараметрыЗапроса Цикл
Если ТипЗнч(киз.Значение)=Тип("Массив") Тогда // раскрываем в последовательность, повторяя ключ
Для каждого знч Из киз.Значение Цикл
рАдресРесурса=рАдресРесурса+разд+киз.Ключ+"="+СокрЛП(Строка(знч)); разд="&";
КонецЦикла;
Иначе
рАдресРесурса=рАдресРесурса+разд+киз.Ключ+"="+СокрЛП(Строка(киз.Значение)); разд="&";
КонецЕсли;
инфо=инфо+Символы.ПС+" "+киз.Ключ+" = "+киз.Значение;
КонецЦикла;
КонецЕсли;
Если рПояснять Тогда
СообщитьИВнестиВПротокол(инфо,рПротокол);
КонецЕсли;
#КонецОбласти
рЗапрос=Новый HTTPЗапрос(рАдресРесурса,рЗаголовкиЗапроса);
//
Если рПараметры.Свойство("ТелоЗапроса") Тогда
Если ТипЗнч(рПараметры.ТелоЗапроса)=Тип("Строка") Тогда
рЗапрос.УстановитьТелоИзСтроки(рПараметры.ТелоЗапроса);
ИначеЕсли ТипЗнч(рПараметры.ТелоЗапроса)=Тип("ДвоичныеДанные") Тогда
рЗапрос.УстановитьТелоИзДвоичныхДанных(рПараметры.ТелоЗапроса);
КонецЕсли;
ИначеЕсли рПараметры.Свойство("ИмяФайлаТелаЗапроса") Тогда
// наличие файла не проверяем, считаем, что он есть априорно
рЗапрос.УстановитьИмяФайлаТела(рПараметры.ИмяФайлаТелаЗапроса);
КонецЕсли;
КонецЕсли;
Если рПараметры.Свойство("HTTPЗапрос") Тогда
// удаляем из параметров, чтобы не повторялось в будущем (кэшировать его нам не надо)
рПараметры.Удалить("HTTPЗапрос");
КонецЕсли;
//
Если рПояснять Тогда // выводим настройки запроса
инфо="Запрос:
|Адрес ресурса: "+рЗапрос.АдресРесурса;
Если рПараметры.Свойство("ТелоЗапроса") и ТипЗнч(рПараметры.ТелоЗапроса)=Тип("Строка") Тогда
инфо=инфо+"
|Тело запроса:"+Символы.ПС+рПараметры.ТелоЗапроса;
КонецЕсли;
СообщитьИВнестиВПротокол(инфо,рПротокол);
КонецЕсли;
#КонецОбласти
рМетодHTTP=?(рПараметры.Свойство("МетодHTTP"),ВРег(рПараметры.МетодHTTP),"");
Если ПустаяСтрока(рМетодHTTP) Тогда рМетодHTTP="GET" КонецЕсли;
Если рПояснять Тогда
СообщитьИВнестиВПротокол("Метод запроса: "+рМетодHTTP,рПротокол);
КонецЕсли;
Если рМетодHTTP="GET" Тогда
рОтвет=рСоединение.Получить(рЗапрос);
ИначеЕсли рМетодHTTP="POST" Тогда
рОтвет=рСоединение.ОтправитьДляОбработки(рЗапрос);
ИначеЕсли рМетодHTTP="PUT" Тогда
рОтвет=рСоединение.Записать(рЗапрос);
ИначеЕсли рМетодHTTP="PATCH" Тогда
рОтвет=рСоединение.Изменить(рЗапрос);
ИначеЕсли рМетодHTTP="DELETE" Тогда
рОтвет=рСоединение.Удалить(рЗапрос);
Иначе
СообщитьИВнестиВПротокол("Указан не поддерживаемый метод: "+рМетодHTTP+", никакое действие не выполняется!!",рПротокол);
Возврат Ложь;
КонецЕсли;
рПараметры.Вставить("HTTPОтвет",рОтвет);
Возврат Истина;
Исключение
инфо="HTTPВыполнитьЗапрос, общая ошибка: "+ОписаниеОшибки();
Сообщить(инфо,СтатусСообщения.Важное);
ЗаписьЖурналаРегистрации("ОбменСайт",УровеньЖурналаРегистрации.Ошибка,,,инфо);
Возврат Ложь;
КонецПопытки;
КонецФункции
// Выполняет анализ ответа rest-сервиса как объекта HTTPОтвет, заносит в параметры результаты разбора.
//
// Параметры:
// HTTPОтвет - объект типа "HTTPСервисОтвет", обязательный; собственно разбираемый ответ;
// КодСостояния - число, вносимый;
// Причина - строка, вносимый; если КодСостояния 200, то пуста;
// ЗаголовкиОтвета - соответствие, вносимый;
// ТелоКакСтрока - булево; если указан и Истина, то читается тело ответа как строка, для уточнения кодировки используется ключ:
// КодировкаТелаОтвета - строка или КодировкаТекста, по умолчанию UTF8;
// ТелоКакДвоичныеДанные - булево; если указан и Истина, то читается тело ответа как двоичные данные;
// ТелоКакИмяФайла - булево; если указан и Истина, то возвращается имя файла, куда прочитано тело ответа;
// ТелоОтвета - результат чтения тела ответа, вносимый, если способ чтения тела не указан, то Неопределено.
// Протокол - текстовый документ, необязательный (по умолчанию пуст и не используется).
//
// Возвращает успешность (булево).
//
Функция HTTPРазобратьОтвет(рПараметры) Экспорт
Попытка
рПротокол=?(рПараметры.Свойство("Протокол"),рПараметры.Протокол,Неопределено);
Если Не (рПараметры.Свойство("HTTPОтвет") и ТипЗнч(рПараметры.HTTPОтвет)=Тип("HTTPОтвет")) Тогда
СообщитьИВнестиВПротокол("В структуре параметров не найден ответ сервиса!!",рПротокол);
Возврат Ложь;
КонецЕсли;
рОтвет=рПараметры.HTTPОтвет;
//
рПараметры.Вставить("КодСостояния",рОтвет.КодСостояния);
Если рОтвет.КодСостояния=200 Тогда
рПараметры.Вставить("Причина","");
Иначе
Попытка рПараметры.Вставить("Причина",рОтвет.Причина) Исключение рПараметры.Вставить("Причина","") КонецПопытки;
КонецЕсли;
рПараметры.Вставить("ЗаголовкиОтвета",рОтвет.Заголовки);
Если рПараметры.Свойство("ТелоКакСтрока") и ТипЗнч(рПараметры.ТелоКакСтрока)=Тип("Булево") и рПараметры.ТелоКакСтрока=Истина Тогда
рКодировка=КодировкаТекста.UTF8;
Если рПараметры.Свойство("КодировкаТелаОтвета") и ЗначениеЗаполнено(рПараметры.КодировкаТелаОтвета) Тогда
Если ТипЗнч(рПараметры.КодировкаТелаОтвета)=Тип("Строка") или ТипЗнч(рПараметры.КодировкаТелаОтвета)=Тип("КодировкаТекста") Тогда
рКодировка=рПараметры.КодировкаТелаОтвета;
КонецЕсли;
КонецЕсли;
рПараметры.Вставить("ТелоОтвета",рОтвет.ПолучитьТелоКакСтроку(рКодировка));
ИначеЕсли рПараметры.Свойство("ТелоКакДвоичныеДанные") и ТипЗнч(рПараметры.ТелоКакДвоичныеДанные)=Тип("Булево") и рПараметры.ТелоКакДвоичныеДанные=Истина Тогда
рПараметры.Вставить("ТелоОтвета",рОтвет.ПолучитьТелоКакДвоичныеДанные());
ИначеЕсли рПараметры.Свойство("ТелоКакИмяФайла") и ТипЗнч(рПараметры.ТелоКакИмяФайла)=Тип("Булево") и рПараметры.ТелоКакИмяФайла=Истина Тогда
рПараметры.Вставить("ТелоОтвета",рОтвет.ПолучитьИмяФайлаТела());
Иначе
СообщитьИВнестиВПротокол("Тело ответа игнорируется, т.к. его тип и способ обработки не указаны!",рПротокол);
рПараметры.Вставить("ТелоОтвета",Неопределено);
КонецЕсли;
//
Возврат Истина;
Исключение
инфо="HTTPРазобратьОтвет, общая ошибка: "+ОписаниеОшибки();
Сообщить(инфо,СтатусСообщения.Важное);
ЗаписьЖурналаРегистрации("ОбменСайт",УровеньЖурналаРегистрации.Ошибка,,,инфо);
Возврат Ложь;
КонецПопытки;
КонецФункции
Отмечу, что веб-интерфейс реагирует на вызов методов, изменяющих данные, мгновенно, без обновления страницы (но, возможно, в других браузерах при других настройках кэширования это не так). При конкурентном доступе из кода или интерфейса результат соответствует действию, последнему по времени вызова.
Также могут представлять интерес следующие действия
* Отметить как прочитанные, т.е. сбросить все оповещения: POST /1/cards/{id}/markAssociatedNotificationsRead
* Архивировать все задачи на листе: POST /1/lists/{id}/archiveAllCards
* Перенести все задачи листа на другой лист: POST /1/lists/{id}/moveAllCards
* Получение ответной реакции на действие (комментарий) в задаче: GET /1/actions/{idAction}/reactions/{id}
Приведённая на скриншоте публичная доска также небезынтересна, кроме того, там можно пообщаться с разработчиками.
Примечания:
1. При отправке дат, как устанавливаемых значений свойств, не забывайте про правильный формат - должен быть ISO 8601-2001 (т.е. вида "ГГГГ-ММ-ДДTЧЧ:ММ:ССZ", вариант записи "УниверсальнаяДата").
2. В ряде случаев, особенно для кириллицы, может потребоваться url-перекодировка, используйте глобальные "КодироватьСтроку" и "РаскодироватьСтроку".
Есть публикация //infostart.ru/public/1195416/, но там конфигурация, а я выкладываю исходник. Желающие также могут найти по ключевому слову "Trello" множество канбанообразных обработок и проектов. Тема не нова, но авось кому пригодится.
Тестировалось на 8.3.16.1063, но должно работать на всех релизах не ниже 8.3.10.