README.md
Особенности реализации
Решения, принятые в ходе разработки
Как работает сама функция.
Функция prepareData получает массив сущностей для обработки и объект с полем sprintId
. Далее функция проходится циклом по всем сущностям, проверяя уникальность каждой (проверка на уникальность добавлена для устойчивости и требует лишь дополнительной памяти на хранение id
пройденных сущностей).
Здесь важно отметить, что в задании было сказано о том, что данные могут быть n-ого уровня вложенности, а также сказано, что работа функции не должна зависеть от платформы (Node.js или браузер), поэтому я решил использовать связанный список, так как у рекурсии в браузерах есть максимальная глубина - 10 000 (в некоторых 100 000).
Работа со связанным списком обстоит следующим образом: при обработке каждой сущности создаётся экземпляр связанного списка, и если во время обработки встречается вложенная сущность, то она добавляется в конец данного списка. Список обходится до конца (если встречаются вложенности, то список увеличивается) и происходит переход к следующей сущности.
Обработка сущности
Для каждой сущности, которую необходимо сохранить выделяется отдельный обработчик, что удобно при тестировании. Всё хранение данных вынесено в отдельный объект - экземпляр класса Storage. Класс используется, так как с помощью него можно при каждом вызове функции создавать экземпляр хранилища (если использовать статичное хранилище, то его придётся чистить, что неудобно).
likes
, users
и summaries
сохраняются в Map структуры, как ключ используется свойство id
сущности (для likes
используется не id
комментария, а id
автора комментария). Используется именно Map структуры, так как это упростит доступ к likes
конкретного пользователя, к конкретному user
'у и к конкретной summary
, соответственно. Без Map структур при необходимости получить конкретного пользователя приходилось бы перебирать всех пользователей, но такой подход имел бы линейную асимптотику, а использование Map (так как Map является хэш-таблицей) имеет константную асимптотику.
Что касается сущностей sprints и commits, то они сохраняются в массивы, так как перебирать их придётся всех в любом случае. У commits есть ещё дополнительно хранилище - Map структура для связывания commit.id
и массива принадлежащих коммиту summary.id
's.
Пометка: во время обработки спринтов дополнительно сохраняется активный спринт в отдельную переменную для удобства.
Работа с сущностями после обработки
После обработки всех сущностей можно подготавливать данные дальше.
Здесь используется функция sortByProperty
, которая использует метод массива .sort()
с определённой функцией (в случае равенства сравниваемых значений будет использована сортировка по id
- данная функция используется только для сущностей). Сначала, данная функция используется для сортировки спринтов по id
, что необходимо для распределения коммитов по спринтам.
Распределение коммитов происходит с помощью бинарного поиска. Бинарный поиск выгоднее использовать, чем стандартный перебор всех возможных спринтов (асимптотика бинарного поиска - логарифмическая, а перебора - линейная).
Связка коммитов со спринтами нам нужна для 3 последних слайдов (chart
, diagram
и activity
) - для подсчёта общего количества за спринт, для сбора характеристик для активного и прошлого спринта, для составления тепловой карты (карты активности) по активному спринту. Поэтому во время распределения коммитов по спринтам дополнительно сохраняются ссылки на коммиты активного спринта и на коммиты прошлого спринта.
Вычисление статистики происходит с помощью ранее сохранённых ссылок на коммиты активного и прошлого спринтов. По commit.id
получаем массив принадлежащих ему summary
(Map структура commitSummaries
) и далее берётся сам объект summary
по его id
(Map структура summaries
).
Определения времени коммита происходит с помощью объекта Date
. Время не UTC
, используется локальное время.
Тестирование
Во время тестирования использовались вспомогательные 'генераторы', что упрощало процесс написания тестов.
В папке ./examples
есть дополнительные тесты - ./examples/heatMapTest.js
и ./examples/bigTest.js
, которые нужны были для проверки результатов работы функции с приведённым примером (данные на вход - ./examples/input.json
, ожидаемый результат - ./examples/output.json
; полученный результат сравнивается с помощью assert.deepStrictEqual
и дополнительно сохраняется в отдельный файл - ./examples/output.dev.json
).
Использованные технологии
- webpack Я использовал Webpack, так как это популярный сборщик с большим количеством удобных плагинов и лоадеров. Также, у меня есть опыт работы с ним, поэтому не нужно перед разработкой что-то дополнительно изучать.
- Jest При выборе фреймворка для тестирования, я сравнивал их популярность и смотрел на возможности. Больше всего мне понравился фреймворк Jest, который имеет очень большую популярность (11 млн скачиваний в месяц с npm), подробную документацию и удобен в использовании.
- ESLint (с конфигом - airbnb-base) Для отслеживания качества кода был использован ESLint вместе с конфигом от airbnb.
- husky Данный пакет удобно использовать с ESLint - в репозиторий не сможет попасть некачественный код.
- Uglify.js Uglify.js сильно уменьшает размер итоговой сборки, что полезно.
Способы подключения функции prepareData:
-
Для получения доступа к функции в окружении Node.js нужно:
const { prepareData } = require('./index'); // можно что-то делать с этой функций далее
-
Для доступа в браузере нужно импортировать index.js и далее глобально будет доступна функция prepareData. Пример HTML:
<script src="index.js"></script> <!-- Функция может быть вызвана так: --> <script> prepareData(slides, { sprintId: 1 }); </script>
-
Функция, также, доступна для импорта в стиле AMD.