Thu, 08 Oct 2020 20:16:47 +0200
adds versions overview
includes major refactoring of side menu generation
--- a/src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java Thu Oct 08 18:28:16 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/AbstractLightPITServlet.java Thu Oct 08 20:16:47 2020 +0200 @@ -230,12 +230,12 @@ /** * Sets the navigation menu. * - * @param req the servlet request object - * @param navigationItems the menu entries for the navigation menu + * @param req the servlet request object + * @param jspName the name of the menu's jsp file * @see Constants#REQ_ATTR_NAVIGATION */ - protected void setNavigationMenu(HttpServletRequest req, List<MenuEntry> navigationItems) { - req.setAttribute(Constants.REQ_ATTR_NAVIGATION, navigationItems); + protected void setNavigationMenu(HttpServletRequest req, String jspName) { + req.setAttribute(Constants.REQ_ATTR_NAVIGATION, Functions.jspPath(jspName)); } /**
--- a/src/main/java/de/uapcore/lightpit/Constants.java Thu Oct 08 18:28:16 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/Constants.java Thu Oct 08 20:16:47 2020 +0200 @@ -72,7 +72,7 @@ public static final String REQ_ATTR_MENU = fqn(AbstractLightPITServlet.class, "mainMenu"); /** - * Key for the request attribute containing the navigation menu. + * Key for the request attribute containing the optional navigation menu jsp. */ public static final String REQ_ATTR_NAVIGATION = fqn(AbstractLightPITServlet.class, "navMenu");
--- a/src/main/java/de/uapcore/lightpit/MenuEntry.java Thu Oct 08 18:28:16 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/MenuEntry.java Thu Oct 08 20:16:47 2020 +0200 @@ -50,33 +50,12 @@ */ private boolean active = false; - /** - * The menu level. - */ - private int level; - public MenuEntry(ResourceKey resourceKey, String pathName) { - this(0, resourceKey, pathName); - } - - public MenuEntry(String text, String pathName) { - this(0, text, pathName); - } - - public MenuEntry(int level, ResourceKey resourceKey, String pathName) { - this.level = level; this.text = null; this.resourceKey = resourceKey; this.pathName = pathName; } - public MenuEntry(int level, String text, String pathName) { - this.level = level; - this.text = text; - this.resourceKey = null; - this.pathName = pathName; - } - public ResourceKey getResourceKey() { return resourceKey; } @@ -94,14 +73,6 @@ } public void setActive(boolean active) { - this.active = true; - } - - public int getLevel() { - return level; - } - - public void setLevel(int level) { - this.level = level; + this.active = active; } }
--- a/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java Thu Oct 08 18:28:16 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java Thu Oct 08 20:16:47 2020 +0200 @@ -42,7 +42,6 @@ import java.io.IOException; import java.sql.Date; import java.sql.SQLException; -import java.util.ArrayList; import java.util.NoSuchElementException; import java.util.Optional; import java.util.stream.Collectors; @@ -68,73 +67,6 @@ return "localization.projects"; } - private String queryParams(Project p, Version v) { - return String.format("pid=%d&vid=%d", - p == null ? -1 : p.getId(), - v == null ? -1 : v.getId() - ); - } - - /** - * Creates the navigation menu. - * - * @param req the servlet request - * @param viewModel the current view model - */ - private void setNavigationMenu(HttpServletRequest req, ProjectView viewModel) { - final Project selectedProject = Optional.ofNullable(viewModel.getProjectInfo()).map(ProjectInfo::getProject).orElse(null); - - final var navigation = new ArrayList<MenuEntry>(); - - for (ProjectInfo plistInfo : viewModel.getProjectList()) { - final var proj = plistInfo.getProject(); - final var projEntry = new MenuEntry( - proj.getName(), - "projects/view?" + queryParams(proj, null) - ); - navigation.add(projEntry); - if (proj.equals(selectedProject)) { - final var projInfo = viewModel.getProjectInfo(); - projEntry.setActive(true); - - // **************** - // Versions Section - // **************** - { - final var entry = new MenuEntry(1, - new ResourceKey(getResourceBundleName(), "menu.versions"), - "projects/view?" + queryParams(proj, null) - ); - navigation.add(entry); - } - - final var level2 = new ArrayList<MenuEntry>(); - { - final var entry = new MenuEntry( - new ResourceKey(getResourceBundleName(), "filter.none"), - "projects/view?" + queryParams(proj, null) - ); - if (viewModel.getVersionFilter() == null) entry.setActive(true); - level2.add(entry); - } - - for (Version version : projInfo.getVersions()) { - final var entry = new MenuEntry( - version.getName(), - "projects/view?" + queryParams(proj, version) - ); - if (version.equals(viewModel.getVersionFilter())) entry.setActive(true); - level2.add(entry); - } - - level2.forEach(e -> e.setLevel(2)); - navigation.addAll(level2); - } - } - - setNavigationMenu(req, navigation); - } - private int syncParamWithSession(HttpServletRequest req, String param, String attr) { final var session = req.getSession(); final var idParam = getParameter(req, Integer.class, param); @@ -179,7 +111,7 @@ setViewModel(req, viewModel); setContentPage(req, name); setStylesheet(req, "projects"); - setNavigationMenu(req, viewModel); + setNavigationMenu(req, "project-navmenu"); return ResponseType.HTML; } @@ -232,7 +164,7 @@ dao.getProjectDao().saveOrUpdate(project); - setRedirectLocation(req, "./projects/view?pid="+project.getId()); + setRedirectLocation(req, "./projects/versions?pid="+project.getId()); setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); LOG.debug("Successfully updated project {}", project.getName()); @@ -270,6 +202,26 @@ return forwardView(req, viewModel, "project-details"); } + @RequestMapping(requestPath = "versions", method = HttpMethod.GET) + public ResponseType versions(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException, SQLException { + final var viewModel = new VersionsView(); + populate(viewModel, req, dao); + viewModel.setVersionFilter(null); + + final var projectInfo = viewModel.getProjectInfo(); + if (projectInfo == null) { + resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No project selected."); + return ResponseType.NONE; + } + + final var issueDao = dao.getIssueDao(); + final var issues = issueDao.list(projectInfo.getProject()); + for (var issue : issues) issueDao.joinVersionInformation(issue); + viewModel.update(projectInfo.getVersions(), issues); + + return forwardView(req, viewModel, "versions"); + } + @RequestMapping(requestPath = "versions/edit", method = HttpMethod.GET) public ResponseType editVersion(HttpServletRequest req, DataAccessObjects dao) throws SQLException { final var viewModel = new VersionEditView(); @@ -297,7 +249,7 @@ dao.getVersionDao().saveOrUpdate(version); // specifying the pid parameter will purposely reset the session selected version! - setRedirectLocation(req, "./projects/view?pid=" + version.getProject().getId()); + setRedirectLocation(req, "./projects/versions?pid=" + version.getProject().getId()); setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) { LOG.warn("Form validation failure: {}", ex.getMessage());
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/VersionsView.java Thu Oct 08 20:16:47 2020 +0200 @@ -0,0 +1,25 @@ +package de.uapcore.lightpit.viewmodel; + +import de.uapcore.lightpit.entities.Issue; +import de.uapcore.lightpit.entities.Version; + +import java.util.ArrayList; +import java.util.List; + +public class VersionsView extends ProjectView { + + private List<VersionInfo> versionInfo = new ArrayList<>(); + + public void update(List<Version> versions, List<Issue> issues) { + versionInfo.clear(); + for (var version : versions) { + final var info = new VersionInfo(version); + info.collectIssues(issues); + versionInfo.add(info); + } + } + + public List<VersionInfo> getVersionInfo() { + return versionInfo; + } +}
--- a/src/main/resources/localization/projects.properties Thu Oct 08 18:28:16 2020 +0200 +++ b/src/main/resources/localization/projects.properties Thu Oct 08 20:16:47 2020 +0200 @@ -31,10 +31,8 @@ no-projects=Welcome to LightPIT. Start off by creating a new project! -filter.all=all -filter.none=none - menu.versions=Versions +menu.versions.unassigned=unassigned menu.issues=Issues name=Name @@ -49,7 +47,6 @@ issues.open=Open issues.active=In Progress issues.done=Done -issues.total=Total issues.reported=Reported Issues issues.resolved=Assigned Issues @@ -62,8 +59,6 @@ placeholder.null-owner=Unassigned placeholder.null-assignee=Unassigned -version.open=open -version.label=Version version.status.Future=Future version.status.Unreleased=Unreleased version.status.Released=Released
--- a/src/main/resources/localization/projects_de.properties Thu Oct 08 18:28:16 2020 +0200 +++ b/src/main/resources/localization/projects_de.properties Thu Oct 08 20:16:47 2020 +0200 @@ -31,10 +31,8 @@ no-projects=Wilkommen bei LightPIT. Beginnen Sie mit der Erstellung eines Projektes! -filter.all=alle -filter.none=keine - menu.versions=Versionen +menu.versions.unassigned=Nicht Zugewiesen menu.issues=Vorg\u00e4nge name=Name @@ -51,7 +49,6 @@ issues.done=Erledigt issues.reported=Er\u00f6ffnete Vorg\u00e4nge issues.resolved=Enthaltene Vorg\u00e4nge -issues.total=Summe version.project=Projekt version.name=Version @@ -62,8 +59,6 @@ placeholder.null-owner=Nicht Zugewiesen placeholder.null-assignee=Niemandem -version.open=ansehen -version.label=Version version.status.Future=Geplant version.status.Unreleased=Unver\u00f6ffentlicht version.status.Released=Ver\u00f6ffentlicht
--- a/src/main/webapp/WEB-INF/jsp/issues.jsp Thu Oct 08 18:28:16 2020 +0200 +++ b/src/main/webapp/WEB-INF/jsp/issues.jsp Thu Oct 08 20:16:47 2020 +0200 @@ -35,7 +35,7 @@ <c:if test="${not empty version}"> <h2> - <fmt:message key="version.label" /> <c:out value="${version.name}" /> - <fmt:message key="version.status.${version.status}"/> + <fmt:message key="version.name" /> <c:out value="${version.name}" /> - <fmt:message key="version.status.${version.status}"/> <a href="./projects/versions/edit?vid=${version.id}">✎</a> </h2> </c:if>
--- a/src/main/webapp/WEB-INF/jsp/project-details.jsp Thu Oct 08 18:28:16 2020 +0200 +++ b/src/main/webapp/WEB-INF/jsp/project-details.jsp Thu Oct 08 20:16:47 2020 +0200 @@ -49,7 +49,7 @@ <c:if test="${not empty viewmodel.versionFilter}"> <c:set var="versionInfo" value="${viewmodel.projectDetails.versionInfo}"/> <h2> - <fmt:message key="version.label" /> <c:out value="${versionInfo.version.name}" /> - <fmt:message key="version.status.${versionInfo.version.status}"/> + <fmt:message key="version.name" /> <c:out value="${versionInfo.version.name}" /> - <fmt:message key="version.status.${versionInfo.version.status}"/> </h2> <h3><fmt:message key="issues.resolved"/> </h3>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/webapp/WEB-INF/jsp/project-navmenu.jsp Thu Oct 08 20:16:47 2020 +0200 @@ -0,0 +1,60 @@ +<%-- +DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + +Copyright 2020 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. +--%> +<%@page pageEncoding="UTF-8" %> +<%@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.ProjectView" scope="request"/> + +<c:forEach var="projectInfo" items="${viewmodel.projectList}"> + <c:set var="isActive" value="${viewmodel.projectInfo.project eq projectInfo.project}" /> + <div class="menuEntry level-0" <c:if test="${isActive}">data-active</c:if> > + <a href="projects/versions?pid=${projectInfo.project.id}"> + <c:out value="${projectInfo.project.name}"/> + </a> + </div> + <c:if test="${isActive}"> + <div class="menuEntry level-1"> + <a href="projects/versions?pid=${projectInfo.project.id}"> + <fmt:message key="menu.versions"/> + </a> + </div> + <div class="menuEntry level-2"> + <a href="projects/view?pid=${projectInfo.project.id}&vid=-1"> + <fmt:message key="menu.versions.unassigned" /> + </a> + </div> + <c:forEach var="version" items="${viewmodel.projectInfo.versions}"> + <c:set var="isVersionActive" value="${viewmodel.versionFilter eq version}" /> + <div class="menuEntry level-2" <c:if test="${isVersionActive}">data-active</c:if> > + <a href="projects/view?pid=${projectInfo.project.id}&vid=${version.id}"> + <c:out value="${version.name}"/> + </a> + </div> + </c:forEach> + </c:if> +</c:forEach>
--- a/src/main/webapp/WEB-INF/jsp/projects.jsp Thu Oct 08 18:28:16 2020 +0200 +++ b/src/main/webapp/WEB-INF/jsp/projects.jsp Thu Oct 08 20:16:47 2020 +0200 @@ -69,7 +69,7 @@ <c:set var="project" scope="page" value="${projectInfo.project}"/> <tr class="nowrap"> <td style="width: 2em;"><a href="./projects/edit?pid=${project.id}">✎</a></td> - <td><a href="./projects/view?pid=${project.id}"><c:out value="${project.name}"/></a> + <td><a href="./projects/versions?pid=${project.id}"><c:out value="${project.name}"/></a> </td> <td> <c:if test="${not empty project.repoUrl}">
--- a/src/main/webapp/WEB-INF/jsp/site.jsp Thu Oct 08 18:28:16 2020 +0200 +++ b/src/main/webapp/WEB-INF/jsp/site.jsp Thu Oct 08 20:16:47 2020 +0200 @@ -39,7 +39,7 @@ <%-- Define an alias for the main menu --%> <c:set scope="page" var="mainMenu" value="${requestScope[Constants.REQ_ATTR_MENU]}"/> -<%-- Define an alias for the main menu --%> +<%-- Define an alias for the navigation menu --%> <c:set scope="page" var="navMenu" value="${requestScope[Constants.REQ_ATTR_NAVIGATION]}"/> <%-- Define an alias for the content page name --%> @@ -86,9 +86,7 @@ <div> <c:if test="${not empty navMenu}"> <div id="sideMenu"> - <c:forEach var="menu" items="${navMenu}"> - <%@include file="../jspf/menu-entry.jsp" %> - </c:forEach> + <c:import url="${navMenu}"/> </div> </c:if> <div id="content-area" <c:if test="${not empty navMenu}">class="sidebar-spacing"</c:if>>
--- a/src/main/webapp/WEB-INF/jsp/version-form.jsp Thu Oct 08 18:28:16 2020 +0200 +++ b/src/main/webapp/WEB-INF/jsp/version-form.jsp Thu Oct 08 20:16:47 2020 +0200 @@ -72,7 +72,7 @@ <tr> <td colspan="2"> <input type="hidden" name="id" value="${version.id}"/> - <a href="./projects/view?pid=${version.project.id}" class="button"> + <a href="./projects/versions?pid=${version.project.id}" class="button"> <fmt:message bundle="${lightpit_bundle}" key="button.cancel"/> </a> <button type="submit"><fmt:message bundle="${lightpit_bundle}" key="button.okay"/></button>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/webapp/WEB-INF/jsp/versions.jsp Thu Oct 08 20:16:47 2020 +0200 @@ -0,0 +1,101 @@ +<%-- +DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + +Copyright 2020 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. +--%> +<%@page pageEncoding="UTF-8" %> +<%@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" /> + +<c:set var="project" scope="page" value="${viewmodel.projectInfo.project}"/> +<%@include file="../jspf/project-header.jsp"%> + +<div id="tool-area"> + <a href="./projects/versions/edit" class="button"><fmt:message key="button.version.create"/></a> + <a href="./projects/issues/edit?pid=${project.id}" class="button"><fmt:message key="button.issue.create"/></a> +</div> + +<h2><fmt:message key="progress" /></h2> + +<c:set var="summary" value="${viewmodel.projectInfo.issueSummary}" /> +<%@include file="../jspf/issue-summary.jsp"%> + +<table id="version-list" class="datatable medskip fullwidth"> + <colgroup> + <col> + <col width="20%"> + <col width="20%"> + <col width="10%"> + <col width="10%"> + <col width="10%"> + <col width="10%"> + <col width="10%"> + <col width="10%"> + </colgroup> + <thead> + <tr> + <th colspan="3"></th> + <th colspan="3" class="hcenter"> + <fmt:message key="issues.resolved"/> + </th> + <th colspan="3" class="hcenter"> + <fmt:message key="issues.reported"/> + </th> + </tr> + <tr> + <th></th> + <th><fmt:message key="version.name"/></th> + <th><fmt:message key="version.status" /></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.versionInfo}" > + <tr> + <td style="width: 2em;"><a href="./projects/versions/edit?vid=${versionInfo.version.id}">✎</a></td> + <td> + <a href="projects/view?pid=${viewmodel.projectInfo.project.id}&vid=${versionInfo.version.id}"> + <c:out value="${versionInfo.version.name}"/> + </a> + </td> + <td> + <fmt:message key="version.status.${versionInfo.version.status}"/> + </td> + <td class="hright">${versionInfo.resolvedTotal.open}</td> + <td class="hright">${versionInfo.resolvedTotal.active}</td> + <td class="hright">${versionInfo.resolvedTotal.done}</td> + <td class="hright">${versionInfo.reportedTotal.open}</td> + <td class="hright">${versionInfo.reportedTotal.active}</td> + <td class="hright">${versionInfo.reportedTotal.done}</td> + </tr> + </c:forEach> + </tbody> +</table> \ No newline at end of file
--- a/src/main/webapp/WEB-INF/jspf/menu-entry.jsp Thu Oct 08 18:28:16 2020 +0200 +++ b/src/main/webapp/WEB-INF/jspf/menu-entry.jsp Thu Oct 08 20:16:47 2020 +0200 @@ -1,4 +1,4 @@ -<div class="menuEntry level-${menu.level}" +<div class="menuEntry" <c:if test="${menu.active}">data-active</c:if> > <a href="${menu.pathName}"> <c:if test="${empty menu.resourceKey}">
--- a/src/main/webapp/lightpit.css Thu Oct 08 18:28:16 2020 +0200 +++ b/src/main/webapp/lightpit.css Thu Oct 08 20:16:47 2020 +0200 @@ -67,6 +67,7 @@ height: 100%; width: 30ch; padding-top: 2.25rem; + color: #3060f8; border-image-source: linear-gradient(to bottom, #606060, rgba(60, 60, 60, .25)); border-image-slice: 1; border-right-style: solid;