Sat, 15 May 2021 16:19:29 +0200
#109 adds RSS feed button to project header and changes feed output slightly
--- a/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Thu May 13 19:31:09 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Sat May 15 16:19:29 2021 +0200 @@ -80,16 +80,24 @@ request.setAttribute(Constants.REQ_ATTR_NAVIGATION, navigationMenu) } - var redirectLocation = "" + var redirectLocation: String? = null set(value) { field = value - request.setAttribute(Constants.REQ_ATTR_REDIRECT_LOCATION, baseHref + value) + if (value == null) { + request.removeAttribute(Constants.REQ_ATTR_REDIRECT_LOCATION) + } else { + request.setAttribute(Constants.REQ_ATTR_REDIRECT_LOCATION, baseHref + value) + } } - var feedPath = "" + var feedPath: String? = null set(value) { field = value - request.setAttribute(Constants.REQ_ATTR_FEED_HREF, baseHref + value) + if (value == null) { + request.removeAttribute(Constants.REQ_ATTR_FEED_HREF) + } else { + request.setAttribute(Constants.REQ_ATTR_FEED_HREF, baseHref + value) + } } /** @@ -116,10 +124,6 @@ */ val baseHref get() = "${request.scheme}://${request.serverName}$portInfo${request.contextPath}/" - init { - feedPath = "feed/projects.rss" - } - private fun String.withExt(ext: String) = if (endsWith(ext)) this else plus(ext) private fun jspPath(name: String) = Constants.JSP_PATH_PREFIX.plus(name).withExt(".jsp")
--- a/src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt Thu May 13 19:31:09 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt Sat May 15 16:19:29 2021 +0200 @@ -32,25 +32,15 @@ import de.uapcore.lightpit.util.IssueSorter import de.uapcore.lightpit.util.SpecificFilter import de.uapcore.lightpit.viewmodel.IssueFeed -import de.uapcore.lightpit.viewmodel.ProjectFeed import javax.servlet.annotation.WebServlet @WebServlet(urlPatterns = ["/feed/*"]) class FeedServlet : AbstractServlet() { init { - get("/projects.rss", this::projects) get("/%project/issues.rss", this::issues) } - private fun projects(http: HttpRequest, dao: DataAccessObject) { - - val projects = dao.listProjects() - - http.view = ProjectFeed(projects) - http.renderFeed("project-feed") - } - private fun issues(http: HttpRequest, dao: DataAccessObject) { val project = http.pathParams["project"]?.let { dao.findProjectByNode(it) } if (project == null) { @@ -58,6 +48,7 @@ return } + // TODO: add a timestamp filter (e.g. last 30 days) val issues = dao.listIssues(IssueFilter(SpecificFilter(project))).sortedWith(IssueSorter.DEFAULT_ISSUE_SORTER) http.view = IssueFeed(project, issues)
--- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt Thu May 13 19:31:09 2021 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt Sat May 15 16:19:29 2021 +0200 @@ -27,12 +27,12 @@ import de.uapcore.lightpit.entities.Issue import de.uapcore.lightpit.entities.Project - -class ProjectFeed( - val projects: List<Project> -) : View() +import java.sql.Timestamp +import java.time.Instant class IssueFeed( val project: Project, val issues: List<Issue> -) : View() \ No newline at end of file +) : View() { + val lastModified = issues.map(Issue::updated).maxOrNull() ?: Timestamp.from(Instant.now()) +} \ No newline at end of file
--- a/src/main/resources/localization/strings.properties Thu May 13 19:31:09 2021 +0200 +++ b/src/main/resources/localization/strings.properties Sat May 15 16:19:29 2021 +0200 @@ -54,11 +54,11 @@ error.message = Server Message error.returnLink = Return to error.timestamp = Timestamp +feed.issues.created=Issue has been created. feed.issues.description=Feed about recently updated issues. feed.issues.title=LightPIT Issues -feed.projects.description=Feed about tracked projects. -feed.projects.source=Issues -feed.projects.title=LightPIT Projects +feed.issues.updated=Issue has been updated. +feed=Feed issue.affected-versions=Affected Versions issue.assignee=Assignee issue.category.Bug=Bug
--- a/src/main/resources/localization/strings_de.properties Thu May 13 19:31:09 2021 +0200 +++ b/src/main/resources/localization/strings_de.properties Sat May 15 16:19:29 2021 +0200 @@ -54,11 +54,11 @@ error.message = Server Nachricht error.returnLink = Kehre zurück zu error.timestamp = Zeitstempel +feed=Feed +feed.issues.created=Vorgang wurde erstellt. feed.issues.description=Feed \u00fcber k\u00fcrzlich aktualisierte Vorg\u00e4nge. feed.issues.title=LightPIT Vorg\u00e4nge -feed.projects.description=Feed \u00fcber verwaltete Projekte. -feed.projects.source=Vorg\u00e4nge -feed.projects.title=LightPIT Projekte +feed.issues.updated=Vorgang wurde aktualisiert. issue.affected-versions=Betroffene Versionen issue.assignee=Zugewiesen issue.category.Bug=Fehler @@ -130,5 +130,5 @@ version.status.LTS=Langzeitsupport version.status.Released=Ver\u00f6ffentlicht version.status.Unreleased=Unver\u00f6ffentlicht -version.status=Status +version.status=Status version=Version
--- a/src/main/webapp/WEB-INF/jsp/issues-feed.jsp Thu May 13 19:31:09 2021 +0200 +++ b/src/main/webapp/WEB-INF/jsp/issues-feed.jsp Sat May 15 16:19:29 2021 +0200 @@ -22,7 +22,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 contentType="application/rss+xml;charset=UTF-8" pageEncoding="UTF-8" %> +<%@page contentType="application/rss+xml;charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %> <%@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.IssueFeed" scope="request"/> @@ -31,11 +31,20 @@ <description><fmt:message key="feed.issues.description"/></description> <link>${baseHref}projects/${viewmodel.project.node}</link> <language>${pageContext.response.locale.language}</language> + <pubDate><fmt:formatDate value="${viewmodel.lastModified}" pattern="EEE, dd MMM yyyy HH:mm:ss zzz" /></pubDate> + <lastBuildDate><fmt:formatDate value="${viewmodel.lastModified}" pattern="EEE, dd MMM yyyy HH:mm:ss zzz" /></lastBuildDate> <c:forEach items="${viewmodel.issues}" var="issue"> <item> <title><c:if test="${not empty issue.component}"><c:out value="${issue.component.name}"/> - </c:if><c:out value="${issue.subject}"/></title> - <description><c:out value="${issue.description}"/></description> + <description><c:choose> + <c:when test="${issue.created eq issue.updated}"> + <fmt:message key="feed.issues.created"/> + </c:when> + <c:otherwise> + <fmt:message key="feed.issues.updated"/> + </c:otherwise> + </c:choose></description> <category><fmt:message key="issue.category.${issue.category}"/></category> <link>${baseHref}projects/${issue.project.node}/issues/-/${empty issue.component ? '-' : issue.component.node}/${issue.id}</link> <guid isPermaLink="true">${baseHref}projects/${issue.project.node}/issues/-/-/${issue.id}</guid>
--- a/src/main/webapp/WEB-INF/jsp/project-feed.jsp Thu May 13 19:31:09 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -<%-- - ~ Copyright 2021 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 contentType="application/rss+xml;charset=UTF-8" 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.ProjectFeed" scope="request"/> -<channel> - <title><fmt:message key="feed.projects.title"/></title> - <description><fmt:message key="feed.projects.description"/></description> - <link>${baseHref}projects/</link> - <language>${pageContext.response.locale.language}</language> - - <c:forEach items="${viewmodel.projects}" var="project"> - <item> - <title><c:out value="${project.name}"/></title> - <description><c:out value="${project.description}"/></description> - <link>${baseHref}projects/${project.node}</link> - <source url="${baseHref}feed/${project.node}/issues.rss"><fmt:message key="feed.projects.source"/></source> - </item> - </c:forEach> -</channel>
--- a/src/main/webapp/WEB-INF/jsp/site.jsp Thu May 13 19:31:09 2021 +0200 +++ b/src/main/webapp/WEB-INF/jsp/site.jsp Sat May 15 16:19:29 2021 +0200 @@ -65,26 +65,31 @@ <meta http-equiv="refresh" content="0; URL=${redirectLocation}"> </c:if> <link rel="stylesheet" href="lightpit.css" type="text/css"> - <link rel="alternate" type="application/rss+xml" title="RSS" href="${feedHref}" /> + <c:if test="${not empty feedHref}"> + <link rel="alternate" type="application/rss+xml" title="RSS Feed" href="${feedHref}"/> + </c:if> <c:if test="${not empty extraCss}"> <c:forEach items="${extraCss}" var="cssFile"> - <link rel="stylesheet" href="${cssFile}" type="text/css"> + <link rel="stylesheet" href="${cssFile}" type="text/css"> </c:forEach> </c:if> </head> <body> <div id="mainMenu"> - <div class="menuEntry" <c:if test="${fn:startsWith(requestPath, '/projects/')}">data-active</c:if> > + <div class="menuEntry" + <c:if test="${fn:startsWith(requestPath, '/projects/')}">data-active</c:if> > <a href="projects/"> <fmt:message key="menu.projects"/> </a> </div> - <div class="menuEntry" <c:if test="${fn:startsWith(requestPath, '/users/')}">data-active</c:if> > + <div class="menuEntry" + <c:if test="${fn:startsWith(requestPath, '/users/')}">data-active</c:if> > <a href="users/"> <fmt:message key="menu.users"/> </a> </div> - <div class="menuEntry" <c:if test="${fn:startsWith(requestPath, '/language/')}">data-active</c:if> > + <div class="menuEntry" + <c:if test="${fn:startsWith(requestPath, '/language/')}">data-active</c:if> > <a href="language/"> <fmt:message key="menu.languages"/> </a> @@ -93,7 +98,7 @@ <div> <c:if test="${not empty navMenu}"> <div id="sideMenu"> - <%@include file="../jspf/navmenu.jspf"%> + <%@include file="../jspf/navmenu.jspf" %> </div> </c:if> <div id="content-area" <c:if test="${not empty navMenu}">class="sidebar-spacing"</c:if>>
--- a/src/main/webapp/WEB-INF/jspf/project-header.jspf Thu May 13 19:31:09 2021 +0200 +++ b/src/main/webapp/WEB-INF/jspf/project-header.jspf Sat May 15 16:19:29 2021 +0200 @@ -4,6 +4,15 @@ --%> <div class="table project-attributes"> <div class="row"> + <div class="caption"><fmt:message key="feed"/>:</div> + <div class="caption"> + <a class="rss-feed" href="./feed/${project.node}/issues.rss"> + <img src="./rss.svg" alt="Feed" style="width: 1em; height: 1em;"> + RSS + </a> + </div> + </div> + <div class="row"> <div class="caption"><fmt:message key="project.name"/>:</div> <div><c:out value="${project.name}"/></div> <div class="caption"><fmt:message key="description"/>:</div>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/webapp/rss.svg Sat May 15 16:19:29 2021 +0200 @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="128px" height="128px" id="RSSicon" viewBox="0 0 256 256"> +<defs> +<linearGradient x1="0.085" y1="0.085" x2="0.915" y2="0.915" id="RSSg"> +<stop offset="0.0" stop-color="#E3702D"/><stop offset="0.1071" stop-color="#EA7D31"/> +<stop offset="0.3503" stop-color="#F69537"/><stop offset="0.5" stop-color="#FB9E3A"/> +<stop offset="0.7016" stop-color="#EA7C31"/><stop offset="0.8866" stop-color="#DE642B"/> +<stop offset="1.0" stop-color="#D95B29"/> +</linearGradient> +</defs> +<rect width="256" height="256" rx="55" ry="55" x="0" y="0" fill="#CC5D15"/> +<rect width="246" height="246" rx="50" ry="50" x="5" y="5" fill="#F49C52"/> +<rect width="236" height="236" rx="47" ry="47" x="10" y="10" fill="url(#RSSg)"/> +<circle cx="68" cy="189" r="24" fill="#FFF"/> +<path d="M160 213h-34a82 82 0 0 0 -82 -82v-34a116 116 0 0 1 116 116z" fill="#FFF"/> +<path d="M184 213A140 140 0 0 0 44 73 V 38a175 175 0 0 1 175 175z" fill="#FFF"/> +</svg>