@sailfish-ai/recorder 1.8.15 → 1.8.17
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/graphql.js +80 -0
- package/dist/{inAppReportIssueModal.js → inAppReportIssueModal/index.js} +660 -83
- package/dist/inAppReportIssueModal/integrations.js +240 -0
- package/dist/inAppReportIssueModal/state.js +69 -0
- package/dist/inAppReportIssueModal/types.js +16 -0
- package/dist/inAppReportIssueModal/ui.js +475 -0
- package/dist/index.js +18 -3
- package/dist/recorder.cjs +1951 -1406
- package/dist/recorder.js +1631 -1084
- package/dist/recorder.js.br +0 -0
- package/dist/recorder.js.gz +0 -0
- package/dist/recorder.umd.cjs +1629 -1084
- package/dist/types/graphql.d.ts +3 -1
- package/dist/types/{inAppReportIssueModal.d.ts → inAppReportIssueModal/index.d.ts} +2 -0
- package/dist/types/inAppReportIssueModal/integrations.d.ts +12 -0
- package/dist/types/inAppReportIssueModal/state.d.ts +18 -0
- package/dist/types/inAppReportIssueModal/types.d.ts +40 -0
- package/dist/types/inAppReportIssueModal/ui.d.ts +6 -0
- package/dist/types/types.d.ts +30 -0
- package/package.json +6 -1
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { createTriageFromRecorder } from "
|
|
1
|
+
import { createTriageAndIssueFromRecorder, createTriageFromRecorder, } from "../graphql";
|
|
2
|
+
import { getFieldsForProject, getIntegrationData, getProjectsForTeam, getUsers, hasValidIntegration, updateFormWithIntegrationData, updateIssueTypeOptions, } from "./integrations";
|
|
3
|
+
import { currentState, isRecording, recordingEndTime, recordingStartTime, resetState, setIsRecording, setRecordingEndTime, setRecordingStartTime, setTimerInterval, timerInterval, } from "./state";
|
|
4
|
+
import { STORAGE_KEYS } from "./types";
|
|
5
|
+
import { getChevronSVG, renderCustomMultiSelect, renderDynamicField, } from "./ui";
|
|
2
6
|
// TODO - enable configuration by keyboard typing in UI
|
|
3
7
|
const DEFAULT_SHORTCUTS = {
|
|
4
8
|
enabled: false,
|
|
@@ -16,17 +20,167 @@ export const ReportIssueContext = {
|
|
|
16
20
|
backendApi: null,
|
|
17
21
|
triageBaseUrl: "https://app.sailfishqa.com",
|
|
18
22
|
deactivateIsolation: () => { },
|
|
23
|
+
integrationData: null,
|
|
19
24
|
};
|
|
20
25
|
let modalEl = null;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
// Function to set up custom multiselect listeners
|
|
27
|
+
function setupCustomMultiSelectListeners(fieldId, onChange) {
|
|
28
|
+
const container = document.getElementById(`${fieldId}-container`);
|
|
29
|
+
const dropdown = document.getElementById(`${fieldId}-dropdown`);
|
|
30
|
+
if (!container || !dropdown)
|
|
31
|
+
return;
|
|
32
|
+
// Handle option clicks
|
|
33
|
+
const options = dropdown.querySelectorAll(".sf-multiselect-option");
|
|
34
|
+
options.forEach((option) => {
|
|
35
|
+
option.addEventListener("click", (e) => {
|
|
36
|
+
e.stopPropagation();
|
|
37
|
+
const optionEl = option;
|
|
38
|
+
// Toggle selection
|
|
39
|
+
const isSelected = optionEl.dataset.selected === "true";
|
|
40
|
+
optionEl.dataset.selected = String(!isSelected);
|
|
41
|
+
// Update background color
|
|
42
|
+
if (!isSelected) {
|
|
43
|
+
optionEl.style.backgroundColor = "#e0f2fe";
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
optionEl.style.backgroundColor = "";
|
|
47
|
+
}
|
|
48
|
+
// Get all selected values and labels
|
|
49
|
+
const selectedValues = [];
|
|
50
|
+
const selectedLabels = [];
|
|
51
|
+
dropdown.querySelectorAll(".sf-multiselect-option").forEach((opt) => {
|
|
52
|
+
const optEl = opt;
|
|
53
|
+
if (optEl.dataset.selected === "true") {
|
|
54
|
+
selectedValues.push(optEl.dataset.value || "");
|
|
55
|
+
selectedLabels.push(optEl.textContent || "");
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
// Update input display
|
|
59
|
+
const input = container.querySelector(".sf-multiselect-input span");
|
|
60
|
+
const displayText = selectedLabels.join(", ");
|
|
61
|
+
if (input) {
|
|
62
|
+
input.textContent = displayText || "Select...";
|
|
63
|
+
input.style.color = displayText ? "#000" : "#9ca3af";
|
|
64
|
+
}
|
|
65
|
+
// Call onChange callback
|
|
66
|
+
onChange(selectedValues);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
// Close dropdown on outside click
|
|
70
|
+
document.addEventListener("click", (e) => {
|
|
71
|
+
const target = e.target;
|
|
72
|
+
if (!container.contains(target)) {
|
|
73
|
+
dropdown.style.display = "none";
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
// Function to render all dynamic fields for a project and issue type
|
|
78
|
+
function renderDynamicFields(projectId, issueTypeId) {
|
|
79
|
+
const dynamicFieldsContainer = document.getElementById("sf-dynamic-fields-container");
|
|
80
|
+
if (!dynamicFieldsContainer)
|
|
81
|
+
return;
|
|
82
|
+
if (!projectId) {
|
|
83
|
+
dynamicFieldsContainer.innerHTML =
|
|
84
|
+
'<div style="font-size:14px; color:#64748B;">Select a project to see additional fields</div>';
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const fields = getFieldsForProject(projectId, issueTypeId);
|
|
88
|
+
const users = getUsers();
|
|
89
|
+
if (!fields || fields.length === 0) {
|
|
90
|
+
dynamicFieldsContainer.innerHTML = "";
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const renderedFields = fields
|
|
94
|
+
.map((field) => renderDynamicField(field, currentState.engTicketCustomFields[field.fieldId || field.key], users))
|
|
95
|
+
.filter(Boolean)
|
|
96
|
+
.join("");
|
|
97
|
+
dynamicFieldsContainer.innerHTML = renderedFields || "";
|
|
98
|
+
// Set up listeners for custom multiselects in dynamic fields
|
|
99
|
+
fields.forEach((field) => {
|
|
100
|
+
const fieldId = field.fieldId || field.key;
|
|
101
|
+
const fieldType = field.schema?.type;
|
|
102
|
+
const allowedValues = field.allowedValues;
|
|
103
|
+
if (fieldType === "array" && allowedValues && allowedValues.length > 0) {
|
|
104
|
+
setupCustomMultiSelectListeners(fieldId, (selectedValues) => {
|
|
105
|
+
currentState.engTicketCustomFields[fieldId] = selectedValues;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
// Function to generate engineering ticket fields HTML based on integration data
|
|
111
|
+
function generateEngTicketFieldsHTML() {
|
|
112
|
+
const integrationData = ReportIssueContext.integrationData;
|
|
113
|
+
if (!integrationData)
|
|
114
|
+
return "";
|
|
115
|
+
const isJira = integrationData.provider?.toLowerCase() === "jira";
|
|
116
|
+
const hasTeams = integrationData.teams &&
|
|
117
|
+
Array.isArray(integrationData.teams) &&
|
|
118
|
+
integrationData.teams.length > 0;
|
|
119
|
+
let fieldsHTML = "<div style='display:flex; flex-direction:column; gap:12px;'>";
|
|
120
|
+
// Team field (only for Linear - when teams exist)
|
|
121
|
+
if (hasTeams) {
|
|
122
|
+
fieldsHTML += `
|
|
123
|
+
<div>
|
|
124
|
+
<label for="sf-eng-ticket-team" style="display:block; font-size:14px; font-weight:500; margin-bottom:6px;">
|
|
125
|
+
Team
|
|
126
|
+
</label>
|
|
127
|
+
<select id="sf-eng-ticket-team"
|
|
128
|
+
style="width:100%; padding:8px 12px; font-size:14px; border:1px solid #cbd5e1; border-radius:6px; outline:none; appearance:none; cursor:pointer; background-color: white; color: #9ca3af;">
|
|
129
|
+
<option value="" disabled selected style="color: #9ca3af;">Select team...</option>
|
|
130
|
+
</select>
|
|
131
|
+
</div>
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
// Project field (always shown)
|
|
135
|
+
fieldsHTML += `
|
|
136
|
+
<div>
|
|
137
|
+
<label for="sf-eng-ticket-project" style="display:block; font-size:14px; font-weight:500; margin-bottom:6px;">
|
|
138
|
+
Project
|
|
139
|
+
</label>
|
|
140
|
+
<select id="sf-eng-ticket-project"
|
|
141
|
+
style="width:100%; padding:8px 12px; font-size:14px; border:1px solid #cbd5e1; border-radius:6px; outline:none; appearance:none; cursor:pointer; background-color: white; color: #9ca3af;">
|
|
142
|
+
<option value="" disabled selected style="color: #9ca3af;">Select project...</option>
|
|
143
|
+
</select>
|
|
144
|
+
</div>
|
|
145
|
+
`;
|
|
146
|
+
// Issue Type field (only for Jira)
|
|
147
|
+
if (isJira) {
|
|
148
|
+
fieldsHTML += `
|
|
149
|
+
<div>
|
|
150
|
+
<label for="sf-eng-ticket-type" style="display:block; font-size:14px; font-weight:500; margin-bottom:6px;">
|
|
151
|
+
Issue Type
|
|
152
|
+
</label>
|
|
153
|
+
<select id="sf-eng-ticket-type"
|
|
154
|
+
style="width:100%; padding:8px 12px; font-size:14px; border:1px solid #cbd5e1; border-radius:6px; outline:none; appearance:none; cursor:pointer; background-color: white; color: #9ca3af;">
|
|
155
|
+
<option value="" disabled selected style="color: #9ca3af;">Select project first...</option>
|
|
156
|
+
</select>
|
|
157
|
+
</div>
|
|
158
|
+
`;
|
|
159
|
+
}
|
|
160
|
+
// Priority field (always shown)
|
|
161
|
+
fieldsHTML += `
|
|
162
|
+
<div>
|
|
163
|
+
<label for="sf-eng-ticket-priority" style="display:block; font-size:14px; font-weight:500; margin-bottom:6px;">
|
|
164
|
+
Priority
|
|
165
|
+
</label>
|
|
166
|
+
<select id="sf-eng-ticket-priority"
|
|
167
|
+
style="width:100%; padding:8px 12px; font-size:14px; border:1px solid #cbd5e1; border-radius:6px; outline:none; appearance:none; cursor:pointer; background-color: white;">
|
|
168
|
+
</select>
|
|
169
|
+
</div>
|
|
170
|
+
`;
|
|
171
|
+
// Labels field (only if integration has labels)
|
|
172
|
+
if (integrationData.labels &&
|
|
173
|
+
Array.isArray(integrationData.labels) &&
|
|
174
|
+
integrationData.labels.length > 0) {
|
|
175
|
+
fieldsHTML += renderCustomMultiSelect("sf-eng-ticket-labels", "Labels", integrationData.labels, currentState.engTicketLabels, false);
|
|
176
|
+
}
|
|
177
|
+
// Dynamic Fields Container
|
|
178
|
+
fieldsHTML += `
|
|
179
|
+
<div id="sf-dynamic-fields-container" style="display: flex; flex-direction: column; gap: 12px;"></div>
|
|
180
|
+
`;
|
|
181
|
+
fieldsHTML += "</div>";
|
|
182
|
+
return fieldsHTML;
|
|
183
|
+
}
|
|
30
184
|
function isMacPlatform() {
|
|
31
185
|
// Newer API (Chrome, Edge, Opera)
|
|
32
186
|
const uaData = navigator.userAgentData;
|
|
@@ -95,6 +249,7 @@ export function setupIssueReporting(options) {
|
|
|
95
249
|
ReportIssueContext.apiKey = options.apiKey;
|
|
96
250
|
ReportIssueContext.backendApi = options.backendApi;
|
|
97
251
|
ReportIssueContext.resolveSessionId = options.getSessionId;
|
|
252
|
+
ReportIssueContext.integrationData = options.integrationData || null;
|
|
98
253
|
if (options.customBaseUrl)
|
|
99
254
|
ReportIssueContext.triageBaseUrl = options.customBaseUrl;
|
|
100
255
|
ReportIssueContext.shortcuts = mergeShortcutsConfig(options.shortcuts);
|
|
@@ -188,16 +343,12 @@ function closeModal() {
|
|
|
188
343
|
}
|
|
189
344
|
modalEl = null;
|
|
190
345
|
if (!isRecording) {
|
|
191
|
-
|
|
192
|
-
mode: "lookback",
|
|
193
|
-
description: "",
|
|
194
|
-
occurredInThisTab: true,
|
|
195
|
-
};
|
|
196
|
-
recordingStartTime = null;
|
|
197
|
-
recordingEndTime = null;
|
|
346
|
+
resetState();
|
|
198
347
|
}
|
|
199
|
-
if (timerInterval)
|
|
348
|
+
if (timerInterval) {
|
|
200
349
|
clearInterval(timerInterval);
|
|
350
|
+
setTimerInterval(null);
|
|
351
|
+
}
|
|
201
352
|
}
|
|
202
353
|
function activateModalIsolation(modal) {
|
|
203
354
|
// A. Ensure modal is a proper dialog & focus anchor
|
|
@@ -378,31 +529,38 @@ function injectModalHTML(initialMode = "lookback") {
|
|
|
378
529
|
modalEl.innerHTML = `
|
|
379
530
|
<div style="position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:9998;"></div>
|
|
380
531
|
<div style="position:fixed; top:50%; left:50%; transform:translate(-50%, -50%);
|
|
381
|
-
background:#fff;
|
|
382
|
-
width:476px; max-width:90%; z-index:9999;
|
|
383
|
-
box-shadow:0 4px 20px rgba(0,0,0,0.15); font-family:sans-serif;
|
|
532
|
+
background:#fff; border-radius:12px;
|
|
533
|
+
width:476px; max-width:90%; max-height:90vh; z-index:9999;
|
|
534
|
+
box-shadow:0 4px 20px rgba(0,0,0,0.15); font-family:sans-serif;
|
|
535
|
+
display:flex; flex-direction:column;">
|
|
384
536
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
<
|
|
388
|
-
|
|
389
|
-
<
|
|
390
|
-
|
|
391
|
-
|
|
537
|
+
<!-- Fixed Header -->
|
|
538
|
+
<div style="padding:24px 24px 16px 24px; flex-shrink:0; position:relative;">
|
|
539
|
+
<button id="sf-modal-close-btn"
|
|
540
|
+
style="position:absolute; top:24px; right:24px; background:none; border:none; cursor:pointer; padding:0;">
|
|
541
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
542
|
+
<path d="M18 6L6 18" stroke="#71717A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
543
|
+
<path d="M6 6L18 18" stroke="#71717A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
544
|
+
</svg>
|
|
545
|
+
</button>
|
|
392
546
|
|
|
393
|
-
|
|
547
|
+
<h2 style="font-size:18px; font-weight:600; margin-bottom:16px;">Report Issue</h2>
|
|
394
548
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
549
|
+
<div id="sf-issue-tabs" style="display:flex; gap:4px; background:#f1f5f9; padding:6px; border-radius:6px; width: fit-content;">
|
|
550
|
+
<button id="sf-tab-lookback" data-mode="lookback" class="sf-issue-tab ${!isStartNow ? "active" : ""}"
|
|
551
|
+
style="padding:6px 12px; border:none; background:${!isStartNow ? "white" : "transparent"}; color: ${!isStartNow ? "#0F172A" : "#64748B"}; border-radius:4px; font-size:14px; cursor:pointer; font-weight:500; height:32px; display: flex; align-items: center; gap: 8px;">
|
|
552
|
+
Existing ${getShortcutLabelFromContext("openModalExistingMode")}
|
|
553
|
+
</button>
|
|
554
|
+
<button id="sf-tab-startnow" data-mode="startnow" class="sf-issue-tab ${isStartNow ? "active" : ""}"
|
|
555
|
+
style="padding:6px 12px; border:none; background:${isStartNow ? "white" : "transparent"}; color: ${isStartNow ? "#0F172A" : "#64748B"}; border-radius:4px; font-size:14px; cursor:pointer; font-weight:500; height:32px; display: flex; align-items: center; gap: 8px;">
|
|
556
|
+
Capture new ${getShortcutLabelFromContext("openModalCaptureNewMode")}
|
|
557
|
+
</button>
|
|
558
|
+
</div>
|
|
404
559
|
</div>
|
|
405
560
|
|
|
561
|
+
<!-- Scrollable Content -->
|
|
562
|
+
<div style="flex:1; overflow-y:auto; padding:0 24px;">
|
|
563
|
+
|
|
406
564
|
<div id="sf-issue-mode-info" style="display:flex; align-items:flex-start; gap:8px; margin-bottom:24px;">
|
|
407
565
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-top:4px;">
|
|
408
566
|
<g clip-path="url(#clip0_2477_11797)">
|
|
@@ -427,6 +585,7 @@ function injectModalHTML(initialMode = "lookback") {
|
|
|
427
585
|
border:1px solid #cbd5e1; border-radius:6px; margin-bottom:20px;
|
|
428
586
|
resize:none; outline:none;">${currentState.description}</textarea>
|
|
429
587
|
|
|
588
|
+
<!-- When did this happen Section -->
|
|
430
589
|
<div id="sf-lookback-container" style="display:${isStartNow ? "none" : "block"}; margin-bottom:20px;">
|
|
431
590
|
<label for="sf-lookback-minutes" style="display:block; font-size:14px; font-weight:500; margin-bottom:6px;">
|
|
432
591
|
When did this happen?
|
|
@@ -483,35 +642,82 @@ function injectModalHTML(initialMode = "lookback") {
|
|
|
483
642
|
</div>
|
|
484
643
|
</div>
|
|
485
644
|
|
|
486
|
-
|
|
645
|
+
<!-- Divider -->
|
|
646
|
+
<div style="height: 1px; background: #e2e8f0; margin: 20px 0;"></div>
|
|
487
647
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
<
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
648
|
+
<!-- Create an Issue & Engineering Ticket Section -->
|
|
649
|
+
<div style="margin-bottom:20px;">
|
|
650
|
+
<!-- Checkboxes on same line -->
|
|
651
|
+
<div style="display:flex; align-items:center; gap:24px; margin-bottom:16px;">
|
|
652
|
+
<label style="display:flex; align-items:center; gap:8px; font-size:14px; font-weight:500; cursor:pointer;">
|
|
653
|
+
<input type="checkbox" id="sf-create-issue-checkbox" ${currentState.createIssue ? "checked" : ""}
|
|
654
|
+
style="width:16px; height:16px; accent-color:#295DBF; cursor:pointer;">
|
|
655
|
+
Create an Issue
|
|
656
|
+
</label>
|
|
657
|
+
|
|
658
|
+
<label id="sf-create-eng-ticket-label" style="display:${ReportIssueContext.integrationData ? "flex" : "none"}; align-items:center; gap:8px; font-size:14px; font-weight:500; cursor:pointer;">
|
|
659
|
+
<input type="checkbox" id="sf-create-eng-ticket-checkbox" ${currentState.createEngTicket ? "checked" : ""}
|
|
660
|
+
style="width:16px; height:16px; accent-color:#295DBF; cursor:pointer;">
|
|
661
|
+
Create an Eng Ticket
|
|
662
|
+
</label>
|
|
663
|
+
</div>
|
|
664
|
+
|
|
665
|
+
<!-- Issue Title Field (always shown when create issue is checked) -->
|
|
666
|
+
<div id="sf-issue-fields-container" style="display:${currentState.createIssue ? "block" : "none"};">
|
|
667
|
+
<div style="display:flex; flex-direction:column; gap:12px;">
|
|
668
|
+
<div>
|
|
669
|
+
<label for="sf-issue-name" style="display:block; font-size:14px; font-weight:500; margin-bottom:6px;">
|
|
670
|
+
Title <span style="color:#ef4444;">*</span>
|
|
671
|
+
</label>
|
|
672
|
+
<input type="text" id="sf-issue-name" placeholder="Enter title"
|
|
673
|
+
value="${currentState.issueName}"
|
|
674
|
+
style="width:100%; padding:8px 12px; font-size:14px; border:1px solid #cbd5e1; border-radius:6px; outline:none;">
|
|
498
675
|
</div>
|
|
499
|
-
|
|
500
|
-
${getShortcutLabelFromContext("startRecording")}
|
|
501
|
-
</button>
|
|
676
|
+
</div>
|
|
502
677
|
</div>
|
|
503
678
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
Report <span style="margin-left: 8px; display: inline-flex; align-items: center; gap: 4px; color: #94A3B8; font-size: 12px; line-height:16px;">
|
|
509
|
-
${getShortcutLabelFromContext("submitReport")}
|
|
510
|
-
</span>
|
|
511
|
-
</button>
|
|
679
|
+
<!-- Engineering Ticket Fields (shown when create eng ticket is checked) -->
|
|
680
|
+
<div id="sf-eng-ticket-fields-container" style="display:${currentState.createEngTicket ? "block" : "none"}; margin-top: ${currentState.createIssue ? "12px" : "0"};">
|
|
681
|
+
${generateEngTicketFieldsHTML()}
|
|
682
|
+
</div>
|
|
512
683
|
</div>
|
|
513
|
-
|
|
514
|
-
|
|
684
|
+
|
|
685
|
+
<!-- Divider -->
|
|
686
|
+
<div style="height: 1px; background: #e2e8f0; margin-top: 20px;"></div>
|
|
687
|
+
</div>
|
|
688
|
+
|
|
689
|
+
<!-- Fixed Footer -->
|
|
690
|
+
<div style="padding:20px 24px; flex-shrink:0;">
|
|
691
|
+
<div id="sf-modal-footer" style="display:flex; justify-content:${isStartNow ? "space-between" : "flex-end"}; align-items:flex-end;">
|
|
692
|
+
|
|
693
|
+
<div id="sf-record-button-container" style="display:${isStartNow ? "block" : "none"};">
|
|
694
|
+
<div id="sf-recording-timer-label" style="display:none; font-size:14px; margin-bottom:20px;">
|
|
695
|
+
Recording: <span id="sf-recording-timer-display">00:00</span>
|
|
696
|
+
</div>
|
|
697
|
+
<button id="sf-start-recording-btn"
|
|
698
|
+
style="display:flex; align-items:center; gap:8px; border:1px solid #fc5555;
|
|
699
|
+
background:transparent; padding:6px 12px; font-size:14px;
|
|
700
|
+
color: #fc5555; border-radius:6px; cursor:pointer; font-weight:500;">
|
|
701
|
+
<div id="sf-record-icon" style="padding: 6px; border-radius: 6px; border: 1px solid #fc5555; cursor: pointer;">
|
|
702
|
+
<div style="width: 14px; height: 14px; background: #fc5555; border-radius: 50%; border: 1px solid #991b1b;"></div>
|
|
703
|
+
</div>
|
|
704
|
+
<span>Start Recording</span>
|
|
705
|
+
${getShortcutLabelFromContext("startRecording")}
|
|
706
|
+
</button>
|
|
707
|
+
</div>
|
|
708
|
+
|
|
709
|
+
<button id="sf-issue-submit-btn"
|
|
710
|
+
style="background: #295DBF; color:white; border:none; padding:8px 16px;
|
|
711
|
+
border-radius:6px; font-size:14px; line-height: 24px; font-weight:500;
|
|
712
|
+
cursor:${isStartNow ? "not-allowed" : "pointer"}; opacity:${isStartNow ? "0.4" : "1"}; margin-bottom:1px" ${isStartNow ? "disabled" : ""}>
|
|
713
|
+
Report <span style="margin-left: 8px; display: inline-flex; align-items: center; gap: 4px; color: #94A3B8; font-size: 12px; line-height:16px;">
|
|
714
|
+
${getShortcutLabelFromContext("submitReport")}
|
|
715
|
+
</span>
|
|
716
|
+
</button>
|
|
717
|
+
</div>
|
|
718
|
+
<div style="display:flex; justify-content:center; font-size: 12px; margin-top: 12px; color: #295dbf;">
|
|
719
|
+
<a href="mailto:info@sailfishqa.com?subject=I'd%20love%20to%20learn%20more&body=Hey%2C%20Sailfish%20AI%20team%20-%20I'd%20love%20to%20learn%20more%20about%20Sailfish%20AI!">Powered by Sailfish AI</a>
|
|
720
|
+
</div>
|
|
515
721
|
</div>
|
|
516
722
|
</div>
|
|
517
723
|
`;
|
|
@@ -519,6 +725,47 @@ function injectModalHTML(initialMode = "lookback") {
|
|
|
519
725
|
document.body.appendChild(modalEl);
|
|
520
726
|
bindListeners();
|
|
521
727
|
ReportIssueContext.deactivateIsolation = activateModalIsolation(modalEl);
|
|
728
|
+
// If engineering ticket is already enabled on load, initialize the form
|
|
729
|
+
if (ReportIssueContext.integrationData && currentState.createEngTicket) {
|
|
730
|
+
initializeEngTicketForm();
|
|
731
|
+
}
|
|
732
|
+
else if (!ReportIssueContext.integrationData) {
|
|
733
|
+
// No integration, disable eng ticket
|
|
734
|
+
currentState.createEngTicket = false;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
function initializeEngTicketForm() {
|
|
738
|
+
const integrationData = ReportIssueContext.integrationData;
|
|
739
|
+
if (!integrationData) {
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
// Set default values from integration data
|
|
743
|
+
if (!currentState.engTicketTeam && integrationData.defaultTeam) {
|
|
744
|
+
currentState.engTicketTeam = integrationData.defaultTeam;
|
|
745
|
+
}
|
|
746
|
+
if (!currentState.engTicketProject && integrationData.defaultProject) {
|
|
747
|
+
currentState.engTicketProject = integrationData.defaultProject;
|
|
748
|
+
}
|
|
749
|
+
if (!currentState.engTicketPriority && integrationData.defaultPriority) {
|
|
750
|
+
currentState.engTicketPriority = integrationData.defaultPriority;
|
|
751
|
+
}
|
|
752
|
+
// Update form with integration data
|
|
753
|
+
updateFormWithIntegrationData(currentState);
|
|
754
|
+
// Handle reporter field for Jira
|
|
755
|
+
if (integrationData.provider?.toLowerCase() === "jira" &&
|
|
756
|
+
integrationData.jiraReporterAccountId &&
|
|
757
|
+
currentState.engTicketProject) {
|
|
758
|
+
const fields = getFieldsForProject(currentState.engTicketProject, currentState.engTicketIssueType);
|
|
759
|
+
const reporterField = fields.find((f) => f.fieldId === "reporter");
|
|
760
|
+
if (reporterField && !currentState.engTicketCustomFields["reporter"]) {
|
|
761
|
+
currentState.engTicketCustomFields["reporter"] =
|
|
762
|
+
integrationData.jiraReporterAccountId;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
// If a project is already selected, render dynamic fields
|
|
766
|
+
if (currentState.engTicketProject) {
|
|
767
|
+
renderDynamicFields(currentState.engTicketProject, currentState.engTicketIssueType);
|
|
768
|
+
}
|
|
522
769
|
}
|
|
523
770
|
function setActiveTab(mode) {
|
|
524
771
|
currentState.mode = mode;
|
|
@@ -617,6 +864,255 @@ function bindListeners() {
|
|
|
617
864
|
}
|
|
618
865
|
});
|
|
619
866
|
}
|
|
867
|
+
// Collapsible sections toggle
|
|
868
|
+
const collapsibleHeaders = modalEl?.querySelectorAll(".sf-collapsible-header");
|
|
869
|
+
collapsibleHeaders?.forEach((header) => {
|
|
870
|
+
header.addEventListener("click", (e) => {
|
|
871
|
+
const button = e.currentTarget;
|
|
872
|
+
const targetId = button.dataset.target;
|
|
873
|
+
const content = document.getElementById(targetId);
|
|
874
|
+
const chevron = button.querySelector(".sf-chevron");
|
|
875
|
+
if (content && chevron) {
|
|
876
|
+
const isExpanded = content.style.display !== "none";
|
|
877
|
+
content.style.display = isExpanded ? "none" : "block";
|
|
878
|
+
chevron.innerHTML = getChevronSVG(!isExpanded);
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
});
|
|
882
|
+
// Create Issue checkbox
|
|
883
|
+
const createIssueCheckbox = document.getElementById("sf-create-issue-checkbox");
|
|
884
|
+
const issueFieldsContainer = document.getElementById("sf-issue-fields-container");
|
|
885
|
+
const createEngTicketCheckbox = document.getElementById("sf-create-eng-ticket-checkbox");
|
|
886
|
+
const engTicketFieldsContainer = document.getElementById("sf-eng-ticket-fields-container");
|
|
887
|
+
if (createIssueCheckbox) {
|
|
888
|
+
createIssueCheckbox.addEventListener("change", () => {
|
|
889
|
+
const isChecked = createIssueCheckbox.checked;
|
|
890
|
+
currentState.createIssue = isChecked;
|
|
891
|
+
localStorage.setItem(STORAGE_KEYS.CREATE_ISSUE, String(isChecked));
|
|
892
|
+
if (issueFieldsContainer) {
|
|
893
|
+
issueFieldsContainer.style.display = isChecked ? "block" : "none";
|
|
894
|
+
}
|
|
895
|
+
// If unchecking create issue, also uncheck eng ticket
|
|
896
|
+
if (!isChecked && createEngTicketCheckbox) {
|
|
897
|
+
createEngTicketCheckbox.checked = false;
|
|
898
|
+
currentState.createEngTicket = false;
|
|
899
|
+
localStorage.setItem(STORAGE_KEYS.CREATE_ENG_TICKET, "false");
|
|
900
|
+
if (engTicketFieldsContainer) {
|
|
901
|
+
engTicketFieldsContainer.style.display = "none";
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
// Create Engineering Ticket checkbox
|
|
907
|
+
if (createEngTicketCheckbox) {
|
|
908
|
+
createEngTicketCheckbox.addEventListener("change", async () => {
|
|
909
|
+
const isChecked = createEngTicketCheckbox.checked;
|
|
910
|
+
currentState.createEngTicket = isChecked;
|
|
911
|
+
localStorage.setItem(STORAGE_KEYS.CREATE_ENG_TICKET, String(isChecked));
|
|
912
|
+
// If checking eng ticket, also check create issue
|
|
913
|
+
if (isChecked && !currentState.createIssue) {
|
|
914
|
+
currentState.createIssue = true;
|
|
915
|
+
localStorage.setItem(STORAGE_KEYS.CREATE_ISSUE, "true");
|
|
916
|
+
if (createIssueCheckbox) {
|
|
917
|
+
createIssueCheckbox.checked = true;
|
|
918
|
+
}
|
|
919
|
+
if (issueFieldsContainer) {
|
|
920
|
+
issueFieldsContainer.style.display = "block";
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
if (engTicketFieldsContainer) {
|
|
924
|
+
engTicketFieldsContainer.style.display = isChecked ? "block" : "none";
|
|
925
|
+
}
|
|
926
|
+
// Validate integration data when checkbox is checked
|
|
927
|
+
// (Data should already be pre-loaded in initRecorder)
|
|
928
|
+
if (isChecked) {
|
|
929
|
+
// Check if we have a valid integration
|
|
930
|
+
if (!hasValidIntegration()) {
|
|
931
|
+
// Uncheck the checkbox if no valid integration
|
|
932
|
+
createEngTicketCheckbox.checked = false;
|
|
933
|
+
currentState.createEngTicket = false;
|
|
934
|
+
localStorage.setItem(STORAGE_KEYS.CREATE_ENG_TICKET, "false");
|
|
935
|
+
// Hide fields
|
|
936
|
+
if (engTicketFieldsContainer) {
|
|
937
|
+
engTicketFieldsContainer.style.display = "none";
|
|
938
|
+
}
|
|
939
|
+
alert("No engineering ticket integration found. Please install and configure an integration (Jira, Linear, or Zendesk) first.");
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
// Update form fields with integration data if available
|
|
943
|
+
const integrationData = getIntegrationData();
|
|
944
|
+
if (integrationData) {
|
|
945
|
+
// Set default values
|
|
946
|
+
if (!currentState.engTicketTeam && integrationData.defaultTeam) {
|
|
947
|
+
currentState.engTicketTeam = integrationData.defaultTeam;
|
|
948
|
+
}
|
|
949
|
+
if (!currentState.engTicketProject &&
|
|
950
|
+
integrationData.defaultProject) {
|
|
951
|
+
currentState.engTicketProject = integrationData.defaultProject;
|
|
952
|
+
}
|
|
953
|
+
if (!currentState.engTicketPriority &&
|
|
954
|
+
integrationData.defaultPriority) {
|
|
955
|
+
currentState.engTicketPriority = integrationData.defaultPriority;
|
|
956
|
+
}
|
|
957
|
+
updateFormWithIntegrationData(currentState);
|
|
958
|
+
// Handle reporter field for Jira
|
|
959
|
+
if (integrationData.provider?.toLowerCase() === "jira" &&
|
|
960
|
+
integrationData.jiraReporterAccountId &&
|
|
961
|
+
currentState.engTicketProject) {
|
|
962
|
+
const fields = getFieldsForProject(currentState.engTicketProject, currentState.engTicketIssueType);
|
|
963
|
+
const reporterField = fields.find((f) => f.fieldId === "reporter");
|
|
964
|
+
if (reporterField &&
|
|
965
|
+
!currentState.engTicketCustomFields["reporter"]) {
|
|
966
|
+
currentState.engTicketCustomFields["reporter"] =
|
|
967
|
+
integrationData.jiraReporterAccountId;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
// Render dynamic fields if a project is already selected
|
|
971
|
+
const projectSelect = document.getElementById("sf-eng-ticket-project");
|
|
972
|
+
const issueTypeSelect = document.getElementById("sf-eng-ticket-type");
|
|
973
|
+
if (projectSelect && projectSelect.value) {
|
|
974
|
+
renderDynamicFields(projectSelect.value, issueTypeSelect?.value);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
// Issue form fields
|
|
981
|
+
const issueNameInput = document.getElementById("sf-issue-name");
|
|
982
|
+
if (issueNameInput) {
|
|
983
|
+
issueNameInput.addEventListener("input", () => {
|
|
984
|
+
currentState.issueName = issueNameInput.value;
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
// Engineering ticket form fields
|
|
988
|
+
const engTeamSelect = document.getElementById("sf-eng-ticket-team");
|
|
989
|
+
const engProjectSelect = document.getElementById("sf-eng-ticket-project");
|
|
990
|
+
const engPrioritySelect = document.getElementById("sf-eng-ticket-priority");
|
|
991
|
+
const engLabelsContainer = document.getElementById("sf-eng-ticket-labels-container");
|
|
992
|
+
const engTypeSelect = document.getElementById("sf-eng-ticket-type");
|
|
993
|
+
if (engTeamSelect) {
|
|
994
|
+
engTeamSelect.addEventListener("change", () => {
|
|
995
|
+
currentState.engTicketTeam = engTeamSelect.value;
|
|
996
|
+
// Update text color based on selection
|
|
997
|
+
engTeamSelect.style.color = engTeamSelect.value ? "" : "#9ca3af";
|
|
998
|
+
// Update projects dropdown when team changes (for Linear)
|
|
999
|
+
if (engProjectSelect) {
|
|
1000
|
+
const projects = getProjectsForTeam(engTeamSelect.value);
|
|
1001
|
+
const projectSelect = document.getElementById("sf-eng-ticket-project");
|
|
1002
|
+
if (projectSelect) {
|
|
1003
|
+
// Clear current selection
|
|
1004
|
+
currentState.engTicketProject = "";
|
|
1005
|
+
currentState.engTicketCustomFields = {};
|
|
1006
|
+
// Populate new projects
|
|
1007
|
+
projectSelect.innerHTML =
|
|
1008
|
+
'<option value="">Select project...</option>';
|
|
1009
|
+
projects.forEach((project) => {
|
|
1010
|
+
const optionElement = document.createElement("option");
|
|
1011
|
+
optionElement.value = project.id || project.value || project;
|
|
1012
|
+
optionElement.textContent =
|
|
1013
|
+
project.name || project.label || project;
|
|
1014
|
+
projectSelect.appendChild(optionElement);
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
if (engProjectSelect) {
|
|
1021
|
+
engProjectSelect.addEventListener("change", () => {
|
|
1022
|
+
currentState.engTicketProject = engProjectSelect.value;
|
|
1023
|
+
// Update text color based on selection
|
|
1024
|
+
engProjectSelect.style.color = engProjectSelect.value ? "" : "#9ca3af";
|
|
1025
|
+
// Clear custom fields when project changes
|
|
1026
|
+
currentState.engTicketCustomFields = {};
|
|
1027
|
+
// Update issue types when project changes
|
|
1028
|
+
const integrationData = getIntegrationData();
|
|
1029
|
+
if (integrationData && engTypeSelect) {
|
|
1030
|
+
updateIssueTypeOptions(engTypeSelect, engProjectSelect.value);
|
|
1031
|
+
// Update state with the selected/default issue type
|
|
1032
|
+
currentState.engTicketIssueType = engTypeSelect.value;
|
|
1033
|
+
}
|
|
1034
|
+
// Handle reporter field for Jira
|
|
1035
|
+
if (integrationData &&
|
|
1036
|
+
integrationData.provider?.toLowerCase() === "jira" &&
|
|
1037
|
+
integrationData.jiraReporterAccountId &&
|
|
1038
|
+
engProjectSelect.value) {
|
|
1039
|
+
const fields = getFieldsForProject(engProjectSelect.value, currentState.engTicketIssueType);
|
|
1040
|
+
const reporterField = fields.find((f) => f.fieldId === "reporter");
|
|
1041
|
+
if (reporterField) {
|
|
1042
|
+
currentState.engTicketCustomFields["reporter"] =
|
|
1043
|
+
integrationData.jiraReporterAccountId;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
// Render dynamic fields for the selected project and issue type
|
|
1047
|
+
renderDynamicFields(engProjectSelect.value, currentState.engTicketIssueType);
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
if (engPrioritySelect) {
|
|
1051
|
+
engPrioritySelect.addEventListener("change", () => {
|
|
1052
|
+
currentState.engTicketPriority = Number(engPrioritySelect.value);
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
if (engLabelsContainer) {
|
|
1056
|
+
// Set up event listeners for custom multiselect
|
|
1057
|
+
setupCustomMultiSelectListeners("sf-eng-ticket-labels", (selectedValues) => {
|
|
1058
|
+
currentState.engTicketLabels = selectedValues;
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
if (engTypeSelect) {
|
|
1062
|
+
engTypeSelect.addEventListener("change", () => {
|
|
1063
|
+
currentState.engTicketIssueType = engTypeSelect.value;
|
|
1064
|
+
// Update text color based on selection
|
|
1065
|
+
engTypeSelect.style.color = engTypeSelect.value ? "" : "#9ca3af";
|
|
1066
|
+
// Re-render dynamic fields when issue type changes
|
|
1067
|
+
const engProjectSelect = document.getElementById("sf-eng-ticket-project");
|
|
1068
|
+
if (engProjectSelect && engProjectSelect.value) {
|
|
1069
|
+
// Clear custom fields except reporter
|
|
1070
|
+
const reporterValue = currentState.engTicketCustomFields["reporter"];
|
|
1071
|
+
currentState.engTicketCustomFields = {};
|
|
1072
|
+
if (reporterValue) {
|
|
1073
|
+
currentState.engTicketCustomFields["reporter"] = reporterValue;
|
|
1074
|
+
}
|
|
1075
|
+
renderDynamicFields(engProjectSelect.value, engTypeSelect.value);
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
// Dynamic fields event delegation
|
|
1080
|
+
const dynamicFieldsContainer = document.getElementById("sf-dynamic-fields-container");
|
|
1081
|
+
if (dynamicFieldsContainer) {
|
|
1082
|
+
dynamicFieldsContainer.addEventListener("input", (e) => {
|
|
1083
|
+
const target = e.target;
|
|
1084
|
+
if (target.classList.contains("sf-dynamic-field")) {
|
|
1085
|
+
const fieldId = target.dataset.fieldId;
|
|
1086
|
+
if (fieldId) {
|
|
1087
|
+
// Handle different input types
|
|
1088
|
+
if (target.type === "checkbox") {
|
|
1089
|
+
currentState.engTicketCustomFields[fieldId] = target.checked;
|
|
1090
|
+
}
|
|
1091
|
+
else if (target.type === "number") {
|
|
1092
|
+
currentState.engTicketCustomFields[fieldId] =
|
|
1093
|
+
parseFloat(target.value) || null;
|
|
1094
|
+
}
|
|
1095
|
+
else {
|
|
1096
|
+
currentState.engTicketCustomFields[fieldId] = target.value;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
1101
|
+
dynamicFieldsContainer.addEventListener("change", (e) => {
|
|
1102
|
+
const target = e.target;
|
|
1103
|
+
if (target.classList.contains("sf-dynamic-field")) {
|
|
1104
|
+
const fieldId = target.dataset.fieldId;
|
|
1105
|
+
if (fieldId) {
|
|
1106
|
+
currentState.engTicketCustomFields[fieldId] = target.value;
|
|
1107
|
+
}
|
|
1108
|
+
// Update text color for select elements (to remove placeholder gray)
|
|
1109
|
+
if (target.tagName === "SELECT") {
|
|
1110
|
+
const selectElement = target;
|
|
1111
|
+
selectElement.style.color = selectElement.value ? "" : "#9ca3af";
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
620
1116
|
// Start recording
|
|
621
1117
|
if (recordBtn) {
|
|
622
1118
|
recordBtn.onclick = () => {
|
|
@@ -635,6 +1131,11 @@ function bindListeners() {
|
|
|
635
1131
|
?.value || "";
|
|
636
1132
|
const mode = currentState.mode;
|
|
637
1133
|
currentState.description = desc;
|
|
1134
|
+
// Validate issue name if creating an issue
|
|
1135
|
+
if (currentState.createIssue && !currentState.issueName.trim()) {
|
|
1136
|
+
alert("Issue title is required when creating an issue.");
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
638
1139
|
let startTimestamp;
|
|
639
1140
|
let endTimestamp;
|
|
640
1141
|
if (mode === "startnow") {
|
|
@@ -647,8 +1148,55 @@ function bindListeners() {
|
|
|
647
1148
|
endTimestamp = Date.now();
|
|
648
1149
|
startTimestamp = endTimestamp - delta;
|
|
649
1150
|
}
|
|
650
|
-
|
|
651
|
-
|
|
1151
|
+
// Read values from DOM inputs BEFORE closing modal (so elements still exist)
|
|
1152
|
+
if (currentState.createIssue) {
|
|
1153
|
+
const issueNameInput = document.getElementById("sf-issue-name");
|
|
1154
|
+
const engTeamSelect = document.getElementById("sf-eng-ticket-team");
|
|
1155
|
+
const engProjectSelect = document.getElementById("sf-eng-ticket-project");
|
|
1156
|
+
const engPrioritySelect = document.getElementById("sf-eng-ticket-priority");
|
|
1157
|
+
const engTypeSelect = document.getElementById("sf-eng-ticket-type");
|
|
1158
|
+
const issueName = issueNameInput?.value || "";
|
|
1159
|
+
// Use the triage description for issue description
|
|
1160
|
+
const issueDescription = desc;
|
|
1161
|
+
const engTicketTeam = engTeamSelect?.value || "";
|
|
1162
|
+
const engTicketProject = engProjectSelect?.value || "";
|
|
1163
|
+
const engTicketPriority = engPrioritySelect
|
|
1164
|
+
? Number(engPrioritySelect.value)
|
|
1165
|
+
: 0;
|
|
1166
|
+
// Labels come from currentState since we update it in the event listener
|
|
1167
|
+
const engTicketLabels = currentState.engTicketLabels;
|
|
1168
|
+
const engTicketIssueType = engTypeSelect?.value || "";
|
|
1169
|
+
// Read dynamic field values from DOM (non-multiselect fields)
|
|
1170
|
+
const engTicketCustomFields = {
|
|
1171
|
+
...currentState.engTicketCustomFields,
|
|
1172
|
+
};
|
|
1173
|
+
const dynamicFieldElements = document.querySelectorAll(".sf-dynamic-field");
|
|
1174
|
+
dynamicFieldElements.forEach((element) => {
|
|
1175
|
+
const fieldElement = element;
|
|
1176
|
+
const fieldId = fieldElement.dataset.fieldId;
|
|
1177
|
+
if (fieldId) {
|
|
1178
|
+
if (fieldElement.type === "checkbox") {
|
|
1179
|
+
engTicketCustomFields[fieldId] = fieldElement.checked;
|
|
1180
|
+
}
|
|
1181
|
+
else if (fieldElement.type === "number") {
|
|
1182
|
+
engTicketCustomFields[fieldId] =
|
|
1183
|
+
parseFloat(fieldElement.value) || null;
|
|
1184
|
+
}
|
|
1185
|
+
else if (!fieldElement.classList.contains("sf-custom-multiselect")) {
|
|
1186
|
+
// Only read from DOM if it's not a custom multiselect (those are already in state)
|
|
1187
|
+
engTicketCustomFields[fieldId] = fieldElement.value;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
closeModal();
|
|
1192
|
+
// Create triage + issue (with optional engineering ticket)
|
|
1193
|
+
createTriageAndIssue(`${startTimestamp}`, `${endTimestamp}`, desc, issueName, issueDescription, currentState.createEngTicket, engTicketTeam, engTicketProject, engTicketPriority, engTicketLabels, engTicketIssueType, engTicketCustomFields);
|
|
1194
|
+
}
|
|
1195
|
+
else {
|
|
1196
|
+
closeModal();
|
|
1197
|
+
// Create triage only
|
|
1198
|
+
createTriage(`${startTimestamp}`, `${endTimestamp}`, desc);
|
|
1199
|
+
}
|
|
652
1200
|
}
|
|
653
1201
|
});
|
|
654
1202
|
}
|
|
@@ -683,11 +1231,11 @@ function startCountdownThenRecord() {
|
|
|
683
1231
|
clearInterval(interval);
|
|
684
1232
|
document.body.removeChild(overlay);
|
|
685
1233
|
// Begin recording
|
|
686
|
-
|
|
687
|
-
|
|
1234
|
+
setRecordingStartTime(Date.now());
|
|
1235
|
+
setIsRecording(true);
|
|
688
1236
|
// Enable function span tracking for this recording session
|
|
689
1237
|
try {
|
|
690
|
-
const { enableFunctionSpanTracking } = await import("
|
|
1238
|
+
const { enableFunctionSpanTracking } = await import("../websocket");
|
|
691
1239
|
enableFunctionSpanTracking();
|
|
692
1240
|
}
|
|
693
1241
|
catch (e) {
|
|
@@ -739,7 +1287,7 @@ function showFloatingTimer() {
|
|
|
739
1287
|
const timerEl = timer.querySelector("#sf-recording-timer");
|
|
740
1288
|
if (!timerEl)
|
|
741
1289
|
return;
|
|
742
|
-
|
|
1290
|
+
const interval = setInterval(() => {
|
|
743
1291
|
const delta = Date.now() - (recordingStartTime ?? Date.now());
|
|
744
1292
|
const mins = Math.floor(delta / 60000)
|
|
745
1293
|
.toString()
|
|
@@ -749,16 +1297,19 @@ function showFloatingTimer() {
|
|
|
749
1297
|
.padStart(2, "0");
|
|
750
1298
|
timerEl.textContent = `${mins}:${secs}`;
|
|
751
1299
|
}, 1000);
|
|
1300
|
+
setTimerInterval(interval);
|
|
752
1301
|
}
|
|
753
1302
|
async function stopRecording() {
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
if (timerInterval)
|
|
1303
|
+
setRecordingEndTime(Date.now());
|
|
1304
|
+
setIsRecording(false);
|
|
1305
|
+
if (timerInterval) {
|
|
757
1306
|
clearInterval(timerInterval);
|
|
1307
|
+
setTimerInterval(null);
|
|
1308
|
+
}
|
|
758
1309
|
document.getElementById("sf-recording-indicator")?.remove();
|
|
759
1310
|
// Disable function span tracking after recording stops
|
|
760
1311
|
try {
|
|
761
|
-
const { disableFunctionSpanTracking } = await import("
|
|
1312
|
+
const { disableFunctionSpanTracking } = await import("../websocket");
|
|
762
1313
|
disableFunctionSpanTracking();
|
|
763
1314
|
}
|
|
764
1315
|
catch (e) {
|
|
@@ -812,27 +1363,53 @@ function reopenModalAfterStop() {
|
|
|
812
1363
|
}
|
|
813
1364
|
async function createTriage(startTimestamp, endTimestamp, description) {
|
|
814
1365
|
try {
|
|
815
|
-
|
|
1366
|
+
showStatusModal(true);
|
|
816
1367
|
const response = await createTriageFromRecorder(ReportIssueContext.apiKey, ReportIssueContext.backendApi, getSessionIdSafely(), startTimestamp, endTimestamp, description);
|
|
817
1368
|
const triageId = response?.data?.createTriageFromRecorder?.id;
|
|
818
1369
|
if (triageId) {
|
|
819
|
-
|
|
1370
|
+
showStatusModal(false, { type: "triage", id: triageId });
|
|
820
1371
|
}
|
|
821
1372
|
else {
|
|
822
1373
|
console.error("No Triage ID returned from backend.");
|
|
823
|
-
|
|
1374
|
+
showStatusModal(false, null);
|
|
824
1375
|
}
|
|
825
1376
|
}
|
|
826
1377
|
catch (error) {
|
|
827
1378
|
console.error("Error creating triage:", error);
|
|
828
|
-
|
|
1379
|
+
showStatusModal(false, null);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
async function createTriageAndIssue(startTimestamp, endTimestamp, description, issueName, issueDescription, createEngTicket, engTicketTeam, engTicketProject, engTicketPriority, engTicketLabels, engTicketIssueType, engTicketCustomFields) {
|
|
1383
|
+
try {
|
|
1384
|
+
showStatusModal(true);
|
|
1385
|
+
const response = await createTriageAndIssueFromRecorder(ReportIssueContext.apiKey, ReportIssueContext.backendApi, getSessionIdSafely(), startTimestamp, endTimestamp, description, issueName, issueDescription, createEngTicket, engTicketTeam, engTicketProject, engTicketPriority, engTicketLabels, engTicketIssueType, engTicketCustomFields);
|
|
1386
|
+
const issueId = response?.data?.createTriageAndIssueFromRecorder?.id;
|
|
1387
|
+
if (issueId) {
|
|
1388
|
+
showStatusModal(false, { type: "issue", id: issueId });
|
|
1389
|
+
}
|
|
1390
|
+
else {
|
|
1391
|
+
console.error("No Issue ID returned from backend.");
|
|
1392
|
+
showStatusModal(false, null);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
catch (error) {
|
|
1396
|
+
console.error("Error creating triage and issue:", error);
|
|
1397
|
+
showStatusModal(false, null);
|
|
829
1398
|
}
|
|
830
1399
|
}
|
|
831
|
-
function
|
|
1400
|
+
function showStatusModal(isLoading, result) {
|
|
1401
|
+
const triageId = result?.type === "triage" ? result.id : undefined;
|
|
1402
|
+
const issueId = result?.type === "issue" ? result.id : undefined;
|
|
1403
|
+
showTriageStatusModal(isLoading, triageId, issueId);
|
|
1404
|
+
}
|
|
1405
|
+
function showTriageStatusModal(isLoading, triageId, issueId) {
|
|
832
1406
|
removeExistingModals();
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1407
|
+
// Prefer issue URL if available, otherwise use triage URL
|
|
1408
|
+
const resultUrl = issueId
|
|
1409
|
+
? `${ReportIssueContext.triageBaseUrl}/issues/${issueId}?from=inAppReportIssue`
|
|
1410
|
+
: triageId
|
|
1411
|
+
? `${ReportIssueContext.triageBaseUrl}/triage/${triageId}?from=inAppReportIssue`
|
|
1412
|
+
: "";
|
|
836
1413
|
const container = document.createElement("div");
|
|
837
1414
|
container.id = "sf-triage-status-modal";
|
|
838
1415
|
Object.assign(container.style, {
|
|
@@ -924,7 +1501,7 @@ function showTriageStatusModal(isLoading, triageId) {
|
|
|
924
1501
|
else {
|
|
925
1502
|
copyBtn.disabled = false;
|
|
926
1503
|
copyBtn.addEventListener("click", () => {
|
|
927
|
-
navigator.clipboard.writeText(
|
|
1504
|
+
navigator.clipboard.writeText(resultUrl).then(() => {
|
|
928
1505
|
const copiedStatus = document.getElementById("sf-copied-status");
|
|
929
1506
|
if (copiedStatus)
|
|
930
1507
|
copiedStatus.style.display = "flex";
|
|
@@ -932,8 +1509,8 @@ function showTriageStatusModal(isLoading, triageId) {
|
|
|
932
1509
|
});
|
|
933
1510
|
viewBtn.disabled = false;
|
|
934
1511
|
viewBtn.addEventListener("click", () => {
|
|
935
|
-
if (triageId) {
|
|
936
|
-
window.open(
|
|
1512
|
+
if (triageId || issueId) {
|
|
1513
|
+
window.open(resultUrl, "_blank");
|
|
937
1514
|
}
|
|
938
1515
|
});
|
|
939
1516
|
// Auto-hide after success
|