wuffle
Version:
A multi-repository task board for GitHub issues
195 lines (150 loc) • 3.49 kB
JavaScript
import { repoAndOwner, Cache } from '../../util/index.js';
import { filterPull } from '../../filters.js';
/**
* This component updates synchronizes GitHub statuses with pull requests.
*
* @constructor
*
* @param {import('../webhook-events/WebhookEvents.js').default} webhookEvents
* @param {import('../../events.js').default} events
* @param {import('../github-client/GithubClient.js').default} githubClient
* @param {import('../../store.js').default} store
*/
export default function GithubStates(webhookEvents, events, githubClient, store) {
// issues /////////////////////
// one day
const CACHE_TTL = process.env.NODE_ENV === 'development'
? 1000 * 15
: 1000 * 60 * 60 * 24;
const cache = new Cache(CACHE_TTL);
async function fetchStatuses(pullRequest) {
const {
head,
repository
} = pullRequest;
const {
sha
} = head;
const statuses = await cache.get(`${repository.id}/${sha}`, async () => {
const {
repo,
owner
} = repoAndOwner(pullRequest);
const github = await githubClient.getOrgScoped(owner);
const {
data: response
} = await github.rest.repos.getCombinedStatusForRef({
owner,
repo,
ref: sha
});
return response.statuses.map(filterStatus).map(status => {
return {
...status,
sha
};
});
});
return statuses;
}
events.on('backgroundSync.sync', async (event) => {
const {
issue
} = event;
if (!issue.pull_request) {
return;
}
const {
id
} = issue;
const statuses = await fetchStatuses(issue);
await store.queueUpdate({
id,
statuses
});
});
webhookEvents.on([
'pull_request.opened',
'pull_request.synchronize'
], async ({ payload }) => {
const {
pull_request: _pull_request,
repository
} = payload;
const pull_request = filterPull(_pull_request, repository);
const {
id
} = pull_request;
const statuses = await fetchStatuses(pull_request);
await store.updateIssue({
id,
statuses
});
});
webhookEvents.on([
'status'
], async ({ payload }) => {
const status = filterStatus(payload);
const {
repository: status_repository
} = payload;
// invalidate cached statuses
await cache.remove(`${status_repository.id}/${status.sha}`);
await store.updateIssues(issue => {
const {
head,
repository,
statuses
} = issue;
if (!head) {
return;
}
if (repository.id === status_repository.id && status.sha === head.sha) {
return {
statuses: addOrUpdate(statuses || [], status)
};
}
});
});
// periodically clear cache
setInterval(() => {
cache.evict();
}, 1000 * 60);
}
function filterStatus(status) {
const {
id,
node_id,
context,
description,
sha,
state,
target_url,
created_at,
updated_at
} = status;
return {
id,
node_id,
context,
description,
sha,
state,
target_url,
created_at,
updated_at
};
}
function addOrUpdate(statuses, status) {
const index = statuses.findIndex(s => s.context === status.context);
return index !== -1
? [
...statuses.slice(0, index),
status,
...statuses.slice(index + 1)
]
: [
...statuses,
status
];
}