Tue, 03 Jan 2023 18:25:51 +0100
fix default sort criteria
universe@184 | 1 | /* |
universe@184 | 2 | * Copyright 2021 Mike Becker. All rights reserved. |
universe@184 | 3 | * |
universe@184 | 4 | * Redistribution and use in source and binary forms, with or without |
universe@184 | 5 | * modification, are permitted provided that the following conditions are met: |
universe@184 | 6 | * |
universe@184 | 7 | * 1. Redistributions of source code must retain the above copyright |
universe@184 | 8 | * notice, this list of conditions and the following disclaimer. |
universe@184 | 9 | * |
universe@184 | 10 | * 2. Redistributions in binary form must reproduce the above copyright |
universe@184 | 11 | * notice, this list of conditions and the following disclaimer in the |
universe@184 | 12 | * documentation and/or other materials provided with the distribution. |
universe@184 | 13 | * |
universe@184 | 14 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
universe@184 | 15 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
universe@184 | 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
universe@184 | 17 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
universe@184 | 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
universe@184 | 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
universe@184 | 20 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
universe@184 | 21 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
universe@184 | 22 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
universe@184 | 23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
universe@184 | 24 | */ |
universe@184 | 25 | |
universe@184 | 26 | package de.uapcore.lightpit.viewmodel |
universe@184 | 27 | |
universe@184 | 28 | import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension |
universe@184 | 29 | import com.vladsch.flexmark.ext.tables.TablesExtension |
universe@184 | 30 | import com.vladsch.flexmark.html.HtmlRenderer |
universe@184 | 31 | import com.vladsch.flexmark.parser.Parser |
universe@184 | 32 | import com.vladsch.flexmark.util.data.MutableDataSet |
universe@234 | 33 | import com.vladsch.flexmark.util.data.SharedDataKeys |
universe@184 | 34 | import de.uapcore.lightpit.entities.* |
universe@263 | 35 | import de.uapcore.lightpit.types.* |
universe@184 | 36 | import kotlin.math.roundToInt |
universe@184 | 37 | |
universe@249 | 38 | class IssueSorter(private vararg val criteria: Criteria) : Comparator<Issue> { |
universe@249 | 39 | enum class Field { |
universe@267 | 40 | DONE, PHASE, STATUS, CATEGORY, ETA, UPDATED, CREATED |
universe@249 | 41 | } |
universe@249 | 42 | |
universe@249 | 43 | data class Criteria(val field: Field, val asc: Boolean = true) |
universe@249 | 44 | |
universe@249 | 45 | override fun compare(left: Issue, right: Issue): Int { |
universe@249 | 46 | if (left == right) { |
universe@260 | 47 | return 0 |
universe@249 | 48 | } |
universe@249 | 49 | for (c in criteria) { |
universe@249 | 50 | val result = when (c.field) { |
universe@267 | 51 | Field.PHASE -> left.status.phase.compareTo(right.status.phase) |
universe@267 | 52 | Field.DONE -> (left.status.phase == IssueStatusPhase.Done).compareTo(right.status.phase == IssueStatusPhase.Done) |
universe@265 | 53 | Field.STATUS -> left.status.compareTo(right.status) |
universe@265 | 54 | Field.CATEGORY -> left.category.compareTo(right.category) |
universe@265 | 55 | Field.ETA -> left.compareEtaTo(right.eta) |
universe@249 | 56 | Field.UPDATED -> left.updated.compareTo(right.updated) |
universe@265 | 57 | Field.CREATED -> left.created.compareTo(right.created) |
universe@249 | 58 | } |
universe@249 | 59 | if (result != 0) { |
universe@249 | 60 | return if (c.asc) result else -result |
universe@249 | 61 | } |
universe@249 | 62 | } |
universe@249 | 63 | return 0 |
universe@249 | 64 | } |
universe@249 | 65 | } |
universe@249 | 66 | |
universe@184 | 67 | class IssueSummary { |
universe@184 | 68 | var open = 0 |
universe@184 | 69 | var active = 0 |
universe@184 | 70 | var done = 0 |
universe@184 | 71 | |
universe@184 | 72 | val total get() = open + active + done |
universe@184 | 73 | |
universe@184 | 74 | val openPercent get() = 100 - activePercent - donePercent |
universe@184 | 75 | val activePercent get() = if (total > 0) (100f * active / total).roundToInt() else 0 |
universe@184 | 76 | val donePercent get() = if (total > 0) (100f * done / total).roundToInt() else 100 |
universe@184 | 77 | |
universe@184 | 78 | /** |
universe@184 | 79 | * Adds the specified issue to the summary by incrementing the respective counter. |
universe@184 | 80 | * @param issue the issue |
universe@184 | 81 | */ |
universe@184 | 82 | fun add(issue: Issue) { |
universe@184 | 83 | when (issue.status.phase) { |
universe@184 | 84 | IssueStatusPhase.Open -> open++ |
universe@184 | 85 | IssueStatusPhase.WorkInProgress -> active++ |
universe@184 | 86 | IssueStatusPhase.Done -> done++ |
universe@184 | 87 | } |
universe@184 | 88 | } |
universe@184 | 89 | } |
universe@184 | 90 | |
universe@184 | 91 | class IssueDetailView( |
universe@184 | 92 | val issue: Issue, |
universe@184 | 93 | val comments: List<IssueComment>, |
universe@184 | 94 | val project: Project, |
universe@263 | 95 | val version: Version?, |
universe@263 | 96 | val component: Component?, |
universe@263 | 97 | projectIssues: List<Issue>, |
universe@263 | 98 | val currentRelations: List<IssueRelation>, |
universe@263 | 99 | /** |
universe@263 | 100 | * Optional resource key to an error message for the relation editor. |
universe@263 | 101 | */ |
universe@263 | 102 | val relationError: String? |
universe@184 | 103 | ) : View() { |
universe@263 | 104 | val relationTypes = RelationType.values() |
universe@263 | 105 | val linkableIssues = projectIssues.filterNot { it.id == issue.id } |
universe@263 | 106 | |
universe@234 | 107 | private val parser: Parser |
universe@234 | 108 | private val renderer: HtmlRenderer |
universe@184 | 109 | |
universe@184 | 110 | init { |
universe@184 | 111 | val options = MutableDataSet() |
universe@234 | 112 | .set(SharedDataKeys.EXTENSIONS, listOf(TablesExtension.create(), StrikethroughExtension.create())) |
universe@234 | 113 | parser = Parser.builder(options).build() |
universe@234 | 114 | renderer = HtmlRenderer.builder(options |
universe@234 | 115 | .set(HtmlRenderer.ESCAPE_HTML, true) |
universe@234 | 116 | ).build() |
universe@184 | 117 | |
universe@234 | 118 | issue.description = formatMarkdown(issue.description ?: "") |
universe@184 | 119 | for (comment in comments) { |
universe@234 | 120 | comment.commentFormatted = formatMarkdown(comment.comment) |
universe@184 | 121 | } |
universe@184 | 122 | } |
universe@234 | 123 | |
universe@234 | 124 | private fun formatEmojis(text: String) = text |
universe@234 | 125 | .replace("(/)", "✅") |
universe@234 | 126 | .replace("(x)", "❌") |
universe@234 | 127 | .replace("(!)", "⚡") |
universe@234 | 128 | |
universe@234 | 129 | private fun formatMarkdown(text: String) = |
universe@234 | 130 | renderer.render(parser.parse(formatEmojis(text))) |
universe@184 | 131 | } |
universe@184 | 132 | |
universe@184 | 133 | class IssueEditView( |
universe@184 | 134 | val issue: Issue, |
universe@184 | 135 | val versions: List<Version>, |
universe@184 | 136 | val components: List<Component>, |
universe@184 | 137 | val users: List<User>, |
universe@184 | 138 | val project: Project, // TODO: allow null values to create issues from the IssuesServlet |
universe@184 | 139 | val version: Version? = null, |
universe@184 | 140 | val component: Component? = null |
universe@184 | 141 | ) : EditView() { |
universe@184 | 142 | |
universe@184 | 143 | val versionsUpcoming: List<Version> |
universe@184 | 144 | val versionsRecent: List<Version> |
universe@184 | 145 | |
universe@184 | 146 | val issueStatus = IssueStatus.values() |
universe@184 | 147 | val issueCategory = IssueCategory.values() |
universe@184 | 148 | |
universe@184 | 149 | init { |
universe@184 | 150 | val recent = mutableListOf<Version>() |
universe@231 | 151 | issue.affected?.let { recent.add(it) } |
universe@184 | 152 | val upcoming = mutableListOf<Version>() |
universe@231 | 153 | issue.resolved?.let { upcoming.add(it) } |
universe@231 | 154 | |
universe@184 | 155 | for (v in versions) { |
universe@184 | 156 | if (v.status.isReleased) { |
universe@184 | 157 | if (v.status != VersionStatus.Deprecated) recent.add(v) |
universe@184 | 158 | } else { |
universe@184 | 159 | upcoming.add(v) |
universe@184 | 160 | } |
universe@184 | 161 | } |
universe@186 | 162 | versionsRecent = recent.distinct() |
universe@186 | 163 | versionsUpcoming = upcoming.distinct() |
universe@184 | 164 | } |
universe@184 | 165 | } |
universe@184 | 166 |