@atolis-hq/wake 0.3.44 → 0.3.46
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/dist/src/bootstrap/board-projection.js +35 -11
- package/dist/src/bootstrap/surface-api-applications.js +20 -12
- package/dist/src/bootstrap/version.js +1 -1
- package/dist/src/integrations/application/poll-service.js +1 -0
- package/dist/src/integrations/github/infrastructure/client-reads.js +9 -5
- package/dist/src/integrations/github/infrastructure/client.js +5 -2
- package/dist/src/integrations/github/infrastructure/comment-source.js +86 -0
- package/dist/src/integrations/github/infrastructure/poll-watermark.js +22 -0
- package/dist/src/integrations/github/infrastructure/source-contracts.js +1 -0
- package/dist/src/integrations/github/infrastructure/source.js +64 -89
- package/dist/src/integrations/github/provider.js +4 -1
- package/dist/src/surfaces/web-assets/assets/index-BTgCXhzq.js +1090 -0
- package/dist/src/surfaces/web-assets/index.html +1 -1
- package/package.json +1 -1
- package/dist/src/surfaces/web-assets/assets/index-ipB6JPGU.js +0 -1090
|
@@ -49,6 +49,7 @@ function projectWork(view, event, occurredAt) {
|
|
|
49
49
|
condition: BoardCondition.Ready,
|
|
50
50
|
dwellSince: occurredAt,
|
|
51
51
|
runCount: 0,
|
|
52
|
+
activeRuns: {},
|
|
52
53
|
totalTokens: 0,
|
|
53
54
|
inputTokens: 0,
|
|
54
55
|
outputTokens: 0,
|
|
@@ -246,9 +247,11 @@ function projectRun(view, event, _occurredAt) {
|
|
|
246
247
|
}
|
|
247
248
|
function projectRunTerminal(view, event, workId, card) {
|
|
248
249
|
const finishedAt = terminalFinishedAt(event);
|
|
249
|
-
const
|
|
250
|
+
const activeRuns = activeRunsFor(view, card, workId);
|
|
251
|
+
const activeRun = activeRuns[event.stream.id];
|
|
252
|
+
const runDurationMs = activeRun === undefined || finishedAt === undefined
|
|
250
253
|
? 0
|
|
251
|
-
: Date.parse(finishedAt) - Date.parse(
|
|
254
|
+
: Date.parse(finishedAt) - Date.parse(activeRun.startedAt);
|
|
252
255
|
const terminal = terminalRunFields(event);
|
|
253
256
|
// Same legacy-checkpoint tolerance as the `children` guard above: a
|
|
254
257
|
// checkpoint persisted before `childRuns` was added round-trips without
|
|
@@ -259,7 +262,8 @@ function projectRunTerminal(view, event, workId, card) {
|
|
|
259
262
|
cards: {
|
|
260
263
|
...view.cards,
|
|
261
264
|
[workId]: {
|
|
262
|
-
...
|
|
265
|
+
...withoutLegacyActiveRun(card),
|
|
266
|
+
activeRuns: withoutActiveRun(activeRuns, event.stream.id),
|
|
263
267
|
...(terminal === undefined ? {} : { lastRunOutcome: terminal.lastRunOutcome }),
|
|
264
268
|
...(terminal === undefined || isChildRun || terminal.condition === undefined
|
|
265
269
|
? {}
|
|
@@ -279,6 +283,7 @@ function projectRunStarted(view, event) {
|
|
|
279
283
|
// that guard, so a checkpoint predating `children` must not crash here either.
|
|
280
284
|
const childAction = view.children?.[event.payload.workflowInstanceId];
|
|
281
285
|
const isChildRun = childAction !== undefined;
|
|
286
|
+
const activeRuns = activeRunsFor(view, card, workId);
|
|
282
287
|
return {
|
|
283
288
|
...view,
|
|
284
289
|
runs: { ...view.runs, [event.stream.id]: workId },
|
|
@@ -286,16 +291,21 @@ function projectRunStarted(view, event) {
|
|
|
286
291
|
cards: {
|
|
287
292
|
...view.cards,
|
|
288
293
|
[workId]: {
|
|
289
|
-
...(isChildRun
|
|
294
|
+
...(isChildRun
|
|
295
|
+
? withoutLegacyActiveRun(card)
|
|
296
|
+
: withoutLegacyActiveRun(withoutLastRunOutcome(withoutAwaitingApproval(card)))),
|
|
290
297
|
runCount: card.runCount + 1,
|
|
291
298
|
...(isChildRun ? {} : { condition: BoardCondition.Active }),
|
|
292
299
|
lastRunAt: event.payload.startedAt,
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
300
|
+
activeRuns: {
|
|
301
|
+
...activeRuns,
|
|
302
|
+
[event.stream.id]: {
|
|
303
|
+
action: childAction ?? card.stage ?? event.payload.activity,
|
|
304
|
+
startedAt: event.payload.startedAt,
|
|
305
|
+
...(event.payload.runner?.name === undefined
|
|
306
|
+
? {}
|
|
307
|
+
: { runnerName: event.payload.runner.name }),
|
|
308
|
+
},
|
|
299
309
|
},
|
|
300
310
|
},
|
|
301
311
|
},
|
|
@@ -327,7 +337,21 @@ function withoutAwaitingApproval(card) {
|
|
|
327
337
|
const { awaitingApproval: _awaitingApproval, ...withoutApproval } = card;
|
|
328
338
|
return withoutApproval;
|
|
329
339
|
}
|
|
330
|
-
function
|
|
340
|
+
function activeRunsFor(view, card, workId) {
|
|
341
|
+
if (card.activeRuns !== undefined)
|
|
342
|
+
return card.activeRuns;
|
|
343
|
+
if (card.activeRun === undefined)
|
|
344
|
+
return {};
|
|
345
|
+
const legacyRunId = Object.entries(view.runs)
|
|
346
|
+
.reverse()
|
|
347
|
+
.find(([, runWorkId]) => runWorkId === workId)?.[0];
|
|
348
|
+
return legacyRunId === undefined ? {} : { [legacyRunId]: card.activeRun };
|
|
349
|
+
}
|
|
350
|
+
function withoutActiveRun(activeRuns, runId) {
|
|
351
|
+
const { [runId]: _completed, ...remaining } = activeRuns;
|
|
352
|
+
return remaining;
|
|
353
|
+
}
|
|
354
|
+
function withoutLegacyActiveRun(card) {
|
|
331
355
|
const { activeRun: _activeRun, ...withoutRun } = card;
|
|
332
356
|
return withoutRun;
|
|
333
357
|
}
|
|
@@ -29,39 +29,47 @@ function createBoardApplications(root, now) {
|
|
|
29
29
|
async list(query) {
|
|
30
30
|
const nowMs = Date.parse(now());
|
|
31
31
|
const stored = await root.projections.read(boardProjection.name, 'global');
|
|
32
|
-
const
|
|
32
|
+
const board = stored?.value ?? boardProjection.initial('global');
|
|
33
|
+
const cards = Object.values(board.cards).sort((left, right) => cardRecency(right).localeCompare(cardRecency(left)));
|
|
33
34
|
const offset = query.cursor?.position ?? 0;
|
|
34
35
|
const page = cards.slice(offset, offset + query.limit);
|
|
35
36
|
const items = await Promise.all(page.map(async (card) => {
|
|
36
37
|
const externalRef = await primaryExternalRef(root, card.workItemId);
|
|
37
|
-
const { activeRun, ...
|
|
38
|
+
const { activeRun: _legacyActiveRun, activeRuns: _activeRuns, ...withoutActiveRuns } = card;
|
|
39
|
+
const activeRuns = activeBoardRuns(board, card);
|
|
38
40
|
return presentBoardCard({
|
|
39
|
-
...
|
|
41
|
+
...withoutActiveRuns,
|
|
40
42
|
totalDurationMs: card.totalDurationMs ?? 0,
|
|
43
|
+
activeRuns: Object.fromEntries(Object.entries(activeRuns).map(([runId, activeRun]) => [
|
|
44
|
+
runId,
|
|
45
|
+
{ ...activeRun, elapsedMs: elapsedSince(activeRun.startedAt, nowMs) },
|
|
46
|
+
])),
|
|
41
47
|
...(externalRef === undefined ? {} : { externalRef }),
|
|
42
48
|
...(card.lastRunAt === undefined
|
|
43
49
|
? {}
|
|
44
50
|
: { lastRunAgeMs: elapsedSince(card.lastRunAt, nowMs) }),
|
|
45
|
-
...(activeRun === undefined
|
|
46
|
-
? {}
|
|
47
|
-
: {
|
|
48
|
-
activeRun: {
|
|
49
|
-
...activeRun,
|
|
50
|
-
elapsedMs: elapsedSince(activeRun.startedAt, nowMs),
|
|
51
|
-
},
|
|
52
|
-
}),
|
|
53
51
|
});
|
|
54
52
|
}));
|
|
55
53
|
return {
|
|
56
54
|
items,
|
|
57
55
|
total: cards.length,
|
|
58
56
|
...(offset + query.limit < cards.length ? { nextPosition: offset + query.limit } : {}),
|
|
59
|
-
conditionCounts: boardConditionCounts(
|
|
57
|
+
conditionCounts: boardConditionCounts(board),
|
|
60
58
|
meta: await projectionMeta(root.journal, stored === null ? [] : [stored], now()),
|
|
61
59
|
};
|
|
62
60
|
},
|
|
63
61
|
};
|
|
64
62
|
}
|
|
63
|
+
function activeBoardRuns(board, card) {
|
|
64
|
+
if (card.activeRuns !== undefined)
|
|
65
|
+
return card.activeRuns;
|
|
66
|
+
if (card.activeRun === undefined)
|
|
67
|
+
return {};
|
|
68
|
+
const legacyRunId = Object.entries(board.runs)
|
|
69
|
+
.reverse()
|
|
70
|
+
.find(([, workItemId]) => workItemId === card.workItemId)?.[0];
|
|
71
|
+
return legacyRunId === undefined ? {} : { [legacyRunId]: card.activeRun };
|
|
72
|
+
}
|
|
65
73
|
function createStatusApplications(root, now) {
|
|
66
74
|
return {
|
|
67
75
|
async get() {
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { PullRequestState } from '../../../activities/index.js';
|
|
3
3
|
import { GitHubListState } from '../contracts/vocabulary.js';
|
|
4
4
|
import { fetchPaginatedWithEtag, fetchWithEtag } from './etag-cache.js';
|
|
5
|
-
export function listIssues(octokit, cache, owner, repo, maxResults) {
|
|
5
|
+
export function listIssues(octokit, cache, owner, repo, maxResults, since) {
|
|
6
6
|
return fetchPaginatedWithEtag({
|
|
7
7
|
cache,
|
|
8
|
-
key: `issues:${owner}/${repo}`,
|
|
8
|
+
key: `issues:${owner}/${repo}:since:${since ?? 'bootstrap'}`,
|
|
9
9
|
maxResults,
|
|
10
10
|
pages: (headers) => octokit.paginate.iterator(octokit.rest.issues.listForRepo, {
|
|
11
11
|
owner,
|
|
@@ -14,6 +14,7 @@ export function listIssues(octokit, cache, owner, repo, maxResults) {
|
|
|
14
14
|
sort: 'updated',
|
|
15
15
|
direction: 'desc',
|
|
16
16
|
per_page: Math.min(maxResults, 100),
|
|
17
|
+
...(since === undefined ? {} : { since }),
|
|
17
18
|
...(headers === undefined ? {} : { headers }),
|
|
18
19
|
}),
|
|
19
20
|
}).then((items) => items.map(normalizeIssue));
|
|
@@ -41,6 +42,8 @@ export async function listPullRequests(octokit, cache, owner, repo, maxResults)
|
|
|
41
42
|
owner,
|
|
42
43
|
repo,
|
|
43
44
|
state: GitHubListState.All,
|
|
45
|
+
sort: 'updated',
|
|
46
|
+
direction: 'desc',
|
|
44
47
|
per_page: Math.min(maxResults, 100),
|
|
45
48
|
...(headers === undefined ? {} : { headers }),
|
|
46
49
|
}),
|
|
@@ -208,15 +211,16 @@ function normalizePullRequestState(pullRequest) {
|
|
|
208
211
|
: PullRequestState.Open,
|
|
209
212
|
};
|
|
210
213
|
}
|
|
211
|
-
export async function listIssueComments(octokit, cache, owner, repo, issueNumber,
|
|
214
|
+
export async function listIssueComments(octokit, cache, owner, repo, issueNumber, options) {
|
|
212
215
|
const comments = await fetchPaginatedWithEtag({
|
|
213
216
|
cache,
|
|
214
|
-
key: `issue-comments:${owner}/${repo}#${issueNumber}`,
|
|
217
|
+
key: `issue-comments:${owner}/${repo}#${issueNumber}:since:${options.since ?? 'bootstrap'}`,
|
|
215
218
|
pages: (headers) => octokit.paginate.iterator(octokit.rest.issues.listComments, {
|
|
216
219
|
owner,
|
|
217
220
|
repo,
|
|
218
221
|
issue_number: issueNumber,
|
|
219
|
-
per_page: Math.min(pageSize, 100),
|
|
222
|
+
per_page: Math.min(options.pageSize, 100),
|
|
223
|
+
...(options.since === undefined ? {} : { since: options.since }),
|
|
220
224
|
...(headers === undefined ? {} : { headers }),
|
|
221
225
|
}),
|
|
222
226
|
});
|
|
@@ -34,9 +34,12 @@ export function createGitHubClient(token) {
|
|
|
34
34
|
const cache = createEtagCache();
|
|
35
35
|
return {
|
|
36
36
|
authenticatedLogin: async () => (await octokit.rest.users.getAuthenticated()).data.login,
|
|
37
|
-
listIssues: (owner, repo, maxResults) => listIssues(octokit, cache, owner, repo, maxResults),
|
|
37
|
+
listIssues: (owner, repo, maxResults, since) => listIssues(octokit, cache, owner, repo, maxResults, since),
|
|
38
38
|
listPullRequests: (owner, repo, maxResults) => listPullRequests(octokit, cache, owner, repo, maxResults),
|
|
39
|
-
listIssueComments: (owner, repo, issueNumber, pageSize) => listIssueComments(octokit, cache, owner, repo, issueNumber,
|
|
39
|
+
listIssueComments: (owner, repo, issueNumber, pageSize, since) => listIssueComments(octokit, cache, owner, repo, issueNumber, {
|
|
40
|
+
pageSize,
|
|
41
|
+
...(since === undefined ? {} : { since }),
|
|
42
|
+
}),
|
|
40
43
|
listReviewComments: (owner, repo, pullNumber, pageSize) => listReviewComments(octokit, cache, owner, repo, pullNumber, pageSize),
|
|
41
44
|
collaboratorPermission: async (owner, repo, login) => {
|
|
42
45
|
const { data } = await octokit.rest.repos.getCollaboratorPermissionLevel({
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { ReviewerAuthorizationSource, } from '../../../activities/index.js';
|
|
2
|
+
import { issueCommentObservation } from './issue-source.js';
|
|
3
|
+
import { mergeBatches, reportPartialPollFailure } from './poll-watermark.js';
|
|
4
|
+
import { githubReviewObservation } from './review-source.js';
|
|
5
|
+
export async function reviewCommentEventsFor(context, pullRequests) {
|
|
6
|
+
if (context.client.listReviewComments === undefined)
|
|
7
|
+
return { drafts: [], succeeded: true };
|
|
8
|
+
const items = await Promise.all(pullRequests.map(async (pullRequest) => {
|
|
9
|
+
try {
|
|
10
|
+
const comments = await context.client.listReviewComments(context.owner, context.repo, pullRequest.number, context.config.polling.commentPageSize);
|
|
11
|
+
return {
|
|
12
|
+
succeeded: true,
|
|
13
|
+
drafts: (await Promise.all(comments.map((comment) => issueCommentEventsForComment(context, pullRequest, comment)))).flat(),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
reportPartialPollFailure(context.repository, `pull-request review comments #${pullRequest.number}`);
|
|
18
|
+
return { drafts: [], succeeded: false };
|
|
19
|
+
}
|
|
20
|
+
}));
|
|
21
|
+
return mergeBatches(items);
|
|
22
|
+
}
|
|
23
|
+
export async function reviewEventsFor(context, pullRequests) {
|
|
24
|
+
const items = await Promise.all(pullRequests.map(async (pullRequest) => {
|
|
25
|
+
try {
|
|
26
|
+
const reviews = await context.client.listReviews(context.owner, context.repo, pullRequest.number, context.config.polling.commentPageSize);
|
|
27
|
+
return {
|
|
28
|
+
succeeded: true,
|
|
29
|
+
drafts: reviews.flatMap((review) => githubReviewObservation({
|
|
30
|
+
repository: context.repository,
|
|
31
|
+
pullRequest,
|
|
32
|
+
review,
|
|
33
|
+
authorizedReviewers: [],
|
|
34
|
+
})),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
reportPartialPollFailure(context.repository, `pull-request reviews #${pullRequest.number}`);
|
|
39
|
+
return { drafts: [], succeeded: false };
|
|
40
|
+
}
|
|
41
|
+
}));
|
|
42
|
+
return mergeBatches(items);
|
|
43
|
+
}
|
|
44
|
+
export async function issueCommentEventsFor(context, issues, since) {
|
|
45
|
+
const items = await Promise.all(issues.map(async (issue) => {
|
|
46
|
+
try {
|
|
47
|
+
const comments = await context.client.listIssueComments(context.owner, context.repo, issue.number, context.config.polling.commentPageSize, since);
|
|
48
|
+
return {
|
|
49
|
+
succeeded: true,
|
|
50
|
+
drafts: (await Promise.all(comments.map((comment) => issueCommentEventsForComment(context, issue, comment)))).flat(),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
reportPartialPollFailure(context.repository, `issue comments #${issue.number}`);
|
|
55
|
+
return { drafts: [], succeeded: false };
|
|
56
|
+
}
|
|
57
|
+
}));
|
|
58
|
+
return mergeBatches(items);
|
|
59
|
+
}
|
|
60
|
+
async function issueCommentEventsForComment(context, issue, comment) {
|
|
61
|
+
const authorization = await retryAuthorization(context, comment);
|
|
62
|
+
const event = issueCommentObservation({
|
|
63
|
+
repository: context.repository,
|
|
64
|
+
issue,
|
|
65
|
+
comment,
|
|
66
|
+
...(authorization === undefined ? {} : { authorization }),
|
|
67
|
+
...(context.adapter === undefined ? {} : { adapter: context.adapter }),
|
|
68
|
+
});
|
|
69
|
+
return event === null ? [] : [event];
|
|
70
|
+
}
|
|
71
|
+
async function retryAuthorization(context, comment) {
|
|
72
|
+
if (comment.body?.trim().toLowerCase() !== '/retry')
|
|
73
|
+
return undefined;
|
|
74
|
+
const login = comment.user?.login;
|
|
75
|
+
if (login === undefined || context.client.collaboratorPermission === undefined)
|
|
76
|
+
return { source: ReviewerAuthorizationSource.None };
|
|
77
|
+
try {
|
|
78
|
+
return {
|
|
79
|
+
source: ReviewerAuthorizationSource.ProviderPermission,
|
|
80
|
+
permission: await context.client.collaboratorPermission(context.owner, context.repo, login),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return { source: ReviewerAuthorizationSource.None };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function mergeBatches(batches) {
|
|
2
|
+
return {
|
|
3
|
+
drafts: batches.flatMap((batch) => batch.drafts),
|
|
4
|
+
succeeded: batches.every((batch) => batch.succeeded),
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export function watermarkCheckpoint(adapter, repository) {
|
|
8
|
+
return `source:github:${adapter ?? 'github'}:${repository}`;
|
|
9
|
+
}
|
|
10
|
+
export async function loadWatermark(checkpoints, adapter, owner, repo) {
|
|
11
|
+
if (checkpoints === undefined)
|
|
12
|
+
return 0;
|
|
13
|
+
return checkpoints.load(watermarkCheckpoint(adapter, `${owner}/${repo}`));
|
|
14
|
+
}
|
|
15
|
+
export function overlapSince(watermark, overlapMs) {
|
|
16
|
+
if (!Number.isFinite(watermark) || watermark <= 0)
|
|
17
|
+
return undefined;
|
|
18
|
+
return new Date(Math.max(0, watermark - overlapMs)).toISOString();
|
|
19
|
+
}
|
|
20
|
+
export function reportPartialPollFailure(repository, query) {
|
|
21
|
+
process.stderr.write(`GitHub poll partial failure for ${repository}: ${query}; preserving watermark for replay\n`);
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,33 +1,37 @@
|
|
|
1
|
-
import { ReviewerAuthorizationSource, } from '../../../activities/index.js';
|
|
2
1
|
import { GitHubEventType } from '../contracts/events.js';
|
|
3
|
-
import {
|
|
2
|
+
import { issueCommentEventsFor, reviewCommentEventsFor, reviewEventsFor, } from './comment-source.js';
|
|
3
|
+
import { issueObservation } from './issue-source.js';
|
|
4
|
+
import { loadWatermark, overlapSince, reportPartialPollFailure, watermarkCheckpoint, } from './poll-watermark.js';
|
|
4
5
|
import { createGitHubPullRequestSource } from './pr-source.js';
|
|
5
6
|
import { createGitHubRequestCoordinator, } from './request-coordinator.js';
|
|
6
|
-
import { githubReviewObservation } from './review-source.js';
|
|
7
7
|
export function createGitHubSource(config, client, adapter, requests = createGitHubRequestCoordinator({
|
|
8
8
|
maxConcurrent: config.polling.maxConcurrent,
|
|
9
|
-
})) {
|
|
10
|
-
let nextPollAt = 0;
|
|
9
|
+
}), state) {
|
|
11
10
|
// Draft eventIds are already content fingerprints (see issue-source.ts/pr-source.ts),
|
|
12
11
|
// so the journal itself is idempotent per item. This cache only avoids re-appending
|
|
13
12
|
// (and re-triggering downstream translation) an unchanged item on every poll within
|
|
14
13
|
// a single process lifetime — it's a perf optimization, not a correctness dependency,
|
|
15
14
|
// so it's safe for it to reset on restart.
|
|
16
15
|
const lastEventIds = new Map();
|
|
16
|
+
const pendingWatermarks = new Map();
|
|
17
17
|
return {
|
|
18
18
|
async poll(signal) {
|
|
19
|
-
|
|
20
|
-
return [];
|
|
21
|
-
nextPollAt = Date.now() + config.polling.lookbackMs;
|
|
22
|
-
const perRepository = await Promise.all(config.repositories.map(({ owner, repo }) => pollRepository({
|
|
19
|
+
const perRepository = await Promise.all(config.repositories.map(async ({ owner, repo }) => pollRepository({
|
|
23
20
|
client: limitGitHubSourceClient(client, requests),
|
|
24
21
|
config,
|
|
25
22
|
adapter,
|
|
26
23
|
signal,
|
|
27
24
|
owner,
|
|
28
25
|
repo,
|
|
26
|
+
watermark: await loadWatermark(state?.checkpoints, adapter, owner, repo),
|
|
27
|
+
now: state?.now ?? Date.now,
|
|
29
28
|
})));
|
|
30
|
-
|
|
29
|
+
for (const result of perRepository)
|
|
30
|
+
if (result.succeeded)
|
|
31
|
+
pendingWatermarks.set(result.repository, result.completedAt);
|
|
32
|
+
return perRepository
|
|
33
|
+
.flatMap((result) => result.drafts)
|
|
34
|
+
.filter((draft) => {
|
|
31
35
|
if (draft.eventType !== GitHubEventType.WorkObserved)
|
|
32
36
|
return true;
|
|
33
37
|
const prior = lastEventIds.get(draft.payload.externalKey);
|
|
@@ -35,17 +39,25 @@ export function createGitHubSource(config, client, adapter, requests = createGit
|
|
|
35
39
|
return prior !== draft.eventId;
|
|
36
40
|
});
|
|
37
41
|
},
|
|
42
|
+
async markPollPersisted() {
|
|
43
|
+
if (state === undefined)
|
|
44
|
+
return;
|
|
45
|
+
for (const [repository, watermark] of pendingWatermarks) {
|
|
46
|
+
await state.checkpoints.save(watermarkCheckpoint(adapter, repository), watermark);
|
|
47
|
+
pendingWatermarks.delete(repository);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
38
50
|
};
|
|
39
51
|
}
|
|
40
52
|
function limitGitHubSourceClient(client, requests) {
|
|
41
53
|
return {
|
|
42
54
|
...client,
|
|
43
|
-
listIssues: (owner, repo, maxResults) => requests.run(() => client.listIssues(owner, repo, maxResults)),
|
|
55
|
+
listIssues: (owner, repo, maxResults, since) => requests.run(() => client.listIssues(owner, repo, maxResults, since)),
|
|
44
56
|
listPullRequests: (owner, repo, maxResults) => requests.run(() => client.listPullRequests(owner, repo, maxResults)),
|
|
45
57
|
listCheckRunsForRef: (owner, repo, ref) => requests.run(() => client.listCheckRunsForRef(owner, repo, ref)),
|
|
46
58
|
getCombinedStatusForRef: (owner, repo, ref) => requests.run(() => client.getCombinedStatusForRef(owner, repo, ref)),
|
|
47
59
|
listPullRequestFiles: (owner, repo, pullNumber) => requests.run(() => client.listPullRequestFiles(owner, repo, pullNumber)),
|
|
48
|
-
listIssueComments: (owner, repo, issueNumber, pageSize) => requests.run(() => client.listIssueComments(owner, repo, issueNumber, pageSize)),
|
|
60
|
+
listIssueComments: (owner, repo, issueNumber, pageSize, since) => requests.run(() => client.listIssueComments(owner, repo, issueNumber, pageSize, since)),
|
|
49
61
|
listReviews: (owner, repo, pullNumber, pageSize) => requests.run(() => client.listReviews(owner, repo, pullNumber, pageSize)),
|
|
50
62
|
...(client.listReviewComments === undefined
|
|
51
63
|
? {}
|
|
@@ -69,21 +81,39 @@ async function pollRepository(input) {
|
|
|
69
81
|
repo,
|
|
70
82
|
repository: `${owner}/${repo}`,
|
|
71
83
|
};
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
]
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
const since = overlapSince(input.watermark, config.polling.lookbackMs);
|
|
85
|
+
const [issuesResult, pullRequestsResult] = await Promise.allSettled([
|
|
86
|
+
client.listIssues(owner, repo, config.polling.maxPerRepo, since),
|
|
87
|
+
client.listPullRequests(owner, repo, config.polling.maxPerRepo),
|
|
88
|
+
]);
|
|
89
|
+
if (!isFulfilled(issuesResult))
|
|
90
|
+
reportPartialPollFailure(context.repository, 'issues');
|
|
91
|
+
if (!isFulfilled(pullRequestsResult))
|
|
92
|
+
reportPartialPollFailure(context.repository, 'pull requests');
|
|
93
|
+
const issues = isFulfilled(issuesResult) ? issuesResult.value : [];
|
|
94
|
+
const pullRequestPayloads = isFulfilled(pullRequestsResult)
|
|
95
|
+
? pullRequestsResult.value.filter((pullRequest) => since === undefined || pullRequest.updated_at >= since)
|
|
96
|
+
: [];
|
|
97
|
+
const [pullRequests, reviews, reviewComments, issueComments] = await Promise.all([
|
|
98
|
+
createGitHubPullRequestSource({
|
|
99
|
+
client: { ...client, listPullRequests: async () => pullRequestPayloads },
|
|
100
|
+
repository: context.repository,
|
|
101
|
+
maxResults: config.polling.maxPerRepo,
|
|
102
|
+
...(adapter === undefined ? {} : { adapter }),
|
|
103
|
+
}).poll(signal),
|
|
104
|
+
reviewEventsFor(context, pullRequestPayloads),
|
|
105
|
+
reviewCommentEventsFor(context, pullRequestPayloads),
|
|
106
|
+
issueCommentEventsFor(context, issues, since),
|
|
107
|
+
]);
|
|
108
|
+
return {
|
|
109
|
+
repository: context.repository,
|
|
110
|
+
completedAt: input.now(),
|
|
111
|
+
succeeded: isFulfilled(issuesResult) &&
|
|
112
|
+
isFulfilled(pullRequestsResult) &&
|
|
113
|
+
reviews.succeeded &&
|
|
114
|
+
reviewComments.succeeded &&
|
|
115
|
+
issueComments.succeeded,
|
|
116
|
+
drafts: [
|
|
87
117
|
...issues
|
|
88
118
|
.filter((issue) => issue.pull_request === undefined)
|
|
89
119
|
.map((issue) => issueObservation({
|
|
@@ -92,67 +122,12 @@ async function pollRepository(input) {
|
|
|
92
122
|
...(adapter === undefined ? {} : { adapter }),
|
|
93
123
|
})),
|
|
94
124
|
...pullRequests,
|
|
95
|
-
...reviews,
|
|
96
|
-
...reviewComments,
|
|
97
|
-
...issueComments,
|
|
98
|
-
]
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
return [];
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
async function reviewCommentEventsFor(context, pullRequests) {
|
|
105
|
-
if (context.client.listReviewComments === undefined)
|
|
106
|
-
return [];
|
|
107
|
-
const items = await Promise.all(pullRequests.map(async (pullRequest) => (async () => {
|
|
108
|
-
const comments = await context.client.listReviewComments(context.owner, context.repo, pullRequest.number, context.config.polling.commentPageSize);
|
|
109
|
-
return (await Promise.all(comments.map((comment) => issueCommentEventsForComment(context, pullRequest, comment)))).flat();
|
|
110
|
-
})()));
|
|
111
|
-
return items.flat();
|
|
112
|
-
}
|
|
113
|
-
async function reviewEventsFor(context, pullRequestPayloads) {
|
|
114
|
-
const items = await Promise.all(pullRequestPayloads.map(async (pullRequest) => {
|
|
115
|
-
const reviewEvents = await context.client.listReviews(context.owner, context.repo, pullRequest.number, context.config.polling.commentPageSize);
|
|
116
|
-
return reviewEvents.flatMap((review) => githubReviewObservation({
|
|
117
|
-
repository: context.repository,
|
|
118
|
-
pullRequest,
|
|
119
|
-
review,
|
|
120
|
-
authorizedReviewers: [],
|
|
121
|
-
}));
|
|
122
|
-
}));
|
|
123
|
-
return items.flat();
|
|
124
|
-
}
|
|
125
|
-
async function issueCommentEventsFor(context, issues) {
|
|
126
|
-
const items = await Promise.all(issues.map(async (issue) => (async () => {
|
|
127
|
-
const comments = await context.client.listIssueComments(context.owner, context.repo, issue.number, context.config.polling.commentPageSize);
|
|
128
|
-
return (await Promise.all(comments.map((comment) => issueCommentEventsForComment(context, issue, comment)))).flat();
|
|
129
|
-
})()));
|
|
130
|
-
return items.flat();
|
|
131
|
-
}
|
|
132
|
-
async function issueCommentEventsForComment(context, issue, comment) {
|
|
133
|
-
const authorization = await retryAuthorization(context, comment);
|
|
134
|
-
const event = issueCommentObservation({
|
|
135
|
-
repository: context.repository,
|
|
136
|
-
issue,
|
|
137
|
-
comment,
|
|
138
|
-
...(authorization === undefined ? {} : { authorization }),
|
|
139
|
-
...(context.adapter === undefined ? {} : { adapter: context.adapter }),
|
|
140
|
-
});
|
|
141
|
-
return event === null ? [] : [event];
|
|
125
|
+
...reviews.drafts,
|
|
126
|
+
...reviewComments.drafts,
|
|
127
|
+
...issueComments.drafts,
|
|
128
|
+
],
|
|
129
|
+
};
|
|
142
130
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return undefined;
|
|
146
|
-
const login = comment.user?.login;
|
|
147
|
-
if (login === undefined || context.client.collaboratorPermission === undefined)
|
|
148
|
-
return { source: ReviewerAuthorizationSource.None };
|
|
149
|
-
try {
|
|
150
|
-
return {
|
|
151
|
-
source: ReviewerAuthorizationSource.ProviderPermission,
|
|
152
|
-
permission: await context.client.collaboratorPermission(context.owner, context.repo, login),
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
catch {
|
|
156
|
-
return { source: ReviewerAuthorizationSource.None };
|
|
157
|
-
}
|
|
131
|
+
function isFulfilled(result) {
|
|
132
|
+
return 'value' in result;
|
|
158
133
|
}
|
|
@@ -27,7 +27,10 @@ export const gitHubProviderDefinition = {
|
|
|
27
27
|
return {
|
|
28
28
|
adapter,
|
|
29
29
|
eventTypes: Object.values(GitHubEventType),
|
|
30
|
-
source: createGitHubSource(config, client, adapter, requests
|
|
30
|
+
source: createGitHubSource(config, client, adapter, requests, {
|
|
31
|
+
checkpoints: services.checkpoints,
|
|
32
|
+
now: () => services.clock.now().getTime(),
|
|
33
|
+
}),
|
|
31
34
|
maintenance: createGitHubWakeLabelReconciler({
|
|
32
35
|
orchestration: services.orchestration,
|
|
33
36
|
resources: services.resources,
|