#159 adds release and eol dates

Wed, 18 Aug 2021 14:57:45 +0200

author
Mike Becker <universe@uap-core.de>
date
Wed, 18 Aug 2021 14:57:45 +0200
changeset 225
87328572e36f
parent 224
da975b1f188d
child 226
c8e1b5282f69

#159 adds release and eol dates

setup/postgres/psql_create_tables.sql file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/entities/Version.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/types/VersionStatus.kt file | annotate | diff | comparison | revisions
src/main/resources/localization/strings.properties file | annotate | diff | comparison | revisions
src/main/resources/localization/strings_de.properties file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/changelogs/changelog-de.jspf file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/changelogs/changelog.jspf file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/project-details.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/site.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/version-form.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/versions.jsp file | annotate | diff | comparison | revisions
src/main/webapp/lightpit.css file | annotate | diff | comparison | revisions
src/main/webapp/projects.css file | annotate | diff | comparison | revisions
--- 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">&#x270e;</a></td>
+            <td rowspan="2" style="width: 2em;"><a
+                    href="./projects/${project.node}/versions/${versionInfo.version.node}/edit">&#x270e;</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
--- a/src/main/webapp/lightpit.css	Wed Aug 18 12:47:32 2021 +0200
+++ b/src/main/webapp/lightpit.css	Wed Aug 18 14:57:45 2021 +0200
@@ -242,10 +242,6 @@
     box-sizing: border-box;
 }
 
-table.formtable input[type=date] {
-    width: auto;
-}
-
 table.formtable tfoot td {
     text-align: right;
 }
--- a/src/main/webapp/projects.css	Wed Aug 18 12:47:32 2021 +0200
+++ b/src/main/webapp/projects.css	Wed Aug 18 14:57:45 2021 +0200
@@ -189,3 +189,7 @@
 table.issue-view th {
     white-space: nowrap;
 }
+
+#issue-eta {
+    width: auto;
+}

mercurial