PageSourceSearch

https://www.brain-score.org/static/benchmarks/js/leaderboard/issues/report-issue.js

js brain-score.org collected 2026-09-25 02:42:48 UTC 10,428 bytes, 364 lines download raw bytes

1// Report Issue functionality for leaderboard
2
3// Setup report issue functionality
4function setupReportIssue() {
5  const reportButton = document.getElementById('reportIssueText');
6  const modal = document.getElementById('reportIssueModal');
7  const closeButton = document.getElementById('closeReportModal');
8  const cancelButton = document.getElementById('cancelReportModal');
9  const form = document.getElementById('reportIssueForm');
10  const systemInfoField = document.getElementById('systemInfo');
11  
12  if (!reportButton || !modal || !form) {
13    console.warn('Report issue elements not found');
14    return;
15  }
16  
17  // Populate system info
18  populateSystemInfo();
19  
20  // Event listeners
21  reportButton.addEventListener('click', openReportModal);
22  closeButton?.addEventListener('click', closeReportModal);
23  cancelButton?.addEventListener('click', () => { form.reset(); populateSystemInfo(); closeReportModal(); });
24  form.addEventListener('submit', handleReportSubmission);
25  
26  // Close modal when clicking outside
27  modal.addEventListener('click', (e) => {
28    if (e.target === modal) {
29      closeReportModal();
30    }
31  });
32
33  // Close modal on escape key press (with saving)
34  document.addEventListener('keydown', (e) => {
35    if (e.key === 'Escape' && modal.style.display === 'flex') {
36      closeReportModal();
37    }
38  });
39}
40
41// Open the report issue modal
42function openReportModal() {
43  const modal = document.getElementById('reportIssueModal');
44  if (modal) {
45    modal.style.display = 'flex';
46    document.body.classList.add('modal-open');
47    
48    // Focus on the first input
49    const firstInput = modal.querySelector('select, input, textarea');
50    if (firstInput) {
51      setTimeout(() => firstInput.focus(), 100);
52    }
53  }
54}
55
56// Close the report issue modal
57function closeReportModal() {
58  const modal = document.getElementById('reportIssueModal');
59  if (modal) {
60    modal.style.display = 'none';
61    document.body.classList.remove('modal-open');
62  }
63}
64
65// Handle form submission
66async function handleReportSubmission(event) {
67  event.preventDefault();
68  
69  const submitBtn = document.getElementById('submitIssueBtn');
70  const originalBtnContent = submitBtn.innerHTML;
71  
72  try {
73    // Show loading state
74    submitBtn.disabled = true;
75    submitBtn.innerHTML = '<span class="icon"><i class="fa-solid fa-spinner fa-spin"></i></span><span>Submitting...</span>';
76    
77    // Collect form data
78    const formData = collectFormData();
79    
80    // Validate required fields
81    if (!validateFormData(formData)) {
82      throw new Error('Please fill in all required fields');
83    }
84    
85    // Submit to backend
86    const response = await submitIssueToBackend(formData);
87    
88    if (response.success) {
89      showSuccessMessage(response.issue_url || 'Issue submitted successfully!');
90      const form = document.getElementById('reportIssueForm');
91      form.reset();
92      populateSystemInfo();
93      closeReportModal();
94    } else {
95      throw new Error(response.error || 'Failed to submit issue');
96    }
97    
98  } catch (error) {
99    console.error('Error submitting issue:', error);
100    showErrorMessage(error.message || 'Failed to submit issue. Please try again.');
101  } finally {
102    // Reset button state
103    submitBtn.disabled = false;
104    submitBtn.innerHTML = originalBtnContent;
105  }
106}
107
108// Collect form data
109function collectFormData() {
110  return {
111    issue_type: document.getElementById('issueType').value,
112    title: document.getElementById('issueTitle').value.trim(),
113    description: document.getElementById('issueDescription').value.trim(),
114    system_info: document.getElementById('systemInfo').value,
115    contact_email: document.getElementById('contactEmail').value.trim(),
116    page_url: window.location.href,
117    user_agent: navigator.userAgent,
118    timestamp: new Date().toISOString(),
119    // Include current filter state for context
120    filter_state: getCurrentFilterState()
121  };
122}
123
124// Validate form data
125function validateFormData(data) {
126  if (!data.issue_type) {
127    showFieldError('issueType', 'Please select an issue type');
128    return false;
129  }
130  
131  if (!data.title || data.title.length < 10) {
132    showFieldError('issueTitle', 'Please provide a descriptive title (at least 10 characters)');
133    return false;
134  }
135  
136  if (!data.description || data.description.length < 20) {
137    showFieldError('issueDescription', 'Please provide a detailed description (at least 20 characters)');
138    return false;
139  }
140  
141  return true;
142}
143
144// Show field-specific error
145function showFieldError(fieldId, message) {
146  const field = document.getElementById(fieldId);
147  if (field) {
148    field.classList.add('is-danger');
149    
150    // Remove existing error message
151    const existingError = field.parentNode.querySelector('.help.is-danger');
152    if (existingError) {
153      existingError.remove();
154    }
155    
156    // Add error message
157    const errorElement = document.createElement('p');
158    errorElement.className = 'help is-danger';
159    errorElement.textContent = message;
160    field.parentNode.appendChild(errorElement);
161    
162    // Remove error styling after user starts typing
163    field.addEventListener('input', function clearError() {
164      field.classList.remove('is-danger');
165      const errorMsg = field.parentNode.querySelector('.help.is-danger');
166      if (errorMsg) {
167        errorMsg.remove();
168      }
169      field.removeEventListener('input', clearError);
170    }, { once: true });
171  }
172}
173
174// Submit issue to backend
175async function submitIssueToBackend(formData) {
176  try {
177    const response = await fetch('/report-issue/', {
178      method: 'POST',
179      headers: {
180        'Content-Type': 'application/json',
181        'X-CSRFToken': getCsrfToken(),
182      },
183      body: JSON.stringify(formData)
184    });
185    
186    if (!response.ok) {
187      const errorData = await response.json().catch(() => ({}));
188      throw new Error(errorData.error || `HTTP ${response.status}: ${response.statusText}`);
189    }
190    
191    return await response.json();
192  } catch (error) {
193    console.error('Backend submission error:', error);
194    throw error;
195  }
196}
197
198// Get CSRF token
199function getCsrfToken() {
200  const token = document.querySelector('[name=csrfmiddlewaretoken]')?.value ||
201                document.querySelector('meta[name=csrf-token]')?.getAttribute('content') ||
202                getCsrfFromCookie();
203  
204  if (!token) {
205    console.warn('CSRF token not found');
206  }
207  
208  return token || '';
209}
210
211// Get CSRF token from cookie (Django default)
212function getCsrfFromCookie() {
213  const name = 'csrftoken';
214  const cookies = document.cookie.split(';');
215  
216  for (let cookie of cookies) {
217    const [key, value] = cookie.trim().split('=');
218    if (key === name) {
219      return decodeURIComponent(value);
220    }
221  }
222  
223  return null;
224}
225
226// Get current filter state for context
227function getCurrentFilterState() {
228  try {
229    const activeFilters = window.activeFilters || {};
230    const searchTerm = document.getElementById('modelSearchInput')?.value || '';
231    
232    return {
233      search_term: searchTerm,
234      active_filters: activeFilters,
235      visible_rows: window.globalGridApi ? getVisibleRowCount() : 'unknown'
236    };
237  } catch (error) {
238    console.warn('Could not collect filter state:', error);
239    return { error: 'Could not collect filter state' };
240  }
241}
242
243// Get count of visible rows in grid
244function getVisibleRowCount() {
245  if (!window.globalGridApi) return 'unknown';
246  
247  let count = 0;
248  window.globalGridApi.forEachNodeAfterFilterAndSort(() => count++);
249  return count;
250}
251
252// Populate system information
253function populateSystemInfo() {
254  const systemInfoField = document.getElementById('systemInfo');
255  if (!systemInfoField) return;
256  
257  const info = [
258    `Browser: ${navigator.userAgent}`,
259    `Screen: ${screen.width}x${screen.height}`,
260    `Viewport: ${window.innerWidth}x${window.innerHeight}`,
261    `Platform: ${navigator.platform}`,
262    `Language: ${navigator.language}`,
263    `Timestamp: ${new Date().toISOString()}`
264  ].join(' | ');
265  
266  systemInfoField.value = info;
267}
268
269// Show success message
270function showSuccessMessage(message) {
271  // Create success notification
272  const notification = document.createElement('div');
273  notification.className = 'notification is-success is-light';
274  notification.style.cssText = `
275    position: fixed;
276    top: 20px;
277    right: 20px;
278    z-index: 9999;
279    max-width: 400px;
280    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
281  `;
282  
283  notification.innerHTML = `
284    <button class="delete"></button>
285    <strong>Success!</strong><br>
286    ${typeof message === 'string' && message.startsWith('http') ? 
287      `<a href="${message}" target="_blank" class="has-text-link">View issue on GitHub</a>` : 
288      message}
289  `;
290  
291  document.body.appendChild(notification);
292  
293  // Auto-remove after 5 seconds
294  setTimeout(() => {
295    if (notification.parentNode) {
296      notification.parentNode.removeChild(notification);
297    }
298  }, 5000);
299  
300  // Manual close
301  const deleteBtn = notification.querySelector('.delete');
302  if (deleteBtn) {
303    deleteBtn.addEventListener('click', () => {
304      if (notification.parentNode) {
305        notification.parentNode.removeChild(notification);
306      }
307    });
308  }
309}
310
311// Show error message
312function showErrorMessage(message) {
313  // Create error notification
314  const notification = document.createElement('div');
315  notification.className = 'notification is-danger is-light';
316  notification.style.cssText = `
317    position: fixed;
318    top: 20px;
319    right: 20px;
320    z-index: 9999;
321    max-width: 400px;
322    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
323  `;
324  
325  notification.innerHTML = `
326    <button class="delete"></button>
327    <strong>Error!</strong><br>
328    ${message}
329  `;
330  
331  document.body.appendChild(notification);
332  
333  // Auto-remove after 8 seconds (longer for errors)
334  setTimeout(() => {
335    if (notification.parentNode) {
336      notification.parentNode.removeChild(notification);
337    }
338  }, 8000);
339  
340  // Manual close
341  const deleteBtn = notification.querySelector('.delete');
342  if (deleteBtn) {
343    deleteBtn.addEventListener('click', () => {
344      if (notification.parentNode) {
345        notification.parentNode.removeChild(notification);
346      }
347    });
348  }
349}
350
351
352
353// Export functions for use by other modules
354window.LeaderboardReportIssue = {
355  setupReportIssue,
356  openReportModal,
357  closeReportModal,
358  handleReportSubmission,
359  collectFormData,
360  validateFormData,
361  submitIssueToBackend,
362  showSuccessMessage,
363  showErrorMessage
364};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.