Анализ ваших git-коммитов: Git Commit Analyzer

Я люблю аналитику и люблю git — поэтому я создал анализатор коммитов git 🙌.

Веб-приложение работает с вашим локальным git-репозиторием, а также интегрируется с GitHub.

Попробуйте его здесь или посмотрите репозиторий на GitHub.

Обзор

Git Commit Analyzer читает ваш журнал git, разбирает его на составляющие, а затем позволяет изучить его с помощью ряда удобных визуализаций.

Вы также можете выбрать импорт проекта непосредственно из GitHub.

Реализация

Если вам интересно, как я реализовал веб-приложение, надеюсь, этот раздел даст вам некоторое представление.

На целевой странице веб-приложения пользователь может либо загрузить git log из локального репозитория, либо продолжить интеграцию с GitHub. В каждом случае данные будут получены, проверены, и пользователь сможет перейти на приборную панель. На приборной панели пользователь может создавать пользовательские визуализации для репозитория.

Технологии

Веб-приложение реализовано с использованием Vue.js и JavaScript. Я использую Bulma в качестве чистого CSS-фреймворка. Веб-приложение развернуто на бесплатном уровне Netlify. Я использовал Jest и Vue Test Utils для модульного тестирования реализации. Визуализация приборной панели реализована с помощью Vue Plotly.

Настройка рабочего процесса OAuth может быть несколько утомительной. К счастью, Pizzly предлагает удивительно простой способ позаботиться об этом. Pizzly предоставляет прокси-сервер для более чем 80 интеграций OAuth, и вы можете бесплатно развернуть свой собственный экземпляр на Heroku (как это сделал я).

Подведем итоги:

  • JavaScript
  • Vue.js как веб-фреймворк
  • Vue Test Utils для тестирования
  • Vue Plotly для визуализации
  • Bulma как CSS фреймворк
  • Pizzly для обработки GitHub OAuth
  • Netlify для развертывания

Разбор журнала Git

Необработанный файл журнала git разбивается на отдельные коммиты с помощью регулярных выражений и преобразуется в объекты Commit, которые выглядят примерно так:

class Commit:
  hash: String
  authorName: String
  authorMail: String
  timestamp: Date
  message: String
  nodes: Array[Node]

class Node:
  path: String
  additions: Integer
  deletions: Integer
Войти в полноэкранный режим Выйти из полноэкранного режима

Если журнал git импортируется из GitHub, история JSON преобразуется в объекты Commit аналогичным образом.

Интеграция с GitHub

Импорт истории коммитов из GitHub обычно довольно прост с помощью официального API GitHub. Однако настройка полного рабочего процесса OAuth для аутентификации в GitHub API может сделать этот процесс более трудоемким.

В процессе разработки я наткнулся на Pizzly, сервис с открытым исходным кодом, который действует как прокси-сервис для более чем 80 часто используемых API, среди которых GitHub. Вы можете бесплатно развернуть свой собственный экземпляр Pizzly на Heroku и использовать его для управления всеми вызовами API.

Рабочий процесс OAuth сводится к нескольким строкам кода:

import Pizzly from "pizzly-js";

// get environment variables
const HOST = process.env.VUE_APP_PIZZLY_HOST
const SECRET = process.env.VUE_APP_PIZZLY_SECRET_KEY

// create pizzly instance and integration instance
const pizzly = new Pizzly({host: HOST, publishableKey: SECRET})
const githubApi = pizzly.integration('github')

/**
* Perform the OAuth workflow using the GitHub API.
* @return authId
**/
const authenticate = function() {
    return githubApi.connect()
}
Вход в полноэкранный режим Выход из полноэкранного режима

Вызовите функцию authenticate, и Pizzly позаботится о вашей аутентификации.

Для примера, чтобы получить названия репозиториев с определенной страницы API, вы можете вызвать следующую функцию. Вам также нужно передать authId, возвращаемый во время процесса аутентификации.

/**
* Get the names of the repositories of a given page of the GitHub API.
* @param page (Number) page to get
* @param perPage (Number) entries per page
* @param authId (String) authentication ID from the auth workflow
* @return (Array) repository names 
**/
const getRepoPage = function(page, perPage, authId) {
    return githubApi
        .auth(authId)
        .get('/user/repos', {
            headers: {"Content-Type": "application/vnd.github.v3+json"},
            query: {"page": page, "per_page": perPage, "visibility": "all"}
        })
        .then(res => res.json())
        .then(jsn => jsn.map(e => e.name))
}
Вход в полноэкранный режим Выход из полноэкранного режима

Довольно аккуратно, вы не находите?

Работа с данными

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

Реализовывать это было очень весело! Используя разобранный журнал git, содержащий объекты Commit (как описано выше), я определил ряд функций, которые могут быть применены к массиву коммитов.

Эти функции делятся на две категории: ключевые и функции значения.

Ключевые функции берут объект Commit и извлекают определенное ключевое значение (например, хэш, дату, автора и т.д.). Функции значения берут массив объектов Commit и суммируют их по одному значению (например, количество фиксаций, добавлений, удалений).

При такой настройке мы можем взять массив объектов Commit и суммировать его по определенной ключевой функции с помощью функции значения. Например, мы можем получить количество коммитов (значение) для каждого автора (ключ).

Рассмотрим следующий класс LogHandler, который определяет aggregateBy и groupBy, а также функцию значения vfNumCommits и функцию ключа kfAuthorName.

class LogHandler {
    constructor(gitlog) {
        this.data = [...gitlog.log]
    }

    // Key function for name of author
    static kfAuthorName(obj) {
        return obj.authorName
    }

    // Value function for number of commits
    static vfNumCommits(array) {
        return array.length
    }

    /**
     * Group by a key function.
     * @param keyFunc: function to get the key per commit
     * */
    groupBy(keyFunc) {
        return this.data.reduce((agg, next) => {
            const curKeyValue = keyFunc(next)
            curKeyValue in agg ? agg[curKeyValue].push(next) : agg[curKeyValue] = [next]
            return agg
        }, {})
    }

    /**
     * Aggregator for top level keys of the Gitlog object.
     * @param keyFunc: function to get the key per commit
     * @param valueFunc: function to aggregate by
     * */
    aggregateBy(keyFunc, valueFunc) {
        const grouped = this.groupBy(keyFunc)
        Object.keys(grouped).forEach((k) => {
            grouped[k] = {
                value: valueFunc(grouped[k]),
            }
        })
        return grouped
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Если мы инстанцируем LogHandler с нашим журналом git, мы можем вызвать aggregateBy(LogHandler.kfAuthorName, LogHandler.vfNumCommits), мы получим объект, содержащий количество коммитов для каждого автора, вот так:

{
  "Alice" : {"value" : 42},
  "Bob" : {"value" : 13}
}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь, что если мы захотим сгруппировать эти результаты по годам, т.е. количество коммитов для каждого автора за каждый год.

Мы можем определить еще один метод в классе LogHandler, который называется groupAggregateBy и ключевую функцию для года kfYear.

static kfYear(obj) {
    return obj.timestamp.getFullYear()
}

groupAggregateBy(groupFunc, keyFunc, valueFunc) {
    const grouped = this.data.reduce((agg, next) => {
        const curKey = [keyFunc(next), groupFunc(next)]
        curKey in agg ? agg[curKey].push(next) : agg[curKey] = [next]
        return agg
    }, {})
    Object.keys(grouped).forEach((k) => {
        grouped[k] = {
            key: keyFunc(grouped[k][0]),
            group: groupFunc(grouped[k][0]),
            value: valueFunc(grouped[k])
        }
    })
    return grouped
}
Вход в полноэкранный режим Выход из полноэкранного режима

Функция groupAggregateBy принимает дополнительный аргумент, groupFunc, который может быть любой ключевой функцией. Каждый ключ, созданный путем применения функции key к массиву объектов Commit, является одной группой.

Продолжая наш пример, вызовем groupAggregateBy(LogHandler.kfYear, LogHandler.kfAuthorName, LogHandler.vfNumCommits), что приведет к созданию следующего объекта:

{
  "[2022,Alice]" : {"key": "Alice", "group": 2022, "value": 2}
  "[2021,Alice]" : {"key": "Alice", "group": 2021, "value": 30}
  "[2020,Alice]" : {"key": "Alice", "group": 2020, "value": 10}
  "[2022,Bob]" : {"key": "Bob", "group": 2022, "value": 10}
  "[2019,Bob]" : {"key": "Bob", "group": 2019, "value": 3}
}
Вход в полноэкранный режим Выход из полноэкранного режима

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

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

Заключение и улучшения

Я получил массу удовольствия от внедрения анализатора git-коммитов, и мне нравится то понимание, которое я получаю от него.

Есть ряд моментов, которые все еще можно улучшить:

  • Разбор расширений файлов: это было бы отличным улучшением для добавления информации о языках, используемых в репозитории.
  • Информация о ветке: сейчас информация о ветке игнорируется инструментом.
  • Сохранение сессии: сейчас визуализации теряются при обновлении страницы.
  • Общие улучшения UX: Я заметил, что пользователи, которые впервые заходят на приборную панель, интуитивно не понимают всех функций.

Тем не менее, я надеюсь, что инструмент будет интересен в использовании, и вы сможете найти новые сведения о своей истории коммитов!

Пожалуйста, не стесняйтесь обращаться ко мне с отзывами, комментариями или идеями по улучшению!

Скриншоты

Оцените статью
Procodings.ru
Добавить комментарий