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

Настройка канбана с помощью шаблона задач

⚠️ Устаревший способ настройки Настройка пользовательского канбана с помощью публикаций больше не поддерживается и считается устаревшей. В настоящее время кастомный канбан настраивается с помощью шаблона задач. См. Шаблон карточки канбана.

В примере, рассмотренном ниже, на Канбан-доске отображаются задачи из одной категории, а перемещение задач между столбцами соответствует смене статуса задачи. В целом такого ограничения нет, и набор данных может формироваться произвольно, если в этом есть бизнес-смысл.

Публикации удобно настраивать с помощью 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 статуса: {"key": "1", "name": "Новая", "template": "1", "style": "info", "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": [{ ... }] [{"key": 1, "label": "Есть срок", "order": 1}, ...] Массив описаний возможных значений поля группировки: "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"} (необязательный) Признаки, определяющие отображение строки в зависимости от фильтра. "onEmpty" — показывать строку даже если в ней нет записей; "values" — массив значений фильтра
"filters": [{ ... }] Массив фильтров для отбора данных. Для каждого фильтра указываются:
"key": "..." (фильтр) "key": "perf" Уникальный ключ фильтра
"label": "..." "label": "Исполнители" Название фильтра (может быть на любом языке)
"icon": "..." (фильтр) "icon": "settings" Название значка из набора material-icons
"type": "..." "type": "multiselect" В настоящее время поддерживается только тип multiselect
"values": [{ ... }] (фильтр) [{"key": "withPerf", "label": "Есть исполнители"}, ...] Массив значений фильтра: "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"}} (необязательный) Дополнительная информация для строк. Для каждой строки указываются:
"[ключ строки]": {"info": "..."} Описывается в секции rows. "info" — текст, который выводится рядом с названием соответствующей строки
"elements": [{ ... }] Массив карточек. Для каждой карточки указываются:
"row": "..." "row": "noPerf" Ключ строки — описывается в секции rows в публикации со структурой данных
"column": "..." "column": 1 Ключ столбца — описывается в секции columns в публикации со структурой данных
"label": "..." "label": "Подготовка к запуску нового продукта" Название карточки
"style": "..." "style": "secondary" Цвет верхней границы карточки: info (голубой), warning (желтый), success (зеленый), danger (красный), primary (синий), secondary (голубой)
"canMove": true / false "canMove": true Если true, поддерживается возможность перемещать карточку
"urlToOpen": "..." "urlToOpen": "/maintaskform.aspx?taskid=123456" Ссылка, которая будет открываться по клику на карточку
"percentage": "..." "percentage": 33 Процент выполнения (определяет процент закрашивания верхней границы)
"info": { ... } {"perfName": "Куролесов", "perfPhoto": "/GetAvatar.ashx?&Id=99", "taskId": 123456, ...} 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. У пользователя недостаточно прав.

Если для перемещения карточки используется смарт-действие Изменить статус задачи, инициатором действия должен быть пользователь с правом Администратор задач в данной категории — только он имеет право выполнять принудительный переход.

Для публикации, которая отвечает за перемещение карточки, могут быть настроены ограничения по правам доступа.

  1. Не пройдены проверки, настроенные на переходе.

Если для перемещения карточки используется смарт-действие Выполнить переход по маршруту или Перейти в заданный статус, то при переходе выполняются все проверки — заполнение обязательных ДП, корректность их заполнения и другие смарт-автоматизации. Если какая-то проверка не пройдена, генерируется ошибка.

  1. Нет подходящего перехода.

Если для перемещения карточки используется смарт-действие Перейти в заданный статус, но в категории не настроен переход из исходного статуса в целевой, то при попытке переместить карточку генерируется ошибка. Чтобы ошибка не возникала, в настройках смарт-действия нужно отключить флажок Кинуть ошибку, если не найден переход.

Полезные ссылки

Обращение к объектам из адресной строки браузера

Публикации

Учебник по SVELTE