Wed, 18 Aug 2021 14:57:45 +0200
#159 adds release and eol dates
--- a/setup/postgres/psql_create_tables.sql Wed Aug 18 12:47:32 2021 +0200 +++ b/setup/postgres/psql_create_tables.sql Wed Aug 18 14:57:45 2021 +0200 @@ -36,7 +36,9 @@ name text not null, node text not null, ordinal integer not null default 0, - status version_status not null default 'Future' + status version_status not null default 'Future', + release date, + eol date ); create unique index lpit_version_node_unique on lpit_version (project, node);
--- a/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Wed Aug 18 12:47:32 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Wed Aug 18 14:57:45 2021 +0200 @@ -33,6 +33,7 @@ import javax.servlet.http.HttpServletResponse import javax.servlet.http.HttpSession import kotlin.math.min +import java.sql.Date as SqlDate typealias MappingMethod = (HttpRequest, DataAccessObject) -> Unit typealias PathParameters = Map<String, String> @@ -289,3 +290,18 @@ } } +// <editor-fold desc="Validators"> + +fun dateOptValidator(input: String?): ValidationResult<SqlDate?> { + return if (input.isNullOrBlank()) { + ValidatedValue(null) + } else { + try { + ValidatedValue(SqlDate.valueOf(input)) + } catch (ignored: IllegalArgumentException) { + ValidationError("validation.date.format") + } + } +} + +// </editor-fold>
--- a/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Wed Aug 18 12:47:32 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Wed Aug 18 14:57:45 2021 +0200 @@ -129,17 +129,19 @@ executeUpdate() } } -//</editor-fold> + //</editor-fold> //<editor-fold desc="Version"> //language=SQL - private val versionQuery = "select versionid, project, name, node, ordinal, status from lpit_version" + private val versionQuery = "select versionid, project, name, node, ordinal, status, release, eol from lpit_version" private fun ResultSet.extractVersion() = Version(getInt("versionid"), getInt("project")).apply { name = getString("name") node = getString("node") ordinal = getInt("ordinal") + release = getDate("release") + eol = getDate("eol") status = getEnum("status") } @@ -171,7 +173,7 @@ from lpit_version v left join issues using (versionid) ) - select v.versionid, project, name, node, ordinal, status, + select v.versionid, project, name, node, ordinal, status, release, eol, ro.total as resolved_open, ra.total as resolved_active, rd.total as resolved_done, ao.total as affected_open, aa.total as affected_active, ad.total as affected_done from lpit_version v @@ -212,13 +214,15 @@ } override fun insertVersion(version: Version) { - withStatement("insert into lpit_version (name, node, ordinal, status, project) values (?, ?, ?, ?::version_status, ?)") { + withStatement("insert into lpit_version (name, node, ordinal, status, project, release, eol) values (?, ?, ?, ?::version_status, ?, ?, ?)") { with(version) { setStringSafe(1, name) setStringSafe(2, node) setInt(3, ordinal) setEnum(4, status) setInt(5, version.projectid) + setDateOrNull(6, version.release) + setDateOrNull(7, version.eol) } executeUpdate() } @@ -226,18 +230,20 @@ } override fun updateVersion(version: Version) { - withStatement("update lpit_version set name = ?, node = ?, ordinal = ?, status = ?::version_status where versionid = ?") { + withStatement("update lpit_version set name = ?, node = ?, ordinal = ?, status = ?::version_status, release=?,eol=? where versionid = ?") { with(version) { setStringSafe(1, name) setStringSafe(2, node) setInt(3, ordinal) setEnum(4, status) - setInt(5, id) + setDateOrNull(5, version.release) + setDateOrNull(6, version.eol) + setInt(7, id) } executeUpdate() } } -//</editor-fold> + //</editor-fold> //<editor-fold desc="Component"> //language=SQL @@ -347,9 +353,9 @@ } } -//</editor-fold> + //</editor-fold> -//<editor-fold desc="Project"> + //<editor-fold desc="Project"> //language=SQL private val projectQuery = @@ -441,9 +447,9 @@ } } -//</editor-fold> + //</editor-fold> -//<editor-fold desc="Issue"> + //<editor-fold desc="Issue"> //language=SQL private val issueQuery = @@ -485,19 +491,24 @@ } //language=SQL - fun versionQuery(table: String) = + val queryAffected = """ - select versionid, project, name, status, ordinal, node - from lpit_version join $table using (versionid) - where issueid = ? - order by ordinal, name + $versionQuery join lpit_issue_affected_version using (versionid) + where issueid = ? order by ordinal, name """.trimIndent() - issue.affectedVersions = withStatement(versionQuery("lpit_issue_affected_version")) { + //language=SQL + val queryResolved = + """ + $versionQuery join lpit_issue_resolved_version using (versionid) + where issueid = ? order by ordinal, name + """.trimIndent() + + issue.affectedVersions = withStatement(queryAffected) { setInt(1, issue.id) queryAll { it.extractVersion() } } - issue.resolvedVersions = withStatement(versionQuery("lpit_issue_resolved_version")) { + issue.resolvedVersions = withStatement(queryResolved) { setInt(1, issue.id) queryAll { it.extractVersion() } } @@ -640,9 +651,9 @@ insertVersionInfo(issue.id, issue) } -//</editor-fold> + //</editor-fold> -//<editor-fold desc="IssueComment"> + //<editor-fold desc="IssueComment"> private fun ResultSet.extractIssueComment() = IssueComment(getInt("commentid"), getInt("issueid")).apply { @@ -693,5 +704,5 @@ } } } -//</editor-fold> + //</editor-fold> } \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/entities/Version.kt Wed Aug 18 12:47:32 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Version.kt Wed Aug 18 14:57:45 2021 +0200 @@ -26,12 +26,22 @@ package de.uapcore.lightpit.entities import de.uapcore.lightpit.types.VersionStatus +import java.sql.Date data class Version(override val id: Int, val projectid: Int) : Entity, HasNode, Comparable<Version> { var name: String = "" override var node = name var ordinal = 0 var status = VersionStatus.Future + var release: Date? = null + var eol: Date? = null + + /** + * If this version is deprecated, this gives the [eol] date, otherwise this gives the [release] date. + * Note that a [release] date may be specified for the actual release in which case in should be + * understood as the planned release date. + */ + val releaseOrEolDate: Date? get() = if (status.isEndOfLife) eol else release override fun compareTo(other: Version): Int { val ord = ordinal.compareTo(other.ordinal)
--- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Wed Aug 18 12:47:32 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Wed Aug 18 14:57:45 2021 +0200 @@ -28,6 +28,7 @@ import de.uapcore.lightpit.AbstractServlet import de.uapcore.lightpit.HttpRequest import de.uapcore.lightpit.dao.DataAccessObject +import de.uapcore.lightpit.dateOptValidator import de.uapcore.lightpit.entities.* import de.uapcore.lightpit.types.IssueCategory import de.uapcore.lightpit.types.IssueStatus @@ -329,12 +330,22 @@ node = http.param("node") ?: "" ordinal = http.param("ordinal")?.toIntOrNull() ?: 0 status = http.param("status")?.let(VersionStatus::valueOf) ?: VersionStatus.Future + // TODO: process error messages + eol = http.param("eol", ::dateOptValidator, null, mutableListOf()) + release = http.param("release", ::dateOptValidator, null, mutableListOf()) // intentional defaults if (node.isBlank()) node = name // sanitizing node = sanitizeNode(node) } + // sanitize eol and release date + if (version.status.isEndOfLife) { + if (version.eol == null) version.eol = Date(System.currentTimeMillis()) + } else if (version.status.isReleased) { + if (version.release == null) version.release = Date(System.currentTimeMillis()) + } + if (id < 0) { dao.insertVersion(version) } else { @@ -548,7 +559,8 @@ else -> dao.findUser(it) } } - eta = http.param("eta")?.let { if (it.isBlank()) null else Date.valueOf(it) } + // TODO: process error messages + eta = http.param("eta", ::dateOptValidator, null, mutableListOf()) affectedVersions = http.paramArray("affected") .mapNotNull { param -> param.toIntOrNull()?.let { Version(it, project.id) } }
--- a/src/main/kotlin/de/uapcore/lightpit/types/VersionStatus.kt Wed Aug 18 12:47:32 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/types/VersionStatus.kt Wed Aug 18 14:57:45 2021 +0200 @@ -29,4 +29,5 @@ Future, Unreleased, Released, LTS, Deprecated; val isReleased get() = this.ordinal >= Released.ordinal + val isEndOfLife get() = this.ordinal >= Deprecated.ordinal } \ No newline at end of file
--- a/src/main/resources/localization/strings.properties Wed Aug 18 12:47:32 2021 +0200 +++ b/src/main/resources/localization/strings.properties Wed Aug 18 14:57:45 2021 +0200 @@ -130,10 +130,13 @@ user.lastname=Last Name user.mail=E-Mail username=User Name +validation.date.format=Illegal date format. validation.username.null=Username is mandatory. validation.username.unique=Username is already taken. +version.eol=End of Life version.latest=Latest Version version.next=Next Version +version.release=Release version.status.Deprecated=Deprecated version.status.Future=Future version.status.LTS=LTS
--- a/src/main/resources/localization/strings_de.properties Wed Aug 18 12:47:32 2021 +0200 +++ b/src/main/resources/localization/strings_de.properties Wed Aug 18 14:57:45 2021 +0200 @@ -129,10 +129,13 @@ user.lastname=Nachname user.mail=E-Mail username=Benutzername +validation.date.format=Datumsformat wird nicht unterst\u00fctzt. validation.username.null=Benutzername ist ein Pflichtfeld. validation.username.unique=Der Benutzername wird bereits verwendet. +version.eol=Supportende version.latest=Neuste Version version.next=N\u00e4chste Version +version.release=Release version.status.Deprecated=Veraltet version.status.Future=Geplant version.status.LTS=Langzeitsupport
--- a/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf Wed Aug 18 12:47:32 2021 +0200 +++ b/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf Wed Aug 18 14:57:45 2021 +0200 @@ -27,6 +27,7 @@ <h3>Version 1.0 (Vorschau)</h3> <ul> + <li>Datum der Veröffentlichung und des Supportendes zu Versionen hinzugefügt.</li> <li>Gesamtanzahl der Kommentare wird nun angezeigt.</li> <li>Spalte für zugewiesener Entwickler zu den Vorgangstabellen hinzugefügt.</li> <li>Installationsanweisungen hinzugefügt.</li>
--- a/src/main/webapp/WEB-INF/changelogs/changelog.jspf Wed Aug 18 12:47:32 2021 +0200 +++ b/src/main/webapp/WEB-INF/changelogs/changelog.jspf Wed Aug 18 14:57:45 2021 +0200 @@ -27,6 +27,7 @@ <h3>Version 1.0 (snapshot)</h3> <ul> + <li>Adds release and end of life dates to versions.</li> <li>Adds the total number of comments to the caption.</li> <li>Adds assignee to the issue overview tables.</li> <li>Adds install instructions.</li>
--- a/src/main/webapp/WEB-INF/jsp/project-details.jsp Wed Aug 18 12:47:32 2021 +0200 +++ b/src/main/webapp/WEB-INF/jsp/project-details.jsp Wed Aug 18 14:57:45 2021 +0200 @@ -69,6 +69,9 @@ <c:set var="versionInfo" value="${viewmodel.versionInfo}"/> <h2> <fmt:message key="version" /> <c:out value="${versionInfo.version.name}" /> - <fmt:message key="version.status.${versionInfo.version.status}"/> + <c:if test="${not empty versionInfo.version.releaseOrEolDate}"> + (<fmt:formatDate type="date" value="${versionInfo.version.releaseOrEolDate}"/>) + </c:if> </h2> <h3><fmt:message key="issues.resolved"/> </h3>
--- a/src/main/webapp/WEB-INF/jsp/site.jsp Wed Aug 18 12:47:32 2021 +0200 +++ b/src/main/webapp/WEB-INF/jsp/site.jsp Wed Aug 18 14:57:45 2021 +0200 @@ -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 --%> -<c:set scope="page" var="versionSuffiuniversex" value="20210812"/> +<c:set scope="page" var="versionSuffiuniversex" value="20210818"/> <%-- Make the base href easily available at request scope --%> <c:set scope="page" var="baseHref" value="${requestScope[Constants.REQ_ATTR_BASE_HREF]}"/>
--- a/src/main/webapp/WEB-INF/jsp/version-form.jsp Wed Aug 18 12:47:32 2021 +0200 +++ b/src/main/webapp/WEB-INF/jsp/version-form.jsp Wed Aug 18 14:57:45 2021 +0200 @@ -47,17 +47,17 @@ </td> </tr> <tr> - <th><fmt:message key="version"/></th> - <td><input name="name" type="text" maxlength="20" required value="<c:out value="${version.name}"/>" /></td> + <th><label for="version-name"><fmt:message key="version"/></label></th> + <td><input id="version-name" name="name" type="text" maxlength="20" required value="<c:out value="${version.name}"/>" /></td> </tr> <tr title="<fmt:message key="node.tooltip"/>"> - <th><fmt:message key="node"/></th> - <td><input name="node" type="text" maxlength="20" value="<c:out value="${version.node}"/>" /></td> + <th><label for="version-node"><fmt:message key="node"/></label></th> + <td><input id="version-node" name="node" type="text" maxlength="20" value="<c:out value="${version.node}"/>" /></td> </tr> <tr> - <th><fmt:message key="version.status"/></th> + <th><label for="version-status"><fmt:message key="version.status"/></label></th> <td> - <select name="status" required> + <select id="version-status" name="status" required> <c:forEach var="elem" items="${viewmodel.versionStatus}"> <option <c:if test="${elem eq version.status}">selected</c:if> value="${elem}"><fmt:message @@ -67,9 +67,21 @@ </td> </tr> <tr title="<fmt:message key="ordinal.tooltip" />"> - <th><fmt:message key="ordinal"/></th> + <th><label for="version-ordinal"><fmt:message key="ordinal"/></label></th> + <td> + <input id="version-ordinal" name="ordinal" type="number" value="${version.ordinal}"/> + </td> + </tr> + <tr> + <th><label for="version-release"><fmt:message key="version.release"/></label></th> <td> - <input name="ordinal" type="number" value="${version.ordinal}"/> + <input id="version-release" name="release" type="date" value="<fmt:formatDate value="${version.release}" pattern="YYYY-MM-dd" />"/> + </td> + </tr> + <tr> + <th><label for="version-eol"><fmt:message key="version.eol"/></label></th> + <td> + <input id="version-eol" name="eol" type="date" value="<fmt:formatDate value="${version.eol}" pattern="YYYY-MM-dd" />"/> </td> </tr> </tbody>
--- a/src/main/webapp/WEB-INF/jsp/versions.jsp Wed Aug 18 12:47:32 2021 +0200 +++ b/src/main/webapp/WEB-INF/jsp/versions.jsp Wed Aug 18 14:57:45 2021 +0200 @@ -28,31 +28,31 @@ <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> -<jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.VersionsView" scope="request" /> +<jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.VersionsView" scope="request"/> <c:set var="project" scope="page" value="${viewmodel.projectInfo.project}"/> -<%@include file="../jspf/project-header.jspf"%> +<%@include file="../jspf/project-header.jspf" %> <div id="tool-area"> <a href="./projects/${project.node}/versions/-/create" class="button"><fmt:message key="button.version.create"/></a> <a href="./projects/${project.node}/issues/-/-/-/create" class="button"><fmt:message key="button.issue.create"/></a> </div> -<h2><fmt:message key="progress" /></h2> +<h2><fmt:message key="progress"/></h2> -<c:set var="summary" value="${viewmodel.projectInfo.issueSummary}" /> -<%@include file="../jspf/issue-summary.jspf"%> +<c:set var="summary" value="${viewmodel.projectInfo.issueSummary}"/> +<%@include file="../jspf/issue-summary.jspf" %> <table id="version-list" class="datatable medskip fullwidth"> <colgroup> <col> - <col width="28%"> - <col width="12%"> - <col width="12%"> - <col width="12%"> - <col width="12%"> - <col width="12%"> - <col width="12%"> + <col style="width: 28%"> + <col style="width: 12%"> + <col style="width: 12%"> + <col style="width: 12%"> + <col style="width: 12%"> + <col style="width: 12%"> + <col style="width: 12%"> </colgroup> <thead> <tr> @@ -67,23 +67,28 @@ <tr> <th></th> <th><fmt:message key="version"/></th> - <th class="hcenter"><fmt:message key="issues.open" /></th> - <th class="hcenter"><fmt:message key="issues.active" /></th> - <th class="hcenter"><fmt:message key="issues.done" /></th> - <th class="hcenter"><fmt:message key="issues.open" /></th> - <th class="hcenter"><fmt:message key="issues.active" /></th> - <th class="hcenter"><fmt:message key="issues.done" /></th> + <th class="hcenter"><fmt:message key="issues.open"/></th> + <th class="hcenter"><fmt:message key="issues.active"/></th> + <th class="hcenter"><fmt:message key="issues.done"/></th> + <th class="hcenter"><fmt:message key="issues.open"/></th> + <th class="hcenter"><fmt:message key="issues.active"/></th> + <th class="hcenter"><fmt:message key="issues.done"/></th> </tr> </thead> <tbody> - <c:forEach var="versionInfo" items="${viewmodel.versionInfos}" > + <c:forEach var="versionInfo" items="${viewmodel.versionInfos}"> <tr> - <td rowspan="2" style="width: 2em;"><a href="./projects/${project.node}/versions/${versionInfo.version.node}/edit">✎</a></td> + <td rowspan="2" style="width: 2em;"><a + href="./projects/${project.node}/versions/${versionInfo.version.node}/edit">✎</a></td> <td rowspan="2"> <a href="./projects/${project.node}/issues/${versionInfo.version.node}/-/"> <c:out value="${versionInfo.version.name}"/> </a> - <div class="version-tag version-${versionInfo.version.status}"> + <div class="version-tag version-${versionInfo.version.status}" + <c:if test="${not empty versionInfo.version.releaseOrEolDate}"> + title="<fmt:formatDate type="date" value="${versionInfo.version.releaseOrEolDate}"/>" + </c:if> + > <fmt:message key="version.status.${versionInfo.version.status}"/> </div> </td> @@ -104,6 +109,6 @@ <%@include file="../jspf/issue-progress.jspf" %> </td> </tr> - </c:forEach> + </c:forEach> </tbody> </table> \ No newline at end of file