implement changing and saving variant status default tip

Sun, 02 Feb 2025 17:08:18 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 02 Feb 2025 17:08:18 +0100
changeset 351
3720c7375146
parent 350
c676c200534d

implement changing and saving variant status

relates to #491

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/Issue.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/logic/IssueLogic.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/servlet/IssuesServlet.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/viewmodel/Issues.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/jsp/issue-form.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/site.jsp file | annotate | diff | comparison | revisions
src/main/webapp/issue-editor.js file | annotate | diff | comparison | revisions
src/main/webapp/projects.css file | annotate | diff | comparison | revisions
--- a/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt	Sun Feb 02 14:12:02 2025 +0100
+++ b/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt	Sun Feb 02 17:08:18 2025 +0100
@@ -156,6 +156,16 @@
     private fun String.withExt(ext: String) = if (endsWith(ext)) this else plus(ext)
     private fun jspPath(name: String) = Constants.JSP_PATH_PREFIX.plus(name).withExt(".jsp")
 
+    fun paramIndexed(prefix: String): Map<Int, String> = buildMap {
+        for (name in request.parameterNames) {
+            if (name.startsWith(prefix)) {
+                val key = name.substring(prefix.length).toIntOrNull()
+                if (key != null) {
+                    put(key, request.getParameter(name))
+                }
+            }
+        }
+    }
     fun param(name: String): String? = request.getParameter(name)
     fun paramArray(name: String): Array<String> = request.getParameterValues(name) ?: emptyArray()
 
--- a/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt	Sun Feb 02 14:12:02 2025 +0100
+++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt	Sun Feb 02 17:08:18 2025 +0100
@@ -879,7 +879,7 @@
                 """
                 insert into lpit_issue_variant_status (issueid, variant, status)
                 values (?, ?, ?::issue_status)
-                on conflict do update
+                on conflict (issueid, variant) do update
                 set status = ?::issue_status, outdated = false
                 """.trimIndent()
             ) {
--- a/src/main/kotlin/de/uapcore/lightpit/entities/Issue.kt	Sun Feb 02 14:12:02 2025 +0100
+++ b/src/main/kotlin/de/uapcore/lightpit/entities/Issue.kt	Sun Feb 02 17:08:18 2025 +0100
@@ -55,6 +55,9 @@
     fun removeVariant(variant: Variant) {
         variantStatus.remove(variant)
     }
+    fun removeAllVariants() {
+        variantStatus.clear()
+    }
     fun getStatusForVariant(variant: Variant): IssueStatus? {
         return variantStatus[variant]
     }
--- a/src/main/kotlin/de/uapcore/lightpit/logic/IssueLogic.kt	Sun Feb 02 14:12:02 2025 +0100
+++ b/src/main/kotlin/de/uapcore/lightpit/logic/IssueLogic.kt	Sun Feb 02 17:08:18 2025 +0100
@@ -3,9 +3,7 @@
 import de.uapcore.lightpit.HttpRequest
 import de.uapcore.lightpit.dao.DataAccessObject
 import de.uapcore.lightpit.dateOptValidator
-import de.uapcore.lightpit.entities.Issue
-import de.uapcore.lightpit.entities.IssueComment
-import de.uapcore.lightpit.entities.IssueRelation
+import de.uapcore.lightpit.entities.*
 import de.uapcore.lightpit.types.IssueCategory
 import de.uapcore.lightpit.types.IssueStatus
 import de.uapcore.lightpit.types.RelationType
@@ -15,7 +13,10 @@
 import de.uapcore.lightpit.viewmodel.projectNavMenu
 import java.sql.Date
 
-fun Issue.hasChanged(reference: Issue) = !(component == reference.component &&
+fun Issue.hasChanged(reference: Issue) =
+    (isTrackingVariantStatus xor reference.isTrackingVariantStatus)
+    || (isTrackingVariantStatus && !variantStatus.equals(reference))
+    || !(component == reference.component &&
         status == reference.status &&
         category == reference.category &&
         subject == reference.subject &&
@@ -33,7 +34,10 @@
     else eta.compareTo(date)
 }
 
-fun Issue.applyFormData(http: HttpRequest, dao: DataAccessObject): Issue = this.apply {
+fun Issue.applyFormData(
+    http: HttpRequest, dao: DataAccessObject,
+    versions: List<Version>? = null, variants: List<Variant>? = null,
+): Issue = this.apply {
     component = dao.findComponent(http.param("component")?.toIntOrNull() ?: -1)
     category = IssueCategory.valueOf(http.param("category") ?: "")
     status = IssueStatus.valueOf(http.param("status") ?: "")
@@ -49,10 +53,32 @@
     // TODO: process error messages
     eta = http.param("eta", ::dateOptValidator, null, mutableListOf())
 
+    // if versions are selected, but we don't have a version cache, request the dao
+    val versionsLookup = versions
+        ?: if (http.param("affected") != null || http.param("resolved") != null)
+            dao.listVersions(project) else emptyList()
+
     // we must resolve the versions with the DAO, otherwise we do not get the names for the history
-    val vlookup = {paramKey: String -> http.param(paramKey)?.toIntOrNull()?.takeIf { it > 0 }?.let { dao.findVersion(it) }}
+    val vlookup = {paramKey: String -> http.param(paramKey)?.toIntOrNull()?.takeIf { it > 0 }?.let {p -> versionsLookup.find { v -> v.id == p } }}
     affected = vlookup("affected")
     resolved = vlookup("resolved")
+
+    // process possible issue variants
+    if (http.param("use-variants") == null) {
+        removeAllVariants()
+    } else {
+        val variantsLookup = variants ?: dao.listVariants(project)
+        http.paramIndexed("status-variant-").forEach { (variantid, status) ->
+            variantsLookup.find { v -> v.id == variantid }?.let { variant ->
+                if (status == "not-relevant") {
+                    removeVariant(variant)
+                } else {
+                    setStatusForVariant(variant, IssueStatus.valueOf(status))
+                }
+            }
+        }
+        // TODO: compute overall status
+    }
 }
 
 fun processIssueForm(issue: Issue, reference: Issue, http: HttpRequest, dao: DataAccessObject) {
--- a/src/main/kotlin/de/uapcore/lightpit/servlet/IssuesServlet.kt	Sun Feb 02 14:12:02 2025 +0100
+++ b/src/main/kotlin/de/uapcore/lightpit/servlet/IssuesServlet.kt	Sun Feb 02 17:08:18 2025 +0100
@@ -71,6 +71,7 @@
                 issue,
                 dao.listVersions(issue.project),
                 dao.listComponents(issue.project),
+                dao.listVariants(issue.project),
                 dao.listUsers(),
                 issue.project
             )
--- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt	Sun Feb 02 14:12:02 2025 +0100
+++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt	Sun Feb 02 17:08:18 2025 +0100
@@ -434,6 +434,7 @@
                     issue,
                     path.projectInfo.versions,
                     path.projectInfo.components,
+                    path.projectInfo.variants,
                     dao.listUsers(),
                     path.projectInfo.project,
                     path
@@ -457,7 +458,7 @@
             val issue = Issue(
                 http.param("id")?.toIntOrNull() ?: -1,
                 project
-            ).applyFormData(http, dao)
+            ).applyFormData(http, dao, projectInfo.versions, projectInfo.variants)
 
             val openId = if (issue.id < 0) {
                 val remoteUser = http.remoteUser?.let { dao.findUserByName(it) }
--- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt	Sun Feb 02 14:12:02 2025 +0100
+++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt	Sun Feb 02 17:08:18 2025 +0100
@@ -178,6 +178,7 @@
     val issue: Issue,
     val versions: List<Version>,
     val components: List<Component>,
+    val variants: List<Variant>,
     val users: List<User>,
     val project: Project,
     val pathInfos: PathInfos? = null
--- a/src/main/resources/localization/strings.properties	Sun Feb 02 14:12:02 2025 +0100
+++ b/src/main/resources/localization/strings.properties	Sun Feb 02 17:08:18 2025 +0100
@@ -128,6 +128,9 @@
 issue.status=Status
 issue.subject=Subject
 issue.updated=Updated
+issue.variants=Variants
+issue.variants.checkbox-text=Use individual status for each variant
+issue.variants.not-relevant=Not Relevant
 issues.active=In Progress
 issues.done=Done
 issues.open=Open
--- a/src/main/resources/localization/strings_de.properties	Sun Feb 02 14:12:02 2025 +0100
+++ b/src/main/resources/localization/strings_de.properties	Sun Feb 02 17:08:18 2025 +0100
@@ -128,6 +128,9 @@
 issue.status=Status
 issue.subject=Thema
 issue.updated=Aktualisiert
+issue.variants=Varianten
+issue.variants.checkbox-text=Individueller Status f\u00fcr jede Variante
+issue.variants.not-relevant=Nicht Relevant
 issues.active=In Arbeit
 issues.done=Erledigt
 issues.open=Offen
--- a/src/main/webapp/WEB-INF/jsp/issue-form.jsp	Sun Feb 02 14:12:02 2025 +0100
+++ b/src/main/webapp/WEB-INF/jsp/issue-form.jsp	Sun Feb 02 17:08:18 2025 +0100
@@ -81,6 +81,20 @@
                 </select>
             </td>
         </tr>
+        <c:if test="${not empty viewmodel.variants}">
+        <tr>
+            <th><fmt:message key="issue.variants"/></th>
+            <td>
+                <input type="checkbox" id="use-variants" name="use-variants"
+                       <c:if test="${issue.trackingVariantStatus}">checked</c:if>
+                        onclick="toggleVariantStatus()"
+                />
+                <label for="use-variants">
+                <fmt:message key="issue.variants.checkbox-text"/>
+                </label>
+            </td>
+        </tr>
+        </c:if>
         <tr>
             <th><label for="issue-status"><fmt:message key="issue.status"/></label></th>
             <td>
@@ -93,6 +107,29 @@
                         </option>
                     </c:forEach>
                 </select>
+                <div id="issue-variant-status">
+                    <c:forEach items="${viewmodel.variants}" var="variant">
+                        <div title="<c:out value="${variant.description}" />">
+                            <label for="issue-status-variant-${variant.id}" >
+                                <c:out value="${variant.name}"/>:
+                            </label>
+                            <select id="issue-status-variant-${variant.id}" name="status-variant-${variant.id}">
+                                <option value="not-relevant"
+                                        <c:if test="${empty issue.getStatusForVariant(variant)}">selected</c:if>
+                                >
+                                    <fmt:message key="issue.variants.not-relevant"/>
+                                </option>
+                                <c:forEach var="status" items="${viewmodel.issueStatus}">
+                                    <option
+                                            <c:if test="${status eq issue.getStatusForVariant(variant)}">selected</c:if>
+                                            value="${status}">
+                                        <fmt:message key="issue.status.${status}" />
+                                    </option>
+                                </c:forEach>
+                            </select>
+                        </div>
+                    </c:forEach>
+                </div>
             </td>
         </tr>
         <tr>
--- a/src/main/webapp/WEB-INF/jsp/site.jsp	Sun Feb 02 14:12:02 2025 +0100
+++ b/src/main/webapp/WEB-INF/jsp/site.jsp	Sun Feb 02 17:08:18 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 --%>
-<c:set scope="page" var="versionSuffix" value="20250130"/>
+<c:set scope="page" var="versionSuffix" value="20250202"/>
 
 <%-- 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/issue-editor.js	Sun Feb 02 14:12:02 2025 +0100
+++ b/src/main/webapp/issue-editor.js	Sun Feb 02 17:08:18 2025 +0100
@@ -41,3 +41,19 @@
     editor.style.display='none'
     view.style.display='block'
 }
+
+function toggleVariantStatus() {
+    const cbox = document.getElementById('use-variants')
+    if (!cbox) return
+    const issue_status = document.getElementById('issue-status')
+    const variant_status = document.getElementById('issue-variant-status')
+    if (cbox.checked) {
+        issue_status.style.display = 'none'
+        variant_status.style.display = 'flex'
+    } else {
+        issue_status.style.display = 'inline-block'
+        variant_status.style.display = 'none'
+    }
+}
+
+window.addEventListener("load", (_) => toggleVariantStatus());
--- a/src/main/webapp/projects.css	Sun Feb 02 14:12:02 2025 +0100
+++ b/src/main/webapp/projects.css	Sun Feb 02 17:08:18 2025 +0100
@@ -193,6 +193,12 @@
     white-space: nowrap;
 }
 
+#issue-variant-status {
+    display: flex;
+    gap: 1em;
+    flex-wrap: wrap;
+}
+
 table.relation-editor input,
 table.relation-editor button,
 table.relation-editor .button {

mercurial