Skip to content

Commit

Permalink
Feature: Build markdown table
Browse files Browse the repository at this point in the history
  • Loading branch information
manuelmhtr committed Dec 3, 2024
1 parent 0f373bb commit c510552
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 70 deletions.
1 change: 1 addition & 0 deletions src/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const getSlackLimits = () => ({
const getTeamsBytesLimit = () => 27_000;
const getGithubApiUrl = () => process.env.GITHUB_API_URL || 'https://api.github.com';
const getGithubServerUrl = () => process.env.GITHUB_SERVER_URL || 'https://github.com';

module.exports = {
getSlackLimits,
getTeamsBytesLimit,
Expand Down
43 changes: 43 additions & 0 deletions src/config/stats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const { durationToString, isNil } = require('../utils');

const noParse = (value) => String(value ?? '-');

const toFixed = (decimals) => (value) => (isNil(value) ? '-' : value.toFixed(decimals));

const STATS = {
totalReviews: {
id: 'totalReviews',
sortOrder: 'DESC',
parser: noParse,
},
timeToReview: {
id: 'timeToReview',
sortOrder: 'ASC',
parser: durationToString,
},
totalComments: {
id: 'totalComments',
sortOrder: 'DESC',
parser: noParse,
},
commentsPerReview: {
id: 'commentsPerReview',
sortOrder: 'DESC',
parser: toFixed(2),
},
openedPullRequests: {
id: 'openedPullRequests',
sortOrder: 'DESC',
parser: noParse,
},
};

const VALID_STATS = Object.keys(STATS);

const DEFAULT_STATS = ['totalReviews', 'timeToReview', 'totalComments'];

module.exports = {
STATS,
VALID_STATS,
DEFAULT_STATS,
};
4 changes: 3 additions & 1 deletion src/i18n/locales/en-US/table.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
"columns": {
"avatar": "",
"username": "User",
"commentsPerReview": "Comments per review",
"timeToReview": "Time to review",
"totalReviews": "Total reviews",
"totalComments": "Total comments"
"totalComments": "Total comments",
"openedPullRequests": "Opened PRs"
},
"footer": "<sup>⚡️ [Pull request stats](https://bit.ly/pull-request-stats)</sup>"
}
69 changes: 0 additions & 69 deletions src/interactors/__tests__/mocks/reviewersWithStats.json

This file was deleted.

64 changes: 64 additions & 0 deletions src/interactors/buildMarkdown/__tests__/getMarkdownContent.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const { t } = require('../../../i18n');
const { table } = require('../../../../tests/mocks');
const getMarkdownContent = require('../getMarkdownContent');

const HEADERS = [
'',
t('table.columns.username'),
t('table.columns.totalReviews'),
t('table.columns.timeToReview'),
t('table.columns.totalComments'),
t('table.columns.commentsPerReview'),
t('table.columns.openedPullRequests'),
];

const AVATAR1_SM = '<a href="https://github.com/user1"><img src="https://avatars.githubusercontent.com/u/user1" width="20"></a>';
const AVATAR1_LG = '<a href="https://github.com/user1"><img src="https://avatars.githubusercontent.com/u/user1" width="32"></a>';

const ROW1_SIMPLE = [
AVATAR1_LG,
'user1<br/>🥇',
'**4**<br/>▀▀▀▀▀▀▀▀',
'[34m](https://app.flowwer.dev/charts/review-time/1)<br/>▀▀',
'1<br/>▀▀',
'0.25<br/>',
'7<br/>▀▀',
];

const ROW1_NO_CHARTS = [
AVATAR1_SM,
'user1',
'**4**',
'[34m](https://app.flowwer.dev/charts/review-time/1)',
'1',
'0.25',
'7',
];

describe('Interactors | .buildMarkdown | .getMarkdownContent', () => {
it('returns the default case data', () => {
const response = getMarkdownContent({ table });
expect(response.length).toEqual(table.rows.length + 1);
expect(response[0]).toEqual(HEADERS);
expect(response[1]).toEqual(ROW1_SIMPLE);
});

it('sets a small avatar size when there a no charts', () => {
const tableCopy = { ...table };
tableCopy.rows[0].user.emoji = null;

const response = getMarkdownContent({ table: tableCopy });
expect(response[1][0]).toEqual(AVATAR1_SM);
expect(response[1][1]).toEqual('user1');
});

it('does not add a character chart when stats have no chart value', () => {
const tableCopy = { ...table };
tableCopy.rows[0].user.emoji = null;
tableCopy.rows[0].stats = tableCopy.rows[0].stats
.map((stat) => ({ ...stat, chartValue: null }));

const response = getMarkdownContent({ table: tableCopy });
expect(response[1]).toEqual(ROW1_NO_CHARTS);
});
});
15 changes: 15 additions & 0 deletions src/interactors/buildMarkdown/__tests__/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { table } = require('../../../../tests/mocks');
const buildTable = require('../index');

const EXPECTED_RESPONSE = `| | User | Total reviews | Time to review | Total comments | Comments per review | Opened PRs |
| ----------------------------------------------------------------------------------------------------------- | ------------ | ------------------ | ------------------------------------------------------------------ | ------------------ | ------------------- | ------------------- |
| <a href="https://github.com/user1"><img src="https://avatars.githubusercontent.com/u/user1" width="32"></a> | user1<br/>🥇 | **4**<br/>▀▀▀▀▀▀▀▀ | [34m](https://app.flowwer.dev/charts/review-time/1)<br/>▀▀ | 1<br/>▀▀ | 0.25<br/> | 7<br/>▀▀ |
| <a href="https://github.com/user2"><img src="https://avatars.githubusercontent.com/u/user2" width="32"></a> | user2<br/>🥈 | 1<br/>▀▀ | [2h 21m](https://app.flowwer.dev/charts/review-time/2)<br/>▀▀▀▀▀▀▀ | **5**<br/>▀▀▀▀▀▀▀▀ | **5**<br/>▀▀▀▀▀▀▀▀ | 3<br/>▀ |
| <a href="https://github.com/user3"><img src="https://avatars.githubusercontent.com/u/user3" width="32"></a> | user3<br/>🥉 | 0<br/> | [**17m**](https://app.flowwer.dev/charts/review-time/3)<br/>▀ | 0<br/> | 1<br/>▀▀ | **30**<br/>▀▀▀▀▀▀▀▀ |`;

describe('Interactors | .buildTable', () => {
it('builds a formatted markdown table', () => {
const response = buildTable({ table });
expect(response).toEqual(EXPECTED_RESPONSE);
});
});
76 changes: 76 additions & 0 deletions src/interactors/buildMarkdown/getMarkdownContent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
const { isNil } = require('../../utils');

const EMOJIS_MAP = {
medal1: String.fromCodePoint(0x1F947), /* 🥇 */
medal2: String.fromCodePoint(0x1F948), /* 🥈 */
medal3: String.fromCodePoint(0x1F949), /* 🥉 */
};

const AVATAR_SIZE = {
SMALL: 20,
LARGE: 32,
};

const NEW_LINE = '<br/>';

const CHART_CHARACTER = '▀';

const CHART_MAX_LENGTH = 10;

const generateChart = (percentage = 0) => {
const length = Math.round(percentage * CHART_MAX_LENGTH);
const chart = Array(length).fill(CHART_CHARACTER).join('');
return `${NEW_LINE}${chart}`;
};

const buildLink = (href, content) => `<a href="${href}">${content}</a>`;

const buildImage = (src, width) => `<img src="${src}" width="${width}">`;

const markdownBold = (value) => `**${value}**`;

const markdownLink = (text, link) => `[${text}](${link})`;

const buildHeader = ({ text }) => (text);

const buildHeaders = ({ headers }) => [
'', // Empty header for the avatar
...headers.map(buildHeader),
];

const buildAvatar = ({ image, link, avatarSize }) => buildLink(link, buildImage(image, avatarSize));

const buildUsername = ({ text, emoji }) => (emoji ? `${text}${NEW_LINE}${EMOJIS_MAP[emoji]}` : text);

const buildStat = ({
text,
link,
chartValue,
bold,
}) => {
const bolded = bold ? markdownBold(text) : text;
const linked = link ? markdownLink(bolded, link) : bolded;
const chart = isNil(chartValue) ? '' : generateChart(chartValue);
return `${linked}${chart}`;
};

const buildRows = ({ table, avatarSize }) => table.rows.map((row) => [
buildAvatar({ ...row.user, avatarSize }),
buildUsername(row.user),
...row.stats.map((stat) => buildStat(stat)),
]);

module.exports = ({
table,
}) => {
const firstUser = table.rows[0].user;
const avatarSize = firstUser.emoji ? AVATAR_SIZE.LARGE : AVATAR_SIZE.SMALL;

const headers = buildHeaders(table);
const rows = buildRows({ table, avatarSize });

return [
headers,
...rows,
];
};
7 changes: 7 additions & 0 deletions src/interactors/buildMarkdown/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const toMarkdownTable = require('markdown-table');
const getMarkdownContent = require('./getMarkdownContent');

module.exports = ({ table }) => {
const content = getMarkdownContent({ table });
return toMarkdownTable(content);
};

0 comments on commit c510552

Please sign in to comment.