Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ jobs:
uses: JamesIves/[email protected]
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENCOLLECTIVE_API_KEY: ${{ secrets.OPENCOLLECTIVE_API_KEY }}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which account holds this key?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer used, could be removed. PR here #4390

BRANCH: gh-pages
FOLDER: dist
CLEAN: true
Expand Down
88 changes: 62 additions & 26 deletions src/utilities/fetch-supporters.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,24 @@ const asyncWriteFile = promisify(fs.writeFile);

const REQUIRED_KEYS = ['totalDonations', 'slug', 'name'];
const filename = '_supporters.json';
const absoluteFilename = path.resolve(__dirname, '..', 'components', 'Support', filename);
const absoluteFilename = path.resolve(
__dirname,
'..',
'components',
'Support',
filename
);

const graphqlEndpoint = 'https://api.opencollective.com/graphql/v2';
let graphqlEndpoint =
'https://api.opencollective.com/graphql/v2';

// https://github.com/opencollective/opencollective-api/blob/master/server/graphql/v2/query/TransactionsQuery.ts#L81
const graphqlPageSize = 1000;

if (process.env && process.env.CI && process.env.OPENCOLLECTIVE_API_KEY) {
// use api key when deploying to production
graphqlEndpoint = graphqlEndpoint + `/${process.env.OPENCOLLECTIVE_API_KEY}`;
}

const membersGraphqlQuery = `query account($limit: Int, $offset: Int) {
account(slug: "webpack") {
Expand All @@ -32,10 +47,12 @@ const membersGraphqlQuery = `query account($limit: Int, $offset: Int) {
}
}`;

const transactionsGraphqlQuery = `query account($limit: Int, $offset: Int) {
account(slug: "webpack") {
transactions(limit: $limit, offset: $offset, includeIncognitoTransactions: false) {
nodes {
// only query transactions in last year
const transactionsGraphqlQuery = `query transactions($dateFrom: ISODateTime, $limit: Int, $offset: Int) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we query only data in last year.

transactions(account: {
slug: "webpack"
}, dateFrom: $dateFrom, limit: $limit, offset: $offset, includeIncognitoTransactions: false) {
nodes {
amountInHostCurrency {
value
}
Expand All @@ -47,28 +64,34 @@ const transactionsGraphqlQuery = `query account($limit: Int, $offset: Int) {
}
createdAt
}
}
}
}`;

const graphqlPageSize = 5000;

const nodeToSupporter = node => ({
const nodeToSupporter = (node) => ({
name: node.account.name,
slug: node.account.slug,
website: node.account.website,
avatar: node.account.imageUrl,
firstDonation: node.createdAt,
totalDonations: node.totalDonations.value * 100,
monthlyDonations: 0
monthlyDonations: 0,
});

const getAllNodes = async (graphqlQuery, getNodes) => {
const requestOptions = {
method: 'POST',
uri: graphqlEndpoint,
body: { query: graphqlQuery, variables: { limit: graphqlPageSize, offset: 0 } },
json: true
body: {
query: graphqlQuery,
variables: {
limit: graphqlPageSize,
offset: 0,
dateFrom: new Date(
new Date().setFullYear(new Date().getFullYear() - 1)
).toISOString(), // data from last year
},
},
json: true,
};

let allNodes = [];
Expand All @@ -82,15 +105,22 @@ const getAllNodes = async (graphqlQuery, getNodes) => {
requestOptions.body.variables.offset += graphqlPageSize;
if (nodes.length < graphqlPageSize) {
return allNodes;
} else {
// sleep for a while
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
};

const oneYearAgo = Date.now() - 365 * 24 * 60 * 60 * 1000;

(async () => {
const members = await getAllNodes(membersGraphqlQuery, data => data.account.members.nodes);
let supporters = members.map(nodeToSupporter).sort((a, b) => b.totalDonations - a.totalDonations);
const members = await getAllNodes(
membersGraphqlQuery,
(data) => data.account.members.nodes
);
let supporters = members
.map(nodeToSupporter)
.sort((a, b) => b.totalDonations - a.totalDonations);

// Deduplicating supporters with multiple orders
supporters = uniqBy(supporters, 'slug');
Expand All @@ -99,37 +129,43 @@ const oneYearAgo = Date.now() - 365 * 24 * 60 * 60 * 1000;
for (const supporter of supporters) {
for (const key of REQUIRED_KEYS) {
if (!supporter || typeof supporter !== 'object') {
throw new Error(`Supporters: ${JSON.stringify(supporter)} is not an object.`);
throw new Error(
`Supporters: ${JSON.stringify(supporter)} is not an object.`
);
}
if (!(key in supporter)) {
throw new Error(`Supporters: ${JSON.stringify(supporter)} doesn't include ${key}.`);
throw new Error(
`Supporters: ${JSON.stringify(supporter)} doesn't include ${key}.`
);
}
}
supportersBySlug.set(supporter.slug, supporter);
}

// Calculate monthly amount from transactions
const transactions = await getAllNodes(transactionsGraphqlQuery, data => data.account.transactions.nodes);
const transactions = await getAllNodes(
transactionsGraphqlQuery,
(data) => data.transactions.nodes
);
for (const transaction of transactions) {
if (!transaction.amountInHostCurrency) continue;
const amount = transaction.amountInHostCurrency.value;
if (!amount || amount <= 0) continue;
const date = +new Date(transaction.createdAt);
if (date < oneYearAgo) continue;
const supporter = supportersBySlug.get(transaction.fromAccount.slug);
if (!supporter) continue;
supporter.monthlyDonations += amount * 100 / 12;
supporter.monthlyDonations += (amount * 100) / 12;
}

for (const supporter of supporters) {
supporter.monthlyDonations = Math.round(supporter.monthlyDonations);
}

// Write the file
return asyncWriteFile(absoluteFilename, JSON.stringify(supporters, null, 2)).then(() =>
console.log(`Fetched 1 file: ${filename}`)
);
})().catch(error => {
return asyncWriteFile(
absoluteFilename,
JSON.stringify(supporters, null, 2)
).then(() => console.log(`Fetched 1 file: ${filename}`));
})().catch((error) => {
console.error('utilities/fetch-supporters:', error);
process.exitCode = 1;
});