@sailfish-ai/recorder 1.10.2 → 1.10.4

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.
Files changed (38) hide show
  1. package/dist/chunks/fiberHook-CEzmPkx_.js +125 -0
  2. package/dist/chunks/fiberHook-CEzmPkx_.js.br +0 -0
  3. package/dist/chunks/fiberHook-CEzmPkx_.js.gz +0 -0
  4. package/dist/chunks/fiberHook-DGANQ2ma.js +130 -0
  5. package/dist/chunks/fiberHook-DGANQ2ma.js.br +0 -0
  6. package/dist/chunks/fiberHook-DGANQ2ma.js.gz +0 -0
  7. package/dist/chunks/rrweb-plugin-console-record-BmAm-Ih_.js +181 -0
  8. package/dist/chunks/rrweb-plugin-console-record-BmAm-Ih_.js.br +0 -0
  9. package/dist/chunks/rrweb-plugin-console-record-BmAm-Ih_.js.gz +0 -0
  10. package/dist/chunks/rrweb-plugin-console-record-Cr-osXuj.js +180 -0
  11. package/dist/chunks/rrweb-plugin-console-record-Cr-osXuj.js.br +0 -0
  12. package/dist/chunks/rrweb-plugin-console-record-Cr-osXuj.js.gz +0 -0
  13. package/dist/chunks/rrweb-record-only-Ba4xyfd6.js +5253 -0
  14. package/dist/chunks/rrweb-record-only-Ba4xyfd6.js.br +0 -0
  15. package/dist/chunks/rrweb-record-only-Ba4xyfd6.js.gz +0 -0
  16. package/dist/chunks/rrweb-record-only-C5Qb-uaQ.js +5253 -0
  17. package/dist/chunks/rrweb-record-only-C5Qb-uaQ.js.br +0 -0
  18. package/dist/chunks/rrweb-record-only-C5Qb-uaQ.js.gz +0 -0
  19. package/dist/inAppReportIssueModal/index.js +171 -129
  20. package/dist/inAppReportIssueModal/integrations.js +84 -19
  21. package/dist/inAppReportIssueModal/state.js +1 -0
  22. package/dist/inAppReportIssueModal/types.js +1 -0
  23. package/dist/inAppReportIssueModal/ui.js +9 -0
  24. package/dist/index.js +259 -60
  25. package/dist/recorder.cjs +1954 -7344
  26. package/dist/recorder.js +1953 -7344
  27. package/dist/recorder.js.br +0 -0
  28. package/dist/recorder.js.gz +0 -0
  29. package/dist/recording.js +41 -32
  30. package/dist/session.js +12 -6
  31. package/dist/types/inAppReportIssueModal/integrations.d.ts +8 -0
  32. package/dist/types/inAppReportIssueModal/types.d.ts +3 -4
  33. package/dist/types/index.d.ts +11 -3
  34. package/dist/types/recording.d.ts +2 -2
  35. package/dist/types/session.d.ts +1 -0
  36. package/dist/types/websocket.d.ts +1 -0
  37. package/dist/websocket.js +11 -10
  38. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { createTriageAndIssueFromRecorder, createTriageFromRecorder, } from "../graphql";
2
- import { getFieldsForProject, getIntegrationData, getProjectsForTeam, getUsers, hasValidIntegration, updateFormWithIntegrationData, updateIssueTypeOptions, } from "./integrations";
2
+ import { getFieldsForProject, getIntegrationData, getProjectsForTeam, getSprintFieldId, getUsers, hasValidIntegration, refreshIntegrationData, updateFormWithIntegrationData, updateIssueTypeOptions, } from "./integrations";
3
3
  import { currentState, isRecording, recordingEndTime, recordingStartTime, resetState, setIsRecording, setRecordingEndTime, setRecordingStartTime, setTimerInterval, timerInterval, } from "./state";
4
4
  import { STORAGE_KEYS } from "./types";
5
5
  import { getChevronSVG, renderCustomMultiSelect, renderDynamicField, } from "./ui";
@@ -174,6 +174,23 @@ function generateEngTicketFieldsHTML() {
174
174
  integrationData.labels.length > 0) {
175
175
  fieldsHTML += renderCustomMultiSelect("sf-eng-ticket-labels", "Labels", integrationData.labels, currentState.engTicketLabels, false);
176
176
  }
177
+ // Sprint field (only for Jira when sprints are available)
178
+ if (isJira &&
179
+ integrationData.sprints &&
180
+ Array.isArray(integrationData.sprints) &&
181
+ integrationData.sprints.length > 0) {
182
+ fieldsHTML += `
183
+ <div>
184
+ <label for="sf-eng-ticket-sprint" style="display:block; font-size:14px; font-weight:500; margin-bottom:6px;">
185
+ Sprint
186
+ </label>
187
+ <select id="sf-eng-ticket-sprint"
188
+ 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;">
189
+ <option value="" disabled selected style="color: #9ca3af;">Select sprint...</option>
190
+ </select>
191
+ </div>
192
+ `;
193
+ }
177
194
  // Dynamic Fields Container
178
195
  fieldsHTML += `
179
196
  <div id="sf-dynamic-fields-container" style="display: flex; flex-direction: column; gap: 12px;"></div>
@@ -733,6 +750,32 @@ function injectModalHTML(initialMode = "lookback") {
733
750
  // No integration, disable eng ticket
734
751
  currentState.createEngTicket = false;
735
752
  }
753
+ // Background refresh — silently re-fetch integration data so cloud
754
+ // changes are picked up without a full page reload.
755
+ if (ReportIssueContext.apiKey && ReportIssueContext.backendApi) {
756
+ refreshIntegrationData(ReportIssueContext.apiKey, ReportIssueContext.backendApi).then((fresh) => {
757
+ if (!fresh || !document.getElementById("sf-report-issue-modal"))
758
+ return;
759
+ ReportIssueContext.integrationData = fresh;
760
+ // Re-generate eng ticket fields HTML now that integration data is available
761
+ const container = document.getElementById("sf-eng-ticket-fields-container");
762
+ if (container) {
763
+ const newFieldsHTML = generateEngTicketFieldsHTML();
764
+ if (newFieldsHTML) {
765
+ container.innerHTML = newFieldsHTML;
766
+ initializeEngTicketForm();
767
+ bindEngTicketListeners();
768
+ updateFormWithIntegrationData(currentState);
769
+ renderDynamicFields(currentState.engTicketProject, currentState.engTicketIssueType);
770
+ }
771
+ }
772
+ // If integration just became available, show the checkbox
773
+ const label = document.getElementById("sf-create-eng-ticket-label");
774
+ if (label) {
775
+ label.style.display = "flex";
776
+ }
777
+ });
778
+ }
736
779
  }
737
780
  function initializeEngTicketForm() {
738
781
  const integrationData = ReportIssueContext.integrationData;
@@ -834,6 +877,125 @@ function updateModeSpecificUI(mode) {
834
877
  submitBtn.style.cursor = "pointer";
835
878
  }
836
879
  }
880
+ function bindEngTicketListeners() {
881
+ const engTeamSelect = document.getElementById("sf-eng-ticket-team");
882
+ const engProjectSelect = document.getElementById("sf-eng-ticket-project");
883
+ const engPrioritySelect = document.getElementById("sf-eng-ticket-priority");
884
+ const engLabelsContainer = document.getElementById("sf-eng-ticket-labels-container");
885
+ const engTypeSelect = document.getElementById("sf-eng-ticket-type");
886
+ const engSprintSelect = document.getElementById("sf-eng-ticket-sprint");
887
+ if (engTeamSelect) {
888
+ engTeamSelect.addEventListener("change", () => {
889
+ currentState.engTicketTeam = engTeamSelect.value;
890
+ engTeamSelect.style.color = engTeamSelect.value ? "" : "#9ca3af";
891
+ // Update projects dropdown when team changes (for Linear)
892
+ const projectSelect = document.getElementById("sf-eng-ticket-project");
893
+ if (projectSelect) {
894
+ currentState.engTicketProject = "";
895
+ currentState.engTicketCustomFields = {};
896
+ const projects = getProjectsForTeam(engTeamSelect.value);
897
+ projectSelect.innerHTML =
898
+ '<option value="">Select project...</option>';
899
+ projects.forEach((project) => {
900
+ const optionElement = document.createElement("option");
901
+ optionElement.value = project.id || project.value || project;
902
+ optionElement.textContent =
903
+ project.name || project.label || project;
904
+ projectSelect.appendChild(optionElement);
905
+ });
906
+ }
907
+ });
908
+ }
909
+ if (engProjectSelect) {
910
+ engProjectSelect.addEventListener("change", () => {
911
+ currentState.engTicketProject = engProjectSelect.value;
912
+ engProjectSelect.style.color = engProjectSelect.value ? "" : "#9ca3af";
913
+ currentState.engTicketCustomFields = {};
914
+ const integrationData = getIntegrationData();
915
+ if (integrationData && engTypeSelect) {
916
+ updateIssueTypeOptions(engTypeSelect, engProjectSelect.value);
917
+ currentState.engTicketIssueType = engTypeSelect.value;
918
+ }
919
+ if (integrationData &&
920
+ integrationData.provider?.toLowerCase() === "jira" &&
921
+ integrationData.jiraReporterAccountId &&
922
+ engProjectSelect.value) {
923
+ const fields = getFieldsForProject(engProjectSelect.value, currentState.engTicketIssueType);
924
+ const reporterField = fields.find((f) => f.fieldId === "reporter");
925
+ if (reporterField) {
926
+ currentState.engTicketCustomFields["reporter"] =
927
+ integrationData.jiraReporterAccountId;
928
+ }
929
+ }
930
+ renderDynamicFields(engProjectSelect.value, currentState.engTicketIssueType);
931
+ });
932
+ }
933
+ if (engSprintSelect) {
934
+ engSprintSelect.addEventListener("change", () => {
935
+ currentState.engTicketSprint = engSprintSelect.value;
936
+ engSprintSelect.style.color = engSprintSelect.value ? "" : "#9ca3af";
937
+ });
938
+ }
939
+ if (engPrioritySelect) {
940
+ engPrioritySelect.addEventListener("change", () => {
941
+ currentState.engTicketPriority = Number(engPrioritySelect.value);
942
+ });
943
+ }
944
+ if (engLabelsContainer) {
945
+ setupCustomMultiSelectListeners("sf-eng-ticket-labels", (selectedValues) => {
946
+ currentState.engTicketLabels = selectedValues;
947
+ });
948
+ }
949
+ if (engTypeSelect) {
950
+ engTypeSelect.addEventListener("change", () => {
951
+ currentState.engTicketIssueType = engTypeSelect.value;
952
+ engTypeSelect.style.color = engTypeSelect.value ? "" : "#9ca3af";
953
+ const projectSelect = document.getElementById("sf-eng-ticket-project");
954
+ if (projectSelect && projectSelect.value) {
955
+ const reporterValue = currentState.engTicketCustomFields["reporter"];
956
+ currentState.engTicketCustomFields = {};
957
+ if (reporterValue) {
958
+ currentState.engTicketCustomFields["reporter"] = reporterValue;
959
+ }
960
+ renderDynamicFields(projectSelect.value, engTypeSelect.value);
961
+ }
962
+ });
963
+ }
964
+ const dynamicFieldsContainer = document.getElementById("sf-dynamic-fields-container");
965
+ if (dynamicFieldsContainer) {
966
+ dynamicFieldsContainer.addEventListener("input", (e) => {
967
+ const target = e.target;
968
+ if (target.classList.contains("sf-dynamic-field")) {
969
+ const fieldId = target.dataset.fieldId;
970
+ if (fieldId) {
971
+ if (target.type === "checkbox") {
972
+ currentState.engTicketCustomFields[fieldId] = target.checked;
973
+ }
974
+ else if (target.type === "number") {
975
+ currentState.engTicketCustomFields[fieldId] =
976
+ parseFloat(target.value) || null;
977
+ }
978
+ else {
979
+ currentState.engTicketCustomFields[fieldId] = target.value;
980
+ }
981
+ }
982
+ }
983
+ });
984
+ dynamicFieldsContainer.addEventListener("change", (e) => {
985
+ const target = e.target;
986
+ if (target.classList.contains("sf-dynamic-field")) {
987
+ const fieldId = target.dataset.fieldId;
988
+ if (fieldId) {
989
+ currentState.engTicketCustomFields[fieldId] = target.value;
990
+ }
991
+ if (target.tagName === "SELECT") {
992
+ const selectElement = target;
993
+ selectElement.style.color = selectElement.value ? "" : "#9ca3af";
994
+ }
995
+ }
996
+ });
997
+ }
998
+ }
837
999
  function bindListeners() {
838
1000
  const tabButtons = modalEl?.querySelectorAll(".sf-issue-tab");
839
1001
  const recordBtn = document.getElementById("sf-start-recording-btn");
@@ -985,134 +1147,7 @@ function bindListeners() {
985
1147
  });
986
1148
  }
987
1149
  // 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
- }
1150
+ bindEngTicketListeners();
1116
1151
  // Start recording
1117
1152
  if (recordBtn) {
1118
1153
  recordBtn.onclick = () => {
@@ -1188,6 +1223,13 @@ function bindListeners() {
1188
1223
  }
1189
1224
  }
1190
1225
  });
1226
+ // Add sprint to custom fields if selected
1227
+ const engSprintSelect = document.getElementById("sf-eng-ticket-sprint");
1228
+ const sprintValue = engSprintSelect?.value || currentState.engTicketSprint;
1229
+ if (sprintValue) {
1230
+ const sprintFieldId = getSprintFieldId();
1231
+ engTicketCustomFields[sprintFieldId] = parseInt(sprintValue, 10);
1232
+ }
1191
1233
  closeModal();
1192
1234
  // Create triage + issue (with optional engineering ticket)
1193
1235
  createTriageAndIssue(`${startTimestamp}`, `${endTimestamp}`, desc, issueName, issueDescription, currentState.createEngTicket, engTicketTeam, engTicketProject, engTicketPriority, engTicketLabels, engTicketIssueType, engTicketCustomFields);
@@ -1,42 +1,63 @@
1
1
  import { fetchEngineeringTicketPlatformIntegrations } from "../graphql";
2
2
  const SUPPORT_TICKET_INTEGRATIONS = ["jira", "linear", "zendesk"];
3
3
  let integrationData = null;
4
+ // Cache per cloud so switching between clouds is instant
5
+ const cloudCache = new Map();
4
6
  export function getIntegrationData() {
5
7
  return integrationData;
6
8
  }
7
9
  export function hasValidIntegration() {
8
10
  return integrationData !== null && integrationData.installed === true;
9
11
  }
12
+ function resolveIntegration(response) {
13
+ if (response?.errors && response.errors.length > 0) {
14
+ console.error("GraphQL errors fetching integrations:", response.errors);
15
+ return null;
16
+ }
17
+ const integrations = response?.data?.getEngineeringTicketPlatformIntegrationsFromApiKey;
18
+ const validIntegrations = (integrations || []).filter((item) => SUPPORT_TICKET_INTEGRATIONS.includes(item.provider?.toLowerCase() || "") && item.installed === true);
19
+ if (validIntegrations.length === 0) {
20
+ console.warn("No valid installed integrations found");
21
+ return null;
22
+ }
23
+ // Prefer Jira integration, fallback to first available
24
+ const chosen = validIntegrations.find((i) => i.provider?.toLowerCase() === "jira") || validIntegrations[0];
25
+ // Cache by cloud ID for instant switching
26
+ if (chosen?.primaryCloudId) {
27
+ cloudCache.set(chosen.primaryCloudId, chosen);
28
+ }
29
+ return chosen;
30
+ }
10
31
  export async function fetchIntegrationData(apiKey, backendApi) {
11
32
  if (integrationData) {
12
33
  return;
13
34
  }
14
35
  try {
15
36
  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];
37
+ integrationData = resolveIntegration(response);
34
38
  }
35
39
  catch (error) {
36
40
  console.error("Error fetching integration data:", error);
37
41
  integrationData = null;
38
42
  }
39
43
  }
44
+ /**
45
+ * Re-fetch integration data in the background. Returns the fresh data
46
+ * and updates the module-level cache. Callers can compare with previous
47
+ * data to decide whether to refresh the UI.
48
+ */
49
+ export async function refreshIntegrationData(apiKey, backendApi) {
50
+ try {
51
+ const response = await fetchEngineeringTicketPlatformIntegrations(apiKey, backendApi);
52
+ const fresh = resolveIntegration(response);
53
+ integrationData = fresh;
54
+ return fresh;
55
+ }
56
+ catch (error) {
57
+ console.error("Error refreshing integration data:", error);
58
+ return integrationData; // keep existing on error
59
+ }
60
+ }
40
61
  export function populateSelectOptions(selectElement, options, defaultValue) {
41
62
  const placeholderOption = document.createElement("option");
42
63
  placeholderOption.value = "";
@@ -103,6 +124,45 @@ export function populatePriorityOptions(selectElement, provider, defaultPriority
103
124
  selectElement.value = "0";
104
125
  }
105
126
  }
127
+ export function populateSprintOptions(selectElement, sprints, currentValue) {
128
+ selectElement.innerHTML = "";
129
+ // Placeholder option
130
+ const placeholderOption = document.createElement("option");
131
+ placeholderOption.value = "";
132
+ placeholderOption.disabled = true;
133
+ placeholderOption.selected = !currentValue;
134
+ placeholderOption.textContent = "Select sprint...";
135
+ placeholderOption.style.color = "#9ca3af";
136
+ selectElement.appendChild(placeholderOption);
137
+ // Only show active or future sprints
138
+ const activeSprints = (sprints || []).filter((s) => s.state === "active" || s.state === "future");
139
+ activeSprints.forEach((sprint) => {
140
+ const optionElement = document.createElement("option");
141
+ optionElement.value = String(sprint.id);
142
+ optionElement.textContent = sprint.name || sprint.id;
143
+ selectElement.appendChild(optionElement);
144
+ });
145
+ if (currentValue) {
146
+ selectElement.value = currentValue;
147
+ }
148
+ }
149
+ export function getSprintFieldId() {
150
+ if (!integrationData?.fieldConfigurations) {
151
+ return "customfield_10020";
152
+ }
153
+ // Search field configurations for the sprint field
154
+ const configs = Array.isArray(integrationData.fieldConfigurations)
155
+ ? integrationData.fieldConfigurations
156
+ : [];
157
+ for (const config of configs) {
158
+ const fields = config.fields || [];
159
+ const sprintField = fields.find((f) => f.schema?.custom === "com.pyxis.greenhopper.jira:gh-sprint");
160
+ if (sprintField?.fieldId) {
161
+ return sprintField.fieldId;
162
+ }
163
+ }
164
+ return "customfield_10020";
165
+ }
106
166
  export function updateIssueTypeOptions(selectElement, projectId) {
107
167
  if (!integrationData?.projects || !projectId) {
108
168
  selectElement.innerHTML = "";
@@ -244,9 +304,14 @@ export function updateFormWithIntegrationData(currentState) {
244
304
  currentState.engTicketPriority = Number(prioritySelect.value);
245
305
  }
246
306
  }
307
+ // Update sprint dropdown (for Jira)
308
+ const sprintSelect = document.getElementById("sf-eng-ticket-sprint");
309
+ const isJira = integrationData.provider?.toLowerCase() === "jira";
310
+ if (sprintSelect && isJira && integrationData.sprints) {
311
+ populateSprintOptions(sprintSelect, integrationData.sprints, currentState.engTicketSprint || undefined);
312
+ }
247
313
  // Update issue type dropdown based on selected project (for Jira)
248
314
  const issueTypeSelect = document.getElementById("sf-eng-ticket-type");
249
- const isJira = integrationData.provider?.toLowerCase() === "jira";
250
315
  if (issueTypeSelect && isJira && currentState.engTicketProject) {
251
316
  updateIssueTypeOptions(issueTypeSelect, currentState.engTicketProject);
252
317
  // Preserve existing value if available, otherwise use DOM value
@@ -21,6 +21,7 @@ export function getInitialState() {
21
21
  engTicketProject: "",
22
22
  engTicketPriority: 0,
23
23
  engTicketLabels: [],
24
+ engTicketSprint: "",
24
25
  engTicketIssueType: "",
25
26
  engTicketCustomFields: {},
26
27
  };
@@ -13,4 +13,5 @@ export const ReportIssueContext = {
13
13
  backendApi: null,
14
14
  triageBaseUrl: "https://app.sailfishqa.com",
15
15
  deactivateIsolation: () => { },
16
+ integrationData: null,
16
17
  };
@@ -412,6 +412,15 @@ export function generateModalHTML(currentState, initialMode = "lookback") {
412
412
  value="${currentState.engTicketLabels.join(", ")}"
413
413
  style="width:100%; padding:8px 12px; font-size:14px; border:1px solid #cbd5e1; border-radius:6px; outline:none;">
414
414
  </div>
415
+ <div>
416
+ <label for="sf-eng-ticket-sprint" style="display:block; font-size:14px; font-weight:500; margin-bottom:6px;">
417
+ Sprint
418
+ </label>
419
+ <select id="sf-eng-ticket-sprint"
420
+ 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;">
421
+ <option value="">None</option>
422
+ </select>
423
+ </div>
415
424
  <div>
416
425
  <label for="sf-eng-ticket-type" style="display:block; font-size:14px; font-weight:500; margin-bottom:6px;">
417
426
  Issue Type