paperclip-github-plugin 0.5.2 → 0.6.0
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 +50 -3
- package/dist/manifest.js +21 -2
- package/dist/ui/index.js +589 -51
- package/dist/ui/index.js.map +3 -3
- package/dist/worker.js +1129 -61
- package/package.json +2 -2
package/dist/worker.js
CHANGED
|
@@ -561,6 +561,12 @@ function parseRepositoryReference(repositoryInput) {
|
|
|
561
561
|
}
|
|
562
562
|
}
|
|
563
563
|
|
|
564
|
+
// src/kpi-contract.ts
|
|
565
|
+
var GITHUB_SYNC_PLUGIN_ID = "paperclip-github-plugin";
|
|
566
|
+
var COMPANY_METRIC_WEBHOOK_ENDPOINT_KEY = "record-company-metric-event";
|
|
567
|
+
var COMPANY_METRIC_WEBHOOK_PATH = `/api/plugins/${GITHUB_SYNC_PLUGIN_ID}/webhooks/${COMPANY_METRIC_WEBHOOK_ENDPOINT_KEY}`;
|
|
568
|
+
var COMPANY_METRIC_WEBHOOK_AUTH_HEADER = "authorization";
|
|
569
|
+
|
|
564
570
|
// src/paperclip-health.ts
|
|
565
571
|
function normalizeOptionalString(value) {
|
|
566
572
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
@@ -604,6 +610,10 @@ var IMPORT_REGISTRY_SCOPE = {
|
|
|
604
610
|
scopeKind: "instance",
|
|
605
611
|
stateKey: "paperclip-github-plugin-import-registry"
|
|
606
612
|
};
|
|
613
|
+
var COMPANY_KPI_SCOPE = {
|
|
614
|
+
scopeKind: "instance",
|
|
615
|
+
stateKey: "paperclip-github-plugin-company-kpis"
|
|
616
|
+
};
|
|
607
617
|
var DEFAULT_SCHEDULE_FREQUENCY_MINUTES = 15;
|
|
608
618
|
var DEFAULT_IMPORTED_ISSUE_STATUS = "backlog";
|
|
609
619
|
var DEFAULT_IGNORED_GITHUB_ISSUE_USERNAMES = ["renovate"];
|
|
@@ -629,6 +639,11 @@ var SYNC_PROGRESS_PERSIST_INTERVAL_MS = 250;
|
|
|
629
639
|
var MAX_SYNC_FAILURE_LOG_ENTRIES = 25;
|
|
630
640
|
var GITHUB_SECONDARY_RATE_LIMIT_FALLBACK_MS = 6e4;
|
|
631
641
|
var IMPORTED_ISSUE_WAKEUP_CONCURRENCY = 4;
|
|
642
|
+
var COMPANY_KPI_CHART_WINDOW_DAYS = 14;
|
|
643
|
+
var COMPANY_KPI_COMPARISON_WINDOW_DAYS = 30;
|
|
644
|
+
var MAX_COMPANY_BACKLOG_SNAPSHOTS = 180;
|
|
645
|
+
var MAX_COMPANY_ACTIVITY_ROLLUPS = 365;
|
|
646
|
+
var MAX_COMPANY_METRIC_EVENT_KEYS = 2e3;
|
|
632
647
|
var MISSING_GITHUB_TOKEN_SYNC_MESSAGE = "Configure a GitHub token before running sync.";
|
|
633
648
|
var MISSING_GITHUB_TOKEN_SYNC_ACTION = 'Open settings and save a GitHub token secret, or create $PAPERCLIP_HOME/plugins/github-sync/config.json (or ~/.paperclip/plugins/github-sync/config.json when PAPERCLIP_HOME is unset) with a "githubToken" value, and then run sync again.';
|
|
634
649
|
var MISSING_MAPPING_SYNC_MESSAGE = "Save at least one mapping with a created Paperclip project before running sync.";
|
|
@@ -646,6 +661,7 @@ var AI_AUTHORED_MARKDOWN_FOOTER_PATTERN = /\n\n---\n###### ✨ This (?:comment|i
|
|
|
646
661
|
var HIDDEN_GITHUB_IMPORT_MARKER_PREFIX = "<!-- paperclip-github-plugin-imported-from: ";
|
|
647
662
|
var HIDDEN_GITHUB_IMPORT_MARKER_SUFFIX = " -->";
|
|
648
663
|
var EMPTY_GITHUB_ISSUE_DESCRIPTION_PLACEHOLDER = "_No description provided on GitHub._";
|
|
664
|
+
var pluginRuntimeContext = null;
|
|
649
665
|
function normalizeCompanyId(value) {
|
|
650
666
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
651
667
|
}
|
|
@@ -2528,6 +2544,144 @@ async function buildToolbarSyncState(ctx, input) {
|
|
|
2528
2544
|
savedMappingCount
|
|
2529
2545
|
};
|
|
2530
2546
|
}
|
|
2547
|
+
function shiftIsoDay(day, offsetDays) {
|
|
2548
|
+
const parsed = /* @__PURE__ */ new Date(`${day}T00:00:00.000Z`);
|
|
2549
|
+
parsed.setUTCDate(parsed.getUTCDate() + offsetDays);
|
|
2550
|
+
return parsed.toISOString().slice(0, 10);
|
|
2551
|
+
}
|
|
2552
|
+
function listIsoDayWindow(endDay, days) {
|
|
2553
|
+
return Array.from(
|
|
2554
|
+
{ length: Math.max(0, days) },
|
|
2555
|
+
(_, index) => shiftIsoDay(endDay, index - (days - 1))
|
|
2556
|
+
);
|
|
2557
|
+
}
|
|
2558
|
+
function getBacklogValueOnOrBeforeDay(snapshots, day) {
|
|
2559
|
+
let currentValue;
|
|
2560
|
+
for (const snapshot of snapshots) {
|
|
2561
|
+
if (snapshot.day > day) {
|
|
2562
|
+
break;
|
|
2563
|
+
}
|
|
2564
|
+
currentValue = snapshot.openIssueCount;
|
|
2565
|
+
}
|
|
2566
|
+
return currentValue;
|
|
2567
|
+
}
|
|
2568
|
+
function buildBacklogHistorySeries(snapshots, endDay, days) {
|
|
2569
|
+
const series = [];
|
|
2570
|
+
for (const day of listIsoDayWindow(endDay, days)) {
|
|
2571
|
+
const value = getBacklogValueOnOrBeforeDay(snapshots, day);
|
|
2572
|
+
if (value === void 0) {
|
|
2573
|
+
continue;
|
|
2574
|
+
}
|
|
2575
|
+
series.push({
|
|
2576
|
+
day,
|
|
2577
|
+
value
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
return series;
|
|
2581
|
+
}
|
|
2582
|
+
function getCompanyActivityMetricValue(rollup, metric) {
|
|
2583
|
+
return metric === "githubIssuesClosedCount" ? rollup.githubIssuesClosedCount : rollup.paperclipPullRequestsCreatedCount;
|
|
2584
|
+
}
|
|
2585
|
+
function buildActivityHistorySeries(rollups, metric, endDay, days) {
|
|
2586
|
+
const rollupsByDay = new Map(rollups.map((rollup) => [rollup.day, rollup]));
|
|
2587
|
+
return listIsoDayWindow(endDay, days).map((day) => ({
|
|
2588
|
+
day,
|
|
2589
|
+
value: getCompanyActivityMetricValue(
|
|
2590
|
+
rollupsByDay.get(day) ?? createEmptyCompanyActivityRollup(day, `${day}T00:00:00.000Z`),
|
|
2591
|
+
metric
|
|
2592
|
+
)
|
|
2593
|
+
}));
|
|
2594
|
+
}
|
|
2595
|
+
function sumActivityMetricForWindow(rollups, metric, endDay, days) {
|
|
2596
|
+
return buildActivityHistorySeries(rollups, metric, endDay, days).reduce((sum, point) => sum + point.value, 0);
|
|
2597
|
+
}
|
|
2598
|
+
async function buildDashboardMetricsData(ctx, input) {
|
|
2599
|
+
const companyId = normalizeCompanyId(input.companyId);
|
|
2600
|
+
if (!companyId) {
|
|
2601
|
+
return {
|
|
2602
|
+
status: "company_required",
|
|
2603
|
+
historyWindowDays: COMPANY_KPI_CHART_WINDOW_DAYS,
|
|
2604
|
+
comparisonWindowDays: COMPANY_KPI_COMPARISON_WINDOW_DAYS,
|
|
2605
|
+
notes: {
|
|
2606
|
+
backlogHistoryAvailable: false,
|
|
2607
|
+
activityHistoryAvailable: false
|
|
2608
|
+
}
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2611
|
+
const settings = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
|
|
2612
|
+
const mappings = getSyncableMappingsForScope(settings.mappings, companyId);
|
|
2613
|
+
const companyKpis = normalizeCompanyKpiState(await ctx.state.get(COMPANY_KPI_SCOPE));
|
|
2614
|
+
const backlogSnapshots = getCompanyBacklogSnapshots(companyKpis, companyId);
|
|
2615
|
+
const activityRollups = getCompanyActivityRollups(companyKpis, companyId);
|
|
2616
|
+
const latestBacklogSnapshot = backlogSnapshots.at(-1);
|
|
2617
|
+
const latestActivityRollup = activityRollups.at(-1);
|
|
2618
|
+
const backlogEndDay = latestBacklogSnapshot?.day;
|
|
2619
|
+
const activityEndDay = latestActivityRollup?.day;
|
|
2620
|
+
return {
|
|
2621
|
+
status: mappings.length > 0 ? "ready" : "no_mappings",
|
|
2622
|
+
companyId,
|
|
2623
|
+
mappedRepositoryCount: mappings.length,
|
|
2624
|
+
historyWindowDays: COMPANY_KPI_CHART_WINDOW_DAYS,
|
|
2625
|
+
comparisonWindowDays: COMPANY_KPI_COMPARISON_WINDOW_DAYS,
|
|
2626
|
+
backlog: {
|
|
2627
|
+
...latestBacklogSnapshot ? { lastCapturedAt: latestBacklogSnapshot.capturedAt } : {},
|
|
2628
|
+
...latestBacklogSnapshot ? { currentOpenIssueCount: latestBacklogSnapshot.openIssueCount } : {},
|
|
2629
|
+
...backlogEndDay ? {
|
|
2630
|
+
comparisonOpenIssueCount: getBacklogValueOnOrBeforeDay(
|
|
2631
|
+
backlogSnapshots,
|
|
2632
|
+
shiftIsoDay(backlogEndDay, -(COMPANY_KPI_COMPARISON_WINDOW_DAYS - 1))
|
|
2633
|
+
)
|
|
2634
|
+
} : {},
|
|
2635
|
+
history: backlogEndDay ? buildBacklogHistorySeries(backlogSnapshots, backlogEndDay, COMPANY_KPI_CHART_WINDOW_DAYS) : []
|
|
2636
|
+
},
|
|
2637
|
+
githubIssuesClosed: {
|
|
2638
|
+
...latestActivityRollup ? { lastRecordedAt: latestActivityRollup.updatedAt } : {},
|
|
2639
|
+
currentPeriodCount: activityEndDay ? sumActivityMetricForWindow(
|
|
2640
|
+
activityRollups,
|
|
2641
|
+
"githubIssuesClosedCount",
|
|
2642
|
+
activityEndDay,
|
|
2643
|
+
COMPANY_KPI_COMPARISON_WINDOW_DAYS
|
|
2644
|
+
) : 0,
|
|
2645
|
+
previousPeriodCount: activityEndDay ? sumActivityMetricForWindow(
|
|
2646
|
+
activityRollups,
|
|
2647
|
+
"githubIssuesClosedCount",
|
|
2648
|
+
shiftIsoDay(activityEndDay, -COMPANY_KPI_COMPARISON_WINDOW_DAYS),
|
|
2649
|
+
COMPANY_KPI_COMPARISON_WINDOW_DAYS
|
|
2650
|
+
) : 0,
|
|
2651
|
+
history: activityEndDay ? buildActivityHistorySeries(
|
|
2652
|
+
activityRollups,
|
|
2653
|
+
"githubIssuesClosedCount",
|
|
2654
|
+
activityEndDay,
|
|
2655
|
+
COMPANY_KPI_CHART_WINDOW_DAYS
|
|
2656
|
+
) : []
|
|
2657
|
+
},
|
|
2658
|
+
paperclipPullRequestsCreated: {
|
|
2659
|
+
...latestActivityRollup ? { lastRecordedAt: latestActivityRollup.updatedAt } : {},
|
|
2660
|
+
currentPeriodCount: activityEndDay ? sumActivityMetricForWindow(
|
|
2661
|
+
activityRollups,
|
|
2662
|
+
"paperclipPullRequestsCreatedCount",
|
|
2663
|
+
activityEndDay,
|
|
2664
|
+
COMPANY_KPI_COMPARISON_WINDOW_DAYS
|
|
2665
|
+
) : 0,
|
|
2666
|
+
previousPeriodCount: activityEndDay ? sumActivityMetricForWindow(
|
|
2667
|
+
activityRollups,
|
|
2668
|
+
"paperclipPullRequestsCreatedCount",
|
|
2669
|
+
shiftIsoDay(activityEndDay, -COMPANY_KPI_COMPARISON_WINDOW_DAYS),
|
|
2670
|
+
COMPANY_KPI_COMPARISON_WINDOW_DAYS
|
|
2671
|
+
) : 0,
|
|
2672
|
+
history: activityEndDay ? buildActivityHistorySeries(
|
|
2673
|
+
activityRollups,
|
|
2674
|
+
"paperclipPullRequestsCreatedCount",
|
|
2675
|
+
activityEndDay,
|
|
2676
|
+
COMPANY_KPI_CHART_WINDOW_DAYS
|
|
2677
|
+
) : []
|
|
2678
|
+
},
|
|
2679
|
+
notes: {
|
|
2680
|
+
backlogHistoryAvailable: backlogSnapshots.length > 0,
|
|
2681
|
+
activityHistoryAvailable: activityRollups.length > 0
|
|
2682
|
+
}
|
|
2683
|
+
};
|
|
2684
|
+
}
|
|
2531
2685
|
async function buildIssueGitHubDetails(ctx, input) {
|
|
2532
2686
|
const issueId = typeof input.issueId === "string" && input.issueId.trim() ? input.issueId.trim() : void 0;
|
|
2533
2687
|
const companyId = typeof input.companyId === "string" && input.companyId.trim() ? input.companyId.trim() : void 0;
|
|
@@ -3605,6 +3759,193 @@ function normalizeSettings(value) {
|
|
|
3605
3759
|
updatedAt: typeof record.updatedAt === "string" ? record.updatedAt : void 0
|
|
3606
3760
|
};
|
|
3607
3761
|
}
|
|
3762
|
+
function getIsoDayString(value) {
|
|
3763
|
+
return coerceDate(value).toISOString().slice(0, 10);
|
|
3764
|
+
}
|
|
3765
|
+
function parseDateValue(value) {
|
|
3766
|
+
if (value instanceof Date && !Number.isNaN(value.getTime())) {
|
|
3767
|
+
return value;
|
|
3768
|
+
}
|
|
3769
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
3770
|
+
const parsed = new Date(value);
|
|
3771
|
+
if (!Number.isNaN(parsed.getTime())) {
|
|
3772
|
+
return parsed;
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
return void 0;
|
|
3776
|
+
}
|
|
3777
|
+
function normalizeIsoDayString(value) {
|
|
3778
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
3779
|
+
return void 0;
|
|
3780
|
+
}
|
|
3781
|
+
return parseDateValue(value.trim())?.toISOString().slice(0, 10);
|
|
3782
|
+
}
|
|
3783
|
+
function normalizeIsoTimestampString(value) {
|
|
3784
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
3785
|
+
return void 0;
|
|
3786
|
+
}
|
|
3787
|
+
return parseDateValue(value.trim())?.toISOString();
|
|
3788
|
+
}
|
|
3789
|
+
function normalizeCompanyBacklogSnapshot(value) {
|
|
3790
|
+
if (!value || typeof value !== "object") {
|
|
3791
|
+
return null;
|
|
3792
|
+
}
|
|
3793
|
+
const record = value;
|
|
3794
|
+
const day = normalizeIsoDayString(record.day);
|
|
3795
|
+
const capturedAt = normalizeIsoTimestampString(record.capturedAt);
|
|
3796
|
+
const openIssueCount = typeof record.openIssueCount === "number" && record.openIssueCount >= 0 ? Math.floor(record.openIssueCount) : NaN;
|
|
3797
|
+
const repositoryCount = typeof record.repositoryCount === "number" && record.repositoryCount >= 0 ? Math.floor(record.repositoryCount) : NaN;
|
|
3798
|
+
if (!day || !capturedAt || Number.isNaN(openIssueCount) || Number.isNaN(repositoryCount)) {
|
|
3799
|
+
return null;
|
|
3800
|
+
}
|
|
3801
|
+
return {
|
|
3802
|
+
day,
|
|
3803
|
+
capturedAt,
|
|
3804
|
+
openIssueCount,
|
|
3805
|
+
repositoryCount
|
|
3806
|
+
};
|
|
3807
|
+
}
|
|
3808
|
+
function normalizeCompanyBacklogSnapshots(value) {
|
|
3809
|
+
if (!Array.isArray(value)) {
|
|
3810
|
+
return [];
|
|
3811
|
+
}
|
|
3812
|
+
const snapshotsByDay = /* @__PURE__ */ new Map();
|
|
3813
|
+
for (const entry of value) {
|
|
3814
|
+
const snapshot = normalizeCompanyBacklogSnapshot(entry);
|
|
3815
|
+
if (!snapshot) {
|
|
3816
|
+
continue;
|
|
3817
|
+
}
|
|
3818
|
+
const existing = snapshotsByDay.get(snapshot.day);
|
|
3819
|
+
if (!existing || existing.capturedAt.localeCompare(snapshot.capturedAt) <= 0) {
|
|
3820
|
+
snapshotsByDay.set(snapshot.day, snapshot);
|
|
3821
|
+
}
|
|
3822
|
+
}
|
|
3823
|
+
return [...snapshotsByDay.values()].sort((left, right) => left.day.localeCompare(right.day)).slice(-MAX_COMPANY_BACKLOG_SNAPSHOTS);
|
|
3824
|
+
}
|
|
3825
|
+
function createEmptyCompanyActivityRollup(day, updatedAt) {
|
|
3826
|
+
return {
|
|
3827
|
+
day,
|
|
3828
|
+
updatedAt,
|
|
3829
|
+
githubIssuesClosedCount: 0,
|
|
3830
|
+
paperclipPullRequestsCreatedCount: 0
|
|
3831
|
+
};
|
|
3832
|
+
}
|
|
3833
|
+
function normalizeCompanyActivityRollup(value) {
|
|
3834
|
+
if (!value || typeof value !== "object") {
|
|
3835
|
+
return null;
|
|
3836
|
+
}
|
|
3837
|
+
const record = value;
|
|
3838
|
+
const day = normalizeIsoDayString(record.day);
|
|
3839
|
+
const updatedAt = normalizeIsoTimestampString(record.updatedAt);
|
|
3840
|
+
const githubIssuesClosedCount = typeof record.githubIssuesClosedCount === "number" && record.githubIssuesClosedCount >= 0 ? Math.floor(record.githubIssuesClosedCount) : 0;
|
|
3841
|
+
const paperclipPullRequestsCreatedCount = typeof record.paperclipPullRequestsCreatedCount === "number" && record.paperclipPullRequestsCreatedCount >= 0 ? Math.floor(record.paperclipPullRequestsCreatedCount) : 0;
|
|
3842
|
+
const paperclipPullRequestsMergedCount = typeof record.paperclipPullRequestsMergedCount === "number" && record.paperclipPullRequestsMergedCount >= 0 ? Math.floor(record.paperclipPullRequestsMergedCount) : void 0;
|
|
3843
|
+
if (!day || !updatedAt) {
|
|
3844
|
+
return null;
|
|
3845
|
+
}
|
|
3846
|
+
return {
|
|
3847
|
+
day,
|
|
3848
|
+
updatedAt,
|
|
3849
|
+
githubIssuesClosedCount,
|
|
3850
|
+
paperclipPullRequestsCreatedCount,
|
|
3851
|
+
...paperclipPullRequestsMergedCount !== void 0 ? { paperclipPullRequestsMergedCount } : {}
|
|
3852
|
+
};
|
|
3853
|
+
}
|
|
3854
|
+
function normalizeCompanyActivityRollups(value) {
|
|
3855
|
+
if (!Array.isArray(value)) {
|
|
3856
|
+
return [];
|
|
3857
|
+
}
|
|
3858
|
+
const rollupsByDay = /* @__PURE__ */ new Map();
|
|
3859
|
+
for (const entry of value) {
|
|
3860
|
+
const rollup = normalizeCompanyActivityRollup(entry);
|
|
3861
|
+
if (!rollup) {
|
|
3862
|
+
continue;
|
|
3863
|
+
}
|
|
3864
|
+
const existing = rollupsByDay.get(rollup.day);
|
|
3865
|
+
if (!existing || existing.updatedAt.localeCompare(rollup.updatedAt) <= 0) {
|
|
3866
|
+
rollupsByDay.set(rollup.day, rollup);
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
return [...rollupsByDay.values()].sort((left, right) => left.day.localeCompare(right.day)).slice(-MAX_COMPANY_ACTIVITY_ROLLUPS);
|
|
3870
|
+
}
|
|
3871
|
+
function normalizeCompanyMetricEventKeyRecord(value) {
|
|
3872
|
+
if (!value || typeof value !== "object") {
|
|
3873
|
+
return null;
|
|
3874
|
+
}
|
|
3875
|
+
const record = value;
|
|
3876
|
+
const key = typeof record.key === "string" && record.key.trim() ? record.key.trim() : "";
|
|
3877
|
+
const recordedAt = typeof record.recordedAt === "string" ? coerceDate(record.recordedAt).toISOString() : void 0;
|
|
3878
|
+
if (!key || !recordedAt) {
|
|
3879
|
+
return null;
|
|
3880
|
+
}
|
|
3881
|
+
return {
|
|
3882
|
+
key,
|
|
3883
|
+
recordedAt
|
|
3884
|
+
};
|
|
3885
|
+
}
|
|
3886
|
+
function normalizeCompanyMetricEventKeyRecords(value) {
|
|
3887
|
+
if (!Array.isArray(value)) {
|
|
3888
|
+
return [];
|
|
3889
|
+
}
|
|
3890
|
+
const entries = value.map((entry) => normalizeCompanyMetricEventKeyRecord(entry)).filter((entry) => entry !== null).sort((left, right) => left.recordedAt.localeCompare(right.recordedAt));
|
|
3891
|
+
const keys = /* @__PURE__ */ new Set();
|
|
3892
|
+
const deduped = [];
|
|
3893
|
+
for (const entry of entries) {
|
|
3894
|
+
if (keys.has(entry.key)) {
|
|
3895
|
+
continue;
|
|
3896
|
+
}
|
|
3897
|
+
keys.add(entry.key);
|
|
3898
|
+
deduped.push(entry);
|
|
3899
|
+
}
|
|
3900
|
+
return deduped.slice(-MAX_COMPANY_METRIC_EVENT_KEYS);
|
|
3901
|
+
}
|
|
3902
|
+
function normalizeCompanyBacklogSnapshotsByCompanyId(value) {
|
|
3903
|
+
if (!value || typeof value !== "object") {
|
|
3904
|
+
return void 0;
|
|
3905
|
+
}
|
|
3906
|
+
const entries = Object.entries(value).map(([companyId, snapshots]) => {
|
|
3907
|
+
const normalizedCompanyId = normalizeCompanyId(companyId);
|
|
3908
|
+
const normalizedSnapshots = normalizeCompanyBacklogSnapshots(snapshots);
|
|
3909
|
+
return normalizedCompanyId && normalizedSnapshots.length > 0 ? [normalizedCompanyId, normalizedSnapshots] : null;
|
|
3910
|
+
}).filter((entry) => entry !== null);
|
|
3911
|
+
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
3912
|
+
}
|
|
3913
|
+
function normalizeCompanyActivityRollupsByCompanyId(value) {
|
|
3914
|
+
if (!value || typeof value !== "object") {
|
|
3915
|
+
return void 0;
|
|
3916
|
+
}
|
|
3917
|
+
const entries = Object.entries(value).map(([companyId, rollups]) => {
|
|
3918
|
+
const normalizedCompanyId = normalizeCompanyId(companyId);
|
|
3919
|
+
const normalizedRollups = normalizeCompanyActivityRollups(rollups);
|
|
3920
|
+
return normalizedCompanyId && normalizedRollups.length > 0 ? [normalizedCompanyId, normalizedRollups] : null;
|
|
3921
|
+
}).filter((entry) => entry !== null);
|
|
3922
|
+
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
3923
|
+
}
|
|
3924
|
+
function normalizeCompanyMetricEventKeysByCompanyId(value) {
|
|
3925
|
+
if (!value || typeof value !== "object") {
|
|
3926
|
+
return void 0;
|
|
3927
|
+
}
|
|
3928
|
+
const entries = Object.entries(value).map(([companyId, records]) => {
|
|
3929
|
+
const normalizedCompanyId = normalizeCompanyId(companyId);
|
|
3930
|
+
const normalizedRecords = normalizeCompanyMetricEventKeyRecords(records);
|
|
3931
|
+
return normalizedCompanyId && normalizedRecords.length > 0 ? [normalizedCompanyId, normalizedRecords] : null;
|
|
3932
|
+
}).filter((entry) => entry !== null);
|
|
3933
|
+
return entries.length > 0 ? Object.fromEntries(entries) : void 0;
|
|
3934
|
+
}
|
|
3935
|
+
function normalizeCompanyKpiState(value) {
|
|
3936
|
+
if (!value || typeof value !== "object") {
|
|
3937
|
+
return {};
|
|
3938
|
+
}
|
|
3939
|
+
const record = value;
|
|
3940
|
+
const backlogSnapshotsByCompanyId = normalizeCompanyBacklogSnapshotsByCompanyId(record.backlogSnapshotsByCompanyId);
|
|
3941
|
+
const activityRollupsByCompanyId = normalizeCompanyActivityRollupsByCompanyId(record.activityRollupsByCompanyId);
|
|
3942
|
+
const metricEventKeysByCompanyId = normalizeCompanyMetricEventKeysByCompanyId(record.metricEventKeysByCompanyId);
|
|
3943
|
+
return {
|
|
3944
|
+
...backlogSnapshotsByCompanyId ? { backlogSnapshotsByCompanyId } : {},
|
|
3945
|
+
...activityRollupsByCompanyId ? { activityRollupsByCompanyId } : {},
|
|
3946
|
+
...metricEventKeysByCompanyId ? { metricEventKeysByCompanyId } : {}
|
|
3947
|
+
};
|
|
3948
|
+
}
|
|
3608
3949
|
function getScopedSyncState(settings, companyId) {
|
|
3609
3950
|
const normalizedCompanyId = normalizeCompanyId(companyId);
|
|
3610
3951
|
if (!normalizedCompanyId) {
|
|
@@ -3702,6 +4043,149 @@ function getSyncableMappingsForScope(mappings, companyId) {
|
|
|
3702
4043
|
function hasAnyScopedValue(valueByCompanyId) {
|
|
3703
4044
|
return Boolean(valueByCompanyId && Object.keys(valueByCompanyId).length > 0);
|
|
3704
4045
|
}
|
|
4046
|
+
function getCompanyBacklogSnapshots(state, companyId) {
|
|
4047
|
+
const normalizedCompanyId = normalizeCompanyId(companyId);
|
|
4048
|
+
if (!normalizedCompanyId) {
|
|
4049
|
+
return [];
|
|
4050
|
+
}
|
|
4051
|
+
return normalizeCompanyBacklogSnapshots(state.backlogSnapshotsByCompanyId?.[normalizedCompanyId]);
|
|
4052
|
+
}
|
|
4053
|
+
function getCompanyActivityRollups(state, companyId) {
|
|
4054
|
+
const normalizedCompanyId = normalizeCompanyId(companyId);
|
|
4055
|
+
if (!normalizedCompanyId) {
|
|
4056
|
+
return [];
|
|
4057
|
+
}
|
|
4058
|
+
return normalizeCompanyActivityRollups(state.activityRollupsByCompanyId?.[normalizedCompanyId]);
|
|
4059
|
+
}
|
|
4060
|
+
function upsertCompanyBacklogSnapshot(state, companyId, snapshot) {
|
|
4061
|
+
const normalizedCompanyId = normalizeCompanyId(companyId);
|
|
4062
|
+
if (!normalizedCompanyId) {
|
|
4063
|
+
return state;
|
|
4064
|
+
}
|
|
4065
|
+
const nextSnapshots = [
|
|
4066
|
+
...getCompanyBacklogSnapshots(state, normalizedCompanyId).filter((entry) => entry.day !== snapshot.day),
|
|
4067
|
+
snapshot
|
|
4068
|
+
].sort((left, right) => left.day.localeCompare(right.day)).slice(-MAX_COMPANY_BACKLOG_SNAPSHOTS);
|
|
4069
|
+
return {
|
|
4070
|
+
...state,
|
|
4071
|
+
backlogSnapshotsByCompanyId: {
|
|
4072
|
+
...state.backlogSnapshotsByCompanyId ?? {},
|
|
4073
|
+
[normalizedCompanyId]: nextSnapshots
|
|
4074
|
+
}
|
|
4075
|
+
};
|
|
4076
|
+
}
|
|
4077
|
+
function incrementCompanyActivityRollup(state, params) {
|
|
4078
|
+
const normalizedCompanyId = normalizeCompanyId(params.companyId);
|
|
4079
|
+
if (!normalizedCompanyId) {
|
|
4080
|
+
return state;
|
|
4081
|
+
}
|
|
4082
|
+
const occurredAt = coerceDate(params.occurredAt ?? /* @__PURE__ */ new Date()).toISOString();
|
|
4083
|
+
const day = getIsoDayString(occurredAt);
|
|
4084
|
+
const count = Math.max(1, Math.floor(params.count ?? 1));
|
|
4085
|
+
const nextRollups = [...getCompanyActivityRollups(state, normalizedCompanyId)];
|
|
4086
|
+
const existingIndex = nextRollups.findIndex((entry) => entry.day === day);
|
|
4087
|
+
const existing = existingIndex >= 0 ? nextRollups[existingIndex] : createEmptyCompanyActivityRollup(day, occurredAt);
|
|
4088
|
+
const updated = {
|
|
4089
|
+
...existing,
|
|
4090
|
+
updatedAt: occurredAt,
|
|
4091
|
+
[params.metric]: existing[params.metric] + count
|
|
4092
|
+
};
|
|
4093
|
+
if (existingIndex >= 0) {
|
|
4094
|
+
nextRollups.splice(existingIndex, 1, updated);
|
|
4095
|
+
} else {
|
|
4096
|
+
nextRollups.push(updated);
|
|
4097
|
+
}
|
|
4098
|
+
nextRollups.sort((left, right) => left.day.localeCompare(right.day));
|
|
4099
|
+
return {
|
|
4100
|
+
...state,
|
|
4101
|
+
activityRollupsByCompanyId: {
|
|
4102
|
+
...state.activityRollupsByCompanyId ?? {},
|
|
4103
|
+
[normalizedCompanyId]: nextRollups.slice(-MAX_COMPANY_ACTIVITY_ROLLUPS)
|
|
4104
|
+
}
|
|
4105
|
+
};
|
|
4106
|
+
}
|
|
4107
|
+
function hasRecordedCompanyMetricEventKey(state, companyId, eventKey) {
|
|
4108
|
+
const normalizedCompanyId = normalizeCompanyId(companyId);
|
|
4109
|
+
if (!normalizedCompanyId || !eventKey.trim()) {
|
|
4110
|
+
return false;
|
|
4111
|
+
}
|
|
4112
|
+
return (state.metricEventKeysByCompanyId?.[normalizedCompanyId] ?? []).some((entry) => entry.key === eventKey);
|
|
4113
|
+
}
|
|
4114
|
+
function rememberCompanyMetricEventKey(state, companyId, eventKey, recordedAt) {
|
|
4115
|
+
const normalizedCompanyId = normalizeCompanyId(companyId);
|
|
4116
|
+
const trimmedKey = eventKey.trim();
|
|
4117
|
+
if (!normalizedCompanyId || !trimmedKey) {
|
|
4118
|
+
return state;
|
|
4119
|
+
}
|
|
4120
|
+
const nextRecords = [
|
|
4121
|
+
...(state.metricEventKeysByCompanyId?.[normalizedCompanyId] ?? []).filter((entry) => entry.key !== trimmedKey),
|
|
4122
|
+
{
|
|
4123
|
+
key: trimmedKey,
|
|
4124
|
+
recordedAt
|
|
4125
|
+
}
|
|
4126
|
+
].sort((left, right) => left.recordedAt.localeCompare(right.recordedAt)).slice(-MAX_COMPANY_METRIC_EVENT_KEYS);
|
|
4127
|
+
return {
|
|
4128
|
+
...state,
|
|
4129
|
+
metricEventKeysByCompanyId: {
|
|
4130
|
+
...state.metricEventKeysByCompanyId ?? {},
|
|
4131
|
+
[normalizedCompanyId]: nextRecords
|
|
4132
|
+
}
|
|
4133
|
+
};
|
|
4134
|
+
}
|
|
4135
|
+
function buildCompanyMetricEventKey(params) {
|
|
4136
|
+
const pullRequestUrl = normalizeGitHubPullRequestHtmlUrl(params.pullRequestUrl);
|
|
4137
|
+
if (pullRequestUrl) {
|
|
4138
|
+
return `${params.metric}:${pullRequestUrl}`;
|
|
4139
|
+
}
|
|
4140
|
+
const repository = typeof params.repositoryUrl === "string" ? parseRepositoryReference(params.repositoryUrl) : null;
|
|
4141
|
+
const pullRequestNumber = typeof params.pullRequestNumber === "number" && params.pullRequestNumber >= 1 ? Math.floor(params.pullRequestNumber) : void 0;
|
|
4142
|
+
if (repository && pullRequestNumber) {
|
|
4143
|
+
return `${params.metric}:${repository.url}/pull/${pullRequestNumber}`;
|
|
4144
|
+
}
|
|
4145
|
+
const explicitEventKey = normalizeOptionalString2(params.eventKey);
|
|
4146
|
+
if (explicitEventKey) {
|
|
4147
|
+
return `${params.metric}:${explicitEventKey}`;
|
|
4148
|
+
}
|
|
4149
|
+
return void 0;
|
|
4150
|
+
}
|
|
4151
|
+
function recordCompanyActivityMetricEvent(state, params) {
|
|
4152
|
+
const normalizedCompanyId = normalizeCompanyId(params.companyId);
|
|
4153
|
+
if (!normalizedCompanyId) {
|
|
4154
|
+
return {
|
|
4155
|
+
state,
|
|
4156
|
+
recorded: false
|
|
4157
|
+
};
|
|
4158
|
+
}
|
|
4159
|
+
const occurredAt = coerceDate(params.occurredAt ?? /* @__PURE__ */ new Date()).toISOString();
|
|
4160
|
+
const eventKey = buildCompanyMetricEventKey({
|
|
4161
|
+
metric: params.metric,
|
|
4162
|
+
eventKey: params.eventKey,
|
|
4163
|
+
repositoryUrl: params.repositoryUrl,
|
|
4164
|
+
pullRequestNumber: params.pullRequestNumber,
|
|
4165
|
+
pullRequestUrl: params.pullRequestUrl
|
|
4166
|
+
});
|
|
4167
|
+
if (eventKey && hasRecordedCompanyMetricEventKey(state, normalizedCompanyId, eventKey)) {
|
|
4168
|
+
return {
|
|
4169
|
+
state,
|
|
4170
|
+
recorded: false,
|
|
4171
|
+
eventKey
|
|
4172
|
+
};
|
|
4173
|
+
}
|
|
4174
|
+
let nextState = incrementCompanyActivityRollup(state, {
|
|
4175
|
+
companyId: normalizedCompanyId,
|
|
4176
|
+
metric: params.metric,
|
|
4177
|
+
count: params.count,
|
|
4178
|
+
occurredAt
|
|
4179
|
+
});
|
|
4180
|
+
if (eventKey) {
|
|
4181
|
+
nextState = rememberCompanyMetricEventKey(nextState, normalizedCompanyId, eventKey, occurredAt);
|
|
4182
|
+
}
|
|
4183
|
+
return {
|
|
4184
|
+
state: nextState,
|
|
4185
|
+
recorded: true,
|
|
4186
|
+
...eventKey ? { eventKey } : {}
|
|
4187
|
+
};
|
|
4188
|
+
}
|
|
3705
4189
|
function normalizeImportRegistry(value) {
|
|
3706
4190
|
if (!Array.isArray(value)) {
|
|
3707
4191
|
return [];
|
|
@@ -3722,6 +4206,10 @@ function normalizeImportRegistry(value) {
|
|
|
3722
4206
|
const paperclipProjectId = typeof record.paperclipProjectId === "string" && record.paperclipProjectId.trim() ? record.paperclipProjectId.trim() : void 0;
|
|
3723
4207
|
const companyId = typeof record.companyId === "string" && record.companyId.trim() ? record.companyId.trim() : void 0;
|
|
3724
4208
|
const lastSeenCommentCount = typeof record.lastSeenCommentCount === "number" && record.lastSeenCommentCount >= 0 ? Math.floor(record.lastSeenCommentCount) : void 0;
|
|
4209
|
+
const lastSeenGitHubState = record.lastSeenGitHubState === "open" || record.lastSeenGitHubState === "closed" ? record.lastSeenGitHubState : void 0;
|
|
4210
|
+
const linkedPullRequestCommentCounts = normalizeGitHubPullRequestCommentCountRecords(
|
|
4211
|
+
record.linkedPullRequestCommentCounts
|
|
4212
|
+
);
|
|
3725
4213
|
if (!mappingId || Number.isNaN(githubIssueId) || !paperclipIssueId || !importedAt) {
|
|
3726
4214
|
return null;
|
|
3727
4215
|
}
|
|
@@ -3734,7 +4222,9 @@ function normalizeImportRegistry(value) {
|
|
|
3734
4222
|
...repositoryUrl ? { repositoryUrl } : {},
|
|
3735
4223
|
...paperclipProjectId ? { paperclipProjectId } : {},
|
|
3736
4224
|
...companyId ? { companyId } : {},
|
|
3737
|
-
...lastSeenCommentCount !== void 0 ? { lastSeenCommentCount } : {}
|
|
4225
|
+
...lastSeenCommentCount !== void 0 ? { lastSeenCommentCount } : {},
|
|
4226
|
+
...lastSeenGitHubState ? { lastSeenGitHubState } : {},
|
|
4227
|
+
...linkedPullRequestCommentCounts.length > 0 ? { linkedPullRequestCommentCounts } : {}
|
|
3738
4228
|
};
|
|
3739
4229
|
}).filter((entry) => entry !== null);
|
|
3740
4230
|
}
|
|
@@ -3780,6 +4270,8 @@ function buildImportedIssueRecord(mapping, issue, paperclipIssueId, importedAt)
|
|
|
3780
4270
|
paperclipIssueId,
|
|
3781
4271
|
importedAt,
|
|
3782
4272
|
lastSeenCommentCount: issue.commentsCount,
|
|
4273
|
+
lastSeenGitHubState: issue.state,
|
|
4274
|
+
linkedPullRequestCommentCounts: [],
|
|
3783
4275
|
repositoryUrl: getNormalizedMappingRepositoryUrl(mapping),
|
|
3784
4276
|
paperclipProjectId: mapping.paperclipProjectId,
|
|
3785
4277
|
companyId: mapping.companyId
|
|
@@ -3789,6 +4281,7 @@ function refreshImportedIssueRecordForMapping(record, mapping, issue) {
|
|
|
3789
4281
|
record.mappingId = mapping.id;
|
|
3790
4282
|
record.githubIssueNumber = issue.number;
|
|
3791
4283
|
record.lastSeenCommentCount ??= issue.commentsCount;
|
|
4284
|
+
record.lastSeenGitHubState ??= issue.state;
|
|
3792
4285
|
record.repositoryUrl = getNormalizedMappingRepositoryUrl(mapping);
|
|
3793
4286
|
record.paperclipProjectId = mapping.paperclipProjectId;
|
|
3794
4287
|
record.companyId = mapping.companyId;
|
|
@@ -4612,7 +5105,7 @@ function buildSyncFallbackExecutionStatePatch(params) {
|
|
|
4612
5105
|
return void 0;
|
|
4613
5106
|
}
|
|
4614
5107
|
function describeGitHubStatusTransitionReason(params) {
|
|
4615
|
-
const { snapshot,
|
|
5108
|
+
const { snapshot, hasTrustedNewComment, maintainerAuthoredImportedIssue } = params;
|
|
4616
5109
|
if (snapshot.state === "closed") {
|
|
4617
5110
|
switch (snapshot.stateReason) {
|
|
4618
5111
|
case "duplicate":
|
|
@@ -4623,8 +5116,7 @@ function describeGitHubStatusTransitionReason(params) {
|
|
|
4623
5116
|
return "the GitHub issue was closed as completed work";
|
|
4624
5117
|
}
|
|
4625
5118
|
}
|
|
4626
|
-
|
|
4627
|
-
if (snapshot.commentCount > baselineCommentCount && hasTrustedNewComment) {
|
|
5119
|
+
if (hasTrustedNewComment) {
|
|
4628
5120
|
return "a new GitHub comment from the issue author or a repository maintainer was added";
|
|
4629
5121
|
}
|
|
4630
5122
|
if (snapshot.linkedPullRequests.length === 0) {
|
|
@@ -4675,13 +5167,11 @@ function buildPaperclipIssueStatusTransitionComment(params) {
|
|
|
4675
5167
|
nextStatus,
|
|
4676
5168
|
repository,
|
|
4677
5169
|
snapshot,
|
|
4678
|
-
previousCommentCount,
|
|
4679
5170
|
hasTrustedNewComment,
|
|
4680
5171
|
maintainerAuthoredImportedIssue
|
|
4681
5172
|
} = params;
|
|
4682
5173
|
const reason = describeGitHubStatusTransitionReason({
|
|
4683
5174
|
snapshot,
|
|
4684
|
-
previousCommentCount,
|
|
4685
5175
|
hasTrustedNewComment,
|
|
4686
5176
|
maintainerAuthoredImportedIssue
|
|
4687
5177
|
});
|
|
@@ -4700,7 +5190,6 @@ function resolvePaperclipIssueStatus(params) {
|
|
|
4700
5190
|
const {
|
|
4701
5191
|
currentStatus,
|
|
4702
5192
|
snapshot,
|
|
4703
|
-
previousCommentCount,
|
|
4704
5193
|
hasTrustedNewComment,
|
|
4705
5194
|
wasImportedThisRun,
|
|
4706
5195
|
defaultImportedStatus,
|
|
@@ -4713,8 +5202,7 @@ function resolvePaperclipIssueStatus(params) {
|
|
|
4713
5202
|
if (currentStatus === "backlog" && !wasImportedThisRun) {
|
|
4714
5203
|
return "backlog";
|
|
4715
5204
|
}
|
|
4716
|
-
|
|
4717
|
-
if (snapshot.commentCount > baselineCommentCount && hasTrustedNewComment) {
|
|
5205
|
+
if (hasTrustedNewComment) {
|
|
4718
5206
|
return hasExecutorHandoffTarget ? "in_progress" : "todo";
|
|
4719
5207
|
}
|
|
4720
5208
|
if (snapshot.linkedPullRequests.length > 0) {
|
|
@@ -5214,54 +5702,203 @@ async function listNewGitHubIssueCommentsSinceCount(octokit, repository, issueNu
|
|
|
5214
5702
|
}
|
|
5215
5703
|
return comments;
|
|
5216
5704
|
}
|
|
5705
|
+
async function listNewGitHubPullRequestReviewCommentsSinceCount(octokit, repository, pullRequestNumber, previousCommentCount, currentCommentCount) {
|
|
5706
|
+
const normalizedPreviousCommentCount = Math.max(0, Math.floor(previousCommentCount));
|
|
5707
|
+
const normalizedCurrentCommentCount = Math.max(0, Math.floor(currentCommentCount));
|
|
5708
|
+
if (normalizedCurrentCommentCount <= normalizedPreviousCommentCount) {
|
|
5709
|
+
return [];
|
|
5710
|
+
}
|
|
5711
|
+
const newCommentCount = normalizedCurrentCommentCount - normalizedPreviousCommentCount;
|
|
5712
|
+
const comments = [];
|
|
5713
|
+
const perPage = 100;
|
|
5714
|
+
let page = Math.floor(normalizedPreviousCommentCount / perPage) + 1;
|
|
5715
|
+
let remainingOffset = normalizedPreviousCommentCount % perPage;
|
|
5716
|
+
while (comments.length < newCommentCount) {
|
|
5717
|
+
const response = await octokit.rest.pulls.listReviewComments({
|
|
5718
|
+
owner: repository.owner,
|
|
5719
|
+
repo: repository.repo,
|
|
5720
|
+
pull_number: pullRequestNumber,
|
|
5721
|
+
page,
|
|
5722
|
+
per_page: perPage,
|
|
5723
|
+
headers: {
|
|
5724
|
+
accept: "application/vnd.github+json",
|
|
5725
|
+
"X-GitHub-Api-Version": GITHUB_API_VERSION
|
|
5726
|
+
}
|
|
5727
|
+
});
|
|
5728
|
+
if (response.data.length === 0) {
|
|
5729
|
+
break;
|
|
5730
|
+
}
|
|
5731
|
+
for (const comment of response.data.slice(remainingOffset)) {
|
|
5732
|
+
comments.push({
|
|
5733
|
+
id: comment.id,
|
|
5734
|
+
body: typeof comment.body === "string" ? stripNullBytes(comment.body) : "",
|
|
5735
|
+
url: comment.html_url ?? void 0,
|
|
5736
|
+
authorLogin: normalizeGitHubUserLogin(comment.user?.login),
|
|
5737
|
+
...normalizeGitHubLowercaseString(comment.author_association) ? { authorAssociation: normalizeGitHubLowercaseString(comment.author_association) } : {},
|
|
5738
|
+
createdAt: comment.created_at ?? void 0,
|
|
5739
|
+
updatedAt: comment.updated_at ?? void 0
|
|
5740
|
+
});
|
|
5741
|
+
if (comments.length >= newCommentCount) {
|
|
5742
|
+
break;
|
|
5743
|
+
}
|
|
5744
|
+
}
|
|
5745
|
+
if (response.data.length < perPage) {
|
|
5746
|
+
break;
|
|
5747
|
+
}
|
|
5748
|
+
page += 1;
|
|
5749
|
+
remainingOffset = 0;
|
|
5750
|
+
}
|
|
5751
|
+
return comments;
|
|
5752
|
+
}
|
|
5753
|
+
async function hasTrustedGitHubCommentRecords(params) {
|
|
5754
|
+
const originalPosterLogin = normalizeGitHubUserLogin(params.originalPosterLogin);
|
|
5755
|
+
const unseenAuthors = /* @__PURE__ */ new Set();
|
|
5756
|
+
for (const comment of params.comments) {
|
|
5757
|
+
const authorLogin = normalizeGitHubUserLogin(comment.authorLogin);
|
|
5758
|
+
if (!authorLogin) {
|
|
5759
|
+
continue;
|
|
5760
|
+
}
|
|
5761
|
+
if (originalPosterLogin && authorLogin === originalPosterLogin) {
|
|
5762
|
+
return true;
|
|
5763
|
+
}
|
|
5764
|
+
const trustedAuthorStatus = cacheGitHubRepositoryTrustedAuthorStatusFromAssociation(
|
|
5765
|
+
params.repository,
|
|
5766
|
+
authorLogin,
|
|
5767
|
+
comment.authorAssociation,
|
|
5768
|
+
params.maintainerCache
|
|
5769
|
+
);
|
|
5770
|
+
if (trustedAuthorStatus === true) {
|
|
5771
|
+
return true;
|
|
5772
|
+
}
|
|
5773
|
+
if (trustedAuthorStatus === false) {
|
|
5774
|
+
continue;
|
|
5775
|
+
}
|
|
5776
|
+
unseenAuthors.add(authorLogin);
|
|
5777
|
+
}
|
|
5778
|
+
for (const authorLogin of unseenAuthors) {
|
|
5779
|
+
if (await isGitHubUserRepositoryMaintainer(
|
|
5780
|
+
params.octokit,
|
|
5781
|
+
params.repository,
|
|
5782
|
+
authorLogin,
|
|
5783
|
+
params.maintainerCache
|
|
5784
|
+
)) {
|
|
5785
|
+
return true;
|
|
5786
|
+
}
|
|
5787
|
+
}
|
|
5788
|
+
return false;
|
|
5789
|
+
}
|
|
5217
5790
|
async function hasTrustedNewGitHubIssueComment(params) {
|
|
5218
5791
|
const normalizedPreviousCommentCount = typeof params.previousCommentCount === "number" && params.previousCommentCount >= 0 ? Math.floor(params.previousCommentCount) : params.currentCommentCount;
|
|
5219
5792
|
const normalizedCurrentCommentCount = Math.max(0, Math.floor(params.currentCommentCount));
|
|
5220
5793
|
if (normalizedCurrentCommentCount <= normalizedPreviousCommentCount) {
|
|
5221
5794
|
return false;
|
|
5222
5795
|
}
|
|
5223
|
-
const newComments = await listNewGitHubIssueCommentsSinceCount(
|
|
5224
|
-
params.octokit,
|
|
5225
|
-
params.repository,
|
|
5226
|
-
params.githubIssue.number,
|
|
5227
|
-
normalizedPreviousCommentCount,
|
|
5228
|
-
normalizedCurrentCommentCount
|
|
5229
|
-
);
|
|
5230
|
-
if (newComments.length === 0) {
|
|
5796
|
+
const newComments = await listNewGitHubIssueCommentsSinceCount(
|
|
5797
|
+
params.octokit,
|
|
5798
|
+
params.repository,
|
|
5799
|
+
params.githubIssue.number,
|
|
5800
|
+
normalizedPreviousCommentCount,
|
|
5801
|
+
normalizedCurrentCommentCount
|
|
5802
|
+
);
|
|
5803
|
+
if (newComments.length === 0) {
|
|
5804
|
+
return false;
|
|
5805
|
+
}
|
|
5806
|
+
return hasTrustedGitHubCommentRecords({
|
|
5807
|
+
octokit: params.octokit,
|
|
5808
|
+
repository: params.repository,
|
|
5809
|
+
comments: newComments,
|
|
5810
|
+
originalPosterLogin: params.githubIssue.authorLogin,
|
|
5811
|
+
maintainerCache: params.maintainerCache
|
|
5812
|
+
});
|
|
5813
|
+
}
|
|
5814
|
+
async function getGitHubPullRequestCommentCountRecord(octokit, repository, pullRequestNumber, cache) {
|
|
5815
|
+
const pullRequestReference = {
|
|
5816
|
+
number: pullRequestNumber,
|
|
5817
|
+
repositoryUrl: repository.url
|
|
5818
|
+
};
|
|
5819
|
+
const cacheKey = buildGitHubPullRequestReferenceKey(pullRequestReference);
|
|
5820
|
+
const cachedRecord = cache.get(cacheKey);
|
|
5821
|
+
if (cachedRecord) {
|
|
5822
|
+
return cachedRecord;
|
|
5823
|
+
}
|
|
5824
|
+
const response = await octokit.rest.pulls.get({
|
|
5825
|
+
owner: repository.owner,
|
|
5826
|
+
repo: repository.repo,
|
|
5827
|
+
pull_number: pullRequestNumber,
|
|
5828
|
+
headers: {
|
|
5829
|
+
"X-GitHub-Api-Version": GITHUB_API_VERSION
|
|
5830
|
+
}
|
|
5831
|
+
});
|
|
5832
|
+
const record = {
|
|
5833
|
+
...pullRequestReference,
|
|
5834
|
+
topLevelCommentCount: typeof response.data.comments === "number" && response.data.comments >= 0 ? Math.floor(response.data.comments) : 0,
|
|
5835
|
+
reviewCommentCount: typeof response.data.review_comments === "number" && response.data.review_comments >= 0 ? Math.floor(response.data.review_comments) : 0
|
|
5836
|
+
};
|
|
5837
|
+
cache.set(cacheKey, record);
|
|
5838
|
+
return record;
|
|
5839
|
+
}
|
|
5840
|
+
async function listGitHubPullRequestCommentCountRecords(octokit, linkedPullRequests, cache) {
|
|
5841
|
+
const commentCounts = [];
|
|
5842
|
+
for (const pullRequest of linkedPullRequests) {
|
|
5843
|
+
commentCounts.push(
|
|
5844
|
+
await getGitHubPullRequestCommentCountRecord(
|
|
5845
|
+
octokit,
|
|
5846
|
+
requireRepositoryReference(pullRequest.repositoryUrl),
|
|
5847
|
+
pullRequest.number,
|
|
5848
|
+
cache
|
|
5849
|
+
)
|
|
5850
|
+
);
|
|
5851
|
+
}
|
|
5852
|
+
return normalizeGitHubPullRequestCommentCountRecords(commentCounts);
|
|
5853
|
+
}
|
|
5854
|
+
async function hasTrustedNewLinkedPullRequestComments(params) {
|
|
5855
|
+
if ((params.currentCommentCounts?.length ?? 0) === 0 || (params.previousCommentCounts?.length ?? 0) === 0) {
|
|
5231
5856
|
return false;
|
|
5232
5857
|
}
|
|
5233
|
-
const
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5858
|
+
const previousCommentCountsByKey = new Map(
|
|
5859
|
+
(params.previousCommentCounts ?? []).map((record) => [buildGitHubPullRequestReferenceKey(record), record])
|
|
5860
|
+
);
|
|
5861
|
+
for (const currentCommentCount of params.currentCommentCounts) {
|
|
5862
|
+
const previousCommentCount = previousCommentCountsByKey.get(buildGitHubPullRequestReferenceKey(currentCommentCount));
|
|
5863
|
+
if (!previousCommentCount) {
|
|
5238
5864
|
continue;
|
|
5239
5865
|
}
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5866
|
+
const pullRequestRepository = requireRepositoryReference(currentCommentCount.repositoryUrl);
|
|
5867
|
+
if (currentCommentCount.topLevelCommentCount > previousCommentCount.topLevelCommentCount) {
|
|
5868
|
+
const newTopLevelComments = await listNewGitHubIssueCommentsSinceCount(
|
|
5869
|
+
params.octokit,
|
|
5870
|
+
pullRequestRepository,
|
|
5871
|
+
currentCommentCount.number,
|
|
5872
|
+
previousCommentCount.topLevelCommentCount,
|
|
5873
|
+
currentCommentCount.topLevelCommentCount
|
|
5874
|
+
);
|
|
5875
|
+
if (await hasTrustedGitHubCommentRecords({
|
|
5876
|
+
octokit: params.octokit,
|
|
5877
|
+
repository: pullRequestRepository,
|
|
5878
|
+
comments: newTopLevelComments,
|
|
5879
|
+
originalPosterLogin: params.githubIssue.authorLogin,
|
|
5880
|
+
maintainerCache: params.maintainerCache
|
|
5881
|
+
})) {
|
|
5882
|
+
return true;
|
|
5883
|
+
}
|
|
5254
5884
|
}
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5885
|
+
if (currentCommentCount.reviewCommentCount > previousCommentCount.reviewCommentCount) {
|
|
5886
|
+
const newReviewComments = await listNewGitHubPullRequestReviewCommentsSinceCount(
|
|
5887
|
+
params.octokit,
|
|
5888
|
+
pullRequestRepository,
|
|
5889
|
+
currentCommentCount.number,
|
|
5890
|
+
previousCommentCount.reviewCommentCount,
|
|
5891
|
+
currentCommentCount.reviewCommentCount
|
|
5892
|
+
);
|
|
5893
|
+
if (await hasTrustedGitHubCommentRecords({
|
|
5894
|
+
octokit: params.octokit,
|
|
5895
|
+
repository: pullRequestRepository,
|
|
5896
|
+
comments: newReviewComments,
|
|
5897
|
+
originalPosterLogin: params.githubIssue.authorLogin,
|
|
5898
|
+
maintainerCache: params.maintainerCache
|
|
5899
|
+
})) {
|
|
5900
|
+
return true;
|
|
5901
|
+
}
|
|
5265
5902
|
}
|
|
5266
5903
|
}
|
|
5267
5904
|
return false;
|
|
@@ -5346,6 +5983,24 @@ function parseGitHubIssueHtmlUrl(value) {
|
|
|
5346
5983
|
function normalizeGitHubIssueHtmlUrl(value) {
|
|
5347
5984
|
return parseGitHubIssueHtmlUrl(value)?.issueUrl;
|
|
5348
5985
|
}
|
|
5986
|
+
function normalizeGitHubPullRequestHtmlUrl(value) {
|
|
5987
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
5988
|
+
return void 0;
|
|
5989
|
+
}
|
|
5990
|
+
try {
|
|
5991
|
+
const parsed = new URL(value);
|
|
5992
|
+
if (parsed.protocol !== "https:" || parsed.hostname !== "github.com") {
|
|
5993
|
+
return void 0;
|
|
5994
|
+
}
|
|
5995
|
+
const match = parsed.pathname.match(/^\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/i);
|
|
5996
|
+
if (!match) {
|
|
5997
|
+
return void 0;
|
|
5998
|
+
}
|
|
5999
|
+
return `https://github.com/${match[1]}/${match[2]}/pull/${match[3]}`;
|
|
6000
|
+
} catch {
|
|
6001
|
+
return void 0;
|
|
6002
|
+
}
|
|
6003
|
+
}
|
|
5349
6004
|
function escapeRegExp(value) {
|
|
5350
6005
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5351
6006
|
}
|
|
@@ -5375,6 +6030,41 @@ function normalizeLinkedPullRequestNumbers(values) {
|
|
|
5375
6030
|
function buildGitHubPullRequestReferenceKey(pullRequest) {
|
|
5376
6031
|
return `${getNormalizedMappingRepositoryUrl({ repositoryUrl: pullRequest.repositoryUrl }).toLowerCase()}#${Math.max(1, Math.floor(pullRequest.number))}`;
|
|
5377
6032
|
}
|
|
6033
|
+
function normalizeGitHubPullRequestCommentCountRecords(value) {
|
|
6034
|
+
if (!Array.isArray(value)) {
|
|
6035
|
+
return [];
|
|
6036
|
+
}
|
|
6037
|
+
const recordsByKey = /* @__PURE__ */ new Map();
|
|
6038
|
+
for (const entry of value) {
|
|
6039
|
+
if (!entry || typeof entry !== "object") {
|
|
6040
|
+
continue;
|
|
6041
|
+
}
|
|
6042
|
+
const record = entry;
|
|
6043
|
+
const pullRequestNumber = typeof record.number === "number" && Number.isInteger(record.number) && record.number > 0 ? Math.floor(record.number) : void 0;
|
|
6044
|
+
const repositoryUrl = typeof record.repositoryUrl === "string" && record.repositoryUrl.trim() ? getNormalizedMappingRepositoryUrl({
|
|
6045
|
+
repositoryUrl: record.repositoryUrl
|
|
6046
|
+
}) : void 0;
|
|
6047
|
+
const topLevelCommentCount = typeof record.topLevelCommentCount === "number" && record.topLevelCommentCount >= 0 ? Math.floor(record.topLevelCommentCount) : void 0;
|
|
6048
|
+
const reviewCommentCount = typeof record.reviewCommentCount === "number" && record.reviewCommentCount >= 0 ? Math.floor(record.reviewCommentCount) : void 0;
|
|
6049
|
+
if (pullRequestNumber === void 0 || !repositoryUrl || topLevelCommentCount === void 0 || reviewCommentCount === void 0) {
|
|
6050
|
+
continue;
|
|
6051
|
+
}
|
|
6052
|
+
const normalizedRecord = {
|
|
6053
|
+
number: pullRequestNumber,
|
|
6054
|
+
repositoryUrl,
|
|
6055
|
+
topLevelCommentCount,
|
|
6056
|
+
reviewCommentCount
|
|
6057
|
+
};
|
|
6058
|
+
recordsByKey.set(buildGitHubPullRequestReferenceKey(normalizedRecord), normalizedRecord);
|
|
6059
|
+
}
|
|
6060
|
+
return [...recordsByKey.values()].sort((left, right) => {
|
|
6061
|
+
const repositoryComparison = left.repositoryUrl.toLowerCase().localeCompare(right.repositoryUrl.toLowerCase());
|
|
6062
|
+
if (repositoryComparison !== 0) {
|
|
6063
|
+
return repositoryComparison;
|
|
6064
|
+
}
|
|
6065
|
+
return left.number - right.number;
|
|
6066
|
+
});
|
|
6067
|
+
}
|
|
5378
6068
|
function normalizeLinkedPullRequestReferences(values, fallbackRepositoryUrl) {
|
|
5379
6069
|
const references = [];
|
|
5380
6070
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
@@ -5953,16 +6643,7 @@ async function upsertStatusTransitionCommentAnnotation(ctx, params) {
|
|
|
5953
6643
|
});
|
|
5954
6644
|
}
|
|
5955
6645
|
function coerceDate(value) {
|
|
5956
|
-
|
|
5957
|
-
return value;
|
|
5958
|
-
}
|
|
5959
|
-
if (typeof value === "string" || typeof value === "number") {
|
|
5960
|
-
const parsed = new Date(value);
|
|
5961
|
-
if (!Number.isNaN(parsed.getTime())) {
|
|
5962
|
-
return parsed;
|
|
5963
|
-
}
|
|
5964
|
-
}
|
|
5965
|
-
return /* @__PURE__ */ new Date();
|
|
6646
|
+
return parseDateValue(value) ?? /* @__PURE__ */ new Date();
|
|
5966
6647
|
}
|
|
5967
6648
|
function parsePaperclipIssueLabel(value, expectedCompanyId) {
|
|
5968
6649
|
if (!value || typeof value !== "object") {
|
|
@@ -6024,6 +6705,9 @@ function getPaperclipIssueEndpoint(baseUrl, issueId) {
|
|
|
6024
6705
|
function getPaperclipHealthEndpoint(baseUrl) {
|
|
6025
6706
|
return new URL("/api/health", baseUrl).toString();
|
|
6026
6707
|
}
|
|
6708
|
+
function getPaperclipCurrentAgentEndpoint(baseUrl) {
|
|
6709
|
+
return new URL("/api/agents/me", baseUrl).toString();
|
|
6710
|
+
}
|
|
6027
6711
|
function getPaperclipAgentWakeupEndpoint(baseUrl, agentId) {
|
|
6028
6712
|
return new URL(`/api/agents/${agentId}/wakeup`, baseUrl).toString();
|
|
6029
6713
|
}
|
|
@@ -7158,7 +7842,7 @@ async function ensurePaperclipIssueImported(ctx, mapping, advancedSettings, issu
|
|
|
7158
7842
|
}
|
|
7159
7843
|
return createdIssue.id;
|
|
7160
7844
|
}
|
|
7161
|
-
async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mapping, advancedSettings, allIssuesById, importedIssues, createdIssueIds, availableLabels, paperclipApiBaseUrl, linkedPullRequestsByIssueNumber, issueStatusSnapshotCache, pullRequestStatusCache, repositoryMaintainerCache, syncFailureContext, failures, assertNotCancelled, onProgress) {
|
|
7845
|
+
async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mapping, advancedSettings, allIssuesById, importedIssues, createdIssueIds, availableLabels, paperclipApiBaseUrl, linkedPullRequestsByIssueNumber, issueStatusSnapshotCache, pullRequestStatusCache, repositoryMaintainerCache, syncFailureContext, failures, assertNotCancelled, onGitHubIssueClosed, onProgress) {
|
|
7162
7846
|
if (!mapping.companyId || !ctx.issues || typeof ctx.issues.get !== "function" || typeof ctx.issues.update !== "function") {
|
|
7163
7847
|
return {
|
|
7164
7848
|
updatedStatusesCount: 0,
|
|
@@ -7172,6 +7856,7 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
|
|
|
7172
7856
|
let completedIssueCount = 0;
|
|
7173
7857
|
const totalIssueCount = importedIssues.length;
|
|
7174
7858
|
const queuedIssueWakeups = [];
|
|
7859
|
+
const pullRequestCommentCountCache = /* @__PURE__ */ new Map();
|
|
7175
7860
|
for (const importedIssue of importedIssues) {
|
|
7176
7861
|
if (assertNotCancelled) {
|
|
7177
7862
|
await assertNotCancelled();
|
|
@@ -7238,6 +7923,7 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
|
|
|
7238
7923
|
pullRequestStatusCache
|
|
7239
7924
|
);
|
|
7240
7925
|
const snapshotLinkedPullRequestNumbers = snapshot?.linkedPullRequests.map((pullRequest) => pullRequest.number) ?? [];
|
|
7926
|
+
const previousGitHubState = importedIssue.lastSeenGitHubState;
|
|
7241
7927
|
if (!doIssueNumberListsMatch(snapshotLinkedPullRequestNumbers, warmedLinkedPullRequestNumbers)) {
|
|
7242
7928
|
updateSyncFailureContext(syncFailureContext, {
|
|
7243
7929
|
phase: "syncing_description",
|
|
@@ -7277,9 +7963,18 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
|
|
|
7277
7963
|
},
|
|
7278
7964
|
snapshot.linkedPullRequests
|
|
7279
7965
|
);
|
|
7966
|
+
if (previousGitHubState === "open" && snapshot.state === "closed" && typeof onGitHubIssueClosed === "function") {
|
|
7967
|
+
await onGitHubIssueClosed({
|
|
7968
|
+
companyId: mapping.companyId,
|
|
7969
|
+
githubIssueId: githubIssue.id,
|
|
7970
|
+
githubIssueNumber: githubIssue.number,
|
|
7971
|
+
repositoryUrl: repository.url,
|
|
7972
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7973
|
+
});
|
|
7974
|
+
}
|
|
7280
7975
|
const previousCommentCount = importedIssue.lastSeenCommentCount;
|
|
7281
|
-
const
|
|
7282
|
-
const
|
|
7976
|
+
const hasNewIssueComments = snapshot.commentCount > (previousCommentCount ?? snapshot.commentCount);
|
|
7977
|
+
const hasTrustedNewIssueComment = paperclipIssue.status === "backlog" || !hasNewIssueComments ? false : await hasTrustedNewGitHubIssueComment({
|
|
7283
7978
|
octokit,
|
|
7284
7979
|
repository,
|
|
7285
7980
|
githubIssue,
|
|
@@ -7287,6 +7982,28 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
|
|
|
7287
7982
|
currentCommentCount: snapshot.commentCount,
|
|
7288
7983
|
maintainerCache: repositoryMaintainerCache
|
|
7289
7984
|
});
|
|
7985
|
+
let currentLinkedPullRequestCommentCounts = snapshot.linkedPullRequests.length === 0 ? [] : importedIssue.linkedPullRequestCommentCounts ?? [];
|
|
7986
|
+
if (snapshot.linkedPullRequests.length > 0) {
|
|
7987
|
+
try {
|
|
7988
|
+
currentLinkedPullRequestCommentCounts = await listGitHubPullRequestCommentCountRecords(
|
|
7989
|
+
octokit,
|
|
7990
|
+
snapshot.linkedPullRequests,
|
|
7991
|
+
pullRequestCommentCountCache
|
|
7992
|
+
);
|
|
7993
|
+
} catch (error) {
|
|
7994
|
+
if (isGitHubRateLimitError(error)) {
|
|
7995
|
+
throw error;
|
|
7996
|
+
}
|
|
7997
|
+
}
|
|
7998
|
+
}
|
|
7999
|
+
const hasTrustedNewLinkedPullRequestComment = paperclipIssue.status === "backlog" || currentLinkedPullRequestCommentCounts.length === 0 ? false : await hasTrustedNewLinkedPullRequestComments({
|
|
8000
|
+
octokit,
|
|
8001
|
+
githubIssue,
|
|
8002
|
+
previousCommentCounts: importedIssue.linkedPullRequestCommentCounts,
|
|
8003
|
+
currentCommentCounts: currentLinkedPullRequestCommentCounts,
|
|
8004
|
+
maintainerCache: repositoryMaintainerCache
|
|
8005
|
+
});
|
|
8006
|
+
const hasTrustedNewComment = hasTrustedNewIssueComment || hasTrustedNewLinkedPullRequestComment;
|
|
7290
8007
|
const wasImportedThisRun = createdIssueIds.has(importedIssue.githubIssueId);
|
|
7291
8008
|
const maintainerAuthoredImportedIssue = wasImportedThisRun && advancedSettings.defaultStatus !== "todo" && snapshot.state === "open" && snapshot.linkedPullRequests.length === 0 ? await isMaintainerAuthoredGitHubIssue({
|
|
7292
8009
|
octokit,
|
|
@@ -7302,7 +8019,6 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
|
|
|
7302
8019
|
const nextStatus = resolvePaperclipIssueStatus({
|
|
7303
8020
|
currentStatus: paperclipIssue.status,
|
|
7304
8021
|
snapshot,
|
|
7305
|
-
previousCommentCount,
|
|
7306
8022
|
hasTrustedNewComment,
|
|
7307
8023
|
wasImportedThisRun,
|
|
7308
8024
|
defaultImportedStatus: advancedSettings.defaultStatus,
|
|
@@ -7320,6 +8036,8 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
|
|
|
7320
8036
|
const shouldWakeTransitionAssignee = paperclipIssue.status !== nextStatus && nextTransitionAssignee?.principal.kind === "agent" && isActionablePaperclipIssueStatus(nextStatus) && (nextAssigneeChanged || paperclipIssue.status !== nextStatus);
|
|
7321
8037
|
importedIssue.githubIssueNumber = githubIssue.number;
|
|
7322
8038
|
importedIssue.lastSeenCommentCount = snapshot.commentCount;
|
|
8039
|
+
importedIssue.lastSeenGitHubState = snapshot.state;
|
|
8040
|
+
importedIssue.linkedPullRequestCommentCounts = currentLinkedPullRequestCommentCounts;
|
|
7323
8041
|
if (paperclipIssue.status === nextStatus) {
|
|
7324
8042
|
if (shouldClearTransitionAssignee) {
|
|
7325
8043
|
updateSyncFailureContext(syncFailureContext, {
|
|
@@ -7353,7 +8071,6 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
|
|
|
7353
8071
|
nextStatus,
|
|
7354
8072
|
repository,
|
|
7355
8073
|
snapshot,
|
|
7356
|
-
previousCommentCount,
|
|
7357
8074
|
hasTrustedNewComment,
|
|
7358
8075
|
maintainerAuthoredImportedIssue
|
|
7359
8076
|
});
|
|
@@ -7621,6 +8338,259 @@ async function executeGitHubTool(fn) {
|
|
|
7621
8338
|
return buildToolErrorResult(error);
|
|
7622
8339
|
}
|
|
7623
8340
|
}
|
|
8341
|
+
function normalizeCompanyActivityMetricInputValue(value) {
|
|
8342
|
+
switch (value) {
|
|
8343
|
+
case "pull_request_created":
|
|
8344
|
+
return "paperclipPullRequestsCreatedCount";
|
|
8345
|
+
default:
|
|
8346
|
+
return void 0;
|
|
8347
|
+
}
|
|
8348
|
+
}
|
|
8349
|
+
async function persistCompanyActivityMetricEvent(ctx, params, options = {}) {
|
|
8350
|
+
const normalizedCompanyId = normalizeCompanyId(params.companyId);
|
|
8351
|
+
if (!normalizedCompanyId) {
|
|
8352
|
+
return {
|
|
8353
|
+
recorded: false
|
|
8354
|
+
};
|
|
8355
|
+
}
|
|
8356
|
+
const currentState = normalizeCompanyKpiState(await ctx.state.get(COMPANY_KPI_SCOPE));
|
|
8357
|
+
const result = recordCompanyActivityMetricEvent(currentState, {
|
|
8358
|
+
companyId: normalizedCompanyId,
|
|
8359
|
+
metric: params.metric,
|
|
8360
|
+
count: params.count,
|
|
8361
|
+
occurredAt: params.occurredAt,
|
|
8362
|
+
eventKey: params.eventKey,
|
|
8363
|
+
repositoryUrl: params.repositoryUrl,
|
|
8364
|
+
pullRequestNumber: params.pullRequestNumber,
|
|
8365
|
+
pullRequestUrl: params.pullRequestUrl
|
|
8366
|
+
});
|
|
8367
|
+
if (!result.recorded) {
|
|
8368
|
+
return {
|
|
8369
|
+
recorded: false,
|
|
8370
|
+
...result.eventKey ? { eventKey: result.eventKey } : {}
|
|
8371
|
+
};
|
|
8372
|
+
}
|
|
8373
|
+
try {
|
|
8374
|
+
await ctx.state.set(COMPANY_KPI_SCOPE, result.state);
|
|
8375
|
+
} catch (error) {
|
|
8376
|
+
if (options.throwOnPersistFailure) {
|
|
8377
|
+
throw error;
|
|
8378
|
+
}
|
|
8379
|
+
ctx.logger.warn("GitHub Sync could not persist a company activity metric event.", {
|
|
8380
|
+
companyId: normalizedCompanyId,
|
|
8381
|
+
metric: params.metric,
|
|
8382
|
+
repositoryUrl: params.repositoryUrl,
|
|
8383
|
+
pullRequestNumber: params.pullRequestNumber,
|
|
8384
|
+
error: getErrorMessage(error)
|
|
8385
|
+
});
|
|
8386
|
+
return {
|
|
8387
|
+
recorded: false,
|
|
8388
|
+
...result.eventKey ? { eventKey: result.eventKey } : {}
|
|
8389
|
+
};
|
|
8390
|
+
}
|
|
8391
|
+
return {
|
|
8392
|
+
recorded: true,
|
|
8393
|
+
...result.eventKey ? { eventKey: result.eventKey } : {}
|
|
8394
|
+
};
|
|
8395
|
+
}
|
|
8396
|
+
function parseWebhookPayloadRecord(input) {
|
|
8397
|
+
if (input.parsedBody && typeof input.parsedBody === "object" && !Array.isArray(input.parsedBody)) {
|
|
8398
|
+
return input.parsedBody;
|
|
8399
|
+
}
|
|
8400
|
+
const rawBody = input.rawBody.trim();
|
|
8401
|
+
if (!rawBody) {
|
|
8402
|
+
throw new Error("Webhook body must be a JSON object.");
|
|
8403
|
+
}
|
|
8404
|
+
let parsed;
|
|
8405
|
+
try {
|
|
8406
|
+
parsed = JSON.parse(rawBody);
|
|
8407
|
+
} catch {
|
|
8408
|
+
throw new Error("Webhook body must be valid JSON.");
|
|
8409
|
+
}
|
|
8410
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8411
|
+
throw new Error("Webhook body must be a JSON object.");
|
|
8412
|
+
}
|
|
8413
|
+
return parsed;
|
|
8414
|
+
}
|
|
8415
|
+
async function resolveCompanyIdForCompanyMetricEvent(ctx, params) {
|
|
8416
|
+
const requestedCompanyId = normalizeCompanyId(params.companyId);
|
|
8417
|
+
if (requestedCompanyId) {
|
|
8418
|
+
return requestedCompanyId;
|
|
8419
|
+
}
|
|
8420
|
+
if (!params.repositoryUrl) {
|
|
8421
|
+
return void 0;
|
|
8422
|
+
}
|
|
8423
|
+
const settings = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
|
|
8424
|
+
const matchingCompanyIds = [
|
|
8425
|
+
...new Set(
|
|
8426
|
+
settings.mappings.filter((mapping) => getNormalizedMappingRepositoryUrl(mapping) === params.repositoryUrl).map((mapping) => normalizeCompanyId(mapping.companyId)).filter((companyId) => Boolean(companyId))
|
|
8427
|
+
)
|
|
8428
|
+
];
|
|
8429
|
+
return matchingCompanyIds.length === 1 ? matchingCompanyIds[0] : void 0;
|
|
8430
|
+
}
|
|
8431
|
+
function getWebhookHeaderValue(headers, name) {
|
|
8432
|
+
const normalizedName = name.trim().toLowerCase();
|
|
8433
|
+
for (const [headerName, headerValue] of Object.entries(headers)) {
|
|
8434
|
+
if (headerName.trim().toLowerCase() !== normalizedName) {
|
|
8435
|
+
continue;
|
|
8436
|
+
}
|
|
8437
|
+
if (typeof headerValue === "string") {
|
|
8438
|
+
const trimmedValue = headerValue.trim();
|
|
8439
|
+
return trimmedValue || void 0;
|
|
8440
|
+
}
|
|
8441
|
+
if (!Array.isArray(headerValue)) {
|
|
8442
|
+
continue;
|
|
8443
|
+
}
|
|
8444
|
+
for (const entry of headerValue) {
|
|
8445
|
+
if (typeof entry !== "string") {
|
|
8446
|
+
continue;
|
|
8447
|
+
}
|
|
8448
|
+
const trimmedValue = entry.trim();
|
|
8449
|
+
if (trimmedValue) {
|
|
8450
|
+
return trimmedValue;
|
|
8451
|
+
}
|
|
8452
|
+
}
|
|
8453
|
+
}
|
|
8454
|
+
return void 0;
|
|
8455
|
+
}
|
|
8456
|
+
function normalizeCompanyMetricWebhookBearerToken(value) {
|
|
8457
|
+
if (!value) {
|
|
8458
|
+
return void 0;
|
|
8459
|
+
}
|
|
8460
|
+
const trimmedValue = value.trim();
|
|
8461
|
+
if (!trimmedValue) {
|
|
8462
|
+
return void 0;
|
|
8463
|
+
}
|
|
8464
|
+
const bearerMatch = trimmedValue.match(/^Bearer\s+(.+)$/i);
|
|
8465
|
+
if (!bearerMatch) {
|
|
8466
|
+
return void 0;
|
|
8467
|
+
}
|
|
8468
|
+
const token = bearerMatch[1]?.trim();
|
|
8469
|
+
return token || void 0;
|
|
8470
|
+
}
|
|
8471
|
+
function normalizePaperclipCurrentAgentRecord(value) {
|
|
8472
|
+
if (!value || typeof value !== "object") {
|
|
8473
|
+
return null;
|
|
8474
|
+
}
|
|
8475
|
+
const record = value;
|
|
8476
|
+
const id = normalizeOptionalString2(record.id);
|
|
8477
|
+
const companyId = normalizeCompanyId(record.companyId);
|
|
8478
|
+
return id && companyId ? {
|
|
8479
|
+
id,
|
|
8480
|
+
companyId
|
|
8481
|
+
} : null;
|
|
8482
|
+
}
|
|
8483
|
+
async function readCompanyMetricWebhookCurrentAgent(paperclipApiBaseUrl, bearerToken) {
|
|
8484
|
+
const response = await fetchPaperclipApi(getPaperclipCurrentAgentEndpoint(paperclipApiBaseUrl), {
|
|
8485
|
+
method: "GET",
|
|
8486
|
+
headers: {
|
|
8487
|
+
accept: "application/json",
|
|
8488
|
+
authorization: `Bearer ${bearerToken}`
|
|
8489
|
+
}
|
|
8490
|
+
});
|
|
8491
|
+
const payloadResult = await readPaperclipApiJsonResponse(response, {
|
|
8492
|
+
operationLabel: "current agent"
|
|
8493
|
+
});
|
|
8494
|
+
if (payloadResult.failure) {
|
|
8495
|
+
if (payloadResult.failure.requiresAuthentication) {
|
|
8496
|
+
throw new Error("Company KPI webhook Authorization must be a valid PAPERCLIP_API_KEY bearer token.");
|
|
8497
|
+
}
|
|
8498
|
+
const detail = payloadResult.failure.errorMessage ? ` ${payloadResult.failure.errorMessage}` : "";
|
|
8499
|
+
throw new Error(`Could not validate the KPI webhook Paperclip API key.${detail}`);
|
|
8500
|
+
}
|
|
8501
|
+
const agent = normalizePaperclipCurrentAgentRecord(payloadResult.data);
|
|
8502
|
+
if (!agent) {
|
|
8503
|
+
throw new Error("Paperclip did not return a usable current agent record while validating the KPI webhook caller.");
|
|
8504
|
+
}
|
|
8505
|
+
return agent;
|
|
8506
|
+
}
|
|
8507
|
+
async function assertCompanyMetricWebhookAuthenticated(ctx, input, companyId) {
|
|
8508
|
+
const rawAuthorization = getWebhookHeaderValue(input.headers, COMPANY_METRIC_WEBHOOK_AUTH_HEADER);
|
|
8509
|
+
const bearerToken = normalizeCompanyMetricWebhookBearerToken(rawAuthorization);
|
|
8510
|
+
if (!bearerToken) {
|
|
8511
|
+
throw new Error(
|
|
8512
|
+
`Missing or invalid ${COMPANY_METRIC_WEBHOOK_AUTH_HEADER} header. Use Bearer <PAPERCLIP_API_KEY>.`
|
|
8513
|
+
);
|
|
8514
|
+
}
|
|
8515
|
+
const settings = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
|
|
8516
|
+
const config = await getResolvedConfig(ctx);
|
|
8517
|
+
const paperclipApiBaseUrl = getConfiguredPaperclipApiBaseUrl(settings, config, companyId);
|
|
8518
|
+
if (!paperclipApiBaseUrl) {
|
|
8519
|
+
throw new Error(
|
|
8520
|
+
"A trusted Paperclip API origin is required to validate PAPERCLIP_API_KEY. Set PAPERCLIP_API_URL or save the Paperclip host origin before sending KPI webhook events."
|
|
8521
|
+
);
|
|
8522
|
+
}
|
|
8523
|
+
const currentAgent = await readCompanyMetricWebhookCurrentAgent(paperclipApiBaseUrl, bearerToken);
|
|
8524
|
+
if (normalizeCompanyId(currentAgent.companyId) !== companyId) {
|
|
8525
|
+
throw new Error("Company KPI webhook Paperclip API key belongs to a different company.");
|
|
8526
|
+
}
|
|
8527
|
+
}
|
|
8528
|
+
async function handleCompanyMetricWebhook(ctx, input) {
|
|
8529
|
+
if (input.endpointKey !== COMPANY_METRIC_WEBHOOK_ENDPOINT_KEY) {
|
|
8530
|
+
throw new Error(`Unsupported webhook endpoint: ${input.endpointKey}.`);
|
|
8531
|
+
}
|
|
8532
|
+
const payload = parseWebhookPayloadRecord(input);
|
|
8533
|
+
const repositoryInput = normalizeOptionalString2(payload.repository);
|
|
8534
|
+
const repository = repositoryInput ? parseRepositoryReference(repositoryInput) : null;
|
|
8535
|
+
if (repositoryInput && !repository) {
|
|
8536
|
+
throw new Error("repository must be owner/repo or https://github.com/owner/repo.");
|
|
8537
|
+
}
|
|
8538
|
+
const companyId = await resolveCompanyIdForCompanyMetricEvent(ctx, {
|
|
8539
|
+
companyId: payload.companyId,
|
|
8540
|
+
repositoryUrl: repository?.url
|
|
8541
|
+
});
|
|
8542
|
+
if (!companyId) {
|
|
8543
|
+
throw new Error("companyId is required unless repository maps to exactly one company.");
|
|
8544
|
+
}
|
|
8545
|
+
await assertCompanyMetricWebhookAuthenticated(ctx, input, companyId);
|
|
8546
|
+
const metric = normalizeCompanyActivityMetricInputValue(payload.metric);
|
|
8547
|
+
if (!metric) {
|
|
8548
|
+
throw new Error('metric must be "pull_request_created".');
|
|
8549
|
+
}
|
|
8550
|
+
const pullRequestNumber = normalizeToolPositiveInteger(payload.pullRequestNumber);
|
|
8551
|
+
const pullRequestUrl = normalizeGitHubPullRequestHtmlUrl(normalizeOptionalString2(payload.pullRequestUrl));
|
|
8552
|
+
const eventKey = normalizeOptionalString2(payload.eventKey);
|
|
8553
|
+
const dedupeKey = buildCompanyMetricEventKey({
|
|
8554
|
+
metric,
|
|
8555
|
+
eventKey,
|
|
8556
|
+
repositoryUrl: repository?.url,
|
|
8557
|
+
pullRequestNumber,
|
|
8558
|
+
pullRequestUrl
|
|
8559
|
+
});
|
|
8560
|
+
if (!dedupeKey) {
|
|
8561
|
+
throw new Error(
|
|
8562
|
+
"Company KPI webhook requires pullRequestUrl, repository plus pullRequestNumber, or eventKey so duplicate deliveries can be ignored."
|
|
8563
|
+
);
|
|
8564
|
+
}
|
|
8565
|
+
const recordedMetric = await persistCompanyActivityMetricEvent(
|
|
8566
|
+
ctx,
|
|
8567
|
+
{
|
|
8568
|
+
companyId,
|
|
8569
|
+
metric,
|
|
8570
|
+
count: normalizeToolPositiveInteger(payload.count),
|
|
8571
|
+
occurredAt: normalizeOptionalString2(payload.occurredAt),
|
|
8572
|
+
eventKey,
|
|
8573
|
+
repositoryUrl: repository?.url,
|
|
8574
|
+
pullRequestNumber,
|
|
8575
|
+
pullRequestUrl
|
|
8576
|
+
},
|
|
8577
|
+
{
|
|
8578
|
+
throwOnPersistFailure: true
|
|
8579
|
+
}
|
|
8580
|
+
);
|
|
8581
|
+
ctx.logger.info(
|
|
8582
|
+
recordedMetric.recorded ? "GitHub Sync recorded a company KPI webhook event." : "GitHub Sync ignored a duplicate company KPI webhook event.",
|
|
8583
|
+
{
|
|
8584
|
+
endpointKey: input.endpointKey,
|
|
8585
|
+
companyId,
|
|
8586
|
+
metric,
|
|
8587
|
+
repositoryUrl: repository?.url,
|
|
8588
|
+
pullRequestNumber,
|
|
8589
|
+
pullRequestUrl,
|
|
8590
|
+
requestId: input.requestId
|
|
8591
|
+
}
|
|
8592
|
+
);
|
|
8593
|
+
}
|
|
7624
8594
|
async function createGitHubToolOctokit(ctx, companyId) {
|
|
7625
8595
|
const token = (await resolveGithubToken(ctx, { companyId })).trim();
|
|
7626
8596
|
if (!token) {
|
|
@@ -10576,6 +11546,8 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
10576
11546
|
const config = await getResolvedConfig(ctx);
|
|
10577
11547
|
const settings = materializeScopedSettings(baseSettings, config, targetCompanyId);
|
|
10578
11548
|
const importRegistry = normalizeImportRegistry(await ctx.state.get(IMPORT_REGISTRY_SCOPE));
|
|
11549
|
+
let companyKpiState = normalizeCompanyKpiState(await ctx.state.get(COMPANY_KPI_SCOPE));
|
|
11550
|
+
let companyKpiStateDirty = false;
|
|
10579
11551
|
const token = typeof options.resolvedToken === "string" ? options.resolvedToken : await resolveGithubToken(ctx, { companyId: targetCompanyId });
|
|
10580
11552
|
const paperclipApiBaseUrl = getConfiguredPaperclipApiBaseUrl(baseSettings, config, targetCompanyId);
|
|
10581
11553
|
const mappings = getSyncableMappingsForTarget(settings.mappings, options.target);
|
|
@@ -10660,6 +11632,19 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
10660
11632
|
erroredIssuesCount: recoverableFailures.length,
|
|
10661
11633
|
progress: currentProgress
|
|
10662
11634
|
});
|
|
11635
|
+
async function flushCompanyKpiState() {
|
|
11636
|
+
if (!companyKpiStateDirty) {
|
|
11637
|
+
return;
|
|
11638
|
+
}
|
|
11639
|
+
try {
|
|
11640
|
+
await ctx.state.set(COMPANY_KPI_SCOPE, companyKpiState);
|
|
11641
|
+
companyKpiStateDirty = false;
|
|
11642
|
+
} catch (error) {
|
|
11643
|
+
ctx.logger.warn("GitHub Sync could not persist company KPI state.", {
|
|
11644
|
+
error: getErrorMessage(error)
|
|
11645
|
+
});
|
|
11646
|
+
}
|
|
11647
|
+
}
|
|
10663
11648
|
async function throwIfSyncCancelled() {
|
|
10664
11649
|
const cancellationRequest = await getSyncCancellationRequest(ctx);
|
|
10665
11650
|
if (!cancellationRequest) {
|
|
@@ -10712,6 +11697,48 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
10712
11697
|
completedTrackedIssueKeys.add(key);
|
|
10713
11698
|
completedTrackedIssueCount += 1;
|
|
10714
11699
|
}
|
|
11700
|
+
function recordCompanyBacklogSnapshotsFromPlans(repositoryPlans2) {
|
|
11701
|
+
if (options.target?.kind === "project" || options.target?.kind === "issue") {
|
|
11702
|
+
return;
|
|
11703
|
+
}
|
|
11704
|
+
const expectedMappingsByCompanyId = /* @__PURE__ */ new Map();
|
|
11705
|
+
for (const mapping of mappings) {
|
|
11706
|
+
const companyId = normalizeCompanyId(mapping.companyId);
|
|
11707
|
+
if (!companyId) {
|
|
11708
|
+
continue;
|
|
11709
|
+
}
|
|
11710
|
+
expectedMappingsByCompanyId.set(companyId, (expectedMappingsByCompanyId.get(companyId) ?? 0) + 1);
|
|
11711
|
+
}
|
|
11712
|
+
const planBacklogByCompanyId = /* @__PURE__ */ new Map();
|
|
11713
|
+
for (const plan of repositoryPlans2) {
|
|
11714
|
+
const companyId = normalizeCompanyId(plan.mapping.companyId);
|
|
11715
|
+
if (!companyId) {
|
|
11716
|
+
continue;
|
|
11717
|
+
}
|
|
11718
|
+
const current = planBacklogByCompanyId.get(companyId) ?? {
|
|
11719
|
+
repositoryCount: 0,
|
|
11720
|
+
openIssueCount: 0
|
|
11721
|
+
};
|
|
11722
|
+
current.repositoryCount += 1;
|
|
11723
|
+
current.openIssueCount += plan.issues.length;
|
|
11724
|
+
planBacklogByCompanyId.set(companyId, current);
|
|
11725
|
+
}
|
|
11726
|
+
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11727
|
+
const day = getIsoDayString(capturedAt);
|
|
11728
|
+
for (const [companyId, expectedRepositoryCount] of expectedMappingsByCompanyId.entries()) {
|
|
11729
|
+
const planned = planBacklogByCompanyId.get(companyId);
|
|
11730
|
+
if (!planned || planned.repositoryCount !== expectedRepositoryCount) {
|
|
11731
|
+
continue;
|
|
11732
|
+
}
|
|
11733
|
+
companyKpiState = upsertCompanyBacklogSnapshot(companyKpiState, companyId, {
|
|
11734
|
+
day,
|
|
11735
|
+
capturedAt,
|
|
11736
|
+
openIssueCount: planned.openIssueCount,
|
|
11737
|
+
repositoryCount: planned.repositoryCount
|
|
11738
|
+
});
|
|
11739
|
+
companyKpiStateDirty = true;
|
|
11740
|
+
}
|
|
11741
|
+
}
|
|
10715
11742
|
const repositoryPlans = [];
|
|
10716
11743
|
try {
|
|
10717
11744
|
await throwIfSyncCancelled();
|
|
@@ -10811,6 +11838,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
10811
11838
|
continue;
|
|
10812
11839
|
}
|
|
10813
11840
|
}
|
|
11841
|
+
recordCompanyBacklogSnapshotsFromPlans(repositoryPlans);
|
|
10814
11842
|
if (repositoryPlans.length > 0) {
|
|
10815
11843
|
const firstPlan = repositoryPlans[0];
|
|
10816
11844
|
currentProgress = {
|
|
@@ -10997,6 +12025,17 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
10997
12025
|
failureContext,
|
|
10998
12026
|
recoverableFailures,
|
|
10999
12027
|
throwIfSyncCancelled,
|
|
12028
|
+
async (params) => {
|
|
12029
|
+
const recorded = recordCompanyActivityMetricEvent(companyKpiState, {
|
|
12030
|
+
companyId: params.companyId,
|
|
12031
|
+
metric: "githubIssuesClosedCount",
|
|
12032
|
+
eventKey: `${params.repositoryUrl}/issues/${params.githubIssueNumber}:${coerceDate(params.occurredAt).toISOString()}`,
|
|
12033
|
+
repositoryUrl: params.repositoryUrl,
|
|
12034
|
+
occurredAt: params.occurredAt
|
|
12035
|
+
});
|
|
12036
|
+
companyKpiState = recorded.state;
|
|
12037
|
+
companyKpiStateDirty = companyKpiStateDirty || recorded.recorded;
|
|
12038
|
+
},
|
|
11000
12039
|
async (progress) => {
|
|
11001
12040
|
markTrackedIssueProcessed(mapping, progress.githubIssueId);
|
|
11002
12041
|
currentProgress = {
|
|
@@ -11100,6 +12139,8 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
11100
12139
|
await saveSettingsSyncState(ctx, currentSettings, next.syncState, targetCompanyId);
|
|
11101
12140
|
await ctx.state.set(IMPORT_REGISTRY_SCOPE, nextRegistry);
|
|
11102
12141
|
return materializeScopedSettings(next, config, targetCompanyId);
|
|
12142
|
+
} finally {
|
|
12143
|
+
await flushCompanyKpiState();
|
|
11103
12144
|
}
|
|
11104
12145
|
}
|
|
11105
12146
|
async function startSync(ctx, trigger, options = {}) {
|
|
@@ -11481,6 +12522,19 @@ function registerGitHubAgentTools(ctx) {
|
|
|
11481
12522
|
"X-GitHub-Api-Version": GITHUB_API_VERSION
|
|
11482
12523
|
}
|
|
11483
12524
|
});
|
|
12525
|
+
await persistCompanyActivityMetricEvent(
|
|
12526
|
+
ctx,
|
|
12527
|
+
{
|
|
12528
|
+
companyId: runCtx.companyId,
|
|
12529
|
+
metric: "paperclipPullRequestsCreatedCount",
|
|
12530
|
+
repositoryUrl: repository.url,
|
|
12531
|
+
pullRequestNumber: response.data.number,
|
|
12532
|
+
pullRequestUrl: response.data.html_url ?? void 0
|
|
12533
|
+
},
|
|
12534
|
+
{
|
|
12535
|
+
throwOnPersistFailure: false
|
|
12536
|
+
}
|
|
12537
|
+
);
|
|
11484
12538
|
return buildToolSuccessResult(
|
|
11485
12539
|
`Created pull request #${response.data.number} in ${formatRepositoryLabel(repository)}.`,
|
|
11486
12540
|
{
|
|
@@ -11978,6 +13032,7 @@ function shouldStartWorkerHost(moduleUrl, entry = process.argv[1]) {
|
|
|
11978
13032
|
}
|
|
11979
13033
|
var plugin = definePlugin({
|
|
11980
13034
|
async setup(ctx) {
|
|
13035
|
+
pluginRuntimeContext = ctx;
|
|
11981
13036
|
ctx.data.register("settings.registration", async (input) => {
|
|
11982
13037
|
const record = input && typeof input === "object" ? input : {};
|
|
11983
13038
|
const requestedCompanyId = normalizeCompanyId(record.companyId);
|
|
@@ -12012,6 +13067,10 @@ var plugin = definePlugin({
|
|
|
12012
13067
|
paperclipBoardAccessNeedsConfigSync: Boolean(savedBoardTokenRef && !configuredBoardTokenRef)
|
|
12013
13068
|
};
|
|
12014
13069
|
});
|
|
13070
|
+
ctx.data.register("dashboard.metrics", async (input) => {
|
|
13071
|
+
const record = input && typeof input === "object" ? input : {};
|
|
13072
|
+
return buildDashboardMetricsData(ctx, record);
|
|
13073
|
+
});
|
|
12015
13074
|
ctx.data.register("sync.toolbarState", async (input) => {
|
|
12016
13075
|
const record = input && typeof input === "object" ? input : {};
|
|
12017
13076
|
return buildToolbarSyncState(ctx, record);
|
|
@@ -12337,6 +13396,15 @@ var plugin = definePlugin({
|
|
|
12337
13396
|
await startSync(ctx, trigger, target ? { target } : {});
|
|
12338
13397
|
}
|
|
12339
13398
|
});
|
|
13399
|
+
},
|
|
13400
|
+
async onWebhook(input) {
|
|
13401
|
+
if (!pluginRuntimeContext) {
|
|
13402
|
+
throw new Error("GitHub Sync worker is not ready to handle webhooks yet.");
|
|
13403
|
+
}
|
|
13404
|
+
await handleCompanyMetricWebhook(pluginRuntimeContext, input);
|
|
13405
|
+
},
|
|
13406
|
+
async onShutdown() {
|
|
13407
|
+
pluginRuntimeContext = null;
|
|
12340
13408
|
}
|
|
12341
13409
|
});
|
|
12342
13410
|
var worker_default = plugin;
|