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

changeset 292
703591e739f4
parent 284
671c1c8fbf1c
     1.1 --- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt	Mon Oct 30 10:06:22 2023 +0100
     1.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt	Mon Oct 30 14:44:36 2023 +0100
     1.3 @@ -25,11 +25,8 @@
     1.4  
     1.5  package de.uapcore.lightpit.servlet
     1.6  
     1.7 -import de.uapcore.lightpit.AbstractServlet
     1.8 -import de.uapcore.lightpit.HttpRequest
     1.9 -import de.uapcore.lightpit.boolValidator
    1.10 +import de.uapcore.lightpit.*
    1.11  import de.uapcore.lightpit.dao.DataAccessObject
    1.12 -import de.uapcore.lightpit.dateOptValidator
    1.13  import de.uapcore.lightpit.entities.*
    1.14  import de.uapcore.lightpit.types.*
    1.15  import de.uapcore.lightpit.viewmodel.*
    1.16 @@ -86,53 +83,6 @@
    1.17          }
    1.18      }
    1.19  
    1.20 -    private fun activeProjectNavMenu(
    1.21 -        projects: List<Project>,
    1.22 -        projectInfo: ProjectInfo,
    1.23 -        selectedVersion: Version? = null,
    1.24 -        selectedComponent: Component? = null
    1.25 -    ) =
    1.26 -        projectNavMenu(
    1.27 -            projects,
    1.28 -            projectInfo.versions,
    1.29 -            projectInfo.components,
    1.30 -            projectInfo.project,
    1.31 -            selectedVersion,
    1.32 -            selectedComponent
    1.33 -        )
    1.34 -
    1.35 -    private sealed interface LookupResult<T>
    1.36 -    private class NotFound<T> : LookupResult<T>
    1.37 -    private data class Found<T>(val elem: T?) : LookupResult<T>
    1.38 -
    1.39 -    private fun <T : HasNode> HttpRequest.lookupPathParam(paramName: String, list: List<T>): LookupResult<T> {
    1.40 -        val node = pathParams[paramName]
    1.41 -        return if (node == null || node == "-") {
    1.42 -            Found(null)
    1.43 -        } else {
    1.44 -            val result = list.find { it.node == node }
    1.45 -            if (result == null) {
    1.46 -                NotFound()
    1.47 -            } else {
    1.48 -                Found(result)
    1.49 -            }
    1.50 -        }
    1.51 -    }
    1.52 -
    1.53 -    private fun obtainProjectInfo(http: HttpRequest, dao: DataAccessObject): ProjectInfo? {
    1.54 -        val project = dao.findProjectByNode(http.pathParams["project"] ?: "") ?: return null
    1.55 -
    1.56 -        val versions: List<Version> = dao.listVersions(project)
    1.57 -        val components: List<Component> = dao.listComponents(project)
    1.58 -
    1.59 -        return ProjectInfo(
    1.60 -            project,
    1.61 -            versions,
    1.62 -            components,
    1.63 -            dao.collectIssueSummary(project)
    1.64 -        )
    1.65 -    }
    1.66 -
    1.67      private fun sanitizeNode(name: String): String {
    1.68          val san = name.replace(Regex("[/\\\\]"), "-")
    1.69          return if (san.startsWith(".")) {
    1.70 @@ -144,46 +94,9 @@
    1.71  
    1.72      private fun feedPath(project: Project) = "feed/${project.node}/issues.rss"
    1.73  
    1.74 -    private data class PathInfos(
    1.75 -        val projectInfo: ProjectInfo,
    1.76 -        val version: Version?,
    1.77 -        val component: Component?
    1.78 -    ) {
    1.79 -        val project = projectInfo.project
    1.80 -        val issuesHref by lazyOf("projects/${project.node}/issues/${version?.node ?: "-"}/${component?.node ?: "-"}/")
    1.81 -    }
    1.82 -
    1.83 -    private fun withPathInfo(http: HttpRequest, dao: DataAccessObject): PathInfos? {
    1.84 -        val projectInfo = obtainProjectInfo(http, dao)
    1.85 -        if (projectInfo == null) {
    1.86 -            http.response.sendError(404)
    1.87 -            return null
    1.88 -        }
    1.89 -
    1.90 -        val version = when (val result = http.lookupPathParam("version", projectInfo.versions)) {
    1.91 -            is NotFound -> {
    1.92 -                http.response.sendError(404)
    1.93 -                return null
    1.94 -            }
    1.95 -            is Found -> {
    1.96 -                result.elem
    1.97 -            }
    1.98 -        }
    1.99 -        val component = when (val result = http.lookupPathParam("component", projectInfo.components)) {
   1.100 -            is NotFound -> {
   1.101 -                http.response.sendError(404)
   1.102 -                return null
   1.103 -            }
   1.104 -            is Found -> {
   1.105 -                result.elem
   1.106 -            }
   1.107 -        }
   1.108 -
   1.109 -        return PathInfos(projectInfo, version, component)
   1.110 -    }
   1.111 -
   1.112      private fun project(http: HttpRequest, dao: DataAccessObject) {
   1.113 -        withPathInfo(http, dao)?.run {
   1.114 +        withPathInfo(http, dao)?.let {path ->
   1.115 +            val project = path.projectInfo.project
   1.116  
   1.117              val filter = IssueFilter(http)
   1.118  
   1.119 @@ -191,7 +104,12 @@
   1.120  
   1.121              val relationsMap = if (needRelationsMap) dao.getIssueRelationMap(project, filter.includeDone) else emptyMap()
   1.122  
   1.123 -            val issues = dao.listIssues(project, filter.includeDone, version, component)
   1.124 +            val specificVersion = path.versionInfo !is OptionalPathInfo.All
   1.125 +            val version = if (path.versionInfo is OptionalPathInfo.Specific) path.versionInfo.elem else null
   1.126 +            val specificComponent = path.componentInfo !is OptionalPathInfo.All
   1.127 +            val component = if (path.componentInfo is OptionalPathInfo.Specific) path.componentInfo.elem else null
   1.128 +
   1.129 +            val issues = dao.listIssues(project, filter.includeDone, specificVersion, version, specificComponent, component)
   1.130                  .sortedWith(IssueSorter(filter.sortPrimary, filter.sortSecondary, filter.sortTertiary))
   1.131                  .filter {
   1.132                      (!filter.onlyMine || (it.assignee?.username ?: "") == (http.remoteUser ?: "<Anonymous>")) &&
   1.133 @@ -202,14 +120,9 @@
   1.134  
   1.135              with(http) {
   1.136                  pageTitle = project.name
   1.137 -                view = ProjectDetails(projectInfo, issues, filter, version, component)
   1.138 +                view = ProjectDetails(path, issues, filter)
   1.139                  feedPath = feedPath(project)
   1.140 -                navigationMenu = activeProjectNavMenu(
   1.141 -                    dao.listProjects(),
   1.142 -                    projectInfo,
   1.143 -                    version,
   1.144 -                    component
   1.145 -                )
   1.146 +                navigationMenu = projectNavMenu(dao.listProjects(), path)
   1.147                  styleSheets = listOf("projects")
   1.148                  javascript = "project-details"
   1.149                  render("project-details")
   1.150 @@ -218,23 +131,18 @@
   1.151      }
   1.152  
   1.153      private fun projectForm(http: HttpRequest, dao: DataAccessObject) {
   1.154 +        http.styleSheets = listOf("projects")
   1.155          if (!http.pathParams.containsKey("project")) {
   1.156              http.view = ProjectEditView(Project(-1), dao.listUsers())
   1.157              http.navigationMenu = projectNavMenu(dao.listProjects())
   1.158 +            http.render("project-form")
   1.159          } else {
   1.160 -            val projectInfo = obtainProjectInfo(http, dao)
   1.161 -            if (projectInfo == null) {
   1.162 -                http.response.sendError(404)
   1.163 -                return
   1.164 +            withPathInfo(http, dao)?.let { path ->
   1.165 +                http.view = ProjectEditView(path.projectInfo.project, dao.listUsers())
   1.166 +                http.navigationMenu = projectNavMenu(dao.listProjects(), path)
   1.167 +                http.render("project-form")
   1.168              }
   1.169 -            http.view = ProjectEditView(projectInfo.project, dao.listUsers())
   1.170 -            http.navigationMenu = activeProjectNavMenu(
   1.171 -                dao.listProjects(),
   1.172 -                projectInfo
   1.173 -            )
   1.174          }
   1.175 -        http.styleSheets = listOf("projects")
   1.176 -        http.render("project-form")
   1.177      }
   1.178  
   1.179      private fun projectCommit(http: HttpRequest, dao: DataAccessObject) {
   1.180 @@ -264,77 +172,50 @@
   1.181      }
   1.182  
   1.183      private fun vcsAnalyze(http: HttpRequest, dao: DataAccessObject) {
   1.184 -        val projectInfo = obtainProjectInfo(http, dao)
   1.185 -        if (projectInfo == null) {
   1.186 -            http.response.sendError(404)
   1.187 -            return
   1.188 +        withPathInfo(http, dao)?.let { path ->
   1.189 +            // if analysis is not configured, reject the request
   1.190 +            if (path.projectInfo.project.vcs == VcsType.None) {
   1.191 +                http.response.sendError(404)
   1.192 +                return
   1.193 +            }
   1.194 +
   1.195 +            // obtain the list of issues for this project to filter cross-project references
   1.196 +            val knownIds = dao.listIssues(path.projectInfo.project, true).map { it.id }
   1.197 +
   1.198 +            // read the provided commit log and merge only the refs that relate issues from the current project
   1.199 +            dao.mergeCommitRefs(parseCommitRefs(http.body).filter { knownIds.contains(it.issueId) })
   1.200          }
   1.201 -
   1.202 -        // if analysis is not configured, reject the request
   1.203 -        if (projectInfo.project.vcs == VcsType.None) {
   1.204 -            http.response.sendError(404)
   1.205 -            return
   1.206 -        }
   1.207 -
   1.208 -        // obtain the list of issues for this project to filter cross-project references
   1.209 -        val knownIds = dao.listIssues(projectInfo.project, true).map { it.id }
   1.210 -
   1.211 -        // read the provided commit log and merge only the refs that relate issues from the current project
   1.212 -        dao.mergeCommitRefs(parseCommitRefs(http.body).filter { knownIds.contains(it.issueId) })
   1.213      }
   1.214  
   1.215      private fun versions(http: HttpRequest, dao: DataAccessObject) {
   1.216 -        val projectInfo = obtainProjectInfo(http, dao)
   1.217 -        if (projectInfo == null) {
   1.218 -            http.response.sendError(404)
   1.219 -            return
   1.220 -        }
   1.221 -
   1.222 -        with(http) {
   1.223 -            pageTitle = "${projectInfo.project.name} - ${i18n("navmenu.versions")}"
   1.224 -            view = VersionsView(
   1.225 -                projectInfo,
   1.226 -                dao.listVersionSummaries(projectInfo.project)
   1.227 -            )
   1.228 -            feedPath = feedPath(projectInfo.project)
   1.229 -            navigationMenu = activeProjectNavMenu(
   1.230 -                dao.listProjects(),
   1.231 -                projectInfo
   1.232 -            )
   1.233 -            styleSheets = listOf("projects")
   1.234 -            javascript = "project-details"
   1.235 -            render("versions")
   1.236 +        withPathInfo(http, dao)?.let { path ->
   1.237 +            with(http) {
   1.238 +                pageTitle = "${path.projectInfo.project.name} - ${i18n("navmenu.versions")}"
   1.239 +                view = VersionsView(
   1.240 +                    path.projectInfo,
   1.241 +                    dao.listVersionSummaries(path.projectInfo.project)
   1.242 +                )
   1.243 +                feedPath = feedPath(path.projectInfo.project)
   1.244 +                navigationMenu = projectNavMenu(dao.listProjects(), path)
   1.245 +                styleSheets = listOf("projects")
   1.246 +                javascript = "project-details"
   1.247 +                render("versions")
   1.248 +            }
   1.249          }
   1.250      }
   1.251  
   1.252      private fun versionForm(http: HttpRequest, dao: DataAccessObject) {
   1.253 -        val projectInfo = obtainProjectInfo(http, dao)
   1.254 -        if (projectInfo == null) {
   1.255 -            http.response.sendError(404)
   1.256 -            return
   1.257 -        }
   1.258 +        withPathInfo(http, dao)?.let { path ->
   1.259 +            val version = if (path.versionInfo is OptionalPathInfo.Specific)
   1.260 +                path.versionInfo.elem else Version(-1, path.projectInfo.project.id)
   1.261  
   1.262 -        val version: Version
   1.263 -        when (val result = http.lookupPathParam("version", projectInfo.versions)) {
   1.264 -            is NotFound -> {
   1.265 -                http.response.sendError(404)
   1.266 -                return
   1.267 +            with(http) {
   1.268 +                view = VersionEditView(path.projectInfo, version)
   1.269 +                feedPath = feedPath(path.projectInfo.project)
   1.270 +                navigationMenu = projectNavMenu(dao.listProjects(), path)
   1.271 +                styleSheets = listOf("projects")
   1.272 +                render("version-form")
   1.273              }
   1.274 -            is Found -> {
   1.275 -                version = result.elem ?: Version(-1, projectInfo.project.id)
   1.276 -            }
   1.277 -        }
   1.278 -
   1.279 -        with(http) {
   1.280 -            view = VersionEditView(projectInfo, version)
   1.281 -            feedPath = feedPath(projectInfo.project)
   1.282 -            navigationMenu = activeProjectNavMenu(
   1.283 -                dao.listProjects(),
   1.284 -                projectInfo,
   1.285 -                selectedVersion = version
   1.286 -            )
   1.287 -            styleSheets = listOf("projects")
   1.288 -            render("version-form")
   1.289          }
   1.290      }
   1.291  
   1.292 @@ -385,57 +266,34 @@
   1.293      }
   1.294  
   1.295      private fun components(http: HttpRequest, dao: DataAccessObject) {
   1.296 -        val projectInfo = obtainProjectInfo(http, dao)
   1.297 -        if (projectInfo == null) {
   1.298 -            http.response.sendError(404)
   1.299 -            return
   1.300 -        }
   1.301 -
   1.302 -        with(http) {
   1.303 -            pageTitle = "${projectInfo.project.name} - ${i18n("navmenu.components")}"
   1.304 -            view = ComponentsView(
   1.305 -                projectInfo,
   1.306 -                dao.listComponentSummaries(projectInfo.project)
   1.307 -            )
   1.308 -            feedPath = feedPath(projectInfo.project)
   1.309 -            navigationMenu = activeProjectNavMenu(
   1.310 -                dao.listProjects(),
   1.311 -                projectInfo
   1.312 -            )
   1.313 -            styleSheets = listOf("projects")
   1.314 -            javascript = "project-details"
   1.315 -            render("components")
   1.316 +        withPathInfo(http, dao)?.let { path ->
   1.317 +            with(http) {
   1.318 +                pageTitle = "${path.projectInfo.project.name} - ${i18n("navmenu.components")}"
   1.319 +                view = ComponentsView(
   1.320 +                    path.projectInfo,
   1.321 +                    dao.listComponentSummaries(path.projectInfo.project)
   1.322 +                )
   1.323 +                feedPath = feedPath(path.projectInfo.project)
   1.324 +                navigationMenu = projectNavMenu(dao.listProjects(), path)
   1.325 +                styleSheets = listOf("projects")
   1.326 +                javascript = "project-details"
   1.327 +                render("components")
   1.328 +            }
   1.329          }
   1.330      }
   1.331  
   1.332      private fun componentForm(http: HttpRequest, dao: DataAccessObject) {
   1.333 -        val projectInfo = obtainProjectInfo(http, dao)
   1.334 -        if (projectInfo == null) {
   1.335 -            http.response.sendError(404)
   1.336 -            return
   1.337 -        }
   1.338 +        withPathInfo(http, dao)?.let { path ->
   1.339 +            val component = if (path.componentInfo is OptionalPathInfo.Specific)
   1.340 +                path.componentInfo.elem else Component(-1, path.projectInfo.project.id)
   1.341  
   1.342 -        val component: Component
   1.343 -        when (val result = http.lookupPathParam("component", projectInfo.components)) {
   1.344 -            is NotFound -> {
   1.345 -                http.response.sendError(404)
   1.346 -                return
   1.347 +            with(http) {
   1.348 +                view = ComponentEditView(path.projectInfo, component, dao.listUsers())
   1.349 +                feedPath = feedPath(path.projectInfo.project)
   1.350 +                navigationMenu = projectNavMenu(dao.listProjects(), path)
   1.351 +                styleSheets = listOf("projects")
   1.352 +                render("component-form")
   1.353              }
   1.354 -            is Found -> {
   1.355 -                component = result.elem ?: Component(-1, projectInfo.project.id)
   1.356 -            }
   1.357 -        }
   1.358 -
   1.359 -        with(http) {
   1.360 -            view = ComponentEditView(projectInfo, component, dao.listUsers())
   1.361 -            feedPath = feedPath(projectInfo.project)
   1.362 -            navigationMenu = activeProjectNavMenu(
   1.363 -                dao.listProjects(),
   1.364 -                projectInfo,
   1.365 -                selectedComponent = component
   1.366 -            )
   1.367 -            styleSheets = listOf("projects")
   1.368 -            render("component-form")
   1.369          }
   1.370      }
   1.371  
   1.372 @@ -484,29 +342,23 @@
   1.373          issue: Issue,
   1.374          relationError: String? = null
   1.375      ) {
   1.376 -        withPathInfo(http, dao)?.run {
   1.377 +        withPathInfo(http, dao)?.let {path ->
   1.378              val comments = dao.listComments(issue)
   1.379  
   1.380              with(http) {
   1.381 -                pageTitle = "${projectInfo.project.name}: #${issue.id} ${issue.subject}"
   1.382 +                pageTitle = "${path.projectInfo.project.name}: #${issue.id} ${issue.subject}"
   1.383                  view = IssueDetailView(
   1.384 +                    path,
   1.385                      issue,
   1.386                      comments,
   1.387 -                    project,
   1.388 -                    version,
   1.389 -                    component,
   1.390 -                    dao.listIssues(project, true),
   1.391 +                    path.projectInfo.project,
   1.392 +                    dao.listIssues(path.projectInfo.project, true),
   1.393                      dao.listIssueRelations(issue),
   1.394                      relationError,
   1.395                      dao.listCommitRefs(issue)
   1.396                  )
   1.397 -                feedPath = feedPath(projectInfo.project)
   1.398 -                navigationMenu = activeProjectNavMenu(
   1.399 -                    dao.listProjects(),
   1.400 -                    projectInfo,
   1.401 -                    version,
   1.402 -                    component
   1.403 -                )
   1.404 +                feedPath = feedPath(path.projectInfo.project)
   1.405 +                navigationMenu = projectNavMenu(dao.listProjects(), path)
   1.406                  styleSheets = listOf("projects")
   1.407                  javascript = "issue-editor"
   1.408                  render("issue-view")
   1.409 @@ -515,23 +367,25 @@
   1.410      }
   1.411  
   1.412      private fun issueForm(http: HttpRequest, dao: DataAccessObject) {
   1.413 -        withPathInfo(http, dao)?.run {
   1.414 +        withPathInfo(http, dao)?.let { path ->
   1.415              val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) ?: Issue(
   1.416                  -1,
   1.417 -                project,
   1.418 +                path.projectInfo.project,
   1.419              )
   1.420  
   1.421              // for new issues set some defaults
   1.422              if (issue.id < 0) {
   1.423                  // pre-select component, if available in the path info
   1.424 -                issue.component = component
   1.425 +                if (path.componentInfo is OptionalPathInfo.Specific) {
   1.426 +                    issue.component = path.componentInfo.elem
   1.427 +                }
   1.428  
   1.429                  // pre-select version, if available in the path info
   1.430 -                if (version != null) {
   1.431 -                    if (version.status.isReleased) {
   1.432 -                        issue.affected = version
   1.433 +                if (path.versionInfo is OptionalPathInfo.Specific) {
   1.434 +                    if (path.versionInfo.elem.status.isReleased) {
   1.435 +                        issue.affected = path.versionInfo.elem
   1.436                      } else {
   1.437 -                        issue.resolved = version
   1.438 +                        issue.resolved = path.versionInfo.elem
   1.439                      }
   1.440                  }
   1.441              }
   1.442 @@ -539,20 +393,14 @@
   1.443              with(http) {
   1.444                  view = IssueEditView(
   1.445                      issue,
   1.446 -                    projectInfo.versions,
   1.447 -                    projectInfo.components,
   1.448 +                    path.projectInfo.versions,
   1.449 +                    path.projectInfo.components,
   1.450                      dao.listUsers(),
   1.451 -                    project,
   1.452 -                    version,
   1.453 -                    component
   1.454 +                    path.projectInfo.project,
   1.455 +                    path
   1.456                  )
   1.457 -                feedPath = feedPath(projectInfo.project)
   1.458 -                navigationMenu = activeProjectNavMenu(
   1.459 -                    dao.listProjects(),
   1.460 -                    projectInfo,
   1.461 -                    version,
   1.462 -                    component
   1.463 -                )
   1.464 +                feedPath = feedPath(path.projectInfo.project)
   1.465 +                navigationMenu = projectNavMenu(dao.listProjects(), path)
   1.466                  styleSheets = listOf("projects")
   1.467                  javascript = "issue-editor"
   1.468                  render("issue-form")
   1.469 @@ -606,7 +454,7 @@
   1.470          withPathInfo(http, dao)?.run {
   1.471              val issue = Issue(
   1.472                  http.param("id")?.toIntOrNull() ?: -1,
   1.473 -                project
   1.474 +                projectInfo.project
   1.475              ).apply {
   1.476                  component = dao.findComponent(http.param("component")?.toIntOrNull() ?: -1)
   1.477                  category = IssueCategory.valueOf(http.param("category") ?: "")

mercurial