Sat, 29 Aug 2020 17:13:09 +0200
simplifies issues per version view and re-adds edit version button
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2018 Mike Becker. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 *
28 */
29 package de.uapcore.lightpit.modules;
32 import de.uapcore.lightpit.*;
33 import de.uapcore.lightpit.dao.DataAccessObjects;
34 import de.uapcore.lightpit.entities.*;
35 import de.uapcore.lightpit.viewmodel.*;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
39 import javax.servlet.annotation.WebServlet;
40 import javax.servlet.http.HttpServletRequest;
41 import javax.servlet.http.HttpServletResponse;
42 import java.io.IOException;
43 import java.sql.Date;
44 import java.sql.SQLException;
45 import java.util.ArrayList;
46 import java.util.NoSuchElementException;
47 import java.util.Optional;
48 import java.util.stream.Collectors;
49 import java.util.stream.Stream;
51 import static de.uapcore.lightpit.Functions.fqn;
53 @WebServlet(
54 name = "ProjectsModule",
55 urlPatterns = "/projects/*"
56 )
57 public final class ProjectsModule extends AbstractLightPITServlet {
59 private static final Logger LOG = LoggerFactory.getLogger(ProjectsModule.class);
61 private static final String SESSION_ATTR_SELECTED_PROJECT = fqn(ProjectsModule.class, "selected_project");
62 private static final String SESSION_ATTR_SELECTED_VERSION = fqn(ProjectsModule.class, "selected_version");
63 private static final String PARAMETER_SELECTED_PROJECT = "pid";
64 private static final String PARAMETER_SELECTED_VERSION = "vid";
66 @Override
67 protected String getResourceBundleName() {
68 return "localization.projects";
69 }
71 private String queryParams(Project p, Version v) {
72 return String.format("pid=%d&vid=%d",
73 p == null ? -1 : p.getId(),
74 v == null ? -1 : v.getId()
75 );
76 }
78 /**
79 * Creates the navigation menu.
80 *
81 * @param req the servlet request
82 * @param viewModel the current view model
83 */
84 private void setNavigationMenu(HttpServletRequest req, ProjectView viewModel) {
85 final Project selectedProject = Optional.ofNullable(viewModel.getProjectInfo()).map(ProjectInfo::getProject).orElse(null);
87 final var navigation = new ArrayList<MenuEntry>();
89 for (ProjectInfo plistInfo : viewModel.getProjectList()) {
90 final var proj = plistInfo.getProject();
91 final var projEntry = new MenuEntry(
92 proj.getName(),
93 "projects/view?" + queryParams(proj, null)
94 );
95 navigation.add(projEntry);
96 if (proj.equals(selectedProject)) {
97 final var projInfo = viewModel.getProjectInfo();
98 projEntry.setActive(true);
100 // ****************
101 // Versions Section
102 // ****************
103 {
104 final var entry = new MenuEntry(1,
105 new ResourceKey(getResourceBundleName(), "menu.versions"),
106 "projects/view?" + queryParams(proj, null)
107 );
108 navigation.add(entry);
109 }
111 final var level2 = new ArrayList<MenuEntry>();
112 {
113 final var entry = new MenuEntry(
114 new ResourceKey(getResourceBundleName(), "filter.none"),
115 "projects/view?" + queryParams(proj, null)
116 );
117 if (viewModel.getVersionFilter() == null) entry.setActive(true);
118 level2.add(entry);
119 }
121 for (Version version : projInfo.getVersions()) {
122 final var entry = new MenuEntry(
123 version.getName(),
124 "projects/view?" + queryParams(proj, version)
125 );
126 if (version.equals(viewModel.getVersionFilter())) entry.setActive(true);
127 level2.add(entry);
128 }
130 level2.forEach(e -> e.setLevel(2));
131 navigation.addAll(level2);
132 }
133 }
135 setNavigationMenu(req, navigation);
136 }
138 private int syncParamWithSession(HttpServletRequest req, String param, String attr) {
139 final var session = req.getSession();
140 final var idParam = getParameter(req, Integer.class, param);
141 final int id;
142 if (idParam.isPresent()) {
143 id = idParam.get();
144 session.setAttribute(attr, id);
145 } else {
146 id = Optional.ofNullable(session.getAttribute(attr)).map(x->(Integer)x).orElse(-1);
147 }
148 return id;
149 }
151 private void populate(ProjectView viewModel, HttpServletRequest req, DataAccessObjects dao) throws SQLException {
152 final var projectDao = dao.getProjectDao();
153 final var versionDao = dao.getVersionDao();
155 projectDao.list().stream().map(ProjectInfo::new).forEach(viewModel.getProjectList()::add);
157 // Select Project
158 final int pid = syncParamWithSession(req, PARAMETER_SELECTED_PROJECT, SESSION_ATTR_SELECTED_PROJECT);
159 if (pid >= 0) {
160 final var project = projectDao.find(pid);
161 final var info = new ProjectInfo(project);
162 info.setVersions(versionDao.list(project));
163 info.setIssueSummary(projectDao.getIssueSummary(project));
164 viewModel.setProjectInfo(info);
165 }
167 // Select Version
168 final int vid = syncParamWithSession(req, PARAMETER_SELECTED_VERSION, SESSION_ATTR_SELECTED_VERSION);
169 if (vid >= 0) {
170 viewModel.setVersionFilter(versionDao.find(vid));
171 }
172 }
174 private ResponseType forwardView(HttpServletRequest req, ProjectView viewModel, String name) {
175 setViewModel(req, viewModel);
176 setContentPage(req, name);
177 setStylesheet(req, "projects");
178 setNavigationMenu(req, viewModel);
179 return ResponseType.HTML;
180 }
182 @RequestMapping(method = HttpMethod.GET)
183 public ResponseType index(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
184 final var viewModel = new ProjectView();
185 populate(viewModel, req, dao);
187 final var projectDao = dao.getProjectDao();
188 final var versionDao = dao.getVersionDao();
190 for (var info : viewModel.getProjectList()) {
191 info.setVersions(versionDao.list(info.getProject()));
192 info.setIssueSummary(projectDao.getIssueSummary(info.getProject()));
193 }
195 return forwardView(req, viewModel, "projects");
196 }
198 private void configure(ProjectEditView viewModel, Project project, DataAccessObjects dao) throws SQLException {
199 viewModel.setProject(project);
200 viewModel.setUsers(dao.getUserDao().list());
201 }
203 @RequestMapping(requestPath = "edit", method = HttpMethod.GET)
204 public ResponseType edit(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
205 final var viewModel = new ProjectEditView();
206 populate(viewModel, req, dao);
208 final var project = Optional.ofNullable(viewModel.getProjectInfo())
209 .map(ProjectInfo::getProject)
210 .orElse(new Project(-1));
211 configure(viewModel, project, dao);
213 return forwardView(req, viewModel, "project-form");
214 }
216 @RequestMapping(requestPath = "commit", method = HttpMethod.POST)
217 public ResponseType commit(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
219 Project project = new Project(-1);
220 try {
221 project = new Project(getParameter(req, Integer.class, "pid").orElseThrow());
222 project.setName(getParameter(req, String.class, "name").orElseThrow());
223 getParameter(req, String.class, "description").ifPresent(project::setDescription);
224 getParameter(req, String.class, "repoUrl").ifPresent(project::setRepoUrl);
225 getParameter(req, Integer.class, "owner").map(
226 ownerId -> ownerId >= 0 ? new User(ownerId) : null
227 ).ifPresent(project::setOwner);
229 dao.getProjectDao().saveOrUpdate(project);
231 setRedirectLocation(req, "./projects/view?pid="+project.getId());
232 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
233 LOG.debug("Successfully updated project {}", project.getName());
235 return ResponseType.HTML;
236 } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
237 LOG.warn("Form validation failure: {}", ex.getMessage());
238 LOG.debug("Details:", ex);
239 final var viewModel = new ProjectEditView();
240 populate(viewModel, req, dao);
241 configure(viewModel, project, dao);
242 // TODO: error text
243 return forwardView(req, viewModel, "project-form");
244 }
245 }
247 @RequestMapping(requestPath = "view", method = HttpMethod.GET)
248 public ResponseType view(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException, IOException {
249 final var viewModel = new ProjectDetailsView();
250 populate(viewModel, req, dao);
252 if (viewModel.getProjectInfo() == null) {
253 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No project selected.");
254 return ResponseType.NONE;
255 }
257 final var issueDao = dao.getIssueDao();
259 final var version = viewModel.getVersionFilter();
261 final var detailView = viewModel.getProjectDetails();
262 final var issues = issueDao.list(version);
263 for (var issue : issues) issueDao.joinVersionInformation(issue);
264 detailView.updateDetails(issues, version);
266 return forwardView(req, viewModel, "project-details");
267 }
269 @RequestMapping(requestPath = "versions/edit", method = HttpMethod.GET)
270 public ResponseType editVersion(HttpServletRequest req, DataAccessObjects dao) throws SQLException {
271 final var viewModel = new VersionEditView();
272 populate(viewModel, req, dao);
274 if (viewModel.getVersionFilter() == null) {
275 viewModel.setVersion(new Version(-1));
276 } else {
277 viewModel.setVersion(viewModel.getVersionFilter());
278 }
280 return forwardView(req, viewModel, "version-form");
281 }
283 @RequestMapping(requestPath = "versions/commit", method = HttpMethod.POST)
284 public ResponseType commitVersion(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException {
286 var version = new Version(-1);
287 try {
288 version = new Version(getParameter(req, Integer.class, "id").orElseThrow());
289 version.setProject(new Project(getParameter(req, Integer.class, "pid").orElseThrow()));
290 version.setName(getParameter(req, String.class, "name").orElseThrow());
291 getParameter(req, Integer.class, "ordinal").ifPresent(version::setOrdinal);
292 version.setStatus(VersionStatus.valueOf(getParameter(req, String.class, "status").orElseThrow()));
293 dao.getVersionDao().saveOrUpdate(version);
295 // specifying the pid parameter will purposely reset the session selected version!
296 setRedirectLocation(req, "./projects/view?pid=" + version.getProject().getId());
297 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
298 } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
299 LOG.warn("Form validation failure: {}", ex.getMessage());
300 LOG.debug("Details:", ex);
301 final var viewModel = new VersionEditView();
302 populate(viewModel, req, dao);
303 viewModel.setVersion(version);
304 // TODO: set Error Text
305 return forwardView(req, viewModel, "version-form");
306 }
308 return ResponseType.HTML;
309 }
311 private void configure(IssueEditView viewModel, Issue issue, DataAccessObjects dao) throws SQLException {
312 issue.setProject(viewModel.getProjectInfo().getProject());
313 viewModel.setIssue(issue);
314 viewModel.configureVersionSelectors(viewModel.getProjectInfo().getVersions());
315 viewModel.setUsers(dao.getUserDao().list());
316 }
318 @RequestMapping(requestPath = "issues/edit", method = HttpMethod.GET)
319 public ResponseType editIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException {
320 final var viewModel = new IssueEditView();
322 final var issueParam = getParameter(req, Integer.class, "issue");
323 if (issueParam.isPresent()) {
324 final var issueDao = dao.getIssueDao();
325 final var issue = issueDao.find(issueParam.get());
326 issueDao.joinVersionInformation(issue);
327 req.getSession().setAttribute(SESSION_ATTR_SELECTED_PROJECT, issue.getProject().getId());
328 populate(viewModel, req, dao);
329 configure(viewModel, issue, dao);
330 } else {
331 populate(viewModel, req, dao);
332 configure(viewModel, new Issue(-1), dao);
333 }
335 return forwardView(req, viewModel, "issue-form");
336 }
338 @RequestMapping(requestPath = "issues/commit", method = HttpMethod.POST)
339 public ResponseType commitIssue(HttpServletRequest req, HttpServletResponse resp, DataAccessObjects dao) throws SQLException {
340 Issue issue = new Issue(-1);
341 try {
342 issue = new Issue(getParameter(req, Integer.class, "id").orElseThrow());
343 issue.setProject(new Project(getParameter(req, Integer.class, "pid").orElseThrow()));
344 getParameter(req, String.class, "category").map(IssueCategory::valueOf).ifPresent(issue::setCategory);
345 getParameter(req, String.class, "status").map(IssueStatus::valueOf).ifPresent(issue::setStatus);
346 issue.setSubject(getParameter(req, String.class, "subject").orElseThrow());
347 getParameter(req, Integer.class, "assignee").map(
348 userid -> userid >= 0 ? new User(userid) : null
349 ).ifPresent(issue::setAssignee);
350 getParameter(req, String.class, "description").ifPresent(issue::setDescription);
351 getParameter(req, Date.class, "eta").ifPresent(issue::setEta);
353 getParameter(req, Integer[].class, "affected")
354 .map(Stream::of)
355 .map(stream ->
356 stream.map(Version::new).collect(Collectors.toList())
357 ).ifPresent(issue::setAffectedVersions);
358 getParameter(req, Integer[].class, "resolved")
359 .map(Stream::of)
360 .map(stream ->
361 stream.map(Version::new).collect(Collectors.toList())
362 ).ifPresent(issue::setResolvedVersions);
364 dao.getIssueDao().saveOrUpdate(issue);
366 // specifying the issue parameter keeps the edited issue as menu item
367 setRedirectLocation(req, "./projects/view?pid=" + issue.getProject().getId());
368 setContentPage(req, Constants.JSP_COMMIT_SUCCESSFUL);
369 } catch (NoSuchElementException | IllegalArgumentException | SQLException ex) {
370 // TODO: set request attribute with error text
371 LOG.warn("Form validation failure: {}", ex.getMessage());
372 LOG.debug("Details:", ex);
373 final var viewModel = new IssueEditView();
374 configure(viewModel, issue, dao);
375 // TODO: set Error Text
376 return forwardView(req, viewModel, "issue-form");
377 }
379 return ResponseType.HTML;
380 }
381 }