Sat, 27 Nov 2021 11:04:23 +0100
use pre-warp instead of <br> for RSS feed
/* * 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. */ package de.uapcore.lightpit.servlet import com.github.difflib.text.DiffRow import com.github.difflib.text.DiffRowGenerator import de.uapcore.lightpit.AbstractServlet import de.uapcore.lightpit.HttpRequest import de.uapcore.lightpit.dao.DataAccessObject import de.uapcore.lightpit.entities.IssueHistoryData import de.uapcore.lightpit.entities.IssueHistoryEntry import de.uapcore.lightpit.viewmodel.IssueDiff import de.uapcore.lightpit.viewmodel.IssueFeed import de.uapcore.lightpit.viewmodel.IssueFeedEntry import java.text.SimpleDateFormat import javax.servlet.annotation.WebServlet @WebServlet(urlPatterns = ["/feed/*"]) class FeedServlet : AbstractServlet() { init { get("/%project/issues.rss", this::issues) } private fun fullContent(issue: IssueHistoryData) = IssueDiff( issue.id, issue.subject, issue.component, issue.status.name, issue.category.name, issue.subject, issue.description.replace("\r", ""), issue.assignee, issue.eta?.let { SimpleDateFormat("dd.MM.yyyy").format(it) } ?: "", issue.affected, issue.resolved ) private fun diffContent(cur: IssueHistoryData, next: IssueHistoryData): IssueDiff { val generator = DiffRowGenerator.create() .showInlineDiffs(true) .mergeOriginalRevised(true) .inlineDiffByWord(true) .oldTag { start -> if (start) "<strike style=\"color:red\">" else "</strike>" } .newTag { start -> if (start) "<i style=\"color: green\">" else "</i>" } .build() val prev = fullContent(next) val diff = fullContent(cur) val result = generator.generateDiffRows( listOf( prev.subject, prev.component, prev.status, prev.category, prev.assignee, prev.eta, prev.affected, prev.resolved ), listOf( diff.subject, diff.component, diff.status, diff.category, diff.assignee, diff.eta, diff.affected, diff.resolved ) ) diff.subject = result[0].oldLine diff.component = result[1].oldLine diff.status = result[2].oldLine diff.category = result[3].oldLine diff.assignee = result[4].oldLine diff.eta = result[5].oldLine diff.affected = result[6].oldLine diff.resolved = result[7].oldLine diff.description = generator.generateDiffRows( prev.description.split('\n'), diff.description.split('\n') ).joinToString("\n", transform = DiffRow::getOldLine) return diff } /** * Generates the feed entries. * Assumes that [historyEntry] is already sorted by timestamp (descending). */ private fun generateFeedEntries(historyEntry: List<IssueHistoryEntry>): List<IssueFeedEntry> = if (historyEntry.isEmpty()) { emptyList() } else { historyEntry.groupBy { it.data.id }.mapValues { (_, history) -> history.zipWithNext().map { (cur, next) -> IssueFeedEntry( cur.time, cur.type, diffContent(cur.data, next.data) ) }.plus( history.last().let { IssueFeedEntry(it.time, it.type, fullContent(it.data)) } ) }.flatMap { it.value }.sortedByDescending { it.time } } private fun issues(http: HttpRequest, dao: DataAccessObject) { val project = http.pathParams["project"]?.let { dao.findProjectByNode(it) } if (project == null) { http.response.sendError(404) return } val assignees = http.param("assignee")?.split(',') val days = http.param("days")?.toIntOrNull() ?: 30 val issuesFromDb = dao.listIssueHistory(project.id, days) val issueHistory = if (assignees == null) issuesFromDb else issuesFromDb.filter { assignees.contains(it.data.assigneeUsername) } // TODO: add comment history depending on parameter http.view = IssueFeed(project, generateFeedEntries(issueHistory)) http.renderFeed("issues-feed") } }