Files
shields/services/github/github-hacktoberfest.service.js
chris48s 5cdef88bcc Add renderDateBadge helper; affects [aur BitbucketLastCommit chrome date eclipse factorio galaxytoolshed GiteaLastCommit GistLastCommit GithubCreatedAt GithubHacktoberfest GithubIssueDetail GithubLastCommit GithubReleaseDate GitlabLastCommit maven npm openvsx snapcraft SourceforgeLastCommit steam vaadin visualstudio wordpress] (#10682)
* add and consistently use parseDate and renderDateBadge helpers

also move

- age
- formatDate
- formatRelativeDate

to date.js

* fix bug in wordpress last update badge

* validate in formatDate() and age()

it is going to be unlikely we'll invoke either of these
directly now, but lets calidate here too

* remove unusued imports

* reverse colours for galaxy toolshed
2024-11-17 13:15:28 +00:00

213 lines
5.8 KiB
JavaScript

import gql from 'graphql-tag'
import Joi from 'joi'
import dayjs from 'dayjs'
import { pathParam, queryParam } from '../index.js'
import { parseDate } from '../date.js'
import { metric, maybePluralize } from '../text-formatters.js'
import { nonNegativeInteger } from '../validators.js'
import { GithubAuthV4Service } from './github-auth-service.js'
import {
documentation as githubDocumentation,
transformErrors,
} from './github-helpers.js'
const description = `
This badge is designed for projects hosted on GitHub which are
participating in
[Hacktoberfest](https://hacktoberfest.digitalocean.com),
an initiative to encourage participating in open-source projects. The
badge can be added to the project readme to encourage potential
contributors to review the suggested issues and to celebrate the
contributions that have already been made.
The badge displays three pieces of information:
- The number of suggested issues. By default this will count open
issues with the <strong>hacktoberfest</strong> label, however you
can pick a different label (e.g.
\`?suggestion_label=good%20first%20issue\`).
- The number of pull requests opened in October. This excludes any
PR with the <strong>invalid</strong> label.
- The number of days left of October.
${githubDocumentation}
`
const schema = Joi.object({
data: Joi.object({
repository: Joi.object({
issues: Joi.object({
totalCount: nonNegativeInteger,
}).required(),
}).required(),
search: Joi.object({
issueCount: nonNegativeInteger,
}).required(),
}).required(),
}).required()
const queryParamSchema = Joi.object({
suggestion_label: Joi.string(),
}).required()
export default class GithubHacktoberfestCombinedStatus extends GithubAuthV4Service {
static category = 'issue-tracking'
static route = {
base: 'github/hacktoberfest',
pattern: ':year(2019|2020|2021|2022|2023|2024)/:user/:repo',
queryParamSchema,
}
static openApi = {
'/github/hacktoberfest/{year}/{user}/{repo}': {
get: {
summary: 'GitHub Hacktoberfest combined status',
description,
parameters: [
pathParam({
name: 'year',
example: '2024',
schema: { type: 'string', enum: this.getEnum('year') },
}),
pathParam({ name: 'user', example: 'tmrowco' }),
pathParam({ name: 'repo', example: 'tmrowapp-contrib' }),
queryParam({ name: 'suggestion_label', example: 'help wanted' }),
],
},
},
}
static defaultBadgeData = { label: 'hacktoberfest', color: 'orange' }
static render({
suggestedIssueCount,
contributionCount,
daysLeft,
daysToStart,
year,
hasStarted = true,
}) {
if (!hasStarted) {
return {
message: `${daysToStart} ${maybePluralize(
'day',
daysToStart,
)} till kickoff!`,
}
}
if (daysLeft === undefined) {
// The global cutoff time is 11/1 noon UTC.
// https://github.com/badges/shields/pull/4109#discussion_r330782093
// We want to show "1 day left" on the last day so we add 1.
daysLeft = parseDate(`${year}-11-01 12:00:00 Z`).diff(dayjs(), 'days') + 1
}
if (daysLeft < 0) {
return {
message: `is over! (${metric(contributionCount)} ${maybePluralize(
'PR',
contributionCount,
)} opened)`,
}
}
const message =
[
suggestedIssueCount
? `${metric(suggestedIssueCount)} ${maybePluralize(
'open issue',
suggestedIssueCount,
)}`
: '',
contributionCount
? `${metric(contributionCount)} ${maybePluralize(
'PR',
contributionCount,
)}`
: '',
daysLeft > 0
? `${daysLeft} ${maybePluralize('day', daysLeft)} left`
: '',
]
.filter(Boolean)
.join(', ') || 'is over!'
return { message }
}
async fetch({ user, repo, year, suggestionLabel = 'hacktoberfest' }) {
const isValidOctoberPR = [
`repo:${user}/${repo}`,
'is:pr',
`created:${year}-10-01..${year}-10-31`,
'-label:invalid',
]
.filter(Boolean)
.join(' ')
const {
data: {
repository: {
issues: { totalCount: suggestedIssueCount },
},
search: { issueCount: contributionCount },
},
} = await this._requestGraphql({
query: gql`
query (
$user: String!
$repo: String!
$suggestionLabel: String!
$isValidOctoberPR: String!
) {
repository(owner: $user, name: $repo) {
issues(states: [OPEN], labels: [$suggestionLabel]) {
totalCount
}
}
search(query: $isValidOctoberPR, type: ISSUE, last: 0) {
issueCount
}
}
`,
variables: {
user,
repo,
suggestionLabel,
isValidOctoberPR,
},
schema,
transformErrors,
})
return {
suggestedIssueCount,
contributionCount,
}
}
static getCalendarPosition(year) {
const daysToStart = parseDate(`${year}-10-01 00:00:00 Z`).diff(
dayjs(),
'days',
)
const isBefore = daysToStart > 0
return { daysToStart, isBefore }
}
async handle({ user, repo, year }, { suggestion_label: suggestionLabel }) {
const { isBefore, daysToStart } =
this.constructor.getCalendarPosition(+year)
if (isBefore) {
return this.constructor.render({ hasStarted: false, daysToStart, year })
}
const { suggestedIssueCount, contributionCount } = await this.fetch({
user,
repo,
year,
suggestionLabel,
})
return this.constructor.render({
suggestedIssueCount,
contributionCount,
year,
})
}
}