воскресенье, 22 марта 2020 г.

SIL: Reports Charts. График выполненных задач по неделям

Если вам, как и мне, потребовался график, который покажет кол-во выполненных задач, группируя их по неделям, то эта статья как раз для вас (smile)
На выходе мы получим следующий гаджет на дашборде:


Итак, чуть более подробно о задаче
Необходим универсальный скрипт, для построения графика по различным проектам в JIRA, выполненных задач, с указанием определенных Issue Type, возможностью указать временной диапазон и результаты выводить в недельном интервале. 
Для этого нам понадобится аддон  Power Dashboard Reports Charts & Gadgets от любимого CPRIME = )
Документация для этого аддона не столь полная, но она есть и это уже хорошо.
У данного аддона имеется возможность вывода параметров, в очень даже достойном виде. Выпадающее меню, автофилл и др. 
Но т.к. мне нужен готовый дашборд, чтобы его можно было просто открыть и видеть картину по проектам, не нажимая никаких кнопок запуска - я решил, что параметры скрипта у меня будут прописаны жестко под каждый гаджет. 
1 гаджет = 1 проект. 

Определяемся с параметрами: 

param0 = будет принимать ключ проекта
param1 = первая дата ОТ в формате гггг-мм-дд
param2 = вторая дата ДО в формате гггг-мм-дд
param3 = Перечисление ишью тайпов, через запятую

Пишем скрипт

отступление
Тут стоит сделать небольшое отступление. 
При написании скриптов, где ожидаются данные с параметров - я тестирую аналогичными скриптами, куда подставляю константы.
Поэтому, рядом с основным файлом скрипта. Я просто создаю скрипт теста рядом, помечая, что это тест.
Например: 
rg_allDoneTF.sil - основной скрипт
rg_allDone_TF_test.sil - скрипт для теста
Именно поэтому, я начну писать изначально тест скрипта. 
Нам нужно перечислить и определить все константы, которые будут ожидаться на входе, в виде параметров. Поэтому пишем: 

string project = "TEST"//проект
 
date a = "2020-01-01"//первая дата
date b = "2020-12-25"//вторая дата
 
string [] it = {"История""Задача"}; //ишью тайпы
Далее, нам нужно подумать о тех данных, которые нам потребуются в ходе подсчета. Нам нужно:
  • решенные задачи (массив где в каждом элементе будет храниться кол-во задач, за неделю)
  • номер недели (чтобы писать эту информацию под столбцом с кол-вом задач) (это тоже массив. он нужен с таким-же кол-вом элементов, как и в массиве решенных задач)
  • номер недели первой даты (для этого используем рутину week(date) которая возвращает номер недели.
  • номер недели второй даты
Добавляем нужные переменные и там где есть возможность, сразу их инициализируем.
string project = "TEST"//проект
 
date a = "2020-01-01"//первая дата
date b = "2020-12-25"//вторая дата
 
string [] it = {"История""Задача"}; //ишью тайпы
 
int [] resolvedIssue; //решенные задачи
string [] nWeek; // 
 
number wa = week(a); //Номер недели первой даты
number wb = week(b); //Номер недели второй даты
забегая вперед
Забегая вперед, я скажу, что когда я оставил данные со второй недели, выставляя любое БУДУЩЕЕ значение, например окончание года - то я столкнулся с не совсем желаемым поведением JQL поиска. 
Результаты мне выводили решенные задачи из прошлого года. Чего мне совсем не хотелось = )
Можно ограничить поиск JQL если вводить во вторую дату текущую неделю. Но если учесть, что я хочу сделать этот дашборд и забыть про него, имея всегда актуальную информацию, то я решил кое что исправить...
А исправить я решил то, что если нам не нужно указывать конкретный диапазон дат ОТ и ДО - то вторую дату можно оставить пустую. А скрипт должен принимать ее как текущую неделю. Таким образом, скрипт не будет считать ненужные недели. Поэтому, давайте сразу внесем изменения:
string project = "TEST"//проект
 
date a = "2020-01-01"//первая дата
date b = "2020-12-25"//вторая дата
 
string [] it = {"История""Задача"}; //ишью тайпы
 
int [] resolvedIssue; //решенные задачи
string [] nWeek; //
 
number wa = week(a); //Номер недели первой даты
//Если второй аргумент пустой - брать сегодняшнюю дату
number wb;
if(isNotNull(b)){
    wb = week(b);
}
else wb = week(currentDate());
По большому счету, все необходимые данные перечислены. И осталось только придумать и реализовать механизм получения нужных данных из JQL и заполнения этими данными массивов.
Безусловно нам нужен цикл. Мы посчитаем разницу между неделей второй даты и первой даты. И ровно столько будем искать нужные нам задачи. Единственная проблема построения JQL строки, это то, что у нас в 4 аргументе приходит массив. И если его просто положить в строку, то это будет выглядеть так: issuetype in (Task|Story|Bug). JQL так ничего не найдет, поэтому нам надо заменить символ | на запятую. 
string project = "TEST"//проект
 
date a = "2020-01-01"//первая дата
date b = "2020-12-25"//вторая дата
 
string [] it = {"История""Задача"}; //ишью тайпы
 
int [] resolvedIssue; //решенные задачи
string [] nWeek; //
 
number wa = week(a); //Номер недели первой даты
//Если второй аргумент пустой - брать сегодняшнюю дату
number wb;
if(isNotNull(b)){
    wb = week(b);
}
else wb = week(currentDate());
 
for(number n = 0;n <= wb-wa;n++){
     
    string jql = "project = \"" + project + "\" AND issuetype in (" + it + ") AND resolved >= startOfWeek(-" + n + ") AND resolved <= endOfWeek(-" + n + ")";
    jql = replace(jql, "|"",");
    runnerLog(jql);
    //runnerLog(countIssues(jql));
    string [] i = selectIssues(jql);
    if(isNotNull(i)){
        string nwTemp = week(i[0].resolutionDate); //просто преобразуем номер недели в строку, для дальнейшей склейки
        nwTemp = nwTemp + " неделя"//это временная переменная в цикле, с помощью которой мы будем наполнять нужный нам массив
         
        nWeek += nwTemp; //наполняем значениями массив с неделями
        resolvedIssue += countIssues(jql); //наполняем значениями массив с кол-вом решенных задач за неделю
    }
}
На выходе мы получим 2 массива nWeek и resolvedIssue которые будут наполнены нужными нам данными. Есть только одно НО. 
Если оставить их в таком виде, то график будет "задом-наперед". Т.е. сначала у вас будут выводиться столбцы ближе к текущей дате. А уходить вправо будут отдаленные даты. 
Чтобы это исправить - нам надо поменять элементы в массиве, в обратном порядке. Для этого, мне пришлось написать отдельную функцию.
На входе функция получает массив данных. В цикле перебираются все значения массива, на основе его размера. И начиная с последнего элемента массива (а это size(a)-1) мы наполняем новый массив, который отдадим обратно в скрипт: 
incl_getReverseArray.sil
//AS-217
//функция меняет местами элементы в массиве
 
function getReverseArray(string [] a){
    string [] reverseArray;
    for(number n = size(a)-1;n >=0;n--){
        reverseArray += a[n];
    }
    return reverseArray;
}
Теперь нам надо заинклюдить нашу функцию в скрипт и вызвать ее, для получения нужного нам порядка
rg_allDone_TF_test.sil готовый для тестирования
include "inclusions/incl_getReverseArray.sil";
string project = "TEST"//проект
 
date a = "2020-01-01"//первая дата
date b = ""//вторая дата
 
string [] it = {"История""Задача"}; //ишью тайпы
 
int [] resolvedIssue; //решенные задачи
string [] nWeek; //
 
number wa = week(a); //Номер недели первой даты
//Если второй аргумент пустой - брать сегодняшнюю дату
number wb;
if(isNotNull(b)){
    wb = week(b);
}
else wb = week(currentDate());
 
for(number n = 0;n <= wb-wa;n++){
     
    string jql = "project = \"" + project + "\" AND issuetype in (" + it + ") AND resolved >= startOfWeek(-" + n + ") AND resolved <= endOfWeek(-" + n + ")";
    jql = replace(jql, "|"",");
    runnerLog(jql);
    //runnerLog(countIssues(jql));
    string [] i = selectIssues(jql);
    if(isNotNull(i)){
        string nwTemp = week(i[0].resolutionDate); //просто преобразуем номер недели в строку, для дальнейшей склейки
        nwTemp = nwTemp + " неделя"//это временная переменная в цикле, с помощью которой мы будем наполнять нужный нам массив
         
        nWeek += nwTemp; //наполняем значениями массив с неделями
        resolvedIssue += countIssues(jql); //наполняем значениями массив с кол-вом решенных задач за неделю
    }
}
 
runnerLog(resolvedIssue);
runnerLog(nWeek);
 
nWeek = getReverseArray(nWeek);
resolvedIssue = getReverseArray(resolvedIssue);
 
runnerLog(resolvedIssue);
runnerLog(nWeek);
раннер лог в финале - позволит вам видеть сначала вывод значений массивов, которые нам даются на выходе из цикла. Затем мы меняем значения, с помощью написанной функции и смотрим уже на то, как элементы расположились в обратном порядке. 
Если тест у нас успешен, то мы можем теперь создать скрипт для гаджета. По сути он ничем не отличается, за исключением лишь того, что переменные у нас будут ожидать параметров с гаджета, ну а после скрипта будет написана структура гаджета. 
Детально я описывать ее не буду. Остановлюсь лишь на паре моментов. 
Для того, чтобы каждому столбцу присвоить цветовое значение - необходимо в массив .backgroundColor подставлять столько значений, сколько столбцов необходимо закрасить. Можно натыркать с запасом и прописать что-то в духе: 
resolvedDataset.backgroundColor = {"#3cba9f", "#3cba9f", "#3cba9f", "#3cba9f", "#3cba9f","#3cba9f","#3cba9f","#3cba9f","#3cba9f","#3cba9f","#3cba9f","#3cba9f","#3cba9f"};
Я решил такой ерундой не страдать и написать еще один цикл, который будет заполнять этот массив требуемым кол-вом. 
Ну и еще один момент. Чтобы гаджеты выглядели чуть более аккуратно (особенно если их много) поиграйтесь со значением options.aspectRatio = 3; для меня, оптимальным стало значение в троечку. 
Итоговый скрипт для гаджета: 
rg_allDone_TF_test.sil готовый для тестирования
include "inclusions/incl_getReverseArray.sil";
string project = argv[0]; //Для какого проекта
 
date a = argv[1]; //первая дата
date b = argv[2]; //вторая дата
 
string [] it = argv[3]; //ишью тайпы
 
int [] resolvedIssue; //решенные задачи
string [] nWeek; //
 
number wa = week(a); //Номер недели первой даты
//Если второй аргумент пустой - брать сегодняшнюю дату
number wb;
if(isNotNull(b)){
    wb = week(b);
}
else wb = week(currentDate());
 
for(number n = 0;n <= wb-wa;n++){
     
    string jql = "project = \"" + project + "\" AND issuetype in (" + it + ") AND resolved >= startOfWeek(-" + n + ") AND resolved <= endOfWeek(-" + n + ")";
    jql = replace(jql, "|"",");
 
    string [] i = selectIssues(jql);
    if(isNotNull(i)){
        string nwTemp = week(i[0].resolutionDate); //просто преобразуем номер недели в строку, для дальнейшей склейки
        nwTemp = nwTemp + " неделя"//это временная переменная в цикле, с помощью которой мы будем наполнять нужный нам массив
         
        nWeek += nwTemp; //наполняем значениями массив с неделями
        resolvedIssue += countIssues(jql); //наполняем значениями массив с кол-вом решенных задач за неделю
    }
}
 
nWeek = getReverseArray(nWeek);
resolvedIssue = getReverseArray(resolvedIssue);
 
//Справочник команд по проектам
string [] teams;
teams["TF"] = "Team Features";
teams["BB"] = "BigBang";
teams["DT"] = "Dream Team";
teams["STREAMT"] = "Trust";
teams["TADS"] = "Team ADS";
teams["UTEAM"] = "Understanding";
teams["AS"] = "Atlassian Support";
 
//Набор массива цветов
string [] bc;
for(number n = size(resolvedIssue);n > 0;n--){
    bc += "#3cba9f";
}
 
SILReportingChartDataset resolvedDataset;
resolvedDataset.label = "Выполненные задачи";
resolvedDataset.backgroundColor = bc;
resolvedDataset.data = resolvedIssue;
 
SILReportingChartData data;
data.labels = nWeek;
data.datasets = {resolvedDataset};
 
SILReportingChartTitle title;
title.display = true;
title.position = "top";
title.text = "Решенные задачи проекта " + teams[project];;
 
SILReportingChartLegend legend;
legend.display = false;
legend.position = "top";
 
SILReportingChartTicks ticks;
ticks.beginAtZero = true;
 
SILReportingChartYAxes yAxes;
yAxes.ticks = ticks;
 
SILReportingChartScales scales;
scales.yAxes = {yAxes};
 
SILReportingChartOptions options;
options.title = title;
options.legend = legend;
options.scales = scales;
options.aspectRatio = 3;
 
SILReportingChart barChart;
barChart.type = "bar";
barChart.data = data;
barChart.options = options;
 
return barChart;
Буду рад вашим дополнениям, замечаниям или просто делитесь опытом, что удалось сделать вам (wink)
Результат на дашборде: 

Комментариев нет:

Отправить комментарий