From 1951d353fea9dac4c4b80ac99a6190ce332d0ddc Mon Sep 17 00:00:00 2001 From: Manuel de la Torre Date: Wed, 4 Dec 2024 21:31:16 -0600 Subject: [PATCH] Feature: Refactor message builder on Post to Slack interactor --- src/interactors/__tests__/mergeStats.test.js | 25 +++-- .../__tests__/buildReviewTimeLink.test.js | 39 +++++++ .../__tests__/calculateTotals.test.js | 37 ++++++ .../__tests__/getContributions.test.js | 16 +++ .../fulfillEntries/__tests__/index.test.js | 25 +++++ .../fulfillEntries/buildReviewTimeLink.js | 49 ++++++++ .../fulfillEntries/calculateTotals.js | 10 ++ .../fulfillEntries/getContributions.js | 13 +++ src/interactors/fulfillEntries/index.js | 18 +++ src/interactors/mergeStats.js | 19 ++-- .../postSlackMessage/__tests__/index.test.js | 8 +- .../__tests__/buildReviewer.test.js | 105 ------------------ .../buildMessage/__tests__/buildRow.test.js | 90 +++++++++++++++ .../buildMessage/__tests__/index.test.js | 41 +++---- .../buildMessage/buildReviewer.js | 72 ------------ .../postSlackMessage/buildMessage/buildRow.js | 55 +++++++++ .../postSlackMessage/buildMessage/index.js | 20 ++-- src/interactors/postSlackMessage/index.js | 10 +- 18 files changed, 409 insertions(+), 243 deletions(-) create mode 100644 src/interactors/fulfillEntries/__tests__/buildReviewTimeLink.test.js create mode 100644 src/interactors/fulfillEntries/__tests__/calculateTotals.test.js create mode 100644 src/interactors/fulfillEntries/__tests__/getContributions.test.js create mode 100644 src/interactors/fulfillEntries/__tests__/index.test.js create mode 100644 src/interactors/fulfillEntries/buildReviewTimeLink.js create mode 100644 src/interactors/fulfillEntries/calculateTotals.js create mode 100644 src/interactors/fulfillEntries/getContributions.js create mode 100644 src/interactors/fulfillEntries/index.js delete mode 100644 src/interactors/postSlackMessage/buildMessage/__tests__/buildReviewer.test.js create mode 100644 src/interactors/postSlackMessage/buildMessage/__tests__/buildRow.test.js delete mode 100644 src/interactors/postSlackMessage/buildMessage/buildReviewer.js create mode 100644 src/interactors/postSlackMessage/buildMessage/buildRow.js diff --git a/src/interactors/__tests__/mergeStats.test.js b/src/interactors/__tests__/mergeStats.test.js index 8f75d51..470f252 100644 --- a/src/interactors/__tests__/mergeStats.test.js +++ b/src/interactors/__tests__/mergeStats.test.js @@ -1,21 +1,22 @@ const mergeStats = require('../mergeStats'); const { VALID_STATS } = require('../../config/stats'); -const { authors, reviewStats, pullRequestStats } = require('../../../tests/mocks'); +const { users, reviewStats, pullRequestStats } = require('../../../tests/mocks'); describe('Interactors | .mergeStats', () => { const baseParams = { - authors, + users, reviewStats, pullRequestStats, }; - it('returns an array with all the stats for each author', async () => { + it('returns an array with all the stats for each user', async () => { const results = mergeStats(baseParams); - expect(results.length).toEqual(authors.length); + expect(results.length).toEqual(users.length); results.forEach((result) => { - expect(result).toHaveProperty('author'); - expect(result.author).toHaveProperty('login'); + expect(result).toHaveProperty('user'); + expect(result.user).toHaveProperty('login'); + expect(result).toHaveProperty('reviews'); expect(result).toHaveProperty('stats'); VALID_STATS.forEach((stat) => { expect(result.stats).toHaveProperty(stat); @@ -23,9 +24,9 @@ describe('Interactors | .mergeStats', () => { }); }); - it('returns all the stats for authors with data', async () => { + it('returns all the stats for users with data', async () => { const results = mergeStats(baseParams) - .find(({ author }) => author.login === 'user1'); + .find(({ user }) => user.login === 'user1'); expect(results.stats).toEqual({ totalReviews: 4, @@ -36,9 +37,9 @@ describe('Interactors | .mergeStats', () => { }); }); - it('returns empty stats for authors with no data', async () => { + it('returns empty stats for users with no data', async () => { const results = mergeStats(baseParams) - .find(({ author }) => author.login === 'user4'); + .find(({ user }) => user.login === 'user4'); expect(results.stats).toEqual({ timeToReview: null, @@ -49,8 +50,8 @@ describe('Interactors | .mergeStats', () => { }); }); - it('returns empty array when no authors passed', async () => { - const results = mergeStats({ ...baseParams, authors: [] }); + it('returns empty array when no users passed', async () => { + const results = mergeStats({ ...baseParams, users: [] }); expect(results).toEqual([]); }); }); diff --git a/src/interactors/fulfillEntries/__tests__/buildReviewTimeLink.test.js b/src/interactors/fulfillEntries/__tests__/buildReviewTimeLink.test.js new file mode 100644 index 0000000..9ffe85a --- /dev/null +++ b/src/interactors/fulfillEntries/__tests__/buildReviewTimeLink.test.js @@ -0,0 +1,39 @@ +const { entries } = require('../../../../tests/mocks'); +const buildReviewTimeLink = require('../buildReviewTimeLink'); + +const [entry] = entries; + +const SUCCESSFUL_LINK = "https://app.flowwer.dev/charts/review-time/~(u~(i~'user1~n~'user1)~p~30~r~(~(d~'qprzn9~t~'84)~(d~'qpvagu~t~'a3)~(d~'qpvn25~t~'3lu)~(d~'qqqtu5~t~'2vy)))"; + +const EMPTY_LINK = "https://app.flowwer.dev/charts/review-time/~(u~(i~'user1~n~'user1)~p~30~r~(~))"; + +const MAX_LENGTH = 1024; + +const buildReview = (submittedAt) => ({ + timeToReview: 1000, + submittedAt: new Date(submittedAt).toISOString(), +}); + +describe('Interactors | .buildTable | .buildReviewTimeLink', () => { + const period = 30; + + it('builds the link correctly', () => { + const response = buildReviewTimeLink(entry, period); + + expect(response).toEqual(SUCCESSFUL_LINK); + }); + + it('builds a link event with empty reviews', () => { + const emptyReviewer = { ...entry, reviews: null }; + const response = buildReviewTimeLink(emptyReviewer, period); + + expect(response).toEqual(EMPTY_LINK); + }); + + it('limits the url to less than 1,024 characters', () => { + const reviews = Array(100).fill().map((_e, index) => buildReview(index * 1000)); + const response = buildReviewTimeLink({ ...entry, reviews }, period); + expect(response.length <= MAX_LENGTH).toEqual(true); + expect(MAX_LENGTH - response.length < 16).toEqual(true); + }); +}); diff --git a/src/interactors/fulfillEntries/__tests__/calculateTotals.test.js b/src/interactors/fulfillEntries/__tests__/calculateTotals.test.js new file mode 100644 index 0000000..748c0b6 --- /dev/null +++ b/src/interactors/fulfillEntries/__tests__/calculateTotals.test.js @@ -0,0 +1,37 @@ +const stats = require('../../__tests__/mocks/stats.json'); +const statsSum = require('../../__tests__/mocks/statsSum.json'); +const calculateTotals = require('../calculateTotals'); + +describe('Interactors | .buildTable | .calculateTotals', () => { + it('sums all the stats in a array', () => { + const response = calculateTotals(stats); + expect(response).toEqual(statsSum); + }); + + it('returns the correct sum event when data contains nulls', () => { + const withNulls = { + totalReviews: undefined, + totalComments: null, + commentsPerReview: null, + timeToReview: 0, + }; + const response = calculateTotals([...stats, withNulls]); + expect(response).toEqual(statsSum); + }); + + it('returns the correct sum event when data contains an empty object', () => { + const empty = {}; + const response = calculateTotals([...stats, empty]); + expect(response).toEqual(statsSum); + }); + + it('returns all stats in zeros when receiving an empty one', () => { + const response = calculateTotals([]); + expect(response).toEqual({ + totalReviews: 0, + totalComments: 0, + commentsPerReview: 0, + timeToReview: 0, + }); + }); +}); diff --git a/src/interactors/fulfillEntries/__tests__/getContributions.test.js b/src/interactors/fulfillEntries/__tests__/getContributions.test.js new file mode 100644 index 0000000..fdadd85 --- /dev/null +++ b/src/interactors/fulfillEntries/__tests__/getContributions.test.js @@ -0,0 +1,16 @@ +const statsSum = require('../../__tests__/mocks/statsSum.json'); +const reviewers = require('../../__tests__/mocks/reviewers.json'); +const getContributions = require('../getContributions'); + +const [reviewer] = reviewers; + +describe('Interactors | .buildTable | .getContributions', () => { + it('adds the percentage of each stat vs the total', () => { + const response = getContributions(reviewer, statsSum); + expect(response).toMatchObject({ + commentsPerReview: 0.004222972972972973, + totalComments: 0.008695652173913044, + totalReviews: 0.8695652173913043, + }); + }); +}); diff --git a/src/interactors/fulfillEntries/__tests__/index.test.js b/src/interactors/fulfillEntries/__tests__/index.test.js new file mode 100644 index 0000000..e629ce5 --- /dev/null +++ b/src/interactors/fulfillEntries/__tests__/index.test.js @@ -0,0 +1,25 @@ +const { entries } = require('../../../../tests/mocks'); +const fulfillEntries = require('../index'); + +describe('Interactors | .fulfillEntries', () => { + const periodLength = 30; + + it('adds contributions to each reviewer', () => { + const response = fulfillEntries(entries, { periodLength }); + expect(response.length).toEqual(entries.length); + + response.forEach((reviewer) => { + expect(reviewer).toHaveProperty('contributions'); + }); + }); + + it('adds urls to each reviewer', () => { + const response = fulfillEntries(entries, { periodLength }); + expect(response.length).toEqual(entries.length); + + response.forEach((reviewer) => { + expect(reviewer).toHaveProperty('urls'); + expect(reviewer.urls).toHaveProperty('timeToReview'); + }); + }); +}); diff --git a/src/interactors/fulfillEntries/buildReviewTimeLink.js b/src/interactors/fulfillEntries/buildReviewTimeLink.js new file mode 100644 index 0000000..be374e2 --- /dev/null +++ b/src/interactors/fulfillEntries/buildReviewTimeLink.js @@ -0,0 +1,49 @@ +const JSURL = require('jsurl'); + +const URL = 'https://app.flowwer.dev/charts/review-time/'; +const MAX_URI_LENGTH = 1024; +const CHARS_PER_REVIEW = 16; + +const toSeconds = (ms) => Math.round(ms / 1000); + +const compressInt = (int) => int.toString(36); + +const compressDate = (date) => compressInt(Math.round(date.getTime() / 1000)); + +const parseReview = ({ submittedAt, timeToReview }) => ({ + d: compressDate(submittedAt), + t: compressInt(toSeconds(timeToReview)), +}); + +const buildUri = ({ user, period, reviews }) => { + const data = JSURL.stringify({ + u: { + i: `${user.id}`, + n: user.login, + }, + p: period, + r: reviews, + }); + + const uri = `${URL}${data}`; + const exceededLength = uri.length - MAX_URI_LENGTH; + if (exceededLength <= 0) return uri; + + // Remove at least one, but trying to guess exactly how many to remove. + const reviewsToRemove = Math.max(1, Math.ceil(exceededLength / CHARS_PER_REVIEW)); + return buildUri({ user, period, reviews: reviews.slice(reviewsToRemove) }); +}; + +module.exports = (entry, period) => { + const { user, reviews } = entry || {}; + const parsedReviews = (reviews || []) + .map((r) => ({ ...r, submittedAt: new Date(r.submittedAt) })) + .sort((a, b) => a.submittedAt - b.submittedAt) + .map(parseReview); + + return buildUri({ + user, + period, + reviews: parsedReviews, + }); +}; diff --git a/src/interactors/fulfillEntries/calculateTotals.js b/src/interactors/fulfillEntries/calculateTotals.js new file mode 100644 index 0000000..f97f82a --- /dev/null +++ b/src/interactors/fulfillEntries/calculateTotals.js @@ -0,0 +1,10 @@ +const { STATS } = require('../../constants'); + +const sumStat = (stats, statName) => stats.reduce((a, values) => a + (values[statName] || 0), 0); + +const calculateTotals = (allStats) => STATS.reduce((prev, statName) => ({ + ...prev, + [statName]: sumStat(allStats, statName), +}), {}); + +module.exports = calculateTotals; diff --git a/src/interactors/fulfillEntries/getContributions.js b/src/interactors/fulfillEntries/getContributions.js new file mode 100644 index 0000000..8f11f66 --- /dev/null +++ b/src/interactors/fulfillEntries/getContributions.js @@ -0,0 +1,13 @@ +const { STATS } = require('../../constants'); + +const calculatePercentage = (value, total) => { + if (!total) return 0; + return Math.min(1, Math.max(0, value / total)); +}; + +const getContributions = (reviewer, totals) => STATS.reduce((prev, statsName) => { + const percentage = calculatePercentage(reviewer.stats[statsName], totals[statsName]); + return { ...prev, [statsName]: percentage }; +}, {}); + +module.exports = getContributions; diff --git a/src/interactors/fulfillEntries/index.js b/src/interactors/fulfillEntries/index.js new file mode 100644 index 0000000..c4efca0 --- /dev/null +++ b/src/interactors/fulfillEntries/index.js @@ -0,0 +1,18 @@ +const buildReviewTimeLink = require('./buildReviewTimeLink'); +const getContributions = require('./getContributions'); +const calculateTotals = require('./calculateTotals'); + +const getUrls = ({ entry, periodLength }) => ({ + timeToReview: buildReviewTimeLink(entry, periodLength), +}); + +module.exports = (entries, { periodLength }) => { + const allStats = entries.map(({ stats }) => stats); + const totals = calculateTotals(allStats); + + return entries.map((entry) => ({ + ...entry, + contributions: getContributions(entry, totals), + urls: getUrls({ entry, periodLength }), + })); +}; diff --git a/src/interactors/mergeStats.js b/src/interactors/mergeStats.js index c562914..9e614bc 100644 --- a/src/interactors/mergeStats.js +++ b/src/interactors/mergeStats.js @@ -4,26 +4,27 @@ const EMPTY_STATS = VALID_STATS .reduce((acc, stat) => ({ ...acc, [stat]: null }), {}); module.exports = ({ - authors, + users, reviewStats, pullRequestStats, }) => { - const reviewStatsByAuthorId = reviewStats.reduce((acc, reviewStat) => ({ + const reviewsByUserId = reviewStats.reduce((acc, reviewsData) => ({ ...acc, - [reviewStat.authorId]: reviewStat.stats, + [reviewsData.userId]: reviewsData, }), {}); - const prStatsByAuthorId = pullRequestStats.reduce((acc, prStat) => ({ + const pullRequestsByUserId = pullRequestStats.reduce((acc, prsData) => ({ ...acc, - [prStat.authorId]: prStat.stats, + [prsData.userId]: prsData, }), {}); - return authors.map((author) => ({ - author, + return users.map((user) => ({ + user, + reviews: reviewsByUserId[user.id]?.reviews || [], stats: { ...EMPTY_STATS, - ...(reviewStatsByAuthorId[author.id] || {}), - ...(prStatsByAuthorId[author.id] || {}), + ...(reviewsByUserId[user.id]?.stats || {}), + ...(pullRequestsByUserId[user.id]?.stats || {}), }, })); }; diff --git a/src/interactors/postSlackMessage/__tests__/index.test.js b/src/interactors/postSlackMessage/__tests__/index.test.js index e12c844..c09f468 100644 --- a/src/interactors/postSlackMessage/__tests__/index.test.js +++ b/src/interactors/postSlackMessage/__tests__/index.test.js @@ -26,11 +26,9 @@ describe('Interactors | .postSlackMessage', () => { const defaultOptions = { core, isSponsor: true, - reviewers: 'REVIEWERS', + table: 'TABLE', pullRequest: 'PULl REQUEST', periodLength: 'PERIOD LENGTH', - disableLinks: 'DISPLAY LINKS', - displayCharts: 'DISPLAY CHARTS', slack: { webhook: 'https://slack.com/webhook', channel: '#my-channel', @@ -81,11 +79,9 @@ describe('Interactors | .postSlackMessage', () => { await postSlackMessage({ ...defaultOptions }); expect(error).not.toHaveBeenCalled(); expect(buildMessage).toBeCalledWith({ - reviewers: defaultOptions.reviewers, + table: defaultOptions.table, pullRequest: defaultOptions.pullRequest, periodLength: defaultOptions.periodLength, - disableLinks: defaultOptions.disableLinks, - displayCharts: defaultOptions.displayCharts, }); expect(Fetchers.postToSlack).toBeCalledTimes(1); expect(Fetchers.postToSlack).toBeCalledWith({ diff --git a/src/interactors/postSlackMessage/buildMessage/__tests__/buildReviewer.test.js b/src/interactors/postSlackMessage/buildMessage/__tests__/buildReviewer.test.js deleted file mode 100644 index 5a08cdb..0000000 --- a/src/interactors/postSlackMessage/buildMessage/__tests__/buildReviewer.test.js +++ /dev/null @@ -1,105 +0,0 @@ -const { t } = require('../../../../i18n'); -const buildReviewer = require('../buildReviewer'); -const reviewers = require('../../../__tests__/mocks/populatedReviewers.json'); - -const [reviewer] = reviewers; -const defaultParams = { - t, - reviewer, - index: 0, - disableLinks: true, - displayCharts: false, -}; - -const DIVIDER = { - type: 'divider', -}; - -const USERNAME = { - type: 'context', - elements: [ - { - type: 'image', - image_url: 'https://avatars.githubusercontent.com/u/1234', - alt_text: 'user1', - }, - { - emoji: true, - type: 'plain_text', - text: 'user1', - }, - ], -}; - -const STATS = { - type: 'section', - fields: [ - { - type: 'mrkdwn', - text: `*${t('table.columns.totalReviews')}:* 4`, - }, - { - type: 'mrkdwn', - text: `*${t('table.columns.totalComments')}:* 1`, - }, - { - type: 'mrkdwn', - text: `*${t('table.columns.timeToReview')}:* 34m`, - }, - ], -}; - -describe('Interactors | postSlackMessage | .buildReviewer', () => { - describe('simplest case', () => { - it('builds a reviewers with basic config', () => { - const response = buildReviewer({ ...defaultParams }); - expect(response).toEqual([ - USERNAME, - STATS, - DIVIDER, - ]); - }); - }); - - describe('requiring charts', () => { - it('adds a medal to username section', () => { - const response = buildReviewer({ ...defaultParams, displayCharts: true }); - expect(response).toEqual([ - { - ...USERNAME, - elements: [ - USERNAME.elements[0], - { - emoji: true, - type: 'plain_text', - text: 'user1 :first_place_medal:', - }, - ], - }, - STATS, - DIVIDER, - ]); - }); - }); - - describe('requiring links', () => { - it('adds a medal to username section', () => { - const response = buildReviewer({ ...defaultParams, disableLinks: false }); - expect(response).toEqual([ - USERNAME, - { - ...STATS, - fields: [ - STATS.fields[0], - STATS.fields[1], - { - type: 'mrkdwn', - text: `*${t('table.columns.timeToReview')}:* `, - }, - ], - }, - DIVIDER, - ]); - }); - }); -}); diff --git a/src/interactors/postSlackMessage/buildMessage/__tests__/buildRow.test.js b/src/interactors/postSlackMessage/buildMessage/__tests__/buildRow.test.js new file mode 100644 index 0000000..921795b --- /dev/null +++ b/src/interactors/postSlackMessage/buildMessage/__tests__/buildRow.test.js @@ -0,0 +1,90 @@ +const { table } = require('../../../../../tests/mocks'); +const buildRow = require('../buildRow'); + +const [row] = table.rows; +const defaultParams = { + row, + statNames: table.headers.slice(1).map(({ text }) => text), +}; + +const DIVIDER = { + type: 'divider', +}; + +const USERNAME = { + type: 'context', + elements: [ + { + type: 'image', + image_url: 'https://avatars.githubusercontent.com/u/user1', + alt_text: 'user1', + }, + { + emoji: true, + type: 'plain_text', + text: 'user1 :first_place_medal:', + }, + ], +}; + +const STATS = { + type: 'section', + fields: [ + { + type: 'mrkdwn', + text: '*Total reviews:* 4', + }, + { + type: 'mrkdwn', + text: '*Time to review:* ', + }, + { + type: 'mrkdwn', + text: '*Total comments:* 1', + }, + { + type: 'mrkdwn', + text: '*Comments per review:* 0.25', + }, + { + type: 'mrkdwn', + text: '*Opened PRs:* 7', + }, + ], +}; + +describe('Interactors | postSlackMessage | .buildRow', () => { + describe('simplest case', () => { + it('builds a reviewers with basic config', () => { + const response = buildRow({ ...defaultParams }); + expect(response).toEqual([ + USERNAME, + STATS, + DIVIDER, + ]); + }); + }); + + describe('when the user has no emoji', () => { + it('adds no medal to the username', () => { + const rowCopy = { ...row }; + rowCopy.user.emoji = null; + const response = buildRow({ ...defaultParams, row: rowCopy }); + expect(response).toEqual([ + { + ...USERNAME, + elements: [ + USERNAME.elements[0], + { + emoji: true, + type: 'plain_text', + text: 'user1', + }, + ], + }, + STATS, + DIVIDER, + ]); + }); + }); +}); diff --git a/src/interactors/postSlackMessage/buildMessage/__tests__/index.test.js b/src/interactors/postSlackMessage/buildMessage/__tests__/index.test.js index 114626e..3c311e8 100644 --- a/src/interactors/postSlackMessage/buildMessage/__tests__/index.test.js +++ b/src/interactors/postSlackMessage/buildMessage/__tests__/index.test.js @@ -1,33 +1,38 @@ +const { table } = require('../../../../../tests/mocks'); const buildMessage = require('../index'); const buildSubtitle = require('../buildSubtitle'); -const buildReviewer = require('../buildReviewer'); +const buildRow = require('../buildRow'); const SUBTITLE = 'SUBTITLE'; -const REVIEWER = 'REVIEWER'; +const ROW = 'ROW'; +const statNames = table.headers.slice(1).map(({ text }) => text); jest.mock('../buildSubtitle', () => jest.fn(() => [SUBTITLE])); -jest.mock('../buildReviewer', () => jest.fn(() => [REVIEWER])); +jest.mock('../buildRow', () => jest.fn(() => [ROW])); const defaultOptions = { - reviewers: ['REVIEWER 1'], + table, + org: 'ORG', + repos: 'REPOS', pullRequest: 'PULL REQUEST', periodLength: 'PERIOD LENGTH', - disableLinks: 'DISABLE LINKS', - displayCharts: 'DISPLAY CHARTS', }; describe('Interactors | postSlackMessage | .buildMessage', () => { beforeEach(() => { buildSubtitle.mockClear(); - buildReviewer.mockClear(); + buildRow.mockClear(); }); it('returns the expected structure', () => { - const response = buildMessage({ ...defaultOptions }); + const tableCopy = { ...table }; + tableCopy.rows = [tableCopy.rows[0]]; + + const response = buildMessage({ ...defaultOptions, table: tableCopy }); expect(response).toEqual({ blocks: [ SUBTITLE, - REVIEWER, + ROW, ], }); }); @@ -36,21 +41,19 @@ describe('Interactors | postSlackMessage | .buildMessage', () => { buildMessage({ ...defaultOptions }); expect(buildSubtitle).toHaveBeenCalledWith({ t: expect.anything(), + org: defaultOptions.org, + repos: defaultOptions.repos, pullRequest: defaultOptions.pullRequest, periodLength: defaultOptions.periodLength, }); - expect(buildReviewer).toHaveBeenCalledWith({ - t: expect.anything(), - index: 0, - reviewer: defaultOptions.reviewers[0], - disableLinks: defaultOptions.disableLinks, - displayCharts: defaultOptions.displayCharts, + expect(buildRow).toHaveBeenCalledWith({ + row: table.rows[0], + statNames, }); }); - it('builds a reviewers per each passed', () => { - const reviewers = ['REVIEWER 1', 'REVIEWER 2', 'REVIEWER 3']; - buildMessage({ ...defaultOptions, reviewers }); - expect(buildReviewer).toHaveBeenCalledTimes(reviewers.length); + it('builds a row per each passed', () => { + buildMessage(defaultOptions); + expect(buildRow).toHaveBeenCalledTimes(table.rows.length); }); }); diff --git a/src/interactors/postSlackMessage/buildMessage/buildReviewer.js b/src/interactors/postSlackMessage/buildMessage/buildReviewer.js deleted file mode 100644 index 6de35c8..0000000 --- a/src/interactors/postSlackMessage/buildMessage/buildReviewer.js +++ /dev/null @@ -1,72 +0,0 @@ -const { durationToString } = require('../../../utils'); - -const MEDALS = [ - ':first_place_medal:', - ':second_place_medal:', - ':third_place_medal:', -]; /* 🥇🥈🥉 */ - -const getUsername = ({ index, reviewer, displayCharts }) => { - const { login, avatarUrl } = reviewer.author; - - const medal = displayCharts ? MEDALS[index] : null; - const suffix = medal ? ` ${medal}` : ''; - - return { - type: 'context', - elements: [ - { - type: 'image', - image_url: avatarUrl, - alt_text: login, - }, - { - emoji: true, - type: 'plain_text', - text: `${login}${suffix}`, - }, - ], - }; -}; - -const getStats = ({ t, reviewer, disableLinks }) => { - const { stats, urls } = reviewer; - const timeToReviewStr = durationToString(stats.timeToReview); - const timeToReview = disableLinks - ? timeToReviewStr - : `<${urls.timeToReview}|${timeToReviewStr}>`; - - return { - type: 'section', - fields: [ - { - type: 'mrkdwn', - text: `*${t('table.columns.totalReviews')}:* ${stats.totalReviews}`, - }, - { - type: 'mrkdwn', - text: `*${t('table.columns.totalComments')}:* ${stats.totalComments}`, - }, - { - type: 'mrkdwn', - text: `*${t('table.columns.timeToReview')}:* ${timeToReview}`, - }, - ], - }; -}; - -const getDivider = () => ({ - type: 'divider', -}); - -module.exports = ({ - t, - index, - reviewer, - disableLinks, - displayCharts, -}) => [ - getUsername({ index, reviewer, displayCharts }), - getStats({ t, reviewer, disableLinks }), - getDivider(), -]; diff --git a/src/interactors/postSlackMessage/buildMessage/buildRow.js b/src/interactors/postSlackMessage/buildMessage/buildRow.js new file mode 100644 index 0000000..4761383 --- /dev/null +++ b/src/interactors/postSlackMessage/buildMessage/buildRow.js @@ -0,0 +1,55 @@ +const EMOJIS_MAP = { + medal1: ':first_place_medal:', /* 🥇 */ + medal2: ':second_place_medal:', /* 🥈 */ + medal3: ':third_place_medal:', /* 🥉 */ +}; + +const getUsername = ({ text, image, emoji }) => { + const medal = EMOJIS_MAP[emoji] || null; + const suffix = medal ? ` ${medal}` : ''; + + return { + type: 'context', + elements: [ + { + type: 'image', + image_url: image, + alt_text: text, + }, + { + emoji: true, + type: 'plain_text', + text: `${text}${suffix}`, + }, + ], + }; +}; + +const getStats = ({ row, statNames }) => { + const { stats } = row; + const fields = stats.map(({ text, link }, index) => { + const value = link ? `<${link}|${text}>` : text; + return { + type: 'mrkdwn', + text: `*${statNames[index]}:* ${value}`, + }; + }); + + return { + type: 'section', + fields, + }; +}; + +const getDivider = () => ({ + type: 'divider', +}); + +module.exports = ({ + row, + statNames, +}) => [ + getUsername(row.user), + getStats({ row, statNames }), + getDivider(), +]; diff --git a/src/interactors/postSlackMessage/buildMessage/index.js b/src/interactors/postSlackMessage/buildMessage/index.js index fe8f444..23b67fc 100644 --- a/src/interactors/postSlackMessage/buildMessage/index.js +++ b/src/interactors/postSlackMessage/buildMessage/index.js @@ -1,15 +1,15 @@ const { t } = require('../../../i18n'); const buildSubtitle = require('./buildSubtitle'); -const buildReviewer = require('./buildReviewer'); +const buildRow = require('./buildRow'); + +const getStatNames = (headers) => headers.slice(1).map(({ text }) => text); module.exports = ({ org, repos, - reviewers, + table, pullRequest, periodLength, - disableLinks, - displayCharts, }) => ({ blocks: [ ...buildSubtitle({ @@ -20,16 +20,10 @@ module.exports = ({ periodLength, }), - ...reviewers.reduce( - (prev, reviewer, index) => [ + ...table.rows.reduce( + (prev, row) => [ ...prev, - ...buildReviewer({ - t, - index, - reviewer, - disableLinks, - displayCharts, - })], + ...buildRow({ row, statNames: getStatNames(table.headers) })], [], ), ], diff --git a/src/interactors/postSlackMessage/index.js b/src/interactors/postSlackMessage/index.js index 141efbe..c387ceb 100644 --- a/src/interactors/postSlackMessage/index.js +++ b/src/interactors/postSlackMessage/index.js @@ -4,15 +4,13 @@ const { SlackSplitter } = require('../../services/splitter'); const buildMessage = require('./buildMessage'); module.exports = async ({ + core, org, repos, - core, slack, isSponsor, - reviewers, + table, periodLength, - disableLinks, - displayCharts, pullRequest = null, }) => { const { webhook, channel } = slack || {}; @@ -44,11 +42,9 @@ module.exports = async ({ const fullMessage = buildMessage({ org, repos, - reviewers, + table, pullRequest, periodLength, - disableLinks, - displayCharts, }); const { chunks } = new SlackSplitter({ message: fullMessage });