Skip to content

Commit

Permalink
Feature: Refactor stats calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
manuelmhtr committed Nov 20, 2024
1 parent 5cfbbb7 commit 60d7950
Show file tree
Hide file tree
Showing 36 changed files with 594 additions and 106 deletions.
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ inputs:
description: 'The maximum number of rows to display in the table. A value of `0` means unlimited.'
required: false
default: 0
stats:
description: 'A comma separated list of the stats to be calculated and displayed.'
required: false
default: 'REVIEWS,TIME,COMMENTS'
charts:
description: 'Whether to add charts to the stats or not. Possible values: "true" or "false"'
required: false
Expand Down
46 changes: 5 additions & 41 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,17 @@
const get = require('lodash.get');
const core = require('@actions/core');
const github = require('@actions/github');
const execute = require('./execute');
const { parseInputs } = require('./parsers');
const { t } = require('./i18n');

const parseArray = (value) => value.split(',');

const getPeriod = () => {
const MAX_PERIOD_DATE = 365;
const value = parseInt(core.getInput('period'), 10);
return Math.min(value, MAX_PERIOD_DATE);
};

const getRepositories = (currentRepo) => {
const input = core.getInput('repositories');
return input ? parseArray(input) : [currentRepo];
};

const getPrId = () => get(github, 'context.payload.pull_request.node_id');

const getParams = () => {
const currentRepo = process.env.GITHUB_REPOSITORY;
const githubToken = core.getInput('githubToken');
const personalToken = core.getInput('token') || githubToken;

return {
return parseInputs({
core,
github,
currentRepo,
githubToken,
personalToken,
org: core.getInput('organization'),
repos: getRepositories(currentRepo),
sortBy: core.getInput('sortBy') || core.getInput('sort-by'),
publishAs: core.getInput('publishAs') || core.getInput('publish-as'),
periodLength: getPeriod(),
displayCharts: core.getBooleanInput('charts'),
disableLinks: core.getBooleanInput('disableLinks') || core.getBooleanInput('disable-links'),
pullRequestId: getPrId(),
limit: parseInt(core.getInput('limit'), 10),
excludeStr: core.getInput('exclude'),
telemetry: core.getBooleanInput('telemetry'),
webhook: core.getInput('webhook'),
slack: {
webhook: core.getInput('slackWebhook') || core.getInput('slack-webhook'),
channel: core.getInput('slackChannel') || core.getInput('slack-channel'),
},
teams: {
webhook: core.getInput('teamsWebhook') || core.getInput('teams-webhook'),
},
};
});
};

const run = async () => {
Expand Down
56 changes: 56 additions & 0 deletions src/interactors/__tests__/mergeStats.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const mergeStats = require('../mergeStats');
const { VALID_STATS } = require('../../config/stats');

Check failure on line 2 in src/interactors/__tests__/mergeStats.test.js

View workflow job for this annotation

GitHub Actions / lint

Unable to resolve path to module '../../config/stats'

Check failure on line 2 in src/interactors/__tests__/mergeStats.test.js

View workflow job for this annotation

GitHub Actions / lint

Missing file extension for "../../config/stats"
const { authors, reviewStats, pullRequestStats } = require('../../../tests/mocks');

describe('Interactors | .mergeStats', () => {
const baseParams = {
authors,
reviewStats,
pullRequestStats,
};

it('returns an array with all the stats for each author', async () => {
const results = mergeStats(baseParams);
expect(results.length).toEqual(authors.length);

results.forEach((result) => {
expect(result).toHaveProperty('author');
expect(result.author).toHaveProperty('login');
expect(result).toHaveProperty('stats');
VALID_STATS.forEach((stat) => {
expect(result.stats).toHaveProperty(stat);
});
});
});

it('returns all the stats for authors with data', async () => {
const results = mergeStats(baseParams)
.find(({ author }) => author.login === 'user1');

expect(results.stats).toEqual({
totalReviews: 4,
totalComments: 1,
timeToReview: 2052500,
commentsPerReview: 0.25,
openedPullRequests: 17,
});
});

it('returns empty stats for authors with no data', async () => {
const results = mergeStats(baseParams)
.find(({ author }) => author.login === 'user4');

expect(results.stats).toEqual({
timeToReview: null,
totalReviews: null,
totalComments: null,
commentsPerReview: null,
openedPullRequests: null,
});
});

it('returns empty array when no authors passed', async () => {
const results = mergeStats({ ...baseParams, authors: [] });
expect(results).toEqual([]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { reviews: input } = require('../../../../tests/mocks');
const calculatePullRequestStats = require('../calculatePullRequestStats');

describe('Interactors | getPullRequestStats | .calculatePullRequestStats', () => {
const result = calculatePullRequestStats(input);

it('calculates the openedPullRequests', () => {
expect(result.openedPullRequests).toBe(3);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { pullRequests: input } = require('../../../../tests/mocks');
const groupPullRequests = require('../groupPullRequests');

const getPRsByAuthorId = (data, authorId) => {
const { pullRequests } = data.find((review) => review.authorId === authorId);
return pullRequests.map(({ id }) => id);
};

describe('Interactors | getPullRequestStats | .groupPullRequests', () => {
it('groups pull requests by author', () => {
const result = groupPullRequests(input);
expect(result.length).toEqual(2);
const authorIds = result.map((pr) => pr.authorId);
expect(authorIds).toContain('1031639', '2009676');
expect(getPRsByAuthorId(result, '1031639')).toEqual([12345]);
expect(getPRsByAuthorId(result, '2009676').sort()).toEqual([12346].sort());
});

it('keeps only the required properties', () => {
const result = groupPullRequests(input);
result.forEach(({ pullRequests }) => pullRequests.forEach((pullRequest) => {
expect(pullRequest).toHaveProperty('id');
expect(pullRequest).toHaveProperty('submittedAt');
expect(pullRequest).not.toHaveProperty('cursor');
expect(pullRequest).not.toHaveProperty('reviews');
}));
});
});
19 changes: 19 additions & 0 deletions src/interactors/getPullRequestStats/__tests__/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const { pullRequests: input } = require('../../../../tests/mocks');
const getPullRequestStats = require('../index');

const getAuthorIds = (reviewers) => reviewers.map((r) => r.authorId);

describe('Interactors | getPullRequestStats', () => {
it('groups pull requests by author and calculate its stats', () => {
const result = getPullRequestStats(input);
expect(result.length).toEqual(2);
expect(getAuthorIds(result)).toContain('1031639', '8755542');

result.forEach((author) => {
expect(author).toHaveProperty('authorId');

expect(author).toHaveProperty('stats');
expect(author.stats).toHaveProperty('openedPullRequests');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = (pulls) => {
const openedPullRequests = pulls.length;

return {
openedPullRequests,
};
};
15 changes: 15 additions & 0 deletions src/interactors/getPullRequestStats/groupPullRequests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module.exports = (pulls) => {
const byAuthor = pulls.reduce((acc, pull) => {
const authorId = pull.author.id;

if (!acc[authorId]) acc[authorId] = { authorId, pullRequests: [] };

acc[authorId].pullRequests.push({
id: pull.id,
submittedAt: pull.submittedAt,
});
return acc;
}, {});

return Object.values(byAuthor);
};
8 changes: 8 additions & 0 deletions src/interactors/getPullRequestStats/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const calculatePullRequestStats = require('./calculatePullRequestStats');
const groupPullRequests = require('./groupPullRequests');

module.exports = (pulls) => groupPullRequests(pulls)
.map(({ authorId, pullRequests }) => {
const stats = calculatePullRequestStats(pullRequests);
return { authorId, pullRequests, stats };
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const input = require('./mocks/reviews');
const { reviews: input } = require('../../../../tests/mocks');
const calculateReviewsStats = require('../calculateReviewsStats');

describe('Interactors | getReviewers | .calculateReviewsStats', () => {
describe('Interactors | getReviewStats | .calculateReviewsStats', () => {
const result = calculateReviewsStats(input);

it('calculates the totalReviews', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
const input = require('./mocks/pullRequests');
const { pullRequests: input } = require('../../../../tests/mocks');
const groupReviews = require('../groupReviews');

const getReviewsByAuthor = (data, login) => {
const { reviews } = data.find(({ author }) => author.login === login);
const getReviewsByAuthorId = (data, authorId) => {
const { reviews } = data.find((review) => review.authorId === authorId);
return reviews.map(({ id }) => id);
};

describe('Interactors | getReviewers | .groupReviews', () => {
describe('Interactors | getReviewStats | .groupReviews', () => {
it('groups reviews by author', () => {
const result = groupReviews(input);
expect(result.length).toEqual(2);
const authors = result.map((r) => r.author.login);
expect(authors).toContain('manuelmhtr', 'jartmez');
expect(getReviewsByAuthor(result, 'manuelmhtr')).toEqual([9876]);
expect(getReviewsByAuthor(result, 'jartmez').sort()).toEqual([5679, 9877].sort());
const authorIds = result.map((r) => r.authorId);
expect(authorIds).toContain('1031639', '8755542');
expect(getReviewsByAuthorId(result, '1031639')).toEqual([9876]);
expect(getReviewsByAuthorId(result, '8755542').sort()).toEqual([5679, 9877].sort());
});

it('removes reviews marked as own pull request', () => {
const result = groupReviews(input);
expect(getReviewsByAuthor(result, 'manuelmhtr')).not.toContain([5678]);
expect(getReviewsByAuthorId(result, '1031639')).not.toContain([5678]);
});

it('keeps only the required properties', () => {
Expand Down
22 changes: 22 additions & 0 deletions src/interactors/getReviewStats/__tests__/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const { pullRequests: input } = require('../../../../tests/mocks');
const getReviewers = require('../index');

const getAuthorIds = (reviewers) => reviewers.map((r) => r.authorId);

describe('Interactors | getReviewStats', () => {
it('groups reviews by author and calculate its stats', () => {
const result = getReviewers(input);
expect(result.length).toEqual(2);
expect(getAuthorIds(result)).toContain('1031639', '8755542');

result.forEach((reviewer) => {
expect(reviewer).toHaveProperty('authorId');

expect(reviewer).toHaveProperty('reviews');
expect(reviewer.reviews.length > 0).toBe(true);

expect(reviewer).toHaveProperty('stats');
expect(reviewer.stats).toHaveProperty('timeToReview');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ module.exports = (pulls) => {

const byAuthor = all.reduce((acc, review) => {
const { author, isOwnPull, ...other } = review;
const key = author.id;
const authorId = author.id;

if (!acc[key]) acc[key] = { author, reviews: [] };
if (!acc[authorId]) acc[authorId] = { authorId, reviews: [] };

acc[key].reviews.push(other);
acc[authorId].reviews.push(other);
return acc;
}, {});

Expand Down
8 changes: 8 additions & 0 deletions src/interactors/getReviewStats/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const calculateReviewsStats = require('./calculateReviewsStats');
const groupReviews = require('./groupReviews');

module.exports = (pulls) => groupReviews(pulls)
.map(({ authorId, reviews }) => {
const stats = calculateReviewsStats(reviews);
return { authorId, reviews, stats };
});
30 changes: 0 additions & 30 deletions src/interactors/getReviewers/__tests__/index.test.js

This file was deleted.

14 changes: 0 additions & 14 deletions src/interactors/getReviewers/index.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const filterReviewer = require('../filterReviewer');
const filterUser = require('../filterUser');

describe('Interactors | getReviewers | .filterReviewer', () => {
describe('Interactors | getUsers | .filterUser', () => {
const reviewers = [
'manuelmhtr',
'jartmez',
Expand All @@ -10,7 +10,7 @@ describe('Interactors | getReviewers | .filterReviewer', () => {

it('filters out reviewers by a list of usernames', () => {
const exclude = ['manuelmhtr', 'jartmez'];
const results = reviewers.filter((reviewer) => filterReviewer(exclude, reviewer));
const results = reviewers.filter((reviewer) => filterUser(exclude, reviewer));
expect(results.length).toEqual(2);
expect(results).toEqual([
'bot1',
Expand All @@ -20,7 +20,7 @@ describe('Interactors | getReviewers | .filterReviewer', () => {

it('filters out reviewers by a regular expression', () => {
const exclude = /bot/;
const results = reviewers.filter((reviewer) => filterReviewer(exclude, reviewer));
const results = reviewers.filter((reviewer) => filterUser(exclude, reviewer));
expect(results.length).toEqual(2);
expect(results).toEqual([
'manuelmhtr',
Expand Down
Loading

0 comments on commit 60d7950

Please sign in to comment.