src/main/kotlin/de/uapcore/lightpit/servlet/FeedServlet.kt

Wed, 28 Dec 2022 13:21:30 +0100

author
Mike Becker <universe@uap-core.de>
date
Wed, 28 Dec 2022 13:21:30 +0100
changeset 254
55ca6cafc3dd
parent 242
b7f3e972b13c
child 260
fb2ae2d63a56
permissions
-rw-r--r--

#233 migrate to Jakarta EE and update dependencies

     1 /*
     2  * Copyright 2021 Mike Becker. All rights reserved.
     3  *
     4  * Redistribution and use in source and binary forms, with or without
     5  * modification, are permitted provided that the following conditions are met:
     6  *
     7  * 1. Redistributions of source code must retain the above copyright
     8  * notice, this list of conditions and the following disclaimer.
     9  *
    10  * 2. Redistributions in binary form must reproduce the above copyright
    11  * notice, this list of conditions and the following disclaimer in the
    12  * documentation and/or other materials provided with the distribution.
    13  *
    14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    17  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    20  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    21  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    22  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    24  */
    26 package de.uapcore.lightpit.servlet
    28 import com.github.difflib.text.DiffRow
    29 import com.github.difflib.text.DiffRowGenerator
    30 import de.uapcore.lightpit.AbstractServlet
    31 import de.uapcore.lightpit.HttpRequest
    32 import de.uapcore.lightpit.dao.DataAccessObject
    33 import de.uapcore.lightpit.entities.IssueCommentHistoryEntry
    34 import de.uapcore.lightpit.entities.IssueHistoryEntry
    35 import de.uapcore.lightpit.types.IssueHistoryType
    36 import de.uapcore.lightpit.viewmodel.CommentDiff
    37 import de.uapcore.lightpit.viewmodel.IssueDiff
    38 import de.uapcore.lightpit.viewmodel.IssueFeed
    39 import de.uapcore.lightpit.viewmodel.IssueFeedEntry
    40 import jakarta.servlet.annotation.WebServlet
    41 import java.text.SimpleDateFormat
    44 @WebServlet(urlPatterns = ["/feed/*"])
    45 class FeedServlet : AbstractServlet() {
    47     init {
    48         get("/%project/issues.rss", this::issues)
    49     }
    51     val diffGenerator by lazyOf(DiffRowGenerator.create()
    52         .showInlineDiffs(true)
    53         .mergeOriginalRevised(true)
    54         .inlineDiffByWord(true)
    55         .oldTag { start -> if (start) "<strike style=\"color:red\">" else "</strike>" }
    56         .newTag { start -> if (start) "<i style=\"color: green\">" else "</i>" }
    57         .build()
    58     )
    60     private fun fullContent(data: IssueCommentHistoryEntry) =
    61         CommentDiff(
    62             data.issueid,
    63             data.commentid,
    64             data.subject,
    65             data.comment.replace("\r", "")
    66         )
    68     private fun diffContent(cur: IssueCommentHistoryEntry, next: IssueCommentHistoryEntry) =
    69         CommentDiff(
    70             cur.issueid,
    71             cur.commentid,
    72             cur.subject,
    73             diffGenerator.generateDiffRows(
    74                 next.comment.replace("\r", "").split('\n'),
    75                 cur.comment.replace("\r", "").split('\n')
    76             ).joinToString("\n", transform = DiffRow::getOldLine)
    77         )
    79     private fun fullContent(issue: IssueHistoryEntry) = IssueDiff(
    80         issue.issueid,
    81         issue.subject,
    82         issue.component,
    83         issue.status.name,
    84         issue.category.name,
    85         issue.subject,
    86         issue.description.replace("\r", ""),
    87         issue.assignee,
    88         issue.eta?.let { SimpleDateFormat("dd.MM.yyyy").format(it) } ?: "",
    89         issue.affected,
    90         issue.resolved
    91     )
    93     private fun diffContent(cur: IssueHistoryEntry, next: IssueHistoryEntry): IssueDiff {
    94         val prev = fullContent(next)
    95         val diff = fullContent(cur)
    96         val result = diffGenerator.generateDiffRows(
    97             listOf(
    98                 prev.subject, prev.component, prev.status,
    99                 prev.category, prev.assignee, prev.eta, prev.affected, prev.resolved
   100             ),
   101             listOf(
   102                 diff.subject, diff.component, diff.status,
   103                 diff.category, diff.assignee, diff.eta, diff.affected, diff.resolved
   104             )
   105         )
   107         diff.subject = result[0].oldLine
   108         diff.component = result[1].oldLine
   109         diff.status = result[2].oldLine
   110         diff.category = result[3].oldLine
   111         diff.assignee = result[4].oldLine
   112         diff.eta = result[5].oldLine
   113         diff.affected = result[6].oldLine
   114         diff.resolved = result[7].oldLine
   116         diff.description = diffGenerator.generateDiffRows(
   117             prev.description.split('\n'),
   118             diff.description.split('\n')
   119         ).joinToString("\n", transform = DiffRow::getOldLine)
   121         return diff
   122     }
   124     /**
   125      * Generates the feed entries.
   126      * Assumes that [issueEntries] and [commentEntries] are already sorted by timestamp (descending).
   127      */
   128     private fun generateFeedEntries(
   129         issueEntries: List<IssueHistoryEntry>,
   130         commentEntries: List<IssueCommentHistoryEntry>
   131     ): List<IssueFeedEntry> =
   132         (generateIssueFeedEntries(issueEntries) + generateCommentFeedEntries(commentEntries)).sortedByDescending { it.time }
   134     private fun generateIssueFeedEntries(entries: List<IssueHistoryEntry>): List<IssueFeedEntry> =
   135         if (entries.isEmpty()) {
   136             emptyList()
   137         } else {
   138             entries.groupBy { it.issueid }.mapValues { (_, history) ->
   139                 history.zipWithNext().map { (cur, next) ->
   140                     IssueFeedEntry(
   141                         cur.time, cur.type, issue = diffContent(cur, next)
   142                     )
   143                 }.plus(
   144                     history.last().let { IssueFeedEntry(it.time, it.type, issue = fullContent(it)) }
   145                 )
   146             }.flatMap { it.value }
   147         }
   149     private fun generateCommentFeedEntries(entries: List<IssueCommentHistoryEntry>): List<IssueFeedEntry> =
   150         if (entries.isEmpty()) {
   151             emptyList()
   152         } else {
   153             entries.groupBy { it.commentid }.mapValues { (_, history) ->
   154                 history.zipWithNext().map { (cur, next) ->
   155                     IssueFeedEntry(
   156                         cur.time, cur.type, comment = diffContent(cur, next)
   157                     )
   158                 }.plus(
   159                     history.last().let { IssueFeedEntry(it.time, it.type, comment = fullContent(it)) }
   160                 )
   161             }.flatMap { it.value }
   162         }
   164     private fun issues(http: HttpRequest, dao: DataAccessObject) {
   165         val project = http.pathParams["project"]?.let { dao.findProjectByNode(it) }
   166         if (project == null) {
   167             http.response.sendError(404)
   168             return
   169         }
   170         val assignees = http.param("assignee")?.split(',')
   171         val comments = http.param("comments") ?: "all"
   173         val days = http.param("days")?.toIntOrNull() ?: 30
   175         val issuesFromDb = dao.listIssueHistory(project.id, days)
   176         val issueHistory = if (assignees == null) issuesFromDb else
   177             issuesFromDb.filter { assignees.contains(it.currentAssignee) }
   179         val commentsFromDb = dao.listIssueCommentHistory(project.id, days)
   180         val commentHistory = when (comments) {
   181             "all" -> commentsFromDb
   182             "new" -> commentsFromDb.filter { it.type == IssueHistoryType.NewComment }
   183             else -> emptyList()
   184         }
   186         http.view = IssueFeed(project, generateFeedEntries(issueHistory, commentHistory))
   187         http.renderFeed("issues-feed")
   188     }
   189 }

mercurial