-
-
Notifications
You must be signed in to change notification settings - Fork 313
feature/Add Issue Suggestion Box with Autocomplete to Report Page description Input #4010
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature/Add Issue Suggestion Box with Autocomplete to Report Page description Input #4010
Conversation
WalkthroughThe changes introduce a new suggestion box feature to the website. New CSS classes for the suggestion box and its items define visual styles, including responsive adjustments. The JavaScript file now includes event listeners on a textarea to handle input and key events, triggering debounced API calls to fetch issue suggestions and enabling keyboard navigation for selecting suggestions. Additionally, the report template has been updated to load the new JavaScript functionality. Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant T as Textarea
participant JS as Issue Suggestion Script
participant API as GitHub API
U->>T: Types in textarea (e.g., "#123")
T->>JS: Trigger input/keydown event
JS->>JS: Debounce and process input
JS->>API: Request issue suggestions
API-->>JS: Return suggestions data
JS->>T: Display suggestion box with results
U->>JS: Navigate and select suggestion (arrow keys/Enter)
JS->>T: Insert issue reference into textarea
Suggested reviewers
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (2)
website/static/js/issue.js (2)
276-340: Good suggestion box DOM creation and styling.The suggestion box creation is well-implemented. Consider moving more of the inline styles to the external CSS file to maintain better separation of concerns.
378-378: Use optional chaining for Link header check.The static analysis tool correctly suggests using optional chaining for more concise code.
- hasMorePages = linkHeader && linkHeader.includes('rel="next"'); + hasMorePages = linkHeader?.includes('rel="next"');🧰 Tools
🪛 Biome (1.9.4)
[error] 378-378: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
website/static/css/style.css(1 hunks)website/static/js/issue.js(1 hunks)website/templates/report.html(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
website/static/js/issue.js
[error] 378-378: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: Run Tests
- GitHub Check: docker-test
- GitHub Check: Analyze (python)
🔇 Additional comments (6)
website/templates/report.html (1)
322-322: LGTM: Script inclusion for issue suggestion feature.The script integration for the issue.js file is correctly placed in the after_js block, which ensures it loads after other JavaScript dependencies.
website/static/css/style.css (1)
587-627: LGTM: Well-structured CSS for the suggestion box feature.The CSS classes for the suggestion box are well-defined with appropriate styling for different states (hover, selected) and responsive design considerations for smaller screens.
One minor inconsistency to note is that the max-height property is duplicated in both the CSS (line 594) and JavaScript (line 302). Consider maintaining this property in one location to avoid potential inconsistencies during future updates.
website/static/js/issue.js (4)
172-192: LGTM: Well-structured initialization and event binding.The code establishes proper event listeners and initializes necessary variables for the suggestion box feature. The use of a cache Map is a good performance optimization.
193-224: Debounced input handling looks good.The implementation of debouncing for API requests is a good practice to prevent excessive API calls while typing. The validation of input to only trigger on numeric characters after a hash symbol is well thought out.
226-275: Keyboard navigation implementation is comprehensive.The keyboard navigation implementation provides a good user experience with support for arrow keys, Enter for selection, and Escape to dismiss. The code also includes logic to load more results when reaching the end of the list.
407-681: Comprehensive UI utility functions.The utility functions for UI manipulation, cursor positioning, and text insertion are well-implemented. The code properly escapes HTML to prevent XSS attacks and handles a variety of edge cases.
Two suggestions for future enhancement:
- Consider adding ARIA attributes to improve accessibility
- Implement a mechanism to clean up event listeners if this component is dynamically loaded/unloaded
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
website/static/js/issue.js (1)
367-369: 🛠️ Refactor suggestionHardcoded repository name should be configurable.
The repository name is hardcoded as 'OWASP-BLT/BLT', which reduces the reusability of this component.
Consider making this configurable by fetching it from a data attribute or a global configuration variable to improve maintainability.
- const url = `https://api.github.com/repos/OWASP-BLT/BLT/issues?state=all&per_page=${perPage}&page=${page}`; + // Get repository name from a data attribute or global variable + const repo = document.querySelector('meta[name="repository"]')?.content || 'OWASP-BLT/BLT'; + const url = `https://api.github.com/repos/${repo}/issues?state=all&per_page=${perPage}&page=${page}`;
🧹 Nitpick comments (4)
website/static/js/issue.js (4)
308-336: Consider moving styles to an external CSS file.Injecting styles directly into the document head works but isn't ideal for maintainability. Consider moving these styles to your project's CSS files.
- const style = document.createElement('style'); - style.textContent = ` - .suggestion-box::-webkit-scrollbar { - display: none; - } - .suggestion-item.selected { - background-color: #f0f0f0; - } - .load-more { - text-align: center; - padding: 8px; - color: #0366d6; - cursor: pointer; - font-weight: bold; - background-color: #f6f8fa; - border-top: 1px solid #e1e4e8; - } - .load-more:hover { - background-color: #f0f0f0; - } - .loading { - text-align: center; - padding: 8px; - color: #666; - font-style: italic; - } - `; - document.head.appendChild(style);These styles should be added to your main CSS file instead.
377-378: Use optional chaining for cleaner code.Instead of using the logical AND operator, use optional chaining for a more concise and modern approach.
- hasMorePages = linkHeader && linkHeader.includes('rel="next"'); + hasMorePages = linkHeader?.includes('rel="next"');🧰 Tools
🪛 Biome (1.9.4)
[error] 377-377: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
394-401: Add user-visible error handling for API failures.While errors are logged to the console, users won't know why suggestions aren't appearing when API calls fail. Consider adding a user-visible error message.
} catch (error) { console.error('Error fetching issues:', error); if (!append) { + // Show error message to user + suggestionBox.innerHTML = '<div class="suggestion-error">Failed to load suggestions</div>'; + suggestionBox.style.display = 'block'; + positionSuggestionBox(); + setTimeout(hideSuggestionBox, 3000); // Hide after 3 seconds hideSuggestionBox(); } else { // Remove loading indicator if there was an error removeLoadingIndicator(); }
356-357: Optimize API usage to prevent unnecessary data fetching.Currently, you're requesting 30 issues per page from GitHub's API but only showing 5 filtered results. This could lead to unnecessary data transfer and potentially hit GitHub API rate limits sooner.
- const perPage = 30; // GitHub API default + const perPage = 10; // Smaller batch size to optimize data transferAdditionally, consider adding a search parameter to filter by issue number directly in the API call:
- const url = `https://api.github.com/repos/OWASP-BLT/BLT/issues?state=all&per_page=${perPage}&page=${page}`; + // Use search API to filter by issue number directly + const url = query ? + `https://api.github.com/search/issues?q=repo:OWASP-BLT/BLT+${query}+in:number&per_page=${perPage}&page=${page}` : + `https://api.github.com/repos/OWASP-BLT/BLT/issues?state=all&per_page=${perPage}&page=${page}`;This way, GitHub will do the filtering for you before sending the response.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
website/static/js/issue.js(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
website/static/js/issue.js
[error] 377-377: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ Context from checks skipped due to timeout of 90000ms (3)
- GitHub Check: Run Tests
- GitHub Check: docker-test
- GitHub Check: Analyze (python)
🔇 Additional comments (7)
website/static/js/issue.js (7)
176-191: Good initialization and event handler setup!The code properly initializes all necessary state variables and sets up appropriate event listeners. The defensive check for textarea existence is a good practice.
194-224: Well-implemented debouncing for API calls!The input handler efficiently:
- Extracts the text after a hashtag
- Checks for numeric queries only
- Implements a 250ms debounce to prevent excessive API calls
- Resets pagination when input changes
This is a good implementation that will prevent excessive API calls while providing a responsive user experience.
227-275: Good keyboard navigation implementation with progressive loading!The keyboard navigation correctly handles arrow keys, Enter for selection, and Escape to dismiss. The smart loading of additional results when navigating to the bottom of the list is an excellent UX consideration.
478-483: Good XSS prevention through proper escaping!Using
escapeHTMLwhen displaying user-generated content is a great security practice to prevent XSS attacks. Well done!
567-611: Excellent cursor positioning technique!The implementation of
getCursorCoordinatesis clever and thorough. By creating a mirror of the textarea with the same styling and measuring the cursor position, you ensure proper suggestion box placement regardless of text wrapping or scroll position.
672-680: Complete HTML escaping implementation.Your
escapeHTMLfunction properly escapes all necessary characters to prevent XSS attacks. This is a critical security practice when rendering user-generated content.
172-173:Details
❓ Verification inconclusive
Consider adding a rate limiting check for GitHub API.
GitHub's API has rate limits that could affect this feature if heavily used. Consider checking the rate limit headers in the API responses and notifying users when limits are close to being reached.
🏁 Script executed:
#!/bin/bash # Check GitHub API rate limits curl -s https://api.github.com/rate_limit | jq '.resources.core'Length of output: 159
GitHub API Rate Limiting Check Recommendation
The GitHub API exhibits strict rate limits (e.g., a limit of 60 requests per hour with current usage at 2 and 58 remaining). To guard against potential disruptions under heavy use, please consider implementing a check that:
- Reads the rate limit headers (e.g.,
X-RateLimit-Remaining).- Notifies users or gracefully handles the situation when the remaining quota approaches a critical threshold.
Please ensure that this additional logic is incorporated into the issue suggestion functionality in
website/static/js/issue.js(around lines 172–173).
|
This would be for the Bugs on BLT, not Github Issues - we used to call them issues but now they are bugs and github issues. There is an API you can use to get the BLT bugs (issues) |
|
@coderabbitai could you please change api request instead of github API use OWSAP-BLT API to fetch the issues BLT |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chainflowchart TB
A[Change API Endpoint for Issue Suggestions] --o B[Understand current GitHub API implementation]
A --o C[Find OWASP-BLT API endpoint for bugs]
A --o D[Modify fetchIssueSuggestions function]
D --o E[Change API URL to OWASP-BLT endpoint]
D --o F[Adjust response handling for BLT API structure]
D --o G[Update issue display format in suggestion box]
@razzasid I'll help you modify the code to use OWASP-BLT API instead of GitHub API for fetching bugs/issues. After analyzing the codebase, I need to find the correct API endpoint for BLT bugs. Let's modify the // Function to fetch issue suggestions from the API
function fetchIssueSuggestions(query, page = 1) {
// Check if we have cached results for this query and page
const cacheKey = `${query}_${page}`;
if (issueCache.has(cacheKey)) {
return Promise.resolve(issueCache.get(cacheKey));
}
// Fetch bugs from OWASP-BLT API
const apiUrl = `/api/v1/bugs/?page=${page}&per_page=5&search=${query}`;
return fetch(apiUrl)
.then(response => {
// Check for pagination information in the response
const totalPages = response.headers.get('X-Total-Pages') || 1;
hasMorePages = page < parseInt(totalPages, 10);
return response.json();
})
.then(data => {
// Process bugs data from OWASP-BLT API
const bugs = data.results || data;
// Cache the results
issueCache.set(cacheKey, bugs);
return bugs;
})
.catch(error => {
console.error('Error fetching bug suggestions:', error);
return [];
});
}
// Function to display suggestions in the suggestion box
function displaySuggestions(suggestions) {
// Clear any existing suggestions
suggestionBox.innerHTML = '';
if (suggestions.length === 0) {
// If no suggestions, display a message
const noResults = document.createElement('div');
noResults.className = 'suggestion-item';
noResults.textContent = 'No bugs found';
suggestionBox.appendChild(noResults);
return;
}
// Add each suggestion to the suggestion box
suggestions.forEach((suggestion, index) => {
const item = document.createElement('div');
item.className = 'suggestion-item';
if (index === selectedIndex) {
item.classList.add('selected');
}
// Format the suggestion text (bug number + title)
item.textContent = `#${suggestion.id} - ${suggestion.description.substring(0, 50)}${suggestion.description.length > 50 ? '...' : ''}`;
// Store the full suggestion data as a data attribute
item.dataset.suggestion = JSON.stringify(suggestion);
// Add click event to handle selection
item.addEventListener('click', function() {
selectedIndex = index;
insertIssueReference(suggestion.id, suggestion.description.substring(0, 50));
hideSuggestionBox();
});
suggestionBox.appendChild(item);
});
// Add "Load more" option if there are more pages
if (hasMorePages) {
const loadMore = document.createElement('div');
loadMore.className = 'suggestion-item';
loadMore.textContent = 'Load more...';
loadMore.addEventListener('click', function() {
currentPage++;
const query = textarea.value.substring(lastHashIndex + 1, cursorPosition);
fetchIssueSuggestions(query, currentPage)
.then(newSuggestions => {
// Append new suggestions to existing ones
allSuggestions = [...allSuggestions, ...newSuggestions];
displaySuggestions(allSuggestions);
});
});
suggestionBox.appendChild(loadMore);
}
}Note: I've assumed the API endpoint is 🏁 Script executed: Length of output: 23088 🏁 Script executed: Length of output: 6608 🏁 Script executed: Length of output: 4499 🏁 Script executed: Length of output: 5496 🏁 Script executed: Length of output: 15392 🏁 Script executed: Length of output: 7297 🏁 Script executed: Length of output: 12121 Here's the modified code for the // Function to fetch issue suggestions from the API
async function fetchIssueSuggestions(query, page = 1, append = false) {
// If the query changed while we were waiting, abort
if (query !== currentSearch) return;
// Check cache first
const cacheKey = `${query}-${page}`;
if (cache.has(cacheKey)) {
const cachedData = cache.get(cacheKey);
displaySuggestions(cachedData.bugs, append);
hasMorePages = cachedData.hasMorePages;
return;
}
try {
if (!append) {
// Show loading indicator if this is the first page
displayLoadingIndicator();
} else {
// Show loading indicator at the bottom if loading more
appendLoadingIndicator();
}
// Get bugs for the current page using OWASP-BLT API
const url = `/api/v1/issues/?search=${query} &page =${page}`;
const response = await fetch(url);
if (!response.ok) throw new Error('API request failed');
const data = await response.json();
// Extract results and pagination info from the response
const bugs = data.results || data;
// Check for pagination info
hasMorePages = data.next !== null && data.next !== undefined;
// Cache the results with pagination info
cache.set(cacheKey, {
bugs: bugs,
hasMorePages: hasMorePages
});
// Only display if this is still the current search
if (query === currentSearch) {
displaySuggestions(bugs, append);
}
} catch (error) {
console.error('Error fetching bugs:', error);
if (!append) {
hideSuggestionBox();
} else {
// Remove loading indicator if there was an error
removeLoadingIndicator();
}
} finally {
isLoadingMore = false;
}
}
// Display suggestions with improved positioning
function displaySuggestions(bugs, append = false) {
if (!append) {
// Clear previous suggestions
suggestionBox.innerHTML = '';
} else {
// Remove loading indicator and load more button
removeLoadingIndicator();
removeLoadMoreButton();
}
if (!bugs.length && !append) {
hideSuggestionBox();
return;
}
if (!append) {
// Reset selection state
selectedIndex = -1;
isKeyboardNavigating = false;
}
// Add suggestion items
bugs.forEach(bug => {
const div = document.createElement('div');
div.className = 'suggestion-item';
div.dataset.issueNumber = bug.id;
// Use description instead of title for BLT bugs, and truncate if needed
const bugDescription = bug.description || '';
const truncatedDescription = bugDescription.length > 50
? bugDescription.substring(0, 50) + '...'
: bugDescription;
div.innerHTML = ` <strong>#${bug.id}</strong> : ${escapeHTML(truncatedDescription)}`;
div.style.cssText = `
padding: 8px 10px;
cursor: pointer;
border-bottom: 1px solid #eee;
`;
div.addEventListener('click', (e) = > {
e.stopPropagation(); // Prevent document click from firing
insertIssueReference(bug.id);
hideSuggestionBox();
});
div.addEventListener('mouseover', () => {
if (!isKeyboardNavigating) {
const items = getSuggestionItems();
items.forEach(item => item.classList.remove('selected'));
div.classList.add('selected');
selectedIndex = Array.from(items).indexOf(div);
}
});
div.addEventListener('mouseout', () => {
if (!isKeyboardNavigating) {
div.classList.remove('selected');
}
});
suggestionBox.appendChild(div);
});
// Add "Load more" button if there are more pages
if (hasMorePages) {
const loadMoreButton = document.createElement('div');
loadMoreButton.className = 'load-more';
loadMoreButton.textContent = 'Load more...';
loadMoreButton.addEventListener('click', (e) => {
e.stopPropagation(); // Prevent document click from firing
loadMoreIssues();
});
suggestionBox.appendChild(loadMoreButton);
}
// Position the suggestion box
positionSuggestionBox();
// Show the suggestion box
suggestionBox.style.display = 'block';
}This implementation:
You may need to adjust the exact endpoint URL or field names based on your specific API implementation. If you have more details about the API structure, I can refine this code further. --- Details✏️ Learnings added
|
|
Tip The multi-step agents feature is in experimental mode. A summary of the edits CodeRabbit will include in a stacked pull request to this pull request:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (4)
website/static/js/issue.js (4)
371-371: Use optional chaining for safer header access.The code should use optional chaining when accessing properties that might be undefined.
-const linkHeader = response.headers.get('Link'); +const linkHeader = response.headers?.get('Link');🧰 Tools
🪛 Biome (1.9.4)
[error] 371-371: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
369-372: Enhance pagination handling with X-Total-Pages header.The current pagination implementation relies on the
Linkheader, but BLT API might use a different pagination strategy.If the BLT API returns pagination information in different headers (like
X-Total-Pages), consider updating the code:// Check for pagination headers (if provided) const linkHeader = response.headers?.get('Link'); -hasMorePages = linkHeader && linkHeader.includes('rel="next"'); +const totalPages = response.headers?.get('X-Total-Pages'); +hasMorePages = totalPages ? currentPage < parseInt(totalPages) : linkHeader && linkHeader.includes('rel="next"');🧰 Tools
🪛 Biome (1.9.4)
[error] 371-371: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
387-396: Improve error handling for API failures.The current error handling only logs to console and hides the suggestion box but doesn't provide user feedback.
} catch (error) { console.error('Error fetching issues:', error); if (!append) { - hideSuggestionBox(); + suggestionBox.innerHTML = '<div class="suggestion-error">Failed to load suggestions</div>'; + suggestionBox.style.display = 'block'; + positionSuggestionBox(); } else { removeLoadingIndicator(); } }
435-441: Consider adding a loading threshold for pagination.The current implementation loads more issues only when the user reaches the last item, which might lead to a poor user experience if they need to arrow through many items.
Consider triggering the load when the user is approaching the end (e.g., 1-2 items before the end):
// Load more issues if we're at the bottom and there are more pages -if (e.key === 'ArrowDown' && selectedIndex === items.length - 1 && hasMorePages && !isLoadingMore) { +if (e.key === 'ArrowDown' && selectedIndex >= items.length - 2 && hasMorePages && !isLoadingMore) { loadMoreIssues(); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
website/static/js/issue.js(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
website/static/js/issue.js
[error] 371-371: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
⏰ Context from checks skipped due to timeout of 90000ms (4)
- GitHub Check: Run Tests
- GitHub Check: docker-test
- GitHub Check: Analyze (python)
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (7)
website/static/js/issue.js (7)
362-363: Update API endpoint to use BLT bugs instead of GitHub issues.According to the PR objectives, this feature should reference bugs in the BLT system rather than GitHub issues. The current API endpoint (
/api/v1/issues) might not be the correct one.Confirm whether this endpoint returns bugs from the BLT system as required in the PR comments. You may need to update this to the specific BLT bugs API endpoint.
362-368: Repository name should be configurable.The API endpoint hardcodes the repository, which reduces reusability.
558-603: Excellent cursor position calculation for suggestion box positioning.The implementation for calculating cursor coordinates within a textarea is very well done. It creates a mirror element to accurately determine cursor position, considers whitespace and line breaks, and cleans up DOM elements properly.
226-275: Well-implemented keyboard navigation.The keyboard navigation with arrow up/down for selection, enter for confirmation, and escape to dismiss is well implemented with proper event prevention and focus management.
194-224: Effectively implemented debouncing for API calls.The implementation correctly uses debouncing for API calls with a 250ms delay, which will help prevent excessive server requests as the user types.
665-672: Good security practice for preventing XSS.The escapeHTML function properly sanitizes user-generated content to prevent cross-site scripting attacks.
519-556: Great handling of viewport boundaries.The code ensures the suggestion box stays within viewport boundaries by checking against window dimensions and repositioning when necessary.
As mentioned in issue #3363, this feature adds an autocomplete suggestion box to the report page’s textarea, allowing users to reference GitHub issues by typing # followed by a number. The suggestion box fetches matching issues from the OWASP-BLT/BLT repository, supports keyboard navigation (Arrow Up/Down, Enter, Escape), and positions itself dynamically below the cursor across all lines. The implementation includes debouncing for API calls, responsive design, and proper cleanup of the suggestion box when deleting the hashtag or clicking outside.
Testing Video
2025-03-20.07-04-47.mp4
Summary by CodeRabbit
New Features
Style