paperclip-github-plugin 0.2.1 → 0.2.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 +177 -181
- package/dist/manifest.js +1 -1
- package/dist/ui/index.js +537 -83
- package/dist/ui/index.js.map +4 -4
- package/dist/worker.js +287 -37
- package/package.json +1 -1
package/dist/ui/index.js
CHANGED
|
@@ -67,6 +67,32 @@ function requiresPaperclipBoardAccess(value) {
|
|
|
67
67
|
return health?.deploymentMode?.toLowerCase() === "authenticated";
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// src/ui/assignees.ts
|
|
71
|
+
function normalizeCompanyAssigneeOptionsResponse(response) {
|
|
72
|
+
if (!Array.isArray(response)) {
|
|
73
|
+
throw new Error("Unexpected company agents response: expected an array.");
|
|
74
|
+
}
|
|
75
|
+
return response.map((entry) => {
|
|
76
|
+
if (!entry || typeof entry !== "object") {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const record = entry;
|
|
80
|
+
const id = typeof record.id === "string" ? record.id.trim() : "";
|
|
81
|
+
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
82
|
+
const status = typeof record.status === "string" ? record.status.trim() : "";
|
|
83
|
+
const title = typeof record.title === "string" ? record.title.trim() : "";
|
|
84
|
+
if (!id || !name || status === "terminated") {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
id,
|
|
89
|
+
name,
|
|
90
|
+
...title ? { title } : {},
|
|
91
|
+
...status ? { status } : {}
|
|
92
|
+
};
|
|
93
|
+
}).filter((entry) => entry !== null).sort((left, right) => left.name.localeCompare(right.name));
|
|
94
|
+
}
|
|
95
|
+
|
|
70
96
|
// src/ui/http.ts
|
|
71
97
|
var JSON_CONTENT_TYPE_PATTERN = /\b(?:application\/json|[^;\s]+\/[^;\s]+\+json)\b/i;
|
|
72
98
|
var HTML_LIKE_RESPONSE_PATTERN = /^\s*</;
|
|
@@ -217,6 +243,48 @@ async function fetchPaperclipHealth(origin) {
|
|
|
217
243
|
}
|
|
218
244
|
}
|
|
219
245
|
|
|
246
|
+
// src/ui/plugin-installation.ts
|
|
247
|
+
var SETTINGS_INDEX_HREF = "/instance/settings/plugins";
|
|
248
|
+
var GITHUB_SYNC_PLUGIN_KEY = "paperclip-github-plugin";
|
|
249
|
+
var GITHUB_SYNC_PLUGIN_DISPLAY_NAME = "GitHub Sync";
|
|
250
|
+
function getStringValue(record, key) {
|
|
251
|
+
if (!record) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
const value = record[key];
|
|
255
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
256
|
+
}
|
|
257
|
+
function resolveGitHubSyncPluginRecord(records) {
|
|
258
|
+
if (!Array.isArray(records)) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
for (const entry of records) {
|
|
262
|
+
if (!entry || typeof entry !== "object") {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const record = entry;
|
|
266
|
+
const manifest = record.manifest && typeof record.manifest === "object" ? record.manifest : null;
|
|
267
|
+
const key = getStringValue(record, "pluginKey") ?? getStringValue(record, "key") ?? getStringValue(record, "packageName") ?? getStringValue(record, "name") ?? getStringValue(manifest, "id");
|
|
268
|
+
const displayName = getStringValue(record, "displayName") ?? getStringValue(manifest, "displayName");
|
|
269
|
+
const id = getStringValue(record, "id") ?? getStringValue(record, "pluginId");
|
|
270
|
+
if (id && (key === GITHUB_SYNC_PLUGIN_KEY || displayName === GITHUB_SYNC_PLUGIN_DISPLAY_NAME)) {
|
|
271
|
+
return record;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
function resolveInstalledGitHubSyncPluginId(records, preferredPluginId) {
|
|
277
|
+
if (typeof preferredPluginId === "string" && preferredPluginId.trim()) {
|
|
278
|
+
return preferredPluginId.trim();
|
|
279
|
+
}
|
|
280
|
+
const record = resolveGitHubSyncPluginRecord(records);
|
|
281
|
+
return getStringValue(record, "id") ?? getStringValue(record, "pluginId");
|
|
282
|
+
}
|
|
283
|
+
function resolvePluginSettingsHref(records) {
|
|
284
|
+
const pluginId = resolveInstalledGitHubSyncPluginId(records);
|
|
285
|
+
return pluginId ? `${SETTINGS_INDEX_HREF}/${pluginId}` : SETTINGS_INDEX_HREF;
|
|
286
|
+
}
|
|
287
|
+
|
|
220
288
|
// src/ui/plugin-config.ts
|
|
221
289
|
function normalizeOptionalString2(value) {
|
|
222
290
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
@@ -612,12 +680,18 @@ function getActiveRateLimitPause(syncState, referenceTimeMs = Date.now()) {
|
|
|
612
680
|
...syncState.errorDetails.rateLimitResource ? { resource: syncState.errorDetails.rateLimitResource } : {}
|
|
613
681
|
};
|
|
614
682
|
}
|
|
683
|
+
function isSyncCancellationRequested(syncState) {
|
|
684
|
+
return syncState.status === "running" && Boolean(syncState.cancelRequestedAt?.trim());
|
|
685
|
+
}
|
|
615
686
|
function getSyncToastTitle(syncState) {
|
|
616
687
|
if (getActiveRateLimitPause(syncState)) {
|
|
617
688
|
return "GitHub sync is paused";
|
|
618
689
|
}
|
|
690
|
+
if (syncState.status === "cancelled") {
|
|
691
|
+
return "GitHub sync was cancelled";
|
|
692
|
+
}
|
|
619
693
|
if (syncState.status === "running") {
|
|
620
|
-
return "GitHub sync is running";
|
|
694
|
+
return isSyncCancellationRequested(syncState) ? "GitHub sync is stopping" : "GitHub sync is running";
|
|
621
695
|
}
|
|
622
696
|
return syncState.status === "error" ? "GitHub sync needs attention" : "GitHub sync finished";
|
|
623
697
|
}
|
|
@@ -626,7 +700,7 @@ function getSyncToastBody(syncState) {
|
|
|
626
700
|
return syncState.message.trim();
|
|
627
701
|
}
|
|
628
702
|
if (syncState.status === "running") {
|
|
629
|
-
return "GitHub sync is running in the background.";
|
|
703
|
+
return isSyncCancellationRequested(syncState) ? "Cancellation requested. GitHub sync will stop after the current step finishes." : "GitHub sync is running in the background.";
|
|
630
704
|
}
|
|
631
705
|
return "GitHub sync completed.";
|
|
632
706
|
}
|
|
@@ -634,7 +708,7 @@ function getSyncToastTone(syncState) {
|
|
|
634
708
|
if (getActiveRateLimitPause(syncState)) {
|
|
635
709
|
return "info";
|
|
636
710
|
}
|
|
637
|
-
if (syncState.status === "running") {
|
|
711
|
+
if (syncState.status === "running" || syncState.status === "cancelled") {
|
|
638
712
|
return "info";
|
|
639
713
|
}
|
|
640
714
|
return syncState.status === "error" ? "error" : "success";
|
|
@@ -765,6 +839,10 @@ var SHARED_PROGRESS_STYLES = `
|
|
|
765
839
|
align-items: stretch;
|
|
766
840
|
flex-direction: column;
|
|
767
841
|
}
|
|
842
|
+
|
|
843
|
+
.ghsync-diagnostics__layout--split {
|
|
844
|
+
grid-template-columns: 1fr;
|
|
845
|
+
}
|
|
768
846
|
}
|
|
769
847
|
`;
|
|
770
848
|
var PAGE_STYLES = `
|
|
@@ -992,6 +1070,79 @@ var PAGE_STYLES = `
|
|
|
992
1070
|
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
|
993
1071
|
}
|
|
994
1072
|
|
|
1073
|
+
.ghsync-diagnostics__layout {
|
|
1074
|
+
display: grid;
|
|
1075
|
+
gap: 10px;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
.ghsync-diagnostics__layout--split {
|
|
1079
|
+
grid-template-columns: minmax(220px, 300px) minmax(0, 1fr);
|
|
1080
|
+
align-items: start;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
.ghsync-diagnostics__detail,
|
|
1084
|
+
.ghsync-diagnostics__failures {
|
|
1085
|
+
display: grid;
|
|
1086
|
+
gap: 10px;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
.ghsync-diagnostics__failures {
|
|
1090
|
+
max-height: 420px;
|
|
1091
|
+
overflow: auto;
|
|
1092
|
+
margin: 0;
|
|
1093
|
+
padding: 0;
|
|
1094
|
+
list-style: none;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
.ghsync-diagnostics__failure {
|
|
1098
|
+
display: grid;
|
|
1099
|
+
gap: 6px;
|
|
1100
|
+
width: 100%;
|
|
1101
|
+
padding: 12px;
|
|
1102
|
+
border-radius: 10px;
|
|
1103
|
+
border: 1px solid var(--ghsync-dangerBorder);
|
|
1104
|
+
background: var(--ghsync-surfaceAlt);
|
|
1105
|
+
text-align: left;
|
|
1106
|
+
transition: border-color 160ms ease, background 160ms ease, transform 160ms ease;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
.ghsync-diagnostics__failure-item {
|
|
1110
|
+
list-style: none;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
.ghsync-diagnostics__failure:hover {
|
|
1114
|
+
border-color: var(--ghsync-dangerText);
|
|
1115
|
+
transform: translateY(-1px);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
.ghsync-diagnostics__failure--active {
|
|
1119
|
+
border-color: var(--ghsync-dangerText);
|
|
1120
|
+
background: color-mix(in srgb, var(--ghsync-dangerBg) 35%, var(--ghsync-surfaceAlt));
|
|
1121
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--ghsync-dangerText) 22%, transparent);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
.ghsync-diagnostics__failure-title {
|
|
1125
|
+
color: var(--ghsync-title);
|
|
1126
|
+
font-size: 13px;
|
|
1127
|
+
line-height: 1.4;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
.ghsync-diagnostics__failure-meta {
|
|
1131
|
+
color: var(--ghsync-muted);
|
|
1132
|
+
font-size: 11px;
|
|
1133
|
+
line-height: 1.4;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
.ghsync-diagnostics__failure-preview {
|
|
1137
|
+
color: var(--ghsync-text);
|
|
1138
|
+
font-size: 12px;
|
|
1139
|
+
line-height: 1.5;
|
|
1140
|
+
display: -webkit-box;
|
|
1141
|
+
-webkit-box-orient: vertical;
|
|
1142
|
+
-webkit-line-clamp: 3;
|
|
1143
|
+
overflow: hidden;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
995
1146
|
.ghsync-diagnostics__item,
|
|
996
1147
|
.ghsync-diagnostics__block {
|
|
997
1148
|
display: grid;
|
|
@@ -1965,6 +2116,72 @@ var WIDGET_STYLES = `
|
|
|
1965
2116
|
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
1966
2117
|
}
|
|
1967
2118
|
|
|
2119
|
+
.ghsync-diagnostics__layout {
|
|
2120
|
+
display: grid;
|
|
2121
|
+
gap: 10px;
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
.ghsync-diagnostics__layout--split {
|
|
2125
|
+
grid-template-columns: minmax(200px, 240px) minmax(0, 1fr);
|
|
2126
|
+
align-items: start;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
.ghsync-diagnostics__detail,
|
|
2130
|
+
.ghsync-diagnostics__failures {
|
|
2131
|
+
display: grid;
|
|
2132
|
+
gap: 10px;
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
.ghsync-diagnostics__failures {
|
|
2136
|
+
max-height: 320px;
|
|
2137
|
+
overflow: auto;
|
|
2138
|
+
margin: 0;
|
|
2139
|
+
padding: 0;
|
|
2140
|
+
list-style: none;
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
.ghsync-diagnostics__failure {
|
|
2144
|
+
display: grid;
|
|
2145
|
+
gap: 6px;
|
|
2146
|
+
width: 100%;
|
|
2147
|
+
padding: 12px;
|
|
2148
|
+
border-radius: 10px;
|
|
2149
|
+
border: 1px solid var(--ghsync-dangerBorder);
|
|
2150
|
+
background: var(--ghsync-surfaceAlt);
|
|
2151
|
+
text-align: left;
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
.ghsync-diagnostics__failure-item {
|
|
2155
|
+
list-style: none;
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
.ghsync-diagnostics__failure--active {
|
|
2159
|
+
border-color: var(--ghsync-dangerText);
|
|
2160
|
+
background: color-mix(in srgb, var(--ghsync-dangerBg) 35%, var(--ghsync-surfaceAlt));
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
.ghsync-diagnostics__failure-title {
|
|
2164
|
+
color: var(--ghsync-title);
|
|
2165
|
+
font-size: 12px;
|
|
2166
|
+
line-height: 1.4;
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
.ghsync-diagnostics__failure-meta {
|
|
2170
|
+
color: var(--ghsync-muted);
|
|
2171
|
+
font-size: 11px;
|
|
2172
|
+
line-height: 1.4;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
.ghsync-diagnostics__failure-preview {
|
|
2176
|
+
color: var(--ghsync-text);
|
|
2177
|
+
font-size: 12px;
|
|
2178
|
+
line-height: 1.5;
|
|
2179
|
+
display: -webkit-box;
|
|
2180
|
+
-webkit-box-orient: vertical;
|
|
2181
|
+
-webkit-line-clamp: 3;
|
|
2182
|
+
overflow: hidden;
|
|
2183
|
+
}
|
|
2184
|
+
|
|
1968
2185
|
.ghsync-diagnostics__item,
|
|
1969
2186
|
.ghsync-diagnostics__block {
|
|
1970
2187
|
display: grid;
|
|
@@ -2105,6 +2322,10 @@ var WIDGET_STYLES = `
|
|
|
2105
2322
|
.ghsync-widget__link {
|
|
2106
2323
|
flex: 1 1 auto;
|
|
2107
2324
|
}
|
|
2325
|
+
|
|
2326
|
+
.ghsync-diagnostics__layout--split {
|
|
2327
|
+
grid-template-columns: 1fr;
|
|
2328
|
+
}
|
|
2108
2329
|
}
|
|
2109
2330
|
|
|
2110
2331
|
${SHARED_PROGRESS_STYLES}
|
|
@@ -2681,24 +2902,39 @@ function getPaperclipApiBaseUrl() {
|
|
|
2681
2902
|
return window.location.origin;
|
|
2682
2903
|
}
|
|
2683
2904
|
var syncedPaperclipApiBaseUrlsByPluginId = /* @__PURE__ */ new Map();
|
|
2905
|
+
var installedGitHubSyncPluginIdPromise = null;
|
|
2906
|
+
async function resolveCurrentPluginId(pluginId) {
|
|
2907
|
+
if (pluginId) {
|
|
2908
|
+
return pluginId;
|
|
2909
|
+
}
|
|
2910
|
+
if (!installedGitHubSyncPluginIdPromise) {
|
|
2911
|
+
installedGitHubSyncPluginIdPromise = fetchJson("/api/plugins").then((records) => resolveInstalledGitHubSyncPluginId(records)).catch(() => null);
|
|
2912
|
+
}
|
|
2913
|
+
const resolvedPluginId = await installedGitHubSyncPluginIdPromise;
|
|
2914
|
+
if (!resolvedPluginId) {
|
|
2915
|
+
installedGitHubSyncPluginIdPromise = null;
|
|
2916
|
+
}
|
|
2917
|
+
return resolvedPluginId;
|
|
2918
|
+
}
|
|
2684
2919
|
async function syncTrustedPaperclipApiBaseUrl(pluginId) {
|
|
2685
2920
|
const paperclipApiBaseUrl = getPaperclipApiBaseUrl();
|
|
2686
2921
|
if (!paperclipApiBaseUrl) {
|
|
2687
2922
|
return void 0;
|
|
2688
2923
|
}
|
|
2689
|
-
|
|
2924
|
+
const resolvedPluginId = await resolveCurrentPluginId(pluginId);
|
|
2925
|
+
if (!resolvedPluginId) {
|
|
2690
2926
|
throw new Error(
|
|
2691
2927
|
"Unable to sync the trusted Paperclip API origin because the plugin ID is missing. Reload the plugin and try again before saving or syncing."
|
|
2692
2928
|
);
|
|
2693
2929
|
}
|
|
2694
|
-
const lastSyncedPaperclipApiBaseUrl = syncedPaperclipApiBaseUrlsByPluginId.get(
|
|
2930
|
+
const lastSyncedPaperclipApiBaseUrl = syncedPaperclipApiBaseUrlsByPluginId.get(resolvedPluginId);
|
|
2695
2931
|
if (lastSyncedPaperclipApiBaseUrl === paperclipApiBaseUrl) {
|
|
2696
2932
|
return paperclipApiBaseUrl;
|
|
2697
2933
|
}
|
|
2698
|
-
await patchPluginConfig(
|
|
2934
|
+
await patchPluginConfig(resolvedPluginId, {
|
|
2699
2935
|
paperclipApiBaseUrl
|
|
2700
2936
|
});
|
|
2701
|
-
syncedPaperclipApiBaseUrlsByPluginId.set(
|
|
2937
|
+
syncedPaperclipApiBaseUrlsByPluginId.set(resolvedPluginId, paperclipApiBaseUrl);
|
|
2702
2938
|
return paperclipApiBaseUrl;
|
|
2703
2939
|
}
|
|
2704
2940
|
function formatDate(value, fallback = "Never") {
|
|
@@ -2804,6 +3040,11 @@ async function listCompanyProjects(companyId) {
|
|
|
2804
3040
|
return id && name ? { id, name } : null;
|
|
2805
3041
|
}).filter((entry) => entry !== null);
|
|
2806
3042
|
}
|
|
3043
|
+
async function listCompanyAssigneeOptions(companyId) {
|
|
3044
|
+
return normalizeCompanyAssigneeOptionsResponse(
|
|
3045
|
+
await fetchJson(`/api/companies/${companyId}/agents`)
|
|
3046
|
+
);
|
|
3047
|
+
}
|
|
2807
3048
|
async function listProjectWorkspaces(projectId) {
|
|
2808
3049
|
const response = await fetchJson(`/api/projects/${projectId}/workspaces`);
|
|
2809
3050
|
if (!Array.isArray(response)) {
|
|
@@ -2991,7 +3232,10 @@ function getSyncStatus(syncState, runningSync, syncUnlocked) {
|
|
|
2991
3232
|
return { label: "Locked", tone: "neutral" };
|
|
2992
3233
|
}
|
|
2993
3234
|
if (runningSync || syncState.status === "running") {
|
|
2994
|
-
return {
|
|
3235
|
+
return {
|
|
3236
|
+
label: isSyncCancellationRequested(syncState) ? "Cancelling" : "Running",
|
|
3237
|
+
tone: "info"
|
|
3238
|
+
};
|
|
2995
3239
|
}
|
|
2996
3240
|
if (getActiveRateLimitPause(syncState)) {
|
|
2997
3241
|
return { label: "Paused", tone: "warning" };
|
|
@@ -3002,6 +3246,9 @@ function getSyncStatus(syncState, runningSync, syncUnlocked) {
|
|
|
3002
3246
|
if (syncState.status === "success") {
|
|
3003
3247
|
return { label: "Ready", tone: "success" };
|
|
3004
3248
|
}
|
|
3249
|
+
if (syncState.status === "cancelled") {
|
|
3250
|
+
return { label: "Cancelled", tone: "neutral" };
|
|
3251
|
+
}
|
|
3005
3252
|
return { label: "Ready", tone: "info" };
|
|
3006
3253
|
}
|
|
3007
3254
|
function getToneClass(tone) {
|
|
@@ -3018,9 +3265,9 @@ function getToneClass(tone) {
|
|
|
3018
3265
|
return "ghsync__badge--neutral";
|
|
3019
3266
|
}
|
|
3020
3267
|
}
|
|
3021
|
-
var
|
|
3268
|
+
var SETTINGS_INDEX_HREF2 = "/instance/settings/plugins";
|
|
3022
3269
|
var GITHUB_SYNC_SETTINGS_UPDATED_EVENT = "paperclip-github-plugin:settings-updated";
|
|
3023
|
-
function
|
|
3270
|
+
function getStringValue2(record, key) {
|
|
3024
3271
|
const value = record[key];
|
|
3025
3272
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
3026
3273
|
}
|
|
@@ -3038,12 +3285,12 @@ function humanizeCompanyPrefix(value) {
|
|
|
3038
3285
|
return trimmed.replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (character) => character.toUpperCase());
|
|
3039
3286
|
}
|
|
3040
3287
|
function getCompanyLabelFromRecord(record) {
|
|
3041
|
-
const explicitLabel =
|
|
3288
|
+
const explicitLabel = getStringValue2(record, "displayName") ?? getStringValue2(record, "name") ?? getStringValue2(record, "title");
|
|
3042
3289
|
if (explicitLabel && !isUuidLike(explicitLabel)) {
|
|
3043
3290
|
return explicitLabel;
|
|
3044
3291
|
}
|
|
3045
3292
|
return humanizeCompanyPrefix(
|
|
3046
|
-
|
|
3293
|
+
getStringValue2(record, "companyPrefix") ?? getStringValue2(record, "prefix") ?? getStringValue2(record, "slug")
|
|
3047
3294
|
);
|
|
3048
3295
|
}
|
|
3049
3296
|
async function resolveCompanyScopeLabel(companyId, companyPrefix) {
|
|
@@ -3069,8 +3316,8 @@ async function resolveCompanyScopeLabel(companyId, companyPrefix) {
|
|
|
3069
3316
|
return false;
|
|
3070
3317
|
}
|
|
3071
3318
|
const record = entry;
|
|
3072
|
-
const entryId =
|
|
3073
|
-
const entryPrefix =
|
|
3319
|
+
const entryId = getStringValue2(record, "id");
|
|
3320
|
+
const entryPrefix = getStringValue2(record, "companyPrefix") ?? getStringValue2(record, "prefix") ?? getStringValue2(record, "slug");
|
|
3074
3321
|
return entryId === companyId || Boolean(companyPrefix && entryPrefix === companyPrefix);
|
|
3075
3322
|
});
|
|
3076
3323
|
if (matchingCompany && typeof matchingCompany === "object") {
|
|
@@ -3154,25 +3401,6 @@ function useResolvedIssueId(params) {
|
|
|
3154
3401
|
loading: false
|
|
3155
3402
|
};
|
|
3156
3403
|
}
|
|
3157
|
-
function resolvePluginSettingsHref(records) {
|
|
3158
|
-
if (!Array.isArray(records)) {
|
|
3159
|
-
return SETTINGS_INDEX_HREF;
|
|
3160
|
-
}
|
|
3161
|
-
for (const entry of records) {
|
|
3162
|
-
if (!entry || typeof entry !== "object") {
|
|
3163
|
-
continue;
|
|
3164
|
-
}
|
|
3165
|
-
const record = entry;
|
|
3166
|
-
const manifest = record.manifest && typeof record.manifest === "object" ? record.manifest : null;
|
|
3167
|
-
const id = getStringValue(record, "id") ?? getStringValue(record, "pluginId");
|
|
3168
|
-
const key = getStringValue(record, "pluginKey") ?? getStringValue(record, "key") ?? getStringValue(record, "packageName") ?? getStringValue(record, "name") ?? (manifest ? getStringValue(manifest, "id") : null);
|
|
3169
|
-
const displayName = getStringValue(record, "displayName") ?? (manifest ? getStringValue(manifest, "displayName") : null);
|
|
3170
|
-
if (id && (key === "paperclip-github-plugin" || displayName === "GitHub Sync")) {
|
|
3171
|
-
return `${SETTINGS_INDEX_HREF}/${id}`;
|
|
3172
|
-
}
|
|
3173
|
-
}
|
|
3174
|
-
return SETTINGS_INDEX_HREF;
|
|
3175
|
-
}
|
|
3176
3404
|
function formatSyncProgressRepository(repositoryUrl) {
|
|
3177
3405
|
if (!repositoryUrl?.trim()) {
|
|
3178
3406
|
return null;
|
|
@@ -3291,10 +3519,10 @@ function getDashboardSummary(params) {
|
|
|
3291
3519
|
if (params.runningSync || params.syncState.status === "running") {
|
|
3292
3520
|
const progress = getRunningSyncProgressModel(params.syncState);
|
|
3293
3521
|
return {
|
|
3294
|
-
label: "Syncing",
|
|
3522
|
+
label: isSyncCancellationRequested(params.syncState) ? "Cancelling" : "Syncing",
|
|
3295
3523
|
tone: "info",
|
|
3296
|
-
title: progress?.title ?? "Sync in progress",
|
|
3297
|
-
body: progress?.description ?? "GitHub issues are being checked right now. This card refreshes automatically until the run finishes."
|
|
3524
|
+
title: isSyncCancellationRequested(params.syncState) ? "Stopping the current sync" : progress?.title ?? "Sync in progress",
|
|
3525
|
+
body: isSyncCancellationRequested(params.syncState) ? "GitHub Sync will stop after the current repository or issue step finishes." : progress?.description ?? "GitHub issues are being checked right now. This card refreshes automatically until the run finishes."
|
|
3298
3526
|
};
|
|
3299
3527
|
}
|
|
3300
3528
|
if (activeRateLimitPause) {
|
|
@@ -3313,6 +3541,14 @@ function getDashboardSummary(params) {
|
|
|
3313
3541
|
body: params.syncState.message ?? "Open settings to review the latest GitHub sync issue."
|
|
3314
3542
|
};
|
|
3315
3543
|
}
|
|
3544
|
+
if (params.syncState.status === "cancelled") {
|
|
3545
|
+
return {
|
|
3546
|
+
label: "Cancelled",
|
|
3547
|
+
tone: "neutral",
|
|
3548
|
+
title: "Sync cancelled",
|
|
3549
|
+
body: params.syncState.message ?? "The last GitHub sync was cancelled before it finished."
|
|
3550
|
+
};
|
|
3551
|
+
}
|
|
3316
3552
|
if (params.syncState.checkedAt) {
|
|
3317
3553
|
return {
|
|
3318
3554
|
label: "Ready",
|
|
@@ -3422,16 +3658,28 @@ function formatSyncFailureRepository(repositoryUrl) {
|
|
|
3422
3658
|
}
|
|
3423
3659
|
return repositoryUrl.trim();
|
|
3424
3660
|
}
|
|
3425
|
-
function
|
|
3661
|
+
function getSyncFailureLogEntries(syncState) {
|
|
3662
|
+
if (syncState.recentFailures?.length) {
|
|
3663
|
+
return syncState.recentFailures.filter((entry) => typeof entry.message === "string" && entry.message.trim());
|
|
3664
|
+
}
|
|
3426
3665
|
if (syncState.status !== "error") {
|
|
3427
|
-
return
|
|
3666
|
+
return [];
|
|
3428
3667
|
}
|
|
3668
|
+
return [
|
|
3669
|
+
{
|
|
3670
|
+
message: syncState.message?.trim() || "GitHub sync failed.",
|
|
3671
|
+
occurredAt: syncState.checkedAt,
|
|
3672
|
+
...syncState.errorDetails ?? {}
|
|
3673
|
+
}
|
|
3674
|
+
];
|
|
3675
|
+
}
|
|
3676
|
+
function getSyncDiagnostics(entry) {
|
|
3429
3677
|
const rows = [];
|
|
3430
|
-
const repositoryLabel = formatSyncFailureRepository(
|
|
3431
|
-
const phaseLabel = formatSyncFailurePhase(
|
|
3432
|
-
const issueNumber =
|
|
3433
|
-
const rateLimitResetAt =
|
|
3434
|
-
const rateLimitResourceLabel = getGitHubRateLimitResourceLabel(
|
|
3678
|
+
const repositoryLabel = formatSyncFailureRepository(entry.repositoryUrl);
|
|
3679
|
+
const phaseLabel = formatSyncFailurePhase(entry.phase);
|
|
3680
|
+
const issueNumber = entry.githubIssueNumber;
|
|
3681
|
+
const rateLimitResetAt = entry.rateLimitResetAt;
|
|
3682
|
+
const rateLimitResourceLabel = getGitHubRateLimitResourceLabel(entry.rateLimitResource);
|
|
3435
3683
|
if (repositoryLabel) {
|
|
3436
3684
|
rows.push({
|
|
3437
3685
|
label: "Repository",
|
|
@@ -3462,12 +3710,19 @@ function getSyncDiagnostics(syncState) {
|
|
|
3462
3710
|
value: formatDate(rateLimitResetAt, rateLimitResetAt)
|
|
3463
3711
|
});
|
|
3464
3712
|
}
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3713
|
+
if (entry.occurredAt) {
|
|
3714
|
+
rows.push({
|
|
3715
|
+
label: "Captured",
|
|
3716
|
+
value: formatDate(entry.occurredAt, entry.occurredAt)
|
|
3717
|
+
});
|
|
3718
|
+
}
|
|
3719
|
+
const rawMessage = entry.rawMessage && entry.rawMessage !== entry.message ? entry.rawMessage : void 0;
|
|
3720
|
+
const suggestedAction = entry.suggestedAction;
|
|
3721
|
+
if (!entry.message && rows.length === 0 && !rawMessage && !suggestedAction) {
|
|
3468
3722
|
return null;
|
|
3469
3723
|
}
|
|
3470
3724
|
return {
|
|
3725
|
+
message: entry.message,
|
|
3471
3726
|
rows,
|
|
3472
3727
|
...rawMessage ? { rawMessage } : {},
|
|
3473
3728
|
...suggestedAction ? { suggestedAction } : {}
|
|
@@ -3521,31 +3776,76 @@ function SyncProgressPanel(props) {
|
|
|
3521
3776
|
);
|
|
3522
3777
|
}
|
|
3523
3778
|
function SyncDiagnosticsPanel(props) {
|
|
3524
|
-
const
|
|
3779
|
+
const failureEntries = getSyncFailureLogEntries(props.syncState);
|
|
3780
|
+
const latestFailureIndex = Math.max(failureEntries.length - 1, 0);
|
|
3781
|
+
const [selectedFailureIndex, setSelectedFailureIndex] = useState(latestFailureIndex);
|
|
3782
|
+
const selectedFailure = failureEntries[Math.min(selectedFailureIndex, latestFailureIndex)];
|
|
3783
|
+
const diagnostics = selectedFailure ? getSyncDiagnostics(selectedFailure) : null;
|
|
3525
3784
|
const requestError = props.requestError?.trim() ? props.requestError.trim() : null;
|
|
3785
|
+
const canSelectFailures = !props.compact && failureEntries.length > 1;
|
|
3786
|
+
const savedFailureCount = props.syncState.erroredIssuesCount ?? failureEntries.length;
|
|
3787
|
+
useEffect(() => {
|
|
3788
|
+
setSelectedFailureIndex(latestFailureIndex);
|
|
3789
|
+
}, [latestFailureIndex, props.syncState.checkedAt, props.syncState.status]);
|
|
3526
3790
|
if (!diagnostics && !requestError) {
|
|
3527
3791
|
return null;
|
|
3528
3792
|
}
|
|
3529
3793
|
return /* @__PURE__ */ jsxs("section", { className: `ghsync-diagnostics${props.compact ? " ghsync-diagnostics--compact" : ""}`, children: [
|
|
3530
3794
|
/* @__PURE__ */ jsxs("div", { className: "ghsync-diagnostics__header", children: [
|
|
3531
3795
|
/* @__PURE__ */ jsx("strong", { children: diagnostics ? "Troubleshooting details" : "Sync request failed" }),
|
|
3532
|
-
/* @__PURE__ */ jsx("span", { children: diagnostics ? "GitHub Sync saved this snapshot from the latest failed run." : "The sync request failed before the worker returned a saved result." })
|
|
3796
|
+
/* @__PURE__ */ jsx("span", { children: diagnostics ? canSelectFailures ? savedFailureCount > failureEntries.length ? `GitHub Sync saved the latest ${failureEntries.length} of ${savedFailureCount} failures from the latest run. Select one to inspect.` : `GitHub Sync saved ${failureEntries.length} failure${failureEntries.length === 1 ? "" : "s"} from the latest run. Select one to inspect.` : "GitHub Sync saved this snapshot from the latest failed run." : "The sync request failed before the worker returned a saved result." })
|
|
3533
3797
|
] }),
|
|
3534
3798
|
requestError ? /* @__PURE__ */ jsxs("div", { className: "ghsync-diagnostics__block", children: [
|
|
3535
3799
|
/* @__PURE__ */ jsx("span", { className: "ghsync-diagnostics__label", children: "Request error" }),
|
|
3536
3800
|
/* @__PURE__ */ jsx("div", { className: "ghsync-diagnostics__value ghsync-diagnostics__value--code", children: requestError })
|
|
3537
3801
|
] }) : null,
|
|
3538
|
-
diagnostics
|
|
3539
|
-
/* @__PURE__ */ jsx("
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3802
|
+
diagnostics ? /* @__PURE__ */ jsxs("div", { className: `ghsync-diagnostics__layout${canSelectFailures ? " ghsync-diagnostics__layout--split" : ""}`, children: [
|
|
3803
|
+
canSelectFailures ? /* @__PURE__ */ jsx("ul", { className: "ghsync-diagnostics__failures", "aria-label": "Latest sync failures", children: failureEntries.map((failure, index) => {
|
|
3804
|
+
const repositoryLabel = formatSyncFailureRepository(failure.repositoryUrl);
|
|
3805
|
+
const issueLabel = failure.githubIssueNumber !== void 0 ? `Issue #${failure.githubIssueNumber}` : null;
|
|
3806
|
+
const phaseLabel = formatSyncFailurePhase(failure.phase);
|
|
3807
|
+
const title = [repositoryLabel, issueLabel].filter((value) => Boolean(value)).join(" \xB7 ");
|
|
3808
|
+
const meta = [phaseLabel, failure.occurredAt ? formatDate(failure.occurredAt, failure.occurredAt) : null].filter((value) => Boolean(value)).join(" \xB7 ");
|
|
3809
|
+
return /* @__PURE__ */ jsx(
|
|
3810
|
+
"li",
|
|
3811
|
+
{
|
|
3812
|
+
className: "ghsync-diagnostics__failure-item",
|
|
3813
|
+
children: /* @__PURE__ */ jsxs(
|
|
3814
|
+
"button",
|
|
3815
|
+
{
|
|
3816
|
+
type: "button",
|
|
3817
|
+
className: `ghsync-diagnostics__failure${index === selectedFailureIndex ? " ghsync-diagnostics__failure--active" : ""}`,
|
|
3818
|
+
"aria-pressed": index === selectedFailureIndex,
|
|
3819
|
+
onClick: () => setSelectedFailureIndex(index),
|
|
3820
|
+
children: [
|
|
3821
|
+
/* @__PURE__ */ jsx("strong", { className: "ghsync-diagnostics__failure-title", children: title || `Failure ${index + 1}` }),
|
|
3822
|
+
meta ? /* @__PURE__ */ jsx("span", { className: "ghsync-diagnostics__failure-meta", children: meta }) : null,
|
|
3823
|
+
/* @__PURE__ */ jsx("span", { className: "ghsync-diagnostics__failure-preview", children: failure.message })
|
|
3824
|
+
]
|
|
3825
|
+
}
|
|
3826
|
+
)
|
|
3827
|
+
},
|
|
3828
|
+
`${failure.occurredAt ?? "unknown"}-${failure.githubIssueNumber ?? "no-issue"}-${index}`
|
|
3829
|
+
);
|
|
3830
|
+
}) }) : null,
|
|
3831
|
+
/* @__PURE__ */ jsxs("div", { className: "ghsync-diagnostics__detail", children: [
|
|
3832
|
+
/* @__PURE__ */ jsxs("div", { className: "ghsync-diagnostics__block", children: [
|
|
3833
|
+
/* @__PURE__ */ jsx("span", { className: "ghsync-diagnostics__label", children: "Summary" }),
|
|
3834
|
+
/* @__PURE__ */ jsx("div", { className: "ghsync-diagnostics__value", children: diagnostics.message })
|
|
3835
|
+
] }),
|
|
3836
|
+
diagnostics.rows.length ? /* @__PURE__ */ jsx("div", { className: "ghsync-diagnostics__grid", children: diagnostics.rows.map((row) => /* @__PURE__ */ jsxs("div", { className: "ghsync-diagnostics__item", children: [
|
|
3837
|
+
/* @__PURE__ */ jsx("span", { className: "ghsync-diagnostics__label", children: row.label }),
|
|
3838
|
+
/* @__PURE__ */ jsx("strong", { className: "ghsync-diagnostics__value", children: row.value })
|
|
3839
|
+
] }, row.label)) }) : null,
|
|
3840
|
+
diagnostics.rawMessage ? /* @__PURE__ */ jsxs("div", { className: "ghsync-diagnostics__block", children: [
|
|
3841
|
+
/* @__PURE__ */ jsx("span", { className: "ghsync-diagnostics__label", children: "Raw error" }),
|
|
3842
|
+
/* @__PURE__ */ jsx("div", { className: "ghsync-diagnostics__value ghsync-diagnostics__value--code", children: diagnostics.rawMessage })
|
|
3843
|
+
] }) : null,
|
|
3844
|
+
diagnostics.suggestedAction ? /* @__PURE__ */ jsxs("div", { className: "ghsync-diagnostics__block", children: [
|
|
3845
|
+
/* @__PURE__ */ jsx("span", { className: "ghsync-diagnostics__label", children: "Next step" }),
|
|
3846
|
+
/* @__PURE__ */ jsx("div", { className: "ghsync-diagnostics__value", children: diagnostics.suggestedAction })
|
|
3847
|
+
] }) : null
|
|
3848
|
+
] })
|
|
3549
3849
|
] }) : null
|
|
3550
3850
|
] });
|
|
3551
3851
|
}
|
|
@@ -3561,11 +3861,13 @@ function GitHubSyncSettingsPage() {
|
|
|
3561
3861
|
const updateBoardAccess = usePluginAction("settings.updateBoardAccess");
|
|
3562
3862
|
const validateToken = usePluginAction("settings.validateToken");
|
|
3563
3863
|
const runSyncNow = usePluginAction("sync.runNow");
|
|
3864
|
+
const cancelSync = usePluginAction("sync.cancel");
|
|
3564
3865
|
const [form, setForm] = useState(EMPTY_SETTINGS);
|
|
3565
3866
|
const [submittingToken, setSubmittingToken] = useState(false);
|
|
3566
3867
|
const [connectingBoardAccess, setConnectingBoardAccess] = useState(false);
|
|
3567
3868
|
const [submittingSetup, setSubmittingSetup] = useState(false);
|
|
3568
3869
|
const [runningSync, setRunningSync] = useState(false);
|
|
3870
|
+
const [cancellingSync, setCancellingSync] = useState(false);
|
|
3569
3871
|
const [manualSyncRequestError, setManualSyncRequestError] = useState(null);
|
|
3570
3872
|
const [scheduleFrequencyDraft, setScheduleFrequencyDraft] = useState(String(DEFAULT_SCHEDULE_FREQUENCY_MINUTES));
|
|
3571
3873
|
const [ignoredAuthorsDraft, setIgnoredAuthorsDraft] = useState(DEFAULT_ADVANCED_SETTINGS.ignoredIssueAuthorUsernames.join(", "));
|
|
@@ -3580,10 +3882,12 @@ function GitHubSyncSettingsPage() {
|
|
|
3580
3882
|
const [existingProjectCandidates, setExistingProjectCandidates] = useState([]);
|
|
3581
3883
|
const [existingProjectCandidatesLoading, setExistingProjectCandidatesLoading] = useState(false);
|
|
3582
3884
|
const [existingProjectCandidatesError, setExistingProjectCandidatesError] = useState(null);
|
|
3885
|
+
const [browserAvailableAssignees, setBrowserAvailableAssignees] = useState([]);
|
|
3583
3886
|
const themeMode = useResolvedThemeMode();
|
|
3584
3887
|
const boardAccessRequirement = usePaperclipBoardAccessRequirement();
|
|
3585
3888
|
const armSyncCompletionToast = useSyncCompletionToast(form.syncState, toast);
|
|
3586
3889
|
const boardAccessConfigSyncAttemptRef = useRef(null);
|
|
3890
|
+
const assigneeFallbackAttemptRef = useRef(null);
|
|
3587
3891
|
const currentSettings = settings.data ?? cachedSettings;
|
|
3588
3892
|
const showInitialLoadingState = settings.loading && !settings.data && !cachedSettings;
|
|
3589
3893
|
useEffect(() => {
|
|
@@ -3659,10 +3963,47 @@ function GitHubSyncSettingsPage() {
|
|
|
3659
3963
|
cancelled = true;
|
|
3660
3964
|
};
|
|
3661
3965
|
}, [hostContext.companyId, settings.data?.updatedAt, tokenStatusOverride]);
|
|
3966
|
+
useEffect(() => {
|
|
3967
|
+
const companyId = hostContext.companyId;
|
|
3968
|
+
const workerAvailableAssignees = currentSettings?.availableAssignees ?? [];
|
|
3969
|
+
const snapshotKey = `${companyId ?? "none"}:${currentSettings?.updatedAt ?? "none"}`;
|
|
3970
|
+
if (!companyId) {
|
|
3971
|
+
assigneeFallbackAttemptRef.current = null;
|
|
3972
|
+
setBrowserAvailableAssignees([]);
|
|
3973
|
+
return;
|
|
3974
|
+
}
|
|
3975
|
+
if (workerAvailableAssignees.length > 0) {
|
|
3976
|
+
assigneeFallbackAttemptRef.current = snapshotKey;
|
|
3977
|
+
setBrowserAvailableAssignees([]);
|
|
3978
|
+
return;
|
|
3979
|
+
}
|
|
3980
|
+
if (assigneeFallbackAttemptRef.current === snapshotKey) {
|
|
3981
|
+
return;
|
|
3982
|
+
}
|
|
3983
|
+
assigneeFallbackAttemptRef.current = snapshotKey;
|
|
3984
|
+
let cancelled = false;
|
|
3985
|
+
void (async () => {
|
|
3986
|
+
try {
|
|
3987
|
+
const assignees = await listCompanyAssigneeOptions(companyId);
|
|
3988
|
+
if (cancelled) {
|
|
3989
|
+
return;
|
|
3990
|
+
}
|
|
3991
|
+
setBrowserAvailableAssignees(assignees);
|
|
3992
|
+
} catch {
|
|
3993
|
+
if (cancelled) {
|
|
3994
|
+
return;
|
|
3995
|
+
}
|
|
3996
|
+
setBrowserAvailableAssignees([]);
|
|
3997
|
+
}
|
|
3998
|
+
})();
|
|
3999
|
+
return () => {
|
|
4000
|
+
cancelled = true;
|
|
4001
|
+
};
|
|
4002
|
+
}, [currentSettings?.availableAssignees?.length, currentSettings?.updatedAt, hostContext.companyId]);
|
|
3662
4003
|
useEffect(() => {
|
|
3663
4004
|
const companyId = hostContext.companyId;
|
|
3664
4005
|
const secretRef = settings.data?.paperclipBoardAccessNeedsConfigSync ? settings.data.paperclipBoardAccessConfigSyncRef : void 0;
|
|
3665
|
-
if (!companyId || !
|
|
4006
|
+
if (!companyId || !secretRef) {
|
|
3666
4007
|
return;
|
|
3667
4008
|
}
|
|
3668
4009
|
const attemptKey = `${companyId}:${secretRef}`;
|
|
@@ -3673,7 +4014,11 @@ function GitHubSyncSettingsPage() {
|
|
|
3673
4014
|
let cancelled = false;
|
|
3674
4015
|
void (async () => {
|
|
3675
4016
|
try {
|
|
3676
|
-
await
|
|
4017
|
+
const pluginId = await resolveCurrentPluginId(pluginIdFromLocation);
|
|
4018
|
+
if (!pluginId) {
|
|
4019
|
+
throw new Error("Plugin id is required to finish syncing Paperclip board access into plugin config.");
|
|
4020
|
+
}
|
|
4021
|
+
await patchPluginConfig(pluginId, {
|
|
3677
4022
|
paperclipBoardApiTokenRefs: {
|
|
3678
4023
|
[companyId]: secretRef
|
|
3679
4024
|
}
|
|
@@ -3760,7 +4105,7 @@ function GitHubSyncSettingsPage() {
|
|
|
3760
4105
|
const boardAccessSectionDescription = "";
|
|
3761
4106
|
const repositoriesUnlocked = tokenStatus === "valid";
|
|
3762
4107
|
const availableAssignees = getAvailableAssigneeOptions(
|
|
3763
|
-
currentSettings?.availableAssignees ?? form.availableAssignees,
|
|
4108
|
+
(currentSettings?.availableAssignees?.length ? currentSettings.availableAssignees : null) ?? (form.availableAssignees?.length ? form.availableAssignees : null) ?? browserAvailableAssignees,
|
|
3764
4109
|
form.advancedSettings.defaultAssigneeAgentId
|
|
3765
4110
|
);
|
|
3766
4111
|
const savedMappingsSource = currentSettings ? currentSettings.mappings ?? [] : form.mappings;
|
|
@@ -3785,6 +4130,10 @@ function GitHubSyncSettingsPage() {
|
|
|
3785
4130
|
hasMappings: savedMappingCount > 0,
|
|
3786
4131
|
hasBoardAccess: boardAccessReady
|
|
3787
4132
|
});
|
|
4133
|
+
const syncPersistedRunning = displaySyncState.status === "running";
|
|
4134
|
+
const syncStartPending = runningSync && !syncPersistedRunning;
|
|
4135
|
+
const syncInFlight = syncStartPending || syncPersistedRunning;
|
|
4136
|
+
const cancellationRequested = syncPersistedRunning && (cancellingSync || isSyncCancellationRequested(displaySyncState));
|
|
3788
4137
|
const mappingsDirty = JSON.stringify(draftMappings) !== JSON.stringify(savedMappings);
|
|
3789
4138
|
const advancedSettingsDirty = JSON.stringify(draftAdvancedSettings) !== JSON.stringify(savedAdvancedSettings);
|
|
3790
4139
|
const scheduleFrequencyError = getScheduleFrequencyError(scheduleFrequencyDraft);
|
|
@@ -3792,7 +4141,6 @@ function GitHubSyncSettingsPage() {
|
|
|
3792
4141
|
const savedScheduleFrequencyMinutes = normalizeScheduleFrequencyMinutes(currentSettings?.scheduleFrequencyMinutes);
|
|
3793
4142
|
const scheduleDirty = scheduleFrequencyError === null && scheduleFrequencyMinutes !== savedScheduleFrequencyMinutes;
|
|
3794
4143
|
const mappings = form.mappings.length > 0 ? form.mappings : [createEmptyMapping(0)];
|
|
3795
|
-
const syncInFlight = runningSync || displaySyncState.status === "running";
|
|
3796
4144
|
const settingsMutationsLocked = syncInFlight;
|
|
3797
4145
|
const settingsMutationsLockReason = settingsMutationsLocked ? "Settings are temporarily locked while a sync is running to avoid overwriting local edits." : null;
|
|
3798
4146
|
const syncStatus = getSyncStatus(displaySyncState, runningSync, syncUnlocked);
|
|
@@ -3814,10 +4162,11 @@ function GitHubSyncSettingsPage() {
|
|
|
3814
4162
|
savedMappingCount
|
|
3815
4163
|
});
|
|
3816
4164
|
const syncSectionDescription = "";
|
|
3817
|
-
const syncSummaryPrimaryText = syncProgress?.title ?? displaySyncState.message ?? (syncUnlocked ? "Ready to sync." : syncSetupMessage);
|
|
4165
|
+
const syncSummaryPrimaryText = (cancellationRequested ? "Cancellation requested." : void 0) ?? syncProgress?.title ?? displaySyncState.message ?? (syncUnlocked ? "Ready to sync." : syncSetupMessage);
|
|
3818
4166
|
const manualSyncScopeSummary = hasCompanyContext ? `Manual sync: ${currentCompanyName}` : "Manual sync: all companies";
|
|
3819
4167
|
const syncSummarySecondaryText = syncProgress ? [
|
|
3820
4168
|
manualSyncScopeSummary,
|
|
4169
|
+
cancellationRequested ? "Stopping after the current step" : null,
|
|
3821
4170
|
syncProgress.issueProgressLabel,
|
|
3822
4171
|
syncProgress.currentIssueLabel ?? syncProgress.repositoryPosition,
|
|
3823
4172
|
`Auto-sync: ${scheduleDescription}`
|
|
@@ -3946,13 +4295,14 @@ function GitHubSyncSettingsPage() {
|
|
|
3946
4295
|
if (!companyId) {
|
|
3947
4296
|
throw new Error("Company context is required to save the GitHub token.");
|
|
3948
4297
|
}
|
|
3949
|
-
|
|
4298
|
+
const pluginId = await resolveCurrentPluginId(pluginIdFromLocation);
|
|
4299
|
+
if (!pluginId) {
|
|
3950
4300
|
throw new Error("Plugin id is required to save the GitHub token.");
|
|
3951
4301
|
}
|
|
3952
4302
|
const trimmedToken = tokenDraft.trim();
|
|
3953
4303
|
const secretName = `github_sync_${companyId.replace(/[^a-z0-9]+/gi, "_").toLowerCase()}`;
|
|
3954
4304
|
const secret = await resolveOrCreateCompanySecret(companyId, secretName, trimmedToken);
|
|
3955
|
-
await patchPluginConfig(
|
|
4305
|
+
await patchPluginConfig(pluginId, {
|
|
3956
4306
|
githubTokenRef: secret.id
|
|
3957
4307
|
});
|
|
3958
4308
|
await saveRegistration({
|
|
@@ -3997,7 +4347,8 @@ function GitHubSyncSettingsPage() {
|
|
|
3997
4347
|
if (!companyId) {
|
|
3998
4348
|
throw new Error("Company context is required to connect Paperclip board access.");
|
|
3999
4349
|
}
|
|
4000
|
-
|
|
4350
|
+
const pluginId = await resolveCurrentPluginId(pluginIdFromLocation);
|
|
4351
|
+
if (!pluginId) {
|
|
4001
4352
|
throw new Error("Plugin id is required to connect Paperclip board access.");
|
|
4002
4353
|
}
|
|
4003
4354
|
if (typeof window !== "undefined") {
|
|
@@ -4020,7 +4371,7 @@ function GitHubSyncSettingsPage() {
|
|
|
4020
4371
|
const identity = await fetchBoardAccessIdentity(boardApiToken);
|
|
4021
4372
|
const secretName = `paperclip_board_api_${companyId.replace(/[^a-z0-9]+/gi, "_").toLowerCase()}`;
|
|
4022
4373
|
const secret = await resolveOrCreateCompanySecret(companyId, secretName, boardApiToken);
|
|
4023
|
-
await patchPluginConfig(
|
|
4374
|
+
await patchPluginConfig(pluginId, {
|
|
4024
4375
|
paperclipBoardApiTokenRefs: {
|
|
4025
4376
|
[companyId]: secret.id
|
|
4026
4377
|
}
|
|
@@ -4184,6 +4535,46 @@ function GitHubSyncSettingsPage() {
|
|
|
4184
4535
|
setRunningSync(false);
|
|
4185
4536
|
}
|
|
4186
4537
|
}
|
|
4538
|
+
async function handleCancelSync() {
|
|
4539
|
+
if (!syncPersistedRunning) {
|
|
4540
|
+
return;
|
|
4541
|
+
}
|
|
4542
|
+
setCancellingSync(true);
|
|
4543
|
+
setManualSyncRequestError(null);
|
|
4544
|
+
try {
|
|
4545
|
+
const result = await cancelSync();
|
|
4546
|
+
setForm((current) => ({
|
|
4547
|
+
...current,
|
|
4548
|
+
syncState: result.syncState
|
|
4549
|
+
}));
|
|
4550
|
+
toast({
|
|
4551
|
+
title: getSyncToastTitle(result.syncState),
|
|
4552
|
+
body: getSyncToastBody(result.syncState),
|
|
4553
|
+
tone: getSyncToastTone(result.syncState)
|
|
4554
|
+
});
|
|
4555
|
+
armSyncCompletionToast(result.syncState);
|
|
4556
|
+
try {
|
|
4557
|
+
await settings.refresh();
|
|
4558
|
+
} catch {
|
|
4559
|
+
return;
|
|
4560
|
+
}
|
|
4561
|
+
} catch (error) {
|
|
4562
|
+
const message = error instanceof Error ? error.message : "Unable to cancel sync.";
|
|
4563
|
+
setManualSyncRequestError(message);
|
|
4564
|
+
toast({
|
|
4565
|
+
title: "Unable to cancel GitHub sync",
|
|
4566
|
+
body: message,
|
|
4567
|
+
tone: "error"
|
|
4568
|
+
});
|
|
4569
|
+
try {
|
|
4570
|
+
await settings.refresh();
|
|
4571
|
+
} catch {
|
|
4572
|
+
return;
|
|
4573
|
+
}
|
|
4574
|
+
} finally {
|
|
4575
|
+
setCancellingSync(false);
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4187
4578
|
return /* @__PURE__ */ jsxs("div", { className: "ghsync", style: themeVars, children: [
|
|
4188
4579
|
/* @__PURE__ */ jsx("style", { children: PAGE_STYLES }),
|
|
4189
4580
|
/* @__PURE__ */ jsxs("section", { className: "ghsync__header", children: [
|
|
@@ -4638,10 +5029,10 @@ function GitHubSyncSettingsPage() {
|
|
|
4638
5029
|
"button",
|
|
4639
5030
|
{
|
|
4640
5031
|
type: "button",
|
|
4641
|
-
className: getPluginActionClassName({ variant: "primary" }),
|
|
4642
|
-
onClick: handleRunSyncNow,
|
|
4643
|
-
disabled:
|
|
4644
|
-
children:
|
|
5032
|
+
className: getPluginActionClassName({ variant: syncPersistedRunning ? "danger" : "primary" }),
|
|
5033
|
+
onClick: syncPersistedRunning ? handleCancelSync : handleRunSyncNow,
|
|
5034
|
+
disabled: showInitialLoadingState || syncStartPending || (syncPersistedRunning ? cancellationRequested : false),
|
|
5035
|
+
children: syncPersistedRunning ? cancellationRequested ? "Cancelling\u2026" : "Cancel sync" : syncStartPending ? "Running\u2026" : manualSyncButtonLabel
|
|
4645
5036
|
}
|
|
4646
5037
|
)
|
|
4647
5038
|
] })
|
|
@@ -4727,9 +5118,11 @@ function GitHubSyncDashboardWidget() {
|
|
|
4727
5118
|
hostContext.companyId ? { companyId: hostContext.companyId } : {}
|
|
4728
5119
|
);
|
|
4729
5120
|
const runSyncNow = usePluginAction("sync.runNow");
|
|
5121
|
+
const cancelSync = usePluginAction("sync.cancel");
|
|
4730
5122
|
const [runningSync, setRunningSync] = useState(false);
|
|
5123
|
+
const [cancellingSync, setCancellingSync] = useState(false);
|
|
4731
5124
|
const [manualSyncRequestError, setManualSyncRequestError] = useState(null);
|
|
4732
|
-
const [settingsHref, setSettingsHref] = useState(
|
|
5125
|
+
const [settingsHref, setSettingsHref] = useState(SETTINGS_INDEX_HREF2);
|
|
4733
5126
|
const [cachedSettings, setCachedSettings] = useState(null);
|
|
4734
5127
|
const themeMode = useResolvedThemeMode();
|
|
4735
5128
|
const boardAccessRequirement = usePaperclipBoardAccessRequirement();
|
|
@@ -4758,7 +5151,10 @@ function GitHubSyncDashboardWidget() {
|
|
|
4758
5151
|
hasBoardAccess: boardAccessReady
|
|
4759
5152
|
});
|
|
4760
5153
|
const syncUnlocked = syncSetupIssue === null;
|
|
4761
|
-
const
|
|
5154
|
+
const syncPersistedRunning = displaySyncState.status === "running";
|
|
5155
|
+
const syncStartPending = runningSync && !syncPersistedRunning;
|
|
5156
|
+
const syncInFlight = syncStartPending || syncPersistedRunning;
|
|
5157
|
+
const cancellationRequested = syncPersistedRunning && (cancellingSync || isSyncCancellationRequested(displaySyncState));
|
|
4762
5158
|
const scheduleFrequencyMinutes = normalizeScheduleFrequencyMinutes(current.scheduleFrequencyMinutes);
|
|
4763
5159
|
const scheduleDescription = formatScheduleFrequency(scheduleFrequencyMinutes);
|
|
4764
5160
|
const summary = getDashboardSummary({
|
|
@@ -4792,7 +5188,7 @@ function GitHubSyncDashboardWidget() {
|
|
|
4792
5188
|
}
|
|
4793
5189
|
} catch {
|
|
4794
5190
|
if (!cancelled) {
|
|
4795
|
-
setSettingsHref(
|
|
5191
|
+
setSettingsHref(SETTINGS_INDEX_HREF2);
|
|
4796
5192
|
}
|
|
4797
5193
|
}
|
|
4798
5194
|
}
|
|
@@ -4858,6 +5254,34 @@ function GitHubSyncDashboardWidget() {
|
|
|
4858
5254
|
setRunningSync(false);
|
|
4859
5255
|
}
|
|
4860
5256
|
}
|
|
5257
|
+
async function handleCancelSync() {
|
|
5258
|
+
if (!syncPersistedRunning) {
|
|
5259
|
+
return;
|
|
5260
|
+
}
|
|
5261
|
+
setCancellingSync(true);
|
|
5262
|
+
setManualSyncRequestError(null);
|
|
5263
|
+
try {
|
|
5264
|
+
const result = await cancelSync();
|
|
5265
|
+
const nextSyncState = result.syncState ?? EMPTY_SETTINGS.syncState;
|
|
5266
|
+
toast({
|
|
5267
|
+
title: getSyncToastTitle(nextSyncState),
|
|
5268
|
+
body: getSyncToastBody(nextSyncState),
|
|
5269
|
+
tone: getSyncToastTone(nextSyncState)
|
|
5270
|
+
});
|
|
5271
|
+
armSyncCompletionToast(nextSyncState);
|
|
5272
|
+
await settings.refresh();
|
|
5273
|
+
} catch (error) {
|
|
5274
|
+
const message = error instanceof Error ? error.message : "Unable to cancel GitHub sync.";
|
|
5275
|
+
setManualSyncRequestError(message);
|
|
5276
|
+
toast({
|
|
5277
|
+
title: "Unable to cancel GitHub sync",
|
|
5278
|
+
body: message,
|
|
5279
|
+
tone: "error"
|
|
5280
|
+
});
|
|
5281
|
+
} finally {
|
|
5282
|
+
setCancellingSync(false);
|
|
5283
|
+
}
|
|
5284
|
+
}
|
|
4861
5285
|
return /* @__PURE__ */ jsxs("section", { className: "ghsync-widget", style: themeVars, children: [
|
|
4862
5286
|
/* @__PURE__ */ jsx("style", { children: WIDGET_STYLES }),
|
|
4863
5287
|
/* @__PURE__ */ jsxs("div", { className: "ghsync-widget__card", children: [
|
|
@@ -4940,10 +5364,10 @@ function GitHubSyncDashboardWidget() {
|
|
|
4940
5364
|
"button",
|
|
4941
5365
|
{
|
|
4942
5366
|
type: "button",
|
|
4943
|
-
className: getPluginActionClassName({ variant: "primary" }),
|
|
4944
|
-
onClick: handleRunSync,
|
|
4945
|
-
disabled:
|
|
4946
|
-
children:
|
|
5367
|
+
className: getPluginActionClassName({ variant: syncPersistedRunning ? "danger" : "primary" }),
|
|
5368
|
+
onClick: syncPersistedRunning ? handleCancelSync : handleRunSync,
|
|
5369
|
+
disabled: showInitialLoadingState || syncStartPending || (syncPersistedRunning ? cancellationRequested : false),
|
|
5370
|
+
children: syncPersistedRunning ? cancellationRequested ? "Cancelling\u2026" : "Cancel sync" : syncStartPending ? "Running\u2026" : "Run sync now"
|
|
4947
5371
|
}
|
|
4948
5372
|
) : null
|
|
4949
5373
|
] }) })
|
|
@@ -4971,6 +5395,7 @@ function GitHubMarkIcon(props) {
|
|
|
4971
5395
|
function GitHubSyncToolbarButtonSurface(props) {
|
|
4972
5396
|
const toast = usePluginToast();
|
|
4973
5397
|
const runSyncNow = usePluginAction("sync.runNow");
|
|
5398
|
+
const cancelSync = usePluginAction("sync.cancel");
|
|
4974
5399
|
const pluginIdFromLocation = getPluginIdFromLocation();
|
|
4975
5400
|
const surfaceRef = useRef(null);
|
|
4976
5401
|
const resolvedIssue = useResolvedIssueId({
|
|
@@ -4990,6 +5415,7 @@ function GitHubSyncToolbarButtonSurface(props) {
|
|
|
4990
5415
|
props.companyId ? { companyId: props.companyId } : {}
|
|
4991
5416
|
);
|
|
4992
5417
|
const [runningSync, setRunningSync] = useState(false);
|
|
5418
|
+
const [cancellingSync, setCancellingSync] = useState(false);
|
|
4993
5419
|
const themeMode = useResolvedThemeMode();
|
|
4994
5420
|
const boardAccessRequirement = usePaperclipBoardAccessRequirement();
|
|
4995
5421
|
const theme = themeMode === "light" ? LIGHT_PALETTE : DARK_PALETTE;
|
|
@@ -5009,7 +5435,10 @@ function GitHubSyncToolbarButtonSurface(props) {
|
|
|
5009
5435
|
const effectiveCanRun = state.canRun && !boardAccessSetupIssue;
|
|
5010
5436
|
const effectiveMessage = boardAccessSetupIssue ? getSyncSetupMessage(boardAccessSetupIssue, hasCompanyContext) : state.message;
|
|
5011
5437
|
const effectiveLabel = boardAccessSetupIssue ? "Board access required" : state.label;
|
|
5012
|
-
const
|
|
5438
|
+
const syncPersistedRunning = state.syncState.status === "running";
|
|
5439
|
+
const syncStartPending = runningSync && !syncPersistedRunning;
|
|
5440
|
+
const syncInFlight = syncStartPending || syncPersistedRunning;
|
|
5441
|
+
const cancellationRequested = syncPersistedRunning && (cancellingSync || isSyncCancellationRequested(state.syncState));
|
|
5013
5442
|
const armSyncCompletionToast = useSyncCompletionToast(state.syncState, toast);
|
|
5014
5443
|
useEffect(() => {
|
|
5015
5444
|
if (state.syncState.status !== "running") {
|
|
@@ -5114,6 +5543,31 @@ function GitHubSyncToolbarButtonSurface(props) {
|
|
|
5114
5543
|
setRunningSync(false);
|
|
5115
5544
|
}
|
|
5116
5545
|
}
|
|
5546
|
+
async function handleCancelSync() {
|
|
5547
|
+
if (!syncPersistedRunning) {
|
|
5548
|
+
return;
|
|
5549
|
+
}
|
|
5550
|
+
try {
|
|
5551
|
+
setCancellingSync(true);
|
|
5552
|
+
const result = await cancelSync();
|
|
5553
|
+
const nextSyncState = result.syncState ?? EMPTY_SETTINGS.syncState;
|
|
5554
|
+
toast({
|
|
5555
|
+
title: getSyncToastTitle(nextSyncState),
|
|
5556
|
+
body: getSyncToastBody(nextSyncState),
|
|
5557
|
+
tone: getSyncToastTone(nextSyncState)
|
|
5558
|
+
});
|
|
5559
|
+
armSyncCompletionToast(nextSyncState);
|
|
5560
|
+
toolbarState.refresh();
|
|
5561
|
+
} catch (error) {
|
|
5562
|
+
toast({
|
|
5563
|
+
title: "Unable to cancel GitHub sync",
|
|
5564
|
+
body: error instanceof Error ? error.message : "Unable to cancel GitHub sync.",
|
|
5565
|
+
tone: "error"
|
|
5566
|
+
});
|
|
5567
|
+
} finally {
|
|
5568
|
+
setCancellingSync(false);
|
|
5569
|
+
}
|
|
5570
|
+
}
|
|
5117
5571
|
return /* @__PURE__ */ jsxs(
|
|
5118
5572
|
"div",
|
|
5119
5573
|
{
|
|
@@ -5131,11 +5585,11 @@ function GitHubSyncToolbarButtonSurface(props) {
|
|
|
5131
5585
|
"data-variant": "outline",
|
|
5132
5586
|
"data-size": "sm",
|
|
5133
5587
|
className: props.entityType ? HOST_ENTITY_BUTTON_CLASSNAME : HOST_GLOBAL_BUTTON_CLASSNAME,
|
|
5134
|
-
disabled:
|
|
5135
|
-
onClick: handleRunSync,
|
|
5588
|
+
disabled: toolbarState.loading || syncStartPending || (syncPersistedRunning ? cancellationRequested : !effectiveCanRun),
|
|
5589
|
+
onClick: syncPersistedRunning ? handleCancelSync : handleRunSync,
|
|
5136
5590
|
children: [
|
|
5137
5591
|
/* @__PURE__ */ jsx(GitHubMarkIcon, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
5138
|
-
/* @__PURE__ */ jsx("span", { children:
|
|
5592
|
+
/* @__PURE__ */ jsx("span", { children: syncPersistedRunning ? cancellationRequested ? "Cancelling\u2026" : "Cancel sync" : syncStartPending ? "Syncing\u2026" : effectiveLabel })
|
|
5139
5593
|
]
|
|
5140
5594
|
}
|
|
5141
5595
|
)
|