Repository: badges/shields (js)

Issue:
Hacktoberfest 2020 Badge
:clipboard: **Description**

The current Hacktoberfest badge is for Hacktoberfest 2019. There's no option to change the year - `2019` is hardcoded.

![Screenshot 2020-09-15 at 10 55 34 AM](https://user-images.githubusercontent.com/20594326/93169118-0ad73680-f742-11ea-83f7-570c58239b60.png)

![GitHub Hacktoberfest combined status](https://img.shields.io/github/hacktoberfest/2019/vinitshahdeo/PortScanner)

:link: **Data**

This badge was already implemented in 2019, I hope the APIs exist with you.

<!--
Where can we get the data from?

- Is there a public API?
- Does the API requires an API key?
- Link to the API documentation.
-->

:microphone: **Motivation**

Having a Hacktoberfest countdown badge will be great as [Hacktoberfest 2020](https://hacktoberfest.digitalocean.com/) is approaching us.

<!--
Please explain why this feature should be implemented and how it would be used.

- What is the specific use case?
-->

<!-- Love Shields? Please consider donating $10 to sustain our activities:
👉  https://opencollective.com/shields -->


Pull request:
[GitHubHacktoberfest] 2020 (and beyond)
Resolves #5545 

I decided to remove the 2019 hardcoding, but also constrain the route param for the year to a set of allowed values that only includes 2019 and 2020 (so should this be merged, next year we'd need to add 2021). I also had to take into account what the badge message should be for an upcoming/future Hacktoberfest, and elected to show a countdown of days until Hacktoberfest would begin. It didn't make sense to me pre-Hacktoberfest kickoff for the badge to show the number of days remaining until the end of October (for example, that'd currently have looked something like ![](https://img.shields.io/badge/hacktoberfest-45%20days%20remaining-orange)), so instead we have:


https://shields-staging-pr-5549.herokuapp.com/github/hacktoberfest/2020/badges/shields
![](https://shields-staging-pr-5549.herokuapp.com/github/hacktoberfest/2020/badges/shields)
https://shields-staging-pr-5549.herokuapp.com/github/hacktoberfest/2019/badges/shields
![](https://shields-staging-pr-5549.herokuapp.com/github/hacktoberfest/2019/badges/shields)

One thing I feel is nice with this is that the badge will serve a dual purpose: folks could go ahead and add the 2020 badge to their projects today and it will act as a countdown, and then on October first it will automatically convert to the issue/PR and days remaining count for them.

In this pre-Hacktoberfest start for specified year scenario, I also decided to skip running the query since there's not really any point trying to determine how many PRs were opened during the month of October before October has started. One side effect of this is that if a user adds the 2020 hacktoberfest badge pre-October 1st but has a typo in their org/repo name, then the countdown days will still show without telling the user the repo wasn't found.. until October 1st at which point it'll change to ![](https://shields-staging-pr-5549.herokuapp.com/github/hacktoberfest/2019/badges/shiels)


Patch:
diff --git a/services/github/github-hacktoberfest.service.js b/services/github/github-hacktoberfest.service.js
index 0192793a39..b0067e1d1a 100644
--- a/services/github/github-hacktoberfest.service.js
+++ b/services/github/github-hacktoberfest.service.js
@@ -59,66 +59,71 @@ const queryParamSchema = Joi.object({
 }).required()
 
 module.exports = class GithubHacktoberfestCombinedStatus extends GithubAuthV4Service {
-  static get category() {
-    return 'issue-tracking'
+  static category = 'issue-tracking'
+  static route = {
+    base: 'github/hacktoberfest',
+    pattern: ':year(2019|2020)/:user/:repo',
+    queryParamSchema,
   }
 
-  static get route() {
-    return {
-      base: 'github/hacktoberfest/2019',
-      pattern: ':user/:repo',
-      queryParamSchema,
-    }
-  }
-
-  static get examples() {
-    return [
-      {
-        title: 'GitHub Hacktoberfest combined status',
-        namedParams: {
-          user: 'snyk',
-          repo: 'snyk',
-        },
-        staticPreview: this.render({
-          suggestedIssueCount: 12,
-          contributionCount: 8,
-          daysLeft: 15,
-        }),
-        documentation,
+  static examples = [
+    {
+      title: 'GitHub Hacktoberfest combined status',
+      namedParams: {
+        year: '2020',
+        user: 'snyk',
+        repo: 'snyk',
       },
-      {
-        title:
-          'GitHub Hacktoberfest combined status (suggestion label override)',
-        namedParams: {
-          user: 'tmrowco',
-          repo: 'tmrowapp-contrib',
-        },
-        queryParams: {
-          suggestion_label: 'help wanted',
-        },
-        staticPreview: this.render({
-          suggestedIssueCount: 12,
-          contributionCount: 8,
-          daysLeft: 15,
-        }),
-        documentation,
+      staticPreview: this.render({
+        suggestedIssueCount: 12,
+        contributionCount: 8,
+        daysLeft: 15,
+      }),
+      documentation,
+    },
+    {
+      title: 'GitHub Hacktoberfest combined status (suggestion label override)',
+      namedParams: {
+        year: '2020',
+        user: 'tmrowco',
+        repo: 'tmrowapp-contrib',
       },
-    ]
-  }
+      queryParams: {
+        suggestion_label: 'help wanted',
+      },
+      staticPreview: this.render({
+        year: '2020',
+        suggestedIssueCount: 12,
+        contributionCount: 8,
+        daysLeft: 15,
+      }),
+      documentation,
+    },
+  ]
 
-  static get defaultBadgeData() {
-    return {
-      label: 'hacktoberfest',
-      color: 'orange',
-    }
-  }
+  static defaultBadgeData = { label: 'hacktoberfest', color: 'orange' }
 
-  static render({ suggestedIssueCount, contributionCount, daysLeft }) {
+  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 = moment('2019-11-01 12:00:00 Z').diff(moment(), 'days') + 1
+      daysLeft = moment(`${year}-11-01 12:00:00 Z`).diff(moment(), 'days') + 1
     }
     if (daysLeft < 0) {
       return {
@@ -152,11 +157,11 @@ module.exports = class GithubHacktoberfestCombinedStatus extends GithubAuthV4Ser
     return { message }
   }
 
-  async fetch({ user, repo, suggestionLabel = 'hacktoberfest' }) {
+  async fetch({ user, repo, year, suggestionLabel = 'hacktoberfest' }) {
     const isValidOctoberPR = [
       `repo:${user}/${repo}`,
       'is:pr',
-      'created:2019-10-01..2019-10-31',
+      `created:${year}-10-01..${year}-10-31`,
       `-label:invalid`,
     ]
       .filter(Boolean)
@@ -201,15 +206,31 @@ module.exports = class GithubHacktoberfestCombinedStatus extends GithubAuthV4Ser
     }
   }
 
-  async handle({ user, repo }, { suggestion_label: suggestionLabel }) {
+  static getCalendarPosition(year) {
+    const daysToStart =
+      moment(`${year}-10-01 12:00:00 Z`).diff(moment(), 'days') + 1
+    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,
     })
   }
 }
diff --git a/services/github/github-hacktoberfest.spec.js b/services/github/github-hacktoberfest.spec.js
index 4f594f062e..01916b5a8a 100644
--- a/services/github/github-hacktoberfest.spec.js
+++ b/services/github/github-hacktoberfest.spec.js
@@ -18,5 +18,12 @@ describe('GitHubHacktoberfest', function () {
     }).expect({
       message: '54 open issues, 27 PRs, 10 days left',
     })
+    given({
+      daysToStart: 5,
+      hasStarted: false,
+      year: 2020,
+    }).expect({
+      message: '5 days till kickoff!',
+    })
   })
 })


Test patch:
diff --git a/services/github/github-hacktoberfest.tester.js b/services/github/github-hacktoberfest.tester.js
index 51eb74c164..69138d33ed 100644
--- a/services/github/github-hacktoberfest.tester.js
+++ b/services/github/github-hacktoberfest.tester.js
@@ -20,7 +20,7 @@ const isHacktoberfestStatus = Joi.alternatives().try(
 )
 
 t.create('GitHub Hacktoberfest combined status')
-  .get('/badges/shields.json')
+  .get('/2019/badges/shields.json')
   .expectBadge({
     label: 'hacktoberfest',
     message: isHacktoberfestStatus,
@@ -28,7 +28,7 @@ t.create('GitHub Hacktoberfest combined status')
 
 t.create('GitHub Hacktoberfest combined status (suggestion label override)')
   .get(
-    `/badges/shields.json?suggestion_label=${encodeURIComponent(
+    `/2019/badges/shields.json?suggestion_label=${encodeURIComponent(
       'good first issue'
     )}`
   )