Skip to content

Commit 52c94bd

Browse files
znarfEugeneHlushko
authored andcommitted
enhancement: fetch supporters with Open Collective GraphQL API (#3054)
1 parent 8917a9b commit 52c94bd

File tree

2 files changed

+80
-12
lines changed

2 files changed

+80
-12
lines changed

src/components/Support/Support.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export default class Support extends React.Component {
144144

145145
{
146146
supporters.map((supporter, index) => (
147-
<a key={ supporter.id || supporter.slug || index }
147+
<a key={ supporter.slug || index }
148148
className="support__item"
149149
title={ `$${formatMoney(supporter.totalDonations / 100)} by ${supporter.name || supporter.slug}` }
150150
target="_blank"

src/utilities/fetch-supporters.js

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,100 @@
11
#!/usr/bin/env node
22
const fs = require('fs');
3+
const path = require('path');
34
const { promisify } = require('util');
45
const request = require('request-promise');
6+
const { uniqBy } = require('lodash');
57

68
const asyncWriteFile = promisify(fs.writeFile);
79

8-
const REQUIRED_KEYS = [ 'totalDonations', 'id' ];
10+
const REQUIRED_KEYS = ['totalDonations', 'slug', 'name'];
911
const filename = '_supporters.json';
10-
const url = 'https://opencollective.com/api/groups/webpack/backers';
12+
const absoluteFilename = path.resolve(__dirname, '..', 'components', 'Support', filename);
1113

12-
request(url)
13-
.then(body => {
14-
// Basic validation
15-
const content = JSON.parse(body);
14+
const graphqlEndpoint = 'https://api.opencollective.com/graphql/v2';
1615

17-
if (!Array.isArray(content)) {
16+
const graphqlQuery = `query account($limit: Int, $offset: Int) {
17+
account(slug: "webpack") {
18+
orders(limit: $limit, offset: $offset) {
19+
limit
20+
offset
21+
totalCount
22+
nodes {
23+
fromAccount {
24+
name
25+
slug
26+
website
27+
imageUrl
28+
}
29+
totalDonations {
30+
value
31+
}
32+
createdAt
33+
}
34+
}
35+
}
36+
}`;
37+
38+
const graphqlPageSize = 1000;
39+
40+
const nodeToSupporter = node => ({
41+
name: node.fromAccount.name,
42+
slug: node.fromAccount.slug,
43+
website: node.fromAccount.website,
44+
avatar: node.fromAccount.imageUrl,
45+
firstDonation: node.createdAt,
46+
totalDonations: node.totalDonations.value * 100
47+
});
48+
49+
const getAllOrders = async () => {
50+
const requestOptions = {
51+
method: 'POST',
52+
uri: graphqlEndpoint,
53+
body: { query: graphqlQuery, variables: { limit: graphqlPageSize, offset: 0 } },
54+
json: true
55+
};
56+
57+
let allOrders = [];
58+
59+
// Handling pagination if necessary (2 pages for ~1400 results in May 2019)
60+
// eslint-disable-next-line
61+
while (true) {
62+
const result = await request(requestOptions);
63+
const orders = result.data.account.orders.nodes;
64+
allOrders = [...allOrders, ...orders];
65+
requestOptions.body.variables.offset += graphqlPageSize;
66+
if (orders.length < graphqlPageSize) {
67+
return allOrders;
68+
}
69+
}
70+
};
71+
72+
getAllOrders()
73+
.then(orders => {
74+
let supporters = orders.map(nodeToSupporter).sort((a, b) => b.totalDonations - a.totalDonations);
75+
76+
// Deduplicating supporters with multiple orders
77+
supporters = uniqBy(supporters, 'slug');
78+
79+
if (!Array.isArray(supporters)) {
1880
throw new Error('Supporters data is not an array.');
1981
}
2082

21-
for (const item of content) {
83+
for (const item of supporters) {
2284
for (const key of REQUIRED_KEYS) {
23-
if (!item || typeof item !== 'object') throw new Error(`Supporters: ${JSON.stringify(item)} is not an object.`);
24-
if (!(key in item)) throw new Error(`Supporters: ${JSON.stringify(item)} doesn't include ${key}.`);
85+
if (!item || typeof item !== 'object') {
86+
throw new Error(`Supporters: ${JSON.stringify(item)} is not an object.`);
87+
}
88+
if (!(key in item)) {
89+
throw new Error(`Supporters: ${JSON.stringify(item)} doesn't include ${key}.`);
90+
}
2591
}
2692
}
2793

2894
// Write the file
29-
return asyncWriteFile(`./src/components/Support/${filename}`, body).then(() => console.log('Fetched 1 file: _supporters.json'));
95+
return asyncWriteFile(absoluteFilename, JSON.stringify(supporters, null, 2)).then(() =>
96+
console.log(`Fetched 1 file: ${filename}`)
97+
);
3098
})
3199
.catch(error => {
32100
console.error('utilities/fetch-supporters:', error);

0 commit comments

Comments
 (0)