#109 adds RSS feed button to project header and changes feed output slightly

Sat, 15 May 2021 16:19:29 +0200

author
Mike Becker <universe@uap-core.de>
date
Sat, 15 May 2021 16:19:29 +0200
changeset 199
59393c8cc557
parent 198
94f174d591ab
child 200
a5ddfaf6b469

#109 adds RSS feed button to project header and changes feed output slightly

src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt file | annotate | diff | comparison | revisions
src/main/resources/localization/strings.properties file | annotate | diff | comparison | revisions
src/main/resources/localization/strings_de.properties file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/issues-feed.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/project-feed.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/site.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jspf/project-header.jspf file | annotate | diff | comparison | revisions
src/main/webapp/rss.svg file | annotate | diff | comparison | revisions
--- 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>

mercurial