Sat, 17 Oct 2020 19:56:50 +0200
completes feature: project components
--- a/src/main/java/de/uapcore/lightpit/dao/IssueDao.java Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/dao/IssueDao.java Sat Oct 17 19:56:50 2020 +0200 @@ -28,10 +28,7 @@ */ package de.uapcore.lightpit.dao; -import de.uapcore.lightpit.entities.Issue; -import de.uapcore.lightpit.entities.IssueComment; -import de.uapcore.lightpit.entities.Project; -import de.uapcore.lightpit.entities.Version; +import de.uapcore.lightpit.entities.*; import java.sql.SQLException; import java.util.List; @@ -39,14 +36,39 @@ public interface IssueDao extends ChildEntityDao<Issue, Project> { /** - * Lists all issues that are somehow related to the specified version. - * If version is null, search for issues that are not related to any version. + * Lists all issues that are related to the specified component and version. + * If component or version is null, search for issues that are not assigned to any + * component or version, respectively. * + * @param project the project + * @param component the component or null * @param version the version or null * @return a list of issues * @throws SQLException on any kind of SQL error */ - List<Issue> list(Version version) throws SQLException; + List<Issue> list(Project project, Component component, Version version) throws SQLException; + + /** + * Lists all issues that are related to the specified version. + * If the version is null, lists issues that are not assigned to any version. + * + * @param project the project (mandatory) + * @param version the version or null + * @return a list of issues + * @throws SQLException on any kind of SQL error + */ + List<Issue> list(Project project, Version version) throws SQLException; + + /** + * Lists all issues that are related to the specified component. + * If the component is null, lists issues that are not assigned to a component. + * + * @param project the project (mandatory) + * @param component the component or null + * @return a list of issues + * @throws SQLException on any kind of SQL error + */ + List<Issue> list(Project project, Component component) throws SQLException; /** * Lists all comments for a specific issue in chronological order.
--- a/src/main/java/de/uapcore/lightpit/dao/postgres/PGIssueDao.java Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/dao/postgres/PGIssueDao.java Sat Oct 17 19:56:50 2020 +0200 @@ -31,13 +31,11 @@ import de.uapcore.lightpit.dao.IssueDao; import de.uapcore.lightpit.entities.*; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; +import java.sql.*; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; import static de.uapcore.lightpit.dao.Functions.*; @@ -51,43 +49,50 @@ public PGIssueDao(Connection connection) throws SQLException { list = connection.prepareStatement( - "select issueid, project, p.name as projectname, status, category, subject, i.description, " + + "select issueid, i.project, p.name as projectname, component, c.name as componentname, " + + "status, category, subject, i.description, " + "userid, username, givenname, lastname, mail, " + "created, updated, eta " + "from lpit_issue i " + - "join lpit_project p on project = projectid " + + "join lpit_project p on i.project = projectid " + + "left join lpit_component c on component = c.id " + "left join lpit_user on userid = assignee " + - "where project = ? "); + "where i.project = ? and coalesce(component, -1) = coalesce(?, component, -1)"); listForVersion = connection.prepareStatement( "with issue_version as ( "+ "select issueid, versionid from lpit_issue_affected_version union "+ "select issueid, versionid from lpit_issue_resolved_version) "+ - "select issueid, project, p.name as projectname, status, category, subject, i.description, " + + "select issueid, i.project, p.name as projectname, component, c.name as componentname, " + + "status, category, subject, i.description, " + "userid, username, givenname, lastname, mail, " + "created, updated, eta " + "from lpit_issue i " + - "join lpit_project p on project = projectid " + + "join lpit_project p on i.project = projectid " + + "left join lpit_component c on component = c.id " + "left join issue_version using (issueid) "+ "left join lpit_user on userid = assignee " + - "where coalesce(versionid,-1) = ? " + "where coalesce(versionid,-1) = ? and coalesce(component, -1) = coalesce(?, component, -1)" ); find = connection.prepareStatement( - "select issueid, project, p.name as projectname, status, category, subject, i.description, " + + "select issueid, i.project, p.name as projectname, component, c.name as componentname, " + + "status, category, subject, i.description, " + "userid, username, givenname, lastname, mail, " + "created, updated, eta " + "from lpit_issue i " + - "left join lpit_project p on project = projectid " + + "join lpit_project p on i.project = projectid " + + "left join lpit_component c on component = c.id " + "left join lpit_user on userid = assignee " + "where issueid = ? "); insert = connection.prepareStatement( - "insert into lpit_issue (project, status, category, subject, description, assignee, eta) " + + "insert into lpit_issue (project, component, status, category, subject, description, assignee, eta) " + "values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?) returning issueid" ); update = connection.prepareStatement( - "update lpit_issue set updated = now(), status = ?::issue_status, category = ?::issue_category, " + + "update lpit_issue set " + + "updated = now(), component = ?, status = ?::issue_status, category = ?::issue_category, " + "subject = ?, description = ?, assignee = ?, eta = ? where issueid = ?" ); @@ -123,8 +128,15 @@ private Issue mapColumns(ResultSet result) throws SQLException { final var project = new Project(result.getInt("project")); project.setName(result.getString("projectname")); + var component = new Component(result.getInt("component")); + if (result.wasNull()) { + component = null; + } else { + component.setName(result.getString("componentname")); + } final var issue = new Issue(result.getInt("issueid")); issue.setProject(project); + issue.setComponent(component); issue.setStatus(IssueStatus.valueOf(result.getString("status"))); issue.setCategory(IssueCategory.valueOf(result.getString("category"))); issue.setSubject(result.getString("subject")); @@ -161,17 +173,24 @@ } } + private int setData(PreparedStatement stmt, int column, Issue instance) throws SQLException { + setForeignKeyOrNull(stmt, ++column, instance.getComponent(), Component::getId); + stmt.setString(++column, instance.getStatus().name()); + stmt.setString(++column, instance.getCategory().name()); + stmt.setString(++column, instance.getSubject()); + setStringOrNull(stmt, ++column, instance.getDescription()); + setForeignKeyOrNull(stmt, ++column, instance.getAssignee(), User::getId); + setDateOrNull(stmt, ++column, instance.getEta()); + return column; + } + @Override public void save(Issue instance, Project project) throws SQLException { Objects.requireNonNull(instance.getSubject()); instance.setProject(project); - insert.setInt(1, instance.getProject().getId()); - insert.setString(2, instance.getStatus().name()); - insert.setString(3, instance.getCategory().name()); - insert.setString(4, instance.getSubject()); - setStringOrNull(insert, 5, instance.getDescription()); - setForeignKeyOrNull(insert, 6, instance.getAssignee(), User::getId); - setDateOrNull(insert, 7, instance.getEta()); + int column = 0; + insert.setInt(++column, instance.getProject().getId()); + setData(insert, column, instance); // insert and retrieve the ID final var rs = insert.executeQuery(); rs.next(); @@ -183,13 +202,8 @@ public boolean update(Issue instance) throws SQLException { if (instance.getId() < 0) return false; Objects.requireNonNull(instance.getSubject()); - update.setString(1, instance.getStatus().name()); - update.setString(2, instance.getCategory().name()); - update.setString(3, instance.getSubject()); - setStringOrNull(update, 4, instance.getDescription()); - setForeignKeyOrNull(update, 5, instance.getAssignee(), User::getId); - setDateOrNull(update, 6, instance.getEta()); - update.setInt(7, instance.getId()); + int column = setData(update, 0, instance); + update.setInt(++column, instance.getId()); boolean success = update.executeUpdate() > 0; if (success) { updateVersionLists(instance); @@ -199,8 +213,7 @@ } } - private List<Issue> list(PreparedStatement query, int arg) throws SQLException { - query.setInt(1, arg); + private List<Issue> executeQuery(PreparedStatement query) throws SQLException { List<Issue> issues = new ArrayList<>(); try (var result = query.executeQuery()) { while (result.next()) { @@ -212,12 +225,30 @@ @Override public List<Issue> list(Project project) throws SQLException { - return list(list, project.getId()); + list.setInt(1, project.getId()); + list.setNull(2, Types.INTEGER); + return executeQuery(list); } @Override - public List<Issue> list(Version version) throws SQLException { - return list(listForVersion, version == null ? -1 : version.getId()); + public List<Issue> list(Project project, Component component, Version version) throws SQLException { + listForVersion.setInt(1, Optional.ofNullable(version).map(Version::getId).orElse(-1)); + listForVersion.setInt(2, Optional.ofNullable(component).map(Component::getId).orElse(-1)); + return executeQuery(listForVersion); + } + + @Override + public List<Issue> list(Project project, Version version) throws SQLException { + listForVersion.setInt(1, Optional.ofNullable(version).map(Version::getId).orElse(-1)); + listForVersion.setNull(2, Types.INTEGER); + return executeQuery(listForVersion); + } + + @Override + public List<Issue> list(Project project, Component component) throws SQLException { + list.setInt(1, project.getId()); + list.setInt(2, Optional.ofNullable(component).map(Component::getId).orElse(-1)); + return executeQuery(list); } @Override
--- a/src/main/java/de/uapcore/lightpit/entities/Component.java Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/entities/Component.java Sat Oct 17 19:56:50 2020 +0200 @@ -38,7 +38,9 @@ private String name; - private WebColor color; + private String node; + + private WebColor color = new WebColor("000000"); private int ordinal = 0; @@ -66,6 +68,14 @@ this.name = name; } + public String getNode() { + return node == null ? String.valueOf(id) : node; + } + + public void setNode(String node) { + this.node = node; + } + public WebColor getColor() { return color; }
--- a/src/main/java/de/uapcore/lightpit/entities/Issue.java Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/entities/Issue.java Sat Oct 17 19:56:50 2020 +0200 @@ -39,6 +39,7 @@ private int id; private Project project; + private Component component; private IssueStatus status; private IssueCategory category; @@ -78,6 +79,14 @@ return project; } + public Component getComponent() { + return component; + } + + public void setComponent(Component component) { + this.component = component; + } + public IssueStatus getStatus() { return status; }
--- a/src/main/java/de/uapcore/lightpit/entities/Project.java Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/entities/Project.java Sat Oct 17 19:56:50 2020 +0200 @@ -34,6 +34,7 @@ private final int id; private String name; + private String node; private String description; private String repoUrl; private User owner; @@ -54,6 +55,14 @@ this.name = name; } + public String getNode() { + return node == null ? String.valueOf(id) : node; + } + + public void setNode(String node) { + this.node = node; + } + public String getDescription() { return description; }
--- a/src/main/java/de/uapcore/lightpit/entities/Version.java Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/entities/Version.java Sat Oct 17 19:56:50 2020 +0200 @@ -34,6 +34,7 @@ private final int id; private String name; + private String node; /** * If we do not want versions to be ordered lexicographically we may specify an order. */ @@ -56,6 +57,14 @@ this.name = name; } + public String getNode() { + return node == null ? String.valueOf(id) : node; + } + + public void setNode(String node) { + this.node = node; + } + public int getOrdinal() { return ordinal; }
--- a/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/modules/ProjectsModule.java Sat Oct 17 19:56:50 2020 +0200 @@ -32,6 +32,7 @@ import de.uapcore.lightpit.*; import de.uapcore.lightpit.dao.DataAccessObjects; import de.uapcore.lightpit.entities.*; +import de.uapcore.lightpit.types.WebColor; import de.uapcore.lightpit.viewmodel.*; import de.uapcore.lightpit.viewmodel.util.IssueSorter; import org.slf4j.Logger; @@ -43,6 +44,7 @@ import java.io.IOException; import java.sql.Date; import java.sql.SQLException; +import java.util.List; import java.util.NoSuchElementException; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -84,19 +86,30 @@ } // Select Version - final int vid = Functions.parseIntOrZero(pathParameters.get("version")); - if (vid > 0) { - viewModel.setVersionFilter(versionDao.find(vid)); + final var pathParamVersion = pathParameters.get("version"); + if ("no-version".equals(pathParamVersion)) { + viewModel.setVersionFilter(ProjectView.NO_VERSION); + } else if ("all-versions".equals(pathParamVersion)) { + viewModel.setVersionFilter(ProjectView.ALL_VERSIONS); + } else { + final int vid = Functions.parseIntOrZero(pathParamVersion); + if (vid > 0) { + viewModel.setVersionFilter(versionDao.find(vid)); + } } - // TODO: don't treat unknown == unassigned - send 404 for unknown and introduce special word for unassigned // Select Component - final int cid = Functions.parseIntOrZero(pathParameters.get("component")); - if (cid > 0) { - viewModel.setComponentFilter(componentDao.find(cid)); + final var pathParamComponent = pathParameters.get("component"); + if ("no-component".equals(pathParamComponent)) { + viewModel.setComponentFilter(ProjectView.NO_COMPONENT); + } else if ("all-components".equals(pathParamComponent)) { + viewModel.setComponentFilter(ProjectView.ALL_COMPONENTS); + } else { + final int cid = Functions.parseIntOrZero(pathParamComponent); + if (cid > 0) { + viewModel.setComponentFilter(componentDao.find(cid)); + } } - - // TODO: distinguish all/unassigned for components } private ResponseType forwardView(HttpServletRequest req, ProjectView viewModel, String name) { @@ -133,7 +146,7 @@ final var viewModel = new ProjectEditView(); populate(viewModel, pathParams, dao); - if (viewModel.getProjectInfo() == null) { + if (!viewModel.isProjectInfoPresent()) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return ResponseType.NONE; } @@ -176,28 +189,60 @@ } } - @RequestMapping(requestPath = "$project/versions/$version", method = HttpMethod.GET) - public ResponseType view(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParams, DataAccessObjects dao) throws SQLException, IOException { + @RequestMapping(requestPath = "$project/$component/$version/issues/", method = HttpMethod.GET) + public ResponseType issues(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParams, DataAccessObjects dao) throws SQLException, IOException { final var viewModel = new ProjectDetailsView(); populate(viewModel, pathParams, dao); - final var version = viewModel.getVersionFilter(); - if (viewModel.getProjectInfo() == null || version == null) { + if (!viewModel.isEveryFilterValid()) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return ResponseType.NONE; } + final var project = viewModel.getProjectInfo().getProject(); + final var version = viewModel.getVersionFilter(); + final var component = viewModel.getComponentFilter(); + final var issueDao = dao.getIssueDao(); - final var detailView = viewModel.getProjectDetails(); - final var issues = issueDao.list(version); + final List<Issue> issues; + if (version.equals(ProjectView.NO_VERSION)) { + if (component.equals(ProjectView.ALL_COMPONENTS)) { + issues = issueDao.list(project, (Version) null); + } else if (component.equals(ProjectView.NO_COMPONENT)) { + issues = issueDao.list(project, null, null); + } else { + issues = issueDao.list(project, component, null); + } + } else if (version.equals(ProjectView.ALL_VERSIONS)) { + if (component.equals(ProjectView.ALL_COMPONENTS)) { + issues = issueDao.list(project); + } else if (component.equals(ProjectView.NO_COMPONENT)) { + issues = issueDao.list(project, (Component)null); + } else { + issues = issueDao.list(project, component); + } + } else { + if (component.equals(ProjectView.ALL_COMPONENTS)) { + issues = issueDao.list(project, version); + } else if (component.equals(ProjectView.NO_COMPONENT)) { + issues = issueDao.list(project, null, version); + } else { + issues = issueDao.list(project, component, version); + } + } + for (var issue : issues) issueDao.joinVersionInformation(issue); issues.sort(new IssueSorter( new IssueSorter.Criteria(IssueSorter.Field.PHASE, true), new IssueSorter.Criteria(IssueSorter.Field.ETA, true), new IssueSorter.Criteria(IssueSorter.Field.UPDATED, false) )); - detailView.updateDetails(issues, version); + + + viewModel.getProjectDetails().updateDetails(issues); + if (version.getId() > 0) + viewModel.getProjectDetails().updateVersionInfo(version); return forwardView(req, viewModel, "project-details"); } @@ -262,7 +307,6 @@ version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow())); dao.getVersionDao().saveOrUpdate(version, project); - // TODO: improve building the redirect location setRedirectLocation(req, "./projects/" + project.getId() + "/versions/"); setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) { @@ -274,11 +318,90 @@ return ResponseType.HTML; } + @RequestMapping(requestPath = "$project/components/", method = HttpMethod.GET) + public ResponseType components(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObjects dao) throws IOException, SQLException { + final var viewModel = new ComponentsView(); + populate(viewModel, pathParameters, dao); + + final var projectInfo = viewModel.getProjectInfo(); + if (projectInfo == null) { + resp.sendError(HttpServletResponse.SC_NOT_FOUND); + return ResponseType.NONE; + } + + final var issueDao = dao.getIssueDao(); + final var issues = issueDao.list(projectInfo.getProject()); + viewModel.update(projectInfo.getComponents(), issues); + + return forwardView(req, viewModel, "components"); + } + + @RequestMapping(requestPath = "$project/components/$component/edit", method = HttpMethod.GET) + public ResponseType editComponent(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObjects dao) throws IOException, SQLException { + final var viewModel = new ComponentEditView(); + populate(viewModel, pathParameters, dao); + + if (viewModel.getProjectInfo() == null || viewModel.getComponentFilter() == null) { + resp.sendError(HttpServletResponse.SC_NOT_FOUND); + return ResponseType.NONE; + } + + viewModel.setComponent(viewModel.getComponentFilter()); + viewModel.setUsers(dao.getUserDao().list()); + + return forwardView(req, viewModel, "component-form"); + } + + @RequestMapping(requestPath = "$project/create-component", method = HttpMethod.GET) + public ResponseType createComponent(HttpServletRequest req, HttpServletResponse resp, PathParameters pathParameters, DataAccessObjects dao) throws IOException, SQLException { + final var viewModel = new ComponentEditView(); + populate(viewModel, pathParameters, dao); + + if (viewModel.getProjectInfo() == null) { + resp.sendError(HttpServletResponse.SC_NOT_FOUND); + return ResponseType.NONE; + } + + viewModel.setComponent(new Component(-1)); + viewModel.setUsers(dao.getUserDao().list()); + + return forwardView(req, viewModel, "component-form"); + } + + @RequestMapping(requestPath = "commit-component", method = HttpMethod.POST) + public ResponseType commitComponent(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws IOException { + + try { + final var project = new Project(getParameter(req, Integer.class, "pid").orElseThrow()); + final var component = new Component(getParameter(req, Integer.class, "id").orElseThrow()); + component.setName(getParameter(req, String.class, "name").orElseThrow()); + component.setColor(getParameter(req, WebColor.class, "color").orElseThrow()); + getParameter(req, Integer.class, "ordinal").ifPresent(component::setOrdinal); + getParameter(req, Integer.class, "lead").map( + userid -> userid >= 0 ? new User(userid) : null + ).ifPresent(component::setLead); + getParameter(req, String.class, "description").ifPresent(component::setDescription); + + dao.getComponentDao().saveOrUpdate(component, project); + + setRedirectLocation(req, "./projects/" + project.getId() + "/components/"); + setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); + } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) { + resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED); + // TODO: implement - fix issue #21 + return ResponseType.NONE; + } + + return ResponseType.HTML; + } + private void configureIssueEditor(IssueEditView viewModel, Issue issue, DataAccessObjects dao) throws SQLException { - issue.setProject(viewModel.getProjectInfo().getProject()); + final var project = viewModel.getProjectInfo().getProject(); + issue.setProject(project); viewModel.setIssue(issue); viewModel.configureVersionSelectors(viewModel.getProjectInfo().getVersions()); viewModel.setUsers(dao.getUserDao().list()); + viewModel.setComponents(dao.getComponentDao().list(project)); if (issue.getId() >= 0) { viewModel.setComments(dao.getIssueDao().listComments(issue)); } @@ -337,6 +460,9 @@ getParameter(req, Integer.class, "assignee").map( userid -> userid >= 0 ? new User(userid) : null ).ifPresent(issue::setAssignee); + getParameter(req, Integer.class, "component").map( + cid -> cid >= 0 ? new Component(cid) : null + ).ifPresent(issue::setComponent); getParameter(req, String.class, "description").ifPresent(issue::setDescription); getParameter(req, Date.class, "eta").ifPresent(issue::setEta); @@ -354,7 +480,7 @@ dao.getIssueDao().saveOrUpdate(issue, issue.getProject()); // TODO: fix issue #14 - setRedirectLocation(req, "./projects/" + issue.getProject().getId() + "/versions/"); + setRedirectLocation(req, "./projects/" + issue.getProject().getId() + "/all-components/all-versions/issues/"); setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL); return ResponseType.HTML;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/ComponentEditView.java Sat Oct 17 19:56:50 2020 +0200 @@ -0,0 +1,40 @@ +package de.uapcore.lightpit.viewmodel; + +import de.uapcore.lightpit.entities.Component; +import de.uapcore.lightpit.entities.User; + +import java.util.List; + +public class ComponentEditView extends ProjectView { + private Component component; + private List<User> users; + private String errorText; + + public ComponentEditView() { + setSelectedPage(SELECTED_PAGE_COMPONENTS); + } + + public void setComponent(Component component) { + this.component = component; + } + + public Component getComponent() { + return component; + } + + public List<User> getUsers() { + return users; + } + + public void setUsers(List<User> users) { + this.users = users; + } + + public String getErrorText() { + return errorText; + } + + public void setErrorText(String errorText) { + this.errorText = errorText; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/ComponentInfo.java Sat Oct 17 19:56:50 2020 +0200 @@ -0,0 +1,42 @@ +package de.uapcore.lightpit.viewmodel; + +import de.uapcore.lightpit.entities.Component; +import de.uapcore.lightpit.entities.Issue; +import de.uapcore.lightpit.entities.IssueSummary; + +import java.util.ArrayList; +import java.util.List; + +public class ComponentInfo { + + private final Component component; + + private final IssueSummary issueSummary = new IssueSummary(); + + private final List<Issue> issues = new ArrayList<>(); + + public ComponentInfo(Component component) { + this.component = component; + } + + public Component getComponent() { + return component; + } + + public IssueSummary getIssueSummary() { + return issueSummary; + } + + public List<Issue> getIssues() { + return issues; + } + + public void collectIssues(List<Issue> issues) { + for (Issue issue : issues) { + if (component.equals(issue.getComponent())) { + this.issues.add(issue); + this.issueSummary.add(issue); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/ComponentsView.java Sat Oct 17 19:56:50 2020 +0200 @@ -0,0 +1,29 @@ +package de.uapcore.lightpit.viewmodel; + +import de.uapcore.lightpit.entities.Component; +import de.uapcore.lightpit.entities.Issue; + +import java.util.ArrayList; +import java.util.List; + +public class ComponentsView extends ProjectView { + + private List<ComponentInfo> componentInfos = new ArrayList<>(); + + public ComponentsView() { + setSelectedPage(SELECTED_PAGE_COMPONENTS); + } + + public void update(List<Component> components, List<Issue> issues) { + componentInfos.clear(); + for (var component : components) { + final var info = new ComponentInfo(component); + info.collectIssues(issues); + componentInfos.add(info); + } + } + + public List<ComponentInfo> getComponentInfos() { + return componentInfos; + } +}
--- a/src/main/java/de/uapcore/lightpit/viewmodel/IssueEditView.java Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/IssueEditView.java Sat Oct 17 19:56:50 2020 +0200 @@ -11,6 +11,7 @@ private Set<Version> versionsUpcoming = new HashSet<>(); private Set<Version> versionsRecent = new HashSet<>(); private List<User> users; + private List<Component> components; private List<IssueComment> comments; public void setIssue(Issue issue) { @@ -45,7 +46,8 @@ versionsUpcoming.addAll(issue.getResolvedVersions()); for (var v : versions) { if (v.getStatus().isReleased()) { - versionsRecent.add(v); + if (!v.getStatus().equals(VersionStatus.Deprecated)) + versionsRecent.add(v); } else { versionsUpcoming.add(v); } @@ -60,6 +62,14 @@ this.users = users; } + public List<Component> getComponents() { + return components; + } + + public void setComponents(List<Component> components) { + this.components = components; + } + public IssueStatus[] getIssueStatus() { return IssueStatus.values(); }
--- a/src/main/java/de/uapcore/lightpit/viewmodel/ProjectDetails.java Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/ProjectDetails.java Sat Oct 17 19:56:50 2020 +0200 @@ -13,14 +13,15 @@ private List<Issue> issues; private IssueSummary issueSummary; - public void updateDetails(List<Issue> issues, Version version) { + public void updateDetails(List<Issue> issues) { this.issues = issues; issueSummary = new IssueSummary(); issues.forEach(issueSummary::add); - if (version != null) { - versionInfo = new VersionInfo(version); - versionInfo.collectIssues(issues); - } + } + + public void updateVersionInfo(Version version) { + versionInfo = new VersionInfo(version); + versionInfo.collectIssues(issues); } public List<Issue> getIssues() {
--- a/src/main/java/de/uapcore/lightpit/viewmodel/ProjectView.java Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/ProjectView.java Sat Oct 17 19:56:50 2020 +0200 @@ -8,11 +8,29 @@ public class ProjectView { + public static final int SELECTED_PAGE_ISSUES = 0; + public static final int SELECTED_PAGE_VERSIONS = 1; + public static final int SELECTED_PAGE_COMPONENTS = 2; + + public static final Version ALL_VERSIONS = new Version(0); + public static final Version NO_VERSION = new Version(-1); + public static final Component ALL_COMPONENTS = new Component(0); + public static final Component NO_COMPONENT = new Component(-1); + + static { + ALL_VERSIONS.setNode("all-versions"); + NO_VERSION.setNode("no-version"); + ALL_COMPONENTS.setNode("all-components"); + NO_COMPONENT.setNode("no-component"); + } + private final List<ProjectInfo> projectList = new ArrayList<>(); private ProjectInfo projectInfo; private Version versionFilter; private Component componentFilter; + private int selectedPage = SELECTED_PAGE_ISSUES; + public List<ProjectInfo> getProjectList() { return projectList; } @@ -25,6 +43,14 @@ this.projectInfo = projectInfo; } + public int getSelectedPage() { + return selectedPage; + } + + public void setSelectedPage(int selectedPage) { + this.selectedPage = selectedPage; + } + public Version getVersionFilter() { return versionFilter; } @@ -40,4 +66,20 @@ public void setComponentFilter(Component componentFilter) { this.componentFilter = componentFilter; } + + public boolean isProjectInfoPresent() { + return projectInfo != null; + } + + public boolean isVersionFilterValid() { + return projectInfo != null && versionFilter != null; + } + + public boolean isComponentFilterValid() { + return projectInfo != null && componentFilter != null; + } + + public boolean isEveryFilterValid() { + return projectInfo != null && versionFilter != null && componentFilter != null; + } }
--- a/src/main/java/de/uapcore/lightpit/viewmodel/VersionEditView.java Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/VersionEditView.java Sat Oct 17 19:56:50 2020 +0200 @@ -7,6 +7,10 @@ private Version version; private String errorText; + public VersionEditView() { + setSelectedPage(SELECTED_PAGE_VERSIONS); + } + public void setVersion(Version version) { this.version = version; }
--- a/src/main/java/de/uapcore/lightpit/viewmodel/VersionsView.java Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/java/de/uapcore/lightpit/viewmodel/VersionsView.java Sat Oct 17 19:56:50 2020 +0200 @@ -8,18 +8,22 @@ public class VersionsView extends ProjectView { - private List<VersionInfo> versionInfo = new ArrayList<>(); + private List<VersionInfo> versionInfos = new ArrayList<>(); + + public VersionsView() { + setSelectedPage(SELECTED_PAGE_VERSIONS); + } public void update(List<Version> versions, List<Issue> issues) { - versionInfo.clear(); + versionInfos.clear(); for (var version : versions) { final var info = new VersionInfo(version); info.collectIssues(issues); - versionInfo.add(info); + versionInfos.add(info); } } - public List<VersionInfo> getVersionInfo() { - return versionInfo; + public List<VersionInfo> getVersionInfos() { + return versionInfos; } }
--- a/src/main/resources/localization/projects.properties Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/resources/localization/projects.properties Sat Oct 17 19:56:50 2020 +0200 @@ -24,6 +24,8 @@ pageTitle=Project Tracking button.create=New Project +button.component.create=New Component +button.component.edit=Edit Component button.version.create=New Version button.version.edit=Edit Version button.issue.create=New Issue @@ -43,6 +45,8 @@ owner=Project Lead version.latest=Latest Version version.next=Next Version +issues=Issues +component=Component progress=Overall Progress @@ -56,10 +60,20 @@ version.name=Version version.status=Status version.ordinal=Ordering + +component.project=Project +component.name=Component +component.color=Color +component.lead=Lead +component.ordinal=Ordering +component.description=Description + tooltip.ordinal=Use to override lexicographic ordering. placeholder.null-owner=Unassigned +placeholder.null-lead=Unassigned placeholder.null-assignee=Unassigned +placeholder.null-component=Unassigned version.status.Future=Future version.status.Unreleased=Unreleased @@ -67,10 +81,10 @@ version.status.LTS=LTS version.status.Deprecated=Deprecated - issue.without-version=No Assigned Version issue.id=Issue ID issue.project=Project +issue.component=Component issue.subject=Subject issue.description=Description issue.assignee=Assignee
--- a/src/main/resources/localization/projects_de.properties Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/resources/localization/projects_de.properties Sat Oct 17 19:56:50 2020 +0200 @@ -24,6 +24,8 @@ pageTitle=Projektverwaltung button.create=Neues Projekt +button.component.create=Neue Komponente +button.component.edit=Komponente Bearbeiten button.version.create=Neue Version button.version.edit=Version Bearbeiten button.issue.create=Neuer Vorgang @@ -43,6 +45,8 @@ owner=Projektleitung version.latest=Neuste Version version.next=N\u00e4chste Version +component=Komponente +issues=Vorg\u00e4nge progress=Gesamtfortschritt @@ -56,10 +60,20 @@ version.name=Version version.status=Status version.ordinal=Sequenznummer + +component.project=Projekt +component.name=Komponente +component.color=Farbe +component.lead=Leitung +component.ordinal=Sequenznummer +component.description=Beschreibung + tooltip.ordinal=\u00dcbersteuert die lexikographische Sortierung. placeholder.null-owner=Nicht Zugewiesen +placeholder.null-lead=Niemand placeholder.null-assignee=Niemandem +placeholder.null-component=Keine version.status.Future=Geplant version.status.Unreleased=Unver\u00f6ffentlicht @@ -70,6 +84,7 @@ issue.without-version=Keine Version zugeordnet issue.id=Vorgangs-ID issue.project=Projekt +issue.component=Komponente issue.subject=Thema issue.description=Beschreibung issue.assignee=Zugewiesen
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/webapp/WEB-INF/jsp/component-form.jsp Sat Oct 17 19:56:50 2020 +0200 @@ -0,0 +1,95 @@ +<%-- +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.ComponentEditView" scope="request" /> +<c:set var="component" scope="page" value="${viewmodel.component}"/> +<c:set var="project" scope="page" value="${viewmodel.projectInfo.project}"/> + +<form action="./projects/commit-component" method="post"> + <table class="formtable" style="width: 70ch"> + <colgroup> + <col> + <col style="width: 100%"> + </colgroup> + <tbody> + <tr> + <th><fmt:message key="component.project"/></th> + <td> + <c:out value="${project.name}" /> + <input type="hidden" name="pid" value="${project.id}" /> + </td> + </tr> + <tr> + <th><fmt:message key="component.name"/></th> + <td><input name="name" type="text" maxlength="20" required value="<c:out value="${component.name}"/>" /></td> + </tr> + <tr> + <th><fmt:message key="component.color"/></th> + <td><input name="color" type="color" required value="${component.color}" /></td> + </tr> + <tr> + <th><fmt:message key="component.lead"/></th> + <td> + <select name="lead"> + <option value="-1"><fmt:message key="placeholder.null-lead"/></option> + <c:forEach var="user" items="${viewmodel.users}"> + <option + <c:if test="${not empty component.lead and user eq component.lead}">selected</c:if> + value="${user.id}"><c:out value="${user.displayname}"/></option> + </c:forEach> + </select> + </td> + </tr> + <tr title="<fmt:message key="tooltip.ordinal" />"> + <th><fmt:message key="component.ordinal"/></th> + <td> + <input name="ordinal" type="number" min="0" value="${component.ordinal}"/> + </td> + </tr> + <tr> + <th class="vtop"><fmt:message key="component.description"/></th> + <td> + <textarea name="description" rows="5"><c:out value="${component.description}"/></textarea> + </td> + </tr> + </tbody> + <tfoot> + <tr> + <td colspan="2"> + <input type="hidden" name="id" value="${component.id}"/> + <a href="./projects/${project.node}/components/" class="button"> + <fmt:message bundle="${lightpit_bundle}" key="button.cancel"/> + </a> + <button type="submit"><fmt:message bundle="${lightpit_bundle}" key="button.okay"/></button> + </td> + </tr> + </tfoot> + </table> +</form>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/webapp/WEB-INF/jsp/components.jsp Sat Oct 17 19:56:50 2020 +0200 @@ -0,0 +1,95 @@ +<%-- +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.ComponentsView" scope="request" /> + +<c:set var="project" scope="page" value="${viewmodel.projectInfo.project}"/> + +<%@include file="../jspf/project-header.jspf"%> + +<div id="tool-area"> + <a href="./projects/${project.id}/create-component" class="button"><fmt:message key="button.component.create"/></a> + <a href="./projects/${project.id}/create-issue" 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.jspf"%> + +<table id="version-list" class="datatable medskip fullwidth"> + <colgroup> + <col> + <col width="20%"> + <col width="44%"> + <col width="12%"> + <col width="12%"> + <col width="12%"> + </colgroup> + <thead> + <tr> + <th colspan="3"></th> + <th colspan="3" class="hcenter"> + <fmt:message key="issues"/> + </th> + </tr> + <tr> + <th></th> + <th><fmt:message key="component.name"/></th> + <th><fmt:message key="component.description"/></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="componentInfo" items="${viewmodel.componentInfos}" > + <tr> + <td rowspan="2" style="width: 2em;"><a href="./projects/${project.node}/components/${componentInfo.component.node}/edit">✎</a></td> + <td rowspan="2"> + <div class="navmenu-icon" style="background-color: ${componentInfo.component.color}"></div> + <a href="./projects/${project.node}/${componentInfo.component.node}/all-versions/issues/"> + <c:out value="${componentInfo.component.name}"/> + </a> + </td> + <td rowspan="2"><c:out value="${componentInfo.component.description}"/> </td> + <td class="hright">${componentInfo.issueSummary.open}</td> + <td class="hright">${componentInfo.issueSummary.active}</td> + <td class="hright">${componentInfo.issueSummary.done}</td> + </tr> + <tr> + <td colspan="3"> + <c:set var="summary" value="${componentInfo.issueSummary}"/> + <%@include file="../jspf/issue-progress.jspf" %> + </td> + </tr> + </c:forEach> + </tbody> +</table> \ No newline at end of file
--- a/src/main/webapp/WEB-INF/jsp/issue-form.jsp Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/webapp/WEB-INF/jsp/issue-form.jsp Sat Oct 17 19:56:50 2020 +0200 @@ -74,6 +74,19 @@ <td><fmt:formatDate value="${issue.updated}" /></td> </tr> <tr> + <th><fmt:message key="issue.component"/></th> + <td> + <select name="component"> + <option value="-1"><fmt:message key="placeholder.null-component"/></option> + <c:forEach var="component" items="${viewmodel.components}"> + <option + <c:if test="${not empty issue.component and component eq issue.component}">selected</c:if> + value="${component.id}"><c:out value="${component.name}"/></option> + </c:forEach> + </select> + </td> + </tr> + <tr> <th><fmt:message key="issue.category"/></th> <td> <select name="category"> @@ -154,7 +167,7 @@ <td colspan="2"> <input type="hidden" name="id" value="${issue.id}"/> <%-- TODO: fix #14 --%> - <a href="./projects/${issue.project.id}/versions/" class="button"> + <a href="./projects/${issue.project.node}/versions/" class="button"> <fmt:message bundle="${lightpit_bundle}" key="button.cancel"/> </a> <button type="submit"><fmt:message bundle="${lightpit_bundle}" key="button.okay"/></button>
--- a/src/main/webapp/WEB-INF/jsp/issues.jsp Sat Oct 17 15:21:56 2020 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,57 +0,0 @@ -<%-- -DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - -Copyright 2018 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" %> - -<h1>TODO: REWRITE THIS PAGE</h1> -<%-- -TODO: rewrite -<jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.IssuesView" scope="request"/> -<c:set var="project" scope="page" value="${viewmodel.project}"/> -<c:set var="version" scope="page" value="${viewmodel.version}"/> -<%@include file="../jspf/project-header.jspf"%> - -<c:if test="${not empty version}"> - <h2> - <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> - -<div id="tool-area"> - <div> - <a href="./projects/issues/edit" class="button"><fmt:message key="button.issue.create"/></a> - <c:if test="${not empty version}"> - <a href="./projects/issues/?pid=${project.id}&vid=-1" class="button"><fmt:message key="button.issue.all"/></a> - </c:if> - </div> -</div> - -<c:set var="issues" value="${viewmodel.issues}"/> -<%@include file="../jspf/issue-list.jspf"%> ---%> \ No newline at end of file
--- a/src/main/webapp/WEB-INF/jsp/project-details.jsp Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/webapp/WEB-INF/jsp/project-details.jsp Sat Oct 17 19:56:50 2020 +0200 @@ -24,21 +24,26 @@ 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" %> +<%@page pageEncoding="UTF-8" import="de.uapcore.lightpit.viewmodel.ProjectView" %> <%@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.ProjectDetailsView" scope="request" /> <c:set var="project" scope="page" value="${viewmodel.projectInfo.project}"/> +<c:set var="component" scope="page" value="${viewmodel.componentFilter}"/> <%@include file="../jspf/project-header.jspf"%> <div id="tool-area"> - <c:if test="${not empty viewmodel.versionFilter}"> - <a href="./projects/${project.id}/versions/${viewmodel.versionFilter.id}/edit" class="button"><fmt:message key="button.version.edit"/></a> + <a href="./projects/${project.node}/create-issue" class="button"><fmt:message key="button.issue.create"/></a> + <c:if test="${viewmodel.versionFilter.id gt 0}"> + <a href="./projects/${project.node}/versions/${viewmodel.versionFilter.node}/edit" class="button"><fmt:message key="button.version.edit"/></a> </c:if> - <a href="./projects/${project.id}/create-version" class="button"><fmt:message key="button.version.create"/></a> - <a href="./projects/${project.id}/create-issue" class="button"><fmt:message key="button.issue.create"/></a> + <a href="./projects/${project.node}/create-version" class="button"><fmt:message key="button.version.create"/></a> + <c:if test="${viewmodel.componentFilter.id gt 0}"> + <a href="./projects/${project.node}/components/${viewmodel.componentFilter.node}/edit" class="button"><fmt:message key="button.component.edit"/></a> + </c:if> + <a href="./projects/${project.node}/create-component" class="button"><fmt:message key="button.component.create"/></a> </div> <h2><fmt:message key="progress" /></h2> @@ -46,36 +51,43 @@ <c:set var="summary" value="${viewmodel.projectInfo.issueSummary}" /> <%@include file="../jspf/issue-summary.jspf"%> -<c:if test="${not empty viewmodel.versionFilter}"> - <c:set var="versionInfo" value="${viewmodel.projectDetails.versionInfo}"/> - <h2> - <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> - <c:set var="summary" value="${versionInfo.resolvedTotal}"/> - <%@include file="../jspf/issue-summary.jspf"%> - <c:set var="issues" value="${versionInfo.resolved}"/> - <c:if test="${not empty issues}"> - <%@include file="../jspf/issue-list.jspf"%> - </c:if> +<c:choose> + <c:when test="${viewmodel.versionFilter eq ProjectView.NO_VERSION or viewmodel.versionFilter eq ProjectView.ALL_VERSIONS}"> + <h2> + <c:if test="${viewmodel.versionFilter eq ProjectView.NO_VERSION}"> + <fmt:message key="issue.without-version" /> + </c:if> + <c:if test="${viewmodel.versionFilter ne ProjectView.NO_VERSION}"> + <fmt:message key="issues" /> + </c:if> + </h2> + <c:set var="summary" value="${viewmodel.projectDetails.issueSummary}"/> + <c:set var="issues" value="${viewmodel.projectDetails.issues}"/> + <%@include file="../jspf/issue-summary.jspf"%> + <c:if test="${not empty issues}"> + <%@include file="../jspf/issue-list.jspf"%> + </c:if> + </c:when> + <c:otherwise> + <c:set var="versionInfo" value="${viewmodel.projectDetails.versionInfo}"/> + <h2> + <fmt:message key="version.name" /> <c:out value="${versionInfo.version.name}" /> - <fmt:message key="version.status.${versionInfo.version.status}"/> + </h2> - <c:set var="issues" value="${versionInfo.reported}"/> - <c:if test="${not empty issues}"> - <h3><fmt:message key="issues.reported"/> </h3> - <c:set var="summary" value="${versionInfo.reportedTotal}"/> + <h3><fmt:message key="issues.resolved"/> </h3> + <c:set var="summary" value="${versionInfo.resolvedTotal}"/> <%@include file="../jspf/issue-summary.jspf"%> - <%@include file="../jspf/issue-list.jspf"%> - </c:if> -</c:if> -<c:if test="${empty viewmodel.versionFilter}"> - <h2> - <fmt:message key="issue.without-version" /> - </h2> - <c:set var="summary" value="${viewmodel.projectDetails.issueSummary}"/> - <c:set var="issues" value="${viewmodel.projectDetails.issues}"/> - <%@include file="../jspf/issue-summary.jspf"%> - <c:if test="${not empty issues}"> - <%@include file="../jspf/issue-list.jspf"%> - </c:if> -</c:if> + <c:set var="issues" value="${versionInfo.resolved}"/> + <c:if test="${not empty issues}"> + <%@include file="../jspf/issue-list.jspf"%> + </c:if> + + <c:set var="issues" value="${versionInfo.reported}"/> + <c:if test="${not empty issues}"> + <h3><fmt:message key="issues.reported"/> </h3> + <c:set var="summary" value="${versionInfo.reportedTotal}"/> + <%@include file="../jspf/issue-summary.jspf"%> + <%@include file="../jspf/issue-list.jspf"%> + </c:if> + </c:otherwise> +</c:choose> \ No newline at end of file
--- a/src/main/webapp/WEB-INF/jsp/project-navmenu.jsp Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/webapp/WEB-INF/jsp/project-navmenu.jsp Sat Oct 17 19:56:50 2020 +0200 @@ -24,7 +24,7 @@ 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" %> +<%@page pageEncoding="UTF-8" import="de.uapcore.lightpit.viewmodel.ProjectView" %> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> @@ -33,20 +33,27 @@ <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/${projectInfo.project.id}/versions/"> + <a href="projects/${projectInfo.project.node}/versions/"> <c:out value="${projectInfo.project.name}"/> </a> </div> <c:if test="${isActive}"> <!-- VERSIONS --> - <div class="menuEntry level-1"> - <a href="projects/${projectInfo.project.id}/versions/"> + <c:set var="componentNode" value="${not empty viewmodel.componentFilter ? viewmodel.componentFilter.node : 'all-components'}"/> + <div class="menuEntry level-1" <c:if test="${viewmodel.selectedPage eq ProjectView.SELECTED_PAGE_VERSIONS}">data-active</c:if> > + <a href="projects/${projectInfo.project.node}/versions/"> <fmt:message key="navmenu.versions"/> </a> </div> - <div class="menuEntry level-2"> + <div class="menuEntry level-2" <c:if test="${viewmodel.versionFilter eq ProjectView.ALL_VERSIONS}">data-active</c:if>> <div class="navmenu-icon" style="background: black"></div> - <a href="projects/${projectInfo.project.id}/versions/unassigned"> + <a href="projects/${projectInfo.project.node}/${componentNode}/all-versions/issues/"> + <fmt:message key="navmenu.all" /> + </a> + </div> + <div class="menuEntry level-2" <c:if test="${viewmodel.versionFilter eq ProjectView.NO_VERSION}">data-active</c:if>> + <div class="navmenu-icon" style="background: black"></div> + <a href="projects/${projectInfo.project.node}/${componentNode}/no-version/issues/"> <fmt:message key="navmenu.unassigned" /> </a> </div> @@ -55,39 +62,38 @@ <div class="menuEntry level-2" <c:if test="${isVersionActive}">data-active</c:if> title="<fmt:message key="version.status.${version.status}" />"> <div class="navmenu-icon version-${version.status}"></div> - <a href="projects/${projectInfo.project.id}/versions/${version.id}"> + <a href="projects/${projectInfo.project.node}/${componentNode}/${version.node}/issues/"> <c:out value="${version.name}"/> </a> </div> </c:forEach> - <%-- COMPONENTS - TODO: find a way to combine version and component into one URL - <div class="menuEntry level-1"> - <a href="projects/${projectInfo.project.id}/components/"> + <!-- COMPONENTS --> + <c:set var="versionNode" value="${not empty viewmodel.versionFilter ? viewmodel.versionFilter.node : 'all-versions'}"/> + <div class="menuEntry level-1" <c:if test="${viewmodel.selectedPage eq ProjectView.SELECTED_PAGE_COMPONENTS}">data-active</c:if>> + <a href="projects/${projectInfo.project.node}/components/"> <fmt:message key="navmenu.components"/> </a> </div> - <div class="menuEntry level-2"> + <div class="menuEntry level-2" <c:if test="${viewmodel.componentFilter eq ProjectView.ALL_COMPONENTS}">data-active</c:if>> <div class="navmenu-icon" style="background: black"></div> - <a href="projects/${projectInfo.project.id}/components/"> + <a href="projects/${projectInfo.project.node}/all-components/${versionNode}/issues/"> <fmt:message key="navmenu.all" /> </a> </div> - <div class="menuEntry level-2"> + <div class="menuEntry level-2" <c:if test="${viewmodel.componentFilter eq ProjectView.NO_COMPONENT}">data-active</c:if>> <div class="navmenu-icon" style="background: black"></div> - <a href="projects/${projectInfo.project.id}/components/unassigned"> + <a href="projects/${projectInfo.project.node}/no-component/${versionNode}/issues/"> <fmt:message key="navmenu.unassigned" /> </a> </div> <c:forEach var="component" items="${viewmodel.projectInfo.components}"> <c:set var="isComponentActive" value="${viewmodel.componentFilter eq component}" /> - <div class="menuEntry level-2" <c:if test="${isVersionActive}">data-active</c:if> > + <div class="menuEntry level-2" <c:if test="${isComponentActive}">data-active</c:if> > <div class="navmenu-icon" style="background-color: ${component.color}"></div> - <a href="projects/view?pid=${projectInfo.project.id}&cid=${component.id}"> + <a href="projects/${projectInfo.project.node}/${component.node}/${versionNode}/issues/"> <c:out value="${component.name}"/> </a> </div> </c:forEach> - --%> </c:if> </c:forEach>
--- a/src/main/webapp/WEB-INF/jsp/projects.jsp Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/webapp/WEB-INF/jsp/projects.jsp Sat Oct 17 19:56:50 2020 +0200 @@ -68,8 +68,8 @@ <c:forEach var="projectInfo" items="${viewmodel.projectList}"> <c:set var="project" scope="page" value="${projectInfo.project}"/> <tr class="nowrap"> - <td style="width: 2em;"><a href="./projects/${project.id}/edit">✎</a></td> - <td><a href="./projects/${project.id}/versions/"><c:out value="${project.name}"/></a> + <td style="width: 2em;"><a href="./projects/${project.node}/edit">✎</a></td> + <td><a href="./projects/${project.node}/versions/"><c:out value="${project.name}"/></a> </td> <td> <c:if test="${not empty project.repoUrl}"> @@ -79,12 +79,12 @@ </td> <td class="hright"> <c:if test="${not empty projectInfo.latestVersion}"> - <a href="./projects/${project.id}/versions/${projectInfo.latestVersion.id}"><c:out value="${projectInfo.latestVersion.name}"/></a> + <a href="./projects/${project.node}/all-components/${projectInfo.latestVersion.node}/issues/"><c:out value="${projectInfo.latestVersion.name}"/></a> </c:if> </td> <td class="hright"> <c:if test="${not empty projectInfo.nextVersion}"> - <a href="./projects/${project.id}/versions/${projectInfo.nextVersion.id}"><c:out value="${projectInfo.nextVersion.name}"/></a> + <a href="./projects/${project.node}/all-components/${projectInfo.nextVersion.node}/issues/"><c:out value="${projectInfo.nextVersion.name}"/></a> </c:if> </td> <td class="hright">${projectInfo.issueSummary.open}</td>
--- a/src/main/webapp/WEB-INF/jsp/version-form.jsp Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/webapp/WEB-INF/jsp/version-form.jsp Sat Oct 17 19:56:50 2020 +0200 @@ -73,7 +73,7 @@ <tr> <td colspan="2"> <input type="hidden" name="id" value="${version.id}"/> - <a href="./projects/${project.id}/versions/" class="button"> + <a href="./projects/${project.node}/versions/" class="button"> <fmt:message bundle="${lightpit_bundle}" key="button.cancel"/> </a> <button type="submit"><fmt:message bundle="${lightpit_bundle}" key="button.okay"/></button>
--- a/src/main/webapp/WEB-INF/jsp/versions.jsp Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/webapp/WEB-INF/jsp/versions.jsp Sat Oct 17 19:56:50 2020 +0200 @@ -76,11 +76,11 @@ </tr> </thead> <tbody> - <c:forEach var="versionInfo" items="${viewmodel.versionInfo}" > + <c:forEach var="versionInfo" items="${viewmodel.versionInfos}" > <tr> - <td rowspan="2" style="width: 2em;"><a href="./projects/${project.id}/versions/${versionInfo.version.id}/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.id}/versions/${versionInfo.version.id}"> + <a href="./projects/${project.node}/all-components/${versionInfo.version.node}/issues/"> <c:out value="${versionInfo.version.name}"/> </a> <div class="version-tag version-${versionInfo.version.status}">
--- a/src/main/webapp/WEB-INF/jspf/project-header.jspf Sat Oct 17 15:21:56 2020 +0200 +++ b/src/main/webapp/WEB-INF/jspf/project-header.jspf Sat Oct 17 19:56:50 2020 +0200 @@ -1,5 +1,6 @@ <%-- project: Project +component: Component (optional) --%> <div class="table project-attributes"> <div class="row"> @@ -21,4 +22,19 @@ </c:if> </div> </div> + <c:if test="${not empty component and component.id gt 0}"> + <div class="row"> + <div class="caption"><fmt:message key="component"/>:</div> + <div><c:out value="${component.name}"/></div> + <div class="caption"><fmt:message key="component.lead"/>:</div> + <div> + <c:if test="${not empty component.lead}"> + <c:out value="${component.lead.displayname}"/> + </c:if> + <c:if test="${empty component.lead}"> + <fmt:message key="placeholder.null-lead"/> + </c:if> + </div> + </div> + </c:if> </div>