#15 add issue filters

Sun, 08 Jan 2023 17:07:26 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 08 Jan 2023 17:07:26 +0100
changeset 268
ca5501d851fa
parent 267
d8ec2d8ffa82
child 269
8646c229bd32

#15 add issue filters

setup/postgres/psql_create_tables.sql file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.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/IssueRelation.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/RelationType.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/viewmodel/Projects.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/jspf/issue-filter.jspf file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jspf/issue-summary.jspf file | annotate | diff | comparison | revisions
src/main/webapp/lightpit.css file | annotate | diff | comparison | revisions
src/main/webapp/project-details.js file | annotate | diff | comparison | revisions
     1.1 --- a/setup/postgres/psql_create_tables.sql	Tue Jan 03 18:25:51 2023 +0100
     1.2 +++ b/setup/postgres/psql_create_tables.sql	Sun Jan 08 17:07:26 2023 +0100
     1.3 @@ -154,6 +154,7 @@
     1.4      'TogetherWith',
     1.5      'Before',
     1.6      'SubtaskOf',
     1.7 +    'DefectOf',
     1.8      'Blocks',
     1.9      'Tests',
    1.10      'Duplicates'
     2.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt	Tue Jan 03 18:25:51 2023 +0100
     2.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt	Sun Jan 08 17:07:26 2023 +0100
     2.3 @@ -70,8 +70,8 @@
     2.4      fun collectIssueSummary(project: Project): IssueSummary
     2.5      fun collectIssueSummary(assignee: User): IssueSummary
     2.6  
     2.7 -    fun listIssues(project: Project): List<Issue>
     2.8 -    fun listIssues(project: Project, version: Version?, component: Component?): List<Issue>
     2.9 +    fun listIssues(project: Project, includeDone: Boolean): List<Issue>
    2.10 +    fun listIssues(project: Project, includeDone: Boolean, version: Version?, component: Component?): List<Issue>
    2.11      fun findIssue(id: Int): Issue?
    2.12      fun insertIssue(issue: Issue): Int
    2.13      fun updateIssue(issue: Issue)
    2.14 @@ -87,6 +87,7 @@
    2.15      fun insertIssueRelation(rel: IssueRelation)
    2.16      fun deleteIssueRelation(rel: IssueRelation)
    2.17      fun listIssueRelations(issue: Issue): List<IssueRelation>
    2.18 +    fun getIssueRelationMap(project: Project, includeDone: Boolean): IssueRelationMap
    2.19  
    2.20      fun insertHistoryEvent(issue: Issue, newId: Int = 0)
    2.21      fun insertHistoryEvent(issue: Issue, issueComment: IssueComment, newId: Int = 0)
     3.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt	Tue Jan 03 18:25:51 2023 +0100
     3.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt	Sun Jan 08 17:07:26 2023 +0100
     3.3 @@ -27,6 +27,7 @@
     3.4  
     3.5  import de.uapcore.lightpit.entities.*
     3.6  import de.uapcore.lightpit.types.IssueHistoryType
     3.7 +import de.uapcore.lightpit.types.RelationType
     3.8  import de.uapcore.lightpit.types.WebColor
     3.9  import de.uapcore.lightpit.viewmodel.ComponentSummary
    3.10  import de.uapcore.lightpit.viewmodel.IssueSummary
    3.11 @@ -480,11 +481,12 @@
    3.12          select issueid,
    3.13              i.project, p.name as projectname, p.node as projectnode,
    3.14              component, c.name as componentname, c.node as componentnode,
    3.15 -            status, category, subject, i.description,
    3.16 +            status, phase, category, subject, i.description,
    3.17              userid, username, givenname, lastname, mail,
    3.18              created, updated, eta, affected, resolved
    3.19          from lpit_issue i
    3.20          join lpit_project p on i.project = projectid
    3.21 +        join lpit_issue_phases using (status)
    3.22          left join lpit_component c on component = c.id
    3.23          left join lpit_user on userid = assignee 
    3.24          """.trimIndent()
    3.25 @@ -534,15 +536,17 @@
    3.26          return i
    3.27      }
    3.28  
    3.29 -    override fun listIssues(project: Project): List<Issue> =
    3.30 -        withStatement("$issueQuery where i.project = ?") {
    3.31 +    override fun listIssues(project: Project, includeDone: Boolean): List<Issue> =
    3.32 +        withStatement("$issueQuery where i.project = ? and (? or phase < 2)") {
    3.33              setInt(1, project.id)
    3.34 +            setBoolean(2, includeDone)
    3.35              queryAll { it.extractIssue() }
    3.36          }
    3.37  
    3.38 -    override fun listIssues(project: Project, version: Version?, component: Component?): List<Issue> =
    3.39 +    override fun listIssues(project: Project, includeDone: Boolean, version: Version?, component: Component?): List<Issue> =
    3.40          withStatement(
    3.41 -            """$issueQuery where i.project = ? and 
    3.42 +            """$issueQuery where i.project = ? and
    3.43 +                (? or phase < 2) and
    3.44                  (not ? or ? in (resolved, affected)) and (not ? or (resolved is null and affected is null)) and
    3.45                  (not ? or component = ?) and (not ? or component is null)
    3.46              """.trimIndent()
    3.47 @@ -559,8 +563,9 @@
    3.48                  }
    3.49              }
    3.50              setInt(1, project.id)
    3.51 -            applyFilter(version, 2, 4, 3)
    3.52 -            applyFilter(component, 5, 7, 6)
    3.53 +            setBoolean(2, includeDone)
    3.54 +            applyFilter(version, 3, 5, 4)
    3.55 +            applyFilter(component, 6, 8, 7)
    3.56  
    3.57              queryAll { it.extractIssue() }
    3.58          }
    3.59 @@ -678,6 +683,21 @@
    3.60              queryAll { IssueRelation(issue, findIssue(it.getInt("from_issue"))!!, it.getEnum("type"), true) }
    3.61          }.forEach(this::add)
    3.62      }
    3.63 +
    3.64 +    override fun getIssueRelationMap(project: Project, includeDone: Boolean): IssueRelationMap =
    3.65 +        withStatement(
    3.66 +            """
    3.67 +                select r.from_issue, r.to_issue, r.type
    3.68 +                from lpit_issue_relation r
    3.69 +                join lpit_issue i on i.issueid = r.from_issue
    3.70 +                join lpit_issue_phases p on i.status = p.status
    3.71 +                where i.project = ? and (? or p.phase < 2)
    3.72 +                """.trimIndent()
    3.73 +        ) {
    3.74 +            setInt(1, project.id)
    3.75 +            setBoolean(2, includeDone)
    3.76 +            queryAll { Pair(it.getInt("from_issue"), Pair(it.getInt("to_issue"), it.getEnum<RelationType>("type"))) }
    3.77 +        }.groupBy({it.first},{it.second})
    3.78      //</editor-fold>
    3.79  
    3.80      //<editor-fold desc="IssueComment">
     4.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/IssueRelation.kt	Tue Jan 03 18:25:51 2023 +0100
     4.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/IssueRelation.kt	Sun Jan 08 17:07:26 2023 +0100
     4.3 @@ -34,3 +34,5 @@
     4.4      val type: RelationType,
     4.5      val reverse: Boolean = false
     4.6  )
     4.7 +
     4.8 +typealias IssueRelationMap = Map<Int, List<Pair<Int, RelationType>>>
     5.1 --- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt	Tue Jan 03 18:25:51 2023 +0100
     5.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt	Sun Jan 08 17:07:26 2023 +0100
     5.3 @@ -184,7 +184,13 @@
     5.4      private fun project(http: HttpRequest, dao: DataAccessObject) {
     5.5          withPathInfo(http, dao)?.run {
     5.6  
     5.7 -            val issues = dao.listIssues(project, version, component)
     5.8 +            val filter = IssueFilter(http)
     5.9 +
    5.10 +            val needRelationsMap = filter.onlyBlocker
    5.11 +
    5.12 +            val relationsMap = if (needRelationsMap) dao.getIssueRelationMap(project, filter.includeDone) else emptyMap()
    5.13 +
    5.14 +            val issues = dao.listIssues(project, filter.includeDone, version, component)
    5.15                  .sortedWith(
    5.16                      IssueSorter(
    5.17                          IssueSorter.Criteria(IssueSorter.Field.DONE),
    5.18 @@ -192,10 +198,16 @@
    5.19                          IssueSorter.Criteria(IssueSorter.Field.UPDATED, false)
    5.20                      )
    5.21                  )
    5.22 +                .filter {
    5.23 +                    (!filter.onlyMine || (it.assignee?.username ?: "") == (http.remoteUser ?: "<Anonymous>")) &&
    5.24 +                    (!filter.onlyBlocker || (relationsMap[it.id]?.any { (_,type) -> type.blocking }?:false)) &&
    5.25 +                    (filter.status.isEmpty() || filter.status.contains(it.status)) &&
    5.26 +                    (filter.category.isEmpty() || filter.category.contains(it.category))
    5.27 +                }
    5.28  
    5.29              with(http) {
    5.30                  pageTitle = project.name
    5.31 -                view = ProjectDetails(projectInfo, issues, version, component)
    5.32 +                view = ProjectDetails(projectInfo, issues, filter, version, component)
    5.33                  feedPath = feedPath(project)
    5.34                  navigationMenu = activeProjectNavMenu(
    5.35                      dao.listProjects(),
    5.36 @@ -467,7 +479,7 @@
    5.37                      project,
    5.38                      version,
    5.39                      component,
    5.40 -                    dao.listIssues(project),
    5.41 +                    dao.listIssues(project, true),
    5.42                      dao.listIssueRelations(issue),
    5.43                      relationError
    5.44                  )
     6.1 --- a/src/main/kotlin/de/uapcore/lightpit/types/RelationType.kt	Tue Jan 03 18:25:51 2023 +0100
     6.2 +++ b/src/main/kotlin/de/uapcore/lightpit/types/RelationType.kt	Sun Jan 08 17:07:26 2023 +0100
     6.3 @@ -26,12 +26,13 @@
     6.4  
     6.5  package de.uapcore.lightpit.types
     6.6  
     6.7 -enum class RelationType(val bidi: Boolean) {
     6.8 -    RelatesTo(true),
     6.9 -    TogetherWith(true),
    6.10 -    Before(false),
    6.11 -    SubtaskOf(false),
    6.12 -    Blocks(false),
    6.13 -    Tests(false),
    6.14 -    Duplicates(false)
    6.15 -}
    6.16 \ No newline at end of file
    6.17 +enum class RelationType(val bidi: Boolean, val blocking: Boolean) {
    6.18 +    RelatesTo(true, false),
    6.19 +    TogetherWith(true, false),
    6.20 +    Before(false, true),
    6.21 +    SubtaskOf(false, true),
    6.22 +    DefectOf(false, true),
    6.23 +    Blocks(false, true),
    6.24 +    Tests(false, true),
    6.25 +    Duplicates(false, false)
    6.26 +}
     7.1 --- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt	Tue Jan 03 18:25:51 2023 +0100
     7.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt	Sun Jan 08 17:07:26 2023 +0100
     7.3 @@ -31,6 +31,7 @@
     7.4  import com.vladsch.flexmark.parser.Parser
     7.5  import com.vladsch.flexmark.util.data.MutableDataSet
     7.6  import com.vladsch.flexmark.util.data.SharedDataKeys
     7.7 +import de.uapcore.lightpit.HttpRequest
     7.8  import de.uapcore.lightpit.entities.*
     7.9  import de.uapcore.lightpit.types.*
    7.10  import kotlin.math.roundToInt
    7.11 @@ -111,8 +112,9 @@
    7.12          val options = MutableDataSet()
    7.13              .set(SharedDataKeys.EXTENSIONS, listOf(TablesExtension.create(), StrikethroughExtension.create()))
    7.14          parser = Parser.builder(options).build()
    7.15 -        renderer = HtmlRenderer.builder(options
    7.16 -            .set(HtmlRenderer.ESCAPE_HTML, true)
    7.17 +        renderer = HtmlRenderer.builder(
    7.18 +            options
    7.19 +                .set(HtmlRenderer.ESCAPE_HTML, true)
    7.20          ).build()
    7.21  
    7.22          issue.description = formatMarkdown(issue.description ?: "")
    7.23 @@ -164,3 +166,57 @@
    7.24      }
    7.25  }
    7.26  
    7.27 +class IssueFilter(http: HttpRequest) {
    7.28 +
    7.29 +    val issueStatus = IssueStatus.values()
    7.30 +    val issueCategory = IssueCategory.values()
    7.31 +    val flagIncludeDone = "f.0"
    7.32 +    val flagMine = "f.1"
    7.33 +    val flagBlocker = "f.2"
    7.34 +
    7.35 +    val includeDone: Boolean = evalFlag(http, flagIncludeDone)
    7.36 +    val onlyMine: Boolean = evalFlag(http, flagMine)
    7.37 +    val onlyBlocker: Boolean = evalFlag(http, flagBlocker)
    7.38 +    val status: List<IssueStatus> = evalEnum(http, "s")
    7.39 +    val category: List<IssueCategory> = evalEnum(http, "c")
    7.40 +
    7.41 +    private fun evalFlag(http: HttpRequest, name: String): Boolean {
    7.42 +        val param = http.paramArray("filter")
    7.43 +        if (param.isNotEmpty()) {
    7.44 +            if (param.contains(name)) {
    7.45 +                http.session.setAttribute(name, true)
    7.46 +            } else {
    7.47 +                http.session.removeAttribute(name)
    7.48 +            }
    7.49 +        }
    7.50 +        return http.session.getAttribute(name) != null
    7.51 +    }
    7.52 +
    7.53 +    private inline fun <reified T : Enum<T>> evalEnum(http: HttpRequest, prefix: String): List<T> {
    7.54 +        val sattr = "f.${prefix}"
    7.55 +        val param = http.paramArray("filter")
    7.56 +        if (param.isNotEmpty()) {
    7.57 +            val list = param.filter { it.startsWith("${prefix}.") }
    7.58 +                .map { it.substring(prefix.length + 1) }
    7.59 +                .map {
    7.60 +                    try {
    7.61 +                        // quick and very dirty validation
    7.62 +                        enumValueOf<T>(it)
    7.63 +                    } catch (_: IllegalArgumentException) {
    7.64 +                        // skip
    7.65 +                    }
    7.66 +                }
    7.67 +            if (list.isEmpty()) {
    7.68 +                http.session.removeAttribute(sattr)
    7.69 +            } else {
    7.70 +                http.session.setAttribute(sattr, list.joinToString(","))
    7.71 +            }
    7.72 +        }
    7.73 +
    7.74 +        return http.session.getAttribute(sattr)
    7.75 +            ?.toString()
    7.76 +            ?.split(",")
    7.77 +            ?.map { enumValueOf(it) }
    7.78 +            ?: emptyList()
    7.79 +    }
    7.80 +}
     8.1 --- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Projects.kt	Tue Jan 03 18:25:51 2023 +0100
     8.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Projects.kt	Sun Jan 08 17:07:26 2023 +0100
     8.3 @@ -47,6 +47,7 @@
     8.4  class ProjectDetails(
     8.5      val projectInfo: ProjectInfo,
     8.6      val issues: List<Issue>,
     8.7 +    val filter: IssueFilter,
     8.8      val version: Version? = null,
     8.9      val component: Component? = null
    8.10  ) : View() {
     9.1 --- a/src/main/resources/localization/strings.properties	Tue Jan 03 18:25:51 2023 +0100
     9.2 +++ b/src/main/resources/localization/strings.properties	Sun Jan 08 17:07:26 2023 +0100
     9.3 @@ -25,6 +25,7 @@
     9.4  app.license.title=License
     9.5  app.name=Lightweight Project and Issue Tracking
     9.6  button.add=Add
     9.7 +button.apply=Apply
     9.8  button.back=Back
     9.9  button.cancel=Cancel
    9.10  button.comment.edit=Edit Comment
    9.11 @@ -85,6 +86,11 @@
    9.12  issue.created=Created
    9.13  issue.description=Description
    9.14  issue.eta=ETA
    9.15 +issue.filter=Filter
    9.16 +issue.filter.blocking=show only blocking
    9.17 +issue.filter.done=show resolved
    9.18 +issue.filter.mine=only assigned to me
    9.19 +issue.filter.more=more filters
    9.20  issue.id=Issue ID
    9.21  issue.relations=Relations
    9.22  issue.relations.issue=Issue
    9.23 @@ -169,4 +175,6 @@
    9.24  version.status.Released=Released
    9.25  version.status.Unreleased=Unreleased
    9.26  version.status=Status
    9.27 -version=Version
    9.28 \ No newline at end of file
    9.29 +version=Version
    9.30 +issue.relations.type.DefectOf=defect of
    9.31 +issue.relations.type.DefectOf.rev=defect
    9.32 \ No newline at end of file
    10.1 --- a/src/main/resources/localization/strings_de.properties	Tue Jan 03 18:25:51 2023 +0100
    10.2 +++ b/src/main/resources/localization/strings_de.properties	Sun Jan 08 17:07:26 2023 +0100
    10.3 @@ -24,6 +24,7 @@
    10.4  app.changelog=Versionshistorie
    10.5  app.license.title=Lizenz (Englisch)
    10.6  app.name=Lightweight Project and Issue Tracking
    10.7 +button.apply=Anwenden
    10.8  button.add=Hinzuf\u00fcgen
    10.9  button.back=Zur\u00fcck
   10.10  button.cancel=Abbrechen
   10.11 @@ -85,6 +86,11 @@
   10.12  issue.created=Erstellt
   10.13  issue.description=Beschreibung
   10.14  issue.eta=Zieldatum
   10.15 +issue.filter=Filter
   10.16 +issue.filter.blocking=zeige nur blockierende
   10.17 +issue.filter.done=zeige erledigte
   10.18 +issue.filter.mine=nur mir zugewiesene
   10.19 +issue.filter.more=mehr Filter
   10.20  issue.id=Vorgangs-ID
   10.21  issue.relations=Beziehungen
   10.22  issue.relations.issue=Vorgang
   10.23 @@ -170,3 +176,5 @@
   10.24  version.status.Unreleased=Unver\u00f6ffentlicht
   10.25  version.status=Status
   10.26  version=Version
   10.27 +issue.relations.type.DefectOf.rev=Fehler
   10.28 +issue.relations.type.DefectOf=Fehler von
    11.1 --- a/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf	Tue Jan 03 18:25:51 2023 +0100
    11.2 +++ b/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf	Sun Jan 08 17:07:26 2023 +0100
    11.3 @@ -32,6 +32,7 @@
    11.4      <li>Mehrfachauswahl für Versionen im Vorgang entfernt.</li>
    11.5      <li>RSS Feeds für Projekte hinzugefügt.</li>
    11.6      <li>Vorgangsansicht vereinfacht.</li>
    11.7 +    <li>Filteroptionen hinzugefügt.</li>
    11.8      <li>Möglichkeit zum Deaktivieren einer Komponente hinzugefügt.</li>
    11.9      <li>Datum der Veröffentlichung und des Supportendes zu Versionen hinzugefügt.</li>
   11.10      <li>Gesamtanzahl der Kommentare wird nun angezeigt.</li>
    12.1 --- a/src/main/webapp/WEB-INF/changelogs/changelog.jspf	Tue Jan 03 18:25:51 2023 +0100
    12.2 +++ b/src/main/webapp/WEB-INF/changelogs/changelog.jspf	Sun Jan 08 17:07:26 2023 +0100
    12.3 @@ -32,6 +32,7 @@
    12.4      <li>Remove multi selection of versions within an issue.</li>
    12.5      <li>Add RSS feeds for projects.</li>
    12.6      <li>Simplify issue view.</li>
    12.7 +    <li>Add filtering options.</li>
    12.8      <li>Add possibility to deactivate a component.</li>
    12.9      <li>Add release and end of life dates to versions.</li>
   12.10      <li>Add the total number of comments to the caption.</li>
    13.1 --- a/src/main/webapp/WEB-INF/jsp/project-details.jsp	Tue Jan 03 18:25:51 2023 +0100
    13.2 +++ b/src/main/webapp/WEB-INF/jsp/project-details.jsp	Sun Jan 08 17:07:26 2023 +0100
    13.3 @@ -40,6 +40,9 @@
    13.4      <button onclick="toggleProjectDetails()" id="toggle-details-button"><fmt:message key="button.project.details"/></button>
    13.5  </div>
    13.6  
    13.7 +<h3><fmt:message key="issue.filter" /></h3>
    13.8 +<%@include file="../jspf/issue-filter.jspf"%>
    13.9 +
   13.10  <h2><fmt:message key="progress" /></h2>
   13.11  
   13.12  <c:set var="summary" value="${viewmodel.projectInfo.issueSummary}" />
    14.1 --- a/src/main/webapp/WEB-INF/jsp/site.jsp	Tue Jan 03 18:25:51 2023 +0100
    14.2 +++ b/src/main/webapp/WEB-INF/jsp/site.jsp	Sun Jan 08 17:07:26 2023 +0100
    14.3 @@ -31,7 +31,7 @@
    14.4  <%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
    14.5  
    14.6  <%-- Version suffix for forcing browsers to update the CSS / JS files --%>
    14.7 -<c:set scope="page" var="versionSuffix" value="20230103"/>
    14.8 +<c:set scope="page" var="versionSuffix" value="20230108"/>
    14.9  
   14.10  <%-- Make the base href easily available at request scope --%>
   14.11  <c:set scope="page" var="baseHref" value="${requestScope[Constants.REQ_ATTR_BASE_HREF]}"/>
    15.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.2 +++ b/src/main/webapp/WEB-INF/jspf/issue-filter.jspf	Sun Jan 08 17:07:26 2023 +0100
    15.3 @@ -0,0 +1,60 @@
    15.4 +<%--
    15.5 +
    15.6 +--%>
    15.7 +<form method="GET">
    15.8 +    <div>
    15.9 +        <label>
   15.10 +            <input name="filter"
   15.11 +                   type="checkbox"
   15.12 +                   value="${viewmodel.filter.flagIncludeDone}"
   15.13 +                   <c:if test="${viewmodel.filter.includeDone}">checked</c:if>
   15.14 +            >
   15.15 +            <fmt:message key="issue.filter.done"/>
   15.16 +        </label>
   15.17 +        <label>
   15.18 +            <input name="filter"
   15.19 +                   type="checkbox"
   15.20 +                   value="${viewmodel.filter.flagMine}"
   15.21 +                   <c:if test="${viewmodel.filter.onlyMine}">checked</c:if>
   15.22 +            >
   15.23 +            <fmt:message key="issue.filter.mine"/>
   15.24 +        </label>
   15.25 +        <label>
   15.26 +            <input name="filter"
   15.27 +                   type="checkbox"
   15.28 +                   value="${viewmodel.filter.flagBlocker}"
   15.29 +                   <c:if test="${viewmodel.filter.onlyBlocker}">checked</c:if>
   15.30 +            >
   15.31 +            <fmt:message key="issue.filter.blocking"/>
   15.32 +        </label>
   15.33 +        <label>
   15.34 +            <input id="show-more-filters" type="checkbox" onclick="toggleFilterDetails()">
   15.35 +            <fmt:message key="issue.filter.more"/>
   15.36 +        </label>
   15.37 +    </div>
   15.38 +    <div id="more-filters" style="display: flex; gap: 1em">
   15.39 +        <div style="display: inline-block">
   15.40 +            <label class="caption" style="display:block;" for="filter-category"><fmt:message key="issue.category"/></label>
   15.41 +            <select id="filter-category" name="filter" multiple size="10">
   15.42 +                <c:forEach var="category" items="${viewmodel.filter.issueCategory}">
   15.43 +                    <option value="c.${category}" <c:if test="${viewmodel.filter.category.contains(category) }">selected</c:if> >
   15.44 +                        <fmt:message key="issue.category.${category}"/>
   15.45 +                    </option>
   15.46 +                </c:forEach>
   15.47 +            </select>
   15.48 +        </div>
   15.49 +        <div style="display: inline-block">
   15.50 +            <label class="caption" style="display:block;" for="filter-status"><fmt:message key="issue.status"/></label>
   15.51 +            <select id="filter-status" name="filter" multiple size="10">
   15.52 +                <c:forEach var="status" items="${viewmodel.filter.issueStatus}">
   15.53 +                    <option value="s.${status}" <c:if test="${viewmodel.filter.status.contains(status) }">selected</c:if>>
   15.54 +                        <fmt:message key="issue.status.${status}"/>
   15.55 +                    </option>
   15.56 +                </c:forEach>
   15.57 +            </select>
   15.58 +        </div>
   15.59 +    </div>
   15.60 +    <div class="medskip">
   15.61 +        <button name="filter" type="submit"><fmt:message key="button.apply"/></button>
   15.62 +    </div>
   15.63 +</form>
    16.1 --- a/src/main/webapp/WEB-INF/jspf/issue-summary.jspf	Tue Jan 03 18:25:51 2023 +0100
    16.2 +++ b/src/main/webapp/WEB-INF/jspf/issue-summary.jspf	Sun Jan 08 17:07:26 2023 +0100
    16.3 @@ -9,7 +9,9 @@
    16.4          <div><c:out value="${summary.open}"/></div>
    16.5          <div class="caption"><fmt:message key="issues.active"/>:</div>
    16.6          <div><c:out value="${summary.active}"/></div>
    16.7 +        <c:if test="${summary.done gt 0}">
    16.8          <div class="caption"><fmt:message key="issues.done"/>:</div>
    16.9          <div><c:out value="${summary.done}"/></div>
   16.10 +        </c:if>
   16.11      </div>
   16.12  </div>
   16.13 \ No newline at end of file
    17.1 --- a/src/main/webapp/lightpit.css	Tue Jan 03 18:25:51 2023 +0100
    17.2 +++ b/src/main/webapp/lightpit.css	Sun Jan 08 17:07:26 2023 +0100
    17.3 @@ -73,6 +73,14 @@
    17.4      padding: 0;
    17.5  }
    17.6  
    17.7 +h2 {
    17.8 +    margin: 0.75em 0;
    17.9 +}
   17.10 +
   17.11 +h3 {
   17.12 +    margin: 0.25em 0;
   17.13 +}
   17.14 +
   17.15  textarea, input, button, select {
   17.16      font-family: inherit;
   17.17      font-size: inherit;
    18.1 --- a/src/main/webapp/project-details.js	Tue Jan 03 18:25:51 2023 +0100
    18.2 +++ b/src/main/webapp/project-details.js	Sun Jan 08 17:07:26 2023 +0100
    18.3 @@ -52,4 +52,20 @@
    18.4          full.style.display = 'none'
    18.5      }
    18.6  }
    18.7 -window.addEventListener('load', function() { toggleProjectDetails() }, false)
    18.8 +
    18.9 +function toggleFilterDetails() {
   18.10 +    const filters = document.getElementById('more-filters')
   18.11 +    const toggle = document.getElementById('show-more-filters')
   18.12 +    if (toggle.checked) {
   18.13 +        filters.style.display = 'flex'
   18.14 +    } else {
   18.15 +        filters.style.display = 'none'
   18.16 +    }
   18.17 +}
   18.18 +
   18.19 +function toggleDetails() {
   18.20 +    toggleProjectDetails()
   18.21 +    toggleFilterDetails()
   18.22 +}
   18.23 +
   18.24 +window.addEventListener('load', function() { toggleDetails() }, false)

mercurial