@bbearai/core 0.2.15 → 0.3.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 +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +80 -91
- package/dist/index.mjs +80 -91
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
type ReportType = 'bug' | 'feedback' | 'suggestion' | 'test_pass' | 'test_fail';
|
|
5
5
|
type Severity = 'critical' | 'high' | 'medium' | 'low';
|
|
6
|
-
type ReportStatus = 'new' | 'triaging' | 'confirmed' | 'in_progress' | 'fixed' | 'verified' | 'wont_fix' | 'duplicate';
|
|
6
|
+
type ReportStatus = 'new' | 'triaging' | 'confirmed' | 'in_progress' | 'fixed' | 'ready_to_test' | 'verified' | 'resolved' | 'reviewed' | 'closed' | 'wont_fix' | 'duplicate';
|
|
7
7
|
interface AppContext {
|
|
8
8
|
/** Current route/screen path */
|
|
9
9
|
currentRoute: string;
|
|
@@ -160,6 +160,11 @@ interface BugBearConfig {
|
|
|
160
160
|
* the full web experience (analytics, discussions, history, etc.)
|
|
161
161
|
*/
|
|
162
162
|
dashboardUrl?: string;
|
|
163
|
+
/**
|
|
164
|
+
* Called when an error occurs inside the BugBear SDK.
|
|
165
|
+
* Wire this to your error reporting service (e.g. Sentry.captureException).
|
|
166
|
+
*/
|
|
167
|
+
onError?: (error: Error, context?: Record<string, unknown>) => void;
|
|
163
168
|
}
|
|
164
169
|
interface BugBearTheme {
|
|
165
170
|
/** Primary brand color */
|
|
@@ -710,6 +715,19 @@ declare class BugBearClient {
|
|
|
710
715
|
* Uses parameterized RPC function to prevent SQL injection
|
|
711
716
|
*/
|
|
712
717
|
getTesterInfo(): Promise<TesterInfo | null>;
|
|
718
|
+
/**
|
|
719
|
+
* Get detailed assignment stats for the current tester via RPC.
|
|
720
|
+
* Returns counts by status (pending, in_progress, passed, failed, blocked, skipped, total).
|
|
721
|
+
*/
|
|
722
|
+
getTesterStats(): Promise<{
|
|
723
|
+
pending: number;
|
|
724
|
+
in_progress: number;
|
|
725
|
+
passed: number;
|
|
726
|
+
failed: number;
|
|
727
|
+
blocked: number;
|
|
728
|
+
skipped: number;
|
|
729
|
+
total: number;
|
|
730
|
+
} | null>;
|
|
713
731
|
/**
|
|
714
732
|
* Basic email format validation (defense in depth)
|
|
715
733
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
type ReportType = 'bug' | 'feedback' | 'suggestion' | 'test_pass' | 'test_fail';
|
|
5
5
|
type Severity = 'critical' | 'high' | 'medium' | 'low';
|
|
6
|
-
type ReportStatus = 'new' | 'triaging' | 'confirmed' | 'in_progress' | 'fixed' | 'verified' | 'wont_fix' | 'duplicate';
|
|
6
|
+
type ReportStatus = 'new' | 'triaging' | 'confirmed' | 'in_progress' | 'fixed' | 'ready_to_test' | 'verified' | 'resolved' | 'reviewed' | 'closed' | 'wont_fix' | 'duplicate';
|
|
7
7
|
interface AppContext {
|
|
8
8
|
/** Current route/screen path */
|
|
9
9
|
currentRoute: string;
|
|
@@ -160,6 +160,11 @@ interface BugBearConfig {
|
|
|
160
160
|
* the full web experience (analytics, discussions, history, etc.)
|
|
161
161
|
*/
|
|
162
162
|
dashboardUrl?: string;
|
|
163
|
+
/**
|
|
164
|
+
* Called when an error occurs inside the BugBear SDK.
|
|
165
|
+
* Wire this to your error reporting service (e.g. Sentry.captureException).
|
|
166
|
+
*/
|
|
167
|
+
onError?: (error: Error, context?: Record<string, unknown>) => void;
|
|
163
168
|
}
|
|
164
169
|
interface BugBearTheme {
|
|
165
170
|
/** Primary brand color */
|
|
@@ -710,6 +715,19 @@ declare class BugBearClient {
|
|
|
710
715
|
* Uses parameterized RPC function to prevent SQL injection
|
|
711
716
|
*/
|
|
712
717
|
getTesterInfo(): Promise<TesterInfo | null>;
|
|
718
|
+
/**
|
|
719
|
+
* Get detailed assignment stats for the current tester via RPC.
|
|
720
|
+
* Returns counts by status (pending, in_progress, passed, failed, blocked, skipped, total).
|
|
721
|
+
*/
|
|
722
|
+
getTesterStats(): Promise<{
|
|
723
|
+
pending: number;
|
|
724
|
+
in_progress: number;
|
|
725
|
+
passed: number;
|
|
726
|
+
failed: number;
|
|
727
|
+
blocked: number;
|
|
728
|
+
skipped: number;
|
|
729
|
+
total: number;
|
|
730
|
+
} | null>;
|
|
713
731
|
/**
|
|
714
732
|
* Basic email format validation (defense in depth)
|
|
715
733
|
*/
|
package/dist/index.js
CHANGED
|
@@ -273,6 +273,11 @@ function captureError(error, errorInfo) {
|
|
|
273
273
|
}
|
|
274
274
|
|
|
275
275
|
// src/client.ts
|
|
276
|
+
var formatPgError = (e) => {
|
|
277
|
+
if (!e || typeof e !== "object") return { raw: e };
|
|
278
|
+
const { message, code, details, hint } = e;
|
|
279
|
+
return { message, code, details, hint };
|
|
280
|
+
};
|
|
276
281
|
var DEFAULT_SUPABASE_URL = "https://kyxgzjnqgvapvlnvqawz.supabase.co";
|
|
277
282
|
var getEnvVar = (key) => {
|
|
278
283
|
try {
|
|
@@ -440,9 +445,9 @@ var BugBearClient = class {
|
|
|
440
445
|
sort_order
|
|
441
446
|
)
|
|
442
447
|
)
|
|
443
|
-
`).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true });
|
|
448
|
+
`).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).limit(100);
|
|
444
449
|
if (error) {
|
|
445
|
-
console.error("BugBear: Failed to fetch assignments", error);
|
|
450
|
+
console.error("BugBear: Failed to fetch assignments", formatPgError(error));
|
|
446
451
|
return [];
|
|
447
452
|
}
|
|
448
453
|
const mapped = (data || []).map((item) => ({
|
|
@@ -594,7 +599,7 @@ var BugBearClient = class {
|
|
|
594
599
|
if (options?.testResult) {
|
|
595
600
|
updateData.test_result = options.testResult;
|
|
596
601
|
}
|
|
597
|
-
const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
|
|
602
|
+
const { data: updatedRow, error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId).eq("status", currentAssignment.status).select("id").maybeSingle();
|
|
598
603
|
if (error) {
|
|
599
604
|
console.error("BugBear: Failed to update assignment status", {
|
|
600
605
|
message: error.message,
|
|
@@ -607,6 +612,9 @@ var BugBearClient = class {
|
|
|
607
612
|
});
|
|
608
613
|
return { success: false, error: error.message };
|
|
609
614
|
}
|
|
615
|
+
if (!updatedRow) {
|
|
616
|
+
return { success: false, error: "Assignment status has changed. Please refresh and try again." };
|
|
617
|
+
}
|
|
610
618
|
if (options?.feedback && ["passed", "failed", "blocked"].includes(status)) {
|
|
611
619
|
const { data: assignmentData, error: fetchError2 } = await this.supabase.from("test_assignments").select("test_case_id").eq("id", assignmentId).single();
|
|
612
620
|
if (fetchError2) {
|
|
@@ -667,7 +675,7 @@ var BugBearClient = class {
|
|
|
667
675
|
}
|
|
668
676
|
const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
|
|
669
677
|
if (error) {
|
|
670
|
-
console.error("BugBear: Failed to skip assignment", error);
|
|
678
|
+
console.error("BugBear: Failed to skip assignment", formatPgError(error));
|
|
671
679
|
return { success: false, error: error.message };
|
|
672
680
|
}
|
|
673
681
|
return { success: true };
|
|
@@ -833,6 +841,28 @@ var BugBearClient = class {
|
|
|
833
841
|
return null;
|
|
834
842
|
}
|
|
835
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* Get detailed assignment stats for the current tester via RPC.
|
|
846
|
+
* Returns counts by status (pending, in_progress, passed, failed, blocked, skipped, total).
|
|
847
|
+
*/
|
|
848
|
+
async getTesterStats() {
|
|
849
|
+
try {
|
|
850
|
+
const testerInfo = await this.getTesterInfo();
|
|
851
|
+
if (!testerInfo) return null;
|
|
852
|
+
const { data, error } = await this.supabase.rpc("get_tester_stats", {
|
|
853
|
+
p_project_id: this.config.projectId,
|
|
854
|
+
p_tester_id: testerInfo.id
|
|
855
|
+
});
|
|
856
|
+
if (error) {
|
|
857
|
+
console.error("BugBear: Failed to fetch tester stats", formatPgError(error));
|
|
858
|
+
return null;
|
|
859
|
+
}
|
|
860
|
+
return data;
|
|
861
|
+
} catch (err) {
|
|
862
|
+
console.error("BugBear: Error fetching tester stats", err);
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
836
866
|
/**
|
|
837
867
|
* Basic email format validation (defense in depth)
|
|
838
868
|
*/
|
|
@@ -975,7 +1005,7 @@ var BugBearClient = class {
|
|
|
975
1005
|
if (updates.platforms !== void 0) updateData.platforms = updates.platforms;
|
|
976
1006
|
const { error } = await this.supabase.from("testers").update(updateData).eq("id", testerInfo.id);
|
|
977
1007
|
if (error) {
|
|
978
|
-
console.error("BugBear: updateTesterProfile error", error);
|
|
1008
|
+
console.error("BugBear: updateTesterProfile error", formatPgError(error));
|
|
979
1009
|
return { success: false, error: error.message };
|
|
980
1010
|
}
|
|
981
1011
|
return { success: true };
|
|
@@ -997,14 +1027,14 @@ var BugBearClient = class {
|
|
|
997
1027
|
*/
|
|
998
1028
|
async isQAEnabled() {
|
|
999
1029
|
try {
|
|
1000
|
-
const { data, error } = await this.supabase.
|
|
1030
|
+
const { data, error } = await this.supabase.rpc("check_qa_enabled", {
|
|
1031
|
+
p_project_id: this.config.projectId
|
|
1032
|
+
});
|
|
1001
1033
|
if (error) {
|
|
1002
|
-
|
|
1003
|
-
console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
|
|
1004
|
-
}
|
|
1034
|
+
console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
|
|
1005
1035
|
return true;
|
|
1006
1036
|
}
|
|
1007
|
-
return data
|
|
1037
|
+
return data ?? true;
|
|
1008
1038
|
} catch (err) {
|
|
1009
1039
|
return true;
|
|
1010
1040
|
}
|
|
@@ -1034,7 +1064,7 @@ var BugBearClient = class {
|
|
|
1034
1064
|
upsert: false
|
|
1035
1065
|
});
|
|
1036
1066
|
if (error) {
|
|
1037
|
-
console.error("BugBear: Failed to upload screenshot", error);
|
|
1067
|
+
console.error("BugBear: Failed to upload screenshot", formatPgError(error));
|
|
1038
1068
|
return null;
|
|
1039
1069
|
}
|
|
1040
1070
|
const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
|
|
@@ -1065,7 +1095,7 @@ var BugBearClient = class {
|
|
|
1065
1095
|
upsert: false
|
|
1066
1096
|
});
|
|
1067
1097
|
if (error) {
|
|
1068
|
-
console.error("BugBear: Failed to upload image from URI", error);
|
|
1098
|
+
console.error("BugBear: Failed to upload image from URI", formatPgError(error));
|
|
1069
1099
|
return null;
|
|
1070
1100
|
}
|
|
1071
1101
|
const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
|
|
@@ -1140,7 +1170,7 @@ var BugBearClient = class {
|
|
|
1140
1170
|
}
|
|
1141
1171
|
const { data, error } = await query;
|
|
1142
1172
|
if (error) {
|
|
1143
|
-
console.error("BugBear: Failed to fetch fix requests", error);
|
|
1173
|
+
console.error("BugBear: Failed to fetch fix requests", formatPgError(error));
|
|
1144
1174
|
return [];
|
|
1145
1175
|
}
|
|
1146
1176
|
return (data || []).map((fr) => ({
|
|
@@ -1166,75 +1196,35 @@ var BugBearClient = class {
|
|
|
1166
1196
|
try {
|
|
1167
1197
|
const testerInfo = await this.getTesterInfo();
|
|
1168
1198
|
if (!testerInfo) return [];
|
|
1169
|
-
const { data
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
priority,
|
|
1174
|
-
is_pinned,
|
|
1175
|
-
is_resolved,
|
|
1176
|
-
last_message_at,
|
|
1177
|
-
created_at
|
|
1178
|
-
`).eq("project_id", this.config.projectId).or(`audience.eq.all,audience_tester_ids.cs.{${testerInfo.id}}`).order("is_pinned", { ascending: false }).order("last_message_at", { ascending: false });
|
|
1199
|
+
const { data, error } = await this.supabase.rpc("get_threads_with_unread", {
|
|
1200
|
+
p_project_id: this.config.projectId,
|
|
1201
|
+
p_tester_id: testerInfo.id
|
|
1202
|
+
});
|
|
1179
1203
|
if (error) {
|
|
1180
|
-
console.error("BugBear: Failed to fetch threads", error);
|
|
1204
|
+
console.error("BugBear: Failed to fetch threads via RPC", formatPgError(error));
|
|
1181
1205
|
return [];
|
|
1182
1206
|
}
|
|
1183
|
-
if (!
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
const unreadCounts = await Promise.all(
|
|
1205
|
-
threads.map(async (thread) => {
|
|
1206
|
-
const readStatus = readStatusMap.get(thread.id);
|
|
1207
|
-
const lastReadAt = readStatus?.last_read_at || "1970-01-01T00:00:00Z";
|
|
1208
|
-
const { count, error: countError } = await this.supabase.from("discussion_messages").select("*", { count: "exact", head: true }).eq("thread_id", thread.id).gt("created_at", lastReadAt);
|
|
1209
|
-
return { threadId: thread.id, count: countError ? 0 : count || 0 };
|
|
1210
|
-
})
|
|
1211
|
-
);
|
|
1212
|
-
const unreadCountMap = new Map(
|
|
1213
|
-
unreadCounts.map((uc) => [uc.threadId, uc.count])
|
|
1214
|
-
);
|
|
1215
|
-
return threads.map((thread) => {
|
|
1216
|
-
const lastMsg = lastMessageMap.get(thread.id);
|
|
1217
|
-
return {
|
|
1218
|
-
id: thread.id,
|
|
1219
|
-
subject: thread.subject,
|
|
1220
|
-
threadType: thread.thread_type,
|
|
1221
|
-
priority: thread.priority,
|
|
1222
|
-
isPinned: thread.is_pinned,
|
|
1223
|
-
isResolved: thread.is_resolved,
|
|
1224
|
-
lastMessageAt: thread.last_message_at,
|
|
1225
|
-
createdAt: thread.created_at,
|
|
1226
|
-
unreadCount: unreadCountMap.get(thread.id) || 0,
|
|
1227
|
-
lastMessage: lastMsg ? {
|
|
1228
|
-
id: lastMsg.id,
|
|
1229
|
-
threadId: lastMsg.thread_id,
|
|
1230
|
-
senderType: lastMsg.sender_type,
|
|
1231
|
-
senderName: lastMsg.sender_name,
|
|
1232
|
-
content: lastMsg.content,
|
|
1233
|
-
createdAt: lastMsg.created_at,
|
|
1234
|
-
attachments: lastMsg.attachments || []
|
|
1235
|
-
} : void 0
|
|
1236
|
-
};
|
|
1237
|
-
});
|
|
1207
|
+
if (!data || data.length === 0) return [];
|
|
1208
|
+
return data.map((row) => ({
|
|
1209
|
+
id: row.thread_id,
|
|
1210
|
+
subject: row.thread_subject,
|
|
1211
|
+
threadType: row.thread_type,
|
|
1212
|
+
priority: row.thread_priority,
|
|
1213
|
+
isPinned: row.is_pinned,
|
|
1214
|
+
isResolved: row.is_resolved,
|
|
1215
|
+
lastMessageAt: row.last_message_at,
|
|
1216
|
+
createdAt: row.created_at,
|
|
1217
|
+
unreadCount: Number(row.unread_count) || 0,
|
|
1218
|
+
lastMessage: row.last_message_preview ? {
|
|
1219
|
+
id: "",
|
|
1220
|
+
threadId: row.thread_id,
|
|
1221
|
+
senderType: row.last_message_sender_type || "system",
|
|
1222
|
+
senderName: row.last_message_sender_name || "",
|
|
1223
|
+
content: row.last_message_preview,
|
|
1224
|
+
createdAt: row.last_message_at,
|
|
1225
|
+
attachments: []
|
|
1226
|
+
} : void 0
|
|
1227
|
+
}));
|
|
1238
1228
|
} catch (err) {
|
|
1239
1229
|
console.error("BugBear: Error fetching threads", err);
|
|
1240
1230
|
return [];
|
|
@@ -1253,9 +1243,9 @@ var BugBearClient = class {
|
|
|
1253
1243
|
content,
|
|
1254
1244
|
created_at,
|
|
1255
1245
|
attachments
|
|
1256
|
-
`).eq("thread_id", threadId).order("created_at", { ascending: true });
|
|
1246
|
+
`).eq("thread_id", threadId).order("created_at", { ascending: true }).limit(200);
|
|
1257
1247
|
if (error) {
|
|
1258
|
-
console.error("BugBear: Failed to fetch messages", error);
|
|
1248
|
+
console.error("BugBear: Failed to fetch messages", formatPgError(error));
|
|
1259
1249
|
return [];
|
|
1260
1250
|
}
|
|
1261
1251
|
return (data || []).map((msg) => ({
|
|
@@ -1299,10 +1289,9 @@ var BugBearClient = class {
|
|
|
1299
1289
|
}
|
|
1300
1290
|
const { error } = await this.supabase.from("discussion_messages").insert(insertData);
|
|
1301
1291
|
if (error) {
|
|
1302
|
-
console.error("BugBear: Failed to send message", error);
|
|
1292
|
+
console.error("BugBear: Failed to send message", formatPgError(error));
|
|
1303
1293
|
return false;
|
|
1304
1294
|
}
|
|
1305
|
-
await this.supabase.from("discussion_threads").update({ last_message_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", threadId);
|
|
1306
1295
|
await this.markThreadAsRead(threadId);
|
|
1307
1296
|
return true;
|
|
1308
1297
|
} catch (err) {
|
|
@@ -1416,7 +1405,7 @@ var BugBearClient = class {
|
|
|
1416
1405
|
p_platform: options.platform || null
|
|
1417
1406
|
});
|
|
1418
1407
|
if (error) {
|
|
1419
|
-
console.error("BugBear: Failed to start session", error);
|
|
1408
|
+
console.error("BugBear: Failed to start session", formatPgError(error));
|
|
1420
1409
|
return { success: false, error: error.message };
|
|
1421
1410
|
}
|
|
1422
1411
|
const session = await this.getSession(data);
|
|
@@ -1441,7 +1430,7 @@ var BugBearClient = class {
|
|
|
1441
1430
|
p_routes_covered: options.routesCovered || null
|
|
1442
1431
|
});
|
|
1443
1432
|
if (error) {
|
|
1444
|
-
console.error("BugBear: Failed to end session", error);
|
|
1433
|
+
console.error("BugBear: Failed to end session", formatPgError(error));
|
|
1445
1434
|
return { success: false, error: error.message };
|
|
1446
1435
|
}
|
|
1447
1436
|
const session = this.transformSession(data);
|
|
@@ -1489,7 +1478,7 @@ var BugBearClient = class {
|
|
|
1489
1478
|
if (!testerInfo) return [];
|
|
1490
1479
|
const { data, error } = await this.supabase.from("qa_sessions").select("*").eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).order("started_at", { ascending: false }).limit(limit);
|
|
1491
1480
|
if (error) {
|
|
1492
|
-
console.error("BugBear: Failed to fetch session history", error);
|
|
1481
|
+
console.error("BugBear: Failed to fetch session history", formatPgError(error));
|
|
1493
1482
|
return [];
|
|
1494
1483
|
}
|
|
1495
1484
|
return (data || []).map((s) => this.transformSession(s));
|
|
@@ -1517,7 +1506,7 @@ var BugBearClient = class {
|
|
|
1517
1506
|
p_app_context: options.appContext || null
|
|
1518
1507
|
});
|
|
1519
1508
|
if (error) {
|
|
1520
|
-
console.error("BugBear: Failed to add finding", error);
|
|
1509
|
+
console.error("BugBear: Failed to add finding", formatPgError(error));
|
|
1521
1510
|
return { success: false, error: error.message };
|
|
1522
1511
|
}
|
|
1523
1512
|
const finding = this.transformFinding(data);
|
|
@@ -1533,9 +1522,9 @@ var BugBearClient = class {
|
|
|
1533
1522
|
*/
|
|
1534
1523
|
async getSessionFindings(sessionId) {
|
|
1535
1524
|
try {
|
|
1536
|
-
const { data, error } = await this.supabase.from("qa_findings").select("*").eq("session_id", sessionId).order("created_at", { ascending: true });
|
|
1525
|
+
const { data, error } = await this.supabase.from("qa_findings").select("*").eq("session_id", sessionId).order("created_at", { ascending: true }).limit(100);
|
|
1537
1526
|
if (error) {
|
|
1538
|
-
console.error("BugBear: Failed to fetch findings", error);
|
|
1527
|
+
console.error("BugBear: Failed to fetch findings", formatPgError(error));
|
|
1539
1528
|
return [];
|
|
1540
1529
|
}
|
|
1541
1530
|
return (data || []).map((f) => this.transformFinding(f));
|
|
@@ -1553,7 +1542,7 @@ var BugBearClient = class {
|
|
|
1553
1542
|
p_finding_id: findingId
|
|
1554
1543
|
});
|
|
1555
1544
|
if (error) {
|
|
1556
|
-
console.error("BugBear: Failed to convert finding", error);
|
|
1545
|
+
console.error("BugBear: Failed to convert finding", formatPgError(error));
|
|
1557
1546
|
return { success: false, error: error.message };
|
|
1558
1547
|
}
|
|
1559
1548
|
return { success: true, bugId: data };
|
|
@@ -1574,7 +1563,7 @@ var BugBearClient = class {
|
|
|
1574
1563
|
dismissed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1575
1564
|
}).eq("id", findingId);
|
|
1576
1565
|
if (error) {
|
|
1577
|
-
console.error("BugBear: Failed to dismiss finding", error);
|
|
1566
|
+
console.error("BugBear: Failed to dismiss finding", formatPgError(error));
|
|
1578
1567
|
return { success: false, error: error.message };
|
|
1579
1568
|
}
|
|
1580
1569
|
return { success: true };
|
package/dist/index.mjs
CHANGED
|
@@ -244,6 +244,11 @@ function captureError(error, errorInfo) {
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
// src/client.ts
|
|
247
|
+
var formatPgError = (e) => {
|
|
248
|
+
if (!e || typeof e !== "object") return { raw: e };
|
|
249
|
+
const { message, code, details, hint } = e;
|
|
250
|
+
return { message, code, details, hint };
|
|
251
|
+
};
|
|
247
252
|
var DEFAULT_SUPABASE_URL = "https://kyxgzjnqgvapvlnvqawz.supabase.co";
|
|
248
253
|
var getEnvVar = (key) => {
|
|
249
254
|
try {
|
|
@@ -411,9 +416,9 @@ var BugBearClient = class {
|
|
|
411
416
|
sort_order
|
|
412
417
|
)
|
|
413
418
|
)
|
|
414
|
-
`).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true });
|
|
419
|
+
`).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).limit(100);
|
|
415
420
|
if (error) {
|
|
416
|
-
console.error("BugBear: Failed to fetch assignments", error);
|
|
421
|
+
console.error("BugBear: Failed to fetch assignments", formatPgError(error));
|
|
417
422
|
return [];
|
|
418
423
|
}
|
|
419
424
|
const mapped = (data || []).map((item) => ({
|
|
@@ -565,7 +570,7 @@ var BugBearClient = class {
|
|
|
565
570
|
if (options?.testResult) {
|
|
566
571
|
updateData.test_result = options.testResult;
|
|
567
572
|
}
|
|
568
|
-
const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
|
|
573
|
+
const { data: updatedRow, error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId).eq("status", currentAssignment.status).select("id").maybeSingle();
|
|
569
574
|
if (error) {
|
|
570
575
|
console.error("BugBear: Failed to update assignment status", {
|
|
571
576
|
message: error.message,
|
|
@@ -578,6 +583,9 @@ var BugBearClient = class {
|
|
|
578
583
|
});
|
|
579
584
|
return { success: false, error: error.message };
|
|
580
585
|
}
|
|
586
|
+
if (!updatedRow) {
|
|
587
|
+
return { success: false, error: "Assignment status has changed. Please refresh and try again." };
|
|
588
|
+
}
|
|
581
589
|
if (options?.feedback && ["passed", "failed", "blocked"].includes(status)) {
|
|
582
590
|
const { data: assignmentData, error: fetchError2 } = await this.supabase.from("test_assignments").select("test_case_id").eq("id", assignmentId).single();
|
|
583
591
|
if (fetchError2) {
|
|
@@ -638,7 +646,7 @@ var BugBearClient = class {
|
|
|
638
646
|
}
|
|
639
647
|
const { error } = await this.supabase.from("test_assignments").update(updateData).eq("id", assignmentId);
|
|
640
648
|
if (error) {
|
|
641
|
-
console.error("BugBear: Failed to skip assignment", error);
|
|
649
|
+
console.error("BugBear: Failed to skip assignment", formatPgError(error));
|
|
642
650
|
return { success: false, error: error.message };
|
|
643
651
|
}
|
|
644
652
|
return { success: true };
|
|
@@ -804,6 +812,28 @@ var BugBearClient = class {
|
|
|
804
812
|
return null;
|
|
805
813
|
}
|
|
806
814
|
}
|
|
815
|
+
/**
|
|
816
|
+
* Get detailed assignment stats for the current tester via RPC.
|
|
817
|
+
* Returns counts by status (pending, in_progress, passed, failed, blocked, skipped, total).
|
|
818
|
+
*/
|
|
819
|
+
async getTesterStats() {
|
|
820
|
+
try {
|
|
821
|
+
const testerInfo = await this.getTesterInfo();
|
|
822
|
+
if (!testerInfo) return null;
|
|
823
|
+
const { data, error } = await this.supabase.rpc("get_tester_stats", {
|
|
824
|
+
p_project_id: this.config.projectId,
|
|
825
|
+
p_tester_id: testerInfo.id
|
|
826
|
+
});
|
|
827
|
+
if (error) {
|
|
828
|
+
console.error("BugBear: Failed to fetch tester stats", formatPgError(error));
|
|
829
|
+
return null;
|
|
830
|
+
}
|
|
831
|
+
return data;
|
|
832
|
+
} catch (err) {
|
|
833
|
+
console.error("BugBear: Error fetching tester stats", err);
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
807
837
|
/**
|
|
808
838
|
* Basic email format validation (defense in depth)
|
|
809
839
|
*/
|
|
@@ -946,7 +976,7 @@ var BugBearClient = class {
|
|
|
946
976
|
if (updates.platforms !== void 0) updateData.platforms = updates.platforms;
|
|
947
977
|
const { error } = await this.supabase.from("testers").update(updateData).eq("id", testerInfo.id);
|
|
948
978
|
if (error) {
|
|
949
|
-
console.error("BugBear: updateTesterProfile error", error);
|
|
979
|
+
console.error("BugBear: updateTesterProfile error", formatPgError(error));
|
|
950
980
|
return { success: false, error: error.message };
|
|
951
981
|
}
|
|
952
982
|
return { success: true };
|
|
@@ -968,14 +998,14 @@ var BugBearClient = class {
|
|
|
968
998
|
*/
|
|
969
999
|
async isQAEnabled() {
|
|
970
1000
|
try {
|
|
971
|
-
const { data, error } = await this.supabase.
|
|
1001
|
+
const { data, error } = await this.supabase.rpc("check_qa_enabled", {
|
|
1002
|
+
p_project_id: this.config.projectId
|
|
1003
|
+
});
|
|
972
1004
|
if (error) {
|
|
973
|
-
|
|
974
|
-
console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
|
|
975
|
-
}
|
|
1005
|
+
console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
|
|
976
1006
|
return true;
|
|
977
1007
|
}
|
|
978
|
-
return data
|
|
1008
|
+
return data ?? true;
|
|
979
1009
|
} catch (err) {
|
|
980
1010
|
return true;
|
|
981
1011
|
}
|
|
@@ -1005,7 +1035,7 @@ var BugBearClient = class {
|
|
|
1005
1035
|
upsert: false
|
|
1006
1036
|
});
|
|
1007
1037
|
if (error) {
|
|
1008
|
-
console.error("BugBear: Failed to upload screenshot", error);
|
|
1038
|
+
console.error("BugBear: Failed to upload screenshot", formatPgError(error));
|
|
1009
1039
|
return null;
|
|
1010
1040
|
}
|
|
1011
1041
|
const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
|
|
@@ -1036,7 +1066,7 @@ var BugBearClient = class {
|
|
|
1036
1066
|
upsert: false
|
|
1037
1067
|
});
|
|
1038
1068
|
if (error) {
|
|
1039
|
-
console.error("BugBear: Failed to upload image from URI", error);
|
|
1069
|
+
console.error("BugBear: Failed to upload image from URI", formatPgError(error));
|
|
1040
1070
|
return null;
|
|
1041
1071
|
}
|
|
1042
1072
|
const { data: { publicUrl } } = this.supabase.storage.from(bucket).getPublicUrl(path);
|
|
@@ -1111,7 +1141,7 @@ var BugBearClient = class {
|
|
|
1111
1141
|
}
|
|
1112
1142
|
const { data, error } = await query;
|
|
1113
1143
|
if (error) {
|
|
1114
|
-
console.error("BugBear: Failed to fetch fix requests", error);
|
|
1144
|
+
console.error("BugBear: Failed to fetch fix requests", formatPgError(error));
|
|
1115
1145
|
return [];
|
|
1116
1146
|
}
|
|
1117
1147
|
return (data || []).map((fr) => ({
|
|
@@ -1137,75 +1167,35 @@ var BugBearClient = class {
|
|
|
1137
1167
|
try {
|
|
1138
1168
|
const testerInfo = await this.getTesterInfo();
|
|
1139
1169
|
if (!testerInfo) return [];
|
|
1140
|
-
const { data
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
priority,
|
|
1145
|
-
is_pinned,
|
|
1146
|
-
is_resolved,
|
|
1147
|
-
last_message_at,
|
|
1148
|
-
created_at
|
|
1149
|
-
`).eq("project_id", this.config.projectId).or(`audience.eq.all,audience_tester_ids.cs.{${testerInfo.id}}`).order("is_pinned", { ascending: false }).order("last_message_at", { ascending: false });
|
|
1170
|
+
const { data, error } = await this.supabase.rpc("get_threads_with_unread", {
|
|
1171
|
+
p_project_id: this.config.projectId,
|
|
1172
|
+
p_tester_id: testerInfo.id
|
|
1173
|
+
});
|
|
1150
1174
|
if (error) {
|
|
1151
|
-
console.error("BugBear: Failed to fetch threads", error);
|
|
1175
|
+
console.error("BugBear: Failed to fetch threads via RPC", formatPgError(error));
|
|
1152
1176
|
return [];
|
|
1153
1177
|
}
|
|
1154
|
-
if (!
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
const unreadCounts = await Promise.all(
|
|
1176
|
-
threads.map(async (thread) => {
|
|
1177
|
-
const readStatus = readStatusMap.get(thread.id);
|
|
1178
|
-
const lastReadAt = readStatus?.last_read_at || "1970-01-01T00:00:00Z";
|
|
1179
|
-
const { count, error: countError } = await this.supabase.from("discussion_messages").select("*", { count: "exact", head: true }).eq("thread_id", thread.id).gt("created_at", lastReadAt);
|
|
1180
|
-
return { threadId: thread.id, count: countError ? 0 : count || 0 };
|
|
1181
|
-
})
|
|
1182
|
-
);
|
|
1183
|
-
const unreadCountMap = new Map(
|
|
1184
|
-
unreadCounts.map((uc) => [uc.threadId, uc.count])
|
|
1185
|
-
);
|
|
1186
|
-
return threads.map((thread) => {
|
|
1187
|
-
const lastMsg = lastMessageMap.get(thread.id);
|
|
1188
|
-
return {
|
|
1189
|
-
id: thread.id,
|
|
1190
|
-
subject: thread.subject,
|
|
1191
|
-
threadType: thread.thread_type,
|
|
1192
|
-
priority: thread.priority,
|
|
1193
|
-
isPinned: thread.is_pinned,
|
|
1194
|
-
isResolved: thread.is_resolved,
|
|
1195
|
-
lastMessageAt: thread.last_message_at,
|
|
1196
|
-
createdAt: thread.created_at,
|
|
1197
|
-
unreadCount: unreadCountMap.get(thread.id) || 0,
|
|
1198
|
-
lastMessage: lastMsg ? {
|
|
1199
|
-
id: lastMsg.id,
|
|
1200
|
-
threadId: lastMsg.thread_id,
|
|
1201
|
-
senderType: lastMsg.sender_type,
|
|
1202
|
-
senderName: lastMsg.sender_name,
|
|
1203
|
-
content: lastMsg.content,
|
|
1204
|
-
createdAt: lastMsg.created_at,
|
|
1205
|
-
attachments: lastMsg.attachments || []
|
|
1206
|
-
} : void 0
|
|
1207
|
-
};
|
|
1208
|
-
});
|
|
1178
|
+
if (!data || data.length === 0) return [];
|
|
1179
|
+
return data.map((row) => ({
|
|
1180
|
+
id: row.thread_id,
|
|
1181
|
+
subject: row.thread_subject,
|
|
1182
|
+
threadType: row.thread_type,
|
|
1183
|
+
priority: row.thread_priority,
|
|
1184
|
+
isPinned: row.is_pinned,
|
|
1185
|
+
isResolved: row.is_resolved,
|
|
1186
|
+
lastMessageAt: row.last_message_at,
|
|
1187
|
+
createdAt: row.created_at,
|
|
1188
|
+
unreadCount: Number(row.unread_count) || 0,
|
|
1189
|
+
lastMessage: row.last_message_preview ? {
|
|
1190
|
+
id: "",
|
|
1191
|
+
threadId: row.thread_id,
|
|
1192
|
+
senderType: row.last_message_sender_type || "system",
|
|
1193
|
+
senderName: row.last_message_sender_name || "",
|
|
1194
|
+
content: row.last_message_preview,
|
|
1195
|
+
createdAt: row.last_message_at,
|
|
1196
|
+
attachments: []
|
|
1197
|
+
} : void 0
|
|
1198
|
+
}));
|
|
1209
1199
|
} catch (err) {
|
|
1210
1200
|
console.error("BugBear: Error fetching threads", err);
|
|
1211
1201
|
return [];
|
|
@@ -1224,9 +1214,9 @@ var BugBearClient = class {
|
|
|
1224
1214
|
content,
|
|
1225
1215
|
created_at,
|
|
1226
1216
|
attachments
|
|
1227
|
-
`).eq("thread_id", threadId).order("created_at", { ascending: true });
|
|
1217
|
+
`).eq("thread_id", threadId).order("created_at", { ascending: true }).limit(200);
|
|
1228
1218
|
if (error) {
|
|
1229
|
-
console.error("BugBear: Failed to fetch messages", error);
|
|
1219
|
+
console.error("BugBear: Failed to fetch messages", formatPgError(error));
|
|
1230
1220
|
return [];
|
|
1231
1221
|
}
|
|
1232
1222
|
return (data || []).map((msg) => ({
|
|
@@ -1270,10 +1260,9 @@ var BugBearClient = class {
|
|
|
1270
1260
|
}
|
|
1271
1261
|
const { error } = await this.supabase.from("discussion_messages").insert(insertData);
|
|
1272
1262
|
if (error) {
|
|
1273
|
-
console.error("BugBear: Failed to send message", error);
|
|
1263
|
+
console.error("BugBear: Failed to send message", formatPgError(error));
|
|
1274
1264
|
return false;
|
|
1275
1265
|
}
|
|
1276
|
-
await this.supabase.from("discussion_threads").update({ last_message_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", threadId);
|
|
1277
1266
|
await this.markThreadAsRead(threadId);
|
|
1278
1267
|
return true;
|
|
1279
1268
|
} catch (err) {
|
|
@@ -1387,7 +1376,7 @@ var BugBearClient = class {
|
|
|
1387
1376
|
p_platform: options.platform || null
|
|
1388
1377
|
});
|
|
1389
1378
|
if (error) {
|
|
1390
|
-
console.error("BugBear: Failed to start session", error);
|
|
1379
|
+
console.error("BugBear: Failed to start session", formatPgError(error));
|
|
1391
1380
|
return { success: false, error: error.message };
|
|
1392
1381
|
}
|
|
1393
1382
|
const session = await this.getSession(data);
|
|
@@ -1412,7 +1401,7 @@ var BugBearClient = class {
|
|
|
1412
1401
|
p_routes_covered: options.routesCovered || null
|
|
1413
1402
|
});
|
|
1414
1403
|
if (error) {
|
|
1415
|
-
console.error("BugBear: Failed to end session", error);
|
|
1404
|
+
console.error("BugBear: Failed to end session", formatPgError(error));
|
|
1416
1405
|
return { success: false, error: error.message };
|
|
1417
1406
|
}
|
|
1418
1407
|
const session = this.transformSession(data);
|
|
@@ -1460,7 +1449,7 @@ var BugBearClient = class {
|
|
|
1460
1449
|
if (!testerInfo) return [];
|
|
1461
1450
|
const { data, error } = await this.supabase.from("qa_sessions").select("*").eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).order("started_at", { ascending: false }).limit(limit);
|
|
1462
1451
|
if (error) {
|
|
1463
|
-
console.error("BugBear: Failed to fetch session history", error);
|
|
1452
|
+
console.error("BugBear: Failed to fetch session history", formatPgError(error));
|
|
1464
1453
|
return [];
|
|
1465
1454
|
}
|
|
1466
1455
|
return (data || []).map((s) => this.transformSession(s));
|
|
@@ -1488,7 +1477,7 @@ var BugBearClient = class {
|
|
|
1488
1477
|
p_app_context: options.appContext || null
|
|
1489
1478
|
});
|
|
1490
1479
|
if (error) {
|
|
1491
|
-
console.error("BugBear: Failed to add finding", error);
|
|
1480
|
+
console.error("BugBear: Failed to add finding", formatPgError(error));
|
|
1492
1481
|
return { success: false, error: error.message };
|
|
1493
1482
|
}
|
|
1494
1483
|
const finding = this.transformFinding(data);
|
|
@@ -1504,9 +1493,9 @@ var BugBearClient = class {
|
|
|
1504
1493
|
*/
|
|
1505
1494
|
async getSessionFindings(sessionId) {
|
|
1506
1495
|
try {
|
|
1507
|
-
const { data, error } = await this.supabase.from("qa_findings").select("*").eq("session_id", sessionId).order("created_at", { ascending: true });
|
|
1496
|
+
const { data, error } = await this.supabase.from("qa_findings").select("*").eq("session_id", sessionId).order("created_at", { ascending: true }).limit(100);
|
|
1508
1497
|
if (error) {
|
|
1509
|
-
console.error("BugBear: Failed to fetch findings", error);
|
|
1498
|
+
console.error("BugBear: Failed to fetch findings", formatPgError(error));
|
|
1510
1499
|
return [];
|
|
1511
1500
|
}
|
|
1512
1501
|
return (data || []).map((f) => this.transformFinding(f));
|
|
@@ -1524,7 +1513,7 @@ var BugBearClient = class {
|
|
|
1524
1513
|
p_finding_id: findingId
|
|
1525
1514
|
});
|
|
1526
1515
|
if (error) {
|
|
1527
|
-
console.error("BugBear: Failed to convert finding", error);
|
|
1516
|
+
console.error("BugBear: Failed to convert finding", formatPgError(error));
|
|
1528
1517
|
return { success: false, error: error.message };
|
|
1529
1518
|
}
|
|
1530
1519
|
return { success: true, bugId: data };
|
|
@@ -1545,7 +1534,7 @@ var BugBearClient = class {
|
|
|
1545
1534
|
dismissed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1546
1535
|
}).eq("id", findingId);
|
|
1547
1536
|
if (error) {
|
|
1548
|
-
console.error("BugBear: Failed to dismiss finding", error);
|
|
1537
|
+
console.error("BugBear: Failed to dismiss finding", formatPgError(error));
|
|
1549
1538
|
return { success: false, error: error.message };
|
|
1550
1539
|
}
|
|
1551
1540
|
return { success: true };
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bbearai/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Core utilities and types for BugBear QA platform",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
10
11
|
"import": "./dist/index.mjs",
|
|
11
|
-
"require": "./dist/index.js"
|
|
12
|
-
"types": "./dist/index.d.ts"
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"files": [
|