diff --git a/CHANGELOG.md b/CHANGELOG.md index b961a3a..2ad3b3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Change Log +## v1.2.0 +- Changed name to *GitHub Pull Request Monitor* +- Use more distinguishable icons for failing tests and conflicts +- Show icon for comments +- Improve showing icons for `approved` and `change requested` + ## v1.1.0 - Save user settings - Rename `setRepository` command to `enterRepositoryName` diff --git a/README.md b/README.md index 9a03a0a..25a6a85 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Pull Request Monitor +# GitHub Pull Request Monitor [![Marketplace Version](https://vsmarketplacebadge.apphb.com/version/erichbehrens.pull-request-monitor.svg)](https://marketplace.visualstudio.com/items?itemName=erichbehrens.pull-request-monitor) [![Installs](https://vsmarketplacebadge.apphb.com/installs/erichbehrens.pull-request-monitor.svg)](https://marketplace.visualstudio.com/items?itemName=erichbehrens.pull-request-monitor) @@ -24,11 +24,11 @@ Source code on GitHub: https://github.com/erichbehrens/pull-request-monitor/ **Red**: opposite of green +**White**: waiting for status #### Missing features - detect outdated branches -- show comments and approved reviews with different icons/colors ## Instructions diff --git a/images/statusBarItems.png b/images/statusBarItems.png index d5d6ca2..1a22289 100644 Binary files a/images/statusBarItems.png and b/images/statusBarItems.png differ diff --git a/package.json b/package.json index 173a1a3..37a99a3 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "pull-request-monitor", - "displayName": "Pull Request Monitor", + "displayName": "GitHub Pull Request Monitor", "description": "Monitors the status of GitHub pull requests. Checks for conflicts, status reports, reviews and whether the branch is up to date.", "icon": "icon.png", - "version": "1.1.2", + "version": "1.2.0", "publisher": "erichbehrens", "engines": { "vscode": "^1.18.0" diff --git a/src/extension.js b/src/extension.js index 61a939d..3cf7b48 100644 --- a/src/extension.js +++ b/src/extension.js @@ -1,7 +1,6 @@ -// import {window, commands, Disposable, ExtensionContext, StatusBarAlignment, StatusBarItem, TextDocument} from 'vscode'; const vscode = require('vscode'); const { clearTimeout, setTimeout } = require('timers'); -const { getCommitIcon, getColor, getMergeableIcon, getMergeableState, getPullRequestStateIcon } = require('./utils'); +const { getCommitIcon, getColor, getMergeableIcon, getMergeableState, getPullRequestStateIcon, getReviewState } = require('./utils'); const { loadPullRequests, loadRepositories } = require('./requests'); const MODES = { @@ -25,7 +24,7 @@ function createStatusBarItem(context, prId, url) { async function getPullRequests(context) { try { - const mode = context.globalState.get('mode', MODES.VIEWER) ; + const mode = context.globalState.get('mode', MODES.VIEWER); const repository = context.globalState.get('currentRepository'); const showMerged = context.globalState.get('showMerged', false); const showClosed = context.globalState.get('showClosed', false); @@ -38,17 +37,24 @@ async function getPullRequests(context) { if (!statusBarItems[prId]) { createStatusBarItem(context, prId, pr.url); } - const reviewsCount = pr.reviews.edges.length; - const reviewsApproved = reviewsCount === 0 || (pr.reviews.edges.every(({ node }) => ['APPROVED', 'COMMENTED'].includes(node.state))); - const mergeableState = getMergeableState(pr, reviewsApproved, pr.commits.nodes[0].commit.status, pr.potentialMergeCommit); + const { + reviewsPassing, + hasComments, + hasPendingChangeRequests, + isApproved + } = getReviewState(pr.reviews); + const mergeableState = getMergeableState(pr, reviewsPassing, pr.commits.nodes[0].commit.status, pr.potentialMergeCommit); const closed = mergeableState === 'CLOSED'; + const statusBarItem = statusBarItems[prId]; const text = [ getPullRequestStateIcon(pr.state), pr.number, !pr.merged && !closed && getCommitIcon(pr.commits.nodes[0].commit.status), !pr.merged && !closed && getMergeableIcon(pr.mergeable), - !closed && reviewsCount > 0 && (reviewsApproved ? '$(thumbsup)' : '$(thumbsdown)'), + hasComments && '$(comment)', + hasPendingChangeRequests && '$(thumbsdown)', + isApproved && '$(thumbsup)', !closed && pr.potentialCommit && pr.potentialCommit.status, !closed && pr.potentialCommit && pr.potentialCommit.status && pr.potentialCommit.status.state, ] diff --git a/src/utils.js b/src/utils.js index 00d8fcd..b352fb7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -3,8 +3,8 @@ exports.getCommitIcon = (value) => { if (!value) return undefined; switch (value.state) { case 'SUCCESS': return '$(check)'; - case 'PENDING': return '$(sync)'; - case 'FAILURE': return '$(issue-opened)'; + case 'PENDING': return '$(kebab-horizontal)'; + case 'FAILURE': return '$(tools)'; } } @@ -12,7 +12,7 @@ exports.getMergeableIcon = (value) => { // https://developer.github.com/v4/reference/enum/statusstate/ switch (value) { case 'MERGEABLE': return '$(git-merge)'; - case 'UNKNOWN': return '$(x)'; + case 'UNKNOWN': return '$(question)'; case 'CONFLICTING': return '$(alert)'; } } @@ -29,9 +29,9 @@ exports.getPullRequestStateIcon = (value) => { exports.getColor = (mergeableState) => { switch (mergeableState) { case 'MERGED': - case 'CLOSED': return 'rgba(255, 0, 255, 1)'; case 'MERGEABLE': return 'rgba(57, 255, 20, 1)'; + case 'CLOSED': case 'FAILURE': return 'rgba(139, 0, 0, 1)'; default: return 'rgba(255, 255, 255, 1)'; } @@ -58,3 +58,43 @@ exports.getStatesFilter = (showMerged, showClosed) => { } return `states: [${states.join(' ')}]`; } + +function getReviewsByAuthor(reviews) { + return reviews.reduce((ret, { node }) => { + ret[node.author.login] = ret[node.author.login] || []; + ret[node.author.login].push(node); + return ret; + }, {}); +} + +function getLastStateByAuthor(reviewsByAuthor) { + return Object.keys(reviewsByAuthor).map((author) => { + let lastState; + reviewsByAuthor[author].forEach(({ state }) => { + if (['CHANGES_REQUESTED', 'APPROVED'].includes(state)) lastState = state; + }); + return lastState; + }).filter(item => item); +} + +exports.getReviewState = reviews => { + const reviewsCount = reviews.edges.length; + let hasPendingChangeRequests; + let isApproved; + if (reviewsCount > 0) { + const reviewsByAuthor = getReviewsByAuthor(reviews.edges); + const lastStateByAuthor = getLastStateByAuthor(reviewsByAuthor); + if (lastStateByAuthor && lastStateByAuthor.length > 0) { + hasPendingChangeRequests = lastStateByAuthor.some(state => state === 'CHANGES_REQUESTED'); + isApproved = lastStateByAuthor.every(state => state === 'APPROVED'); + } + } + const hasComments = reviews.edges.some(({ node }) => node.state === 'COMMENTED'); + const reviewsPassing = reviewsCount === 0 || !hasPendingChangeRequests || isApproved; + return { + reviewsPassing, + hasComments, + hasPendingChangeRequests, + isApproved, + } +}