aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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
+ }
+ });
+ });
}
}