aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Smith <[email protected]>2025-04-29 10:32:37 +0200
committerDaniel Smith <[email protected]>2025-05-20 09:50:29 +0000
commite9119a682e6ad9f70845a9c6efacc219ed5407fa (patch)
tree44ec03f82adec7e40da94c6f06c11a5d51c7d6a3
parentf3b411be465684ecb238a7561f7a5e2393f0b98d (diff)
Add functionality for branch filteringHEADdev
It may be helpful during release crunch time to filter on the release branch target and ensure that all changes targeting that branch get merged. This patch adds a new Branches filter where one or more branches can be specified. The backend will return only those change that have a yet-unmerged or unpicked change to the specified branches. This patch also updates styles and organization of the filter area to improve visual clarity. Task-number: QTQAINFRA-7144 Change-Id: I5dd98940b9d3bfc4f26b5f37a83a1ef2e511b917 Reviewed-by: Daniel Smith <[email protected]>
-rw-r--r--src/main/java/com/google/gerrit/plugins/cherrypickstatus/CherryPickRestApi.java15
-rw-r--r--src/main/resources/static/cherrypick-status-viewer.js383
2 files changed, 361 insertions, 37 deletions
diff --git a/src/main/java/com/google/gerrit/plugins/cherrypickstatus/CherryPickRestApi.java b/src/main/java/com/google/gerrit/plugins/cherrypickstatus/CherryPickRestApi.java
index d559305..2e70b88 100644
--- a/src/main/java/com/google/gerrit/plugins/cherrypickstatus/CherryPickRestApi.java
+++ b/src/main/java/com/google/gerrit/plugins/cherrypickstatus/CherryPickRestApi.java
@@ -105,6 +105,14 @@ public class CherryPickRestApi {
this.onlyExternalTqtc = onlyExternalTqtc;
}
+ // Option for filtering by branch names. Populated from query parameters.
+ @Option(name = "--branches", usage = "Comma-separated list of branches to filter by")
+ private String branches;
+
+ public void setBranches(String branches) {
+ this.branches = branches;
+ }
+
// Constructor for dependency injection. Initializes fields.
@Inject
public GetDashboard(
@@ -208,7 +216,12 @@ public class CherryPickRestApi {
requestBody.put("exclAbandon", true);
}
if (onlyExternalTqtc) {
- requestBody.put("onlyExternalTqtc", true);
+ requestBody.put("onlyExternalTqtc", true);
+ }
+ if (branches != null && !branches.isEmpty()) {
+ // Split the comma-separated string into a list of branch names
+ List<String> branchList = List.of(branches.split(","));
+ requestBody.put("branches", branchList); // Pass branches as a list of strings
}
// Add pagination parameter. Convert page string to integer.
diff --git a/src/main/resources/static/cherrypick-status-viewer.js b/src/main/resources/static/cherrypick-status-viewer.js
index d6c0ca6..db04284 100644
--- a/src/main/resources/static/cherrypick-status-viewer.js
+++ b/src/main/resources/static/cherrypick-status-viewer.js
@@ -49,6 +49,7 @@ Gerrit.install(plugin => {
this.errorMessage = null;
this.changes = [];
this.branchColumns = new Set();
+ this.selectedBranches = new Set();
this.loading = false;
// Read initial state from URL parameters
const urlParams = new URLSearchParams(window.location.search);
@@ -57,19 +58,124 @@ Gerrit.install(plugin => {
repo: urlParams.get('repo') || '',
onlyStuck: urlParams.get('onlyStuck') === 'true',
exclAbandon: urlParams.get('exclAbandon') === 'true',
- onlyExternalTqtc: urlParams.get('onlyExternalTqtc') === 'true'
+ onlyExternalTqtc: urlParams.get('onlyExternalTqtc') === 'true',
+ branches: urlParams.get('branches') ? urlParams.get('branches').split(',') : [] // Read branches from URL
};
this.currentPage = parseInt(urlParams.get('page') || '1', 10);
this.totalPages = 1;
+ // Initialize selectedBranches from currentFilters
+ this.selectedBranches = new Set(this.currentFilters.branches);
this.shadowRoot.innerHTML = `
<style>
- .filter-container { margin-bottom: 1rem; }
- .filter-input { margin-right: 1rem; padding: 0.5rem; }
- table { width: 100%; border-collapse: collapse; }
+ /* Main container for all filter groups */
+ .filter-container { margin-bottom: 1rem; display: flex; flex-wrap: wrap; align-items: flex-start; gap: 1rem; }
+
+ /* Style for visual grouping of filters */
+ .filter-group {
+ display: flex;
+ flex-wrap: wrap; /* Allow items to wrap within the group */
+ align-items: flex-start; /* Align items to the top */
+ gap: 0.5rem; /* Spacing between items in the group */
+ padding: 0.5rem;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ }
+
+ /* Individual filter item (label + input/checkbox) */
+ .filter-item { display: flex; align-items: center; gap: 0.25rem; }
+
+ /* Input field styling */
+ .filter-input { padding: 0.5rem; }
+
+ /* Label for checkboxes */
+ .filter-checkbox-label { margin-left: 0.1rem; margin-right: 0.5rem; }
+
+ /* Wrapper for branch input and tags */
+ .branch-filter-wrapper {
+ display: flex;
+ flex-direction: column; /* Stack input and tags vertically */
+ gap: 0.5rem; /* Space between input row and tags */
+ align-items: flex-start; /* Align items to the start */
+ }
+
+ /* Container for branch tags */
+ .branch-tags-container {
+ display: flex; /* Keep using flex for tags themselves */
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ }
+ .branch-tag { background-color: #e0e0e0; color: black; padding: 0.25rem 0.5rem; border-radius: 4px; display: inline-flex; align-items: center; gap: 0.25rem; }
+ .remove-branch-btn { cursor: pointer; background: none; border: none; font-weight: bold; color: #555; padding: 0 0.2rem; }
+ .remove-branch-btn:hover { color: black; }
+
+ /* Tooltip Styles */
+ .tooltip-container {
+ position: relative;
+ display: inline-block;
+ }
+ .filter-item.tooltip-container {
+ display: flex;
+ }
+ .branch-filter-wrapper.tooltip-container {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ align-items: flex-start;
+ }
+ button.tooltip-container {
+ /* Standard button styles apply */
+ }
+
+
+ .tooltip-text {
+ visibility: hidden;
+ opacity: 0;
+ width: max-content;
+ max-width: 250px;
+ background-color: #555;
+ color: #fff;
+ text-align: center;
+ border-radius: 6px;
+ padding: 5px 8px;
+ position: absolute;
+ z-index: 10; /* Ensure tooltip is on top */
+ top: 115%; /* Position below the element */
+ left: 50%;
+ transform: translateX(-50%);
+ transition: opacity 0.2s ease-in-out;
+ /* Delay is handled by the hover rule */
+ white-space: normal;
+ font-size: 0.85em;
+ pointer-events: none;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+ }
+
+ /* Tooltip Arrow */
+ .tooltip-text::after {
+ content: "";
+ position: absolute;
+ bottom: 100%; /* Position arrow at the top of the tooltip */
+ left: 50%;
+ margin-left: -5px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: transparent transparent #555 transparent; /* Arrow pointing up */
+ }
+
+ /* Show tooltip on hover with delay */
+ .tooltip-container:hover .tooltip-text {
+ visibility: visible;
+ opacity: 1;
+ transition-delay: 0.2s; /* 200ms delay */
+ }
+ /* End Tooltip Styles */
+
+
+ table { width: 100%; border-collapse: collapse; margin-top: 1rem; }
th, td { padding: 0.5rem; border: 1px solid #ddd; text-align: left; }
- a { color: currentColor;}
+ a { color: currentColor; }
.loading { padding: 1rem; }
.error { color: red; }
.pagination { margin-top: 1rem; }
@@ -86,37 +192,105 @@ Gerrit.install(plugin => {
.repo-filter-icon, .owner-filter-icon {
position: absolute;
- right: 0.5rem; /* Adjust as needed */
+ right: 0.5rem;
top: 50%;
- transform: translateY(-50%); /* Center vertically */
- display: none; /* Initially hidden */
+ transform: translateY(-50%);
+ display: none;
+ cursor: pointer;
+ }
+
+ th { /* Make header cells relative for icon positioning */
+ position: relative;
+ padding-right: 2rem; /* Add padding to prevent text overlap */
+ }
+
+ .branch-filter-icon {
+ position: absolute;
+ right: 0.5rem;
+ top: 50%;
+ transform: translateY(-50%);
+ display: none;
cursor: pointer;
}
td:nth-child(2):hover .repo-filter-icon, /* Repo column */
- td:nth-child(3):hover .owner-filter-icon { /* Owner column */
- display: inline-block;
+ td:nth-child(3):hover .owner-filter-icon, /* Owner column */
+ th:hover .branch-filter-icon { /* Branch header */
+ display: inline-block;
}
</style>
<div class="filter-container">
- <label for="ownerFilter">Owner Email:</label>
- <input class="filter-input" type="text" id="ownerFilter" list="ownerSuggestions" placeholder="Filter by owner email">
- <datalist id="ownerSuggestions"></datalist>
- ${isLoggedIn ? `
- <input type="checkbox" id="onlyMineFilter" name="onlyMineFilter">
- <label for="onlyMineFilter">Only Mine &nbsp;</label>
- ` : ''}
- <input class="filter-input" type="text" id="repoFilter" list="repoSuggestions" placeholder="Filter by repository">
- <datalist id="repoSuggestions"></datalist>
- <input type="checkbox" id="onlyStuckFilter" name="onlyStuckFilter">
- <label for="onlyStuckFilter">Only Stuck Changes &nbsp;</label>
- <input type="checkbox" id="exclAbandonFilter" name="exclAbandonFilter">
- <label for="exclAbandonFilter">Exclude Abandon &nbsp;</label>
- <input type="checkbox" id="onlyExternalTqtcFilter" name="onlyExternalTqtcFilter" disabled> <!-- Initially disabled -->
- <label for="onlyExternalTqtcFilter">Only Externals &nbsp;</label>
- <button id="applyFilters">Apply Filters</button>
- <button id="clearFilters">Clear Filters</button>
+
+ <!-- Group 1: Text Filters (Owner, Repo, Branch) & Branch Tags -->
+ <div class="filter-group">
+ <!-- Owner Filter -->
+ <div class="filter-item tooltip-container">
+ <label for="ownerFilter">Owner:</label>
+ <input class="filter-input" type="text" id="ownerFilter" list="ownerSuggestions" placeholder="Filter by owner email">
+ <datalist id="ownerSuggestions"></datalist>
+ <span class="tooltip-text">Filter changes by owner's email address (full match, select from suggestions).</span>
+ </div>
+ <!-- Only Mine Filter -->
+ ${isLoggedIn ? `
+ <div class="filter-item tooltip-container" style="padding-top:7px">
+ <input type="checkbox" id="onlyMineFilter" name="onlyMineFilter">
+ <label for="onlyMineFilter" class="filter-checkbox-label">Only Mine</label>
+ <span class="tooltip-text">Show only changes owned by you.</span>
+ </div>
+ ` : ''}
+ <!-- Repo Filter -->
+ <div class="filter-item tooltip-container">
+ <label for="repoFilter">Repo:</label>
+ <input class="filter-input" type="text" id="repoFilter" list="repoSuggestions" placeholder="Filter by repository">
+ <datalist id="repoSuggestions"></datalist>
+ <span class="tooltip-text">Filter changes by repository name (prefix or full match).</span>
+ </div>
+ <!-- Branch Filter Wrapper -->
+ <div class="branch-filter-wrapper">
+ <!-- Branch Input/Button -->
+ <div class="filter-item tooltip-container">
+ <label for="branchFilterInput">Branch:</label>
+ <input class="filter-input" type="text" id="branchFilterInput" placeholder="Add branch filter">
+ <button id="addBranchFilterButton">Add</button>
+ <span class="tooltip-text">Enter a branch name (e.g., dev, 6.5) and click 'Add' to filter by specific branches.</span>
+ </div>
+ <div class="branch-tags-container" id="branchFilterTagsContainer">
+ <!-- Selected branch tags will be rendered here -->
+ </div>
+ </div>
+ </div>
+
+ <!-- Group 2: Status Checkboxes -->
+ <div class="filter-group">
+ <!-- Only Stuck Filter -->
+ <div class="filter-item tooltip-container">
+ <input type="checkbox" id="onlyStuckFilter" name="onlyStuckFilter">
+ <label for="onlyStuckFilter" class="filter-checkbox-label">Only Stuck</label>
+ <span class="tooltip-text">Show only changes that are not yet merged or actively integrating. (Automatically enabled when filtering by branch).</span>
+ </div>
+ <!-- Exclude Abandon Filter -->
+ <div class="filter-item tooltip-container">
+ <input type="checkbox" id="exclAbandonFilter" name="exclAbandonFilter">
+ <label for="exclAbandonFilter" class="filter-checkbox-label">Exclude Abandon</label>
+ <span class="tooltip-text">Hide changes where the only unmerged branch targets are abandoned, or depend directly on an abandonded change. Requires 'Only Stuck' to be enabled.</span>
+ </div>
+ <!-- Only Externals Filter -->
+ <div class="filter-item tooltip-container">
+ <input type="checkbox" id="onlyExternalTqtcFilter" name="onlyExternalTqtcFilter" disabled> <!-- Initially disabled -->
+ <label for="onlyExternalTqtcFilter" class="filter-checkbox-label">Only Externals</label>
+ <span class="tooltip-text">Show only changes originating from external contributors (non-TQTC). Requires 'Only Stuck' to be enabled.</span>
+ </div>
+ </div>
+
+ <!-- Group 3: Actions -->
+ <div class="filter-group">
+ <button id="clearFilters" class="tooltip-container">
+ Clear All Filters
+ <span class="tooltip-text">Remove all active filters and reset the view.</span>
+ </button>
+ </div>
+
</div>
<div id="loginMessage" class="error" hidden>This page requires login.</div>
@@ -156,8 +330,12 @@ Gerrit.install(plugin => {
this.onlyStuckFilterCheckbox = this.shadowRoot.getElementById('onlyStuckFilter');
this.exclAbandonFilterCheckbox = this.shadowRoot.getElementById('exclAbandonFilter');
this.onlyExternalTqtcFilterCheckbox = this.shadowRoot.getElementById('onlyExternalTqtcFilter');
+ this.branchFilterInput = this.shadowRoot.getElementById('branchFilterInput');
+ this.addBranchFilterButton = this.shadowRoot.getElementById('addBranchFilterButton');
+ this.branchFilterTagsContainer = this.shadowRoot.getElementById('branchFilterTagsContainer');
- this.applyFiltersButton?.addEventListener('click', () => this.applyFilters());
+ // Remove explicit apply button listener, apply on change/add/remove
+ // this.applyFiltersButton?.addEventListener('click', () => this.applyFilters());
this.clearFiltersButton?.addEventListener('click', () => this.clearFilters());
this.onlyStuckFilterCheckbox?.addEventListener('change', () => this.handleOnlyStuckFilterChange());
this.exclAbandonFilterCheckbox?.addEventListener('change', () => this.handleExclAbandonFilterChange());
@@ -186,12 +364,85 @@ Gerrit.install(plugin => {
this.debouncedFetchRepos.cancel();
this.applyFilters();
});
+
+ // Branch filter listeners
+ this.addBranchFilterButton?.addEventListener('click', () => this.addBranchFilter());
+ this.branchFilterInput?.addEventListener('keypress', (e) => {
+ if (e.key === 'Enter') {
+ this.addBranchFilter();
+ }
+ });
+ this.branchFilterTagsContainer?.addEventListener('click', (event) => {
+ if (event.target.classList.contains('remove-branch-btn')) {
+ const branchToRemove = event.target.dataset.branch;
+ this.removeBranchFilter(branchToRemove);
+ }
+ });
+
this.shadowRoot.getElementById('prevPage')?.addEventListener('click', () => this.handlePageChange(-1));
this.shadowRoot.getElementById('nextPage')?.addEventListener('click', () => this.handlePageChange(1));
}
- // Updates the enabled/checked state of dependent checkboxes based on the "Only Stuck" state
+ addBranchFilter() {
+ const branchName = this.branchFilterInput.value.trim();
+ if (branchName && !this.selectedBranches.has(branchName)) {
+ this.selectedBranches.add(branchName);
+ this.branchFilterInput.value = '';
+ this._renderBranchTags(); // Update tags UI
+ this.updateDependentCheckboxStates(); // Update "Only Stuck" state *before* applying filters
+ this.applyFilters(); // Re-fetch data
+ } else if (!branchName) {
+ console.log("Branch name cannot be empty.");
+ } else {
+ console.log(`Branch "${branchName}" is already added.`);
+ }
+ }
+
+ removeBranchFilter(branchName) {
+ if (this.selectedBranches.has(branchName)) {
+ this.selectedBranches.delete(branchName);
+ this._renderBranchTags(); // Update tags UI
+ this.updateDependentCheckboxStates(); // Update "Only Stuck" state *before* applying filters
+ this.applyFilters(); // Re-fetch data
+ }
+ }
+
+ _renderBranchTags() {
+ this.branchFilterTagsContainer.innerHTML = '';
+ this.selectedBranches.forEach(branch => {
+ const tag = document.createElement('span');
+ tag.classList.add('branch-tag');
+ tag.textContent = branch;
+
+ const removeBtn = document.createElement('button');
+ removeBtn.classList.add('remove-branch-btn');
+ removeBtn.textContent = '×'; // Multiplication sign as 'x'
+ removeBtn.dataset.branch = branch;
+ removeBtn.title = `Remove filter for branch: ${branch}`;
+
+ tag.appendChild(removeBtn);
+ this.branchFilterTagsContainer.appendChild(tag);
+ });
+ }
+
+ // Updates the enabled/checked state of "Only Stuck" based on branch filters,
+ // and the state of dependent checkboxes based on the "Only Stuck" state.
updateDependentCheckboxStates() {
+ const hasBranchFilters = this.selectedBranches.size > 0;
+
+ if (hasBranchFilters) {
+ // Force "Only Stuck" on and disable it when branch filters are active
+ this.onlyStuckFilterCheckbox.checked = true;
+ this.onlyStuckFilterCheckbox.disabled = true;
+ this.currentFilters.onlyStuck = true;
+ } else {
+ // Enable "Only Stuck" if no branch filters are active
+ this.onlyStuckFilterCheckbox.disabled = false;
+ // Restore checked state based on currentFilters (which might be from URL or prior user action)
+ this.onlyStuckFilterCheckbox.checked = this.currentFilters.onlyStuck;
+ }
+
+ // Use the potentially forced state of isStuckChecked
const isStuckChecked = this.onlyStuckFilterCheckbox.checked;
// Handle "Exclude Abandon" checkbox
@@ -199,18 +450,30 @@ Gerrit.install(plugin => {
if (!isStuckChecked) {
this.exclAbandonFilterCheckbox.checked = false;
this.currentFilters.exclAbandon = false;
+ } else {
+ // If stuck is checked, ensure the checkbox reflects the filter state
+ // (it might have been loaded from URL or set by user before branches were added)
+ this.exclAbandonFilterCheckbox.checked = this.currentFilters.exclAbandon;
}
+
// Handle "Only Externals" checkbox
const shouldTqtcBeEnabled = isStuckChecked;
this.onlyExternalTqtcFilterCheckbox.disabled = !shouldTqtcBeEnabled;
if (!shouldTqtcBeEnabled) {
this.onlyExternalTqtcFilterCheckbox.checked = false;
this.currentFilters.onlyExternalTqtc = false;
+ } else {
+ // If stuck is checked, ensure the checkbox reflects the filter state
+ this.onlyExternalTqtcFilterCheckbox.checked = this.currentFilters.onlyExternalTqtc;
}
}
handleOnlyStuckFilterChange() {
+ // Prevent manual changes if the checkbox is disabled (e.g., by branch filter)
+ if (this.onlyStuckFilterCheckbox.disabled) {
+ return;
+ }
this.currentFilters.onlyStuck = this.onlyStuckFilterCheckbox.checked;
this.updateDependentCheckboxStates(); // Update dependent checkboxes
this.applyFilters();
@@ -239,16 +502,22 @@ Gerrit.install(plugin => {
this.ownerFilterInput.value = this.currentFilters.owner;
this.repoFilterInput.value = this.currentFilters.repo;
this.onlyStuckFilterCheckbox.checked = this.currentFilters.onlyStuck;
- this.exclAbandonFilterCheckbox.checked = this.currentFilters.exclAbandon;
- this.onlyExternalTqtcFilterCheckbox.checked = this.currentFilters.onlyExternalTqtc;
+ // Note: exclAbandon and onlyExternalTqtc are handled by updateDependentCheckboxStates
+
// Set initial enabled/disabled states and potentially correct checked states
this.updateDependentCheckboxStates();
+ // Ensure the actual checked state matches the filter state after dependency logic
+ this.exclAbandonFilterCheckbox.checked = this.currentFilters.exclAbandon && !this.exclAbandonFilterCheckbox.disabled;
+ this.onlyExternalTqtcFilterCheckbox.checked = this.currentFilters.onlyExternalTqtc && !this.onlyExternalTqtcFilterCheckbox.disabled;
+
// Handle 'Only Mine' checkbox state based on loaded owner filter
if (this.onlyMineFilterCheckbox) {
this.onlyMineFilterCheckbox.checked = !!this.currentFilters.owner;
}
+ // Render initial branch tags
+ this._renderBranchTags();
// Fetch data using initial filters (potentially from URL)
this.fetchData();
// Add listener for browser back/forward navigation
@@ -268,18 +537,22 @@ Gerrit.install(plugin => {
repo: urlParams.get('repo') || '',
onlyStuck: urlParams.get('onlyStuck') === 'true',
exclAbandon: urlParams.get('exclAbandon') === 'true',
- onlyExternalTqtc: urlParams.get('onlyExternalTqtc') === 'true'
+ onlyExternalTqtc: urlParams.get('onlyExternalTqtc') === 'true',
+ branches: urlParams.get('branches') ? urlParams.get('branches').split(',') : []
};
this.currentPage = parseInt(urlParams.get('page') || '1', 10);
+ this.selectedBranches = new Set(this.currentFilters.branches); // Update selected branches set
// Update UI elements to reflect the state from the URL
this.ownerFilterInput.value = this.currentFilters.owner;
this.repoFilterInput.value = this.currentFilters.repo;
this.onlyStuckFilterCheckbox.checked = this.currentFilters.onlyStuck;
- this.exclAbandonFilterCheckbox.checked = this.currentFilters.exclAbandon;
- this.onlyExternalTqtcFilterCheckbox.checked = this.currentFilters.onlyExternalTqtc;
// Restore enabled/disabled states and potentially correct checked states
this.updateDependentCheckboxStates();
+ // Ensure the actual checked state matches the filter state after dependency logic
+ this.exclAbandonFilterCheckbox.checked = this.currentFilters.exclAbandon && !this.exclAbandonFilterCheckbox.disabled;
+ this.onlyExternalTqtcFilterCheckbox.checked = this.currentFilters.onlyExternalTqtc && !this.onlyExternalTqtcFilterCheckbox.disabled;
+
if (this.onlyMineFilterCheckbox) {
// If owner matches logged-in user's email (fetched elsewhere), check 'Only Mine'
@@ -288,6 +561,8 @@ Gerrit.install(plugin => {
}
}
+ // Re-render branch tags
+ this._renderBranchTags();
// Re-fetch data based on the restored state
this.fetchData(false); // Pass false to prevent pushing state again
}
@@ -435,6 +710,9 @@ Gerrit.install(plugin => {
if (this.currentFilters.onlyStuck) params.set('onlyStuck', 'true');
if (this.currentFilters.exclAbandon) params.set('exclAbandon', 'true');
if (this.currentFilters.onlyExternalTqtc) params.set('onlyExternalTqtc', 'true');
+ if (this.selectedBranches.size > 0) {
+ params.set('branches', Array.from(this.selectedBranches).join(','));
+ }
params.set('page', this.currentPage);
const response = await fetch(`/config/server/getdashboard?${params.toString()}`)
@@ -469,6 +747,9 @@ Gerrit.install(plugin => {
if (this.currentFilters.onlyStuck) params.set('onlyStuck', 'true');
if (this.currentFilters.exclAbandon) params.set('exclAbandon', 'true');
if (this.currentFilters.onlyExternalTqtc) params.set('onlyExternalTqtc', 'true');
+ if (this.selectedBranches.size > 0) {
+ params.set('branches', Array.from(this.selectedBranches).join(','));
+ }
if (this.currentPage > 1) params.set('page', this.currentPage); // Only add page if > 1
const newUrl = `${window.location.pathname}?${params.toString()}`;
@@ -500,7 +781,10 @@ Gerrit.install(plugin => {
}
this.currentFilters.onlyStuck = false;
this.currentFilters.exclAbandon = false;
- this.currentFilters.onlyExternalTqtc = false; // Reset filter states
+ this.currentFilters.onlyExternalTqtc = false;
+ this.selectedBranches.clear();
+ this.branchFilterInput.value = '';
+ this._renderBranchTags(); // Clear branch tags UI
this.updateDependentCheckboxStates(); // Ensure dependent checkboxes are disabled and unchecked
this.repoSuggestionsDatalist.innerHTML = '';
this.ownerSuggestionsDatalist.innerHTML = '';
@@ -523,6 +807,7 @@ Gerrit.install(plugin => {
const errorEl = this.shadowRoot.querySelector('.error'); // General error display
const tableEl = this.shadowRoot.querySelector('table');
const paginationEl = this.shadowRoot.querySelector('.pagination');
+ const branchTagsContainerEl = this.shadowRoot.getElementById('branchFilterTagsContainer');
const currentPageEl = this.shadowRoot.getElementById('currentPage');
const totalPagesEl = this.shadowRoot.getElementById('totalPages');
const prevPageButton = this.shadowRoot.getElementById('prevPage');
@@ -543,6 +828,10 @@ Gerrit.install(plugin => {
// Proceed with normal rendering if logged in
loginMessageEl.hidden = true; // Ensure login message is hidden
if (filterContainerEl) filterContainerEl.hidden = false; // Show filters
+ // Hide branch tags container if it's empty using display style
+ if (branchTagsContainerEl) {
+ branchTagsContainerEl.style.display = this.selectedBranches.size === 0 ? 'none' : 'flex';
+ }
loadingEl.hidden = !this.loading;
errorEl.hidden = !this.errorMessage; // Show/hide general error based on state
@@ -555,6 +844,9 @@ Gerrit.install(plugin => {
return; // Stop rendering if there's an error
}
+ // Render branch tags (might be redundant if called elsewhere, but ensures consistency)
+ this._renderBranchTags();
+
currentPageEl.textContent = this.currentPage;
totalPagesEl.textContent = this.totalPages;
prevPageButton.disabled = this.currentPage === 1;
@@ -567,7 +859,13 @@ Gerrit.install(plugin => {
<th>Repo</th>
<th>Owner Email</th>
<th>Merged Date</th>
- ${Array.from(this.branchColumns).map(b => `<th>${b}</th>`).join('')}
+ ${Array.from(this.branchColumns).map(b => `
+ <th>
+ ${b}
+ <span class="branch-filter-icon" data-branch="${b}" title="Filter by this branch">
+ &#x1F50D;
+ </span>
+ </th>`).join('')}
`;
const tbody = this.shadowRoot.querySelector('tbody');
@@ -628,6 +926,19 @@ Gerrit.install(plugin => {
this.applyFilters();
});
});
+
+ // Add listeners for branch header filter icons
+ thead.querySelectorAll('.branch-filter-icon').forEach(icon => {
+ icon.addEventListener('click', (event) => {
+ const branchName = event.target.dataset.branch;
+ if (branchName && !this.selectedBranches.has(branchName)) {
+ this.selectedBranches.add(branchName);
+ this._renderBranchTags(); // Update tags UI immediately
+ this.updateDependentCheckboxStates(); // Update "Only Stuck" state
+ this.applyFilters(); // Re-fetch data
+ }
+ });
+ });
}
}