Автоматизация учёта рабочего времени

На моём предыдущем месте работы оценка трудозатрат на реализацию задачи выражалась условными баллами: 1, 2, 3, 5, 8, 13, 21 — чем сложнее задача, тем больше балл. Каждая задача предварительно оценивалась коммандой разработчиков перед началом следующего спринта (по Agile Scrum), балл выбирался средний, нетривиальные задачи обсуждались детальнее. С одной стороны такой подход выражает сложность задачи не очень объективно — определяя её сроки — сроком спринта. С другой стороны один и тот же балл для разных разработчиков займёт разное время.

Только перед моим уходом вводился переход на оценку задачи в часах, требуемых на её реализацию. На моём следующем месте работы такая система оценки уже использовалась, и более того было принято вести учёт рабочего времени в трекере задач.

Jira Tempo example screenshot (рис. 1 — пример интерфейса Jira Tempo)

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

Для фиксации смены контекста и более честного его отражения приходилось бы каждый раз вручную открывать журнал рабочего времени и заносить отчёт длительности работы над задачей с момента взятия её в работу. Однако операция смены контекста должна быть максимально лёгкой, дешёвой и удобной.

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

С первого дня я начал фиксировать в текстовых файлах интервалы времени работы над задачами, а в конце рабочего дня считал суммарное время по каждой, после чего уже вручную заполнял ворклоги. Это удобнее заполнения каждого интервала как минимум меньшим количеством записей, так как каждая из них требует описания.

# 9.30

9.30-9.45   - PP-950 - стабилизация
9.45-10.00  - PP-975 - review
10.00-11.00 - PP-1000 - реализация
11.00-11.30 - PP-975
11.30-12.00 - PP-1000

# 12.00-13.00 обед

13.00-16.00 - PP-1000
16.00-18.30 - PP-950

---

PP-950 - 15m + 2h30m = 2h45m
PP-1000 - 1h + 30m + 3h = 4h30m
PP-975 - 15m + 30m = 45m

(пример 1 — содержимое файла учёта рабочего времени)

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

В обеденное время была написан небольшой скрипт, в основу которого лёг простой парсер, анализируищий отчёт рабочего времени. Из текстового файла на выходе имелась информация, с агрегированным описанием и суммой времени затраченной на неё.

PP-950  185m стабилизация
PP-1000 270m реализация
PP-975  45m  реализация

480

(пример 2 — вывод команды первой версии скрипта)

Это позволяло понимать завершённость учёта и решала проблему подсчёта суммарного времени по задаче с формированием её описания.

Спустя время возникла необходимость упростить процесс переноса данных, полученной из команды, в трекер задач. Так как делать это вручную далеко не самое приятное занятие и эта операция сильно недешёвая, было решено написать небольшой скрипт, который автоматизирует весь этот процесс.

Один из вариантов решения данной задачи было использование официального API. Это вариант имеет определённую сложность и высокий порог вхождения — возникала мысль, что реализацию можно сделать проще и дешевле.

Самым простым вариантом было воспроизвести коммуникацию приложения и применить её для решения задачи, поэтому был проанализирован трафик запросов во время заполнения ворклога вручную и воспроизведён для проверки что концепт работает.

Сейчас инициализация этого решения укладывается в 4 шага:

  1. открыть страницу Jira
  2. открыть консоль разработчика (Ctrl + Shift + I)
  3. сделать два шага назад по истории консоли (↑, ↑) (или, если история не сохранена, скопировать код из файла проекта)
  4. применить выбранный код (см. листинг 1)
const jiraUrl = `https://jira.company.com`;  // fixme
const userName = 'your.username';  // fixme

function _toJson(response) {
    var contentType = response.headers.get("content-type");
    if (contentType && contentType.includes("application/json")) {
        return response.json();
    }
    throw new TypeError("Oops, we haven't got JSON!");
}

function search(project_key) {  // eg. project_key = PP-1000
    return fetch(`${jiraUrl}/rest/api/2/issue/${project_key}?fields=project%2Csummary%2Ctimeestimate%2Cissuetype`);
}

function validate(data) {
    let headers = new Headers();
    headers.append('Content-Type', 'application/json');
    return fetch(`${jiraUrl}/rest/tempo-timesheets/3/worklogs/validate`, {
        method: 'POST', body: JSON.stringify(data), headers: headers
    })
}

function submit(data) {
    let headers = new Headers();
    headers.append('Content-Type', 'application/json');
    return fetch(`${jiraUrl}/rest/tempo-timesheets/3/worklogs/`, {
        method: 'POST', body: JSON.stringify(data), headers: headers
    });
}

function _(task, dateStarted) {
    search(task['key'])
        .then(_toJson)
        .then(function (data) {
            return {
                "id": null,
                "issue": {
                    "key": task['key'].toUpperCase(),
                    "remainingEstimateSeconds": Math.max(0, data.fields.timeestimate - task['timespent']),
                    "id": data.id
                },
                "timeSpentSeconds": task['timespent'],
                "dateStarted": dateStarted,
                "comment": task['comment'],
                "meta": {"analytics-origin-action": "clicked"},
                "author": {"name": userName},
                "workAttributeValues": []
            }
        }).then(worklog => validate(worklog).then(function () {submit(worklog)}));
}

function submitDay(day) {
    let dateStarted = day['date'];  // eg. "2019-04-30T00:00:00.000"
    let taskList = day['entries'];

    for (task of taskList) {
        _(task, dateStarted);
    }
}

function submitDays(days) {
    for (day of days) {
        submitDay(day);
    }
}

(листинг 1 — набор функций для отправки отчёта рабочего времени)

Обкатка решения показала что не каждая сборка php работает предсказуемо под cygwin. Обновления рабочей среды может нарушить стабильность работы интепретатора. Полученный опыт подвёл к размышлениям проанализировать полученное решение, что позволило понять, что для сборки одного исполняемого файла php, либо нужно скомпановать компоненты приложения в один файл (что уже противоречит стандартам и соглашениям разработки на php), либо собрать phar архив, который и собрать и сопровождать легко не получится. php плох для создания легковесных исполняемых скриптов в виде одного файла.

Несколько позже, разработка других проектов на Python позволила получить опыт создания легковесных, эффективных CLI приложений — поэтому заново переоценив задачу с новым видением было решено переписать утилиту с php на python.

Заключение

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

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


github: https://github.com/unsektor/worklog/