#109 add comment history

Sat, 27 Nov 2021 13:03:57 +0100

author
Mike Becker <universe@uap-core.de>
date
Sat, 27 Nov 2021 13:03:57 +0100
changeset 242
b7f3e972b13c
parent 241
1ca4f27cefe8
child 243
a1c2611b02fc

#109 add comment history

setup/postgres/psql_create_tables.sql file | annotate | diff | comparison | revisions
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/servlet/ProjectServlet.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/issues-feed.jsp file | annotate | diff | comparison | revisions
     1.1 --- a/setup/postgres/psql_create_tables.sql	Sat Nov 27 12:12:20 2021 +0100
     1.2 +++ b/setup/postgres/psql_create_tables.sql	Sat Nov 27 13:03:57 2021 +0100
     1.3 @@ -112,6 +112,7 @@
     1.4  (
     1.5      eventid serial primary key,
     1.6      issueid integer                  not null references lpit_issue (issueid) on delete cascade,
     1.7 +    subject text                     not null,
     1.8      time    timestamp with time zone not null default now(),
     1.9      type    issue_history_event      not null
    1.10  );
    1.11 @@ -122,7 +123,6 @@
    1.12      component         text,
    1.13      status            issue_status   not null,
    1.14      category          issue_category not null,
    1.15 -    subject           text           not null,
    1.16      description       text,
    1.17      assignee          text,
    1.18      eta               date,
     2.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt	Sat Nov 27 12:12:20 2021 +0100
     2.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt	Sat Nov 27 13:03:57 2021 +0100
     2.3 @@ -80,7 +80,7 @@
     2.4      fun updateComment(issueComment: IssueComment)
     2.5  
     2.6      fun insertHistoryEvent(issue: Issue, newId: Int = 0)
     2.7 -    fun insertHistoryEvent(issueComment: IssueComment, newId: Int = 0)
     2.8 +    fun insertHistoryEvent(issue: Issue, issueComment: IssueComment, newId: Int = 0)
     2.9  
    2.10      /**
    2.11       * Lists the issue history of the project with [projectId] for the past [days].
     3.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt	Sat Nov 27 12:12:20 2021 +0100
     3.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt	Sat Nov 27 13:03:57 2021 +0100
     3.3 @@ -597,28 +597,27 @@
     3.4          val issueid = if (newId > 0) newId else issue.id
     3.5  
     3.6          val eventid =
     3.7 -            withStatement("insert into lpit_issue_history_event(issueid, type) values (?,?::issue_history_event) returning eventid") {
     3.8 +            withStatement("insert into lpit_issue_history_event(issueid, subject, type) values (?,?,?::issue_history_event) returning eventid") {
     3.9                  setInt(1, issueid)
    3.10 -                setEnum(2, type)
    3.11 +                setString(2, issue.subject)
    3.12 +                setEnum(3, type)
    3.13                  querySingle { it.getInt(1) }!!
    3.14              }
    3.15          withStatement(
    3.16              """
    3.17 -            insert into lpit_issue_history_data (component, status, category, subject, description, assignee, assignee_username, eta, affected, resolved, eventid)
    3.18 -            values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?, ?, ?, ?, ?)
    3.19 +            insert into lpit_issue_history_data (component, status, category, description, assignee, eta, affected, resolved, eventid)
    3.20 +            values (?, ?::issue_status, ?::issue_category, ?, ?, ?, ?, ?, ?)
    3.21              """.trimIndent()
    3.22          ) {
    3.23              setStringOrNull(1, issue.component?.name)
    3.24              setEnum(2, issue.status)
    3.25              setEnum(3, issue.category)
    3.26 -            setString(4, issue.subject)
    3.27 -            setStringOrNull(5, issue.description)
    3.28 -            setStringOrNull(6, issue.assignee?.shortDisplayname)
    3.29 -            setStringOrNull(7, issue.assignee?.username)
    3.30 -            setDateOrNull(8, issue.eta)
    3.31 -            setStringOrNull(9, issue.affected?.name)
    3.32 -            setStringOrNull(10, issue.resolved?.name)
    3.33 -            setInt(11, eventid)
    3.34 +            setStringOrNull(4, issue.description)
    3.35 +            setStringOrNull(5, issue.assignee?.shortDisplayname)
    3.36 +            setDateOrNull(6, issue.eta)
    3.37 +            setStringOrNull(7, issue.affected?.name)
    3.38 +            setStringOrNull(8, issue.resolved?.name)
    3.39 +            setInt(9, eventid)
    3.40              executeUpdate()
    3.41          }
    3.42      }
    3.43 @@ -678,17 +677,18 @@
    3.44      }
    3.45  
    3.46  
    3.47 -    override fun insertHistoryEvent(issueComment: IssueComment, newId: Int) {
    3.48 +    override fun insertHistoryEvent(issue: Issue, issueComment: IssueComment, newId: Int) {
    3.49          val type = if (newId > 0) IssueHistoryType.NewComment else IssueHistoryType.UpdateComment
    3.50          val commentid = if (newId > 0) newId else issueComment.id
    3.51  
    3.52          val eventid =
    3.53 -            withStatement("insert into lpit_issue_history_event(issueid, type) values (?,?::issue_history_event) returning eventid") {
    3.54 +            withStatement("insert into lpit_issue_history_event(issueid, subject, type) values (?,?,?::issue_history_event) returning eventid") {
    3.55                  setInt(1, issueComment.issueid)
    3.56 -                setEnum(2, type)
    3.57 +                setString(1, issue.subject)
    3.58 +                setEnum(3, type)
    3.59                  querySingle { it.getInt(1) }!!
    3.60              }
    3.61 -        withStatement("insert into lpit_issue_comment_history (commentid, eventid, comment) values (?,?,?)") {
    3.62 +        withStatement("insert into lpit_issue_comment_history (commentid, eventid, comment) values (?,?,?,?)") {
    3.63              setInt(1, commentid)
    3.64              setInt(2, eventid)
    3.65              setString(3, issueComment.comment)
    3.66 @@ -718,20 +718,19 @@
    3.67              queryAll { rs->
    3.68                  with(rs) {
    3.69                      IssueHistoryEntry(
    3.70 -                        getTimestamp("time"),
    3.71 -                        getEnum("type"),
    3.72 -                        getString("current_assignee"),
    3.73 -                        IssueHistoryData(getInt("issueid"),
    3.74 -                            component = getString("component") ?: "",
    3.75 -                            status = getEnum("status"),
    3.76 -                            category = getEnum("category"),
    3.77 -                            subject = getString("subject"),
    3.78 -                            description = getString("description") ?: "",
    3.79 -                            assignee = getString("assignee") ?: "",
    3.80 -                            eta = getDate("eta"),
    3.81 -                            affected = getString("affected") ?: "",
    3.82 -                            resolved = getString("resolved") ?: ""
    3.83 -                        )
    3.84 +                        subject = getString("subject"),
    3.85 +                        time = getTimestamp("time"),
    3.86 +                        type = getEnum("type"),
    3.87 +                        currentAssignee = getString("current_assignee"),
    3.88 +                        issueid = getInt("issueid"),
    3.89 +                        component = getString("component") ?: "",
    3.90 +                        status = getEnum("status"),
    3.91 +                        category = getEnum("category"),
    3.92 +                        description = getString("description") ?: "",
    3.93 +                        assignee = getString("assignee") ?: "",
    3.94 +                        eta = getDate("eta"),
    3.95 +                        affected = getString("affected") ?: "",
    3.96 +                        resolved = getString("resolved") ?: ""
    3.97                      )
    3.98                  }
    3.99              }
   3.100 @@ -755,13 +754,13 @@
   3.101              queryAll { rs->
   3.102                  with(rs) {
   3.103                      IssueCommentHistoryEntry(
   3.104 -                        getTimestamp("time"),
   3.105 -                        getEnum("type"),
   3.106 -                        getString("current_assignee"),
   3.107 -                        IssueCommentHistoryData(
   3.108 -                            getInt("commentid"),
   3.109 -                            getString("comment")
   3.110 -                        )
   3.111 +                        subject = getString("subject"),
   3.112 +                        time = getTimestamp("time"),
   3.113 +                        type = getEnum("type"),
   3.114 +                        currentAssignee = getString("current_assignee"),
   3.115 +                        issueid = getInt("issueid"),
   3.116 +                        commentid = getInt("commentid"),
   3.117 +                        comment = getString("comment")
   3.118                      )
   3.119                  }
   3.120              }
     4.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/IssueHistoryEntry.kt	Sat Nov 27 12:12:20 2021 +0100
     4.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/IssueHistoryEntry.kt	Sat Nov 27 13:03:57 2021 +0100
     4.3 @@ -31,12 +31,15 @@
     4.4  import java.sql.Date
     4.5  import java.sql.Timestamp
     4.6  
     4.7 -class IssueHistoryData(
     4.8 -    val id: Int,
     4.9 +class IssueHistoryEntry(
    4.10 +    val subject: String,
    4.11 +    val time: Timestamp,
    4.12 +    val type: IssueHistoryType,
    4.13 +    val currentAssignee: String?,
    4.14 +    val issueid: Int,
    4.15      val component: String,
    4.16      val status: IssueStatus,
    4.17      val category: IssueCategory,
    4.18 -    val subject: String,
    4.19      val description: String,
    4.20      val assignee: String,
    4.21      val eta: Date?,
    4.22 @@ -44,21 +47,12 @@
    4.23      val resolved: String,
    4.24  )
    4.25  
    4.26 -class IssueCommentHistoryData(
    4.27 -    val commentid: Int,
    4.28 -    val comment: String
    4.29 -)
    4.30 -
    4.31 -class IssueHistoryEntry(
    4.32 +class IssueCommentHistoryEntry(
    4.33 +    val subject: String,
    4.34      val time: Timestamp,
    4.35      val type: IssueHistoryType,
    4.36      val currentAssignee: String?,
    4.37 -    val data: IssueHistoryData
    4.38 -)
    4.39 -
    4.40 -class IssueCommentHistoryEntry(
    4.41 -    val time: Timestamp,
    4.42 -    val type: IssueHistoryType,
    4.43 -    val currentAssignee: String?,
    4.44 -    val data: IssueCommentHistoryData
    4.45 +    val issueid: Int,
    4.46 +    val commentid: Int,
    4.47 +    val comment: String,
    4.48  )
    4.49 \ No newline at end of file
     5.1 --- a/src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt	Sat Nov 27 12:12:20 2021 +0100
     5.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt	Sat Nov 27 13:03:57 2021 +0100
     5.3 @@ -30,8 +30,10 @@
     5.4  import de.uapcore.lightpit.AbstractServlet
     5.5  import de.uapcore.lightpit.HttpRequest
     5.6  import de.uapcore.lightpit.dao.DataAccessObject
     5.7 -import de.uapcore.lightpit.entities.IssueHistoryData
     5.8 +import de.uapcore.lightpit.entities.IssueCommentHistoryEntry
     5.9  import de.uapcore.lightpit.entities.IssueHistoryEntry
    5.10 +import de.uapcore.lightpit.types.IssueHistoryType
    5.11 +import de.uapcore.lightpit.viewmodel.CommentDiff
    5.12  import de.uapcore.lightpit.viewmodel.IssueDiff
    5.13  import de.uapcore.lightpit.viewmodel.IssueFeed
    5.14  import de.uapcore.lightpit.viewmodel.IssueFeedEntry
    5.15 @@ -46,8 +48,36 @@
    5.16          get("/%project/issues.rss", this::issues)
    5.17      }
    5.18  
    5.19 -    private fun fullContent(issue: IssueHistoryData) = IssueDiff(
    5.20 -        issue.id,
    5.21 +    val diffGenerator by lazyOf(DiffRowGenerator.create()
    5.22 +        .showInlineDiffs(true)
    5.23 +        .mergeOriginalRevised(true)
    5.24 +        .inlineDiffByWord(true)
    5.25 +        .oldTag { start -> if (start) "<strike style=\"color:red\">" else "</strike>" }
    5.26 +        .newTag { start -> if (start) "<i style=\"color: green\">" else "</i>" }
    5.27 +        .build()
    5.28 +    )
    5.29 +
    5.30 +    private fun fullContent(data: IssueCommentHistoryEntry) =
    5.31 +        CommentDiff(
    5.32 +            data.issueid,
    5.33 +            data.commentid,
    5.34 +            data.subject,
    5.35 +            data.comment.replace("\r", "")
    5.36 +        )
    5.37 +
    5.38 +    private fun diffContent(cur: IssueCommentHistoryEntry, next: IssueCommentHistoryEntry) =
    5.39 +        CommentDiff(
    5.40 +            cur.issueid,
    5.41 +            cur.commentid,
    5.42 +            cur.subject,
    5.43 +            diffGenerator.generateDiffRows(
    5.44 +                next.comment.replace("\r", "").split('\n'),
    5.45 +                cur.comment.replace("\r", "").split('\n')
    5.46 +            ).joinToString("\n", transform = DiffRow::getOldLine)
    5.47 +        )
    5.48 +
    5.49 +    private fun fullContent(issue: IssueHistoryEntry) = IssueDiff(
    5.50 +        issue.issueid,
    5.51          issue.subject,
    5.52          issue.component,
    5.53          issue.status.name,
    5.54 @@ -60,19 +90,10 @@
    5.55          issue.resolved
    5.56      )
    5.57  
    5.58 -    private fun diffContent(cur: IssueHistoryData, next: IssueHistoryData): IssueDiff {
    5.59 -        val generator = DiffRowGenerator.create()
    5.60 -            .showInlineDiffs(true)
    5.61 -            .mergeOriginalRevised(true)
    5.62 -            .inlineDiffByWord(true)
    5.63 -            .oldTag { start -> if (start) "<strike style=\"color:red\">" else "</strike>" }
    5.64 -            .newTag { start -> if (start) "<i style=\"color: green\">" else "</i>" }
    5.65 -            .build()
    5.66 -
    5.67 +    private fun diffContent(cur: IssueHistoryEntry, next: IssueHistoryEntry): IssueDiff {
    5.68          val prev = fullContent(next)
    5.69          val diff = fullContent(cur)
    5.70 -
    5.71 -        val result = generator.generateDiffRows(
    5.72 +        val result = diffGenerator.generateDiffRows(
    5.73              listOf(
    5.74                  prev.subject, prev.component, prev.status,
    5.75                  prev.category, prev.assignee, prev.eta, prev.affected, prev.resolved
    5.76 @@ -92,7 +113,7 @@
    5.77          diff.affected = result[6].oldLine
    5.78          diff.resolved = result[7].oldLine
    5.79  
    5.80 -        diff.description = generator.generateDiffRows(
    5.81 +        diff.description = diffGenerator.generateDiffRows(
    5.82              prev.description.split('\n'),
    5.83              diff.description.split('\n')
    5.84          ).joinToString("\n", transform = DiffRow::getOldLine)
    5.85 @@ -102,21 +123,42 @@
    5.86  
    5.87      /**
    5.88       * Generates the feed entries.
    5.89 -     * Assumes that [historyEntry] is already sorted by timestamp (descending).
    5.90 +     * Assumes that [issueEntries] and [commentEntries] are already sorted by timestamp (descending).
    5.91       */
    5.92 -    private fun generateFeedEntries(historyEntry: List<IssueHistoryEntry>): List<IssueFeedEntry> =
    5.93 -        if (historyEntry.isEmpty()) {
    5.94 +    private fun generateFeedEntries(
    5.95 +        issueEntries: List<IssueHistoryEntry>,
    5.96 +        commentEntries: List<IssueCommentHistoryEntry>
    5.97 +    ): List<IssueFeedEntry> =
    5.98 +        (generateIssueFeedEntries(issueEntries) + generateCommentFeedEntries(commentEntries)).sortedByDescending { it.time }
    5.99 +
   5.100 +    private fun generateIssueFeedEntries(entries: List<IssueHistoryEntry>): List<IssueFeedEntry> =
   5.101 +        if (entries.isEmpty()) {
   5.102              emptyList()
   5.103          } else {
   5.104 -            historyEntry.groupBy { it.data.id }.mapValues { (_, history) ->
   5.105 +            entries.groupBy { it.issueid }.mapValues { (_, history) ->
   5.106                  history.zipWithNext().map { (cur, next) ->
   5.107                      IssueFeedEntry(
   5.108 -                        cur.time, cur.type, diffContent(cur.data, next.data)
   5.109 +                        cur.time, cur.type, issue = diffContent(cur, next)
   5.110                      )
   5.111                  }.plus(
   5.112 -                    history.last().let { IssueFeedEntry(it.time, it.type, fullContent(it.data)) }
   5.113 +                    history.last().let { IssueFeedEntry(it.time, it.type, issue = fullContent(it)) }
   5.114                  )
   5.115 -            }.flatMap { it.value }.sortedByDescending { it.time }
   5.116 +            }.flatMap { it.value }
   5.117 +        }
   5.118 +
   5.119 +    private fun generateCommentFeedEntries(entries: List<IssueCommentHistoryEntry>): List<IssueFeedEntry> =
   5.120 +        if (entries.isEmpty()) {
   5.121 +            emptyList()
   5.122 +        } else {
   5.123 +            entries.groupBy { it.commentid }.mapValues { (_, history) ->
   5.124 +                history.zipWithNext().map { (cur, next) ->
   5.125 +                    IssueFeedEntry(
   5.126 +                        cur.time, cur.type, comment = diffContent(cur, next)
   5.127 +                    )
   5.128 +                }.plus(
   5.129 +                    history.last().let { IssueFeedEntry(it.time, it.type, comment = fullContent(it)) }
   5.130 +                )
   5.131 +            }.flatMap { it.value }
   5.132          }
   5.133  
   5.134      private fun issues(http: HttpRequest, dao: DataAccessObject) {
   5.135 @@ -126,6 +168,7 @@
   5.136              return
   5.137          }
   5.138          val assignees = http.param("assignee")?.split(',')
   5.139 +        val comments = http.param("comments") ?: "all"
   5.140  
   5.141          val days = http.param("days")?.toIntOrNull() ?: 30
   5.142  
   5.143 @@ -133,9 +176,14 @@
   5.144          val issueHistory = if (assignees == null) issuesFromDb else
   5.145              issuesFromDb.filter { assignees.contains(it.currentAssignee) }
   5.146  
   5.147 -        // TODO: add comment history depending on parameter
   5.148 +        val commentsFromDb = dao.listIssueCommentHistory(project.id, days)
   5.149 +        val commentHistory = when (comments) {
   5.150 +            "all" -> commentsFromDb
   5.151 +            "new" -> commentsFromDb.filter { it.type == IssueHistoryType.NewComment }
   5.152 +            else -> emptyList()
   5.153 +        }
   5.154  
   5.155 -        http.view = IssueFeed(project, generateFeedEntries(issueHistory))
   5.156 +        http.view = IssueFeed(project, generateFeedEntries(issueHistory, commentHistory))
   5.157          http.renderFeed("issues-feed")
   5.158      }
   5.159  }
   5.160 \ No newline at end of file
     6.1 --- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt	Sat Nov 27 12:12:20 2021 +0100
     6.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt	Sat Nov 27 13:03:57 2021 +0100
     6.3 @@ -531,7 +531,7 @@
     6.4                      if (!newComment.isNullOrBlank()) {
     6.5                          comment.comment = newComment
     6.6                          dao.updateComment(comment)
     6.7 -                        dao.insertHistoryEvent(comment)
     6.8 +                        dao.insertHistoryEvent(issue, comment)
     6.9                      } else {
    6.10                          logger().debug("Not updating comment ${comment.id} because nothing changed.")
    6.11                      }
    6.12 @@ -545,7 +545,7 @@
    6.13                      comment = http.param("comment") ?: ""
    6.14                  }
    6.15                  val newId = dao.insertComment(comment)
    6.16 -                dao.insertHistoryEvent(comment, newId)
    6.17 +                dao.insertHistoryEvent(issue, comment, newId)
    6.18              }
    6.19  
    6.20              http.renderCommit("${issuesHref}${issue.id}")
    6.21 @@ -602,7 +602,7 @@
    6.22                          comment = newComment
    6.23                      }
    6.24                      val commentid = dao.insertComment(comment)
    6.25 -                    dao.insertHistoryEvent(comment, commentid)
    6.26 +                    dao.insertHistoryEvent(issue, comment, commentid)
    6.27                  }
    6.28                  issue.id
    6.29              }
     7.1 --- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt	Sat Nov 27 12:12:20 2021 +0100
     7.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Feeds.kt	Sat Nov 27 13:03:57 2021 +0100
     7.3 @@ -44,10 +44,18 @@
     7.4      var resolved: String,
     7.5  )
     7.6  
     7.7 +class CommentDiff(
     7.8 +    val issueid: Int,
     7.9 +    val id: Int,
    7.10 +    val currentSubject: String,
    7.11 +    val comment: String
    7.12 +)
    7.13 +
    7.14  class IssueFeedEntry(
    7.15      val time: Timestamp,
    7.16      val type: IssueHistoryType,
    7.17 -    val issue: IssueDiff
    7.18 +    val issue: IssueDiff? = null,
    7.19 +    val comment: CommentDiff? = null
    7.20  )
    7.21  
    7.22  class IssueFeed(
     8.1 --- a/src/main/webapp/WEB-INF/jsp/issues-feed.jsp	Sat Nov 27 12:12:20 2021 +0100
     8.2 +++ b/src/main/webapp/WEB-INF/jsp/issues-feed.jsp	Sat Nov 27 13:03:57 2021 +0100
     8.3 @@ -27,34 +27,50 @@
     8.4  <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
     8.5  <jsp:useBean id="viewmodel" type="de.uapcore.lightpit.viewmodel.IssueFeed" scope="request"/>
     8.6  <channel>
     8.7 -    <title><c:out value="${viewmodel.project.name}"/> | <fmt:message key="feed.issues.title"/></title>
     8.8 +    <title>
     8.9 +        <c:out value="${viewmodel.project.name}"/>
    8.10 +        |<fmt:message key="feed.issues.title"/></title>
    8.11      <description><fmt:message key="feed.issues.description"/></description>
    8.12      <link>${baseHref}projects/${viewmodel.project.node}</link>
    8.13      <language>${pageContext.response.locale.language}</language>
    8.14 -    <pubDate><fmt:formatDate value="${viewmodel.lastModified}" pattern="EEE, dd MMM yyyy HH:mm:ss zzz" /></pubDate>
    8.15 -    <lastBuildDate><fmt:formatDate value="${viewmodel.lastModified}" pattern="EEE, dd MMM yyyy HH:mm:ss zzz" /></lastBuildDate>
    8.16 +    <pubDate><fmt:formatDate value="${viewmodel.lastModified}" pattern="EEE, dd MMM yyyy HH:mm:ss zzz"/></pubDate>
    8.17 +    <lastBuildDate><fmt:formatDate value="${viewmodel.lastModified}"
    8.18 +                                   pattern="EEE, dd MMM yyyy HH:mm:ss zzz"/></lastBuildDate>
    8.19  
    8.20      <c:forEach items="${viewmodel.entries}" var="entry">
    8.21 -        <c:set var="issue" value="${entry.issue}"/>
    8.22          <item>
    8.23 -            <title>[<fmt:message key="feed.issues.type.${entry.type}"/>] #${issue.id} - <c:out value="${issue.currentSubject}"/></title>
    8.24 -            <description><![CDATA[
    8.25 -                <h1>#${issue.id} - ${issue.subject}</h1>
    8.26 -                <div><b><fmt:message key="component"/></b>: ${issue.component}</div>
    8.27 -                <div><b><fmt:message key="issue.category"/></b>: ${issue.category}</div>
    8.28 -                <div><b><fmt:message key="issue.status"/></b>: ${issue.status}</div>
    8.29 -                <div><b><fmt:message key="issue.resolved-versions"/></b>: ${issue.resolved}</div>
    8.30 -                <div><b><fmt:message key="issue.affected-versions"/></b>: ${issue.affected}</div>
    8.31 -                <div><b><fmt:message key="issue.assignee"/></b>: ${issue.assignee}</div>
    8.32 -                <div><b><fmt:message key="issue.eta"/></b>: ${issue.eta}</div>
    8.33 -                <h2><fmt:message key="issue.description"/></h2>
    8.34 -                <div style="white-space: pre-wrap;">${issue.description}</div>
    8.35 -            ]]></description>
    8.36 -            <category><fmt:message key="issue.category.${issue.category}"/></category>
    8.37 -            <c:set var="link" value="${baseHref}projects/${viewmodel.project.node}/issues/-/-/${issue.id}"/>
    8.38 +            <c:choose>
    8.39 +                <c:when test="${not empty entry.issue}">
    8.40 +                    <c:set var="issue" value="${entry.issue}"/>
    8.41 +                    <c:set var="link" value="${baseHref}projects/${viewmodel.project.node}/issues/-/-/${issue.id}"/>
    8.42 +                    <title>[<fmt:message key="feed.issues.type.${entry.type}"/>] #${issue.id} - <c:out value="${issue.currentSubject}"/></title>
    8.43 +                    <description><![CDATA[
    8.44 +                        <h1>#${issue.id} - ${issue.subject}</h1>
    8.45 +                        <div><b><fmt:message key="component"/></b>: ${issue.component}</div>
    8.46 +                        <div><b><fmt:message key="issue.category"/></b>: ${issue.category}</div>
    8.47 +                        <div><b><fmt:message key="issue.status"/></b>: ${issue.status}</div>
    8.48 +                        <div><b><fmt:message key="issue.resolved-versions"/></b>: ${issue.resolved}</div>
    8.49 +                        <div><b><fmt:message key="issue.affected-versions"/></b>: ${issue.affected}</div>
    8.50 +                        <div><b><fmt:message key="issue.assignee"/></b>: ${issue.assignee}</div>
    8.51 +                        <div><b><fmt:message key="issue.eta"/></b>: ${issue.eta}</div>
    8.52 +                        <h2><fmt:message key="issue.description"/></h2>
    8.53 +                        <div style="white-space: pre-wrap;">${issue.description}</div>
    8.54 +                    ]]></description>
    8.55 +                    <category><fmt:message key="issue.category.${issue.category}"/></category>
    8.56 +                </c:when>
    8.57 +                <c:when test="${not empty entry.comment}">
    8.58 +                    <c:set var="comment" value="${entry.comment}"/>
    8.59 +                    <c:set var="link" value="${baseHref}projects/${viewmodel.project.node}/issues/-/-/${comment.issueid}"/>
    8.60 +                    <title>[<fmt:message key="feed.issues.type.${entry.type}"/>] #${comment.issueid} - <c:out value="${comment.currentSubject}"/></title>
    8.61 +                    <description><![CDATA[
    8.62 +                        <div style="white-space: pre-wrap;">${comment.comment}</div>
    8.63 +                    ]]></description>
    8.64 +                    <category><fmt:message key="feed.issues.type.${entry.type}"/></category>
    8.65 +                </c:when>
    8.66 +            </c:choose>
    8.67              <link>${link}</link>
    8.68              <guid isPermaLink="true">${link}</guid>
    8.69 -            <pubDate><fmt:formatDate value="${entry.time}" pattern="EEE, dd MMM yyyy HH:mm:ss zzz" /></pubDate>
    8.70 +            <pubDate><fmt:formatDate value="${entry.time}" pattern="EEE, dd MMM yyyy HH:mm:ss zzz"/></pubDate>
    8.71          </item>
    8.72      </c:forEach>
    8.73  </channel>

mercurial