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

Sun, 10 Oct 2021 15:12:12 +0200

author
Mike Becker <universe@uap-core.de>
date
Sun, 10 Oct 2021 15:12:12 +0200
changeset 238
1d48b38ca349
parent 236
819c5178b6fe
child 239
9365c7fb0240
permissions
-rw-r--r--

fix diff generated between feed entries with different issue IDs

/*
 * 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("<br>", 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 days = http.param("days")?.toIntOrNull() ?: 30

        val issueHistory = dao.listIssueHistory(project.id, days)
        // TODO: add comment history depending on parameter

        http.view = IssueFeed(project, generateFeedEntries(issueHistory))
        http.renderFeed("issues-feed")
    }
}

mercurial