В примере, рассмотренном ниже, на Канбан-доске отображаются задачи из одной категории, а перемещение задач между столбцами соответствует смене статуса задачи. В целом такого ограничения нет, и набор данных может формироваться произвольно, если в этом есть бизнес-смысл.
Публикации удобно настраивать с помощью SVELTE.
Реализация
Для вывода Канбан-доски нужно настроить три публикации пакетов действий:
1. Публикация, которая описывает структуру данных.
2. Публикация, которая возвращает данные.
3. Публикация, которая выполняет перемещение Канбан-карточки между столбцами.
Пусть в нашем примере название первой публикации — kanban-structure, название второй публикации — kanban-data, название третьей публикации — kanban-step.
Вызов Канбан-доски
Интерфейс Канбан-доски вызывается следующим образом:
~/spa/kanban/kanban-structure
где ~ — адрес приложения "Первая Форма", а kanban-structure — название публикации, возвращающей структуру данных.
1. Публикация, которая описывает структуру данных
В нашем примере это публикация kanban-structure.
Входящих параметров у публикации нет. Тип запроса — GET.
Пакет содержит единственное действие HTTP ответ, которое возвращает результат в формате JSON.
Тело ответа содержит описание структуры данных, строки вызова двух остальных публикаций и шаблоны вывода Канбан-карточек. Эти шаблоны описываются в формате HTML и mustache (по аналогии с виджетами Smart Html).
Структура тела ответа
Ключи
|
Примеры
|
Описание
|
"columns": [
{
|
|
массив столбцов. Как правило, столбец соответствует статусу задачи. Для каждого столбца указываются:
|
"key": "...",
|
"key": "1",
|
ключ столбца.
Ключом столбца может являться, к примеру, id статуса:
"columns": [
{"key": "1", "name": "Новая", "template": "1", "style": "info", "icon": "help", "movableTo": true, "movableFrom": true}
|
"icon": "...",
|
"icon": "check_circle",
|
(необязательный) название значка, который выводится рядом с названием столбца
|
"color": "...",
|
"color": "blue-500",
|
(необязательный) цвет в формате material ("цвет-интенсивность")
|
"name": "...",
|
"name": "Новая",
|
название столбца
|
"style": "...",
|
"style": "info",
|
стиль отображения названия столбца. Можно использовать преднастроенные значения: info (голубой), warning (желтый), success (зеленый), danger (красный), primary (синий), secondary (голубой)
|
"template": "...",
|
"template": "1",
|
ключ шаблона вывода карточки задачи в данном столбце (см. ниже блок templates)
|
"movableFrom": true / false,
|
"movableFrom": true,
|
если true, то поддерживается возможность перемещать карточку из данного столбца
|
"movableTo": true / false,
|
"movableTo": true,
|
если true, то поддерживается возможность перемещать карточку в данный столбец
|
"showArrow": true / false
|
"showArrow": false
|
если true, то название столбца отображается как стрелка вправо, если false — то как прямоугольник без стрелки. По умолчанию — true
|
},
{...}
],
|
|
|
"group": {
|
|
группировка данных внутри столбца. Для группировки указываются:
|
"key":"...",
|
"key": "hasDueDate",
|
название поля группировки
|
"values": [
{
|
"values": [
{ "key": 1,
"label": "Есть срок",
"order": 1
},
{ "key": 0,
"label": "Нет срока",
"order": 2
}
]
|
массив выражений с описанием возможных значений поля группировки, где каждое выражение содержит поля:
|
"key": "...",
|
значение поля группировки
|
"label": "...",
|
метка, соответствующая данному значению, которая будет отображаться на Канбан-доске
|
"order": "..."
|
порядковый номер значения поля группировки
|
},
{...}
]
|
|
},
|
|
|
"rows": [
{
|
|
(необязательный)
строки канбан-доски. Строка соответствует группе карточек, признак группировки может быть любым. Для каждой строки указываются:
|
"key": "...",
|
"key": "noPerf",
|
уникальный ключ строки
|
"icon": "...",
или
"image": "...",
|
"icon": "tags",
или
"image": "/GetAvatar.ashx?Id=99",
|
(необязательный)
для icon указывается название значка (из набора material-icons),
для image указывается путь к изображению
|
"color": "...",
|
"color": "blue-900",
|
(необязательный) цвет в формате material ("цвет-интенсивность", например, red-900)
|
"sort_key": "...",
|
"sort_key": 1,
|
значение, которое определяет порядковый номер строки при сортировке
|
"name": "...",
|
"name": "Нет исполнителя",
|
название строки
|
"info": "...",
|
"info": "...",
|
(необязательный) дополнительная информация, которая выводится рядом с названием. Может подгружаться из секции rowsInfo публикации, возвращающей данные
|
"movableFrom": true / false,
|
"movableFrom": true,
|
если true, то поддерживается возможность перемещать карточку из данной строки
|
"movableTo": true / false,
|
"movableTo": true,
|
если true, то поддерживается возможность перемещать карточку в данную строку
|
"showByFilterValue": {
|
|
(необязательный) набор признаков, которые определяют, отображается ли строка в зависимости от выбранного фильтра
|
"уникальный ключ фильтра": {
|
"perf": {
"onEmpty": true,
"values": ["noPerf"]
}
|
описывается в секции filters
|
"onEmpty": true / false,
|
если true, то строка отображается даже если в ней нет записей
|
"values": [...]
|
массив значений фильтра.
Строка будет отображаться, если в фильтре выбрано одно из указанных значений
|
},
"уникальный ключ фильтра": {...}
}
|
|
|
},
{...}
],
|
|
|
"filters": [
{
|
|
массив фильтров для отбора данных. Для каждого фильтра указываются:
|
"key": "...",
|
"key": "perf",
|
уникальный ключ фильтра
|
"label": "...",
|
"label": "Исполнители",
|
название (может быть на любом языке)
|
"icon": "...",
|
"icon": "settings",
|
название значка, который выводится рядом с названием столбца (из набора material-icons)
|
"type": "...",
|
"type": "multiselect",
|
в настоящее время поддерживается только тип multiselect
|
"values": [
{
|
"values": [
{
"key": "withPerf",
"label": "Есть исполнители",
"style": "style-primary"
},
{
"key": "noPerf",
"label": "Нет исполнителей",
"style": "style-primary"
}
]
|
массив значений, которые может принимать фильтр
|
"key": "...",
|
уникальный ключ значения фильтра
|
"label": "...",
|
название значения фильтра
|
"style": "..."
|
стиль значения фильтра
|
},
{...}
]
|
|
},
|
|
|
"refreshOnPreviewClose": true / false,
|
"refreshOnPreviewClose": true,
|
признак, выполняется ли обновление Канбан-доски после закрытия окна просмотра карточки (если в карточку вносятся изменения, может потребоваться актуализировать данные на Канбан-доске)
|
"dataSet": "...",
|
|
адрес вызова публикации, возвращающей данные
|
"moveWebRequest": "...",
|
|
(необязательный) адрес вызова публикации, выполняющей перемещение карточки между столбцами. Если не указан, то перемещение не поддерживается
|
"templates": {
"ключ_шаблона":"...",
"ключ_шаблона":"..."
}
|
"templates": {
"1":"..."
}
|
список шаблонов вывода карточки задачи в формате HTML и mustache (по аналогии с виджетом Smart Html).Список должен содержать хотя бы один шаблон
|
Пример смарт-выражения, формирующего тела ответа
DECLARE @res NVARCHAR(MAX);
SET @res = '
{
"columns": [
{"key": "1", "name": "Новая", "template": "1", "style": "info", "icon": "help", "movableTo": true, "movableFrom": true},
{"key": "2", "name": "Выполняется", "template": "1", "style": "warning", "icon": "check_circle", "movableTo": true, "movableFrom": true},
{"key": "3", "name": "Завершена", "template": "1", "style": "success", "icon": "check_circle", "movableTo": true, "movableFrom": true, "showArrow": false}
],
"group": {
"key": "hasDueDate",
"values": [
{"key": 1, "label": "Есть срок", "order": 1},
{"key": 0, "label": "Нет срока", "order": 2}
]
},
"rows": [
{
"key": "withPerf",
"icon": "security",
"color": "blue-900",
"sort_key": 1,
"name": "Есть исполнитель",
"showByFilterValue": {
"perf": {
"onEmpty": true,
"values": ["withPerf"]
}
}
},
{
"key": "noPerf",
"icon": "tags",
"color": "blue-200",
"sort_key": 2,
"name": "Нет исполнителя",
"showByFilterValue": {
"perf": {
"onEmpty": true,
"values": ["noPerf"]
}
}
}
],
"filters": [
{
"key": "perf",
"label": "Исполнители",
"icon": "settings",
"type": "multiselect",
"values": [
{
"key": "withPerf",
"label": "Есть исполнители",
"style": "style-primary"
},
{
"key": "noPerf",
"label": "Нет исполнителей",
"style": "style-primary"
}
]
}
],
"refreshOnPreviewClose": true,
"moveWebRequest": "/app/v1.2/api/publications/action/kanban-step",
"dataSet": "/app/v1.2/api/publications/action/kanban-data",
"templates": {
"1": "<style>.cmkb{display: flex;justify-content: space-between;align-items:flex-end;}.cmkb-user-label{color:rgba(0,0,0,.54);margin-top: 2px;margin-top: 5px;font-weight: bold;}.cmkb-footer{margin-top:5px;justify-content: space-between;align-items: center;}.cmkb-footer i{font-size: 15px;height: 15px; min-height: 15px; min-width: 15px; line-height: 14px; width: 15px; inline-size: 15px; vertical-align: middle; margin-right: 5px;}.cmkb-pic{flex: 0 0 40px;margin-top: 5px;margin-right: 5px;}.cmkb-priority{color: #f44336}</style><div class=''cmkb''> <div class=''cmkb-main''>{{#perfName}}<div class=''cmkb-user-label''>{{perfName}}</div>{{/perfName}}<div class=''cmkb-footer''>{{#isPriorityHigh}}<i title=''Высокий приоритет'' class=''cmkb-priority material-icons''>flash_on</i>{{/isPriorityHigh}}<span title=''Срок'' class=''cmkb-odate''>{{#orderedDate}}<i class=''material-icons''>query_builder</i>{{orderedDate}}{{/orderedDate}}</span> </div>{{#tags}}<div class=''cmkb-tags''><span>{{tags}}</span> </div>{{/tags}}</div>{{#perfName}}<div class=''cmkb-pic''> <img class=''kb-photo'' src=''{{perfPhoto}}''/> </div>{{/perfName}}</div>"
}
}'
select @res
Обратите внимание: поскольку текст формируется как строка и заключен в одинарные кавычки, внутри шаблона template одинарные кавычки экранируются (дублируются).
|
|
2. Публикация, возвращающая данные
В нашем примере это публикация kanban-data.
Входящих параметров у публикации нет. Тип запроса — GET.
Пакет содержит единственное действие HTTP ответ, которое возвращает результат в формате JSON.
Тело ответа формируется с помощью смарт-выражения.
Структура тела ответа
Ключи
|
Примеры
|
Описание
|
"rowsInfo": {
|
"rowsInfo":{
"withPerf":{
"info":"6 из 9"
},
"noPerf":{
"info":"3 из 9"
}
},
|
(необязательный) дополнительная информация для строк. Для каждой строки указываются:
|
"уникальный ключ строки": {
|
описывается в секции rows
|
"info": "..."
|
текст, который выводится рядом с названием соответствующей строки
|
},
{...}
},
|
|
"elements": [
{
|
|
массив карточек. Для каждой карточки указываются:
|
"row": "ключ строки",
|
"row":"noPerf",
|
описывается в секции rows в публикации со структурой данных
|
"column": "ключ столбца",
|
"column":1,
|
описывается в секции columns в публикации со структурой данных
|
"label": "...",
|
"label": "Подготовка к запуску нового продукта",
|
название карточки
|
"style": "...",
|
"style":"secondary",
|
цвет верхней границы карточки (отображается как progress-bar).
Можно использовать преднастроенные значения: info (голубой), warning (желтый), success (зеленый), danger (красный), primary (синий), secondary (голубой)
|
"canMove": true / false,
|
"canMove":true,
|
если true, то поддерживается возможность перемещать карточку
|
"urlToOpen": "...",
|
"urlToOpen":"/maintaskform.aspx?taskid=123456",
|
ссылка, которая будет открываться по клику на карточку
|
"percentage": "...",
|
"percentage":33,
|
процент выполнения (определяет процент закрашивания верхней границы)
|
"info": {...}
|
"info":{
"perfName":"Куролесов",
"perfPhoto":"/GetAvatar.ashx?&Id=99",
"perfId":99,
"taskId":123456,
"hasDueDate":0
}
|
JSON произвольной структуры. Содержит элементы, которые можно выводить в шаблоне (template)
|
},
{...}
]
|
|
|
Пример смарт-выражения, формирующего тело ответа
DECLARE @elements NVARCHAR(MAX);
DECLARE @rows NVARCHAR(MAX);
SET @elements = (
SELECT TOP 200
StateId AS [column],
case when IsNull(th.UserID,0)=0 then 'noPerf' else 'withPerf' end [row],
TaskText AS label,
cast(ExtParam2869NativeValue as int) AS percentage,
CASE
WHEN td.PriorityId = 3 THEN 'warning'
WHEN td.PriorityId = 1 THEN 'secondary'
WHEN td.PriorityId = 0 THEN 'primary'
END AS style,
'true' AS canMove,
'/maintaskform.aspx?taskid=' + CAST(td.TaskId AS VARCHAR(10)) AS urlToOpen,
FORMAT(OrderedTime, 'dd.MM') AS [info.orderedDate],
u.DisplayName AS [info.perfName],
'/GetAvatar.ashx?&Id=' + CAST(u.UserID AS VARCHAR(10)) AS [info.perfPhoto],
u.UserID AS [info.perfId],
td.TaskID AS [info.taskId],
CASE WHEN td.OrderedTime IS NOT NULL THEN 1 ELSE 0 END AS [info.hasDueDate]
FROM TasksInSubcatXXXDenormalized td
LEFT JOIN TaskHelpers th ON td.TaskID = th.TaskID AND th.IsResponsible = 1
LEFT JOIN Users u ON th.UserID = u.UserID
WHERE DATEDIFF(day, IsNull(td.EndTime, GetDate()), GetDate()) < 30
FOR JSON PATH, ROOT('elements')
)
SET @rows = (
SELECT
cast(sum(case when IsNull(th.UserID,0)=0 then 0 else 1 end) as varchar(max))+'/'+cast(count(*) as varchar(max)) as [withPerf.info],
cast(sum(case when IsNull(th.UserID,0)=0 then 1 else 0 end) as varchar(max))+'/'+cast(count(*) as varchar(max)) as [noPerf.info]
FROM TasksInSubcatXXXDenormalized td
LEFT JOIN TaskHelpers th ON td.TaskID = th.TaskID AND th.IsResponsible = 1
WHERE DATEDIFF(day, IsNull(td.EndTime, GetDate()), GetDate()) < 30
FOR JSON PATH
)
SELECT '{"rowsInfo":' + substring(@rows,2,len(@rows)-2) + ',' + substring(@elements,2,len(@elements)-2) + '}'
В данном примере XXX — ID категории, по которой строится Канбан-доска.
|
3. Публикация, выполняющая перемещение карточки между столбцами
В нашем примере это публикация kanban-step. Тип запроса — POST.
У публикации есть один входящий параметр — строка requestBody. Она формируется автоматически — в ней передаются данные о перемещаемой карточке. При настройке смарт-пакета к содержимому строки requestBody можно обращаться из смарт-выражений как к параметру @eventParam0.
Структура строки requestBody имеет формат JSON и содержит два ключа: item и newColumn:
•значение ключа item это структура, которая соответствует элементу elements из публикации с данными.
•значение ключа newColumn — ID целевого столбца, в который перемещается карточка.
{
"item":{
"column":1,
"label":"...",
"style":"...",
"canMove":true,
"urlToOpen":"...",
"info":{
...
}
},
"newColumn":"2"
}
Пакет публикации должен содержать два смарт-действия: первое выполняет перемещение, а второе возвращает код ответа. Второе смарт-действие необходимо, чтобы после перемещения канбан-доска автоматически обновилась.
3.1. Смарт-действие Изменить статус задачи.
Пример настройки смарт-действия Изменить статус задачи
Параметр смарт-действия
|
Значение
|
Задача
|
Смарт-выражение в формате TSQL:
SELECT JSON_VALUE(@eventParam0, '$.requestBody.item.info.taskId')
|
Инициатор
|
Смарт-выражение:
Текущий пользователь
(пользователь, который перетаскивает задачу. В редакторе смарт-выражений текущего пользователя можно выбрать в дереве сущностей, в папке "Прочее").
Т.к. выполняется принудительный переход по маршруту, чтобы он отработал корректно, инициатором должен быть пользователь, обладающий правом Администратор задач в данной категории
|
Статус
|
Смарт-выражение в формате TSQL:
SELECT JSON_VALUE(@eventParam0, '$.requestBody.newColumn');
|
Поскольку в данном примере перемещение выполняется как принудительный переход в статус, не выполняются никакие проверки и смарт-автоматизации, настроенные на переходах.
|
Вместо действия Изменить статус задачи можно использовать действия Выполнить переход по маршруту или Перейти в заданный статус, но в этом случае будут выполняться проверки и действия, настроенные на переходах, и их нужно обрабатывать отдельно. Например, если на переходе запрашивается подпись, нужно выделить отдельный столбец Канбан-доски для промежуточного статуса На подписи. Если на переходе проверяется обязательность заполнения ДП, эти ДП лучше отображать в шаблоне карточки, и т.д.
Если условия перехода не выполняются, при перетаскивании карточки пользователю отобразится сообщение "Не удалось перенести элемент", а в журнал ошибок будет записано сообщение об ошибке.
Дополнительная возможность ограничить право перемещать задачи между столбцами Канбан-доски — установить ограничение по правам доступа к публикации.
|
3.2. Смарт-действие HTTP ответ
Пример настройки смарт-действия HTTP ответ
Параметр смарт-действия
|
Значение
|
Тип возвращаемого результата
|
Значение
|
JSON
|
Тело ответа
|
Значение
|
true
|
Код ответа
|
Значение
|
OK
|
Возможные ошибки
Если при перетаскивании карточки задачи генерируется ошибка и отображается сообщение "Не удалось перенести элемент", рассмотрите возможные причины:
1. У пользователя недостаточно прав.
Если для перемещения карточки используется смарт-действие Изменить статус задачи, инициатором действия должен быть пользователь с правом Администратор задач в данной категории — только он имеет право выполнять принудительный переход.
Для публикации, которая отвечает за перемещение карточки, могут быть настроены ограничения по правам доступа.
2. Не пройдены проверки, настроенные на переходе.
Если для перемещения карточки используется смарт-действие Выполнить переход по маршруту или Перейти в заданный статус, то при переходе выполняются все проверки — заполнение обязательных ДП, корректность их заполнения и другие смарт-автоматизации. Если какая-то проверка не пройдена, генерируется ошибка.
3. Нет подходящего перехода.
Если для перемещения карточки используется смарт-действие Перейти в заданный статус, но в категории не настроен переход из исходного статуса в целевой, то при попытке переместить карточку генерируется ошибка. Чтобы ошибка не возникала, в настройках смарт-действия нужно отключить флажок Кинуть ошибку, если не найден переход.
Полезные ссылки
Обращение к объектам из адресной строки браузера
Публикации
Учебник по SVELTE