# HG changeset patch # User Mike Becker # Date 1741708890 -3600 # Node ID 576bd8ac4d96ffa7a46f99f6dc47b9534fd25548 # Parent 749d71470b0f3f3b73964858c650f754cd3f067d dynamically load suggestions for related issues resolves #616 diff -r 749d71470b0f -r 576bd8ac4d96 src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt --- a/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Tue Mar 11 14:01:48 2025 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Tue Mar 11 17:01:30 2025 +0100 @@ -88,10 +88,9 @@ fun listIssues(includeDone: Boolean): List /** - * Lists issues for the specified [project]. - * This list will NOT include variant data and is intended for simple lookups. + * Lists issue IDs for the specified [project]. */ - fun listIssues(project: Project): List + fun listIssueIds(project: Project): List /** * Search for issue by subject and id using the case-insensitive [query]. diff -r 749d71470b0f -r 576bd8ac4d96 src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt --- a/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Tue Mar 11 14:01:48 2025 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Tue Mar 11 17:01:30 2025 +0100 @@ -748,11 +748,10 @@ } } - override fun listIssues(project: Project): List = - withStatement("$issueQuery where i.project = ?") { + override fun listIssueIds(project: Project): List = + withStatement("select issueid from lpit_issue where project = ?") { setInt(1, project.id) - queryAll { it.extractIssue() } - // do not add variant data here - not needed in this use case! + queryAll { it.getInt(1) } } override fun searchIssues(query: String, project: Project?): List { diff -r 749d71470b0f -r 576bd8ac4d96 src/main/kotlin/de/uapcore/lightpit/logic/IssueLogic.kt --- a/src/main/kotlin/de/uapcore/lightpit/logic/IssueLogic.kt Tue Mar 11 14:01:48 2025 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/logic/IssueLogic.kt Tue Mar 11 17:01:30 2025 +0100 @@ -154,7 +154,6 @@ view = IssueDetailView( issue, comments, - dao.listIssues(issue.project), dao.listIssueRelations(issue), dao.listCommitRefs(issue), relationError, diff -r 749d71470b0f -r 576bd8ac4d96 src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt --- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Tue Mar 11 14:01:48 2025 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Tue Mar 11 17:01:30 2025 +0100 @@ -187,8 +187,8 @@ return } - // obtain the list of issues for this project to filter cross-project references - val knownIds = dao.listIssues(path.project).map { it.id } + // obtain the list of issue IDs for this project to filter cross-project references + val knownIds = dao.listIssueIds(path.project) // read the provided commit log and merge only the refs that relate issues from the current project dao.mergeCommitRefs(parseCommitRefs(http.body).filter { knownIds.contains(it.issueId) }) diff -r 749d71470b0f -r 576bd8ac4d96 src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt --- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt Tue Mar 11 14:01:48 2025 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt Tue Mar 11 17:01:30 2025 +0100 @@ -119,7 +119,6 @@ class IssueDetailView( val issue: Issue, val comments: List, - projectIssues: List, val currentRelations: List, commitRefs: List, /** @@ -129,7 +128,6 @@ val pathInfos: PathInfos? = null ) : View() { val relationTypes = RelationType.entries - val linkableIssues = projectIssues.filterNot { it.id == issue.id } val commitLinks: List private val parser: Parser diff -r 749d71470b0f -r 576bd8ac4d96 src/main/webapp/WEB-INF/jsp/issue-view.jsp --- a/src/main/webapp/WEB-INF/jsp/issue-view.jsp Tue Mar 11 14:01:48 2025 +0100 +++ b/src/main/webapp/WEB-INF/jsp/issue-view.jsp Tue Mar 11 17:01:30 2025 +0100 @@ -252,12 +252,8 @@ - - - - - - + + diff -r 749d71470b0f -r 576bd8ac4d96 src/main/webapp/WEB-INF/jsp/site.jsp --- a/src/main/webapp/WEB-INF/jsp/site.jsp Tue Mar 11 14:01:48 2025 +0100 +++ b/src/main/webapp/WEB-INF/jsp/site.jsp Tue Mar 11 17:01:30 2025 +0100 @@ -31,7 +31,7 @@ <%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%-- Version suffix for forcing browsers to update the CSS / JS files --%> - + <%-- Make the base href easily available at request scope --%> diff -r 749d71470b0f -r 576bd8ac4d96 src/main/webapp/issue-editor.js --- a/src/main/webapp/issue-editor.js Tue Mar 11 14:01:48 2025 +0100 +++ b/src/main/webapp/issue-editor.js Tue Mar 11 17:01:30 2025 +0100 @@ -56,4 +56,8 @@ } } -window.addEventListener("load", (_) => toggleVariantStatus()); +window.addEventListener("load", () => { + toggleVariantStatus(); + const project = document.getElementById('linkable-issues').dataset.project; + configureSearchBox('linkable-issues', project); +}); diff -r 749d71470b0f -r 576bd8ac4d96 src/main/webapp/issue-search.js --- a/src/main/webapp/issue-search.js Tue Mar 11 14:01:48 2025 +0100 +++ b/src/main/webapp/issue-search.js Tue Mar 11 17:01:30 2025 +0100 @@ -25,21 +25,20 @@ function debounce(func){ let timer; - return () => { + return (...args) => { clearTimeout(timer); - timer = setTimeout(() => { func.apply(this); }, 300); + timer = setTimeout(() => { func.apply(this, args); }, 300); }; } -const issueSearch = debounce(() => issueSearchImpl()) let searchBoxOldContent = ''; -function issueSearchImpl() { - const searchBox = document.getElementById('search-box'); +function issueSearchImpl(elementId, project) { + const searchBox = document.getElementById(elementId); if (searchBoxOldContent !== searchBox.value) { searchBoxOldContent = searchBox.value; const req = new XMLHttpRequest(); req.addEventListener("load", (evt) => { - const dataList = document.getElementById('search-box-list'); + const dataList = document.getElementById(elementId+'-list'); dataList.innerHTML = ''; JSON.parse(evt.target.responseText).forEach(function(item){ const option = document.createElement('option'); @@ -47,27 +46,36 @@ dataList.appendChild(option); }); }); - req.open("GET", baseHref+'issues/search?q='+encodeURIComponent(searchBox.value)); + let url = baseHref+'issues/search?q='+encodeURIComponent(searchBox.value); + if (project > 0) url+='&p='+project; + req.open("GET", url); req.send(); } } +const issueSearch = debounce((elementId, project = 0) => issueSearchImpl(elementId, project)) + +function configureSearchBox(elementId, project, navigateOnEnter = false) { + const searchBox = document.getElementById(elementId); + searchBox.addEventListener('change', () => issueSearch(elementId, project)); + if (navigateOnEnter) { + searchBox.addEventListener('keyup', (evt) => { + if (evt.code === 'Enter' || evt.code === 'NumpadEnter') { + let stext = searchBox.value.trim() + if (stext.length === 0) return; + if (stext[0] === '#') stext = stext.substring(1); + const snum = Number.parseInt(stext.split(' ')[0], 10); + if (snum !== Number.NaN) { + location.assign(baseHref + 'issues/' + snum + '?in_project=true'); + } + } else { + issueSearch(elementId, project); + } + }) + } else { + searchBox.addEventListener('keyup', () => issueSearch(elementId, project)); + } +} -// configure the search box -window.addEventListener('load', () => { - const searchBox = document.getElementById('search-box'); - searchBox.addEventListener('change', () => issueSearch()) - searchBox.addEventListener('keyup', (evt) => { - if (evt.code === 'Enter' || evt.code === 'NumpadEnter') { - let stext = searchBox.value.trim() - if (stext.length === 0) return; - if (stext[0] === '#') stext = stext.substring(1); - const snum = Number.parseInt(stext.split(' ')[0], 10); - if (snum !== Number.NaN) { - location.assign(baseHref+'issues/'+snum+'?in_project=true'); - } - } else { - issueSearch(); - } - }) -}); +// configure the global search box +window.addEventListener('load', () => configureSearchBox('search-box', 0, true));