@stats-forge/github-stats-forge-core 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -10
- package/build/api/api-result.d.ts +6 -7
- package/build/api/api-result.d.ts.map +1 -1
- package/build/api/api-result.js +4 -5
- package/build/api/contributed-to.d.ts +23 -0
- package/build/api/contributed-to.d.ts.map +1 -0
- package/build/api/contributed-to.js +43 -0
- package/build/api/gist.d.ts +8 -22
- package/build/api/gist.d.ts.map +1 -1
- package/build/api/gist.js +14 -36
- package/build/api/handler.d.ts +16 -0
- package/build/api/handler.d.ts.map +1 -0
- package/build/api/handler.js +29 -0
- package/build/api/index.d.ts +13 -12
- package/build/api/index.d.ts.map +1 -1
- package/build/api/index.js +1 -0
- package/build/api/params.d.ts +21 -12
- package/build/api/params.d.ts.map +1 -1
- package/build/api/params.js +41 -19
- package/build/api/pin.d.ts +11 -31
- package/build/api/pin.d.ts.map +1 -1
- package/build/api/pin.js +35 -63
- package/build/api/stats.d.ts +15 -16
- package/build/api/stats.d.ts.map +1 -1
- package/build/api/stats.js +60 -99
- package/build/api/top-langs.d.ts +13 -18
- package/build/api/top-langs.d.ts.map +1 -1
- package/build/api/top-langs.js +35 -72
- package/build/api/wakatime.d.ts +11 -17
- package/build/api/wakatime.d.ts.map +1 -1
- package/build/api/wakatime.js +27 -64
- package/build/calculateRank.d.ts +0 -9
- package/build/calculateRank.d.ts.map +1 -1
- package/build/calculateRank.js +12 -17
- package/build/cards/contributed-to.d.ts +19 -0
- package/build/cards/contributed-to.d.ts.map +1 -0
- package/build/cards/contributed-to.js +242 -0
- package/build/cards/gist.d.ts +3 -4
- package/build/cards/gist.d.ts.map +1 -1
- package/build/cards/gist.js +38 -43
- package/build/cards/options.d.ts +1 -1
- package/build/cards/repo.d.ts +14 -5
- package/build/cards/repo.d.ts.map +1 -1
- package/build/cards/repo.js +93 -103
- package/build/cards/stats.d.ts +18 -11
- package/build/cards/stats.d.ts.map +1 -1
- package/build/cards/stats.js +191 -222
- package/build/cards/top-languages.d.ts +18 -33
- package/build/cards/top-languages.d.ts.map +1 -1
- package/build/cards/top-languages.js +196 -387
- package/build/cards/wakatime.d.ts +17 -8
- package/build/cards/wakatime.d.ts.map +1 -1
- package/build/cards/wakatime.js +133 -187
- package/build/common/Card.d.ts +20 -59
- package/build/common/Card.d.ts.map +1 -1
- package/build/common/Card.js +131 -198
- package/build/common/I18n.d.ts +1 -7
- package/build/common/I18n.d.ts.map +1 -1
- package/build/common/I18n.js +7 -8
- package/build/common/brand.d.ts +76 -0
- package/build/common/brand.d.ts.map +1 -0
- package/build/common/brand.js +73 -0
- package/build/common/color.d.ts +4 -17
- package/build/common/color.d.ts.map +1 -1
- package/build/common/color.js +67 -49
- package/build/common/config.d.ts +4 -14
- package/build/common/config.d.ts.map +1 -1
- package/build/common/config.js +3 -16
- package/build/common/constants.d.ts +6 -2
- package/build/common/constants.d.ts.map +1 -1
- package/build/common/constants.js +5 -1
- package/build/common/date.d.ts +50 -5
- package/build/common/date.d.ts.map +1 -1
- package/build/common/date.js +104 -4
- package/build/common/emojiMap.d.ts +8 -0
- package/build/common/emojiMap.d.ts.map +1 -0
- package/build/common/emojiMap.js +10 -0
- package/build/common/emojiMap.json +1915 -0
- package/build/common/error.d.ts +1 -10
- package/build/common/error.d.ts.map +1 -1
- package/build/common/error.js +1 -10
- package/build/common/fmt.d.ts +3 -15
- package/build/common/fmt.d.ts.map +1 -1
- package/build/common/fmt.js +7 -35
- package/build/common/http.d.ts +2 -7
- package/build/common/http.d.ts.map +1 -1
- package/build/common/http.js +8 -17
- package/build/common/icons.d.ts +20 -20
- package/build/common/icons.d.ts.map +1 -1
- package/build/common/icons.js +108 -67
- package/build/common/languageColors.d.ts +0 -1
- package/build/common/languageColors.d.ts.map +1 -1
- package/build/common/languageColors.js +1 -4
- package/build/common/languageColors.json +18 -2
- package/build/common/ops.d.ts +11 -17
- package/build/common/ops.d.ts.map +1 -1
- package/build/common/ops.js +22 -39
- package/build/common/render.d.ts +34 -82
- package/build/common/render.d.ts.map +1 -1
- package/build/common/render.js +225 -259
- package/build/common/retryer.d.ts +2 -7
- package/build/common/retryer.d.ts.map +1 -1
- package/build/common/retryer.js +4 -11
- package/build/fetchers/contributed-to.d.ts +62 -0
- package/build/fetchers/contributed-to.d.ts.map +1 -0
- package/build/fetchers/contributed-to.js +184 -0
- package/build/fetchers/gist.d.ts +2 -5
- package/build/fetchers/gist.d.ts.map +1 -1
- package/build/fetchers/gist.js +1 -5
- package/build/fetchers/graphql-error.d.ts +13 -0
- package/build/fetchers/graphql-error.d.ts.map +1 -0
- package/build/fetchers/graphql-error.js +29 -0
- package/build/fetchers/index.d.ts +13 -12
- package/build/fetchers/index.d.ts.map +1 -1
- package/build/fetchers/index.js +1 -0
- package/build/fetchers/repo.d.ts +2 -11
- package/build/fetchers/repo.d.ts.map +1 -1
- package/build/fetchers/repo.js +20 -33
- package/build/fetchers/stats.d.ts +16 -40
- package/build/fetchers/stats.d.ts.map +1 -1
- package/build/fetchers/stats.js +89 -271
- package/build/fetchers/top-languages.d.ts +2 -9
- package/build/fetchers/top-languages.d.ts.map +1 -1
- package/build/fetchers/top-languages.js +16 -47
- package/build/fetchers/types.d.ts +25 -1
- package/build/fetchers/types.d.ts.map +1 -1
- package/build/fetchers/wakatime.d.ts +2 -6
- package/build/fetchers/wakatime.d.ts.map +1 -1
- package/build/fetchers/wakatime.js +1 -5
- package/build/graphql/contributionsCollection.d.ts +9 -0
- package/build/graphql/contributionsCollection.d.ts.map +1 -0
- package/build/graphql/contributionsCollection.js +12 -0
- package/build/graphql/contributionsDocument.d.ts +6 -6
- package/build/graphql/contributionsDocument.d.ts.map +1 -1
- package/build/graphql/contributionsDocument.js +8 -16
- package/build/graphql/generated/contributed-to.d.ts +22 -0
- package/build/graphql/generated/contributed-to.d.ts.map +1 -0
- package/build/graphql/generated/contributed-to.js +11 -0
- package/build/graphql/generated/gist.d.ts +1 -1
- package/build/graphql/generated/repo.d.ts +1 -1
- package/build/graphql/generated/stats.d.ts +16 -4
- package/build/graphql/generated/stats.d.ts.map +1 -1
- package/build/graphql/generated/stats.js +2 -2
- package/build/graphql/generated/top-languages.d.ts +2 -2
- package/build/graphql/graphqlDocument.d.ts +1 -2
- package/build/graphql/graphqlDocument.d.ts.map +1 -1
- package/build/graphql/graphqlDocument.js +0 -1
- package/build/graphql/reposContributedToDocument.d.ts +4 -5
- package/build/graphql/reposContributedToDocument.d.ts.map +1 -1
- package/build/graphql/reposContributedToDocument.js +11 -7
- package/build/markup/css.d.ts +35 -0
- package/build/markup/css.d.ts.map +1 -0
- package/build/markup/css.js +64 -0
- package/build/markup/element.d.ts +41 -0
- package/build/markup/element.d.ts.map +1 -0
- package/build/markup/element.js +127 -0
- package/build/markup/html.d.ts +13 -0
- package/build/markup/html.d.ts.map +1 -0
- package/build/markup/html.js +27 -0
- package/build/markup/index.d.ts +9 -0
- package/build/markup/index.d.ts.map +1 -0
- package/build/markup/index.js +6 -0
- package/build/markup/serialize.d.ts +16 -0
- package/build/markup/serialize.d.ts.map +1 -0
- package/build/markup/serialize.js +17 -0
- package/build/themes/index.d.ts +332 -333
- package/build/themes/index.d.ts.map +1 -1
- package/build/themes/index.js +1 -1
- package/build/translations.d.ts +66 -7
- package/build/translations.d.ts.map +1 -1
- package/build/translations.js +686 -629
- package/package.json +17 -26
- package/src/api/api-result.ts +10 -9
- package/src/api/contributed-to.ts +84 -0
- package/src/api/gist.ts +22 -46
- package/src/api/handler.ts +42 -0
- package/src/api/index.ts +13 -12
- package/src/api/params.ts +74 -29
- package/src/api/pin.ts +38 -65
- package/src/api/stats.ts +81 -118
- package/src/api/top-langs.ts +38 -80
- package/src/api/wakatime.ts +35 -73
- package/src/calculateRank.ts +12 -23
- package/src/cards/contributed-to.ts +373 -0
- package/src/cards/gist.ts +55 -49
- package/src/cards/options.ts +1 -1
- package/src/cards/repo.ts +134 -120
- package/src/cards/stats.ts +262 -252
- package/src/cards/top-languages.ts +315 -422
- package/src/cards/wakatime.ts +207 -225
- package/src/common/Card.ts +207 -214
- package/src/common/I18n.ts +7 -8
- package/src/common/brand.ts +109 -0
- package/src/common/color.ts +80 -57
- package/src/common/config.ts +5 -18
- package/src/common/constants.ts +8 -2
- package/src/common/date.ts +135 -5
- package/src/common/emojiMap.json +1915 -0
- package/src/common/emojiMap.ts +13 -0
- package/src/common/error.ts +2 -11
- package/src/common/fmt.ts +7 -44
- package/src/common/http.ts +9 -15
- package/src/common/icons.ts +122 -42
- package/src/common/languageColors.json +18 -2
- package/src/common/languageColors.ts +1 -4
- package/src/common/ops.ts +31 -48
- package/src/common/render.ts +334 -284
- package/src/common/retryer.ts +8 -15
- package/src/fetchers/contributed-to.ts +271 -0
- package/src/fetchers/gist.ts +8 -12
- package/src/fetchers/graphql-error.ts +36 -0
- package/src/fetchers/index.ts +15 -12
- package/src/fetchers/repo.ts +27 -40
- package/src/fetchers/stats.ts +157 -366
- package/src/fetchers/top-languages.ts +27 -55
- package/src/fetchers/types.ts +27 -1
- package/src/fetchers/wakatime.ts +5 -9
- package/src/graphql/contributionsCollection.ts +18 -0
- package/src/graphql/contributionsDocument.ts +14 -19
- package/src/graphql/generated/contributed-to.ts +25 -0
- package/src/graphql/generated/gist.ts +1 -1
- package/src/graphql/generated/repo.ts +1 -1
- package/src/graphql/generated/stats.ts +21 -8
- package/src/graphql/generated/top-languages.ts +2 -2
- package/src/graphql/graphqlDocument.ts +1 -2
- package/src/graphql/queries/contributed-to.graphql +8 -0
- package/src/graphql/queries/stats.graphql +14 -2
- package/src/graphql/reposContributedToDocument.ts +16 -15
- package/src/markup/css.ts +118 -0
- package/src/markup/element.ts +246 -0
- package/src/markup/html.ts +34 -0
- package/src/markup/index.ts +9 -0
- package/src/markup/serialize.ts +27 -0
- package/src/themes/index.ts +1 -1
- package/src/translations.ts +699 -629
- package/build/cards/index.d.ts +0 -11
- package/build/cards/index.d.ts.map +0 -1
- package/build/cards/index.js +0 -7
- package/build/common/html.d.ts +0 -8
- package/build/common/html.d.ts.map +0 -1
- package/build/common/html.js +0 -14
- package/build/index.d.ts +0 -15
- package/build/index.d.ts.map +0 -1
- package/build/index.js +0 -10
- package/src/_emoji-name-map.d.ts +0 -10
- package/src/_github-username-regex.d.ts +0 -4
- package/src/cards/index.ts +0 -21
- package/src/common/html.ts +0 -17
- package/src/index.ts +0 -18
package/src/fetchers/stats.ts
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import type {
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import type {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
1
|
+
import { calculateRank } from '../calculateRank.ts';
|
|
2
|
+
import type { CardConfig } from '../common/config.ts';
|
|
3
|
+
import { GITHUB_USERNAME_PATTERN } from '../common/constants.ts';
|
|
4
|
+
import type { GitHubDateRange } from '../common/date.ts';
|
|
5
|
+
import {
|
|
6
|
+
getWidestRange,
|
|
7
|
+
toContributionRanges,
|
|
8
|
+
toGitHubDateTime,
|
|
9
|
+
toRange,
|
|
10
|
+
toYearRanges,
|
|
11
|
+
} from '../common/date.ts';
|
|
12
|
+
import { CardError, USER_NOT_FOUND } from '../common/error.ts';
|
|
13
|
+
import { createGraphQLFetcher, httpRequest } from '../common/http.ts';
|
|
14
|
+
import type { FetcherContext, GraphQLResponse } from '../common/http.ts';
|
|
15
|
+
import { logger } from '../common/log.ts';
|
|
16
|
+
import { buildSearchFilter, parseOwnerAffiliations } from '../common/ops.ts';
|
|
17
|
+
import { retryer } from '../common/retryer.ts';
|
|
18
|
+
import type { FetcherResponse } from '../common/retryer.ts';
|
|
19
|
+
import { buildContributionsDocument } from '../graphql/contributionsDocument.ts';
|
|
20
|
+
import { UserInfoDocument, UserReposDocument } from '../graphql/generated/stats.ts';
|
|
17
21
|
import type {
|
|
22
|
+
RangeContributionsFragment,
|
|
18
23
|
RepoNodeFragment,
|
|
19
24
|
UserInfoQuery,
|
|
20
25
|
UserInfoQueryVariables,
|
|
21
|
-
} from '../graphql/generated/stats.
|
|
22
|
-
import {
|
|
23
|
-
MAX_REPOSITORIES_LIMIT,
|
|
24
|
-
buildReposContributedToDocument,
|
|
25
|
-
} from '../graphql/reposContributedToDocument.js';
|
|
26
|
+
} from '../graphql/generated/stats.ts';
|
|
26
27
|
|
|
27
|
-
import
|
|
28
|
+
import { fetchReposContributedTo } from './contributed-to.ts';
|
|
29
|
+
import { graphqlError } from './graphql-error.ts';
|
|
30
|
+
import type { RepoUserStats, StatsData } from './types.ts';
|
|
28
31
|
|
|
29
32
|
/** The subset of the stats response `statsFetcher` returns and threads on. */
|
|
30
33
|
type StatsFetcherResponse = Pick<GraphQLResponse<UserInfoQuery>, 'data' | 'statusText'>;
|
|
@@ -36,19 +39,10 @@ const reposFetcher = createGraphQLFetcher(UserReposDocument, 'bearer');
|
|
|
36
39
|
/**
|
|
37
40
|
* Fetch stats information for a given username.
|
|
38
41
|
*
|
|
39
|
-
* @param variables Fetcher variables.
|
|
40
|
-
* @param variables.username GitHub username.
|
|
41
|
-
* @param variables.includeMergedPullRequests Include merged pull requests.
|
|
42
|
-
* @param variables.includeDiscussions Include discussions.
|
|
43
|
-
* @param variables.includeDiscussionsAnswers Include discussions answers.
|
|
44
|
-
* @param variables.startTime Time to start the count of total commits.
|
|
45
|
-
* @param variables.ownerAffiliations The owner affiliations to filter by. Default: OWNER.
|
|
46
|
-
* @param variables.includeUserRepositories Whether to include the user's own repositories in the repos contributed to.
|
|
47
|
-
* @param config Deployment config supplying the PAT pool.
|
|
48
|
-
* @returns The stats response, with every fetched page's repos merged in.
|
|
49
|
-
*
|
|
50
42
|
* @description Supports multi-page fetching when the `FETCH_MULTI_PAGE_STARS`
|
|
51
43
|
* env variable is `true` or a fetch limit.
|
|
44
|
+
*
|
|
45
|
+
* @returns The stats response, with every fetched page's repos merged in.
|
|
52
46
|
*/
|
|
53
47
|
const statsFetcher = async (
|
|
54
48
|
{
|
|
@@ -56,7 +50,7 @@ const statsFetcher = async (
|
|
|
56
50
|
includeMergedPullRequests,
|
|
57
51
|
includeDiscussions,
|
|
58
52
|
includeDiscussionsAnswers,
|
|
59
|
-
|
|
53
|
+
commitsRange,
|
|
60
54
|
ownerAffiliations,
|
|
61
55
|
includeUserRepositories,
|
|
62
56
|
}: {
|
|
@@ -64,7 +58,8 @@ const statsFetcher = async (
|
|
|
64
58
|
includeMergedPullRequests: boolean;
|
|
65
59
|
includeDiscussions: boolean;
|
|
66
60
|
includeDiscussionsAnswers: boolean;
|
|
67
|
-
|
|
61
|
+
/** The range the commit count covers; the last year, as GitHub reckons it, when absent. */
|
|
62
|
+
commitsRange: GitHubDateRange | undefined;
|
|
68
63
|
ownerAffiliations: UserInfoQueryVariables['ownerAffiliations'];
|
|
69
64
|
includeUserRepositories: boolean;
|
|
70
65
|
},
|
|
@@ -79,7 +74,9 @@ const statsFetcher = async (
|
|
|
79
74
|
includeMergedPullRequests,
|
|
80
75
|
includeDiscussions,
|
|
81
76
|
includeDiscussionsAnswers,
|
|
82
|
-
|
|
77
|
+
// both ends together: an open `to` would run the range into the next 1st of January
|
|
78
|
+
startTime: commitsRange && toGitHubDateTime(commitsRange.from),
|
|
79
|
+
endTime: commitsRange && toGitHubDateTime(commitsRange.to),
|
|
83
80
|
ownerAffiliations,
|
|
84
81
|
includeUserRepositories,
|
|
85
82
|
},
|
|
@@ -118,11 +115,11 @@ const statsFetcher = async (
|
|
|
118
115
|
|
|
119
116
|
pageRepositories = page.data.data.user?.repositories;
|
|
120
117
|
extraRepoNodes.push(...(pageRepositories?.nodes ?? []));
|
|
121
|
-
fetchedPages
|
|
118
|
+
fetchedPages += 1;
|
|
122
119
|
}
|
|
123
120
|
|
|
124
121
|
if (extraRepoNodes.length > 0) {
|
|
125
|
-
//
|
|
122
|
+
// defensive: the merge below is the only place a parsed response is mutated
|
|
126
123
|
stats = structuredClone({
|
|
127
124
|
data: stats.data,
|
|
128
125
|
statusText: stats.statusText,
|
|
@@ -133,31 +130,34 @@ const statsFetcher = async (
|
|
|
133
130
|
return stats;
|
|
134
131
|
};
|
|
135
132
|
|
|
133
|
+
/** One REST search: which index, the qualifiers scoping it, and the ones naming what to count. */
|
|
134
|
+
interface SearchVariables {
|
|
135
|
+
/** `commits` or `issues`, the latter covering pull requests too. */
|
|
136
|
+
type: string;
|
|
137
|
+
/** Qualifiers appended after the scope, already `+`-joined. */
|
|
138
|
+
filter: string;
|
|
139
|
+
repo: Array<string>;
|
|
140
|
+
owner: Array<string>;
|
|
141
|
+
}
|
|
142
|
+
|
|
136
143
|
/**
|
|
137
144
|
* Fetch total items count using the REST search API.
|
|
138
145
|
*
|
|
139
|
-
* @param variables Fetcher variables.
|
|
140
|
-
* @param token GitHub token.
|
|
141
|
-
* @param context What the retryer supplies each attempt.
|
|
142
|
-
* @param context.fetch Transport to send the request with.
|
|
143
|
-
* @returns The search response, carrying `total_count`.
|
|
144
|
-
*
|
|
145
146
|
* @see https://developer.github.com/v3/search/#search-commits
|
|
147
|
+
*
|
|
148
|
+
* @returns The search response, carrying `total_count`.
|
|
146
149
|
*/
|
|
147
150
|
const fetchTotalItems = (
|
|
148
|
-
|
|
151
|
+
{ type, filter, repo, owner }: SearchVariables,
|
|
149
152
|
token: string,
|
|
150
153
|
{ fetch }: FetcherContext,
|
|
151
|
-
): Promise<FetcherResponse<{ total_count?: number }>> =>
|
|
152
|
-
|
|
153
|
-
const filter = String(variables['filter']);
|
|
154
|
-
const repo = variables['repo'] as Array<string> | string;
|
|
155
|
-
const owner = variables['owner'] as Array<string> | string;
|
|
156
|
-
return httpRequest(
|
|
154
|
+
): Promise<FetcherResponse<{ total_count?: number }>> =>
|
|
155
|
+
httpRequest(
|
|
157
156
|
fetch,
|
|
158
|
-
`https://api.github.com/search/${type}?per_page=1&q
|
|
159
|
-
|
|
160
|
-
|
|
157
|
+
`https://api.github.com/search/${type}?per_page=1&q=${buildSearchFilter(repo, owner).replaceAll(
|
|
158
|
+
' ',
|
|
159
|
+
'+',
|
|
160
|
+
)}${filter}`,
|
|
161
161
|
{
|
|
162
162
|
method: 'GET',
|
|
163
163
|
headers: {
|
|
@@ -167,41 +167,21 @@ const fetchTotalItems = (
|
|
|
167
167
|
},
|
|
168
168
|
},
|
|
169
169
|
);
|
|
170
|
-
};
|
|
171
170
|
|
|
172
171
|
/**
|
|
173
172
|
* Fetch a total count for a given username via the REST search API.
|
|
174
173
|
*
|
|
175
|
-
* @param props Fetcher props.
|
|
176
|
-
* @param props.username GitHub username.
|
|
177
|
-
* @param props.repo Repositories the search is scoped to.
|
|
178
|
-
* @param props.owner Owners the search is scoped to.
|
|
179
|
-
* @param props.type Search endpoint to hit, `issues` or `commits`.
|
|
180
|
-
* @param props.filter Query fragment appended to the scope filter.
|
|
181
|
-
* @param config Deployment config supplying the PAT pool.
|
|
182
|
-
* @returns Total count.
|
|
183
|
-
*
|
|
184
174
|
* The GraphQL API can't return this.
|
|
185
175
|
* @see https://github.com/anuraghazra/github-readme-stats/issues/92#issuecomment-661026467
|
|
186
176
|
* @see https://github.com/anuraghazra/github-readme-stats/pull/211
|
|
177
|
+
*
|
|
178
|
+
* @returns Total count.
|
|
187
179
|
*/
|
|
188
180
|
const totalItemsFetcher = async (
|
|
189
|
-
{
|
|
190
|
-
username,
|
|
191
|
-
repo,
|
|
192
|
-
owner,
|
|
193
|
-
type,
|
|
194
|
-
filter,
|
|
195
|
-
}: {
|
|
196
|
-
username: string;
|
|
197
|
-
repo: Array<string>;
|
|
198
|
-
owner: Array<string>;
|
|
199
|
-
type: string;
|
|
200
|
-
filter: string;
|
|
201
|
-
},
|
|
181
|
+
{ username, ...search }: SearchVariables & { username: string },
|
|
202
182
|
config: CardConfig,
|
|
203
183
|
): Promise<number> => {
|
|
204
|
-
if (!
|
|
184
|
+
if (!GITHUB_USERNAME_PATTERN.test(username)) {
|
|
205
185
|
logger.log('Invalid username provided.');
|
|
206
186
|
throw new CardError('Invalid username provided.', {
|
|
207
187
|
code: 'invalid_param',
|
|
@@ -211,19 +191,15 @@ const totalItemsFetcher = async (
|
|
|
211
191
|
|
|
212
192
|
let res: FetcherResponse<{ total_count?: number }>;
|
|
213
193
|
try {
|
|
214
|
-
res = await retryer
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
);
|
|
219
|
-
} catch (err) {
|
|
220
|
-
logger.log(err);
|
|
221
|
-
throw err;
|
|
194
|
+
res = await retryer(fetchTotalItems, search, config);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
logger.log(error);
|
|
197
|
+
throw error;
|
|
222
198
|
}
|
|
223
199
|
|
|
224
200
|
const totalCount = res.data.total_count;
|
|
225
|
-
if (totalCount
|
|
226
|
-
logger.error(
|
|
201
|
+
if (typeof totalCount !== 'number' || Number.isNaN(totalCount)) {
|
|
202
|
+
logger.error(`GitHub error: ${JSON.stringify(res.data)}`);
|
|
227
203
|
throw new CardError('Could not fetch data from GitHub REST API.', {
|
|
228
204
|
code: 'upstream',
|
|
229
205
|
});
|
|
@@ -231,19 +207,41 @@ const totalItemsFetcher = async (
|
|
|
231
207
|
return totalCount;
|
|
232
208
|
};
|
|
233
209
|
|
|
210
|
+
/** The flags that ask for a per-repository count, as the query names them. */
|
|
211
|
+
interface RepoUserStatsFlags {
|
|
212
|
+
include_prs_authored?: boolean | undefined;
|
|
213
|
+
include_prs_commented?: boolean | undefined;
|
|
214
|
+
include_prs_reviewed?: boolean | undefined;
|
|
215
|
+
include_issues_authored?: boolean | undefined;
|
|
216
|
+
include_issues_commented?: boolean | undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Each count the REST search API answers: the flag asking for it, and the qualifiers that find it. */
|
|
220
|
+
const REPO_USER_SEARCHES: ReadonlyArray<
|
|
221
|
+
[flag: keyof RepoUserStatsFlags, stat: keyof RepoUserStats, filter: (username: string) => string]
|
|
222
|
+
> = [
|
|
223
|
+
['include_prs_authored', 'totalPRsAuthored', (username) => `author:${username}+type:pr`],
|
|
224
|
+
[
|
|
225
|
+
'include_prs_commented',
|
|
226
|
+
'totalPRsCommented',
|
|
227
|
+
(username) => `commenter:${username}+-author:${username}+type:pr`,
|
|
228
|
+
],
|
|
229
|
+
[
|
|
230
|
+
'include_prs_reviewed',
|
|
231
|
+
'totalPRsReviewed',
|
|
232
|
+
(username) => `reviewed-by:${username}+-author:${username}+type:pr`,
|
|
233
|
+
],
|
|
234
|
+
['include_issues_authored', 'totalIssuesAuthored', (username) => `author:${username}+type:issue`],
|
|
235
|
+
[
|
|
236
|
+
'include_issues_commented',
|
|
237
|
+
'totalIssuesCommented',
|
|
238
|
+
(username) => `commenter:${username}+-author:${username}+type:issue`,
|
|
239
|
+
],
|
|
240
|
+
];
|
|
241
|
+
|
|
234
242
|
/**
|
|
235
243
|
* Fetch the per-repository counts the REST search API answers, one request each.
|
|
236
244
|
*
|
|
237
|
-
* @param props Fetcher props.
|
|
238
|
-
* @param props.username GitHub username.
|
|
239
|
-
* @param props.repo Repositories the search is scoped to.
|
|
240
|
-
* @param props.owner Owners the search is scoped to.
|
|
241
|
-
* @param props.include_prs_authored Include count of PRs authored.
|
|
242
|
-
* @param props.include_prs_commented Include count of PRs commented.
|
|
243
|
-
* @param props.include_prs_reviewed Include count of PRs reviewed.
|
|
244
|
-
* @param props.include_issues_authored Include count of issues authored.
|
|
245
|
-
* @param props.include_issues_commented Include count of issues commented.
|
|
246
|
-
* @param config Deployment config supplying the PAT pool.
|
|
247
245
|
* @returns Only the counts that were asked for.
|
|
248
246
|
*/
|
|
249
247
|
const fetchRepoUserStats = async (
|
|
@@ -251,126 +249,46 @@ const fetchRepoUserStats = async (
|
|
|
251
249
|
username,
|
|
252
250
|
repo = [],
|
|
253
251
|
owner = [],
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
include_prs_reviewed = false,
|
|
257
|
-
include_issues_authored = false,
|
|
258
|
-
include_issues_commented = false,
|
|
259
|
-
}: {
|
|
252
|
+
...flags
|
|
253
|
+
}: RepoUserStatsFlags & {
|
|
260
254
|
username: string;
|
|
261
255
|
repo?: Array<string>;
|
|
262
256
|
owner?: Array<string>;
|
|
263
|
-
include_prs_authored?: boolean | undefined;
|
|
264
|
-
include_prs_commented?: boolean | undefined;
|
|
265
|
-
include_prs_reviewed?: boolean | undefined;
|
|
266
|
-
include_issues_authored?: boolean | undefined;
|
|
267
|
-
include_issues_commented?: boolean | undefined;
|
|
268
257
|
},
|
|
269
258
|
config: CardConfig,
|
|
270
259
|
): Promise<RepoUserStats> => {
|
|
271
260
|
const stats: RepoUserStats = {};
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
username,
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
filter: `author:${username}+type:pr`,
|
|
280
|
-
},
|
|
281
|
-
config,
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
if (include_prs_commented) {
|
|
285
|
-
stats.totalPRsCommented = await totalItemsFetcher(
|
|
286
|
-
{
|
|
287
|
-
username,
|
|
288
|
-
repo,
|
|
289
|
-
owner,
|
|
290
|
-
type: 'issues',
|
|
291
|
-
filter: `commenter:${username}+-author:${username}+type:pr`,
|
|
292
|
-
},
|
|
293
|
-
config,
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
if (include_prs_reviewed) {
|
|
297
|
-
stats.totalPRsReviewed = await totalItemsFetcher(
|
|
298
|
-
{
|
|
299
|
-
username,
|
|
300
|
-
repo,
|
|
301
|
-
owner,
|
|
302
|
-
type: 'issues',
|
|
303
|
-
filter: `reviewed-by:${username}+-author:${username}+type:pr`,
|
|
304
|
-
},
|
|
305
|
-
config,
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
if (include_issues_authored) {
|
|
309
|
-
stats.totalIssuesAuthored = await totalItemsFetcher(
|
|
310
|
-
{
|
|
311
|
-
username,
|
|
312
|
-
repo,
|
|
313
|
-
owner,
|
|
314
|
-
type: 'issues',
|
|
315
|
-
filter: `author:${username}+type:issue`,
|
|
316
|
-
},
|
|
317
|
-
config,
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
if (include_issues_commented) {
|
|
321
|
-
stats.totalIssuesCommented = await totalItemsFetcher(
|
|
322
|
-
{
|
|
323
|
-
username,
|
|
324
|
-
repo,
|
|
325
|
-
owner,
|
|
326
|
-
type: 'issues',
|
|
327
|
-
filter: `commenter:${username}+-author:${username}+type:issue`,
|
|
328
|
-
},
|
|
329
|
-
config,
|
|
330
|
-
);
|
|
261
|
+
for (const [flag, stat, filter] of REPO_USER_SEARCHES) {
|
|
262
|
+
if (flags[flag]) {
|
|
263
|
+
stats[stat] = await totalItemsFetcher(
|
|
264
|
+
{ username, repo, owner, type: 'issues', filter: filter(username) },
|
|
265
|
+
config,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
331
268
|
}
|
|
332
269
|
return stats;
|
|
333
270
|
};
|
|
334
271
|
|
|
335
272
|
/**
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
* @param errors Errors from the response envelope.
|
|
339
|
-
* @param statusText HTTP status text, used as the error type when GitHub gave a message.
|
|
340
|
-
* @param fallback Message when GitHub gave none.
|
|
341
|
-
*/
|
|
342
|
-
const graphqlError = (
|
|
343
|
-
errors: NonNullable<GraphQLResponse<unknown>['data']['errors']>,
|
|
344
|
-
statusText: string,
|
|
345
|
-
fallback: string,
|
|
346
|
-
): CardError => {
|
|
347
|
-
logger.error(errors);
|
|
348
|
-
const message = errors[0]?.message;
|
|
349
|
-
return message
|
|
350
|
-
? new CardError(wrapTextMultiline(message, 525, 12)[0] ?? '', {
|
|
351
|
-
code: 'upstream',
|
|
352
|
-
secondaryMessage: statusText,
|
|
353
|
-
})
|
|
354
|
-
: new CardError(fallback, { code: 'upstream' });
|
|
355
|
-
};
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Fetch all-time contributions by building a single GraphQL query
|
|
359
|
-
* for all the given years.
|
|
273
|
+
* Sum one figure over a set of ranges, fetched as aliased fields of a single query.
|
|
360
274
|
*
|
|
361
275
|
* Whether private contributions are included depends on the user's profile settings:
|
|
362
276
|
* https://docs.github.com/en/account-and-profile/how-tos/contribution-settings/manage-visibility-settings-for-private-contributions-and-achievements#changing-the-visibility-of-your-private-contributions
|
|
277
|
+
*
|
|
278
|
+
* @returns The total over every range, and 0 when there are none.
|
|
363
279
|
*/
|
|
364
|
-
const
|
|
280
|
+
const sumOverRanges = async (
|
|
365
281
|
username: string,
|
|
366
|
-
|
|
282
|
+
ranges: Array<GitHubDateRange>,
|
|
283
|
+
/** What one range contributes to the total. */
|
|
284
|
+
pick: (block: RangeContributionsFragment) => number,
|
|
367
285
|
config: CardConfig,
|
|
368
286
|
): Promise<number> => {
|
|
369
|
-
if (
|
|
287
|
+
if (ranges.length === 0) {
|
|
370
288
|
return 0;
|
|
371
289
|
}
|
|
372
290
|
|
|
373
|
-
const contributionsFetcher = createGraphQLFetcher(buildContributionsDocument(
|
|
291
|
+
const contributionsFetcher = createGraphQLFetcher(buildContributionsDocument(ranges), 'bearer');
|
|
374
292
|
|
|
375
293
|
const contribRes = await retryer(contributionsFetcher, { login: username }, config);
|
|
376
294
|
|
|
@@ -382,159 +300,21 @@ const fetchTotalContributions = async (
|
|
|
382
300
|
);
|
|
383
301
|
}
|
|
384
302
|
|
|
385
|
-
const user = contribRes.data.data
|
|
303
|
+
const { user } = contribRes.data.data;
|
|
386
304
|
if (!user) {
|
|
387
305
|
return 0;
|
|
388
306
|
}
|
|
389
307
|
|
|
390
308
|
let total = 0;
|
|
391
|
-
for (const
|
|
392
|
-
|
|
393
|
-
if (yearBlock?.contributionCalendar.totalContributions) {
|
|
394
|
-
total += yearBlock.contributionCalendar.totalContributions;
|
|
395
|
-
}
|
|
309
|
+
for (const block of Object.values(user)) {
|
|
310
|
+
total += pick(block);
|
|
396
311
|
}
|
|
397
312
|
return total;
|
|
398
313
|
};
|
|
399
314
|
|
|
400
|
-
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Ranges per request.
|
|
404
|
-
* Each costs roughly `4 * MAX_REPOSITORIES_LIMIT` nodes,
|
|
405
|
-
* so an unchunked round of a heavily split account would breach GitHub's 500k node ceiling
|
|
406
|
-
* and lose the ranges that had already resolved along with it.
|
|
407
|
-
*/
|
|
408
|
-
const MAX_RANGES_PER_REQUEST = 100;
|
|
409
|
-
|
|
410
|
-
const REPOS_CONTRIBUTED_TO_ERROR =
|
|
411
|
-
'Something went wrong while trying to retrieve the repository contributions data using the GraphQL API.';
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Count the repositories a user contributed to across every contribution year.
|
|
415
|
-
*
|
|
416
|
-
* `repositoriesContributedTo` spans at most one year,
|
|
417
|
-
* so every year is fetched as an aliased `contributionsCollection(from, to)` in one request and the repos de-duplicated.
|
|
418
|
-
* A range returning `MAX_REPOSITORIES_LIMIT` results may have more,
|
|
419
|
-
* so it is halved and requeried in the next round.
|
|
420
|
-
*
|
|
421
|
-
* Whether private contributions are included depends on the used PAT.
|
|
422
|
-
*
|
|
423
|
-
* @param canonicalUsername Username as GitHub reports it; `nameWithOwner` uses its
|
|
424
|
-
* casing, which the raw query-string username need not match.
|
|
425
|
-
* @param years Contribution years to walk.
|
|
426
|
-
* @param includeOwnRepos Whether to count the user's own repositories.
|
|
427
|
-
* @param config Deployment config supplying the PAT pool.
|
|
428
|
-
* @returns Count of repositories.
|
|
429
|
-
*/
|
|
430
|
-
const fetchAllTimeReposContributedTo = async (
|
|
431
|
-
canonicalUsername: string,
|
|
432
|
-
years: Array<number>,
|
|
433
|
-
includeOwnRepos: boolean,
|
|
434
|
-
config: CardConfig,
|
|
435
|
-
): Promise<number> => {
|
|
436
|
-
const repos = new Set<string>();
|
|
437
|
-
let pending = years.map(getGitHubYearRange);
|
|
438
|
-
|
|
439
|
-
while (pending.length > 0) {
|
|
440
|
-
const nextPending: Array<GitHubDateRange> = [];
|
|
441
|
-
|
|
442
|
-
for (const chunk of chunkArray(pending, MAX_RANGES_PER_REQUEST)) {
|
|
443
|
-
const fetcher = createGraphQLFetcher(
|
|
444
|
-
buildReposContributedToDocument(chunk, includeOwnRepos),
|
|
445
|
-
'bearer',
|
|
446
|
-
);
|
|
447
|
-
const res = await retryer(
|
|
448
|
-
fetcher,
|
|
449
|
-
{ login: canonicalUsername, maxRepositories: MAX_REPOSITORIES_LIMIT },
|
|
450
|
-
config,
|
|
451
|
-
);
|
|
452
|
-
if (res.data.errors) {
|
|
453
|
-
throw graphqlError(res.data.errors, res.statusText, REPOS_CONTRIBUTED_TO_ERROR);
|
|
454
|
-
}
|
|
455
|
-
const user = res.data.data.user;
|
|
456
|
-
if (!user) {
|
|
457
|
-
throw new CardError(REPOS_CONTRIBUTED_TO_ERROR, { code: 'upstream' });
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
for (const [index, range] of chunk.entries()) {
|
|
461
|
-
const rangeResponse = user[`range_${index}`];
|
|
462
|
-
if (!rangeResponse) {
|
|
463
|
-
throw new CardError(REPOS_CONTRIBUTED_TO_ERROR, {
|
|
464
|
-
code: 'upstream',
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
const lists = [
|
|
469
|
-
rangeResponse.commitContributionsByRepository,
|
|
470
|
-
rangeResponse.issueContributionsByRepository,
|
|
471
|
-
rangeResponse.pullRequestContributionsByRepository,
|
|
472
|
-
(rangeResponse.repositoryContributions?.nodes ?? []).filter((node) => node !== null),
|
|
473
|
-
];
|
|
474
|
-
const isSaturated = lists.some((list) => list.length >= MAX_REPOSITORIES_LIMIT);
|
|
475
|
-
|
|
476
|
-
const rangeDays = Math.round((range.to.getTime() - range.from.getTime()) / MS_PER_DAY);
|
|
477
|
-
// a range of 1 day or less can't be split any further
|
|
478
|
-
if (isSaturated && rangeDays >= 2) {
|
|
479
|
-
// every `from` sits on UTC midnight, so the split lands on a day boundary too
|
|
480
|
-
const mid = new Date(range.from.getTime() + Math.floor(rangeDays / 2) * MS_PER_DAY);
|
|
481
|
-
// GitHub only reads the date portion,
|
|
482
|
-
// so the first half ends 1 second before `mid` to keep the halves from sharing a day
|
|
483
|
-
nextPending.push(
|
|
484
|
-
{ from: range.from, to: new Date(mid.getTime() - 1000) },
|
|
485
|
-
{ from: mid, to: range.to },
|
|
486
|
-
);
|
|
487
|
-
continue;
|
|
488
|
-
}
|
|
489
|
-
if (isSaturated) {
|
|
490
|
-
logger.log(
|
|
491
|
-
`Range ${range.from.toISOString()} - ${range.to.toISOString()} is saturated but cannot be split further.`,
|
|
492
|
-
);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
for (const { repository } of lists.flat()) {
|
|
496
|
-
const name = repository.nameWithOwner;
|
|
497
|
-
if (includeOwnRepos || !name.startsWith(`${canonicalUsername}/`)) {
|
|
498
|
-
repos.add(name);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// each saturated range pushes both of its halves
|
|
505
|
-
const saturatedCount = nextPending.length / 2;
|
|
506
|
-
if (saturatedCount > 0) {
|
|
507
|
-
logger.log(`found ${saturatedCount} saturated ranges, splitting and retrying...`);
|
|
508
|
-
}
|
|
509
|
-
pending = nextPending;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
return repos.size;
|
|
513
|
-
};
|
|
514
|
-
|
|
515
315
|
/**
|
|
516
316
|
* Fetch stats for a given username.
|
|
517
317
|
*
|
|
518
|
-
* @param props Fetcher props.
|
|
519
|
-
* @param props.username GitHub username.
|
|
520
|
-
* @param props.include_all_commits Include all commits.
|
|
521
|
-
* @param props.exclude_repo Repositories to exclude.
|
|
522
|
-
* @param props.include_merged_pull_requests Include merged pull requests.
|
|
523
|
-
* @param props.include_discussions Include discussions.
|
|
524
|
-
* @param props.include_discussions_answers Include discussions answers.
|
|
525
|
-
* @param props.commits_year Year to count total commits.
|
|
526
|
-
* @param props.repo Repositories to scope the REST search to.
|
|
527
|
-
* @param props.owner Owners to scope the REST search to.
|
|
528
|
-
* @param props.include_prs_authored Include count of PRs authored.
|
|
529
|
-
* @param props.include_prs_commented Include count of PRs commented.
|
|
530
|
-
* @param props.include_prs_reviewed Include count of PRs reviewed.
|
|
531
|
-
* @param props.include_issues_authored Include count of issues authored.
|
|
532
|
-
* @param props.include_issues_commented Include count of issues commented.
|
|
533
|
-
* @param props.ownerAffiliations Owner affiliations. Default: OWNER.
|
|
534
|
-
* @param props.include_contributions Include all-time contributions.
|
|
535
|
-
* @param props.include_all_time_contribs Include all-time count of repos contributed to.
|
|
536
|
-
* @param props.contribs_include_own_repos Include user-owned repos in contributed-to counts.
|
|
537
|
-
* @param config Deployment config supplying the PAT pool.
|
|
538
318
|
* @returns Stats data.
|
|
539
319
|
*/
|
|
540
320
|
const fetchStats = async (
|
|
@@ -545,7 +325,8 @@ const fetchStats = async (
|
|
|
545
325
|
include_merged_pull_requests = false,
|
|
546
326
|
include_discussions = false,
|
|
547
327
|
include_discussions_answers = false,
|
|
548
|
-
|
|
328
|
+
from,
|
|
329
|
+
to,
|
|
549
330
|
repo = [],
|
|
550
331
|
owner = [],
|
|
551
332
|
include_prs_authored = false,
|
|
@@ -564,7 +345,10 @@ const fetchStats = async (
|
|
|
564
345
|
include_merged_pull_requests?: boolean;
|
|
565
346
|
include_discussions?: boolean;
|
|
566
347
|
include_discussions_answers?: boolean;
|
|
567
|
-
|
|
348
|
+
/** Start of the range the commit count covers; GitHub's own last year when neither end is given. */
|
|
349
|
+
from?: Date | undefined;
|
|
350
|
+
/** End of that range. */
|
|
351
|
+
to?: Date | undefined;
|
|
568
352
|
repo?: Array<string>;
|
|
569
353
|
owner?: Array<string>;
|
|
570
354
|
include_prs_authored?: boolean;
|
|
@@ -590,6 +374,7 @@ const fetchStats = async (
|
|
|
590
374
|
mergedPRsPercentage: 0,
|
|
591
375
|
totalReviews: 0,
|
|
592
376
|
totalCommits: 0,
|
|
377
|
+
commitsRange: undefined,
|
|
593
378
|
totalIssues: 0,
|
|
594
379
|
totalStars: 0,
|
|
595
380
|
totalDiscussionsStarted: 0,
|
|
@@ -606,45 +391,34 @@ const fetchStats = async (
|
|
|
606
391
|
};
|
|
607
392
|
const affiliations = parseOwnerAffiliations(ownerAffiliations);
|
|
608
393
|
|
|
394
|
+
// neither end given leaves GitHub's trailing year in place, which `userInfo` already carries
|
|
395
|
+
const commitsRange = from === undefined && to === undefined ? undefined : toRange(from, to);
|
|
396
|
+
const commitsYearRanges = commitsRange ? toYearRanges(commitsRange) : [];
|
|
397
|
+
// one calendar year is a range `userInfo` can ask for itself; anything else is summed separately
|
|
398
|
+
const inlineRange = commitsYearRanges.length === 1 ? commitsYearRanges[0] : undefined;
|
|
399
|
+
|
|
609
400
|
const res = await statsFetcher(
|
|
610
401
|
{
|
|
611
402
|
username,
|
|
612
403
|
includeMergedPullRequests: include_merged_pull_requests,
|
|
613
404
|
includeDiscussions: include_discussions,
|
|
614
405
|
includeDiscussionsAnswers: include_discussions_answers,
|
|
615
|
-
|
|
616
|
-
commits_year === undefined
|
|
617
|
-
? undefined
|
|
618
|
-
: toGitHubDateTime(getGitHubYearRange(commits_year).from),
|
|
406
|
+
commitsRange: inlineRange,
|
|
619
407
|
ownerAffiliations: affiliations,
|
|
620
408
|
includeUserRepositories: contribs_include_own_repos,
|
|
621
409
|
},
|
|
622
410
|
config,
|
|
623
411
|
);
|
|
624
412
|
|
|
625
|
-
// Catch GraphQL errors.
|
|
626
413
|
if (res.data.errors) {
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
throw new CardError(firstError.message || 'Could not fetch user.', {
|
|
631
|
-
code: 'not_found',
|
|
632
|
-
secondaryMessage: USER_NOT_FOUND,
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
if (firstError?.message) {
|
|
636
|
-
throw new CardError(wrapTextMultiline(firstError.message, 525, 12)[0] ?? '', {
|
|
637
|
-
code: 'upstream',
|
|
638
|
-
secondaryMessage: res.statusText,
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
throw new CardError(
|
|
414
|
+
throw graphqlError(
|
|
415
|
+
res.data.errors,
|
|
416
|
+
res.statusText,
|
|
642
417
|
'Something went wrong while trying to retrieve the stats data using the GraphQL API.',
|
|
643
|
-
{ code: 'upstream' },
|
|
644
418
|
);
|
|
645
419
|
}
|
|
646
420
|
|
|
647
|
-
const user = res.data.data
|
|
421
|
+
const { user } = res.data.data;
|
|
648
422
|
if (!user) {
|
|
649
423
|
throw new CardError('Could not fetch user.', {
|
|
650
424
|
code: 'not_found',
|
|
@@ -667,7 +441,17 @@ const fetchStats = async (
|
|
|
667
441
|
config,
|
|
668
442
|
);
|
|
669
443
|
} else {
|
|
670
|
-
stats.
|
|
444
|
+
stats.commitsRange = commitsRange;
|
|
445
|
+
// an inverted range sums no years, which is no commits rather than GitHub's default year
|
|
446
|
+
stats.totalCommits =
|
|
447
|
+
commitsRange && !inlineRange
|
|
448
|
+
? await sumOverRanges(
|
|
449
|
+
username,
|
|
450
|
+
commitsYearRanges,
|
|
451
|
+
(block) => block.totalCommitContributions,
|
|
452
|
+
config,
|
|
453
|
+
)
|
|
454
|
+
: user.commits.totalCommitContributions;
|
|
671
455
|
}
|
|
672
456
|
const repoUserStats = await fetchRepoUserStats(
|
|
673
457
|
{
|
|
@@ -700,21 +484,28 @@ const fetchStats = async (
|
|
|
700
484
|
}
|
|
701
485
|
stats.contributedTo = user.repositoriesContributedTo.totalCount;
|
|
702
486
|
|
|
487
|
+
const contributionRanges = toContributionRanges(
|
|
488
|
+
user.contributionsCollection.contributionYears,
|
|
489
|
+
getWidestRange(),
|
|
490
|
+
);
|
|
491
|
+
|
|
703
492
|
if (include_contributions) {
|
|
704
|
-
stats.totalContributions = await
|
|
493
|
+
stats.totalContributions = await sumOverRanges(
|
|
705
494
|
username,
|
|
706
|
-
|
|
495
|
+
contributionRanges,
|
|
496
|
+
(block) => block.contributionCalendar.totalContributions,
|
|
707
497
|
config,
|
|
708
498
|
);
|
|
709
499
|
}
|
|
710
500
|
|
|
711
501
|
if (include_all_time_contribs) {
|
|
712
|
-
|
|
502
|
+
const reposContributedTo = await fetchReposContributedTo(
|
|
713
503
|
user.login,
|
|
714
|
-
|
|
504
|
+
contributionRanges,
|
|
715
505
|
contribs_include_own_repos,
|
|
716
506
|
config,
|
|
717
507
|
);
|
|
508
|
+
stats.allTimeContributedTo = reposContributedTo.size;
|
|
718
509
|
}
|
|
719
510
|
|
|
720
511
|
// Retrieve stars while filtering out repositories to be hidden.
|