change rss feed to display the issue history

Sat, 09 Oct 2021 20:05:39 +0200

author
Mike Becker <universe@uap-core.de>
date
Sat, 09 Oct 2021 20:05:39 +0200
changeset 235
4258b9e010ae
parent 234
d71bc6db42ef
child 236
819c5178b6fe

change rss feed to display the issue history

TODO: diffs and comments

src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/entities/IssueHistoryEntry.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
     1.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt	Sat Oct 09 17:46:12 2021 +0200
     1.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt	Sat Oct 09 20:05:39 2021 +0200
     1.3 @@ -81,4 +81,6 @@
     1.4  
     1.5      fun insertHistoryEvent(issue: Issue, newId: Int = 0)
     1.6      fun insertHistoryEvent(issueComment: IssueComment, newId: Int = 0)
     1.7 +
     1.8 +    fun listIssueHistory(projectId: Int, days: Int): List<IssueHistoryEntry>
     1.9  }
     2.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt	Sat Oct 09 17:46:12 2021 +0200
     2.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt	Sat Oct 09 20:05:39 2021 +0200
     2.3 @@ -696,4 +696,43 @@
     2.4      }
     2.5  
     2.6      //</editor-fold>
     2.7 +
     2.8 +    //<editor-fold desc="Issue History">
     2.9 +
    2.10 +    override fun listIssueHistory(projectId: Int, days: Int) =
    2.11 +        withStatement(
    2.12 +            """
    2.13 +                select evt.*, evtdata.*
    2.14 +                from lpit_issue_history_event evt
    2.15 +                join lpit_issue using (issueid)
    2.16 +                join lpit_issue_history_data evtdata using (eventid)
    2.17 +                where project = ?
    2.18 +                and time > now() - (? * interval '1' day) 
    2.19 +                order by time desc
    2.20 +            """.trimIndent()
    2.21 +        ) {
    2.22 +            setInt(1, projectId)
    2.23 +            setInt(2, days)
    2.24 +            queryAll { rs->
    2.25 +                with(rs) {
    2.26 +                    IssueHistoryEntry(
    2.27 +                        getTimestamp("time"),
    2.28 +                        getEnum("type"),
    2.29 +                        IssueHistoryData(getInt("issueid"),
    2.30 +                            component = getString("component") ?: "",
    2.31 +                            status = getEnum("status"),
    2.32 +                            category = getEnum("category"),
    2.33 +                            subject = getString("subject"),
    2.34 +                            description = getString("description") ?: "",
    2.35 +                            assignee = getString("assignee") ?: "",
    2.36 +                            eta = getDate("eta"),
    2.37 +                            affected = getString("affected") ?: "",
    2.38 +                            resolved = getString("resolved") ?: ""
    2.39 +                        )
    2.40 +                    )
    2.41 +                }
    2.42 +            }
    2.43 +        }
    2.44 +
    2.45 +    //</editor-fold>
    2.46  }
    2.47 \ No newline at end of file
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/IssueHistoryEntry.kt	Sat Oct 09 20:05:39 2021 +0200
     3.3 @@ -0,0 +1,47 @@
     3.4 +/*
     3.5 + * Copyright 2021 Mike Becker. All rights reserved.
     3.6 + *
     3.7 + * Redistribution and use in source and binary forms, with or without
     3.8 + * modification, are permitted provided that the following conditions are met:
     3.9 + *
    3.10 + * 1. Redistributions of source code must retain the above copyright
    3.11 + * notice, this list of conditions and the following disclaimer.
    3.12 + *
    3.13 + * 2. Redistributions in binary form must reproduce the above copyright
    3.14 + * notice, this list of conditions and the following disclaimer in the
    3.15 + * documentation and/or other materials provided with the distribution.
    3.16 + *
    3.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    3.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    3.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    3.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    3.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    3.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    3.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    3.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    3.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    3.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    3.27 + */
    3.28 +
    3.29 +package de.uapcore.lightpit.entities
    3.30 +
    3.31 +import de.uapcore.lightpit.types.IssueCategory
    3.32 +import de.uapcore.lightpit.types.IssueHistoryType
    3.33 +import de.uapcore.lightpit.types.IssueStatus
    3.34 +import java.sql.Date
    3.35 +import java.sql.Timestamp
    3.36 +
    3.37 +class IssueHistoryData(
    3.38 +    val id: Int,
    3.39 +    val component: String,
    3.40 +    val status: IssueStatus,
    3.41 +    val category: IssueCategory,
    3.42 +    val subject: String,
    3.43 +    val description: String,
    3.44 +    val assignee: String,
    3.45 +    val eta: Date?,
    3.46 +    val affected: String,
    3.47 +    val resolved: String,
    3.48 +)
    3.49 +
    3.50 +class IssueHistoryEntry(val time: Timestamp, val type: IssueHistoryType, val data: IssueHistoryData)
    3.51 \ No newline at end of file
     4.1 --- a/src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt	Sat Oct 09 17:46:12 2021 +0200
     4.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt	Sat Oct 09 20:05:39 2021 +0200
     4.3 @@ -28,10 +28,12 @@
     4.4  import de.uapcore.lightpit.AbstractServlet
     4.5  import de.uapcore.lightpit.HttpRequest
     4.6  import de.uapcore.lightpit.dao.DataAccessObject
     4.7 -import de.uapcore.lightpit.util.IssueFilter
     4.8 -import de.uapcore.lightpit.util.IssueSorter
     4.9 -import de.uapcore.lightpit.util.SpecificFilter
    4.10 +import de.uapcore.lightpit.entities.IssueHistoryData
    4.11 +import de.uapcore.lightpit.entities.IssueHistoryEntry
    4.12 +import de.uapcore.lightpit.viewmodel.IssueDiff
    4.13  import de.uapcore.lightpit.viewmodel.IssueFeed
    4.14 +import de.uapcore.lightpit.viewmodel.IssueFeedEntry
    4.15 +import java.text.SimpleDateFormat
    4.16  import javax.servlet.annotation.WebServlet
    4.17  
    4.18  @WebServlet(urlPatterns = ["/feed/*"])
    4.19 @@ -41,6 +43,43 @@
    4.20          get("/%project/issues.rss", this::issues)
    4.21      }
    4.22  
    4.23 +    private fun String.convertLF() = replace("\r", "").replace("\n", "<br>")
    4.24 +
    4.25 +    private fun fullContent(issue: IssueHistoryData) = IssueDiff(
    4.26 +        issue.id,
    4.27 +        issue.subject,
    4.28 +        issue.component,
    4.29 +        issue.status.name,
    4.30 +        issue.category.name,
    4.31 +        issue.subject,
    4.32 +        issue.description.convertLF(),
    4.33 +        issue.assignee,
    4.34 +        issue.eta?.let { SimpleDateFormat("dd.MM.yyyy").format(it) } ?: "",
    4.35 +        issue.affected,
    4.36 +        issue.resolved
    4.37 +    )
    4.38 +
    4.39 +    private fun diffContent(cur: IssueHistoryData, next: IssueHistoryData): IssueDiff {
    4.40 +        val prev = fullContent(next)
    4.41 +        val diff = fullContent(cur)
    4.42 +        // TODO: compute and apply diff
    4.43 +        return diff
    4.44 +    }
    4.45 +
    4.46 +    /**
    4.47 +     * Generates the feed entries.
    4.48 +     * Assumes that [historyEntry] is already sorted by timestamp (descending).
    4.49 +     */
    4.50 +    private fun generateFeedEntries(historyEntry: List<IssueHistoryEntry>) =
    4.51 +        if (historyEntry.isEmpty()) emptyList()
    4.52 +        else historyEntry.zipWithNext().map { (cur, next) ->
    4.53 +            IssueFeedEntry(
    4.54 +                cur.time, cur.type, diffContent(cur.data, next.data)
    4.55 +            )
    4.56 +        }.plus(
    4.57 +            historyEntry.last().let { IssueFeedEntry(it.time, it.type, fullContent(it.data)) }
    4.58 +        )
    4.59 +
    4.60      private fun issues(http: HttpRequest, dao: DataAccessObject) {
    4.61          val project = http.pathParams["project"]?.let { dao.findProjectByNode(it) }
    4.62          if (project == null) {
    4.63 @@ -48,10 +87,12 @@
    4.64              return
    4.65          }
    4.66  
    4.67 -        // TODO: add a timestamp filter (e.g. last 30 days)
    4.68 -        val issues = dao.listIssues(IssueFilter(SpecificFilter(project))).sortedWith(IssueSorter.DEFAULT_ISSUE_SORTER)
    4.69 +        val days = http.param("days")?.toIntOrNull() ?: 30
    4.70  
    4.71 -        http.view = IssueFeed(project, issues)
    4.72 +        val issueHistory = dao.listIssueHistory(project.id, days)
    4.73 +        // TODO: add comment history depending on parameter
    4.74 +
    4.75 +        http.view = IssueFeed(project, generateFeedEntries(issueHistory))
    4.76          http.renderFeed("issues-feed")
    4.77      }
    4.78  }
    4.79 \ No newline at end of file
     5.1 --- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt	Sat Oct 09 17:46:12 2021 +0200
     5.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt	Sat Oct 09 20:05:39 2021 +0200
     5.3 @@ -25,14 +25,35 @@
     5.4  
     5.5  package de.uapcore.lightpit.viewmodel
     5.6  
     5.7 -import de.uapcore.lightpit.entities.Issue
     5.8  import de.uapcore.lightpit.entities.Project
     5.9 +import de.uapcore.lightpit.types.IssueHistoryType
    5.10  import java.sql.Timestamp
    5.11  import java.time.Instant
    5.12  
    5.13 +class IssueDiff(
    5.14 +    val id: Int,
    5.15 +    val currentSubject: String,
    5.16 +    var component: String,
    5.17 +    var status: String,
    5.18 +    var category: String,
    5.19 +    var subject: String,
    5.20 +    var description: String,
    5.21 +    var assignee: String,
    5.22 +    var eta: String,
    5.23 +    var affected: String,
    5.24 +    var resolved: String,
    5.25 +)
    5.26 +
    5.27 +class IssueFeedEntry(
    5.28 +    val time: Timestamp,
    5.29 +    val type: IssueHistoryType,
    5.30 +    val issue: IssueDiff
    5.31 +)
    5.32 +
    5.33  class IssueFeed(
    5.34      val project: Project,
    5.35 -    val issues: List<Issue>
    5.36 +    val entries: List<IssueFeedEntry>
    5.37  ) : View() {
    5.38 -    val lastModified = issues.map(Issue::updated).maxOrNull() ?: Timestamp.from(Instant.now())
    5.39 +    val lastModified: Timestamp =
    5.40 +        entries.map(IssueFeedEntry::time).maxOrNull() ?: Timestamp.from(Instant.now())
    5.41  }
    5.42 \ No newline at end of file
     6.1 --- a/src/main/resources/localization/strings.properties	Sat Oct 09 17:46:12 2021 +0200
     6.2 +++ b/src/main/resources/localization/strings.properties	Sat Oct 09 20:05:39 2021 +0200
     6.3 @@ -59,10 +59,12 @@
     6.4  error.message = Server Message
     6.5  error.returnLink = Return to
     6.6  error.timestamp = Timestamp
     6.7 -feed.issues.created=Issue has been created.
     6.8  feed.issues.description=Feed about recently updated issues.
     6.9  feed.issues.title=LightPIT Issues
    6.10 -feed.issues.updated=Issue has been updated.
    6.11 +feed.issues.type.New=New
    6.12 +feed.issues.type.Update=Update
    6.13 +feed.issues.type.NewComment=Comment
    6.14 +feed.issues.type.UpdateComment=Comment Update
    6.15  feed=Feed
    6.16  issue.affected-versions=Affected
    6.17  issue.assignee=Assignee
     7.1 --- a/src/main/resources/localization/strings_de.properties	Sat Oct 09 17:46:12 2021 +0200
     7.2 +++ b/src/main/resources/localization/strings_de.properties	Sat Oct 09 20:05:39 2021 +0200
     7.3 @@ -58,10 +58,12 @@
     7.4  error.message = Server Nachricht
     7.5  error.returnLink = Kehre zurück zu
     7.6  error.timestamp = Zeitstempel
     7.7 -feed.issues.created=Vorgang wurde erstellt.
     7.8  feed.issues.description=Feed \u00fcber k\u00fcrzlich aktualisierte Vorg\u00e4nge.
     7.9  feed.issues.title=LightPIT Vorg\u00e4nge
    7.10 -feed.issues.updated=Vorgang wurde aktualisiert.
    7.11 +feed.issues.type.New=Neu
    7.12 +feed.issues.type.Update=Update
    7.13 +feed.issues.type.NewComment=Kommentar
    7.14 +feed.issues.type.UpdateComment=Kommentar Update
    7.15  feed=Feed
    7.16  issue.affected-versions=Betroffen
    7.17  issue.assignee=Zugewiesen
     8.1 --- a/src/main/webapp/WEB-INF/jsp/issues-feed.jsp	Sat Oct 09 17:46:12 2021 +0200
     8.2 +++ b/src/main/webapp/WEB-INF/jsp/issues-feed.jsp	Sat Oct 09 20:05:39 2021 +0200
     8.3 @@ -34,21 +34,27 @@
     8.4      <pubDate><fmt:formatDate value="${viewmodel.lastModified}" pattern="EEE, dd MMM yyyy HH:mm:ss zzz" /></pubDate>
     8.5      <lastBuildDate><fmt:formatDate value="${viewmodel.lastModified}" pattern="EEE, dd MMM yyyy HH:mm:ss zzz" /></lastBuildDate>
     8.6  
     8.7 -    <c:forEach items="${viewmodel.issues}" var="issue">
     8.8 +    <c:forEach items="${viewmodel.entries}" var="entry">
     8.9 +        <c:set var="issue" value="${entry.issue}"/>
    8.10          <item>
    8.11 -            <title><c:if test="${not empty issue.component}"><c:out value="${issue.component.name}"/> - </c:if><c:out value="${issue.subject}"/></title>
    8.12 -            <description><c:choose>
    8.13 -                <c:when test="${issue.created eq issue.updated}">
    8.14 -                    <fmt:message key="feed.issues.created"/>
    8.15 -                </c:when>
    8.16 -                <c:otherwise>
    8.17 -                    <fmt:message key="feed.issues.updated"/>
    8.18 -                </c:otherwise>
    8.19 -            </c:choose></description>
    8.20 +            <title>[<fmt:message key="feed.issues.type.${entry.type}"/>] #${issue.id} - <c:out value="${issue.currentSubject}"/></title>
    8.21 +            <description><![CDATA[
    8.22 +                <h1>#${issue.id} - <c:out value="${issue.subject}"/></h1>
    8.23 +                <div><b><fmt:message key="component"/></b>: ${issue.component}</div>
    8.24 +                <div><b><fmt:message key="issue.category"/></b>: ${issue.category}</div>
    8.25 +                <div><b><fmt:message key="issue.status"/></b>: ${issue.status}</div>
    8.26 +                <div><b><fmt:message key="issue.resolved-versions"/></b>: ${issue.resolved}</div>
    8.27 +                <div><b><fmt:message key="issue.affected-versions"/></b>: ${issue.affected}</div>
    8.28 +                <div><b><fmt:message key="issue.assignee"/></b>: ${issue.assignee}</div>
    8.29 +                <div><b><fmt:message key="issue.eta"/></b>: ${issue.eta}</div>
    8.30 +                <h2><fmt:message key="issue.description"/></h2>
    8.31 +                ${issue.description}
    8.32 +            ]]></description>
    8.33              <category><fmt:message key="issue.category.${issue.category}"/></category>
    8.34 -            <link>${baseHref}projects/${issue.project.node}/issues/-/${empty issue.component ? '-' : issue.component.node}/${issue.id}</link>
    8.35 -            <guid isPermaLink="true">${baseHref}projects/${issue.project.node}/issues/-/-/${issue.id}</guid>
    8.36 -            <pubDate><fmt:formatDate value="${issue.updated}" pattern="EEE, dd MMM yyyy HH:mm:ss zzz" /></pubDate>
    8.37 +            <c:set var="link" value="${baseHref}projects/${viewmodel.project.node}/issues/-/-/${issue.id}"/>
    8.38 +            <link>${link}</link>
    8.39 +            <guid isPermaLink="true">${link}</guid>
    8.40 +            <pubDate><fmt:formatDate value="${entry.time}" pattern="EEE, dd MMM yyyy HH:mm:ss zzz" /></pubDate>
    8.41          </item>
    8.42      </c:forEach>
    8.43  </channel>

mercurial