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

changeset 242
b7f3e972b13c
parent 241
1ca4f27cefe8
child 254
55ca6cafc3dd
equal deleted inserted replaced
241:1ca4f27cefe8 242:b7f3e972b13c
28 import com.github.difflib.text.DiffRow 28 import com.github.difflib.text.DiffRow
29 import com.github.difflib.text.DiffRowGenerator 29 import com.github.difflib.text.DiffRowGenerator
30 import de.uapcore.lightpit.AbstractServlet 30 import de.uapcore.lightpit.AbstractServlet
31 import de.uapcore.lightpit.HttpRequest 31 import de.uapcore.lightpit.HttpRequest
32 import de.uapcore.lightpit.dao.DataAccessObject 32 import de.uapcore.lightpit.dao.DataAccessObject
33 import de.uapcore.lightpit.entities.IssueHistoryData 33 import de.uapcore.lightpit.entities.IssueCommentHistoryEntry
34 import de.uapcore.lightpit.entities.IssueHistoryEntry 34 import de.uapcore.lightpit.entities.IssueHistoryEntry
35 import de.uapcore.lightpit.types.IssueHistoryType
36 import de.uapcore.lightpit.viewmodel.CommentDiff
35 import de.uapcore.lightpit.viewmodel.IssueDiff 37 import de.uapcore.lightpit.viewmodel.IssueDiff
36 import de.uapcore.lightpit.viewmodel.IssueFeed 38 import de.uapcore.lightpit.viewmodel.IssueFeed
37 import de.uapcore.lightpit.viewmodel.IssueFeedEntry 39 import de.uapcore.lightpit.viewmodel.IssueFeedEntry
38 import java.text.SimpleDateFormat 40 import java.text.SimpleDateFormat
39 import javax.servlet.annotation.WebServlet 41 import javax.servlet.annotation.WebServlet
44 46
45 init { 47 init {
46 get("/%project/issues.rss", this::issues) 48 get("/%project/issues.rss", this::issues)
47 } 49 }
48 50
49 private fun fullContent(issue: IssueHistoryData) = IssueDiff( 51 val diffGenerator by lazyOf(DiffRowGenerator.create()
50 issue.id, 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 )
59
60 private fun fullContent(data: IssueCommentHistoryEntry) =
61 CommentDiff(
62 data.issueid,
63 data.commentid,
64 data.subject,
65 data.comment.replace("\r", "")
66 )
67
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 )
78
79 private fun fullContent(issue: IssueHistoryEntry) = IssueDiff(
80 issue.issueid,
51 issue.subject, 81 issue.subject,
52 issue.component, 82 issue.component,
53 issue.status.name, 83 issue.status.name,
54 issue.category.name, 84 issue.category.name,
55 issue.subject, 85 issue.subject,
58 issue.eta?.let { SimpleDateFormat("dd.MM.yyyy").format(it) } ?: "", 88 issue.eta?.let { SimpleDateFormat("dd.MM.yyyy").format(it) } ?: "",
59 issue.affected, 89 issue.affected,
60 issue.resolved 90 issue.resolved
61 ) 91 )
62 92
63 private fun diffContent(cur: IssueHistoryData, next: IssueHistoryData): IssueDiff { 93 private fun diffContent(cur: IssueHistoryEntry, next: IssueHistoryEntry): IssueDiff {
64 val generator = DiffRowGenerator.create()
65 .showInlineDiffs(true)
66 .mergeOriginalRevised(true)
67 .inlineDiffByWord(true)
68 .oldTag { start -> if (start) "<strike style=\"color:red\">" else "</strike>" }
69 .newTag { start -> if (start) "<i style=\"color: green\">" else "</i>" }
70 .build()
71
72 val prev = fullContent(next) 94 val prev = fullContent(next)
73 val diff = fullContent(cur) 95 val diff = fullContent(cur)
74 96 val result = diffGenerator.generateDiffRows(
75 val result = generator.generateDiffRows(
76 listOf( 97 listOf(
77 prev.subject, prev.component, prev.status, 98 prev.subject, prev.component, prev.status,
78 prev.category, prev.assignee, prev.eta, prev.affected, prev.resolved 99 prev.category, prev.assignee, prev.eta, prev.affected, prev.resolved
79 ), 100 ),
80 listOf( 101 listOf(
90 diff.assignee = result[4].oldLine 111 diff.assignee = result[4].oldLine
91 diff.eta = result[5].oldLine 112 diff.eta = result[5].oldLine
92 diff.affected = result[6].oldLine 113 diff.affected = result[6].oldLine
93 diff.resolved = result[7].oldLine 114 diff.resolved = result[7].oldLine
94 115
95 diff.description = generator.generateDiffRows( 116 diff.description = diffGenerator.generateDiffRows(
96 prev.description.split('\n'), 117 prev.description.split('\n'),
97 diff.description.split('\n') 118 diff.description.split('\n')
98 ).joinToString("\n", transform = DiffRow::getOldLine) 119 ).joinToString("\n", transform = DiffRow::getOldLine)
99 120
100 return diff 121 return diff
101 } 122 }
102 123
103 /** 124 /**
104 * Generates the feed entries. 125 * Generates the feed entries.
105 * Assumes that [historyEntry] is already sorted by timestamp (descending). 126 * Assumes that [issueEntries] and [commentEntries] are already sorted by timestamp (descending).
106 */ 127 */
107 private fun generateFeedEntries(historyEntry: List<IssueHistoryEntry>): List<IssueFeedEntry> = 128 private fun generateFeedEntries(
108 if (historyEntry.isEmpty()) { 129 issueEntries: List<IssueHistoryEntry>,
130 commentEntries: List<IssueCommentHistoryEntry>
131 ): List<IssueFeedEntry> =
132 (generateIssueFeedEntries(issueEntries) + generateCommentFeedEntries(commentEntries)).sortedByDescending { it.time }
133
134 private fun generateIssueFeedEntries(entries: List<IssueHistoryEntry>): List<IssueFeedEntry> =
135 if (entries.isEmpty()) {
109 emptyList() 136 emptyList()
110 } else { 137 } else {
111 historyEntry.groupBy { it.data.id }.mapValues { (_, history) -> 138 entries.groupBy { it.issueid }.mapValues { (_, history) ->
112 history.zipWithNext().map { (cur, next) -> 139 history.zipWithNext().map { (cur, next) ->
113 IssueFeedEntry( 140 IssueFeedEntry(
114 cur.time, cur.type, diffContent(cur.data, next.data) 141 cur.time, cur.type, issue = diffContent(cur, next)
115 ) 142 )
116 }.plus( 143 }.plus(
117 history.last().let { IssueFeedEntry(it.time, it.type, fullContent(it.data)) } 144 history.last().let { IssueFeedEntry(it.time, it.type, issue = fullContent(it)) }
118 ) 145 )
119 }.flatMap { it.value }.sortedByDescending { it.time } 146 }.flatMap { it.value }
147 }
148
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 }
120 } 162 }
121 163
122 private fun issues(http: HttpRequest, dao: DataAccessObject) { 164 private fun issues(http: HttpRequest, dao: DataAccessObject) {
123 val project = http.pathParams["project"]?.let { dao.findProjectByNode(it) } 165 val project = http.pathParams["project"]?.let { dao.findProjectByNode(it) }
124 if (project == null) { 166 if (project == null) {
125 http.response.sendError(404) 167 http.response.sendError(404)
126 return 168 return
127 } 169 }
128 val assignees = http.param("assignee")?.split(',') 170 val assignees = http.param("assignee")?.split(',')
171 val comments = http.param("comments") ?: "all"
129 172
130 val days = http.param("days")?.toIntOrNull() ?: 30 173 val days = http.param("days")?.toIntOrNull() ?: 30
131 174
132 val issuesFromDb = dao.listIssueHistory(project.id, days) 175 val issuesFromDb = dao.listIssueHistory(project.id, days)
133 val issueHistory = if (assignees == null) issuesFromDb else 176 val issueHistory = if (assignees == null) issuesFromDb else
134 issuesFromDb.filter { assignees.contains(it.currentAssignee) } 177 issuesFromDb.filter { assignees.contains(it.currentAssignee) }
135 178
136 // TODO: add comment history depending on parameter 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 }
137 185
138 http.view = IssueFeed(project, generateFeedEntries(issueHistory)) 186 http.view = IssueFeed(project, generateFeedEntries(issueHistory, commentHistory))
139 http.renderFeed("issues-feed") 187 http.renderFeed("issues-feed")
140 } 188 }
141 } 189 }

mercurial