paperclip-github-plugin 0.8.2 → 0.8.4
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/README.md +10 -4
- package/dist/manifest.js +2 -1
- package/dist/ui/index.js +50 -1
- package/dist/ui/index.js.map +2 -2
- package/dist/worker.js +478 -9
- package/package.json +1 -1
package/dist/worker.js
CHANGED
|
@@ -613,6 +613,7 @@ function requiresPaperclipBoardAccess(value) {
|
|
|
613
613
|
}
|
|
614
614
|
|
|
615
615
|
// src/worker.ts
|
|
616
|
+
var GITHUB_SYNC_BASE_ORIGIN_KIND = `plugin:${GITHUB_SYNC_PLUGIN_ID}`;
|
|
616
617
|
var GITHUB_ISSUE_ORIGIN_KIND = `plugin:${GITHUB_SYNC_PLUGIN_ID}:github-issue`;
|
|
617
618
|
var GITHUB_PULL_REQUEST_ORIGIN_KIND = `plugin:${GITHUB_SYNC_PLUGIN_ID}:github-pull-request`;
|
|
618
619
|
var SETTINGS_SCOPE = {
|
|
@@ -5020,6 +5021,29 @@ function getPaperclipIssueSyncContext(issue) {
|
|
|
5020
5021
|
executionState: normalizePaperclipIssueExecutionState(record.executionState)
|
|
5021
5022
|
};
|
|
5022
5023
|
}
|
|
5024
|
+
function hasUnresolvedPaperclipIssueBlockerSummary(blockers) {
|
|
5025
|
+
if (!Array.isArray(blockers)) {
|
|
5026
|
+
return false;
|
|
5027
|
+
}
|
|
5028
|
+
return blockers.some((blocker) => {
|
|
5029
|
+
if (!blocker || typeof blocker !== "object") {
|
|
5030
|
+
return true;
|
|
5031
|
+
}
|
|
5032
|
+
const status = blocker.status;
|
|
5033
|
+
return status !== "done" && status !== "cancelled";
|
|
5034
|
+
});
|
|
5035
|
+
}
|
|
5036
|
+
async function hasUnresolvedPaperclipIssueBlocker(ctx, issue, companyId) {
|
|
5037
|
+
const record = issue;
|
|
5038
|
+
if (hasUnresolvedPaperclipIssueBlockerSummary(record.blockedBy)) {
|
|
5039
|
+
return true;
|
|
5040
|
+
}
|
|
5041
|
+
if (typeof ctx.issues.relations?.get !== "function") {
|
|
5042
|
+
return false;
|
|
5043
|
+
}
|
|
5044
|
+
const relations = await ctx.issues.relations.get(issue.id, companyId);
|
|
5045
|
+
return hasUnresolvedPaperclipIssueBlockerSummary(relations.blockedBy);
|
|
5046
|
+
}
|
|
5023
5047
|
function isSamePaperclipIssueAssigneePrincipal(left, right) {
|
|
5024
5048
|
if (!left || !right) {
|
|
5025
5049
|
return !left && !right;
|
|
@@ -5201,6 +5225,9 @@ function isHealthyMaintainerWaitTransition(params) {
|
|
|
5201
5225
|
const { currentStatus, nextStatus, syncContext } = params;
|
|
5202
5226
|
return nextStatus === "in_review" && (currentStatus === "done" || currentStatus === "in_review") && syncContext.executionState === null && syncContext.executionPolicy !== null;
|
|
5203
5227
|
}
|
|
5228
|
+
function shouldPreserveImportedTriageAssignee(params) {
|
|
5229
|
+
return params.wasImportedThisRun && params.maintainerAuthoredImportedIssue === true && params.currentStatus === "backlog" && params.nextStatus === "todo";
|
|
5230
|
+
}
|
|
5204
5231
|
function doesPaperclipIssueAssigneeMatch(currentAssignee, nextAssignee) {
|
|
5205
5232
|
return isSamePaperclipIssueAssigneePrincipal(currentAssignee, nextAssignee);
|
|
5206
5233
|
}
|
|
@@ -5552,10 +5579,10 @@ function normalizeGitHubPullRequestReviewDecision(value) {
|
|
|
5552
5579
|
}
|
|
5553
5580
|
}
|
|
5554
5581
|
function isGitHubPullRequestActionRequiredForSync(pullRequest) {
|
|
5555
|
-
return pullRequest.
|
|
5582
|
+
return pullRequest.mergeability === "conflicting" || ACTION_REQUIRED_GITHUB_PULL_REQUEST_MERGE_STATE_STATUSES.has(pullRequest.mergeStateStatus);
|
|
5556
5583
|
}
|
|
5557
5584
|
function isGitHubPullRequestReviewReadyForSync(pullRequest) {
|
|
5558
|
-
if (pullRequest.ciState !== "green" || pullRequest.hasUnresolvedReviewThreads
|
|
5585
|
+
if (pullRequest.ciState !== "green" || pullRequest.hasUnresolvedReviewThreads) {
|
|
5559
5586
|
return false;
|
|
5560
5587
|
}
|
|
5561
5588
|
return REVIEW_READY_GITHUB_PULL_REQUEST_MERGE_STATE_STATUSES.has(pullRequest.mergeStateStatus);
|
|
@@ -5588,9 +5615,6 @@ function listGitHubPullRequestSyncBlockingConditions(pullRequest) {
|
|
|
5588
5615
|
if (pullRequest.hasUnresolvedReviewThreads) {
|
|
5589
5616
|
conditions.push("unresolved review threads");
|
|
5590
5617
|
}
|
|
5591
|
-
if (pullRequest.reviewDecision === "changes_requested") {
|
|
5592
|
-
conditions.push("requested changes");
|
|
5593
|
-
}
|
|
5594
5618
|
return conditions;
|
|
5595
5619
|
}
|
|
5596
5620
|
function tryBuildGitHubPullRequestStatusSnapshotFromBatchNode(node, repository) {
|
|
@@ -6365,6 +6389,18 @@ function extractImportedGitHubIssueUrlFromDescription(description) {
|
|
|
6365
6389
|
}
|
|
6366
6390
|
return normalizeGitHubIssueHtmlUrl(legacyMatch[1]);
|
|
6367
6391
|
}
|
|
6392
|
+
function removeGitHubIssueLinkMetadataFromDescription(description) {
|
|
6393
|
+
if (typeof description !== "string") {
|
|
6394
|
+
return void 0;
|
|
6395
|
+
}
|
|
6396
|
+
let next = description;
|
|
6397
|
+
const hiddenMarkerPattern = getHiddenGitHubImportMarkerPattern();
|
|
6398
|
+
while (hiddenMarkerPattern.test(next)) {
|
|
6399
|
+
next = next.replace(hiddenMarkerPattern, "");
|
|
6400
|
+
}
|
|
6401
|
+
next = next.replace(/^\*\s+GitHub issue:\s+\[[^\]]+\]\([^)]+\)\s*$/gm, "").replace(/^Imported from https:\/\/github\.com\/[^\s)]+\/[^\s)]+\/issues\/\d+\.?\s*$/gim, "").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
6402
|
+
return next === description ? void 0 : next;
|
|
6403
|
+
}
|
|
6368
6404
|
function compareImportedPaperclipIssueCreatedAt(left, right) {
|
|
6369
6405
|
const leftTime = Date.parse(String(left.createdAt ?? ""));
|
|
6370
6406
|
const rightTime = Date.parse(String(right.createdAt ?? ""));
|
|
@@ -6693,6 +6729,7 @@ async function listGitHubIssueLinkRecords(ctx, query = {}) {
|
|
|
6693
6729
|
}
|
|
6694
6730
|
records.push({
|
|
6695
6731
|
paperclipIssueId: entry.scopeId,
|
|
6732
|
+
...typeof entry.externalId === "string" && entry.externalId.trim() ? { externalId: entry.externalId.trim() } : {},
|
|
6696
6733
|
...typeof entry.createdAt === "string" ? { createdAt: entry.createdAt } : {},
|
|
6697
6734
|
...typeof entry.updatedAt === "string" ? { updatedAt: entry.updatedAt } : {},
|
|
6698
6735
|
...typeof entry.title === "string" && entry.title.trim() ? { title: entry.title.trim() } : {},
|
|
@@ -6739,6 +6776,7 @@ async function listGitHubPullRequestLinkRecords(ctx, query = {}) {
|
|
|
6739
6776
|
}
|
|
6740
6777
|
records.push({
|
|
6741
6778
|
paperclipIssueId: entry.scopeId,
|
|
6779
|
+
...typeof entry.externalId === "string" && entry.externalId.trim() ? { externalId: entry.externalId.trim() } : {},
|
|
6742
6780
|
...typeof entry.createdAt === "string" ? { createdAt: entry.createdAt } : {},
|
|
6743
6781
|
...typeof entry.updatedAt === "string" ? { updatedAt: entry.updatedAt } : {},
|
|
6744
6782
|
...typeof entry.title === "string" && entry.title.trim() ? { title: entry.title.trim() } : {},
|
|
@@ -7147,6 +7185,134 @@ async function linkPaperclipIssueToGitHubPullRequest(ctx, params) {
|
|
|
7147
7185
|
githubPullRequestState: pullRequestState
|
|
7148
7186
|
};
|
|
7149
7187
|
}
|
|
7188
|
+
function invalidateGitHubLinkCachesForTarget(params) {
|
|
7189
|
+
if (!params.companyId || !params.projectId || !params.repositoryUrl) {
|
|
7190
|
+
return;
|
|
7191
|
+
}
|
|
7192
|
+
const repository = parseRepositoryReference(params.repositoryUrl);
|
|
7193
|
+
if (!repository) {
|
|
7194
|
+
return;
|
|
7195
|
+
}
|
|
7196
|
+
invalidateProjectPullRequestCaches({
|
|
7197
|
+
companyId: params.companyId,
|
|
7198
|
+
projectId: params.projectId,
|
|
7199
|
+
repository
|
|
7200
|
+
});
|
|
7201
|
+
}
|
|
7202
|
+
async function tombstoneGitHubIssueLinkRecord(ctx, record, unlinkedAt) {
|
|
7203
|
+
await ctx.entities.upsert({
|
|
7204
|
+
entityType: ISSUE_LINK_ENTITY_TYPE,
|
|
7205
|
+
scopeKind: "issue",
|
|
7206
|
+
scopeId: record.paperclipIssueId,
|
|
7207
|
+
externalId: record.externalId ?? record.data.githubIssueUrl,
|
|
7208
|
+
title: record.title ?? `GitHub issue #${record.data.githubIssueNumber}`,
|
|
7209
|
+
status: "unlinked",
|
|
7210
|
+
data: {
|
|
7211
|
+
kind: "issue",
|
|
7212
|
+
paperclipIssueId: record.paperclipIssueId,
|
|
7213
|
+
repositoryUrl: record.data.repositoryUrl,
|
|
7214
|
+
githubIssueNumber: record.data.githubIssueNumber,
|
|
7215
|
+
githubIssueUrl: record.data.githubIssueUrl,
|
|
7216
|
+
...record.data.companyId ? { companyId: record.data.companyId } : {},
|
|
7217
|
+
...record.data.paperclipProjectId ? { paperclipProjectId: record.data.paperclipProjectId } : {},
|
|
7218
|
+
unlinkedAt
|
|
7219
|
+
}
|
|
7220
|
+
});
|
|
7221
|
+
}
|
|
7222
|
+
async function tombstoneGitHubPullRequestLinkRecord(ctx, record, unlinkedAt) {
|
|
7223
|
+
await ctx.entities.upsert({
|
|
7224
|
+
entityType: PULL_REQUEST_LINK_ENTITY_TYPE,
|
|
7225
|
+
scopeKind: "issue",
|
|
7226
|
+
scopeId: record.paperclipIssueId,
|
|
7227
|
+
externalId: record.externalId ?? record.data.githubPullRequestUrl,
|
|
7228
|
+
title: record.title ?? `GitHub pull request #${record.data.githubPullRequestNumber}`,
|
|
7229
|
+
status: "unlinked",
|
|
7230
|
+
data: {
|
|
7231
|
+
kind: "pull_request",
|
|
7232
|
+
paperclipIssueId: record.paperclipIssueId,
|
|
7233
|
+
repositoryUrl: record.data.repositoryUrl,
|
|
7234
|
+
githubPullRequestNumber: record.data.githubPullRequestNumber,
|
|
7235
|
+
githubPullRequestUrl: record.data.githubPullRequestUrl,
|
|
7236
|
+
...record.data.companyId ? { companyId: record.data.companyId } : {},
|
|
7237
|
+
...record.data.paperclipProjectId ? { paperclipProjectId: record.data.paperclipProjectId } : {},
|
|
7238
|
+
unlinkedAt
|
|
7239
|
+
}
|
|
7240
|
+
});
|
|
7241
|
+
}
|
|
7242
|
+
async function unlinkPaperclipIssueFromGitHub(ctx, params) {
|
|
7243
|
+
const companyId = normalizeCompanyId(params.companyId);
|
|
7244
|
+
const issueId = normalizeOptionalString2(params.issueId);
|
|
7245
|
+
if (!companyId || !issueId) {
|
|
7246
|
+
throw new Error("companyId and issueId are required.");
|
|
7247
|
+
}
|
|
7248
|
+
const issue = await ctx.issues.get(issueId, companyId);
|
|
7249
|
+
if (!issue) {
|
|
7250
|
+
throw new Error("Paperclip issue was not found.");
|
|
7251
|
+
}
|
|
7252
|
+
const issueLinkRecords = (await listGitHubIssueLinkRecords(ctx, {
|
|
7253
|
+
paperclipIssueId: issueId
|
|
7254
|
+
})).filter((record) => !record.data.companyId || record.data.companyId === companyId);
|
|
7255
|
+
const pullRequestLinkRecords = (await listGitHubPullRequestLinkRecords(ctx, {
|
|
7256
|
+
paperclipIssueId: issueId
|
|
7257
|
+
})).filter((record) => !record.data.companyId || record.data.companyId === companyId);
|
|
7258
|
+
const importRegistry = normalizeImportRegistry(await ctx.state.get(IMPORT_REGISTRY_SCOPE));
|
|
7259
|
+
const removedImportRegistryEntries = importRegistry.filter(
|
|
7260
|
+
(entry) => entry.paperclipIssueId === issueId && (!entry.companyId || entry.companyId === companyId)
|
|
7261
|
+
);
|
|
7262
|
+
const nextImportRegistry = importRegistry.filter(
|
|
7263
|
+
(entry) => !(entry.paperclipIssueId === issueId && (!entry.companyId || entry.companyId === companyId))
|
|
7264
|
+
);
|
|
7265
|
+
const unlinkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7266
|
+
for (const record of issueLinkRecords) {
|
|
7267
|
+
await tombstoneGitHubIssueLinkRecord(ctx, record, unlinkedAt);
|
|
7268
|
+
invalidateGitHubLinkCachesForTarget({
|
|
7269
|
+
companyId: record.data.companyId ?? companyId,
|
|
7270
|
+
projectId: record.data.paperclipProjectId ?? issue.projectId ?? void 0,
|
|
7271
|
+
repositoryUrl: record.data.repositoryUrl
|
|
7272
|
+
});
|
|
7273
|
+
}
|
|
7274
|
+
for (const record of pullRequestLinkRecords) {
|
|
7275
|
+
await tombstoneGitHubPullRequestLinkRecord(ctx, record, unlinkedAt);
|
|
7276
|
+
invalidateGitHubLinkCachesForTarget({
|
|
7277
|
+
companyId: record.data.companyId ?? companyId,
|
|
7278
|
+
projectId: record.data.paperclipProjectId ?? issue.projectId ?? void 0,
|
|
7279
|
+
repositoryUrl: record.data.repositoryUrl
|
|
7280
|
+
});
|
|
7281
|
+
}
|
|
7282
|
+
for (const entry of removedImportRegistryEntries) {
|
|
7283
|
+
invalidateGitHubLinkCachesForTarget({
|
|
7284
|
+
companyId: entry.companyId ?? companyId,
|
|
7285
|
+
projectId: entry.paperclipProjectId ?? issue.projectId ?? void 0,
|
|
7286
|
+
repositoryUrl: entry.repositoryUrl
|
|
7287
|
+
});
|
|
7288
|
+
}
|
|
7289
|
+
if (removedImportRegistryEntries.length > 0) {
|
|
7290
|
+
await ctx.state.set(IMPORT_REGISTRY_SCOPE, nextImportRegistry);
|
|
7291
|
+
}
|
|
7292
|
+
const shouldClearGitHubOrigin = issue.originKind === GITHUB_ISSUE_ORIGIN_KIND || issue.originKind === GITHUB_PULL_REQUEST_ORIGIN_KIND;
|
|
7293
|
+
const nextDescription = removeGitHubIssueLinkMetadataFromDescription(issue.description);
|
|
7294
|
+
const issuePatch = {};
|
|
7295
|
+
if (shouldClearGitHubOrigin) {
|
|
7296
|
+
issuePatch.originKind = GITHUB_SYNC_BASE_ORIGIN_KIND;
|
|
7297
|
+
issuePatch.originId = null;
|
|
7298
|
+
issuePatch.originRunId = null;
|
|
7299
|
+
}
|
|
7300
|
+
if (nextDescription !== void 0) {
|
|
7301
|
+
issuePatch.description = nextDescription;
|
|
7302
|
+
}
|
|
7303
|
+
if (Object.keys(issuePatch).length > 0) {
|
|
7304
|
+
await ctx.issues.update(issueId, issuePatch, companyId);
|
|
7305
|
+
}
|
|
7306
|
+
return {
|
|
7307
|
+
paperclipIssueId: issueId,
|
|
7308
|
+
unlinked: issueLinkRecords.length > 0 || pullRequestLinkRecords.length > 0 || removedImportRegistryEntries.length > 0 || shouldClearGitHubOrigin || nextDescription !== void 0,
|
|
7309
|
+
unlinkedIssueLinksCount: issueLinkRecords.length,
|
|
7310
|
+
unlinkedPullRequestLinksCount: pullRequestLinkRecords.length,
|
|
7311
|
+
removedImportRegistryEntriesCount: removedImportRegistryEntries.length,
|
|
7312
|
+
clearedIssueOrigin: shouldClearGitHubOrigin,
|
|
7313
|
+
removedDescriptionMetadata: nextDescription !== void 0
|
|
7314
|
+
};
|
|
7315
|
+
}
|
|
7150
7316
|
async function upsertStatusTransitionCommentAnnotation(ctx, params) {
|
|
7151
7317
|
const { issueId, commentId, annotation } = params;
|
|
7152
7318
|
await ctx.entities.upsert({
|
|
@@ -8450,6 +8616,261 @@ async function ensurePaperclipIssueImported(ctx, mapping, advancedSettings, issu
|
|
|
8450
8616
|
}
|
|
8451
8617
|
return createdIssue.id;
|
|
8452
8618
|
}
|
|
8619
|
+
function getGitHubIssueRepositoryReference(issue) {
|
|
8620
|
+
return parseGitHubIssueHtmlUrl(issue.htmlUrl);
|
|
8621
|
+
}
|
|
8622
|
+
function findTransferredIssueTargetMapping(mappings, params) {
|
|
8623
|
+
const companyId = normalizeCompanyId(params.companyId);
|
|
8624
|
+
const repository = parseRepositoryReference(params.repositoryUrl);
|
|
8625
|
+
if (!companyId || !repository) {
|
|
8626
|
+
return void 0;
|
|
8627
|
+
}
|
|
8628
|
+
return getSyncableMappings(mappings).find((mapping) => {
|
|
8629
|
+
if (normalizeCompanyId(mapping.companyId) !== companyId) {
|
|
8630
|
+
return false;
|
|
8631
|
+
}
|
|
8632
|
+
const mappingRepository = parseRepositoryReference(mapping.repositoryUrl);
|
|
8633
|
+
return Boolean(mappingRepository && areRepositoriesEqual(mappingRepository, repository));
|
|
8634
|
+
});
|
|
8635
|
+
}
|
|
8636
|
+
async function patchPaperclipIssueForGitHubIssueTransfer(ctx, params) {
|
|
8637
|
+
const nextDescription = buildPaperclipIssueDescription(params.githubIssue, []);
|
|
8638
|
+
const nextOriginId = normalizeGitHubIssueHtmlUrl(params.githubIssue.htmlUrl) ?? params.githubIssue.htmlUrl;
|
|
8639
|
+
const descriptionUpdated = normalizeIssueDescriptionValue(params.currentDescription) !== nextDescription;
|
|
8640
|
+
const patch = {
|
|
8641
|
+
projectId: params.projectId,
|
|
8642
|
+
originKind: GITHUB_ISSUE_ORIGIN_KIND,
|
|
8643
|
+
originId: nextOriginId,
|
|
8644
|
+
...descriptionUpdated ? { description: nextDescription } : {}
|
|
8645
|
+
};
|
|
8646
|
+
let issueUpdated = false;
|
|
8647
|
+
if (params.paperclipApiBaseUrl) {
|
|
8648
|
+
try {
|
|
8649
|
+
const response = await fetchPaperclipApi(
|
|
8650
|
+
getPaperclipIssueEndpoint(params.paperclipApiBaseUrl, params.issueId),
|
|
8651
|
+
{
|
|
8652
|
+
method: "PATCH",
|
|
8653
|
+
headers: {
|
|
8654
|
+
accept: "application/json",
|
|
8655
|
+
"content-type": "application/json"
|
|
8656
|
+
},
|
|
8657
|
+
body: JSON.stringify(patch)
|
|
8658
|
+
},
|
|
8659
|
+
{
|
|
8660
|
+
companyId: params.companyId
|
|
8661
|
+
}
|
|
8662
|
+
);
|
|
8663
|
+
const payloadResult = await readPaperclipApiJsonResponse(response, {
|
|
8664
|
+
operationLabel: "issue update",
|
|
8665
|
+
bodyRequired: false
|
|
8666
|
+
});
|
|
8667
|
+
issueUpdated = !payloadResult.failure;
|
|
8668
|
+
} catch {
|
|
8669
|
+
issueUpdated = false;
|
|
8670
|
+
}
|
|
8671
|
+
}
|
|
8672
|
+
if (!issueUpdated) {
|
|
8673
|
+
await ctx.issues.update(
|
|
8674
|
+
params.issueId,
|
|
8675
|
+
patch,
|
|
8676
|
+
params.companyId
|
|
8677
|
+
);
|
|
8678
|
+
}
|
|
8679
|
+
return {
|
|
8680
|
+
descriptionUpdated
|
|
8681
|
+
};
|
|
8682
|
+
}
|
|
8683
|
+
async function moveImportedIssueToTransferredGitHubMapping(ctx, params) {
|
|
8684
|
+
const companyId = normalizeCompanyId(params.targetMapping.companyId);
|
|
8685
|
+
const projectId = normalizeOptionalString2(params.targetMapping.paperclipProjectId);
|
|
8686
|
+
if (!companyId || !projectId) {
|
|
8687
|
+
return {
|
|
8688
|
+
updatedDescriptionsCount: 0
|
|
8689
|
+
};
|
|
8690
|
+
}
|
|
8691
|
+
const paperclipIssue = await ctx.issues.get(params.importedIssue.paperclipIssueId, companyId);
|
|
8692
|
+
if (!paperclipIssue) {
|
|
8693
|
+
return {
|
|
8694
|
+
updatedDescriptionsCount: 0
|
|
8695
|
+
};
|
|
8696
|
+
}
|
|
8697
|
+
const transferPatchResult = await patchPaperclipIssueForGitHubIssueTransfer(ctx, {
|
|
8698
|
+
companyId,
|
|
8699
|
+
issueId: params.importedIssue.paperclipIssueId,
|
|
8700
|
+
projectId,
|
|
8701
|
+
githubIssue: params.githubIssue,
|
|
8702
|
+
currentDescription: paperclipIssue.description,
|
|
8703
|
+
paperclipApiBaseUrl: params.paperclipApiBaseUrl
|
|
8704
|
+
});
|
|
8705
|
+
const nextGitHubIssueUrl = normalizeGitHubIssueHtmlUrl(params.githubIssue.htmlUrl) ?? params.githubIssue.htmlUrl;
|
|
8706
|
+
const existingLinks = await listGitHubIssueLinkRecords(ctx, {
|
|
8707
|
+
paperclipIssueId: params.importedIssue.paperclipIssueId
|
|
8708
|
+
});
|
|
8709
|
+
const unlinkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8710
|
+
for (const link of existingLinks) {
|
|
8711
|
+
if ((!link.data.companyId || link.data.companyId === companyId) && link.data.githubIssueUrl !== nextGitHubIssueUrl) {
|
|
8712
|
+
await tombstoneGitHubIssueLinkRecord(ctx, link, unlinkedAt);
|
|
8713
|
+
}
|
|
8714
|
+
}
|
|
8715
|
+
await upsertGitHubIssueLinkRecord(
|
|
8716
|
+
ctx,
|
|
8717
|
+
params.targetMapping,
|
|
8718
|
+
params.importedIssue.paperclipIssueId,
|
|
8719
|
+
params.githubIssue,
|
|
8720
|
+
[]
|
|
8721
|
+
);
|
|
8722
|
+
upsertImportedIssueRecord(
|
|
8723
|
+
params.nextRegistry,
|
|
8724
|
+
buildImportedIssueRecord(
|
|
8725
|
+
params.targetMapping,
|
|
8726
|
+
params.githubIssue,
|
|
8727
|
+
params.importedIssue.paperclipIssueId,
|
|
8728
|
+
params.importedIssue.importedAt
|
|
8729
|
+
)
|
|
8730
|
+
);
|
|
8731
|
+
invalidateGitHubLinkCachesForTarget({
|
|
8732
|
+
companyId,
|
|
8733
|
+
projectId: params.sourceMapping.paperclipProjectId ?? paperclipIssue.projectId ?? void 0,
|
|
8734
|
+
repositoryUrl: params.sourceMapping.repositoryUrl
|
|
8735
|
+
});
|
|
8736
|
+
invalidateGitHubLinkCachesForTarget({
|
|
8737
|
+
companyId,
|
|
8738
|
+
projectId,
|
|
8739
|
+
repositoryUrl: params.targetMapping.repositoryUrl
|
|
8740
|
+
});
|
|
8741
|
+
return {
|
|
8742
|
+
updatedDescriptionsCount: transferPatchResult.descriptionUpdated ? 1 : 0
|
|
8743
|
+
};
|
|
8744
|
+
}
|
|
8745
|
+
function buildUnmappedTransferredIssueCancellationComment(params) {
|
|
8746
|
+
return `GitHub Sync updated the status from \`${formatPaperclipIssueStatus(params.previousStatus)}\` to \`${formatPaperclipIssueStatus(params.nextStatus)}\` because the linked GitHub issue was transferred to \`${formatRepositoryLabel(params.transferredRepository)}\`, which is not mapped to a Paperclip project. The GitHub link was removed so sync will stop updating this Paperclip issue.`;
|
|
8747
|
+
}
|
|
8748
|
+
async function cancelUnmappedTransferredGitHubIssue(ctx, params) {
|
|
8749
|
+
const companyId = normalizeCompanyId(params.mapping.companyId);
|
|
8750
|
+
if (!companyId) {
|
|
8751
|
+
return {
|
|
8752
|
+
updatedStatusesCount: 0
|
|
8753
|
+
};
|
|
8754
|
+
}
|
|
8755
|
+
const paperclipIssue = await ctx.issues.get(params.importedIssue.paperclipIssueId, companyId);
|
|
8756
|
+
if (!paperclipIssue) {
|
|
8757
|
+
return {
|
|
8758
|
+
updatedStatusesCount: 0
|
|
8759
|
+
};
|
|
8760
|
+
}
|
|
8761
|
+
await unlinkPaperclipIssueFromGitHub(ctx, {
|
|
8762
|
+
companyId,
|
|
8763
|
+
issueId: params.importedIssue.paperclipIssueId
|
|
8764
|
+
});
|
|
8765
|
+
const nextStatus = "cancelled";
|
|
8766
|
+
await updatePaperclipIssueState(ctx, {
|
|
8767
|
+
companyId,
|
|
8768
|
+
issueId: params.importedIssue.paperclipIssueId,
|
|
8769
|
+
currentStatus: paperclipIssue.status,
|
|
8770
|
+
syncContext: getPaperclipIssueSyncContext(paperclipIssue),
|
|
8771
|
+
nextStatus,
|
|
8772
|
+
transitionComment: buildUnmappedTransferredIssueCancellationComment({
|
|
8773
|
+
previousStatus: paperclipIssue.status,
|
|
8774
|
+
nextStatus,
|
|
8775
|
+
transferredRepository: params.transferredRepository
|
|
8776
|
+
}),
|
|
8777
|
+
paperclipApiBaseUrl: params.paperclipApiBaseUrl
|
|
8778
|
+
});
|
|
8779
|
+
return {
|
|
8780
|
+
updatedStatusesCount: paperclipIssue.status === nextStatus ? 0 : 1
|
|
8781
|
+
};
|
|
8782
|
+
}
|
|
8783
|
+
async function fetchTransferredGitHubIssueForImportedRecord(octokit, repository, importedIssue) {
|
|
8784
|
+
if (importedIssue.githubIssueNumber === void 0) {
|
|
8785
|
+
return null;
|
|
8786
|
+
}
|
|
8787
|
+
const response = await octokit.rest.issues.get({
|
|
8788
|
+
owner: repository.owner,
|
|
8789
|
+
repo: repository.repo,
|
|
8790
|
+
issue_number: importedIssue.githubIssueNumber,
|
|
8791
|
+
headers: {
|
|
8792
|
+
accept: "application/vnd.github+json",
|
|
8793
|
+
"X-GitHub-Api-Version": GITHUB_API_VERSION
|
|
8794
|
+
}
|
|
8795
|
+
});
|
|
8796
|
+
const rawIssue = response.data;
|
|
8797
|
+
if (rawIssue.pull_request) {
|
|
8798
|
+
return null;
|
|
8799
|
+
}
|
|
8800
|
+
const githubIssue = normalizeGitHubIssueRecord(rawIssue);
|
|
8801
|
+
const transferredIssueReference = getGitHubIssueRepositoryReference(githubIssue);
|
|
8802
|
+
const transferredRepository = transferredIssueReference ? parseRepositoryReference(transferredIssueReference.repositoryUrl) : void 0;
|
|
8803
|
+
if (!transferredRepository || areRepositoriesEqual(repository, transferredRepository)) {
|
|
8804
|
+
return null;
|
|
8805
|
+
}
|
|
8806
|
+
return {
|
|
8807
|
+
githubIssue,
|
|
8808
|
+
transferredRepository
|
|
8809
|
+
};
|
|
8810
|
+
}
|
|
8811
|
+
async function reconcileTransferredImportedIssues(ctx, params) {
|
|
8812
|
+
let updatedStatusesCount = 0;
|
|
8813
|
+
let updatedDescriptionsCount = 0;
|
|
8814
|
+
for (const importedIssue of params.importedIssues) {
|
|
8815
|
+
if (params.allIssuesById.has(importedIssue.githubIssueId)) {
|
|
8816
|
+
continue;
|
|
8817
|
+
}
|
|
8818
|
+
try {
|
|
8819
|
+
updateSyncFailureContext(params.syncFailureContext, {
|
|
8820
|
+
phase: "evaluating_github_status",
|
|
8821
|
+
repositoryUrl: params.sourceRepository.url,
|
|
8822
|
+
githubIssueNumber: importedIssue.githubIssueNumber
|
|
8823
|
+
});
|
|
8824
|
+
const transfer = await fetchTransferredGitHubIssueForImportedRecord(
|
|
8825
|
+
params.octokit,
|
|
8826
|
+
params.sourceRepository,
|
|
8827
|
+
importedIssue
|
|
8828
|
+
);
|
|
8829
|
+
if (!transfer) {
|
|
8830
|
+
continue;
|
|
8831
|
+
}
|
|
8832
|
+
const targetMapping = findTransferredIssueTargetMapping(params.allMappings, {
|
|
8833
|
+
companyId: params.sourceMapping.companyId,
|
|
8834
|
+
repositoryUrl: transfer.transferredRepository.url
|
|
8835
|
+
});
|
|
8836
|
+
if (targetMapping) {
|
|
8837
|
+
const moveResult = await moveImportedIssueToTransferredGitHubMapping(ctx, {
|
|
8838
|
+
sourceMapping: params.sourceMapping,
|
|
8839
|
+
targetMapping,
|
|
8840
|
+
importedIssue,
|
|
8841
|
+
githubIssue: transfer.githubIssue,
|
|
8842
|
+
nextRegistry: params.nextRegistry,
|
|
8843
|
+
paperclipApiBaseUrl: params.paperclipApiBaseUrl
|
|
8844
|
+
});
|
|
8845
|
+
updatedDescriptionsCount += moveResult.updatedDescriptionsCount;
|
|
8846
|
+
continue;
|
|
8847
|
+
}
|
|
8848
|
+
const cancelResult = await cancelUnmappedTransferredGitHubIssue(ctx, {
|
|
8849
|
+
mapping: params.sourceMapping,
|
|
8850
|
+
importedIssue,
|
|
8851
|
+
transferredRepository: transfer.transferredRepository,
|
|
8852
|
+
paperclipApiBaseUrl: params.paperclipApiBaseUrl
|
|
8853
|
+
});
|
|
8854
|
+
for (let index = params.nextRegistry.length - 1; index >= 0; index -= 1) {
|
|
8855
|
+
const entry = params.nextRegistry[index];
|
|
8856
|
+
if (entry?.paperclipIssueId === importedIssue.paperclipIssueId && (!entry.companyId || entry.companyId === params.sourceMapping.companyId)) {
|
|
8857
|
+
params.nextRegistry.splice(index, 1);
|
|
8858
|
+
}
|
|
8859
|
+
}
|
|
8860
|
+
updatedStatusesCount += cancelResult.updatedStatusesCount;
|
|
8861
|
+
} catch (error) {
|
|
8862
|
+
if (isGitHubRateLimitError(error)) {
|
|
8863
|
+
throw error;
|
|
8864
|
+
}
|
|
8865
|
+
recordRecoverableSyncFailure(ctx, params.failures, error, params.syncFailureContext);
|
|
8866
|
+
continue;
|
|
8867
|
+
}
|
|
8868
|
+
}
|
|
8869
|
+
return {
|
|
8870
|
+
updatedStatusesCount,
|
|
8871
|
+
updatedDescriptionsCount
|
|
8872
|
+
};
|
|
8873
|
+
}
|
|
8453
8874
|
async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mapping, advancedSettings, allIssuesById, importedIssues, createdIssueIds, availableLabels, paperclipApiBaseUrl, linkedPullRequestsByIssueNumber, issueStatusSnapshotCache, pullRequestStatusCache, repositoryMaintainerCache, syncFailureContext, failures, assertNotCancelled, onGitHubIssueClosed, onProgress) {
|
|
8454
8875
|
if (!mapping.companyId || !ctx.issues || typeof ctx.issues.get !== "function" || typeof ctx.issues.update !== "function") {
|
|
8455
8876
|
return {
|
|
@@ -8624,7 +9045,7 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
|
|
|
8624
9045
|
paperclipIssueSyncContext,
|
|
8625
9046
|
advancedSettings
|
|
8626
9047
|
);
|
|
8627
|
-
|
|
9048
|
+
let nextStatus = resolvePaperclipIssueStatus({
|
|
8628
9049
|
currentStatus: paperclipIssue.status,
|
|
8629
9050
|
snapshot,
|
|
8630
9051
|
hasTrustedNewComment,
|
|
@@ -8633,12 +9054,21 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
|
|
|
8633
9054
|
maintainerAuthoredImportedIssue,
|
|
8634
9055
|
hasExecutorHandoffTarget: Boolean(executorTransitionAssignee)
|
|
8635
9056
|
});
|
|
9057
|
+
if (paperclipIssue.status === "blocked" && nextStatus !== "blocked" && await hasUnresolvedPaperclipIssueBlocker(ctx, paperclipIssue, mapping.companyId)) {
|
|
9058
|
+
nextStatus = "blocked";
|
|
9059
|
+
}
|
|
8636
9060
|
const shouldPreserveMaintainerWaitRouting = isHealthyMaintainerWaitTransition({
|
|
8637
9061
|
currentStatus: paperclipIssue.status,
|
|
8638
9062
|
nextStatus,
|
|
8639
9063
|
syncContext: paperclipIssueSyncContext
|
|
8640
9064
|
});
|
|
8641
|
-
const
|
|
9065
|
+
const shouldPreserveImportedTriageRouting = shouldPreserveImportedTriageAssignee({
|
|
9066
|
+
currentStatus: paperclipIssue.status,
|
|
9067
|
+
nextStatus,
|
|
9068
|
+
wasImportedThisRun,
|
|
9069
|
+
maintainerAuthoredImportedIssue
|
|
9070
|
+
});
|
|
9071
|
+
const nextTransitionAssignee = shouldPreserveImportedTriageRouting ? null : resolveSyncTransitionAssignee({
|
|
8642
9072
|
currentStatus: paperclipIssue.status,
|
|
8643
9073
|
nextStatus,
|
|
8644
9074
|
syncContext: paperclipIssueSyncContext,
|
|
@@ -8646,7 +9076,7 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
|
|
|
8646
9076
|
});
|
|
8647
9077
|
const shouldClearTransitionAssignee = nextStatus === "in_review" && (nextTransitionAssignee === null || shouldPreserveMaintainerWaitRouting) && paperclipIssueSyncContext.assignee !== null;
|
|
8648
9078
|
const nextAssigneeChanged = nextTransitionAssignee ? !doesPaperclipIssueAssigneeMatch(paperclipIssueSyncContext.assignee, nextTransitionAssignee.principal) : false;
|
|
8649
|
-
const shouldWakeImportedAssignee = wasImportedThisRun && paperclipIssue.status === nextStatus
|
|
9079
|
+
const shouldWakeImportedAssignee = wasImportedThisRun && nextStatus === "todo" && (paperclipIssue.status === nextStatus || shouldPreserveImportedTriageRouting) && paperclipIssueSyncContext.assignee?.kind === "agent";
|
|
8650
9080
|
const shouldWakeTransitionAssignee = paperclipIssue.status !== nextStatus && nextTransitionAssignee?.principal.kind === "agent" && isActionablePaperclipIssueStatus(nextStatus) && (nextAssigneeChanged || paperclipIssue.status !== nextStatus);
|
|
8651
9081
|
importedIssue.githubIssueNumber = githubIssue.number;
|
|
8652
9082
|
importedIssue.lastSeenCommentCount = snapshot.commentCount;
|
|
@@ -8708,6 +9138,14 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
|
|
|
8708
9138
|
paperclipApiBaseUrl
|
|
8709
9139
|
});
|
|
8710
9140
|
updatedStatusesCount += 1;
|
|
9141
|
+
if (shouldWakeImportedAssignee) {
|
|
9142
|
+
queuedIssueWakeups.push({
|
|
9143
|
+
assigneeAgentId: paperclipIssueSyncContext.assignee?.kind === "agent" ? paperclipIssueSyncContext.assignee.id : null,
|
|
9144
|
+
paperclipIssueId: importedIssue.paperclipIssueId,
|
|
9145
|
+
reason: IMPORTED_ISSUE_WAKE_REASON,
|
|
9146
|
+
mutation: "import"
|
|
9147
|
+
});
|
|
9148
|
+
}
|
|
8711
9149
|
if (shouldWakeTransitionAssignee && nextTransitionAssignee?.principal.kind === "agent") {
|
|
8712
9150
|
queuedIssueWakeups.push({
|
|
8713
9151
|
assigneeAgentId: nextTransitionAssignee.principal.id,
|
|
@@ -8821,11 +9259,14 @@ async function synchronizePaperclipPullRequestIssueStatuses(ctx, octokit, mappin
|
|
|
8821
9259
|
paperclipIssueSyncContext,
|
|
8822
9260
|
advancedSettings
|
|
8823
9261
|
);
|
|
8824
|
-
|
|
9262
|
+
let nextStatus = resolvePaperclipPullRequestIssueStatus({
|
|
8825
9263
|
currentStatus: paperclipIssue.status,
|
|
8826
9264
|
pullRequest,
|
|
8827
9265
|
hasExecutorHandoffTarget: Boolean(executorTransitionAssignee)
|
|
8828
9266
|
});
|
|
9267
|
+
if (paperclipIssue.status === "blocked" && nextStatus !== "blocked" && await hasUnresolvedPaperclipIssueBlocker(ctx, paperclipIssue, mapping.companyId)) {
|
|
9268
|
+
nextStatus = "blocked";
|
|
9269
|
+
}
|
|
8829
9270
|
const shouldPreserveMaintainerWaitRouting = isHealthyMaintainerWaitTransition({
|
|
8830
9271
|
currentStatus: paperclipIssue.status,
|
|
8831
9272
|
nextStatus,
|
|
@@ -12723,6 +13164,22 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
12723
13164
|
await persistRunningProgress(issueIndex === issues.length - 1);
|
|
12724
13165
|
}
|
|
12725
13166
|
}
|
|
13167
|
+
const transferReconciliationResult = await reconcileTransferredImportedIssues(ctx, {
|
|
13168
|
+
octokit,
|
|
13169
|
+
sourceMapping: mapping,
|
|
13170
|
+
sourceRepository: repository,
|
|
13171
|
+
allMappings: settings.mappings,
|
|
13172
|
+
importedIssues: [...importRegistryByIssueId.values()].filter(
|
|
13173
|
+
(importedIssue) => doesImportedIssueMatchTarget(importedIssue, options.target)
|
|
13174
|
+
),
|
|
13175
|
+
allIssuesById,
|
|
13176
|
+
nextRegistry,
|
|
13177
|
+
paperclipApiBaseUrl,
|
|
13178
|
+
syncFailureContext: failureContext,
|
|
13179
|
+
failures: recoverableFailures
|
|
13180
|
+
});
|
|
13181
|
+
updatedStatusesCount += transferReconciliationResult.updatedStatusesCount;
|
|
13182
|
+
updatedDescriptionsCount += transferReconciliationResult.updatedDescriptionsCount;
|
|
12726
13183
|
const importedIssuesForSynchronization = [...importRegistryByIssueId.values()].filter(
|
|
12727
13184
|
(importedIssue) => allIssuesById.has(importedIssue.githubIssueId) && doesImportedIssueMatchTarget(importedIssue, options.target)
|
|
12728
13185
|
);
|
|
@@ -14008,6 +14465,18 @@ var plugin = definePlugin({
|
|
|
14008
14465
|
requireUnlinked: true
|
|
14009
14466
|
});
|
|
14010
14467
|
});
|
|
14468
|
+
ctx.actions.register("issue.unlinkGitHubItem", async (input) => {
|
|
14469
|
+
const record = input && typeof input === "object" ? input : {};
|
|
14470
|
+
const companyId = normalizeCompanyId(record.companyId);
|
|
14471
|
+
const issueId = normalizeOptionalString2(record.issueId);
|
|
14472
|
+
if (!companyId || !issueId) {
|
|
14473
|
+
throw new Error("companyId and issueId are required.");
|
|
14474
|
+
}
|
|
14475
|
+
return unlinkPaperclipIssueFromGitHub(ctx, {
|
|
14476
|
+
companyId,
|
|
14477
|
+
issueId
|
|
14478
|
+
});
|
|
14479
|
+
});
|
|
14011
14480
|
ctx.actions.register("settings.saveRegistration", async (input) => {
|
|
14012
14481
|
const previous = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
|
|
14013
14482
|
const config = await getResolvedConfig(ctx);
|