paperclip-github-plugin 0.8.1 → 0.8.3
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 +6 -1
- package/dist/manifest.js +1 -1
- package/dist/ui/index.js +50 -1
- package/dist/ui/index.js.map +2 -2
- package/dist/worker.js +469 -3
- 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 = {
|
|
@@ -5201,6 +5202,9 @@ function isHealthyMaintainerWaitTransition(params) {
|
|
|
5201
5202
|
const { currentStatus, nextStatus, syncContext } = params;
|
|
5202
5203
|
return nextStatus === "in_review" && (currentStatus === "done" || currentStatus === "in_review") && syncContext.executionState === null && syncContext.executionPolicy !== null;
|
|
5203
5204
|
}
|
|
5205
|
+
function shouldPreserveImportedTriageAssignee(params) {
|
|
5206
|
+
return params.wasImportedThisRun && params.maintainerAuthoredImportedIssue === true && params.currentStatus === "backlog" && params.nextStatus === "todo";
|
|
5207
|
+
}
|
|
5204
5208
|
function doesPaperclipIssueAssigneeMatch(currentAssignee, nextAssignee) {
|
|
5205
5209
|
return isSamePaperclipIssueAssigneePrincipal(currentAssignee, nextAssignee);
|
|
5206
5210
|
}
|
|
@@ -6365,6 +6369,18 @@ function extractImportedGitHubIssueUrlFromDescription(description) {
|
|
|
6365
6369
|
}
|
|
6366
6370
|
return normalizeGitHubIssueHtmlUrl(legacyMatch[1]);
|
|
6367
6371
|
}
|
|
6372
|
+
function removeGitHubIssueLinkMetadataFromDescription(description) {
|
|
6373
|
+
if (typeof description !== "string") {
|
|
6374
|
+
return void 0;
|
|
6375
|
+
}
|
|
6376
|
+
let next = description;
|
|
6377
|
+
const hiddenMarkerPattern = getHiddenGitHubImportMarkerPattern();
|
|
6378
|
+
while (hiddenMarkerPattern.test(next)) {
|
|
6379
|
+
next = next.replace(hiddenMarkerPattern, "");
|
|
6380
|
+
}
|
|
6381
|
+
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();
|
|
6382
|
+
return next === description ? void 0 : next;
|
|
6383
|
+
}
|
|
6368
6384
|
function compareImportedPaperclipIssueCreatedAt(left, right) {
|
|
6369
6385
|
const leftTime = Date.parse(String(left.createdAt ?? ""));
|
|
6370
6386
|
const rightTime = Date.parse(String(right.createdAt ?? ""));
|
|
@@ -6693,6 +6709,7 @@ async function listGitHubIssueLinkRecords(ctx, query = {}) {
|
|
|
6693
6709
|
}
|
|
6694
6710
|
records.push({
|
|
6695
6711
|
paperclipIssueId: entry.scopeId,
|
|
6712
|
+
...typeof entry.externalId === "string" && entry.externalId.trim() ? { externalId: entry.externalId.trim() } : {},
|
|
6696
6713
|
...typeof entry.createdAt === "string" ? { createdAt: entry.createdAt } : {},
|
|
6697
6714
|
...typeof entry.updatedAt === "string" ? { updatedAt: entry.updatedAt } : {},
|
|
6698
6715
|
...typeof entry.title === "string" && entry.title.trim() ? { title: entry.title.trim() } : {},
|
|
@@ -6739,6 +6756,7 @@ async function listGitHubPullRequestLinkRecords(ctx, query = {}) {
|
|
|
6739
6756
|
}
|
|
6740
6757
|
records.push({
|
|
6741
6758
|
paperclipIssueId: entry.scopeId,
|
|
6759
|
+
...typeof entry.externalId === "string" && entry.externalId.trim() ? { externalId: entry.externalId.trim() } : {},
|
|
6742
6760
|
...typeof entry.createdAt === "string" ? { createdAt: entry.createdAt } : {},
|
|
6743
6761
|
...typeof entry.updatedAt === "string" ? { updatedAt: entry.updatedAt } : {},
|
|
6744
6762
|
...typeof entry.title === "string" && entry.title.trim() ? { title: entry.title.trim() } : {},
|
|
@@ -7147,6 +7165,134 @@ async function linkPaperclipIssueToGitHubPullRequest(ctx, params) {
|
|
|
7147
7165
|
githubPullRequestState: pullRequestState
|
|
7148
7166
|
};
|
|
7149
7167
|
}
|
|
7168
|
+
function invalidateGitHubLinkCachesForTarget(params) {
|
|
7169
|
+
if (!params.companyId || !params.projectId || !params.repositoryUrl) {
|
|
7170
|
+
return;
|
|
7171
|
+
}
|
|
7172
|
+
const repository = parseRepositoryReference(params.repositoryUrl);
|
|
7173
|
+
if (!repository) {
|
|
7174
|
+
return;
|
|
7175
|
+
}
|
|
7176
|
+
invalidateProjectPullRequestCaches({
|
|
7177
|
+
companyId: params.companyId,
|
|
7178
|
+
projectId: params.projectId,
|
|
7179
|
+
repository
|
|
7180
|
+
});
|
|
7181
|
+
}
|
|
7182
|
+
async function tombstoneGitHubIssueLinkRecord(ctx, record, unlinkedAt) {
|
|
7183
|
+
await ctx.entities.upsert({
|
|
7184
|
+
entityType: ISSUE_LINK_ENTITY_TYPE,
|
|
7185
|
+
scopeKind: "issue",
|
|
7186
|
+
scopeId: record.paperclipIssueId,
|
|
7187
|
+
externalId: record.externalId ?? record.data.githubIssueUrl,
|
|
7188
|
+
title: record.title ?? `GitHub issue #${record.data.githubIssueNumber}`,
|
|
7189
|
+
status: "unlinked",
|
|
7190
|
+
data: {
|
|
7191
|
+
kind: "issue",
|
|
7192
|
+
paperclipIssueId: record.paperclipIssueId,
|
|
7193
|
+
repositoryUrl: record.data.repositoryUrl,
|
|
7194
|
+
githubIssueNumber: record.data.githubIssueNumber,
|
|
7195
|
+
githubIssueUrl: record.data.githubIssueUrl,
|
|
7196
|
+
...record.data.companyId ? { companyId: record.data.companyId } : {},
|
|
7197
|
+
...record.data.paperclipProjectId ? { paperclipProjectId: record.data.paperclipProjectId } : {},
|
|
7198
|
+
unlinkedAt
|
|
7199
|
+
}
|
|
7200
|
+
});
|
|
7201
|
+
}
|
|
7202
|
+
async function tombstoneGitHubPullRequestLinkRecord(ctx, record, unlinkedAt) {
|
|
7203
|
+
await ctx.entities.upsert({
|
|
7204
|
+
entityType: PULL_REQUEST_LINK_ENTITY_TYPE,
|
|
7205
|
+
scopeKind: "issue",
|
|
7206
|
+
scopeId: record.paperclipIssueId,
|
|
7207
|
+
externalId: record.externalId ?? record.data.githubPullRequestUrl,
|
|
7208
|
+
title: record.title ?? `GitHub pull request #${record.data.githubPullRequestNumber}`,
|
|
7209
|
+
status: "unlinked",
|
|
7210
|
+
data: {
|
|
7211
|
+
kind: "pull_request",
|
|
7212
|
+
paperclipIssueId: record.paperclipIssueId,
|
|
7213
|
+
repositoryUrl: record.data.repositoryUrl,
|
|
7214
|
+
githubPullRequestNumber: record.data.githubPullRequestNumber,
|
|
7215
|
+
githubPullRequestUrl: record.data.githubPullRequestUrl,
|
|
7216
|
+
...record.data.companyId ? { companyId: record.data.companyId } : {},
|
|
7217
|
+
...record.data.paperclipProjectId ? { paperclipProjectId: record.data.paperclipProjectId } : {},
|
|
7218
|
+
unlinkedAt
|
|
7219
|
+
}
|
|
7220
|
+
});
|
|
7221
|
+
}
|
|
7222
|
+
async function unlinkPaperclipIssueFromGitHub(ctx, params) {
|
|
7223
|
+
const companyId = normalizeCompanyId(params.companyId);
|
|
7224
|
+
const issueId = normalizeOptionalString2(params.issueId);
|
|
7225
|
+
if (!companyId || !issueId) {
|
|
7226
|
+
throw new Error("companyId and issueId are required.");
|
|
7227
|
+
}
|
|
7228
|
+
const issue = await ctx.issues.get(issueId, companyId);
|
|
7229
|
+
if (!issue) {
|
|
7230
|
+
throw new Error("Paperclip issue was not found.");
|
|
7231
|
+
}
|
|
7232
|
+
const issueLinkRecords = (await listGitHubIssueLinkRecords(ctx, {
|
|
7233
|
+
paperclipIssueId: issueId
|
|
7234
|
+
})).filter((record) => !record.data.companyId || record.data.companyId === companyId);
|
|
7235
|
+
const pullRequestLinkRecords = (await listGitHubPullRequestLinkRecords(ctx, {
|
|
7236
|
+
paperclipIssueId: issueId
|
|
7237
|
+
})).filter((record) => !record.data.companyId || record.data.companyId === companyId);
|
|
7238
|
+
const importRegistry = normalizeImportRegistry(await ctx.state.get(IMPORT_REGISTRY_SCOPE));
|
|
7239
|
+
const removedImportRegistryEntries = importRegistry.filter(
|
|
7240
|
+
(entry) => entry.paperclipIssueId === issueId && (!entry.companyId || entry.companyId === companyId)
|
|
7241
|
+
);
|
|
7242
|
+
const nextImportRegistry = importRegistry.filter(
|
|
7243
|
+
(entry) => !(entry.paperclipIssueId === issueId && (!entry.companyId || entry.companyId === companyId))
|
|
7244
|
+
);
|
|
7245
|
+
const unlinkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7246
|
+
for (const record of issueLinkRecords) {
|
|
7247
|
+
await tombstoneGitHubIssueLinkRecord(ctx, record, unlinkedAt);
|
|
7248
|
+
invalidateGitHubLinkCachesForTarget({
|
|
7249
|
+
companyId: record.data.companyId ?? companyId,
|
|
7250
|
+
projectId: record.data.paperclipProjectId ?? issue.projectId ?? void 0,
|
|
7251
|
+
repositoryUrl: record.data.repositoryUrl
|
|
7252
|
+
});
|
|
7253
|
+
}
|
|
7254
|
+
for (const record of pullRequestLinkRecords) {
|
|
7255
|
+
await tombstoneGitHubPullRequestLinkRecord(ctx, record, unlinkedAt);
|
|
7256
|
+
invalidateGitHubLinkCachesForTarget({
|
|
7257
|
+
companyId: record.data.companyId ?? companyId,
|
|
7258
|
+
projectId: record.data.paperclipProjectId ?? issue.projectId ?? void 0,
|
|
7259
|
+
repositoryUrl: record.data.repositoryUrl
|
|
7260
|
+
});
|
|
7261
|
+
}
|
|
7262
|
+
for (const entry of removedImportRegistryEntries) {
|
|
7263
|
+
invalidateGitHubLinkCachesForTarget({
|
|
7264
|
+
companyId: entry.companyId ?? companyId,
|
|
7265
|
+
projectId: entry.paperclipProjectId ?? issue.projectId ?? void 0,
|
|
7266
|
+
repositoryUrl: entry.repositoryUrl
|
|
7267
|
+
});
|
|
7268
|
+
}
|
|
7269
|
+
if (removedImportRegistryEntries.length > 0) {
|
|
7270
|
+
await ctx.state.set(IMPORT_REGISTRY_SCOPE, nextImportRegistry);
|
|
7271
|
+
}
|
|
7272
|
+
const shouldClearGitHubOrigin = issue.originKind === GITHUB_ISSUE_ORIGIN_KIND || issue.originKind === GITHUB_PULL_REQUEST_ORIGIN_KIND;
|
|
7273
|
+
const nextDescription = removeGitHubIssueLinkMetadataFromDescription(issue.description);
|
|
7274
|
+
const issuePatch = {};
|
|
7275
|
+
if (shouldClearGitHubOrigin) {
|
|
7276
|
+
issuePatch.originKind = GITHUB_SYNC_BASE_ORIGIN_KIND;
|
|
7277
|
+
issuePatch.originId = null;
|
|
7278
|
+
issuePatch.originRunId = null;
|
|
7279
|
+
}
|
|
7280
|
+
if (nextDescription !== void 0) {
|
|
7281
|
+
issuePatch.description = nextDescription;
|
|
7282
|
+
}
|
|
7283
|
+
if (Object.keys(issuePatch).length > 0) {
|
|
7284
|
+
await ctx.issues.update(issueId, issuePatch, companyId);
|
|
7285
|
+
}
|
|
7286
|
+
return {
|
|
7287
|
+
paperclipIssueId: issueId,
|
|
7288
|
+
unlinked: issueLinkRecords.length > 0 || pullRequestLinkRecords.length > 0 || removedImportRegistryEntries.length > 0 || shouldClearGitHubOrigin || nextDescription !== void 0,
|
|
7289
|
+
unlinkedIssueLinksCount: issueLinkRecords.length,
|
|
7290
|
+
unlinkedPullRequestLinksCount: pullRequestLinkRecords.length,
|
|
7291
|
+
removedImportRegistryEntriesCount: removedImportRegistryEntries.length,
|
|
7292
|
+
clearedIssueOrigin: shouldClearGitHubOrigin,
|
|
7293
|
+
removedDescriptionMetadata: nextDescription !== void 0
|
|
7294
|
+
};
|
|
7295
|
+
}
|
|
7150
7296
|
async function upsertStatusTransitionCommentAnnotation(ctx, params) {
|
|
7151
7297
|
const { issueId, commentId, annotation } = params;
|
|
7152
7298
|
await ctx.entities.upsert({
|
|
@@ -8054,6 +8200,8 @@ async function updatePaperclipIssueState(ctx, params) {
|
|
|
8054
8200
|
paperclipApiBaseUrl
|
|
8055
8201
|
} = params;
|
|
8056
8202
|
const trimmedTransitionComment = transitionComment.trim();
|
|
8203
|
+
const statusWillChange = currentStatus !== nextStatus;
|
|
8204
|
+
let createdTransitionComment = null;
|
|
8057
8205
|
let issueUpdated = false;
|
|
8058
8206
|
const syncExecutionStatePatch = buildSyncFallbackExecutionStatePatch({
|
|
8059
8207
|
currentStatus,
|
|
@@ -8078,6 +8226,15 @@ async function updatePaperclipIssueState(ctx, params) {
|
|
|
8078
8226
|
issuePatch.assigneeAgentId = null;
|
|
8079
8227
|
issuePatch.assigneeUserId = null;
|
|
8080
8228
|
}
|
|
8229
|
+
if (statusWillChange) {
|
|
8230
|
+
if (!trimmedTransitionComment) {
|
|
8231
|
+
throw new Error("GitHub Sync refused to update a Paperclip issue status without an explanatory transition comment.");
|
|
8232
|
+
}
|
|
8233
|
+
if (typeof ctx.issues.createComment !== "function") {
|
|
8234
|
+
throw new Error("This Paperclip runtime does not expose issue comment creation, so GitHub Sync refused to update a Paperclip issue status without an explanatory comment.");
|
|
8235
|
+
}
|
|
8236
|
+
createdTransitionComment = await ctx.issues.createComment(issueId, trimmedTransitionComment, companyId);
|
|
8237
|
+
}
|
|
8081
8238
|
if (paperclipApiBaseUrl) {
|
|
8082
8239
|
try {
|
|
8083
8240
|
const response = await fetchPaperclipApi(
|
|
@@ -8139,7 +8296,19 @@ async function updatePaperclipIssueState(ctx, params) {
|
|
|
8139
8296
|
}
|
|
8140
8297
|
await ctx.issues.update(issueId, sdkIssuePatch, companyId);
|
|
8141
8298
|
}
|
|
8142
|
-
if (
|
|
8299
|
+
if (createdTransitionComment) {
|
|
8300
|
+
if (transitionCommentAnnotation) {
|
|
8301
|
+
await upsertStatusTransitionCommentAnnotation(ctx, {
|
|
8302
|
+
issueId,
|
|
8303
|
+
commentId: createdTransitionComment.id,
|
|
8304
|
+
annotation: {
|
|
8305
|
+
...transitionCommentAnnotation,
|
|
8306
|
+
companyId,
|
|
8307
|
+
paperclipIssueId: issueId
|
|
8308
|
+
}
|
|
8309
|
+
});
|
|
8310
|
+
}
|
|
8311
|
+
} else if (trimmedTransitionComment && typeof ctx.issues.createComment === "function") {
|
|
8143
8312
|
const createdComment = await ctx.issues.createComment(issueId, trimmedTransitionComment, companyId);
|
|
8144
8313
|
if (transitionCommentAnnotation) {
|
|
8145
8314
|
await upsertStatusTransitionCommentAnnotation(ctx, {
|
|
@@ -8427,6 +8596,261 @@ async function ensurePaperclipIssueImported(ctx, mapping, advancedSettings, issu
|
|
|
8427
8596
|
}
|
|
8428
8597
|
return createdIssue.id;
|
|
8429
8598
|
}
|
|
8599
|
+
function getGitHubIssueRepositoryReference(issue) {
|
|
8600
|
+
return parseGitHubIssueHtmlUrl(issue.htmlUrl);
|
|
8601
|
+
}
|
|
8602
|
+
function findTransferredIssueTargetMapping(mappings, params) {
|
|
8603
|
+
const companyId = normalizeCompanyId(params.companyId);
|
|
8604
|
+
const repository = parseRepositoryReference(params.repositoryUrl);
|
|
8605
|
+
if (!companyId || !repository) {
|
|
8606
|
+
return void 0;
|
|
8607
|
+
}
|
|
8608
|
+
return getSyncableMappings(mappings).find((mapping) => {
|
|
8609
|
+
if (normalizeCompanyId(mapping.companyId) !== companyId) {
|
|
8610
|
+
return false;
|
|
8611
|
+
}
|
|
8612
|
+
const mappingRepository = parseRepositoryReference(mapping.repositoryUrl);
|
|
8613
|
+
return Boolean(mappingRepository && areRepositoriesEqual(mappingRepository, repository));
|
|
8614
|
+
});
|
|
8615
|
+
}
|
|
8616
|
+
async function patchPaperclipIssueForGitHubIssueTransfer(ctx, params) {
|
|
8617
|
+
const nextDescription = buildPaperclipIssueDescription(params.githubIssue, []);
|
|
8618
|
+
const nextOriginId = normalizeGitHubIssueHtmlUrl(params.githubIssue.htmlUrl) ?? params.githubIssue.htmlUrl;
|
|
8619
|
+
const descriptionUpdated = normalizeIssueDescriptionValue(params.currentDescription) !== nextDescription;
|
|
8620
|
+
const patch = {
|
|
8621
|
+
projectId: params.projectId,
|
|
8622
|
+
originKind: GITHUB_ISSUE_ORIGIN_KIND,
|
|
8623
|
+
originId: nextOriginId,
|
|
8624
|
+
...descriptionUpdated ? { description: nextDescription } : {}
|
|
8625
|
+
};
|
|
8626
|
+
let issueUpdated = false;
|
|
8627
|
+
if (params.paperclipApiBaseUrl) {
|
|
8628
|
+
try {
|
|
8629
|
+
const response = await fetchPaperclipApi(
|
|
8630
|
+
getPaperclipIssueEndpoint(params.paperclipApiBaseUrl, params.issueId),
|
|
8631
|
+
{
|
|
8632
|
+
method: "PATCH",
|
|
8633
|
+
headers: {
|
|
8634
|
+
accept: "application/json",
|
|
8635
|
+
"content-type": "application/json"
|
|
8636
|
+
},
|
|
8637
|
+
body: JSON.stringify(patch)
|
|
8638
|
+
},
|
|
8639
|
+
{
|
|
8640
|
+
companyId: params.companyId
|
|
8641
|
+
}
|
|
8642
|
+
);
|
|
8643
|
+
const payloadResult = await readPaperclipApiJsonResponse(response, {
|
|
8644
|
+
operationLabel: "issue update",
|
|
8645
|
+
bodyRequired: false
|
|
8646
|
+
});
|
|
8647
|
+
issueUpdated = !payloadResult.failure;
|
|
8648
|
+
} catch {
|
|
8649
|
+
issueUpdated = false;
|
|
8650
|
+
}
|
|
8651
|
+
}
|
|
8652
|
+
if (!issueUpdated) {
|
|
8653
|
+
await ctx.issues.update(
|
|
8654
|
+
params.issueId,
|
|
8655
|
+
patch,
|
|
8656
|
+
params.companyId
|
|
8657
|
+
);
|
|
8658
|
+
}
|
|
8659
|
+
return {
|
|
8660
|
+
descriptionUpdated
|
|
8661
|
+
};
|
|
8662
|
+
}
|
|
8663
|
+
async function moveImportedIssueToTransferredGitHubMapping(ctx, params) {
|
|
8664
|
+
const companyId = normalizeCompanyId(params.targetMapping.companyId);
|
|
8665
|
+
const projectId = normalizeOptionalString2(params.targetMapping.paperclipProjectId);
|
|
8666
|
+
if (!companyId || !projectId) {
|
|
8667
|
+
return {
|
|
8668
|
+
updatedDescriptionsCount: 0
|
|
8669
|
+
};
|
|
8670
|
+
}
|
|
8671
|
+
const paperclipIssue = await ctx.issues.get(params.importedIssue.paperclipIssueId, companyId);
|
|
8672
|
+
if (!paperclipIssue) {
|
|
8673
|
+
return {
|
|
8674
|
+
updatedDescriptionsCount: 0
|
|
8675
|
+
};
|
|
8676
|
+
}
|
|
8677
|
+
const transferPatchResult = await patchPaperclipIssueForGitHubIssueTransfer(ctx, {
|
|
8678
|
+
companyId,
|
|
8679
|
+
issueId: params.importedIssue.paperclipIssueId,
|
|
8680
|
+
projectId,
|
|
8681
|
+
githubIssue: params.githubIssue,
|
|
8682
|
+
currentDescription: paperclipIssue.description,
|
|
8683
|
+
paperclipApiBaseUrl: params.paperclipApiBaseUrl
|
|
8684
|
+
});
|
|
8685
|
+
const nextGitHubIssueUrl = normalizeGitHubIssueHtmlUrl(params.githubIssue.htmlUrl) ?? params.githubIssue.htmlUrl;
|
|
8686
|
+
const existingLinks = await listGitHubIssueLinkRecords(ctx, {
|
|
8687
|
+
paperclipIssueId: params.importedIssue.paperclipIssueId
|
|
8688
|
+
});
|
|
8689
|
+
const unlinkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8690
|
+
for (const link of existingLinks) {
|
|
8691
|
+
if ((!link.data.companyId || link.data.companyId === companyId) && link.data.githubIssueUrl !== nextGitHubIssueUrl) {
|
|
8692
|
+
await tombstoneGitHubIssueLinkRecord(ctx, link, unlinkedAt);
|
|
8693
|
+
}
|
|
8694
|
+
}
|
|
8695
|
+
await upsertGitHubIssueLinkRecord(
|
|
8696
|
+
ctx,
|
|
8697
|
+
params.targetMapping,
|
|
8698
|
+
params.importedIssue.paperclipIssueId,
|
|
8699
|
+
params.githubIssue,
|
|
8700
|
+
[]
|
|
8701
|
+
);
|
|
8702
|
+
upsertImportedIssueRecord(
|
|
8703
|
+
params.nextRegistry,
|
|
8704
|
+
buildImportedIssueRecord(
|
|
8705
|
+
params.targetMapping,
|
|
8706
|
+
params.githubIssue,
|
|
8707
|
+
params.importedIssue.paperclipIssueId,
|
|
8708
|
+
params.importedIssue.importedAt
|
|
8709
|
+
)
|
|
8710
|
+
);
|
|
8711
|
+
invalidateGitHubLinkCachesForTarget({
|
|
8712
|
+
companyId,
|
|
8713
|
+
projectId: params.sourceMapping.paperclipProjectId ?? paperclipIssue.projectId ?? void 0,
|
|
8714
|
+
repositoryUrl: params.sourceMapping.repositoryUrl
|
|
8715
|
+
});
|
|
8716
|
+
invalidateGitHubLinkCachesForTarget({
|
|
8717
|
+
companyId,
|
|
8718
|
+
projectId,
|
|
8719
|
+
repositoryUrl: params.targetMapping.repositoryUrl
|
|
8720
|
+
});
|
|
8721
|
+
return {
|
|
8722
|
+
updatedDescriptionsCount: transferPatchResult.descriptionUpdated ? 1 : 0
|
|
8723
|
+
};
|
|
8724
|
+
}
|
|
8725
|
+
function buildUnmappedTransferredIssueCancellationComment(params) {
|
|
8726
|
+
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.`;
|
|
8727
|
+
}
|
|
8728
|
+
async function cancelUnmappedTransferredGitHubIssue(ctx, params) {
|
|
8729
|
+
const companyId = normalizeCompanyId(params.mapping.companyId);
|
|
8730
|
+
if (!companyId) {
|
|
8731
|
+
return {
|
|
8732
|
+
updatedStatusesCount: 0
|
|
8733
|
+
};
|
|
8734
|
+
}
|
|
8735
|
+
const paperclipIssue = await ctx.issues.get(params.importedIssue.paperclipIssueId, companyId);
|
|
8736
|
+
if (!paperclipIssue) {
|
|
8737
|
+
return {
|
|
8738
|
+
updatedStatusesCount: 0
|
|
8739
|
+
};
|
|
8740
|
+
}
|
|
8741
|
+
await unlinkPaperclipIssueFromGitHub(ctx, {
|
|
8742
|
+
companyId,
|
|
8743
|
+
issueId: params.importedIssue.paperclipIssueId
|
|
8744
|
+
});
|
|
8745
|
+
const nextStatus = "cancelled";
|
|
8746
|
+
await updatePaperclipIssueState(ctx, {
|
|
8747
|
+
companyId,
|
|
8748
|
+
issueId: params.importedIssue.paperclipIssueId,
|
|
8749
|
+
currentStatus: paperclipIssue.status,
|
|
8750
|
+
syncContext: getPaperclipIssueSyncContext(paperclipIssue),
|
|
8751
|
+
nextStatus,
|
|
8752
|
+
transitionComment: buildUnmappedTransferredIssueCancellationComment({
|
|
8753
|
+
previousStatus: paperclipIssue.status,
|
|
8754
|
+
nextStatus,
|
|
8755
|
+
transferredRepository: params.transferredRepository
|
|
8756
|
+
}),
|
|
8757
|
+
paperclipApiBaseUrl: params.paperclipApiBaseUrl
|
|
8758
|
+
});
|
|
8759
|
+
return {
|
|
8760
|
+
updatedStatusesCount: paperclipIssue.status === nextStatus ? 0 : 1
|
|
8761
|
+
};
|
|
8762
|
+
}
|
|
8763
|
+
async function fetchTransferredGitHubIssueForImportedRecord(octokit, repository, importedIssue) {
|
|
8764
|
+
if (importedIssue.githubIssueNumber === void 0) {
|
|
8765
|
+
return null;
|
|
8766
|
+
}
|
|
8767
|
+
const response = await octokit.rest.issues.get({
|
|
8768
|
+
owner: repository.owner,
|
|
8769
|
+
repo: repository.repo,
|
|
8770
|
+
issue_number: importedIssue.githubIssueNumber,
|
|
8771
|
+
headers: {
|
|
8772
|
+
accept: "application/vnd.github+json",
|
|
8773
|
+
"X-GitHub-Api-Version": GITHUB_API_VERSION
|
|
8774
|
+
}
|
|
8775
|
+
});
|
|
8776
|
+
const rawIssue = response.data;
|
|
8777
|
+
if (rawIssue.pull_request) {
|
|
8778
|
+
return null;
|
|
8779
|
+
}
|
|
8780
|
+
const githubIssue = normalizeGitHubIssueRecord(rawIssue);
|
|
8781
|
+
const transferredIssueReference = getGitHubIssueRepositoryReference(githubIssue);
|
|
8782
|
+
const transferredRepository = transferredIssueReference ? parseRepositoryReference(transferredIssueReference.repositoryUrl) : void 0;
|
|
8783
|
+
if (!transferredRepository || areRepositoriesEqual(repository, transferredRepository)) {
|
|
8784
|
+
return null;
|
|
8785
|
+
}
|
|
8786
|
+
return {
|
|
8787
|
+
githubIssue,
|
|
8788
|
+
transferredRepository
|
|
8789
|
+
};
|
|
8790
|
+
}
|
|
8791
|
+
async function reconcileTransferredImportedIssues(ctx, params) {
|
|
8792
|
+
let updatedStatusesCount = 0;
|
|
8793
|
+
let updatedDescriptionsCount = 0;
|
|
8794
|
+
for (const importedIssue of params.importedIssues) {
|
|
8795
|
+
if (params.allIssuesById.has(importedIssue.githubIssueId)) {
|
|
8796
|
+
continue;
|
|
8797
|
+
}
|
|
8798
|
+
try {
|
|
8799
|
+
updateSyncFailureContext(params.syncFailureContext, {
|
|
8800
|
+
phase: "evaluating_github_status",
|
|
8801
|
+
repositoryUrl: params.sourceRepository.url,
|
|
8802
|
+
githubIssueNumber: importedIssue.githubIssueNumber
|
|
8803
|
+
});
|
|
8804
|
+
const transfer = await fetchTransferredGitHubIssueForImportedRecord(
|
|
8805
|
+
params.octokit,
|
|
8806
|
+
params.sourceRepository,
|
|
8807
|
+
importedIssue
|
|
8808
|
+
);
|
|
8809
|
+
if (!transfer) {
|
|
8810
|
+
continue;
|
|
8811
|
+
}
|
|
8812
|
+
const targetMapping = findTransferredIssueTargetMapping(params.allMappings, {
|
|
8813
|
+
companyId: params.sourceMapping.companyId,
|
|
8814
|
+
repositoryUrl: transfer.transferredRepository.url
|
|
8815
|
+
});
|
|
8816
|
+
if (targetMapping) {
|
|
8817
|
+
const moveResult = await moveImportedIssueToTransferredGitHubMapping(ctx, {
|
|
8818
|
+
sourceMapping: params.sourceMapping,
|
|
8819
|
+
targetMapping,
|
|
8820
|
+
importedIssue,
|
|
8821
|
+
githubIssue: transfer.githubIssue,
|
|
8822
|
+
nextRegistry: params.nextRegistry,
|
|
8823
|
+
paperclipApiBaseUrl: params.paperclipApiBaseUrl
|
|
8824
|
+
});
|
|
8825
|
+
updatedDescriptionsCount += moveResult.updatedDescriptionsCount;
|
|
8826
|
+
continue;
|
|
8827
|
+
}
|
|
8828
|
+
const cancelResult = await cancelUnmappedTransferredGitHubIssue(ctx, {
|
|
8829
|
+
mapping: params.sourceMapping,
|
|
8830
|
+
importedIssue,
|
|
8831
|
+
transferredRepository: transfer.transferredRepository,
|
|
8832
|
+
paperclipApiBaseUrl: params.paperclipApiBaseUrl
|
|
8833
|
+
});
|
|
8834
|
+
for (let index = params.nextRegistry.length - 1; index >= 0; index -= 1) {
|
|
8835
|
+
const entry = params.nextRegistry[index];
|
|
8836
|
+
if (entry?.paperclipIssueId === importedIssue.paperclipIssueId && (!entry.companyId || entry.companyId === params.sourceMapping.companyId)) {
|
|
8837
|
+
params.nextRegistry.splice(index, 1);
|
|
8838
|
+
}
|
|
8839
|
+
}
|
|
8840
|
+
updatedStatusesCount += cancelResult.updatedStatusesCount;
|
|
8841
|
+
} catch (error) {
|
|
8842
|
+
if (isGitHubRateLimitError(error)) {
|
|
8843
|
+
throw error;
|
|
8844
|
+
}
|
|
8845
|
+
recordRecoverableSyncFailure(ctx, params.failures, error, params.syncFailureContext);
|
|
8846
|
+
continue;
|
|
8847
|
+
}
|
|
8848
|
+
}
|
|
8849
|
+
return {
|
|
8850
|
+
updatedStatusesCount,
|
|
8851
|
+
updatedDescriptionsCount
|
|
8852
|
+
};
|
|
8853
|
+
}
|
|
8430
8854
|
async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mapping, advancedSettings, allIssuesById, importedIssues, createdIssueIds, availableLabels, paperclipApiBaseUrl, linkedPullRequestsByIssueNumber, issueStatusSnapshotCache, pullRequestStatusCache, repositoryMaintainerCache, syncFailureContext, failures, assertNotCancelled, onGitHubIssueClosed, onProgress) {
|
|
8431
8855
|
if (!mapping.companyId || !ctx.issues || typeof ctx.issues.get !== "function" || typeof ctx.issues.update !== "function") {
|
|
8432
8856
|
return {
|
|
@@ -8615,7 +9039,13 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
|
|
|
8615
9039
|
nextStatus,
|
|
8616
9040
|
syncContext: paperclipIssueSyncContext
|
|
8617
9041
|
});
|
|
8618
|
-
const
|
|
9042
|
+
const shouldPreserveImportedTriageRouting = shouldPreserveImportedTriageAssignee({
|
|
9043
|
+
currentStatus: paperclipIssue.status,
|
|
9044
|
+
nextStatus,
|
|
9045
|
+
wasImportedThisRun,
|
|
9046
|
+
maintainerAuthoredImportedIssue
|
|
9047
|
+
});
|
|
9048
|
+
const nextTransitionAssignee = shouldPreserveImportedTriageRouting ? null : resolveSyncTransitionAssignee({
|
|
8619
9049
|
currentStatus: paperclipIssue.status,
|
|
8620
9050
|
nextStatus,
|
|
8621
9051
|
syncContext: paperclipIssueSyncContext,
|
|
@@ -8623,7 +9053,7 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
|
|
|
8623
9053
|
});
|
|
8624
9054
|
const shouldClearTransitionAssignee = nextStatus === "in_review" && (nextTransitionAssignee === null || shouldPreserveMaintainerWaitRouting) && paperclipIssueSyncContext.assignee !== null;
|
|
8625
9055
|
const nextAssigneeChanged = nextTransitionAssignee ? !doesPaperclipIssueAssigneeMatch(paperclipIssueSyncContext.assignee, nextTransitionAssignee.principal) : false;
|
|
8626
|
-
const shouldWakeImportedAssignee = wasImportedThisRun && paperclipIssue.status === nextStatus
|
|
9056
|
+
const shouldWakeImportedAssignee = wasImportedThisRun && nextStatus === "todo" && (paperclipIssue.status === nextStatus || shouldPreserveImportedTriageRouting) && paperclipIssueSyncContext.assignee?.kind === "agent";
|
|
8627
9057
|
const shouldWakeTransitionAssignee = paperclipIssue.status !== nextStatus && nextTransitionAssignee?.principal.kind === "agent" && isActionablePaperclipIssueStatus(nextStatus) && (nextAssigneeChanged || paperclipIssue.status !== nextStatus);
|
|
8628
9058
|
importedIssue.githubIssueNumber = githubIssue.number;
|
|
8629
9059
|
importedIssue.lastSeenCommentCount = snapshot.commentCount;
|
|
@@ -8685,6 +9115,14 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
|
|
|
8685
9115
|
paperclipApiBaseUrl
|
|
8686
9116
|
});
|
|
8687
9117
|
updatedStatusesCount += 1;
|
|
9118
|
+
if (shouldWakeImportedAssignee) {
|
|
9119
|
+
queuedIssueWakeups.push({
|
|
9120
|
+
assigneeAgentId: paperclipIssueSyncContext.assignee?.kind === "agent" ? paperclipIssueSyncContext.assignee.id : null,
|
|
9121
|
+
paperclipIssueId: importedIssue.paperclipIssueId,
|
|
9122
|
+
reason: IMPORTED_ISSUE_WAKE_REASON,
|
|
9123
|
+
mutation: "import"
|
|
9124
|
+
});
|
|
9125
|
+
}
|
|
8688
9126
|
if (shouldWakeTransitionAssignee && nextTransitionAssignee?.principal.kind === "agent") {
|
|
8689
9127
|
queuedIssueWakeups.push({
|
|
8690
9128
|
assigneeAgentId: nextTransitionAssignee.principal.id,
|
|
@@ -12700,6 +13138,22 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
12700
13138
|
await persistRunningProgress(issueIndex === issues.length - 1);
|
|
12701
13139
|
}
|
|
12702
13140
|
}
|
|
13141
|
+
const transferReconciliationResult = await reconcileTransferredImportedIssues(ctx, {
|
|
13142
|
+
octokit,
|
|
13143
|
+
sourceMapping: mapping,
|
|
13144
|
+
sourceRepository: repository,
|
|
13145
|
+
allMappings: settings.mappings,
|
|
13146
|
+
importedIssues: [...importRegistryByIssueId.values()].filter(
|
|
13147
|
+
(importedIssue) => doesImportedIssueMatchTarget(importedIssue, options.target)
|
|
13148
|
+
),
|
|
13149
|
+
allIssuesById,
|
|
13150
|
+
nextRegistry,
|
|
13151
|
+
paperclipApiBaseUrl,
|
|
13152
|
+
syncFailureContext: failureContext,
|
|
13153
|
+
failures: recoverableFailures
|
|
13154
|
+
});
|
|
13155
|
+
updatedStatusesCount += transferReconciliationResult.updatedStatusesCount;
|
|
13156
|
+
updatedDescriptionsCount += transferReconciliationResult.updatedDescriptionsCount;
|
|
12703
13157
|
const importedIssuesForSynchronization = [...importRegistryByIssueId.values()].filter(
|
|
12704
13158
|
(importedIssue) => allIssuesById.has(importedIssue.githubIssueId) && doesImportedIssueMatchTarget(importedIssue, options.target)
|
|
12705
13159
|
);
|
|
@@ -13985,6 +14439,18 @@ var plugin = definePlugin({
|
|
|
13985
14439
|
requireUnlinked: true
|
|
13986
14440
|
});
|
|
13987
14441
|
});
|
|
14442
|
+
ctx.actions.register("issue.unlinkGitHubItem", async (input) => {
|
|
14443
|
+
const record = input && typeof input === "object" ? input : {};
|
|
14444
|
+
const companyId = normalizeCompanyId(record.companyId);
|
|
14445
|
+
const issueId = normalizeOptionalString2(record.issueId);
|
|
14446
|
+
if (!companyId || !issueId) {
|
|
14447
|
+
throw new Error("companyId and issueId are required.");
|
|
14448
|
+
}
|
|
14449
|
+
return unlinkPaperclipIssueFromGitHub(ctx, {
|
|
14450
|
+
companyId,
|
|
14451
|
+
issueId
|
|
14452
|
+
});
|
|
14453
|
+
});
|
|
13988
14454
|
ctx.actions.register("settings.saveRegistration", async (input) => {
|
|
13989
14455
|
const previous = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
|
|
13990
14456
|
const config = await getResolvedConfig(ctx);
|