@sailfish-ai/recorder 1.8.13 → 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.
@@ -0,0 +1,240 @@
1
+ import { fetchEngineeringTicketPlatformIntegrations } from "../graphql";
2
+ const SUPPORT_TICKET_INTEGRATIONS = ["jira", "linear", "zendesk"];
3
+ let integrationData = null;
4
+ export function getIntegrationData() {
5
+ return integrationData;
6
+ }
7
+ export function hasValidIntegration() {
8
+ return integrationData !== null && integrationData.installed === true;
9
+ }
10
+ export async function fetchIntegrationData(apiKey, backendApi) {
11
+ if (integrationData) {
12
+ return;
13
+ }
14
+ try {
15
+ const response = await fetchEngineeringTicketPlatformIntegrations(apiKey, backendApi);
16
+ // Check for GraphQL errors
17
+ if (response?.errors && response.errors.length > 0) {
18
+ console.error("GraphQL errors fetching integrations:", response.errors);
19
+ integrationData = null;
20
+ return;
21
+ }
22
+ const integrations = response?.data?.getEngineeringTicketPlatformIntegrationsFromApiKey;
23
+ // Filter for installed support ticket integrations only
24
+ const validIntegrations = (integrations || []).filter((item) => SUPPORT_TICKET_INTEGRATIONS.includes(item.provider?.toLowerCase() || "") && item.installed === true);
25
+ if (validIntegrations.length === 0) {
26
+ console.warn("No valid installed integrations found");
27
+ integrationData = null;
28
+ return;
29
+ }
30
+ // Prefer Jira integration, fallback to first available
31
+ integrationData =
32
+ validIntegrations.find((i) => i.provider?.toLowerCase() === "jira") ||
33
+ validIntegrations[0];
34
+ }
35
+ catch (error) {
36
+ console.error("Error fetching integration data:", error);
37
+ integrationData = null;
38
+ }
39
+ }
40
+ export function populateSelectOptions(selectElement, options, defaultValue) {
41
+ const placeholderOption = document.createElement("option");
42
+ placeholderOption.value = "";
43
+ placeholderOption.disabled = true;
44
+ placeholderOption.selected = !defaultValue;
45
+ placeholderOption.textContent = "Select...";
46
+ placeholderOption.style.color = "#9ca3af";
47
+ selectElement.innerHTML = "";
48
+ selectElement.appendChild(placeholderOption);
49
+ options.forEach((option) => {
50
+ const optionElement = document.createElement("option");
51
+ optionElement.value = option.id || option.value || option;
52
+ optionElement.textContent = option.name || option.label || option;
53
+ selectElement.appendChild(optionElement);
54
+ });
55
+ if (defaultValue) {
56
+ selectElement.value = defaultValue;
57
+ selectElement.style.color = "";
58
+ }
59
+ else {
60
+ selectElement.style.color = "#9ca3af";
61
+ }
62
+ }
63
+ export function populatePriorityOptions(selectElement, provider, defaultPriority) {
64
+ const isJira = provider?.toLowerCase() === "jira";
65
+ selectElement.innerHTML = "";
66
+ if (isJira) {
67
+ // Jira priorities: 1-5 (Highest to Lowest)
68
+ const jiraPriorities = [
69
+ { id: "1", name: "Highest" },
70
+ { id: "2", name: "High" },
71
+ { id: "3", name: "Medium" },
72
+ { id: "4", name: "Low" },
73
+ { id: "5", name: "Lowest" },
74
+ ];
75
+ jiraPriorities.forEach((priority) => {
76
+ const optionElement = document.createElement("option");
77
+ optionElement.value = priority.id;
78
+ optionElement.textContent = priority.name;
79
+ selectElement.appendChild(optionElement);
80
+ });
81
+ }
82
+ else {
83
+ // Linear priorities: 0-4 (No Priority, Urgent to Low)
84
+ const linearPriorities = [
85
+ { id: "0", name: "No Priority" },
86
+ { id: "1", name: "Urgent" },
87
+ { id: "2", name: "High" },
88
+ { id: "3", name: "Medium" },
89
+ { id: "4", name: "Low" },
90
+ ];
91
+ linearPriorities.forEach((priority) => {
92
+ const optionElement = document.createElement("option");
93
+ optionElement.value = priority.id;
94
+ optionElement.textContent = priority.name;
95
+ selectElement.appendChild(optionElement);
96
+ });
97
+ }
98
+ if (defaultPriority !== undefined && defaultPriority !== null) {
99
+ selectElement.value = String(defaultPriority);
100
+ }
101
+ else if (!isJira) {
102
+ // Default to 0 (No Priority) for Linear if not specified
103
+ selectElement.value = "0";
104
+ }
105
+ }
106
+ export function updateIssueTypeOptions(selectElement, projectId) {
107
+ if (!integrationData?.projects || !projectId) {
108
+ selectElement.innerHTML = "";
109
+ const placeholderOption = document.createElement("option");
110
+ placeholderOption.value = "";
111
+ placeholderOption.disabled = true;
112
+ placeholderOption.selected = true;
113
+ placeholderOption.textContent = "Select project first...";
114
+ placeholderOption.style.color = "#9ca3af";
115
+ selectElement.appendChild(placeholderOption);
116
+ selectElement.style.color = "#9ca3af";
117
+ return;
118
+ }
119
+ // Find the project in the projects array to get issue_types
120
+ const selectedProject = integrationData.projects.find((p) => p.id === projectId);
121
+ if (!selectedProject || !selectedProject.issue_types) {
122
+ selectElement.innerHTML = "";
123
+ const placeholderOption = document.createElement("option");
124
+ placeholderOption.value = "";
125
+ placeholderOption.disabled = true;
126
+ placeholderOption.selected = true;
127
+ placeholderOption.textContent = "No issue types available";
128
+ placeholderOption.style.color = "#9ca3af";
129
+ selectElement.appendChild(placeholderOption);
130
+ selectElement.style.color = "#9ca3af";
131
+ return;
132
+ }
133
+ const issueTypes = selectedProject.issue_types || [];
134
+ selectElement.innerHTML = "";
135
+ const placeholderOption = document.createElement("option");
136
+ placeholderOption.value = "";
137
+ placeholderOption.disabled = true;
138
+ placeholderOption.textContent = "Select...";
139
+ placeholderOption.style.color = "#9ca3af";
140
+ selectElement.appendChild(placeholderOption);
141
+ issueTypes.forEach((issueType) => {
142
+ const optionElement = document.createElement("option");
143
+ optionElement.value = issueType.id || issueType.value || issueType;
144
+ optionElement.textContent = issueType.name || issueType.label || issueType;
145
+ selectElement.appendChild(optionElement);
146
+ });
147
+ // Set default issue type (prefer "bug", then "task", then first available)
148
+ if (issueTypes.length > 0) {
149
+ const bugIssueType = issueTypes.find((it) => it.name?.toLowerCase() === "bug");
150
+ const taskIssueType = issueTypes.find((it) => it.name?.toLowerCase() === "task");
151
+ const defaultIssueType = bugIssueType?.id || taskIssueType?.id || issueTypes[0]?.id;
152
+ if (defaultIssueType) {
153
+ selectElement.value = defaultIssueType;
154
+ selectElement.style.color = "#000"; // Explicitly set to black
155
+ }
156
+ else {
157
+ placeholderOption.selected = true;
158
+ selectElement.style.color = "#9ca3af";
159
+ }
160
+ }
161
+ }
162
+ export function getFieldsForProject(projectId, issueTypeId) {
163
+ if (!integrationData?.fieldConfigurations || !projectId) {
164
+ return [];
165
+ }
166
+ // fieldConfigurations is organized by project+issue_type combination
167
+ // Match on both project_key and issue_type_id
168
+ const projectConfig = Array.isArray(integrationData.fieldConfigurations)
169
+ ? integrationData.fieldConfigurations.find((config) => config.project_key === projectId &&
170
+ (!issueTypeId || config.issue_type_id === issueTypeId))
171
+ : integrationData.fieldConfigurations[projectId];
172
+ if (!projectConfig || !projectConfig.fields) {
173
+ return [];
174
+ }
175
+ return projectConfig.fields;
176
+ }
177
+ export function getUsers() {
178
+ if (!integrationData?.users) {
179
+ return [];
180
+ }
181
+ return integrationData.users;
182
+ }
183
+ // Get projects based on team selection (for Linear) or directly from integration (for Jira)
184
+ export function getProjectsForTeam(teamId) {
185
+ if (!integrationData)
186
+ return [];
187
+ const hasTeams = integrationData.teams &&
188
+ Array.isArray(integrationData.teams) &&
189
+ integrationData.teams.length > 0;
190
+ // For Linear (with teams): get projects from selected team
191
+ if (hasTeams && teamId) {
192
+ const selectedTeam = integrationData.teams.find((t) => t.id === teamId);
193
+ return selectedTeam?.projects || [];
194
+ }
195
+ // For Jira (no teams): get projects directly from integration
196
+ if (!hasTeams && integrationData.projects) {
197
+ return integrationData.projects;
198
+ }
199
+ return [];
200
+ }
201
+ export function updateFormWithIntegrationData(currentState) {
202
+ if (!integrationData)
203
+ return currentState;
204
+ const hasTeams = integrationData.teams &&
205
+ Array.isArray(integrationData.teams) &&
206
+ integrationData.teams.length > 0;
207
+ // Update team dropdown (only for Linear)
208
+ const teamSelect = document.getElementById("sf-eng-ticket-team");
209
+ if (teamSelect && hasTeams) {
210
+ populateSelectOptions(teamSelect, integrationData.teams, integrationData.defaultTeam);
211
+ currentState.engTicketTeam = teamSelect.value;
212
+ }
213
+ // Update project dropdown
214
+ const projectSelect = document.getElementById("sf-eng-ticket-project");
215
+ if (projectSelect) {
216
+ // Get projects based on whether teams exist
217
+ const projects = hasTeams
218
+ ? getProjectsForTeam(currentState.engTicketTeam)
219
+ : integrationData.projects || [];
220
+ populateSelectOptions(projectSelect, projects, integrationData.defaultProject);
221
+ currentState.engTicketProject = projectSelect.value;
222
+ }
223
+ // Update priority dropdown with proper values
224
+ const prioritySelect = document.getElementById("sf-eng-ticket-priority");
225
+ if (prioritySelect) {
226
+ populatePriorityOptions(prioritySelect, integrationData.provider || "", integrationData.defaultPriority);
227
+ currentState.engTicketPriority = Number(prioritySelect.value);
228
+ }
229
+ // Update issue type dropdown based on selected project (for Jira)
230
+ const issueTypeSelect = document.getElementById("sf-eng-ticket-type");
231
+ const isJira = integrationData.provider?.toLowerCase() === "jira";
232
+ if (issueTypeSelect && isJira && currentState.engTicketProject) {
233
+ updateIssueTypeOptions(issueTypeSelect, currentState.engTicketProject);
234
+ // Always update currentState with the selected issue type value
235
+ if (issueTypeSelect.value) {
236
+ currentState.engTicketIssueType = issueTypeSelect.value;
237
+ }
238
+ }
239
+ return currentState;
240
+ }
@@ -0,0 +1,69 @@
1
+ import { STORAGE_KEYS } from "./types";
2
+ // Helper function to load user preferences from localStorage
3
+ function loadUserPreferences() {
4
+ return {
5
+ createIssue: localStorage.getItem(STORAGE_KEYS.CREATE_ISSUE) === "true",
6
+ createEngTicket: localStorage.getItem(STORAGE_KEYS.CREATE_ENG_TICKET) === "true",
7
+ };
8
+ }
9
+ // Helper function to get initial state
10
+ export function getInitialState() {
11
+ const prefs = loadUserPreferences();
12
+ return {
13
+ mode: "lookback",
14
+ description: "",
15
+ occurredInThisTab: true,
16
+ createIssue: prefs.createIssue,
17
+ issueName: "",
18
+ issueDescription: "",
19
+ createEngTicket: prefs.createEngTicket,
20
+ engTicketTeam: "",
21
+ engTicketProject: "",
22
+ engTicketPriority: 0,
23
+ engTicketLabels: [],
24
+ engTicketIssueType: "",
25
+ engTicketCustomFields: {},
26
+ };
27
+ }
28
+ // State variables
29
+ export let currentState = getInitialState();
30
+ export let recordingStartTime = null;
31
+ export let recordingEndTime = null;
32
+ export let timerInterval = null;
33
+ export let isRecording = false;
34
+ // State setters
35
+ export function setCurrentState(state) {
36
+ currentState = state;
37
+ }
38
+ export function resetState() {
39
+ currentState = getInitialState();
40
+ recordingStartTime = null;
41
+ recordingEndTime = null;
42
+ }
43
+ export function setRecordingStartTime(time) {
44
+ recordingStartTime = time;
45
+ }
46
+ export function setRecordingEndTime(time) {
47
+ recordingEndTime = time;
48
+ }
49
+ export function setTimerInterval(interval) {
50
+ timerInterval = interval;
51
+ }
52
+ export function setIsRecording(recording) {
53
+ isRecording = recording;
54
+ }
55
+ export function getCurrentState() {
56
+ return currentState;
57
+ }
58
+ export function getRecordingStartTime() {
59
+ return recordingStartTime;
60
+ }
61
+ export function getRecordingEndTime() {
62
+ return recordingEndTime;
63
+ }
64
+ export function getTimerInterval() {
65
+ return timerInterval;
66
+ }
67
+ export function getIsRecording() {
68
+ return isRecording;
69
+ }
@@ -0,0 +1,16 @@
1
+ export const STORAGE_KEYS = {
2
+ CREATE_ISSUE: "sf-create-issue-preference",
3
+ CREATE_ENG_TICKET: "sf-create-eng-ticket-preference",
4
+ };
5
+ export const DEFAULT_SHORTCUTS = {
6
+ openReportIssue: { key: "r", requireCmdCtrl: true },
7
+ openStartNowMode: { key: "n", requireCmdCtrl: true },
8
+ };
9
+ export const ReportIssueContext = {
10
+ shortcuts: { ...DEFAULT_SHORTCUTS },
11
+ resolveSessionId: null,
12
+ apiKey: null,
13
+ backendApi: null,
14
+ triageBaseUrl: "https://app.sailfishqa.com",
15
+ deactivateIsolation: () => { },
16
+ };