forked from angular/angular
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathget-data.ts
241 lines (224 loc) · 8.09 KB
/
get-data.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* This script gets contribution stats for all members of the angular org,
* since a provided date.
* The script expects the following flag(s):
*
* required:
* --since [date] The data after which contributions are queried for.
* Uses githubs search format for dates, e.g. "2020-01-21".
* See
* https://help.github.com/en/github/searching-for-information-on-github/understanding-the-search-syntax#query-for-dates
*
* optional:
* --use-created [boolean] If the created timestamp should be used for
* time comparisons, defaults otherwise to the updated timestamp.
*/
import {graphql as unauthenticatedGraphql} from '@octokit/graphql';
import {alias, params, query as graphqlQuery, types} from 'typed-graphqlify';
import yargs from 'yargs';
// The organization to be considered for the queries.
const ORG = 'angular';
// The repositories to be considered for the queries.
const REPOS = ['angular', 'components', 'angular-cli'];
/**
* Handle flags for the script.
*/
const args = yargs.option('use-created', {type: 'boolean'})
.option('since', {type: 'string', demandOption: true})
.strictOptions()
.argv;
/**
* Authenticated instance of Github GraphQl API service, relies on a
* personal access token being available in the TOKEN environment variable.
*/
const graphql = unauthenticatedGraphql.defaults({
headers: {
// TODO(josephperrott): Remove reference to TOKEN environment variable as part of larger
// effort to migrate to expecting tokens via GITHUB_ACCESS_TOKEN environment variables.
authorization: `token ${process.env.TOKEN || process.env.GITHUB_ACCESS_TOKEN}`,
}
});
/**
* Retrieves all current members of an organization.
*/
async function getAllOrgMembers() {
// The GraphQL query object to get a page of members of an organization.
const MEMBERS_QUERY = params(
{
$first: 'Int', // How many entries to get with each request
$after: 'String', // The cursor to start the page at
$owner: 'String!', // The organization to query for
},
{
organization: params({login: '$owner'}, {
membersWithRole: params(
{
first: '$first',
after: '$after',
},
{
nodes: [{login: types.string}],
pageInfo: {
hasNextPage: types.boolean,
endCursor: types.string,
},
}),
})
});
const query = graphqlQuery('members', MEMBERS_QUERY);
/**
* Gets the query and queryParams for a specific page of entries.
*/
const queryBuilder = (count: number, cursor?: string) => {
return {
query,
params: {
after: cursor || null,
first: count,
owner: ORG,
},
};
};
// The current cursor
let cursor = undefined;
// If an additional page of members is expected
let hasNextPage = true;
// Array of Github usernames of the organization
const members: string[] = [];
while (hasNextPage) {
const {query, params} = queryBuilder(100, cursor);
const results = await graphql(query.toString(), params) as typeof MEMBERS_QUERY;
results.organization.membersWithRole.nodes.forEach(
(node: {login: string}) => members.push(node.login));
hasNextPage = results.organization.membersWithRole.pageInfo.hasNextPage;
cursor = results.organization.membersWithRole.pageInfo.endCursor;
}
return members.sort();
}
/**
* Build metadata for making requests for a specific user and date.
*
* Builds GraphQL query string, Query Params and Labels for making queries to GraphQl.
*/
function buildQueryAndParams(username: string, date: string) {
// Whether the updated or created timestamp should be used.
const updatedOrCreated = args['use-created'] ? 'created' : 'updated';
let dataQueries: {[key: string]: {query: string, label: string}} = {};
// Add queries and params for all values queried for each repo.
for (let repo of REPOS) {
dataQueries = {
...dataQueries,
[`${repo.replace(/[\/\-]/g, '_')}_issue_author`]: {
query: `repo:${ORG}/${repo} is:issue author:${username} ${updatedOrCreated}:>${date}`,
label: `${ORG}/${repo} Issue Authored`,
},
[`${repo.replace(/[\/\-]/g, '_')}_issues_involved`]: {
query: `repo:${ORG}/${repo} is:issue -author:${username} involves:${username} ${
updatedOrCreated}:>${date}`,
label: `${ORG}/${repo} Issue Involved`,
},
[`${repo.replace(/[\/\-]/g, '_')}_pr_author`]: {
query: `repo:${ORG}/${repo} is:pr author:${username} ${updatedOrCreated}:>${date}`,
label: `${ORG}/${repo} PR Author`,
},
[`${repo.replace(/[\/\-]/g, '_')}_pr_involved`]: {
query: `repo:${ORG}/${repo} is:pr involves:${username} ${updatedOrCreated}:>${date}`,
label: `${ORG}/${repo} PR Involved`,
},
[`${repo.replace(/[\/\-]/g, '_')}_pr_reviewed`]: {
query: `repo:${ORG}/${repo} is:pr -author:${username} reviewed-by:${username} ${
updatedOrCreated}:>${date}`,
label: `${ORG}/${repo} PR Reviewed`,
},
[`${repo.replace(/[\/\-]/g, '_')}_pr_commented`]: {
query: `repo:${ORG}/${repo} is:pr -author:${username} commenter:${username} ${
updatedOrCreated}:>${date}`,
label: `${ORG}/${repo} PR Commented`,
},
};
}
// Add queries and params for all values queried for the org.
dataQueries = {
...dataQueries,
[`${ORG}_org_issue_author`]: {
query: `org:${ORG} is:issue author:${username} ${updatedOrCreated}:>${date}`,
label: `${ORG} org Issue Authored`,
},
[`${ORG}_org_issues_involved`]: {
query: `org:${ORG} is:issue -author:${username} involves:${username} ${updatedOrCreated}:>${
date}`,
label: `${ORG} org Issue Involved`,
},
[`${ORG}_org_pr_author`]: {
query: `org:${ORG} is:pr author:${username} ${updatedOrCreated}:>${date}`,
label: `${ORG} org PR Author`,
},
[`${ORG}_org_pr_involved`]: {
query: `org:${ORG} is:pr involves:${username} ${updatedOrCreated}:>${date}`,
label: `${ORG} org PR Involved`,
},
[`${ORG}_org_pr_reviewed`]: {
query: `org:${ORG} is:pr -author:${username} reviewed-by:${username} ${updatedOrCreated}:>${
date}`,
label: `${ORG} org PR Reviewed`,
},
[`${ORG}_org_pr_commented`]: {
query:
`org:${ORG} is:pr -author:${username} commenter:${username} ${updatedOrCreated}:>${date}`,
label: `${ORG} org PR Commented`,
},
};
/**
* Gets the labels for each requested value to be used as headers.
*/
function getLabels(pairs: typeof dataQueries) {
return Object.values(pairs).map(val => val.label);
}
/**
* Gets the graphql query object for the GraphQL query.
*/
function getQuery(pairs: typeof dataQueries) {
const output: {[key: string]: {}} = {};
Object.entries(pairs).map(([key, val]) => {
output[alias(key, 'search')] = params(
{
query: `"${val.query}"`,
type: 'ISSUE',
},
{
issueCount: types.number,
});
});
return output;
}
return {
query: graphqlQuery(getQuery(dataQueries)),
labels: getLabels(dataQueries),
};
}
/**
* Runs the script to create a CSV string with the requested data for each member
* of the organization.
*/
async function run(date: string) {
try {
const allOrgMembers = await getAllOrgMembers();
console.info(['Username', ...buildQueryAndParams('', date).labels].join(','));
for (const username of allOrgMembers) {
const results = await graphql(buildQueryAndParams(username, date).query.toString());
const values = Object.values(results).map(result => `${result.issueCount}`);
console.info([username, ...values].join(','));
}
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
}
run(args['since']);