Skip to content

Commit

Permalink
Merge 84b944f into 182bfef
Browse files Browse the repository at this point in the history
  • Loading branch information
bobvanderlinden committed Aug 19, 2018
2 parents 182bfef + 84b944f commit 6a5f237
Show file tree
Hide file tree
Showing 27 changed files with 1,421 additions and 935 deletions.
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -58,7 +58,7 @@
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"testRegex": "\\.test\\.tsx?$",
"moduleFileExtensions": [
"ts",
"tsx",
Expand Down
4 changes: 4 additions & 0 deletions src/condition.ts
@@ -0,0 +1,4 @@
import { HandlerContext, PullRequestInfo } from './models'

export type Condition = (context: HandlerContext, pullRequestInfo: PullRequestInfo) => ConditionResult
export type ConditionResult = { status: 'success' } | { status: 'fail', message: string } | { status: 'pending', message?: string }
38 changes: 38 additions & 0 deletions src/conditions/blockingChecks.ts
@@ -0,0 +1,38 @@
import { HandlerContext, PullRequestInfo } from '../models'
import { ConditionResult } from '../condition'
import { groupByLastMap } from '../utils'

export default function doesNotHaveBlockingChecks (
context: HandlerContext,
pullRequestInfo: PullRequestInfo
): ConditionResult {
const checkRuns = pullRequestInfo.checkRuns
const allChecksCompleted = checkRuns.every(
checkRun => checkRun.status === 'completed'
)
if (!allChecksCompleted) {
return {
status: 'pending',
message: 'There are still pending checks'
}
}
const checkConclusions = groupByLastMap(
checkRun => checkRun.conclusion,
_ => true,
checkRuns
)
const checksBlocking =
checkConclusions.failure ||
checkConclusions.cancelled ||
checkConclusions.timed_out ||
checkConclusions.action_required
if (checksBlocking) {
return {
status: 'fail',
message: 'There are blocking checks'
}
}
return {
status: 'success'
}
}
24 changes: 24 additions & 0 deletions src/conditions/blockingLabels.ts
@@ -0,0 +1,24 @@
import { HandlerContext, PullRequestInfo } from '../models'
import { ConditionResult } from '../condition'

export default function doesNotHaveBlockingLabels (
context: HandlerContext,
pullRequestInfo: PullRequestInfo
): ConditionResult {
const { config } = context
const pullRequestLabels = new Set(pullRequestInfo.labels.nodes.map(label => label.name))
const foundBlockingLabels = config.blockingLabels
.filter(blockingLabel => pullRequestLabels.has(blockingLabel))

if (foundBlockingLabels.length > 0) {
return {
status: 'fail',
message: `Blocking labels are missing (${
foundBlockingLabels.join(', ')
})`
}
}
return {
status: 'success'
}
}
24 changes: 24 additions & 0 deletions src/conditions/index.ts
@@ -0,0 +1,24 @@
import { ConditionResult } from './../condition'
import open from './open'
import mergeable from './mergeable'
import requiredLabels from './requiredLabels'
import blockingLabels from './blockingLabels'
import blockingChecks from './blockingChecks'
import minimumApprovals from './minimumApprovals'
import maximumChangesRequested from './maximumChangesRequested'
import upToDateBranch from './upToDateBranch'
import { keysOf } from '../utils'

export const conditions = {
open,
mergeable,
requiredLabels,
blockingLabels,
minimumApprovals,
maximumChangesRequested,
blockingChecks,
upToDateBranch
}
export const conditionNames: ConditionName[] = keysOf<ConditionName>(conditions)
export type ConditionName = keyof (typeof conditions)
export type ConditionResults = { [key in ConditionName]: ConditionResult }
28 changes: 28 additions & 0 deletions src/conditions/maximumChangesRequested.ts
@@ -0,0 +1,28 @@
import { HandlerContext, PullRequestInfo } from '../models'
import { ConditionResult } from '../condition'
import { getLatestReviews, arrayToMap, mapToArray, or, get } from '../utils'
import { associations, getAssociationPriority } from '../association'

export default function doesNotHaveMaximumChangesRequested (context: HandlerContext, pullRequestInfo: PullRequestInfo): ConditionResult {
const { config } = context

const latestReviews = getLatestReviews(pullRequestInfo)
const changesRequestedCountByAssociation =
arrayToMap(associations,
(association) => association,
(association) => latestReviews
.filter(review => getAssociationPriority(review.authorAssociation) >= getAssociationPriority(association))
.filter(review => review.state === 'CHANGES_REQUESTED')
.length
)

return mapToArray(config.maxRequestedChanges)
.some(([association, maxRequestedChanges]) => or(get(changesRequestedCountByAssociation, association), 0) > maxRequestedChanges)
? {
status: 'fail',
message: `There are changes requested by a reviewer.`
}
: {
status: 'success'
}
}
23 changes: 23 additions & 0 deletions src/conditions/mergeable.ts
@@ -0,0 +1,23 @@
import { HandlerContext, PullRequestInfo } from '../models'
import { ConditionResult } from '../condition'

export default function isMergeable (context: HandlerContext, pullRequestInfo: PullRequestInfo): ConditionResult {
switch (pullRequestInfo.mergeable) {
case 'MERGEABLE':
return {
status: 'success'
}
case 'CONFLICTING':
return {
status: 'fail',
message: 'Pull request has conflicts'
}
case 'UNKNOWN':
return {
status: 'pending',
message: 'Github could not yet determine the mergeable status of the pull request'
}
default:
throw new Error(`Invalid mergeable state for pull request. mergeable=${pullRequestInfo.mergeable}`)
}
}
28 changes: 28 additions & 0 deletions src/conditions/minimumApprovals.ts
@@ -0,0 +1,28 @@
import { HandlerContext, PullRequestInfo } from '../models'
import { ConditionResult } from '../condition'
import { getLatestReviews, arrayToMap, or, get, mapToArray } from '../utils'
import { associations, getAssociationPriority } from '../association'

export default function hasMinimumApprovals (context: HandlerContext, pullRequestInfo: PullRequestInfo): ConditionResult {
const { config } = context

const latestReviews = getLatestReviews(pullRequestInfo)
const approvalCountByAssociation =
arrayToMap(associations,
(association) => association,
(association) => latestReviews
.filter(review => getAssociationPriority(review.authorAssociation) >= getAssociationPriority(association))
.filter(review => review.state === 'APPROVED')
.length
)

return mapToArray(config.minApprovals)
.some(([association, minApproval]) => or(get(approvalCountByAssociation, association), 0) < minApproval)
? {
status: 'fail',
message: 'There are not enough approvals by reviewers'
}
: {
status: 'success'
}
}
13 changes: 13 additions & 0 deletions src/conditions/open.ts
@@ -0,0 +1,13 @@
import { HandlerContext, PullRequestInfo } from '../models'
import { ConditionResult } from '../condition'

export default function isOpen (context: HandlerContext, pullRequestInfo: PullRequestInfo): ConditionResult {
return pullRequestInfo.state === 'OPEN'
? {
status: 'success'
}
: {
status: 'fail',
message: `State of pull request is ${pullRequestInfo.state}`
}
}
25 changes: 25 additions & 0 deletions src/conditions/requiredLabels.ts
@@ -0,0 +1,25 @@
import { HandlerContext, PullRequestInfo } from '../models'
import { ConditionResult } from '../condition'

export default function hasRequiredLabels (
context: HandlerContext,
pullRequestInfo: PullRequestInfo
): ConditionResult {
const { config } = context
const pullRequestLabels = new Set(pullRequestInfo.labels.nodes.map(label => label.name))

const missingRequiredLabels = config.requiredLabels
.filter(requiredLabel => !pullRequestLabels.has(requiredLabel))

if (missingRequiredLabels.length > 0) {
return {
status: 'fail',
message: `Required labels are missing (${
missingRequiredLabels.join(', ')
})`
}
}
return {
status: 'success'
}
}
27 changes: 27 additions & 0 deletions src/conditions/upToDateBranch.ts
@@ -0,0 +1,27 @@
import { HandlerContext, PullRequestInfo } from '../models'
import { ConditionResult } from '../condition'

export default function hasUpToDateBranch (
context: HandlerContext,
pullRequestInfo: PullRequestInfo
): ConditionResult {
const protectedBranch = pullRequestInfo.repository.protectedBranches.nodes
.filter(protectedBranch => protectedBranch.name === pullRequestInfo.baseRef.name)[0]

if (protectedBranch
&& protectedBranch.hasStrictRequiredStatusChecks
&& pullRequestInfo.baseRef.target.oid !== pullRequestInfo.baseRefOid) {
return {
status: 'fail',
message: `Pull request is based on a strict protected branch (${
pullRequestInfo.baseRef.name
}) and base sha of pull request (${
pullRequestInfo.baseRefOid
}) differs from sha of branch (${pullRequestInfo.baseRef.target.oid})`
}
}

return {
status: 'success'
}
}
4 changes: 2 additions & 2 deletions src/global.d.ts
@@ -1,3 +1,3 @@
declare module "probot-config" {
export default function getConfig<TContext, TConfig>(context: TContext, fileName: String, defaultConfig: TConfig): Promise<TConfig>
declare module 'probot-config' {
export default function getConfig<TContext, TConfig> (context: TContext, fileName: String, defaultConfig: TConfig): Promise<TConfig>
}

0 comments on commit 6a5f237

Please sign in to comment.