# HG changeset patch # User Mike Becker # Date 1759750672 -7200 # Node ID 0b59551086f4d7e1d9bd83401367272c67b44525 # Parent 384a98f0220da9b8a89f1773c9724768c702e680 add version planning feature - resolves #446 diff -r 384a98f0220d -r 0b59551086f4 src/main/kotlin/de/uapcore/lightpit/Constants.kt --- a/src/main/kotlin/de/uapcore/lightpit/Constants.kt Mon Oct 06 13:02:12 2025 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/Constants.kt Mon Oct 06 13:37:52 2025 +0200 @@ -29,7 +29,7 @@ /** * A data in yyyy-mm-dd format to identify the release. */ - const val VERSION_DATE = "2025-10-05" + const val VERSION_DATE = "2025-10-06" /** * The path where the JSP files reside. diff -r 384a98f0220d -r 0b59551086f4 src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt --- a/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Mon Oct 06 13:02:12 2025 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Mon Oct 06 13:37:52 2025 +0200 @@ -121,9 +121,12 @@ specificVariant: Boolean, variant: Variant? ): List + fun listIssues(project: Project, includeDone: Boolean) = + listIssues(project, includeDone, false, null, false, null, false, null) fun findIssue(id: Int): Issue? fun insertIssue(issue: Issue): Int fun updateIssue(issue: Issue) + fun updateIssueResolvedVersion(issue: Issue, version: Version) fun listComments(issue: Issue): List fun findComment(id: Int): IssueComment? diff -r 384a98f0220d -r 0b59551086f4 src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt --- a/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Mon Oct 06 13:02:12 2025 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Mon Oct 06 13:37:52 2025 +0200 @@ -977,6 +977,15 @@ updateVariantStatus(issue.id, issue.variantStatus) } + override fun updateIssueResolvedVersion(issue: Issue, version: Version) { + issue.resolved = version // leave the DTO in a consistent state + withStatement("update lpit_issue set updated = now(), resolved = ? where issueid = ?") { + setInt(1, version.id) + setInt(2, issue.id) + executeUpdate() + } + } + override fun listCommitRefs(issue: Issue): List = withStatement("select commit_hash, commit_brief, commit_time from lpit_commit_ref where issueid = ? order by commit_time") { setInt(1, issue.id) diff -r 384a98f0220d -r 0b59551086f4 src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt --- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Mon Oct 06 13:02:12 2025 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Mon Oct 06 13:37:52 2025 +0200 @@ -48,6 +48,8 @@ get("/%project/versions/", this::versions) get("/%project/versions/%version/edit", this::versionForm) + get("/%project/versions/%version/plan", this::versionPlanning) + post("/%project/versions/%version/plan", this::versionPlanningCommit) get("/%project/versions/-/create", this::versionForm) post("/%project/versions/-/commit", this::versionCommit) @@ -266,6 +268,52 @@ http.renderCommit(http.sanitizeReferer(http.param("returnLink")) ?: "projects/${project.node}/versions/") } + private fun versionPlanning(http: HttpRequest, dao: DataAccessObject) { + withPathInfo(http, dao)?.let { path -> + val project = path.project + val versionInfo = path.versionInfo as? OptionalPathInfo.Specific + if (versionInfo == null || versionInfo.elem.status.isReleased) { + http.response.sendError(404) + return + } + val filter = IssueFilter(http, dao) + val issues = dao.listIssues(project, filter.includeDone) + .sortedWith(IssueSorter(filter.sortPrimary, filter.sortSecondary, filter.sortTertiary)) + .filter(issueFilterFunction(filter, dao, http.user?.username ?: "", project)) + + with(http) { + pageTitle = "${project.name} - ${i18n("version.planning.title")} ${versionInfo.elem.name}" + view = VersionPlanning(path.projectInfo, versionInfo.elem, issues, filter) + navigationMenu = projectNavMenu(dao.listProjects(), path) + styleSheets = listOf("projects") + javascript = "issue-overview" + render("version-plan") + } + } + } + + private fun versionPlanningCommit(http: HttpRequest, dao: DataAccessObject) { + withPathInfo(http, dao)?.let { path -> + val project = path.project + val versionInfo = path.versionInfo as? OptionalPathInfo.Specific + if (versionInfo == null || versionInfo.elem.status.isReleased) { + http.response.sendError(404) + return + } + val version = versionInfo.elem + val issueIds = http.paramArray("issueIds").mapNotNull { it.toIntOrNull() } + + for (issueId in issueIds) { + dao.findIssue(issueId)?.let { issue -> + dao.updateIssueResolvedVersion(issue, version) + dao.insertHistoryEvent(http.user, issue) + } + } + + http.renderCommit("projects/${project.node}/versions/${version.node}/plan") + } + } + private fun components(http: HttpRequest, dao: DataAccessObject) { withPathInfo(http, dao)?.let { path -> with(http) { diff -r 384a98f0220d -r 0b59551086f4 src/main/kotlin/de/uapcore/lightpit/viewmodel/Versions.kt --- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Versions.kt Mon Oct 06 13:02:12 2025 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Versions.kt Mon Oct 06 13:37:52 2025 +0200 @@ -74,3 +74,18 @@ ) : EditView() { val versionStatus = VersionStatus.entries } + +class VersionPlanning( + val projectInfo: ProjectInfo, + val version: Version, + val issues: List, + val filter: IssueFilter +) : View() { + val versionInfo = VersionInfo(version, issues) + val issueSummary = IssueSummary() + val candidateIssues = issues.filter { it.resolved != version } + + init { + issues.forEach(issueSummary::add) + } +} diff -r 384a98f0220d -r 0b59551086f4 src/main/resources/localization/strings.properties --- a/src/main/resources/localization/strings.properties Mon Oct 06 13:02:12 2025 +0200 +++ b/src/main/resources/localization/strings.properties Mon Oct 06 13:37:52 2025 +0200 @@ -50,6 +50,7 @@ button.variant.edit=Edit Variant button.version.create=New Version button.version.edit=Edit Version +button.version.plan=Plan Version button.whats-new=Show Changelog commit.redirect-link=If redirection does not work, click the following link: commit.success=Operation successful - you will be redirected in a second. @@ -204,6 +205,8 @@ version.status.Unreleased=Unreleased version.status=Status version=Version +version.planning.title=Planning Version +version.planning.open=Select Issues whats-new.info = A new version of LightPIT has been released. Do you want to check the release notes? issue.filter.sort.primary=Order by issue.filter.sort.secondary=then by diff -r 384a98f0220d -r 0b59551086f4 src/main/resources/localization/strings_de.properties --- a/src/main/resources/localization/strings_de.properties Mon Oct 06 13:02:12 2025 +0200 +++ b/src/main/resources/localization/strings_de.properties Mon Oct 06 13:37:52 2025 +0200 @@ -50,6 +50,7 @@ button.variant.edit=Variante Bearbeiten button.version.create=Neue Version button.version.edit=Version Bearbeiten +button.version.plan=Version Planen button.whats-new=Versionshinweise \u00d6ffnen commit.redirect-link=Falls die Weiterleitung nicht klappt, klicken Sie bitte hier: commit.success=Operation erfolgreich - Sie werden jeden Moment weitergeleitet. @@ -204,6 +205,8 @@ version.status.Unreleased=Unver\u00f6ffentlicht version.status=Status version=Version +version.planning.title=Plane Version +version.planning.open=Auswahl whats-new.info = Eine neue LightPIT-Version wurde ver\u00f6ffentlicht. Wollen Sie mehr erfahren? issue.filter.sort.primary=Sortiere nach issue.filter.sort.secondary=dann nach diff -r 384a98f0220d -r 0b59551086f4 src/main/webapp/WEB-INF/changelogs/changelog-de.jspf --- a/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf Mon Oct 06 13:02:12 2025 +0200 +++ b/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf Mon Oct 06 13:37:52 2025 +0200 @@ -27,6 +27,7 @@

Version 1.6.0 (Vorschau)

    +
  • Neues Feature zur Planung von Versionen hinzugefügt.
  • Pop-Up hinzugefügt, das über eine neue LightPIT-Version informiert.
  • Neue Schaltflächen zur Vorgangsansicht hinzugefügt, mit denen der Status des Vorgangs schnell geändert werden kann.
  • "In Projekt Öffnen" Schaltfläche zur (globalen) Vorgangsansicht hinzugefügt.
  • diff -r 384a98f0220d -r 0b59551086f4 src/main/webapp/WEB-INF/changelogs/changelog.jspf --- a/src/main/webapp/WEB-INF/changelogs/changelog.jspf Mon Oct 06 13:02:12 2025 +0200 +++ b/src/main/webapp/WEB-INF/changelogs/changelog.jspf Mon Oct 06 13:37:52 2025 +0200 @@ -27,6 +27,7 @@

    Version 1.6.0 (preview)

      +
    • Add new version planning UI.
    • Add popup informing about a new LightPIT release.
    • Add convenience buttons to the issue view to quickly change the issue status.
    • Add convenience OPEN IN PROJECT button to the global issue view.
    • diff -r 384a98f0220d -r 0b59551086f4 src/main/webapp/WEB-INF/jsp/project-details.jsp --- a/src/main/webapp/WEB-INF/jsp/project-details.jsp Mon Oct 06 13:02:12 2025 +0200 +++ b/src/main/webapp/WEB-INF/jsp/project-details.jsp Mon Oct 06 13:37:52 2025 +0200 @@ -39,6 +39,9 @@
      + + + diff -r 384a98f0220d -r 0b59551086f4 src/main/webapp/WEB-INF/jsp/version-plan.jsp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/webapp/WEB-INF/jsp/version-plan.jsp Mon Oct 06 13:37:52 2025 +0200 @@ -0,0 +1,86 @@ +<%-- +DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + +Copyright 2025 Mike Becker. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--%> +<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> +<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> + + + + + + +<%@include file="../jspf/project-header.jspf"%> + +

      + + +<%@include file="../jspf/issue-summary.jspf"%> + +

      + +

      +<%@include file="../jspf/issue-filter.jspf"%> + + + + + + + + +

      + +
      +
      + + + + +
      + + + +<%@include file="../jspf/issue-list.jspf"%> + + + +

      + + +<%@include file="../jspf/issue-summary.jspf"%> + + + <%@include file="../jspf/issue-list.jspf"%> + +<% // corner case: a version was released before and is now open again for planning %> + + +

      + + <%@include file="../jspf/issue-summary.jspf"%> + <%@include file="../jspf/issue-list.jspf"%> +
      diff -r 384a98f0220d -r 0b59551086f4 src/main/webapp/WEB-INF/jspf/issue-list.jspf --- a/src/main/webapp/WEB-INF/jspf/issue-list.jspf Mon Oct 06 13:02:12 2025 +0200 +++ b/src/main/webapp/WEB-INF/jspf/issue-list.jspf Mon Oct 06 13:37:52 2025 +0200 @@ -1,6 +1,8 @@ <%-- issues: List issuesHref: String +openIssuesInTabs: boolean +showCheckbox: boolean showComponentInfo: boolean showVersionInfo: boolean showVariantInfo: boolean @@ -8,6 +10,9 @@ --%> + + + @@ -18,6 +23,9 @@ + + + @@ -30,6 +38,11 @@ + + +
      + + @@ -39,7 +52,7 @@ - + target="_blank" > #${issue.id} -