@atolis-hq/wake 0.3.23 → 0.3.24
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/external-ref.js +5 -4
- package/dist/src/bootstrap/persistence-composition.js +18 -1
- package/dist/src/bootstrap/projection-runtime.js +4 -2
- package/dist/src/bootstrap/surface-api-work-applications.js +24 -31
- package/dist/src/bootstrap/version.js +1 -1
- package/dist/src/execution/application/execution-projection.js +18 -1
- package/dist/src/integrations/github/application/inbound-review-signals.js +42 -7
- package/dist/src/integrations/github/contracts/events.js +1 -0
- package/dist/src/integrations/github/infrastructure/client.js +21 -1
- package/dist/src/integrations/github/infrastructure/issue-source.js +2 -1
- package/dist/src/integrations/github/infrastructure/source.js +36 -18
- package/dist/src/orchestration/application/orchestration-projection.js +20 -0
- package/dist/src/resources/application/lookup-projections.js +2 -0
- package/package.json +1 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { ResourceCorrelationRole } from '../resources/index.js';
|
|
1
|
+
import { ResourceCorrelationRole, } from '../resources/index.js';
|
|
2
2
|
import { workItemId } from '../work/index.js';
|
|
3
3
|
// Work items never embed a provider/repo/issue number (see docs/adrs/0001); this joins
|
|
4
4
|
// the primary correlated resource's adapter-formatted key at the presentation layer only.
|
|
5
|
-
export async function primaryExternalRef(root, rawWorkItemId) {
|
|
6
|
-
const correlations = await root.resources.correlationsForWork(workItemId(rawWorkItemId));
|
|
5
|
+
export async function primaryExternalRef(root, rawWorkItemId, suppliedCorrelations, suppliedResources) {
|
|
6
|
+
const correlations = suppliedCorrelations ?? (await root.resources.correlationsForWork(workItemId(rawWorkItemId)));
|
|
7
7
|
const primary = correlations.find((value) => value.role === ResourceCorrelationRole.Primary);
|
|
8
8
|
if (primary === undefined)
|
|
9
9
|
return undefined;
|
|
10
|
-
const resource =
|
|
10
|
+
const resource = suppliedResources?.find((value) => value.resourceId === primary.resourceId) ??
|
|
11
|
+
(await root.resources.get(primary.resourceId));
|
|
11
12
|
return resource?.externalKey.key;
|
|
12
13
|
}
|
|
@@ -2,9 +2,26 @@ import { FileCheckpointStore, FileEventJournal, FileProjectionStore, } from '../
|
|
|
2
2
|
function identity(value) {
|
|
3
3
|
return value;
|
|
4
4
|
}
|
|
5
|
+
function serializeJournalAppends(journal) {
|
|
6
|
+
let appendTail = Promise.resolve();
|
|
7
|
+
return {
|
|
8
|
+
append(stream, expectedSequence, events) {
|
|
9
|
+
const appended = appendTail.then(() => journal.append(stream, expectedSequence, events));
|
|
10
|
+
appendTail = appended.then(() => undefined, () => undefined);
|
|
11
|
+
return appended;
|
|
12
|
+
},
|
|
13
|
+
readStream: (stream) => journal.readStream(stream),
|
|
14
|
+
readAll: (afterGlobalPosition, limit) => journal.readAll(afterGlobalPosition, limit),
|
|
15
|
+
...(journal.readLatest === undefined
|
|
16
|
+
? {}
|
|
17
|
+
: {
|
|
18
|
+
readLatest: (beforeGlobalPosition, limit) => journal.readLatest(beforeGlobalPosition, limit),
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
5
22
|
export function composePersistence(paths, clock, options) {
|
|
6
23
|
return {
|
|
7
|
-
journal: (options.decorateJournal ?? identity)(options.journal ?? new FileEventJournal(paths.dataRoot, clock)),
|
|
24
|
+
journal: serializeJournalAppends((options.decorateJournal ?? identity)(options.journal ?? new FileEventJournal(paths.dataRoot, clock))),
|
|
8
25
|
projections: (options.decorateProjections ?? identity)(options.projections ?? new FileProjectionStore(paths.dataRoot)),
|
|
9
26
|
checkpoints: (options.decorateCheckpoints ?? identity)(options.checkpoints ?? new FileCheckpointStore(paths.dataRoot)),
|
|
10
27
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { activityProjectionDefinitions } from '../activities/index.js';
|
|
2
2
|
import { controlPlaneProjectionDefinitions } from '../control-plane/index.js';
|
|
3
|
-
import { executionProjection } from '../execution/index.js';
|
|
3
|
+
import { executionProjection, runsByWorkflowInstanceProjection } from '../execution/index.js';
|
|
4
4
|
import { deliveryProjectionDefinitions } from '../integrations/index.js';
|
|
5
|
-
import { orchestrationProjection, workflowDefinitionsProjection } from '../orchestration/index.js';
|
|
5
|
+
import { orchestrationProjection, workflowDefinitionsProjection, workflowsByWorkItemProjection, } from '../orchestration/index.js';
|
|
6
6
|
import { ProjectionRunner } from '../persistence/index.js';
|
|
7
7
|
import { resourceCorrelationProjection, resourceProjection, resourcesByExternalKeyProjection, workCorrelationsProjection, } from '../resources/index.js';
|
|
8
8
|
import { workProjection } from '../work/index.js';
|
|
@@ -18,8 +18,10 @@ export const runtimeProjectionDefinitions = [
|
|
|
18
18
|
...deliveryProjectionDefinitions,
|
|
19
19
|
...controlPlaneProjectionDefinitions,
|
|
20
20
|
orchestrationProjection,
|
|
21
|
+
workflowsByWorkItemProjection,
|
|
21
22
|
workflowDefinitionsProjection,
|
|
22
23
|
executionProjection,
|
|
24
|
+
runsByWorkflowInstanceProjection,
|
|
23
25
|
boardProjection,
|
|
24
26
|
analyticsProjection,
|
|
25
27
|
];
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { pullRequestProjection } from '../activities/index.js';
|
|
2
|
+
import { executionProjection, runsByWorkflowInstanceProjection, } from '../execution/index.js';
|
|
1
3
|
import { correlationId, EventActorKind } from '../kernel/index.js';
|
|
2
|
-
import { isOperatorRetryEligible, OperatorRetryIneligibleError, } from '../orchestration/index.js';
|
|
3
|
-
import {
|
|
4
|
+
import { isOperatorRetryEligible, OperatorRetryIneligibleError, orchestrationProjection, workflowsByWorkItemProjection, } from '../orchestration/index.js';
|
|
5
|
+
import { workCorrelationsProjection, } from '../resources/index.js';
|
|
4
6
|
import { ApiCommandStatus, fromWorkItemKey, presentResource, presentRun, presentWorkflowInstance, presentWorkItem, } from '../surfaces/index.js';
|
|
5
7
|
import { workItemId, WorkStatus } from '../work/index.js';
|
|
6
8
|
import { primaryExternalRef } from './external-ref.js';
|
|
@@ -92,14 +94,16 @@ async function workDetail(root, key, now) {
|
|
|
92
94
|
const work = await root.work.get(id);
|
|
93
95
|
if (work === null)
|
|
94
96
|
return undefined;
|
|
95
|
-
const
|
|
97
|
+
const correlationProjection = await root.projections.read(workCorrelationsProjection.name, id);
|
|
98
|
+
const correlations = correlationProjection?.value ?? workCorrelationsProjection.initial(id);
|
|
96
99
|
const resources = (await Promise.all(correlations.map((item) => root.resources.get(item.resourceId)))).filter((value) => value !== null);
|
|
97
|
-
const
|
|
100
|
+
const workflowIds = (await root.projections.read(workflowsByWorkItemProjection.name, id))
|
|
101
|
+
?.value ?? workflowsByWorkItemProjection.initial(id);
|
|
102
|
+
const workflows = (await Promise.all(workflowIds.map((workflowInstanceId) => root.projections.read(orchestrationProjection.name, workflowInstanceId)))).flatMap((entry) => (entry === null || entry.value.view === null ? [] : [entry.value.view]));
|
|
98
103
|
const runs = await runsForWorkItem(root, id, workflows);
|
|
99
|
-
const
|
|
100
|
-
const pullRequest = pullRequests.find((entry) => resources.some((resource) => resource.resourceId === entry.key));
|
|
104
|
+
const pullRequest = (await Promise.all(resources.map((resource) => root.projections.read(pullRequestProjection.name, resource.resourceId)))).find((entry) => entry !== null && entry.value !== null);
|
|
101
105
|
const primary = workflows.find((value) => value.parentWorkflowInstanceId === undefined) ?? null;
|
|
102
|
-
const externalRef = await primaryExternalRef(root, work.workItemId);
|
|
106
|
+
const externalRef = await primaryExternalRef(root, work.workItemId, correlations, resources);
|
|
103
107
|
const data = {
|
|
104
108
|
work: { ...presentWorkItem(work), ...(externalRef === undefined ? {} : { externalRef }) },
|
|
105
109
|
resources: resources.map(presentResource(root.resolveResourceLink)),
|
|
@@ -117,23 +121,25 @@ async function workDetail(root, key, now) {
|
|
|
117
121
|
};
|
|
118
122
|
const [projections, correlationFacts] = await Promise.all([
|
|
119
123
|
contributingProjections(root, id, resources, workflows, runs),
|
|
120
|
-
|
|
124
|
+
Promise.resolve(correlationProjection).then((entry) => (entry === null ? [] : [entry])),
|
|
121
125
|
]);
|
|
122
126
|
return {
|
|
123
127
|
data,
|
|
124
|
-
meta: await projectionMeta(root.journal, [...projections, ...correlationFacts, ...(pullRequest
|
|
128
|
+
meta: await projectionMeta(root.journal, [...projections, ...correlationFacts, ...(pullRequest == null ? [] : [pullRequest])], now()),
|
|
125
129
|
};
|
|
126
130
|
}
|
|
127
131
|
async function runsForWorkItem(root, id, workflows) {
|
|
128
|
-
const matching = workflows ??
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return (await root.projections.list('execution'))
|
|
133
|
-
.flatMap((entry) => (entry.value.view === null ? [] : [entry.value.view]))
|
|
134
|
-
.filter((run) => matching.some((workflow) => workflow.workflowInstanceId === run.workflowInstanceId))
|
|
132
|
+
const matching = workflows ?? (await workflowsForWorkItem(root, id));
|
|
133
|
+
const runIds = (await Promise.all(matching.map((workflow) => root.projections.read(runsByWorkflowInstanceProjection.name, workflow.workflowInstanceId)))).flatMap((entry) => entry?.value ?? []);
|
|
134
|
+
return (await Promise.all(runIds.map((runId) => root.projections.read(executionProjection.name, runId))))
|
|
135
|
+
.flatMap((entry) => (entry === null || entry.value.view === null ? [] : [entry.value.view]))
|
|
135
136
|
.sort((left, right) => right.startedAt.localeCompare(left.startedAt));
|
|
136
137
|
}
|
|
138
|
+
async function workflowsForWorkItem(root, id) {
|
|
139
|
+
const workflowIds = (await root.projections.read(workflowsByWorkItemProjection.name, id))
|
|
140
|
+
?.value ?? workflowsByWorkItemProjection.initial(id);
|
|
141
|
+
return (await Promise.all(workflowIds.map((workflowInstanceId) => root.projections.read(orchestrationProjection.name, workflowInstanceId)))).flatMap((entry) => (entry === null || entry.value.view === null ? [] : [entry.value.view]));
|
|
142
|
+
}
|
|
137
143
|
function decodeWorkItemId(key) {
|
|
138
144
|
try {
|
|
139
145
|
return workItemId(fromWorkItemKey(key));
|
|
@@ -159,24 +165,11 @@ async function contributingProjections(root, id, resources, workflows, runs) {
|
|
|
159
165
|
const records = await Promise.all([
|
|
160
166
|
root.projections.read('work', id),
|
|
161
167
|
...resources.map((value) => root.projections.read('resources', value.resourceId)),
|
|
162
|
-
...workflows.map((value) => root.projections.read(
|
|
163
|
-
...runs.map((value) => root.projections.read(
|
|
168
|
+
...workflows.map((value) => root.projections.read(orchestrationProjection.name, value.workflowInstanceId)),
|
|
169
|
+
...runs.map((value) => root.projections.read(executionProjection.name, value.runId)),
|
|
164
170
|
]);
|
|
165
171
|
return records.filter((value) => value !== null);
|
|
166
172
|
}
|
|
167
|
-
async function contributingResourceCorrelationFacts(root, id) {
|
|
168
|
-
const events = await root.journal.readAll(0);
|
|
169
|
-
return events.flatMap((event) => {
|
|
170
|
-
const owned = selectResourceEvent(event);
|
|
171
|
-
if (owned === null)
|
|
172
|
-
return [];
|
|
173
|
-
if (owned.eventType !== ResourceEventType.WorkCorrelationEstablished &&
|
|
174
|
-
owned.eventType !== ResourceEventType.WorkCorrelationRetracted &&
|
|
175
|
-
owned.eventType !== ResourceEventType.WorkCorrelationConflicted)
|
|
176
|
-
return [];
|
|
177
|
-
return owned.payload.workItemId === id ? [{ lastGlobalPosition: owned.globalPosition }] : [];
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
173
|
function commandContext(idempotencyKey, now) {
|
|
181
174
|
const occurredAt = now();
|
|
182
175
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { selectRunExecutionEvent } from '../contracts/events.js';
|
|
1
|
+
import { ExecutionEventType, selectRunExecutionEvent, } from '../contracts/events.js';
|
|
2
2
|
import { foldRun } from '../domain/run.js';
|
|
3
3
|
export const executionProjection = {
|
|
4
4
|
name: 'execution',
|
|
@@ -15,3 +15,20 @@ export const executionProjection = {
|
|
|
15
15
|
return { events, view: foldRun(events) };
|
|
16
16
|
},
|
|
17
17
|
};
|
|
18
|
+
/** Run membership keyed by workflow instance for scoped readers. */
|
|
19
|
+
export const runsByWorkflowInstanceProjection = {
|
|
20
|
+
name: 'runs-by-workflow-instance',
|
|
21
|
+
select(event) {
|
|
22
|
+
const owned = selectRunExecutionEvent(event);
|
|
23
|
+
return owned?.eventType === ExecutionEventType.RunStarted
|
|
24
|
+
? { key: owned.payload.workflowInstanceId }
|
|
25
|
+
: null;
|
|
26
|
+
},
|
|
27
|
+
initial: () => [],
|
|
28
|
+
project(previous, event) {
|
|
29
|
+
const owned = selectRunExecutionEvent(event);
|
|
30
|
+
if (owned?.eventType !== ExecutionEventType.RunStarted)
|
|
31
|
+
return previous;
|
|
32
|
+
return previous.includes(owned.stream.id) ? previous : [...previous, owned.stream.id];
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { ActivityOutcomeKind, ReviewActorKind, ReviewDecisionKind, ReviewerAuthorizationSource, createPullRequestService, } from '../../../activities/index.js';
|
|
1
|
+
import { ActivityOutcomeKind, ReviewActorKind, ReviewDecisionKind, ReviewerAuthorizationSource, createPullRequestService, isReviewAuthorized, } from '../../../activities/index.js';
|
|
2
2
|
import { ApprovalAuthorityKind } from '../../../orchestration/index.js';
|
|
3
3
|
import { BuiltInResourceKind, ResourceCorrelationRole, ResourceStreamKind, resourceId, } from '../../../resources/index.js';
|
|
4
|
+
import { WorkStatus } from '../../../work/index.js';
|
|
4
5
|
import { UnknownGitHubIdentity } from '../contracts/vocabulary.js';
|
|
5
6
|
import { commandContext } from './inbound-context.js';
|
|
6
7
|
import { translateGitHubReviewCommand } from './review-command-translator.js';
|
|
@@ -10,7 +11,7 @@ export async function applyReviewSignal(input) {
|
|
|
10
11
|
return;
|
|
11
12
|
const payload = event.payload;
|
|
12
13
|
if (payload.reviewKind === 'issue') {
|
|
13
|
-
await applyIssueReviewSignal({ event, resources, lookup, orchestration, adapter });
|
|
14
|
+
await applyIssueReviewSignal({ event, resources, work, lookup, orchestration, adapter });
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
17
|
await applyFormalReviewSignal({ ...input, journal, resources, work });
|
|
@@ -58,7 +59,7 @@ async function applyFormalReviewSignal(input) {
|
|
|
58
59
|
});
|
|
59
60
|
}
|
|
60
61
|
async function applyIssueReviewSignal(input) {
|
|
61
|
-
const { event, resources, lookup, orchestration, adapter } = input;
|
|
62
|
+
const { event, resources, work, lookup, orchestration, adapter } = input;
|
|
62
63
|
if (event.payload.actor.kind !== ReviewActorKind.Human || isWakeDelivery(event.payload.body))
|
|
63
64
|
return;
|
|
64
65
|
if (resources === undefined || lookup === undefined || orchestration === undefined)
|
|
@@ -70,22 +71,54 @@ async function applyIssueReviewSignal(input) {
|
|
|
70
71
|
if (resourceIdValue === null)
|
|
71
72
|
return;
|
|
72
73
|
const resource = await resources.get(resourceIdValue);
|
|
74
|
+
const command = recognizedCommand(event.payload.body);
|
|
75
|
+
if (command === '/retry') {
|
|
76
|
+
await applyIssueRetrySignal({
|
|
77
|
+
event,
|
|
78
|
+
resources,
|
|
79
|
+
work,
|
|
80
|
+
orchestration,
|
|
81
|
+
resourceId: resourceIdValue,
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
73
85
|
if (resource?.kind === BuiltInResourceKind.PullRequest) {
|
|
74
86
|
await applyWorkflowSignal({
|
|
75
87
|
event,
|
|
76
88
|
resources,
|
|
77
89
|
orchestration,
|
|
78
90
|
resourceId: resourceIdValue,
|
|
79
|
-
outcome:
|
|
80
|
-
? ActivityOutcomeKind.Done
|
|
81
|
-
: ActivityOutcomeKind.Rejected,
|
|
91
|
+
outcome: command === '/approved' ? ActivityOutcomeKind.Done : ActivityOutcomeKind.Rejected,
|
|
82
92
|
});
|
|
83
93
|
return;
|
|
84
94
|
}
|
|
85
|
-
const command = recognizedCommand(event.payload.body);
|
|
86
95
|
if (command !== null)
|
|
87
96
|
await applyIssueApprovalSignal({ ...input, command });
|
|
88
97
|
}
|
|
98
|
+
async function applyIssueRetrySignal(input) {
|
|
99
|
+
const { event, resources, work, orchestration, resourceId: resourceIdValue } = input;
|
|
100
|
+
if (work === undefined || orchestration === undefined)
|
|
101
|
+
return;
|
|
102
|
+
if (!isReviewAuthorized({
|
|
103
|
+
actorId: event.payload.actor.id,
|
|
104
|
+
actorKind: event.payload.actor.kind,
|
|
105
|
+
resourceAuthorId: UnknownGitHubIdentity,
|
|
106
|
+
authorization: event.payload.authorization ?? { source: ReviewerAuthorizationSource.None },
|
|
107
|
+
}))
|
|
108
|
+
return;
|
|
109
|
+
const workItemIds = (await resources.correlations(resourceIdValue))
|
|
110
|
+
.filter((correlation) => correlation.role === ResourceCorrelationRole.Primary)
|
|
111
|
+
.map((correlation) => correlation.workItemId);
|
|
112
|
+
for (const workflow of await orchestration.listAll()) {
|
|
113
|
+
if (!workItemIds.includes(workflow.workItemId) ||
|
|
114
|
+
workflow.parentWorkflowInstanceId !== undefined)
|
|
115
|
+
continue;
|
|
116
|
+
const item = await work.get(workflow.workItemId);
|
|
117
|
+
if (item === null || item.deleted || item.frozen || item.state !== WorkStatus.Open)
|
|
118
|
+
continue;
|
|
119
|
+
await orchestration.retryBlockedFailedStage(workflow.workflowInstanceId, commandContext(event));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
89
122
|
async function applyIssueApprovalSignal(input) {
|
|
90
123
|
const { event, command, resources, lookup, orchestration, adapter } = input;
|
|
91
124
|
if (resources === undefined || lookup === undefined || orchestration === undefined)
|
|
@@ -163,5 +196,7 @@ function recognizedCommand(body) {
|
|
|
163
196
|
return '/approved';
|
|
164
197
|
if (normalized === '/changes' || normalized.startsWith('/changes '))
|
|
165
198
|
return '/changes';
|
|
199
|
+
if (normalized === '/retry')
|
|
200
|
+
return '/retry';
|
|
166
201
|
return null;
|
|
167
202
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Octokit } from '@octokit/rest';
|
|
2
|
-
import { MergeMethod, PullRequestState } from '../../../activities/index.js';
|
|
2
|
+
import { MergeMethod, ProviderPermission, PullRequestState } from '../../../activities/index.js';
|
|
3
3
|
import { GitHubOutboundAction } from '../contracts/vocabulary.js';
|
|
4
4
|
import { branch, getCombinedStatusForRef, getIssueLabels, getPullRequest, listCheckRunsForRef, listIssueComments, listIssues, listPullRequestFiles, listPullRequests, listReviewComments, listReviews, } from './client-reads.js';
|
|
5
5
|
import { createEtagCache } from './etag-cache.js';
|
|
@@ -38,6 +38,14 @@ export function createGitHubClient(token) {
|
|
|
38
38
|
listPullRequests: (owner, repo, maxResults) => listPullRequests(octokit, cache, owner, repo, maxResults),
|
|
39
39
|
listIssueComments: (owner, repo, issueNumber, pageSize) => listIssueComments(octokit, cache, owner, repo, issueNumber, pageSize),
|
|
40
40
|
listReviewComments: (owner, repo, pullNumber, pageSize) => listReviewComments(octokit, cache, owner, repo, pullNumber, pageSize),
|
|
41
|
+
collaboratorPermission: async (owner, repo, login) => {
|
|
42
|
+
const { data } = await octokit.rest.repos.getCollaboratorPermissionLevel({
|
|
43
|
+
owner,
|
|
44
|
+
repo,
|
|
45
|
+
username: login,
|
|
46
|
+
});
|
|
47
|
+
return providerPermission(data.permission);
|
|
48
|
+
},
|
|
41
49
|
getIssueLabels: (owner, repo, issueNumber) => getIssueLabels(octokit, cache, owner, repo, issueNumber),
|
|
42
50
|
getIssue: async (owner, repo, issueNumber) => octokit.rest.issues.get({ owner, repo, issue_number: issueNumber }).then(({ data }) => ({
|
|
43
51
|
id: String(data.id),
|
|
@@ -60,6 +68,18 @@ export function createGitHubClient(token) {
|
|
|
60
68
|
deliver: (command) => deliver(octokit, command),
|
|
61
69
|
};
|
|
62
70
|
}
|
|
71
|
+
function providerPermission(permission) {
|
|
72
|
+
switch (permission) {
|
|
73
|
+
case ProviderPermission.Read:
|
|
74
|
+
case ProviderPermission.Triage:
|
|
75
|
+
case ProviderPermission.Write:
|
|
76
|
+
case ProviderPermission.Maintain:
|
|
77
|
+
case ProviderPermission.Admin:
|
|
78
|
+
return permission;
|
|
79
|
+
default:
|
|
80
|
+
return ProviderPermission.None;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
63
83
|
async function deliver(octokit, command) {
|
|
64
84
|
const marker = `<!-- wake:delivery:${command.idempotencyKey} -->`;
|
|
65
85
|
if (command.action === 'approve') {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PullRequestState, ReviewActorKind } from '../../../activities/index.js';
|
|
1
|
+
import { PullRequestState, ReviewActorKind, } from '../../../activities/index.js';
|
|
2
2
|
import { createEventDraft, EventActorKind, EventSourceKind } from '../../../kernel/index.js';
|
|
3
3
|
import { ExternalWorkOutcome } from '../../contracts/outcome-vocabulary.js';
|
|
4
4
|
import { integrationStream } from '../../contracts/streams.js';
|
|
@@ -95,6 +95,7 @@ export function issueCommentObservation(input) {
|
|
|
95
95
|
id: input.comment.user?.login ?? UnknownGitHubIdentity,
|
|
96
96
|
kind: input.comment.user?.type === 'Bot' ? ReviewActorKind.Bot : ReviewActorKind.Human,
|
|
97
97
|
},
|
|
98
|
+
...(input.authorization === undefined ? {} : { authorization: input.authorization }),
|
|
98
99
|
raw: { id: input.comment.id },
|
|
99
100
|
},
|
|
100
101
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ReviewerAuthorizationSource, } from '../../../activities/index.js';
|
|
1
2
|
import { GitHubEventType } from '../contracts/events.js';
|
|
2
3
|
import { issueCommentObservation, issueObservation } from './issue-source.js';
|
|
3
4
|
import { createGitHubPullRequestSource } from './pr-source.js';
|
|
@@ -71,15 +72,10 @@ async function pollRepository(input) {
|
|
|
71
72
|
async function reviewCommentEventsFor(context, pullRequests) {
|
|
72
73
|
if (context.client.listReviewComments === undefined)
|
|
73
74
|
return [];
|
|
74
|
-
const items = await Promise.all(pullRequests.map(async (pullRequest) => (
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
comment,
|
|
79
|
-
...(context.adapter === undefined ? {} : { adapter: context.adapter }),
|
|
80
|
-
});
|
|
81
|
-
return event === null ? [] : [event];
|
|
82
|
-
})));
|
|
75
|
+
const items = await Promise.all(pullRequests.map(async (pullRequest) => (async () => {
|
|
76
|
+
const comments = await context.client.listReviewComments(context.owner, context.repo, pullRequest.number, context.config.polling.commentPageSize);
|
|
77
|
+
return (await Promise.all(comments.map((comment) => issueCommentEventsForComment(context, pullRequest, comment)))).flat();
|
|
78
|
+
})()));
|
|
83
79
|
return items.flat();
|
|
84
80
|
}
|
|
85
81
|
async function reviewEventsFor(context, pullRequestPayloads) {
|
|
@@ -95,14 +91,36 @@ async function reviewEventsFor(context, pullRequestPayloads) {
|
|
|
95
91
|
return items.flat();
|
|
96
92
|
}
|
|
97
93
|
async function issueCommentEventsFor(context, issues) {
|
|
98
|
-
const items = await Promise.all(issues.map(async (issue) => (
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
comment,
|
|
103
|
-
...(context.adapter === undefined ? {} : { adapter: context.adapter }),
|
|
104
|
-
});
|
|
105
|
-
return event === null ? [] : [event];
|
|
106
|
-
})));
|
|
94
|
+
const items = await Promise.all(issues.map(async (issue) => (async () => {
|
|
95
|
+
const comments = await context.client.listIssueComments(context.owner, context.repo, issue.number, context.config.polling.commentPageSize);
|
|
96
|
+
return (await Promise.all(comments.map((comment) => issueCommentEventsForComment(context, issue, comment)))).flat();
|
|
97
|
+
})()));
|
|
107
98
|
return items.flat();
|
|
108
99
|
}
|
|
100
|
+
async function issueCommentEventsForComment(context, issue, comment) {
|
|
101
|
+
const authorization = await retryAuthorization(context, comment);
|
|
102
|
+
const event = issueCommentObservation({
|
|
103
|
+
repository: context.repository,
|
|
104
|
+
issue,
|
|
105
|
+
comment,
|
|
106
|
+
...(authorization === undefined ? {} : { authorization }),
|
|
107
|
+
...(context.adapter === undefined ? {} : { adapter: context.adapter }),
|
|
108
|
+
});
|
|
109
|
+
return event === null ? [] : [event];
|
|
110
|
+
}
|
|
111
|
+
async function retryAuthorization(context, comment) {
|
|
112
|
+
if (comment.body?.trim().toLowerCase() !== '/retry')
|
|
113
|
+
return undefined;
|
|
114
|
+
const login = comment.user?.login;
|
|
115
|
+
if (login === undefined || context.client.collaboratorPermission === undefined)
|
|
116
|
+
return { source: ReviewerAuthorizationSource.None };
|
|
117
|
+
try {
|
|
118
|
+
return {
|
|
119
|
+
source: ReviewerAuthorizationSource.ProviderPermission,
|
|
120
|
+
permission: await context.client.collaboratorPermission(context.owner, context.repo, login),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return { source: ReviewerAuthorizationSource.None };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { selectWorkflowOrchestrationEvent } from '../contracts/event-decoder.js';
|
|
2
|
+
import { OrchestrationEventType } from '../contracts/events.js';
|
|
2
3
|
import { isWorkflowInstanceStream } from '../contracts/streams.js';
|
|
3
4
|
import { foldWorkflowInstance } from '../domain/workflow-instance.js';
|
|
4
5
|
export const orchestrationProjection = {
|
|
@@ -18,3 +19,22 @@ export const orchestrationProjection = {
|
|
|
18
19
|
return { events, view: foldWorkflowInstance(events) };
|
|
19
20
|
},
|
|
20
21
|
};
|
|
22
|
+
/** Workflow-instance membership keyed by its owning work item for scoped readers. */
|
|
23
|
+
export const workflowsByWorkItemProjection = {
|
|
24
|
+
name: 'workflows-by-work-item',
|
|
25
|
+
select(event) {
|
|
26
|
+
const owned = selectWorkflowOrchestrationEvent(event);
|
|
27
|
+
return owned?.eventType === OrchestrationEventType.InstanceStarted &&
|
|
28
|
+
isWorkflowInstanceStream(owned.stream)
|
|
29
|
+
? { key: owned.payload.workItemId }
|
|
30
|
+
: null;
|
|
31
|
+
},
|
|
32
|
+
initial: () => [],
|
|
33
|
+
project(previous, event) {
|
|
34
|
+
const owned = selectWorkflowOrchestrationEvent(event);
|
|
35
|
+
if (owned?.eventType !== OrchestrationEventType.InstanceStarted ||
|
|
36
|
+
!isWorkflowInstanceStream(owned.stream))
|
|
37
|
+
return previous;
|
|
38
|
+
return previous.includes(owned.stream.id) ? previous : [...previous, owned.stream.id];
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -24,6 +24,8 @@ export const workCorrelationsProjection = {
|
|
|
24
24
|
return { key: owned.payload.workItemId };
|
|
25
25
|
if (owned?.eventType === ResourceEventType.WorkCorrelationRetracted)
|
|
26
26
|
return { key: owned.payload.workItemId };
|
|
27
|
+
if (owned?.eventType === ResourceEventType.WorkCorrelationConflicted)
|
|
28
|
+
return { key: owned.payload.workItemId };
|
|
27
29
|
return null;
|
|
28
30
|
},
|
|
29
31
|
initial: () => [],
|