• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

badges / shields / 39217188618

18 Oct 2026 11:42PM UTC coverage: 79.763% (-18.4%) from 98.173%
39217188618

push

github

web-flow
Update simple-icons to 16.35.0 (#12233)

Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com>

3358 of 3601 branches covered (93.25%)

41381 of 51880 relevant lines covered (79.76%)

50.71 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.76
/services/github/github-commit-activity.service.js
1
import gql from 'graphql-tag'
2 ✔
2
import Joi from 'joi'
2 ✔
3
import parseLinkHeader from 'parse-link-header'
2 ✔
4
import { InvalidResponse, pathParam, queryParam } from '../index.js'
2 ✔
5
import { metric } from '../text-formatters.js'
2 ✔
6
import { nonNegativeInteger } from '../validators.js'
2 ✔
7
import { GithubAuthV4Service } from './github-auth-service.js'
2 ✔
8
import {
2 ✔
9
  transformErrors,
2 ✔
10
  documentation,
2 ✔
11
  httpErrorsFor,
2 ✔
12
} from './github-helpers.js'
2 ✔
13

2 ✔
14
const schema = Joi.object({
2 ✔
15
  data: Joi.object({
2 ✔
16
    repository: Joi.object({
2 ✔
17
      object: Joi.object({
2 ✔
18
        history: Joi.object({
2 ✔
19
          totalCount: nonNegativeInteger,
2 ✔
20
        }).required(),
2 ✔
21
      }).allow(null),
2 ✔
22
    }).required(),
2 ✔
23
  }).required(),
2 ✔
24
}).required()
2 ✔
25

2 ✔
26
const queryParamSchema = Joi.object({
2 ✔
27
  authorFilter: Joi.string(),
2 ✔
28
})
2 ✔
29

2 ✔
30
export default class GitHubCommitActivity extends GithubAuthV4Service {
2 ✔
31
  static category = 'activity'
2 ✔
32
  static route = {
2 ✔
33
    base: 'github/commit-activity',
2 ✔
34
    pattern: ':interval/:user/:repo/:branch*',
2 ✔
35
    queryParamSchema,
2 ✔
36
  }
2 ✔
37
  static routeEnum = ['t', 'y', 'm', '4w', 'w']
2 ✔
38

2 ✔
39
  static openApi = {
2 ✔
40
    '/github/commit-activity/{interval}/{user}/{repo}': {
2 ✔
41
      get: {
2 ✔
42
        summary: 'GitHub commit activity',
2 ✔
43
        description: documentation,
2 ✔
44
        parameters: [
2 ✔
45
          pathParam({
2 ✔
46
            name: 'interval',
2 ✔
47
            example: 'm',
2 ✔
48
            description: 'Commits in the last Week, Month, Year, or Total',
2 ✔
49
            schema: {
2 ✔
50
              type: 'string',
2 ✔
51
              // Override the enum to omit the deprecated interval "4w".
2 ✔
52
              enum: ['w', 'm', 'y', 't'],
2 ✔
53
            },
2 ✔
54
          }),
2 ✔
55
          pathParam({ name: 'user', example: 'badges' }),
2 ✔
56
          pathParam({ name: 'repo', example: 'squint' }),
2 ✔
57
          queryParam({ name: 'authorFilter', example: 'calebcartwright' }),
2 ✔
58
        ],
2 ✔
59
      },
2 ✔
60
    },
2 ✔
61
    '/github/commit-activity/{interval}/{user}/{repo}/{branch}': {
2 ✔
62
      get: {
2 ✔
63
        summary: 'GitHub commit activity (branch)',
2 ✔
64
        description: documentation,
2 ✔
65
        parameters: [
2 ✔
66
          pathParam({
2 ✔
67
            name: 'interval',
2 ✔
68
            example: 'm',
2 ✔
69
            description: 'Commits in the last Week, Month, Year, or Total',
2 ✔
70
            schema: {
2 ✔
71
              type: 'string',
2 ✔
72
              // Override the enum to omit the deprecated interval "4w".
2 ✔
73
              enum: ['w', 'm', 'y', 't'],
2 ✔
74
            },
2 ✔
75
          }),
2 ✔
76
          pathParam({ name: 'user', example: 'badges' }),
2 ✔
77
          pathParam({ name: 'repo', example: 'squint' }),
2 ✔
78
          pathParam({ name: 'branch', example: 'main' }),
2 ✔
79
          queryParam({ name: 'authorFilter', example: 'calebcartwright' }),
2 ✔
80
        ],
2 ✔
81
      },
2 ✔
82
    },
2 ✔
83
  }
2 ✔
84

2 ✔
85
  static defaultBadgeData = { label: 'commit activity', color: 'blue' }
2 ✔
86

2 ✔
87
  static render({ interval, commitCount, authorFilter }) {
2 ✔
88
    // If total commits selected change label from commit activity to commits
×
89
    const label = interval === 't' ? 'commits' : 'commit activity'
×
90
    const authorFilterLabel = authorFilter ? ` by ${authorFilter}` : ''
×
91

×
92
    const intervalLabel = {
×
93
      t: '',
×
94
      y: '/year',
×
95
      m: '/month',
×
96
      '4w': '/four weeks',
×
97
      w: '/week',
×
98
    }[interval]
×
99

×
100
    return {
×
101
      label: `${label}${authorFilterLabel}`,
×
102
      message: `${metric(commitCount)}${intervalLabel}`,
×
103
    }
×
104
  }
×
105

2 ✔
106
  async fetch({ interval, user, repo, branch = 'HEAD' }) {
2 ✔
107
    const since = this.constructor.getIntervalQueryStartDate({ interval })
×
108
    return this._requestGraphql({
×
109
      query: gql`
×
110
        query (
×
111
          $user: String!
×
112
          $repo: String!
×
113
          $branch: String!
×
114
          $since: GitTimestamp
×
115
        ) {
×
116
          repository(owner: $user, name: $repo) {
×
117
            object(expression: $branch) {
×
118
              ... on Commit {
×
119
                history(since: $since) {
×
120
                  totalCount
×
121
                }
×
122
              }
×
123
            }
×
124
          }
×
125
        }
×
126
      `,
×
127
      variables: {
×
128
        user,
×
129
        repo,
×
130
        branch,
×
131
        since,
×
132
      },
×
133
      schema,
×
134
      transformErrors,
×
135
    })
×
136
  }
×
137

2 ✔
138
  async fetchAuthorFilter({
2 ✔
139
    interval,
×
140
    user,
×
141
    repo,
×
142
    branch = 'HEAD',
×
143
    authorFilter,
×
144
  }) {
×
145
    const since =
×
146
      this.constructor.getIntervalQueryStartDate({ interval }) || undefined
×
147

×
148
    return this._request({
×
149
      url: `/repos/${user}/${repo}/commits`,
×
150
      options: {
×
151
        searchParams: {
×
152
          sha: branch,
×
153
          author: authorFilter,
×
154
          per_page: '1',
×
155
          since,
×
156
        },
×
157
      },
×
158
      httpErrors: httpErrorsFor('repo or branch not found'),
×
159
    })
×
160
  }
×
161

2 ✔
162
  static transform({ data }) {
2 ✔
163
    const {
1 ✔
164
      repository: { object: repo },
1 ✔
165
    } = data
1 ✔
166

1 ✔
167
    if (!repo) {
1 ✔
168
      throw new InvalidResponse({ prettyMessage: 'invalid branch' })
1 ✔
169
    }
1 ✔
170

×
171
    return repo.history.totalCount
×
172
  }
1 ✔
173

2 ✔
174
  static transformAuthorFilter({ res }) {
2 ✔
175
    const parsed = parseLinkHeader(res.headers.link)
×
176

×
177
    if (!parsed) {
×
178
      return 0
×
179
    }
×
180

×
181
    return parsed.last.page
×
182
  }
×
183

2 ✔
184
  static getIntervalQueryStartDate({ interval }) {
2 ✔
185
    const now = new Date()
4 ✔
186

4 ✔
187
    if (interval === 't') {
4 !
188
      return null
×
189
    } else if (interval === 'y') {
4 ✔
190
      now.setUTCFullYear(now.getUTCFullYear() - 1)
1 ✔
191
    } else if (interval === 'm' || interval === '4w') {
4 ✔
192
      now.setUTCDate(now.getUTCDate() - 30)
2 ✔
193
    } else {
3 ✔
194
      now.setUTCDate(now.getUTCDate() - 7)
1 ✔
195
    }
1 ✔
196

4 ✔
197
    return now.toISOString()
4 ✔
198
  }
4 ✔
199

2 ✔
200
  async handle({ interval, user, repo, branch }, { authorFilter }) {
2 ✔
201
    let commitCount
×
202
    if (authorFilter) {
×
203
      const authorFilterRes = await this.fetchAuthorFilter({
×
204
        interval,
×
205
        user,
×
206
        repo,
×
207
        branch,
×
208
        authorFilter,
×
209
      })
×
210
      commitCount = this.constructor.transformAuthorFilter(authorFilterRes)
×
211
    } else {
×
212
      const json = await this.fetch({ interval, user, repo, branch })
×
213
      commitCount = this.constructor.transform(json)
×
214
    }
×
215
    return this.constructor.render({ interval, commitCount, authorFilter })
×
216
  }
×
217
}
2 ✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc