Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add author filter option for [GithubCommitActivity] #9251

Merged
merged 14 commits into from
Jun 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 67 additions & 8 deletions services/github/github-commit-activity.service.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import gql from 'graphql-tag'
import Joi from 'joi'
import parseLinkHeader from 'parse-link-header'
import { InvalidResponse } from '../index.js'
import { metric } from '../text-formatters.js'
import { nonNegativeInteger } from '../validators.js'
import { GithubAuthV4Service } from './github-auth-service.js'
import { transformErrors, documentation } from './github-helpers.js'
import {
transformErrors,
documentation,
httpErrorsFor,
} from './github-helpers.js'

const schema = Joi.object({
data: Joi.object({
Expand All @@ -18,11 +23,16 @@ const schema = Joi.object({
}).required(),
}).required()

const queryParamSchema = Joi.object({
authorFilter: Joi.string(),
})

export default class GitHubCommitActivity extends GithubAuthV4Service {
static category = 'activity'
static route = {
base: 'github/commit-activity',
pattern: ':interval(t|y|m|4w|w)/:user/:repo/:branch*',
queryParamSchema,
}

static examples = [
chris48s marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -31,6 +41,7 @@ export default class GitHubCommitActivity extends GithubAuthV4Service {
// Override the pattern to omit the deprecated interval "4w".
pattern: ':interval(t|y|m|w)/:user/:repo',
namedParams: { interval: 'm', user: 'eslint', repo: 'eslint' },
queryParams: { authorFilter: 'nzakas' },
staticPreview: this.render({ interval: 'm', commitCount: 457 }),
keywords: ['commits'],
documentation,
Expand All @@ -45,6 +56,7 @@ export default class GitHubCommitActivity extends GithubAuthV4Service {
repo: 'squint',
branch: 'main',
},
queryParams: { authorFilter: 'calebcartwright' },
staticPreview: this.render({ interval: 'm', commitCount: 5 }),
keywords: ['commits'],
documentation,
Expand All @@ -53,9 +65,10 @@ export default class GitHubCommitActivity extends GithubAuthV4Service {

static defaultBadgeData = { label: 'commit activity', color: 'blue' }

static render({ interval, commitCount }) {
static render({ interval, commitCount, authorFilter }) {
// If total commits selected change label from commit activity to commits
const label = interval === 't' ? 'commits' : undefined
const label = interval === 't' ? 'commits' : 'commit activity'
const authorFilterLabel = authorFilter ? ` by ${authorFilter}` : ''

const intervalLabel = {
t: '',
Expand All @@ -66,7 +79,7 @@ export default class GitHubCommitActivity extends GithubAuthV4Service {
}[interval]

return {
label,
label: `${label}${authorFilterLabel}`,
message: `${metric(commitCount)}${intervalLabel}`,
}
}
Expand Down Expand Up @@ -103,6 +116,30 @@ export default class GitHubCommitActivity extends GithubAuthV4Service {
})
}

async fetchAuthorFilter({
interval,
user,
repo,
branch = 'HEAD',
authorFilter,
}) {
const since =
this.constructor.getIntervalQueryStartDate({ interval }) || undefined

return this._request({
url: `/repos/${user}/${repo}/commits`,
options: {
searchParams: {
sha: branch,
author: authorFilter,
per_page: '1',
since,
},
},
httpErrors: httpErrorsFor('repo or branch not found'),
})
}

static transform({ data }) {
const {
repository: { object: repo },
Expand All @@ -115,6 +152,16 @@ export default class GitHubCommitActivity extends GithubAuthV4Service {
return repo.history.totalCount
}

static transformAuthorFilter({ res }) {
const parsed = parseLinkHeader(res.headers.link)

if (!parsed) {
return 0
}

return parsed.last.page
}

static getIntervalQueryStartDate({ interval }) {
const now = new Date()

Expand All @@ -131,9 +178,21 @@ export default class GitHubCommitActivity extends GithubAuthV4Service {
return now.toISOString()
}

async handle({ interval, user, repo, branch }) {
const json = await this.fetch({ interval, user, repo, branch })
const commitCount = this.constructor.transform(json)
return this.constructor.render({ interval, commitCount })
async handle({ interval, user, repo, branch }, { authorFilter }) {
let commitCount
if (authorFilter) {
const authorFilterRes = await this.fetchAuthorFilter({
interval,
user,
repo,
branch,
authorFilter,
})
commitCount = this.constructor.transformAuthorFilter(authorFilterRes)
} else {
const json = await this.fetch({ interval, user, repo, branch })
commitCount = this.constructor.transform(json)
}
return this.constructor.render({ interval, commitCount, authorFilter })
}
}
59 changes: 59 additions & 0 deletions services/github/github-commit-activity.tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,84 @@ const isCommitActivity = Joi.alternatives().try(
isZeroOverTimePeriod
)

const authorFilterUser = 'jnullj'

t.create('commit activity (total)').get('/t/badges/shields.json').expectBadge({
label: 'commits',
message: isMetric,
})

t.create('commit activity (total) by author')
.get(`/t/badges/shields.json?authorFilter=${authorFilterUser}`)
.expectBadge({
label: `commits by ${authorFilterUser}`,
message: isMetric,
})

t.create('commit activity (1 year)').get('/y/eslint/eslint.json').expectBadge({
label: 'commit activity',
message: isMetricOverTimePeriod,
})

t.create('commit activity (1 year) by author')
.get(`/y/badges/shields.json?authorFilter=${authorFilterUser}`)
.expectBadge({
label: `commit activity by ${authorFilterUser}`,
message: isMetricOverTimePeriod,
})

t.create('commit activity (1 month)').get('/m/eslint/eslint.json').expectBadge({
label: 'commit activity',
message: isMetricOverTimePeriod,
})

t.create('commit activity (1 month) by author')
.get(`/m/badges/shields.json?authorFilter=${authorFilterUser}`)
.expectBadge({
label: `commit activity by ${authorFilterUser}`,
message: isMetricOverTimePeriod,
})

t.create('commit activity (4 weeks)')
.get('/4w/eslint/eslint.json')
.expectBadge({
label: 'commit activity',
message: isMetricOverTimePeriod,
})

t.create('commit activity (4 weeks) by author')
.get(`/4w/badges/shields.json?authorFilter=${authorFilterUser}`)
.expectBadge({
label: `commit activity by ${authorFilterUser}`,
message: isMetricOverTimePeriod,
})

t.create('commit activity (1 week)').get('/w/eslint/eslint.json').expectBadge({
label: 'commit activity',
message: isCommitActivity,
})

t.create('commit activity (1 week) by author')
.get(`/w/badges/shields.json?authorFilter=${authorFilterUser}`)
.expectBadge({
label: `commit activity by ${authorFilterUser}`,
message: isCommitActivity,
})

t.create('commit activity (custom branch)')
.get('/y/badges/squint/main.json')
.expectBadge({
label: 'commit activity',
message: isCommitActivity,
})

t.create('commit activity (custom branch) by author')
.get(`/y/badges/squint/main.json?authorFilter=${authorFilterUser}`)
.expectBadge({
label: `commit activity by ${authorFilterUser}`,
message: isCommitActivity,
})

t.create('commit activity (repo not found)')
.get('/w/badges/helmets.json')
.expectBadge({
Expand All @@ -59,3 +103,18 @@ t.create('commit activity (invalid branch)')
label: 'commit activity',
message: 'invalid branch',
})

// test for error handling of author filter as it uses REST and not GraphQL
t.create('commit activity (repo not found)')
.get('/w/badges/helmets.json?authorFilter=zaphod')
.expectBadge({
label: 'commit activity',
message: 'repo or branch not found',
})

t.create('commit activity (invalid branch)')
.get('/w/badges/shields/invalidBranchName.json?authorFilter=zaphod')
.expectBadge({
label: 'commit activity',
message: 'repo or branch not found',
})