Thu, 30 Jan 2025 21:20:27 +0100
prepare implementation of "variants"
introduces the class, implements basic DAO stuff, and
adds the new path parameter
relates to #491
--- a/setup/postgres/psql_create_tables.sql Thu Jan 30 19:47:21 2025 +0100 +++ b/setup/postgres/psql_create_tables.sql Thu Jan 30 21:20:27 2025 +0100 @@ -58,6 +58,20 @@ create unique index lpit_component_node_unique on lpit_component (project, node); +create table lpit_variant +( + id serial primary key, + project integer not null references lpit_project (projectid), + name text not null, + node text not null, + color char(6) not null default '000000', + ordinal integer not null default 0, + description text, + active boolean not null default true +); + +create unique index lpit_variant_node_unique on lpit_variant (project, node); + create type issue_status as enum ( 'InSpecification', 'ToDo',
--- a/setup/postgres/psql_patch_1.5.0.sql Thu Jan 30 19:47:21 2025 +0100 +++ b/setup/postgres/psql_patch_1.5.0.sql Thu Jan 30 21:20:27 2025 +0100 @@ -2,3 +2,17 @@ alter table lpit_issue_history_event add userid integer null references lpit_user (userid) on delete set null; + +create table lpit_variant +( + id serial primary key, + project integer not null references lpit_project (projectid), + name text not null, + node text not null, + color char(6) not null default '000000', + ordinal integer not null default 0, + description text, + active boolean not null default true +); + +create unique index lpit_variant_node_unique on lpit_variant (project, node);
--- a/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Thu Jan 30 19:47:21 2025 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Thu Jan 30 21:20:27 2025 +0100 @@ -59,6 +59,13 @@ fun insertComponent(component: Component) fun updateComponent(component: Component) + fun listVariants(project: Project): List<Variant> + //fun listVariantSummaries(project: Project): List<VariantSummary> + fun findVariant(id: Int): Variant? + fun findVariantByNode(project: Project, node: String): Variant? + fun insertVariant(variant: Variant) + fun updateVariant(variant: Variant) + /** * Lists all projects ordered by name. */
--- a/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Thu Jan 30 19:47:21 2025 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Thu Jan 30 21:20:27 2025 +0100 @@ -354,6 +354,114 @@ //</editor-fold> + //<editor-fold desc="Variant"> + //language=SQL + private val variantQuery = + """ + select id, project, name, node, color, ordinal, description, active + from lpit_variant + """.trimIndent() + + private fun ResultSet.extractVariant(): Variant = + Variant(getInt("id"), getInt("project")).apply { + name = getString("name") + node = getString("node") + color = try { + WebColor(getString("color")) + } catch (ex: IllegalArgumentException) { + WebColor("000000") + } + ordinal = getInt("ordinal") + description = getString("description") + active = getBoolean("active") + } + + private fun PreparedStatement.setVariant(index: Int, variant: Variant): Int { + with(variant) { + var i = index + setStringSafe(i++, name) + setStringSafe(i++, node) + setStringSafe(i++, color.hex) + setInt(i++, ordinal) + setStringOrNull(i++, description) + setBoolean(i++, active) + return i + } + } + + override fun listVariants(project: Project): List<Variant> = + withStatement("$variantQuery where project = ? order by ordinal, lower(name)") { + setInt(1, project.id) + queryAll { it.extractVariant() } + } +/* + override fun listVariantSummaries(project: Project): List<VariantSummary> = + withStatement( + """ + with issues as ( + select variant, phase, count(issueid) as total + from lpit_issue + join lpit_issue_phases using (status) + group by variant, phase + ), + summary as ( + select v.id, phase, total + from lpit_variant v + left join issues i on v.id = i.variant + ) + select c.id, project, name, node, color, ordinal, description, active, + userid, username, givenname, lastname, mail, + open.total as open, wip.total as wip, done.total as done + from lpit_component c + left join lpit_user on lead = userid + left join summary open on c.id = open.id and open.phase = 0 + left join summary wip on c.id = wip.id and wip.phase = 1 + left join summary done on c.id = done.id and done.phase = 2 + where c.project = ? + order by ordinal, name + """.trimIndent() + ) { + setInt(1, project.id) + queryAll { rs -> + ComponentSummary(rs.extractComponent()).apply { + issueSummary.open = rs.getInt("open") + issueSummary.active = rs.getInt("wip") + issueSummary.done = rs.getInt("done") + } + } + } +*/ + override fun findVariant(id: Int): Variant? = + withStatement("$variantQuery where id = ?") { + setInt(1, id) + querySingle { it.extractVariant() } + } + + override fun findVariantByNode(project: Project, node: String): Variant? = + withStatement("$variantQuery where project = ? and node = ?") { + setInt(1, project.id) + setString(2, node) + querySingle { it.extractVariant() } + } + + override fun insertVariant(variant: Variant) { + withStatement("insert into lpit_variant (name, node, color, ordinal, description, active, project) values (?, ?, ?, ?, ?, ?, ?)") { + val col = setVariant(1, variant) + setInt(col, variant.projectid) + executeUpdate() + } + } + + override fun updateVariant(variant: Variant) { + withStatement("update lpit_variant set name = ?, node = ?, color = ?, ordinal = ?, description = ?, active = ? where id = ?") { + val col = setVariant(1, variant) + setInt(col, variant.id) + executeUpdate() + } + } + + //</editor-fold> + //<editor-fold desc="Project"> //language=SQL
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Variant.kt Thu Jan 30 21:20:27 2025 +0100 @@ -0,0 +1,37 @@ +/* + * Copyright 2025 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.entities + +import de.uapcore.lightpit.types.WebColor + +data class Variant(override val id: Int, val projectid: Int) : Entity, HasNode { + var name: String = "" + override var node: String = name + var ordinal = 0 + var color = WebColor("000000") + var description: String? = null + var active = true +} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/servlet/IssuesServlet.kt Thu Jan 30 19:47:21 2025 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/IssuesServlet.kt Thu Jan 30 21:20:27 2025 +0100 @@ -11,7 +11,7 @@ @WebServlet(urlPatterns = ["/issues/*"]) class IssuesServlet : AbstractServlet() { - private val pathInfos = PathInfosOnlyIssues("issues/") + private val pathInfos = PathInfosSimple() init { get("/", this::issues) @@ -49,7 +49,7 @@ return } if (http.param("in_project") != null) { - http.response.sendRedirect("${http.baseHref}projects/${issue.project.node}/issues/-/-/${issue.id}") + http.response.sendRedirect("${http.baseHref}${PathInfosSimple(issue.project)}${issue.id}") return } renderIssueView(http, dao, issue, pathInfos) @@ -62,7 +62,7 @@ return } if (http.param("in_project") != null) { - http.response.sendRedirect("${http.baseHref}projects/${issue.project.node}/issues/-/-/${issue.id}/edit") + http.response.sendRedirect("${http.baseHref}${PathInfosSimple(issue.project)}${issue.id}/edit") return }
--- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Thu Jan 30 19:47:21 2025 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Thu Jan 30 21:20:27 2025 +0100 @@ -46,7 +46,7 @@ init { get("/", this::projects) get("/%project", this::project) - get("/%project/issues/%version/%component/", this::project) + get("/%project/issues/%version/%component/%variant/", this::project) get("/%project/edit", this::projectForm) get("/-/create", this::projectForm) post("/-/commit", this::projectCommit) @@ -62,13 +62,13 @@ get("/%project/components/-/create", this::componentForm) post("/%project/components/-/commit", this::componentCommit) - get("/%project/issues/%version/%component/%issue", this::issue) - get("/%project/issues/%version/%component/%issue/edit", this::issueForm) - post("/%project/issues/%version/%component/%issue/comment", this::issueComment) - post("/%project/issues/%version/%component/%issue/relation", this::issueRelation) - get("/%project/issues/%version/%component/%issue/removeRelation", this::issueRemoveRelation) - get("/%project/issues/%version/%component/-/create", this::issueForm) - post("/%project/issues/%version/%component/-/commit", this::issueCommit) + get("/%project/issues/%version/%component/%variant/%issue", this::issue) + get("/%project/issues/%version/%component/%variant/%issue/edit", this::issueForm) + post("/%project/issues/%version/%component/%variant/%issue/comment", this::issueComment) + post("/%project/issues/%version/%component/%variant/%issue/relation", this::issueRelation) + get("/%project/issues/%version/%component/%variant/%issue/removeRelation", this::issueRemoveRelation) + get("/%project/issues/%version/%component/%variant/-/create", this::issueForm) + post("/%project/issues/%version/%component/%variant/-/commit", this::issueCommit) } private fun projects(http: HttpRequest, dao: DataAccessObject) { @@ -78,6 +78,7 @@ project = it, versions = dao.listVersions(it), components = emptyList(), // not required in this view + variants = emptyList(), // not required in this view issueSummary = dao.collectIssueSummary(it) ) } @@ -101,7 +102,7 @@ private fun project(http: HttpRequest, dao: DataAccessObject) { withPathInfo(http, dao)?.let {path -> - val project = path.projectInfo.project + val project = path.project val filter = IssueFilter(http, dao) @@ -137,7 +138,7 @@ http.render("project-form") } else { withPathInfo(http, dao)?.let { path -> - http.view = ProjectEditView(path.projectInfo.project, dao.listUsers()) + http.view = ProjectEditView(path.project, dao.listUsers()) http.navigationMenu = projectNavMenu(dao.listProjects(), path) http.render("project-form") } @@ -173,13 +174,13 @@ private fun vcsAnalyze(http: HttpRequest, dao: DataAccessObject) { withPathInfo(http, dao)?.let { path -> // if analysis is not configured, reject the request - if (path.projectInfo.project.vcs == VcsType.None) { + if (path.project.vcs == VcsType.None) { http.response.sendError(404) return } // obtain the list of issues for this project to filter cross-project references - val knownIds = dao.listIssues(path.projectInfo.project, true).map { it.id } + val knownIds = dao.listIssues(path.project, true).map { it.id } // read the provided commit log and merge only the refs that relate issues from the current project dao.mergeCommitRefs(parseCommitRefs(http.body).filter { knownIds.contains(it.issueId) }) @@ -189,10 +190,10 @@ private fun versions(http: HttpRequest, dao: DataAccessObject) { withPathInfo(http, dao)?.let { path -> with(http) { - pageTitle = "${path.projectInfo.project.name} - ${i18n("navmenu.versions")}" + pageTitle = "${path.project.name} - ${i18n("navmenu.versions")}" view = VersionsView( path.projectInfo, - dao.listVersionSummaries(path.projectInfo.project) + dao.listVersionSummaries(path.project) ) navigationMenu = projectNavMenu(dao.listProjects(), path) styleSheets = listOf("projects") @@ -205,7 +206,7 @@ private fun versionForm(http: HttpRequest, dao: DataAccessObject) { withPathInfo(http, dao)?.let { path -> val version = if (path.versionInfo is OptionalPathInfo.Specific) - path.versionInfo.elem else Version(-1, path.projectInfo.project.id) + path.versionInfo.elem else Version(-1, path.project.id) with(http) { view = VersionEditView(path.projectInfo, version) @@ -265,10 +266,10 @@ private fun components(http: HttpRequest, dao: DataAccessObject) { withPathInfo(http, dao)?.let { path -> with(http) { - pageTitle = "${path.projectInfo.project.name} - ${i18n("navmenu.components")}" + pageTitle = "${path.project.name} - ${i18n("navmenu.components")}" view = ComponentsView( path.projectInfo, - dao.listComponentSummaries(path.projectInfo.project) + dao.listComponentSummaries(path.project) ) navigationMenu = projectNavMenu(dao.listProjects(), path) styleSheets = listOf("projects") @@ -281,7 +282,7 @@ private fun componentForm(http: HttpRequest, dao: DataAccessObject) { withPathInfo(http, dao)?.let { path -> val component = if (path.componentInfo is OptionalPathInfo.Specific) - path.componentInfo.elem else Component(-1, path.projectInfo.project.id) + path.componentInfo.elem else Component(-1, path.project.id) with(http) { view = ComponentEditView(path.projectInfo, component, dao.listUsers()) @@ -337,7 +338,7 @@ withPathInfo(http, dao)?.let { path -> val issue = http.pathParams["issue"]?.toIntOrNull()?.let(dao::findIssue) ?: Issue( -1, - path.projectInfo.project, + path.project, ) // for new issues set some defaults @@ -391,7 +392,7 @@ withPathInfo(http, dao)?.run { val issue = Issue( http.param("id")?.toIntOrNull() ?: -1, - projectInfo.project + project ).applyFormData(http, dao) val openId = if (issue.id < 0) {
--- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/NavMenus.kt Thu Jan 30 19:47:21 2025 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/NavMenus.kt Thu Jan 30 21:20:27 2025 +0100 @@ -60,6 +60,7 @@ sequence { val cnode = pathInfos.componentInfo.node val vnode = pathInfos.versionInfo.node + val varnode = pathInfos.variantInfo.node for (project in projects) { val active = project == pathInfos.projectInfo.project yield( @@ -84,7 +85,7 @@ level = 2, caption = "navmenu.all", resolveCaption = true, - href = "projects/${project.node}/issues/-/${cnode}/", + href = "projects/${project.node}/issues/-/${cnode}/${varnode}/", iconColor = "#000000", active = vnode == "-", ) @@ -94,7 +95,7 @@ level = 2, caption = "navmenu.none", resolveCaption = true, - href = "projects/${project.node}/issues/~/${cnode}/", + href = "projects/${project.node}/issues/~/${cnode}/${varnode}/", iconColor = "#000000", active = vnode == "~", ) @@ -106,7 +107,7 @@ level = 2, caption = version.name, title = "version.status.${version.status}", - href = "projects/${project.node}/issues/${version.node}/${cnode}/", + href = "projects/${project.node}/issues/${version.node}/${cnode}/${varnode}/", iconColor = "version-${version.status}", active = version.node == vnode ) @@ -125,7 +126,7 @@ level = 2, caption = "navmenu.all", resolveCaption = true, - href = "projects/${project.node}/issues/${vnode}/-/", + href = "projects/${project.node}/issues/${vnode}/-/${varnode}/", iconColor = "#000000", active = cnode == "-", ) @@ -135,7 +136,7 @@ level = 2, caption = "navmenu.none", resolveCaption = true, - href = "projects/${project.node}/issues/${vnode}/~/", + href = "projects/${project.node}/issues/${vnode}/~/${varnode}/", iconColor = "#000000", active = cnode == "~", ) @@ -146,12 +147,52 @@ NavMenuEntry( level = 2, caption = component.name, - href = "projects/${project.node}/issues/${vnode}/${component.node}/", + href = "projects/${project.node}/issues/${vnode}/${component.node}/${varnode}/", iconColor = "${component.color}", active = component.node == cnode ) ) } + yield( + NavMenuEntry( + level = 1, + caption = "navmenu.variants", + resolveCaption = true, + href = "projects/${project.node}/variants/" + ) + ) + yield( + NavMenuEntry( + level = 2, + caption = "navmenu.all", + resolveCaption = true, + href = "projects/${project.node}/issues/${vnode}/${cnode}/-/", + iconColor = "#000000", + active = varnode == "-", + ) + ) + yield( + NavMenuEntry( + level = 2, + caption = "navmenu.none", + resolveCaption = true, + href = "projects/${project.node}/issues/${vnode}/${cnode}/~/", + iconColor = "#000000", + active = varnode == "~", + ) + ) + for (variant in pathInfos.projectInfo.variants) { + if (!variant.active && variant.node != cnode) continue + yield( + NavMenuEntry( + level = 2, + caption = variant.name, + href = "projects/${project.node}/issues/${vnode}/${cnode}/${variant.node}/", + iconColor = "${variant.color}", + active = variant.node == varnode + ) + ) + } } } }.toList()
--- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/PathInfos.kt Thu Jan 30 19:47:21 2025 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/PathInfos.kt Thu Jan 30 21:20:27 2025 +0100 @@ -29,15 +29,25 @@ import de.uapcore.lightpit.OptionalPathInfo import de.uapcore.lightpit.dao.DataAccessObject import de.uapcore.lightpit.entities.Component +import de.uapcore.lightpit.entities.Project +import de.uapcore.lightpit.entities.Variant import de.uapcore.lightpit.entities.Version -abstract class PathInfos(val issuesHref: String) -class PathInfosOnlyIssues(issuesHref: String): PathInfos(issuesHref) +abstract class PathInfos(val issuesHref: String) { + override fun toString(): String { + return issuesHref + } +} +class PathInfosSimple(project: Project? = null) + : PathInfos(if (project == null) "issues/" else "projects/${project.node}/issues/-/-/-/") data class PathInfosFull( val projectInfo: ProjectInfo, - val versionInfo: OptionalPathInfo<Version>, - val componentInfo: OptionalPathInfo<Component> -): PathInfos("projects/${projectInfo.project.node}/issues/${versionInfo.node}/${componentInfo.node}/") + val versionInfo: OptionalPathInfo<Version> = OptionalPathInfo.All, + val componentInfo: OptionalPathInfo<Component> = OptionalPathInfo.All, + val variantInfo: OptionalPathInfo<Variant> = OptionalPathInfo.All, +): PathInfos("projects/${projectInfo.project.node}/issues/${versionInfo.node}/${componentInfo.node}/${variantInfo.node}/") { + val project = projectInfo.project +} private fun obtainProjectInfo(http: HttpRequest, dao: DataAccessObject): ProjectInfo? { val pathParam = http.pathParams["project"] ?: return null @@ -45,11 +55,13 @@ val versions: List<Version> = dao.listVersions(project) val components: List<Component> = dao.listComponents(project) + val variants: List<Variant> = dao.listVariants(project) return ProjectInfo( project, versions, components, + variants, dao.collectIssueSummary(project) ) } @@ -63,11 +75,12 @@ val version = http.lookupPathParam("version", projectInfo.versions) val component = http.lookupPathParam("component", projectInfo.components) + val variant = http.lookupPathParam("variant", projectInfo.variants) if (version == OptionalPathInfo.NotFound || component == OptionalPathInfo.NotFound) { http.response.sendError(404) return null } - return PathInfosFull(projectInfo, version, component) + return PathInfosFull(projectInfo, version, component, variant) }
--- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Projects.kt Thu Jan 30 19:47:21 2025 +0100 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Projects.kt Thu Jan 30 21:20:27 2025 +0100 @@ -35,6 +35,7 @@ */ var versions: List<Version>, var components: List<Component>, + var variants: List<Variant>, var issueSummary: IssueSummary ) { val latestVersion = versions.firstOrNull { it.status.isReleased } @@ -54,6 +55,7 @@ val issueSummary = IssueSummary() val versionInfo: VersionInfo? val componentDetails: Component? + val variantDetails: Variant? init { feedHref = "feed/${projectInfo.project.node}/issues.rss" @@ -66,6 +68,10 @@ is OptionalPathInfo.Specific -> cinfo.elem else -> null } + variantDetails = when (val varinfo = pathInfos.variantInfo){ + is OptionalPathInfo.Specific -> varinfo.elem + else -> null + } } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Variants.kt Thu Jan 30 21:20:27 2025 +0100 @@ -0,0 +1,44 @@ +/* + * 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.viewmodel + +import de.uapcore.lightpit.entities.Variant + +class VariantSummary( + val variant: Variant, +) { + val issueSummary = IssueSummary() +} + +class VariantsView( + val projectInfo: ProjectInfo, + val variantInfos: List<VariantSummary> +) : View() + +class VariantEditView( + val projectInfo: ProjectInfo, + val variant: Variant +) : EditView()
--- a/src/main/resources/localization/strings.properties Thu Jan 30 19:47:21 2025 +0100 +++ b/src/main/resources/localization/strings.properties Thu Jan 30 21:20:27 2025 +0100 @@ -143,6 +143,7 @@ navmenu.all=all navmenu.components=Components navmenu.none=none +navmenu.variants=Variants navmenu.versions=Versions no-projects=Welcome to LightPIT. Start off by creating a new project! no-users=No developers have been configured yet.
--- a/src/main/resources/localization/strings_de.properties Thu Jan 30 19:47:21 2025 +0100 +++ b/src/main/resources/localization/strings_de.properties Thu Jan 30 21:20:27 2025 +0100 @@ -143,6 +143,7 @@ navmenu.all=Alle navmenu.components=Komponenten navmenu.none=Keine +navmenu.variants=Varianten navmenu.versions=Versionen no-projects=Wilkommen bei LightPIT. Beginnen Sie mit der Erstellung eines Projektes! no-users=Bislang wurden keine Entwickler hinterlegt.
--- a/src/main/webapp/WEB-INF/jsp/components.jsp Thu Jan 30 19:47:21 2025 +0100 +++ b/src/main/webapp/WEB-INF/jsp/components.jsp Thu Jan 30 21:20:27 2025 +0100 @@ -37,7 +37,7 @@ <div> <a href="./projects/${project.node}/components/-/create" class="button"><fmt:message key="button.component.create"/></a> <button onclick="toggleProjectDetails()" id="toggle-details-button"><fmt:message key="button.project.details"/></button> - <a href="./projects/${project.node}/issues/-/-/-/create" class="button"><fmt:message key="button.issue.create"/></a> + <a href="./projects/${project.node}/issues/-/-/-/-/create" class="button"><fmt:message key="button.issue.create"/></a> </div> <h2><fmt:message key="progress" /></h2> @@ -76,7 +76,7 @@ <td rowspan="2" style="width: 2em;"><a href="./projects/${project.node}/components/${componentInfo.component.node}/edit">✎</a></td> <td rowspan="2"> <div class="navmenu-icon" style="background-color: ${componentInfo.component.color}"></div> - <a href="./projects/${project.node}/issues/-/${componentInfo.component.node}/" + <a href="./projects/${project.node}/issues/-/${componentInfo.component.node}/-/" <c:if test="${not componentInfo.component.active}">style="text-decoration: line-through;"</c:if> > <c:out value="${componentInfo.component.name}"/>
--- a/src/main/webapp/WEB-INF/jsp/issue-view.jsp Thu Jan 30 19:47:21 2025 +0100 +++ b/src/main/webapp/WEB-INF/jsp/issue-view.jsp Thu Jan 30 21:20:27 2025 +0100 @@ -67,14 +67,14 @@ <tr> <th><fmt:message key="project"/></th> <td> - <a href="./projects/${issue.project.node}/issues/-/-/"> + <a href="./projects/${issue.project.node}/issues/-/-/-/"> <c:out value="${issue.project.name}" /> </a> </td> <th><fmt:message key="component"/></th> <td> <c:if test="${not empty issue.component}"> - <a href="./projects/${issue.project.node}/issues/-/${issue.component.node}/"> + <a href="./projects/${issue.project.node}/issues/-/${issue.component.node}/-/"> <c:out value="${issue.component.name}"/> </a> </c:if>
--- a/src/main/webapp/WEB-INF/jsp/issues-feed.jsp Thu Jan 30 19:47:21 2025 +0100 +++ b/src/main/webapp/WEB-INF/jsp/issues-feed.jsp Thu Jan 30 21:20:27 2025 +0100 @@ -30,7 +30,7 @@ <c:if test="${not empty viewmodel.project}"> <title><c:out value="${viewmodel.project.name}"/>|<fmt:message key="feed.issues.title"/></title> <link>${baseHref}projects/${viewmodel.project.node}</link> - <c:set var="issueHref" value="${baseHref}projects/${viewmodel.project.node}/issues/-/-/"/> + <c:set var="issueHref" value="${baseHref}projects/${viewmodel.project.node}/issues/-/-/-/"/> </c:if> <c:if test="${empty viewmodel.project}"> <title><fmt:message key="feed.issues.title"/></title>
--- a/src/main/webapp/WEB-INF/jsp/projects.jsp Thu Jan 30 19:47:21 2025 +0100 +++ b/src/main/webapp/WEB-INF/jsp/projects.jsp Thu Jan 30 21:20:27 2025 +0100 @@ -79,12 +79,12 @@ </td> <td class="hright"> <c:if test="${not empty projectInfo.latestVersion}"> - <a href="./projects/${project.node}/issues/${projectInfo.latestVersion.node}/-/"><c:out value="${projectInfo.latestVersion.name}"/></a> + <a href="./projects/${project.node}/issues/${projectInfo.latestVersion.node}/-/-/"><c:out value="${projectInfo.latestVersion.name}"/></a> </c:if> </td> <td class="hright"> <c:if test="${not empty projectInfo.nextVersion}"> - <a href="./projects/${project.node}/issues/${projectInfo.nextVersion.node}/-/"><c:out value="${projectInfo.nextVersion.name}"/></a> + <a href="./projects/${project.node}/issues/${projectInfo.nextVersion.node}/-/-/"><c:out value="${projectInfo.nextVersion.name}"/></a> </c:if> </td> <td class="hright">${projectInfo.issueSummary.open}</td>
--- a/src/main/webapp/WEB-INF/jsp/versions.jsp Thu Jan 30 19:47:21 2025 +0100 +++ b/src/main/webapp/WEB-INF/jsp/versions.jsp Thu Jan 30 21:20:27 2025 +0100 @@ -36,7 +36,7 @@ <div> <a href="./projects/${project.node}/versions/-/create" class="button"><fmt:message key="button.version.create"/></a> <button onclick="toggleProjectDetails()" id="toggle-details-button"><fmt:message key="button.project.details"/></button> - <a href="./projects/${project.node}/issues/-/-/-/create" class="button"><fmt:message key="button.issue.create"/></a> + <a href="./projects/${project.node}/issues/-/-/-/-/create" class="button"><fmt:message key="button.issue.create"/></a> </div> <h2><fmt:message key="progress"/></h2> @@ -82,7 +82,7 @@ <td rowspan="2" style="width: 2em;"><a href="./projects/${project.node}/versions/${versionInfo.version.node}/edit">✎</a></td> <td rowspan="2"> - <a href="./projects/${project.node}/issues/${versionInfo.version.node}/-/"> + <a href="./projects/${project.node}/issues/${versionInfo.version.node}/-/-/"> <c:out value="${versionInfo.version.name}"/> </a> <div class="version-tag version-${versionInfo.version.status}"