@bbearai/core 0.7.2 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +33 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +174 -51
- package/dist/index.mjs +174 -51
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -377,6 +377,8 @@ interface TestAssignment {
|
|
|
377
377
|
startedAt?: string;
|
|
378
378
|
/** Duration in seconds (calculated when completed) */
|
|
379
379
|
durationSeconds?: number;
|
|
380
|
+
/** Active time in seconds, computed from presence heartbeats */
|
|
381
|
+
activeSeconds?: number;
|
|
380
382
|
/** Whether this is a verification assignment for a fixed bug */
|
|
381
383
|
isVerification?: boolean;
|
|
382
384
|
/** Original report ID if this is a verification assignment */
|
|
@@ -479,6 +481,7 @@ interface TesterThread {
|
|
|
479
481
|
createdAt: string;
|
|
480
482
|
unreadCount: number;
|
|
481
483
|
lastMessage?: TesterMessage;
|
|
484
|
+
reporterName?: string;
|
|
482
485
|
}
|
|
483
486
|
interface TesterMessage {
|
|
484
487
|
id: string;
|
|
@@ -788,6 +791,14 @@ interface TesterIssue {
|
|
|
788
791
|
originalBugId?: string;
|
|
789
792
|
/** Original bug title (for reopened/test_fail issues) */
|
|
790
793
|
originalBugTitle?: string;
|
|
794
|
+
/** Resolution notes from the developer (for ready_to_test issues) */
|
|
795
|
+
resolutionNotes?: string;
|
|
796
|
+
/** Fix commit SHA (for ready_to_test issues) */
|
|
797
|
+
fixCommitSha?: string;
|
|
798
|
+
/** Fix commit message */
|
|
799
|
+
fixCommitMessage?: string;
|
|
800
|
+
/** Files changed in the fix */
|
|
801
|
+
fixFilesChanged?: string[];
|
|
791
802
|
}
|
|
792
803
|
/** Delivery status for captured emails */
|
|
793
804
|
type EmailDeliveryStatus = 'pending' | 'sent' | 'delivered' | 'bounced' | 'dropped' | 'deferred';
|
|
@@ -1102,6 +1113,12 @@ declare class BugBearClient {
|
|
|
1102
1113
|
private initialized;
|
|
1103
1114
|
/** Initialization error, if any. */
|
|
1104
1115
|
private initError;
|
|
1116
|
+
/** Active presence session ID (passive time tracking). */
|
|
1117
|
+
private _presenceSessionId;
|
|
1118
|
+
/** Heartbeat interval for presence tracking. */
|
|
1119
|
+
private _presenceInterval;
|
|
1120
|
+
/** Whether presence is paused (tab hidden / app backgrounded). */
|
|
1121
|
+
private _presencePaused;
|
|
1105
1122
|
constructor(config: BugBearConfig);
|
|
1106
1123
|
/** Whether the client is ready for requests. */
|
|
1107
1124
|
get isReady(): boolean;
|
|
@@ -1561,6 +1578,22 @@ declare class BugBearClient {
|
|
|
1561
1578
|
* Transform database finding to QAFinding type
|
|
1562
1579
|
*/
|
|
1563
1580
|
private transformFinding;
|
|
1581
|
+
/** Current presence session ID (null if not tracking). */
|
|
1582
|
+
get presenceSessionId(): string | null;
|
|
1583
|
+
/**
|
|
1584
|
+
* Start passive presence tracking for this tester.
|
|
1585
|
+
* Idempotent — reuses an existing active session if one exists.
|
|
1586
|
+
*/
|
|
1587
|
+
startPresence(platform: 'web' | 'ios' | 'android'): Promise<string | null>;
|
|
1588
|
+
/** Gracefully end the current presence session. */
|
|
1589
|
+
endPresence(): Promise<void>;
|
|
1590
|
+
/** Pause heartbeat (tab hidden / app backgrounded). Sends one final beat. */
|
|
1591
|
+
pausePresence(): void;
|
|
1592
|
+
/** Resume heartbeat after pause. Restarts if session was cleaned up. */
|
|
1593
|
+
resumePresence(): Promise<void>;
|
|
1594
|
+
private heartbeatPresence;
|
|
1595
|
+
private startPresenceHeartbeat;
|
|
1596
|
+
private stopPresenceHeartbeat;
|
|
1564
1597
|
}
|
|
1565
1598
|
/**
|
|
1566
1599
|
* Create a BugBear client instance
|
package/dist/index.d.ts
CHANGED
|
@@ -377,6 +377,8 @@ interface TestAssignment {
|
|
|
377
377
|
startedAt?: string;
|
|
378
378
|
/** Duration in seconds (calculated when completed) */
|
|
379
379
|
durationSeconds?: number;
|
|
380
|
+
/** Active time in seconds, computed from presence heartbeats */
|
|
381
|
+
activeSeconds?: number;
|
|
380
382
|
/** Whether this is a verification assignment for a fixed bug */
|
|
381
383
|
isVerification?: boolean;
|
|
382
384
|
/** Original report ID if this is a verification assignment */
|
|
@@ -479,6 +481,7 @@ interface TesterThread {
|
|
|
479
481
|
createdAt: string;
|
|
480
482
|
unreadCount: number;
|
|
481
483
|
lastMessage?: TesterMessage;
|
|
484
|
+
reporterName?: string;
|
|
482
485
|
}
|
|
483
486
|
interface TesterMessage {
|
|
484
487
|
id: string;
|
|
@@ -788,6 +791,14 @@ interface TesterIssue {
|
|
|
788
791
|
originalBugId?: string;
|
|
789
792
|
/** Original bug title (for reopened/test_fail issues) */
|
|
790
793
|
originalBugTitle?: string;
|
|
794
|
+
/** Resolution notes from the developer (for ready_to_test issues) */
|
|
795
|
+
resolutionNotes?: string;
|
|
796
|
+
/** Fix commit SHA (for ready_to_test issues) */
|
|
797
|
+
fixCommitSha?: string;
|
|
798
|
+
/** Fix commit message */
|
|
799
|
+
fixCommitMessage?: string;
|
|
800
|
+
/** Files changed in the fix */
|
|
801
|
+
fixFilesChanged?: string[];
|
|
791
802
|
}
|
|
792
803
|
/** Delivery status for captured emails */
|
|
793
804
|
type EmailDeliveryStatus = 'pending' | 'sent' | 'delivered' | 'bounced' | 'dropped' | 'deferred';
|
|
@@ -1102,6 +1113,12 @@ declare class BugBearClient {
|
|
|
1102
1113
|
private initialized;
|
|
1103
1114
|
/** Initialization error, if any. */
|
|
1104
1115
|
private initError;
|
|
1116
|
+
/** Active presence session ID (passive time tracking). */
|
|
1117
|
+
private _presenceSessionId;
|
|
1118
|
+
/** Heartbeat interval for presence tracking. */
|
|
1119
|
+
private _presenceInterval;
|
|
1120
|
+
/** Whether presence is paused (tab hidden / app backgrounded). */
|
|
1121
|
+
private _presencePaused;
|
|
1105
1122
|
constructor(config: BugBearConfig);
|
|
1106
1123
|
/** Whether the client is ready for requests. */
|
|
1107
1124
|
get isReady(): boolean;
|
|
@@ -1561,6 +1578,22 @@ declare class BugBearClient {
|
|
|
1561
1578
|
* Transform database finding to QAFinding type
|
|
1562
1579
|
*/
|
|
1563
1580
|
private transformFinding;
|
|
1581
|
+
/** Current presence session ID (null if not tracking). */
|
|
1582
|
+
get presenceSessionId(): string | null;
|
|
1583
|
+
/**
|
|
1584
|
+
* Start passive presence tracking for this tester.
|
|
1585
|
+
* Idempotent — reuses an existing active session if one exists.
|
|
1586
|
+
*/
|
|
1587
|
+
startPresence(platform: 'web' | 'ios' | 'android'): Promise<string | null>;
|
|
1588
|
+
/** Gracefully end the current presence session. */
|
|
1589
|
+
endPresence(): Promise<void>;
|
|
1590
|
+
/** Pause heartbeat (tab hidden / app backgrounded). Sends one final beat. */
|
|
1591
|
+
pausePresence(): void;
|
|
1592
|
+
/** Resume heartbeat after pause. Restarts if session was cleaned up. */
|
|
1593
|
+
resumePresence(): Promise<void>;
|
|
1594
|
+
private heartbeatPresence;
|
|
1595
|
+
private startPresenceHeartbeat;
|
|
1596
|
+
private stopPresenceHeartbeat;
|
|
1564
1597
|
}
|
|
1565
1598
|
/**
|
|
1566
1599
|
* Create a BugBear client instance
|
package/dist/index.js
CHANGED
|
@@ -914,6 +914,12 @@ var BugBearClient = class {
|
|
|
914
914
|
this.initialized = false;
|
|
915
915
|
/** Initialization error, if any. */
|
|
916
916
|
this.initError = null;
|
|
917
|
+
/** Active presence session ID (passive time tracking). */
|
|
918
|
+
this._presenceSessionId = null;
|
|
919
|
+
/** Heartbeat interval for presence tracking. */
|
|
920
|
+
this._presenceInterval = null;
|
|
921
|
+
/** Whether presence is paused (tab hidden / app backgrounded). */
|
|
922
|
+
this._presencePaused = false;
|
|
917
923
|
this.config = config;
|
|
918
924
|
if (config.apiKey) {
|
|
919
925
|
this.pendingInit = this.resolveFromApiKey(config.apiKey);
|
|
@@ -1469,55 +1475,66 @@ var BugBearClient = class {
|
|
|
1469
1475
|
...pendingResult.data || [],
|
|
1470
1476
|
...completedResult.data || []
|
|
1471
1477
|
];
|
|
1472
|
-
const mapItem = (item) =>
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1478
|
+
const mapItem = (item) => {
|
|
1479
|
+
const tc = item.test_case;
|
|
1480
|
+
return {
|
|
1481
|
+
id: item.id,
|
|
1482
|
+
status: item.status,
|
|
1483
|
+
startedAt: item.started_at,
|
|
1484
|
+
skipReason: item.skip_reason,
|
|
1485
|
+
isVerification: item.is_verification || false,
|
|
1486
|
+
originalReportId: item.original_report_id,
|
|
1487
|
+
testCase: tc ? {
|
|
1488
|
+
id: tc.id,
|
|
1489
|
+
title: tc.title,
|
|
1490
|
+
testKey: tc.test_key,
|
|
1491
|
+
description: tc.description,
|
|
1492
|
+
steps: tc.steps,
|
|
1493
|
+
expectedResult: tc.expected_result,
|
|
1494
|
+
priority: tc.priority,
|
|
1495
|
+
targetRoute: tc.target_route,
|
|
1496
|
+
track: tc.track ? {
|
|
1497
|
+
id: tc.track.id,
|
|
1498
|
+
name: tc.track.name,
|
|
1499
|
+
icon: tc.track.icon,
|
|
1500
|
+
color: tc.track.color,
|
|
1501
|
+
testTemplate: tc.track.test_template,
|
|
1502
|
+
rubricMode: tc.track.rubric_mode || "pass_fail",
|
|
1503
|
+
description: tc.track.description
|
|
1504
|
+
} : void 0,
|
|
1505
|
+
group: tc.group ? {
|
|
1506
|
+
id: tc.group.id,
|
|
1507
|
+
name: tc.group.name,
|
|
1508
|
+
description: tc.group.description,
|
|
1509
|
+
sortOrder: tc.group.sort_order
|
|
1510
|
+
} : void 0,
|
|
1511
|
+
role: tc.role ? {
|
|
1512
|
+
id: tc.role.id,
|
|
1513
|
+
name: tc.role.name,
|
|
1514
|
+
slug: tc.role.slug,
|
|
1515
|
+
color: tc.role.color,
|
|
1516
|
+
description: tc.role.description,
|
|
1517
|
+
loginHint: tc.role.login_hint
|
|
1518
|
+
} : void 0,
|
|
1519
|
+
platforms: tc.platforms || void 0
|
|
1520
|
+
} : {
|
|
1521
|
+
// Standalone verification assignment (bug reported without a test case)
|
|
1522
|
+
id: item.original_report_id || item.id,
|
|
1523
|
+
title: item.notes || "Bug Verification",
|
|
1524
|
+
testKey: "VERIFY",
|
|
1525
|
+
description: "Verify that the reported bug has been fixed",
|
|
1526
|
+
steps: [],
|
|
1527
|
+
expectedResult: "The bug should no longer be reproducible",
|
|
1528
|
+
priority: "P1",
|
|
1529
|
+
targetRoute: void 0,
|
|
1530
|
+
track: void 0,
|
|
1531
|
+
group: void 0,
|
|
1532
|
+
role: void 0,
|
|
1533
|
+
platforms: void 0
|
|
1534
|
+
}
|
|
1535
|
+
};
|
|
1536
|
+
};
|
|
1537
|
+
const mapped = allData.map(mapItem);
|
|
1521
1538
|
mapped.sort((a, b) => {
|
|
1522
1539
|
if (a.isVerification && !b.isVerification) return -1;
|
|
1523
1540
|
if (!a.isVerification && b.isVerification) return 1;
|
|
@@ -1611,7 +1628,7 @@ var BugBearClient = class {
|
|
|
1611
1628
|
async updateAssignmentStatus(assignmentId, status, options) {
|
|
1612
1629
|
try {
|
|
1613
1630
|
await this.ensureReady();
|
|
1614
|
-
const { data: currentAssignment, error: fetchError } = await this.supabase.from("test_assignments").select("status, started_at").eq("id", assignmentId).single();
|
|
1631
|
+
const { data: currentAssignment, error: fetchError } = await this.supabase.from("test_assignments").select("status, started_at, tester_id, project_id").eq("id", assignmentId).single();
|
|
1615
1632
|
if (fetchError || !currentAssignment) {
|
|
1616
1633
|
console.error("BugBear: Assignment not found", {
|
|
1617
1634
|
message: fetchError?.message,
|
|
@@ -1632,6 +1649,19 @@ var BugBearClient = class {
|
|
|
1632
1649
|
const completedAt = /* @__PURE__ */ new Date();
|
|
1633
1650
|
durationSeconds = Math.round((completedAt.getTime() - startedAt.getTime()) / 1e3);
|
|
1634
1651
|
updateData.duration_seconds = durationSeconds;
|
|
1652
|
+
if (currentAssignment.tester_id && currentAssignment.project_id) {
|
|
1653
|
+
try {
|
|
1654
|
+
const { data: activeTime } = await this.supabase.rpc("compute_assignment_active_time", {
|
|
1655
|
+
p_tester_id: currentAssignment.tester_id,
|
|
1656
|
+
p_project_id: currentAssignment.project_id,
|
|
1657
|
+
p_started_at: currentAssignment.started_at,
|
|
1658
|
+
p_completed_at: updateData.completed_at
|
|
1659
|
+
});
|
|
1660
|
+
updateData.active_seconds = typeof activeTime === "number" ? activeTime : Math.min(durationSeconds, 1800);
|
|
1661
|
+
} catch {
|
|
1662
|
+
updateData.active_seconds = Math.min(durationSeconds, 1800);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1635
1665
|
}
|
|
1636
1666
|
}
|
|
1637
1667
|
if (options?.notes) {
|
|
@@ -1710,6 +1740,7 @@ var BugBearClient = class {
|
|
|
1710
1740
|
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1711
1741
|
completed_at: null,
|
|
1712
1742
|
duration_seconds: null,
|
|
1743
|
+
active_seconds: null,
|
|
1713
1744
|
skip_reason: null
|
|
1714
1745
|
}).eq("id", assignmentId).eq("status", current.status);
|
|
1715
1746
|
if (error) {
|
|
@@ -2017,7 +2048,11 @@ var BugBearClient = class {
|
|
|
2017
2048
|
verifiedByName: row.verified_by_name || void 0,
|
|
2018
2049
|
verifiedAt: row.verified_at || void 0,
|
|
2019
2050
|
originalBugId: row.original_bug_id || void 0,
|
|
2020
|
-
originalBugTitle: row.original_bug_title || void 0
|
|
2051
|
+
originalBugTitle: row.original_bug_title || void 0,
|
|
2052
|
+
resolutionNotes: row.resolution_notes || void 0,
|
|
2053
|
+
fixCommitSha: row.code_context?.fix?.commit_sha || void 0,
|
|
2054
|
+
fixCommitMessage: row.code_context?.fix?.commit_message || void 0,
|
|
2055
|
+
fixFilesChanged: row.code_context?.fix?.files_changed || void 0
|
|
2021
2056
|
}));
|
|
2022
2057
|
} catch (err) {
|
|
2023
2058
|
console.error("BugBear: Error fetching issues", err);
|
|
@@ -2559,6 +2594,7 @@ var BugBearClient = class {
|
|
|
2559
2594
|
lastMessageAt: row.last_message_at,
|
|
2560
2595
|
createdAt: row.created_at,
|
|
2561
2596
|
unreadCount: Number(row.unread_count) || 0,
|
|
2597
|
+
reporterName: row.reporter_name || void 0,
|
|
2562
2598
|
lastMessage: row.last_message_preview ? {
|
|
2563
2599
|
id: "",
|
|
2564
2600
|
threadId: row.thread_id,
|
|
@@ -2998,6 +3034,93 @@ var BugBearClient = class {
|
|
|
2998
3034
|
updatedAt: data.updated_at
|
|
2999
3035
|
};
|
|
3000
3036
|
}
|
|
3037
|
+
// ─── Passive Presence Tracking ──────────────────────────────
|
|
3038
|
+
/** Current presence session ID (null if not tracking). */
|
|
3039
|
+
get presenceSessionId() {
|
|
3040
|
+
return this._presenceSessionId;
|
|
3041
|
+
}
|
|
3042
|
+
/**
|
|
3043
|
+
* Start passive presence tracking for this tester.
|
|
3044
|
+
* Idempotent — reuses an existing active session if one exists.
|
|
3045
|
+
*/
|
|
3046
|
+
async startPresence(platform) {
|
|
3047
|
+
try {
|
|
3048
|
+
await this.ensureReady();
|
|
3049
|
+
const testerInfo = await this.getTesterInfo();
|
|
3050
|
+
if (!testerInfo) return null;
|
|
3051
|
+
const { data, error } = await this.supabase.rpc("upsert_tester_presence", {
|
|
3052
|
+
p_project_id: this.config.projectId,
|
|
3053
|
+
p_tester_id: testerInfo.id,
|
|
3054
|
+
p_platform: platform
|
|
3055
|
+
});
|
|
3056
|
+
if (error) {
|
|
3057
|
+
console.error("BugBear: Failed to start presence", formatPgError(error));
|
|
3058
|
+
return null;
|
|
3059
|
+
}
|
|
3060
|
+
this._presenceSessionId = data;
|
|
3061
|
+
this._presencePaused = false;
|
|
3062
|
+
this.startPresenceHeartbeat();
|
|
3063
|
+
return data;
|
|
3064
|
+
} catch (err) {
|
|
3065
|
+
console.error("BugBear: Error starting presence", err);
|
|
3066
|
+
return null;
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
/** Gracefully end the current presence session. */
|
|
3070
|
+
async endPresence() {
|
|
3071
|
+
this.stopPresenceHeartbeat();
|
|
3072
|
+
if (!this._presenceSessionId) return;
|
|
3073
|
+
try {
|
|
3074
|
+
await this.supabase.rpc("end_tester_presence", {
|
|
3075
|
+
p_session_id: this._presenceSessionId
|
|
3076
|
+
});
|
|
3077
|
+
} catch {
|
|
3078
|
+
}
|
|
3079
|
+
this._presenceSessionId = null;
|
|
3080
|
+
}
|
|
3081
|
+
/** Pause heartbeat (tab hidden / app backgrounded). Sends one final beat. */
|
|
3082
|
+
pausePresence() {
|
|
3083
|
+
this._presencePaused = true;
|
|
3084
|
+
this.heartbeatPresence();
|
|
3085
|
+
}
|
|
3086
|
+
/** Resume heartbeat after pause. Restarts if session was cleaned up. */
|
|
3087
|
+
async resumePresence() {
|
|
3088
|
+
if (!this._presenceSessionId) return;
|
|
3089
|
+
this._presencePaused = false;
|
|
3090
|
+
try {
|
|
3091
|
+
const { data } = await this.supabase.rpc("heartbeat_tester_presence", {
|
|
3092
|
+
p_session_id: this._presenceSessionId
|
|
3093
|
+
});
|
|
3094
|
+
if (!data) {
|
|
3095
|
+
this._presenceSessionId = null;
|
|
3096
|
+
}
|
|
3097
|
+
} catch {
|
|
3098
|
+
this._presenceSessionId = null;
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
async heartbeatPresence() {
|
|
3102
|
+
if (!this._presenceSessionId || this._presencePaused) return;
|
|
3103
|
+
try {
|
|
3104
|
+
const { data, error } = await this.supabase.rpc("heartbeat_tester_presence", {
|
|
3105
|
+
p_session_id: this._presenceSessionId
|
|
3106
|
+
});
|
|
3107
|
+
if (error || data === false) {
|
|
3108
|
+
this.stopPresenceHeartbeat();
|
|
3109
|
+
this._presenceSessionId = null;
|
|
3110
|
+
}
|
|
3111
|
+
} catch {
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
startPresenceHeartbeat() {
|
|
3115
|
+
this.stopPresenceHeartbeat();
|
|
3116
|
+
this._presenceInterval = setInterval(() => this.heartbeatPresence(), 6e4);
|
|
3117
|
+
}
|
|
3118
|
+
stopPresenceHeartbeat() {
|
|
3119
|
+
if (this._presenceInterval) {
|
|
3120
|
+
clearInterval(this._presenceInterval);
|
|
3121
|
+
this._presenceInterval = null;
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3001
3124
|
};
|
|
3002
3125
|
function createBugBear(config) {
|
|
3003
3126
|
return new BugBearClient(config);
|
package/dist/index.mjs
CHANGED
|
@@ -868,6 +868,12 @@ var BugBearClient = class {
|
|
|
868
868
|
this.initialized = false;
|
|
869
869
|
/** Initialization error, if any. */
|
|
870
870
|
this.initError = null;
|
|
871
|
+
/** Active presence session ID (passive time tracking). */
|
|
872
|
+
this._presenceSessionId = null;
|
|
873
|
+
/** Heartbeat interval for presence tracking. */
|
|
874
|
+
this._presenceInterval = null;
|
|
875
|
+
/** Whether presence is paused (tab hidden / app backgrounded). */
|
|
876
|
+
this._presencePaused = false;
|
|
871
877
|
this.config = config;
|
|
872
878
|
if (config.apiKey) {
|
|
873
879
|
this.pendingInit = this.resolveFromApiKey(config.apiKey);
|
|
@@ -1423,55 +1429,66 @@ var BugBearClient = class {
|
|
|
1423
1429
|
...pendingResult.data || [],
|
|
1424
1430
|
...completedResult.data || []
|
|
1425
1431
|
];
|
|
1426
|
-
const mapItem = (item) =>
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1432
|
+
const mapItem = (item) => {
|
|
1433
|
+
const tc = item.test_case;
|
|
1434
|
+
return {
|
|
1435
|
+
id: item.id,
|
|
1436
|
+
status: item.status,
|
|
1437
|
+
startedAt: item.started_at,
|
|
1438
|
+
skipReason: item.skip_reason,
|
|
1439
|
+
isVerification: item.is_verification || false,
|
|
1440
|
+
originalReportId: item.original_report_id,
|
|
1441
|
+
testCase: tc ? {
|
|
1442
|
+
id: tc.id,
|
|
1443
|
+
title: tc.title,
|
|
1444
|
+
testKey: tc.test_key,
|
|
1445
|
+
description: tc.description,
|
|
1446
|
+
steps: tc.steps,
|
|
1447
|
+
expectedResult: tc.expected_result,
|
|
1448
|
+
priority: tc.priority,
|
|
1449
|
+
targetRoute: tc.target_route,
|
|
1450
|
+
track: tc.track ? {
|
|
1451
|
+
id: tc.track.id,
|
|
1452
|
+
name: tc.track.name,
|
|
1453
|
+
icon: tc.track.icon,
|
|
1454
|
+
color: tc.track.color,
|
|
1455
|
+
testTemplate: tc.track.test_template,
|
|
1456
|
+
rubricMode: tc.track.rubric_mode || "pass_fail",
|
|
1457
|
+
description: tc.track.description
|
|
1458
|
+
} : void 0,
|
|
1459
|
+
group: tc.group ? {
|
|
1460
|
+
id: tc.group.id,
|
|
1461
|
+
name: tc.group.name,
|
|
1462
|
+
description: tc.group.description,
|
|
1463
|
+
sortOrder: tc.group.sort_order
|
|
1464
|
+
} : void 0,
|
|
1465
|
+
role: tc.role ? {
|
|
1466
|
+
id: tc.role.id,
|
|
1467
|
+
name: tc.role.name,
|
|
1468
|
+
slug: tc.role.slug,
|
|
1469
|
+
color: tc.role.color,
|
|
1470
|
+
description: tc.role.description,
|
|
1471
|
+
loginHint: tc.role.login_hint
|
|
1472
|
+
} : void 0,
|
|
1473
|
+
platforms: tc.platforms || void 0
|
|
1474
|
+
} : {
|
|
1475
|
+
// Standalone verification assignment (bug reported without a test case)
|
|
1476
|
+
id: item.original_report_id || item.id,
|
|
1477
|
+
title: item.notes || "Bug Verification",
|
|
1478
|
+
testKey: "VERIFY",
|
|
1479
|
+
description: "Verify that the reported bug has been fixed",
|
|
1480
|
+
steps: [],
|
|
1481
|
+
expectedResult: "The bug should no longer be reproducible",
|
|
1482
|
+
priority: "P1",
|
|
1483
|
+
targetRoute: void 0,
|
|
1484
|
+
track: void 0,
|
|
1485
|
+
group: void 0,
|
|
1486
|
+
role: void 0,
|
|
1487
|
+
platforms: void 0
|
|
1488
|
+
}
|
|
1489
|
+
};
|
|
1490
|
+
};
|
|
1491
|
+
const mapped = allData.map(mapItem);
|
|
1475
1492
|
mapped.sort((a, b) => {
|
|
1476
1493
|
if (a.isVerification && !b.isVerification) return -1;
|
|
1477
1494
|
if (!a.isVerification && b.isVerification) return 1;
|
|
@@ -1565,7 +1582,7 @@ var BugBearClient = class {
|
|
|
1565
1582
|
async updateAssignmentStatus(assignmentId, status, options) {
|
|
1566
1583
|
try {
|
|
1567
1584
|
await this.ensureReady();
|
|
1568
|
-
const { data: currentAssignment, error: fetchError } = await this.supabase.from("test_assignments").select("status, started_at").eq("id", assignmentId).single();
|
|
1585
|
+
const { data: currentAssignment, error: fetchError } = await this.supabase.from("test_assignments").select("status, started_at, tester_id, project_id").eq("id", assignmentId).single();
|
|
1569
1586
|
if (fetchError || !currentAssignment) {
|
|
1570
1587
|
console.error("BugBear: Assignment not found", {
|
|
1571
1588
|
message: fetchError?.message,
|
|
@@ -1586,6 +1603,19 @@ var BugBearClient = class {
|
|
|
1586
1603
|
const completedAt = /* @__PURE__ */ new Date();
|
|
1587
1604
|
durationSeconds = Math.round((completedAt.getTime() - startedAt.getTime()) / 1e3);
|
|
1588
1605
|
updateData.duration_seconds = durationSeconds;
|
|
1606
|
+
if (currentAssignment.tester_id && currentAssignment.project_id) {
|
|
1607
|
+
try {
|
|
1608
|
+
const { data: activeTime } = await this.supabase.rpc("compute_assignment_active_time", {
|
|
1609
|
+
p_tester_id: currentAssignment.tester_id,
|
|
1610
|
+
p_project_id: currentAssignment.project_id,
|
|
1611
|
+
p_started_at: currentAssignment.started_at,
|
|
1612
|
+
p_completed_at: updateData.completed_at
|
|
1613
|
+
});
|
|
1614
|
+
updateData.active_seconds = typeof activeTime === "number" ? activeTime : Math.min(durationSeconds, 1800);
|
|
1615
|
+
} catch {
|
|
1616
|
+
updateData.active_seconds = Math.min(durationSeconds, 1800);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1589
1619
|
}
|
|
1590
1620
|
}
|
|
1591
1621
|
if (options?.notes) {
|
|
@@ -1664,6 +1694,7 @@ var BugBearClient = class {
|
|
|
1664
1694
|
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1665
1695
|
completed_at: null,
|
|
1666
1696
|
duration_seconds: null,
|
|
1697
|
+
active_seconds: null,
|
|
1667
1698
|
skip_reason: null
|
|
1668
1699
|
}).eq("id", assignmentId).eq("status", current.status);
|
|
1669
1700
|
if (error) {
|
|
@@ -1971,7 +2002,11 @@ var BugBearClient = class {
|
|
|
1971
2002
|
verifiedByName: row.verified_by_name || void 0,
|
|
1972
2003
|
verifiedAt: row.verified_at || void 0,
|
|
1973
2004
|
originalBugId: row.original_bug_id || void 0,
|
|
1974
|
-
originalBugTitle: row.original_bug_title || void 0
|
|
2005
|
+
originalBugTitle: row.original_bug_title || void 0,
|
|
2006
|
+
resolutionNotes: row.resolution_notes || void 0,
|
|
2007
|
+
fixCommitSha: row.code_context?.fix?.commit_sha || void 0,
|
|
2008
|
+
fixCommitMessage: row.code_context?.fix?.commit_message || void 0,
|
|
2009
|
+
fixFilesChanged: row.code_context?.fix?.files_changed || void 0
|
|
1975
2010
|
}));
|
|
1976
2011
|
} catch (err) {
|
|
1977
2012
|
console.error("BugBear: Error fetching issues", err);
|
|
@@ -2513,6 +2548,7 @@ var BugBearClient = class {
|
|
|
2513
2548
|
lastMessageAt: row.last_message_at,
|
|
2514
2549
|
createdAt: row.created_at,
|
|
2515
2550
|
unreadCount: Number(row.unread_count) || 0,
|
|
2551
|
+
reporterName: row.reporter_name || void 0,
|
|
2516
2552
|
lastMessage: row.last_message_preview ? {
|
|
2517
2553
|
id: "",
|
|
2518
2554
|
threadId: row.thread_id,
|
|
@@ -2952,6 +2988,93 @@ var BugBearClient = class {
|
|
|
2952
2988
|
updatedAt: data.updated_at
|
|
2953
2989
|
};
|
|
2954
2990
|
}
|
|
2991
|
+
// ─── Passive Presence Tracking ──────────────────────────────
|
|
2992
|
+
/** Current presence session ID (null if not tracking). */
|
|
2993
|
+
get presenceSessionId() {
|
|
2994
|
+
return this._presenceSessionId;
|
|
2995
|
+
}
|
|
2996
|
+
/**
|
|
2997
|
+
* Start passive presence tracking for this tester.
|
|
2998
|
+
* Idempotent — reuses an existing active session if one exists.
|
|
2999
|
+
*/
|
|
3000
|
+
async startPresence(platform) {
|
|
3001
|
+
try {
|
|
3002
|
+
await this.ensureReady();
|
|
3003
|
+
const testerInfo = await this.getTesterInfo();
|
|
3004
|
+
if (!testerInfo) return null;
|
|
3005
|
+
const { data, error } = await this.supabase.rpc("upsert_tester_presence", {
|
|
3006
|
+
p_project_id: this.config.projectId,
|
|
3007
|
+
p_tester_id: testerInfo.id,
|
|
3008
|
+
p_platform: platform
|
|
3009
|
+
});
|
|
3010
|
+
if (error) {
|
|
3011
|
+
console.error("BugBear: Failed to start presence", formatPgError(error));
|
|
3012
|
+
return null;
|
|
3013
|
+
}
|
|
3014
|
+
this._presenceSessionId = data;
|
|
3015
|
+
this._presencePaused = false;
|
|
3016
|
+
this.startPresenceHeartbeat();
|
|
3017
|
+
return data;
|
|
3018
|
+
} catch (err) {
|
|
3019
|
+
console.error("BugBear: Error starting presence", err);
|
|
3020
|
+
return null;
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
/** Gracefully end the current presence session. */
|
|
3024
|
+
async endPresence() {
|
|
3025
|
+
this.stopPresenceHeartbeat();
|
|
3026
|
+
if (!this._presenceSessionId) return;
|
|
3027
|
+
try {
|
|
3028
|
+
await this.supabase.rpc("end_tester_presence", {
|
|
3029
|
+
p_session_id: this._presenceSessionId
|
|
3030
|
+
});
|
|
3031
|
+
} catch {
|
|
3032
|
+
}
|
|
3033
|
+
this._presenceSessionId = null;
|
|
3034
|
+
}
|
|
3035
|
+
/** Pause heartbeat (tab hidden / app backgrounded). Sends one final beat. */
|
|
3036
|
+
pausePresence() {
|
|
3037
|
+
this._presencePaused = true;
|
|
3038
|
+
this.heartbeatPresence();
|
|
3039
|
+
}
|
|
3040
|
+
/** Resume heartbeat after pause. Restarts if session was cleaned up. */
|
|
3041
|
+
async resumePresence() {
|
|
3042
|
+
if (!this._presenceSessionId) return;
|
|
3043
|
+
this._presencePaused = false;
|
|
3044
|
+
try {
|
|
3045
|
+
const { data } = await this.supabase.rpc("heartbeat_tester_presence", {
|
|
3046
|
+
p_session_id: this._presenceSessionId
|
|
3047
|
+
});
|
|
3048
|
+
if (!data) {
|
|
3049
|
+
this._presenceSessionId = null;
|
|
3050
|
+
}
|
|
3051
|
+
} catch {
|
|
3052
|
+
this._presenceSessionId = null;
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
async heartbeatPresence() {
|
|
3056
|
+
if (!this._presenceSessionId || this._presencePaused) return;
|
|
3057
|
+
try {
|
|
3058
|
+
const { data, error } = await this.supabase.rpc("heartbeat_tester_presence", {
|
|
3059
|
+
p_session_id: this._presenceSessionId
|
|
3060
|
+
});
|
|
3061
|
+
if (error || data === false) {
|
|
3062
|
+
this.stopPresenceHeartbeat();
|
|
3063
|
+
this._presenceSessionId = null;
|
|
3064
|
+
}
|
|
3065
|
+
} catch {
|
|
3066
|
+
}
|
|
3067
|
+
}
|
|
3068
|
+
startPresenceHeartbeat() {
|
|
3069
|
+
this.stopPresenceHeartbeat();
|
|
3070
|
+
this._presenceInterval = setInterval(() => this.heartbeatPresence(), 6e4);
|
|
3071
|
+
}
|
|
3072
|
+
stopPresenceHeartbeat() {
|
|
3073
|
+
if (this._presenceInterval) {
|
|
3074
|
+
clearInterval(this._presenceInterval);
|
|
3075
|
+
this._presenceInterval = null;
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
2955
3078
|
};
|
|
2956
3079
|
function createBugBear(config) {
|
|
2957
3080
|
return new BugBearClient(config);
|