Allow user to filter github tags and releases (#9193)
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import Joi from 'joi'
|
||||
import { matcher } from 'matcher'
|
||||
import { nonNegativeInteger } from '../validators.js'
|
||||
import { latest } from '../version.js'
|
||||
import { NotFound } from '../index.js'
|
||||
@@ -68,8 +69,39 @@ function getLatestRelease({ releases, sort, includePrereleases }) {
|
||||
const queryParamSchema = Joi.object({
|
||||
include_prereleases: Joi.equal(''),
|
||||
sort: Joi.string().valid('date', 'semver').default('date'),
|
||||
filter: Joi.string(),
|
||||
}).required()
|
||||
|
||||
const filterDocs = `<div>
|
||||
<p>
|
||||
The <code>filter</code> param can be used to apply a filter to the
|
||||
project's tag or release names before selecting the latest from the list.
|
||||
Two constructs are available: <code>*</code> is a wildcard matching zero
|
||||
or more characters, and if the pattern starts with a <code>!</code>,
|
||||
the whole pattern is negated.
|
||||
</p>
|
||||
</div>`
|
||||
|
||||
function applyFilter({ releases, filter, displayName }) {
|
||||
if (!filter) {
|
||||
return releases
|
||||
}
|
||||
if (displayName === 'tag') {
|
||||
const filteredTagNames = matcher(
|
||||
releases.map(release => release.tag_name),
|
||||
filter
|
||||
)
|
||||
return releases.filter(release =>
|
||||
filteredTagNames.includes(release.tag_name)
|
||||
)
|
||||
}
|
||||
const filteredReleaseNames = matcher(
|
||||
releases.map(release => release.name),
|
||||
filter
|
||||
)
|
||||
return releases.filter(release => filteredReleaseNames.includes(release.name))
|
||||
}
|
||||
|
||||
// Fetch the latest release as defined by query params
|
||||
async function fetchLatestRelease(
|
||||
serviceInstance,
|
||||
@@ -78,8 +110,10 @@ async function fetchLatestRelease(
|
||||
) {
|
||||
const sort = queryParams.sort
|
||||
const includePrereleases = queryParams.include_prereleases !== undefined
|
||||
const filter = queryParams.filter
|
||||
const displayName = queryParams.display_name
|
||||
|
||||
if (!includePrereleases && sort === 'date') {
|
||||
if (!includePrereleases && sort === 'date' && !filter) {
|
||||
const releaseInfo = await fetchLatestGitHubRelease(serviceInstance, {
|
||||
user,
|
||||
repo,
|
||||
@@ -87,13 +121,23 @@ async function fetchLatestRelease(
|
||||
return releaseInfo
|
||||
}
|
||||
|
||||
const releases = await fetchReleases(serviceInstance, { user, repo })
|
||||
const releases = applyFilter({
|
||||
releases: await fetchReleases(serviceInstance, { user, repo }),
|
||||
filter,
|
||||
displayName,
|
||||
})
|
||||
if (releases.length === 0) {
|
||||
throw new NotFound({ prettyMessage: 'no releases' })
|
||||
const prettyMessage = filter
|
||||
? 'no matching releases found'
|
||||
: 'no releases found'
|
||||
throw new NotFound({ prettyMessage })
|
||||
}
|
||||
const latestRelease = getLatestRelease({ releases, sort, includePrereleases })
|
||||
return latestRelease
|
||||
}
|
||||
|
||||
export { fetchLatestRelease, queryParamSchema }
|
||||
export const _getLatestRelease = getLatestRelease // currently only used for tests
|
||||
export { fetchLatestRelease, filterDocs, queryParamSchema }
|
||||
|
||||
// currently only used for tests
|
||||
export const _getLatestRelease = getLatestRelease
|
||||
export const _applyFilter = applyFilter
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { test, given } from 'sazerac'
|
||||
import { _getLatestRelease } from './github-common-release.js'
|
||||
import { _applyFilter, _getLatestRelease } from './github-common-release.js'
|
||||
|
||||
describe('GithubRelease', function () {
|
||||
test(_getLatestRelease, () => {
|
||||
@@ -42,4 +42,50 @@ describe('GithubRelease', function () {
|
||||
includePrereleases: false,
|
||||
}).expect({ tag_name: '1.2.0-beta', prerelease: true })
|
||||
})
|
||||
|
||||
test(_applyFilter, () => {
|
||||
const releases = [
|
||||
{ name: 'release/1.1.0', tag_name: 'tag/1.1.0', prerelease: false },
|
||||
{ name: 'release/1.2.0', tag_name: 'tag/1.2.0', prerelease: false },
|
||||
{
|
||||
name: 'release/server-2022-01-01',
|
||||
tag_name: 'tag/server-2022-01-01',
|
||||
prerelease: false,
|
||||
},
|
||||
]
|
||||
|
||||
given({ releases, filter: undefined }).expect(releases)
|
||||
given({ releases, filter: '' }).expect(releases)
|
||||
given({ releases, filter: '*' }).expect(releases)
|
||||
given({ releases, filter: '!*' }).expect([])
|
||||
given({ releases, filter: 'foo' }).expect([])
|
||||
given({ releases, filter: 'release/server-*' }).expect([
|
||||
{
|
||||
name: 'release/server-2022-01-01',
|
||||
tag_name: 'tag/server-2022-01-01',
|
||||
prerelease: false,
|
||||
},
|
||||
])
|
||||
given({ releases, filter: '!release/server-*' }).expect([
|
||||
{ name: 'release/1.1.0', tag_name: 'tag/1.1.0', prerelease: false },
|
||||
{ name: 'release/1.2.0', tag_name: 'tag/1.2.0', prerelease: false },
|
||||
])
|
||||
|
||||
given({ releases, displayName: 'tag', filter: undefined }).expect(releases)
|
||||
given({ releases, displayName: 'tag', filter: '' }).expect(releases)
|
||||
given({ releases, displayName: 'tag', filter: '*' }).expect(releases)
|
||||
given({ releases, displayName: 'tag', filter: '!*' }).expect([])
|
||||
given({ releases, displayName: 'tag', filter: 'foo' }).expect([])
|
||||
given({ releases, displayName: 'tag', filter: 'tag/server-*' }).expect([
|
||||
{
|
||||
name: 'release/server-2022-01-01',
|
||||
tag_name: 'tag/server-2022-01-01',
|
||||
prerelease: false,
|
||||
},
|
||||
])
|
||||
given({ releases, displayName: 'tag', filter: '!tag/server-*' }).expect([
|
||||
{ name: 'release/1.1.0', tag_name: 'tag/1.1.0', prerelease: false },
|
||||
{ name: 'release/1.2.0', tag_name: 'tag/1.2.0', prerelease: false },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import { redirector } from '../index.js'
|
||||
import { GithubAuthV3Service } from './github-auth-service.js'
|
||||
import {
|
||||
fetchLatestRelease,
|
||||
filterDocs,
|
||||
queryParamSchema,
|
||||
} from './github-common-release.js'
|
||||
import { documentation } from './github-helpers.js'
|
||||
@@ -85,6 +86,21 @@ class GithubRelease extends GithubAuthV3Service {
|
||||
}),
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'GitHub release (with filter)',
|
||||
namedParams: { user: 'RetroMusicPlayer', repo: 'RetroMusicPlayer' },
|
||||
queryParams: {
|
||||
sort: 'date',
|
||||
display_name: 'release',
|
||||
filter: '*Open Beta',
|
||||
},
|
||||
staticPreview: this.render({
|
||||
version: 'Release v6.0.2 - Open Beta',
|
||||
sort: 'date',
|
||||
isPrerelease: false,
|
||||
}),
|
||||
documentation: documentation + filterDocs,
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'release', namedLogo: 'github' }
|
||||
|
||||
@@ -32,7 +32,7 @@ t.create('Release (No releases)')
|
||||
|
||||
t.create('Prerelease (No releases)')
|
||||
.get('/v/release/badges/daily-tests.json?include_prereleases')
|
||||
.expectBadge({ label: 'release', message: 'no releases' })
|
||||
.expectBadge({ label: 'release', message: 'no releases found' })
|
||||
|
||||
t.create('Release (repo not found)')
|
||||
.get('/v/release/badges/helmets.json')
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import gql from 'graphql-tag'
|
||||
import Joi from 'joi'
|
||||
import { matcher } from 'matcher'
|
||||
import { addv } from '../text-formatters.js'
|
||||
import { version as versionColor } from '../color-formatters.js'
|
||||
import { latest } from '../version.js'
|
||||
import { NotFound, redirector } from '../index.js'
|
||||
import { GithubAuthV4Service } from './github-auth-service.js'
|
||||
import { queryParamSchema } from './github-common-release.js'
|
||||
import { filterDocs, queryParamSchema } from './github-common-release.js'
|
||||
import { documentation, transformErrors } from './github-helpers.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
@@ -60,6 +61,16 @@ class GithubTag extends GithubAuthV4Service {
|
||||
}),
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'GitHub tag (with filter)',
|
||||
namedParams: { user: 'badges', repo: 'shields' },
|
||||
queryParams: { filter: '!server-*' },
|
||||
staticPreview: this.render({
|
||||
version: 'v3.3.1',
|
||||
sort: 'date',
|
||||
}),
|
||||
documentation: documentation + filterDocs,
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = {
|
||||
@@ -73,8 +84,21 @@ class GithubTag extends GithubAuthV4Service {
|
||||
}
|
||||
}
|
||||
|
||||
async fetch({ user, repo, sort }) {
|
||||
const limit = sort === 'semver' ? 100 : 1
|
||||
static getLimit({ sort, filter }) {
|
||||
if (!filter && sort === 'date') {
|
||||
return 1
|
||||
}
|
||||
return 100
|
||||
}
|
||||
|
||||
static applyFilter({ tags, filter }) {
|
||||
if (!filter) {
|
||||
return tags
|
||||
}
|
||||
return matcher(tags, filter)
|
||||
}
|
||||
|
||||
async fetch({ user, repo, limit }) {
|
||||
return this._requestGraphql({
|
||||
query: gql`
|
||||
query ($user: String!, $repo: String!, $limit: Int!) {
|
||||
@@ -109,11 +133,17 @@ class GithubTag extends GithubAuthV4Service {
|
||||
async handle({ user, repo }, queryParams) {
|
||||
const sort = queryParams.sort
|
||||
const includePrereleases = queryParams.include_prereleases !== undefined
|
||||
const filter = queryParams.filter
|
||||
const limit = this.constructor.getLimit({ sort, filter })
|
||||
|
||||
const json = await this.fetch({ user, repo, sort })
|
||||
const tags = json.data.repository.refs.edges.map(edge => edge.node.name)
|
||||
const json = await this.fetch({ user, repo, limit })
|
||||
const tags = this.constructor.applyFilter({
|
||||
tags: json.data.repository.refs.edges.map(edge => edge.node.name),
|
||||
filter,
|
||||
})
|
||||
if (tags.length === 0) {
|
||||
throw new NotFound({ prettyMessage: 'no tags found' })
|
||||
const prettyMessage = filter ? 'no matching tags found' : 'no tags found'
|
||||
throw new NotFound({ prettyMessage })
|
||||
}
|
||||
return this.constructor.render({
|
||||
version: this.constructor.getLatestTag({
|
||||
|
||||
@@ -53,4 +53,24 @@ describe('GithubTag', function () {
|
||||
color: 'blue',
|
||||
})
|
||||
})
|
||||
|
||||
test(GithubTag.getLimit, () => {
|
||||
given({ sort: 'date', filter: undefined }).expect(1)
|
||||
given({ sort: 'date', filter: '' }).expect(1)
|
||||
given({ sort: 'date', filter: '!*-dev' }).expect(100)
|
||||
given({ sort: 'semver', filter: undefined }).expect(100)
|
||||
given({ sort: 'semver', filter: '' }).expect(100)
|
||||
given({ sort: 'semver', filter: '!*-dev' }).expect(100)
|
||||
})
|
||||
|
||||
test(GithubTag.applyFilter, () => {
|
||||
const tags = ['v1.1.0', 'v1.2.0', 'server-2022-01-01']
|
||||
given({ tags, filter: undefined }).expect(tags)
|
||||
given({ tags, filter: '' }).expect(tags)
|
||||
given({ tags, filter: '*' }).expect(tags)
|
||||
given({ tags, filter: '!*' }).expect([])
|
||||
given({ tags, filter: 'foo' }).expect([])
|
||||
given({ tags, filter: 'server-*' }).expect(['server-2022-01-01'])
|
||||
given({ tags, filter: '!server-*' }).expect(['v1.1.0', 'v1.2.0'])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user