Skip to content

Commit d3bf381

Browse files
authored
Merge pull request #858 from modelix/fix/delete-repository-modal
MODELIX-781 Add confirmation modal to "delete repository" button
2 parents a94ee79 + c0beae2 commit d3bf381

File tree

4 files changed

+105
-32
lines changed

4 files changed

+105
-32
lines changed

model-server/src/main/kotlin/org/modelix/model/server/handlers/RepositoriesManager.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ class RepositoriesManager(val client: LocalModelClient) : IRepositoriesManager {
181181
}
182182

183183
fun getBranchNames(repositoryId: RepositoryId): Set<String> {
184-
return store.getGenericStore()[branchListKey(repositoryId)]?.lines()?.toSet().orEmpty()
184+
return store.getGenericStore()[branchListKey(repositoryId)]?.ifEmpty { null }?.lines()?.toSet().orEmpty()
185185
}
186186

187187
override fun getBranches(repositoryId: RepositoryId): Set<BranchReference> {

model-server/src/main/kotlin/org/modelix/model/server/handlers/ui/RepositoryOverview.kt

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@ import io.ktor.server.routing.routing
99
import kotlinx.html.FlowContent
1010
import kotlinx.html.FlowOrInteractiveOrPhrasingContent
1111
import kotlinx.html.a
12-
import kotlinx.html.form
12+
import kotlinx.html.button
1313
import kotlinx.html.h1
1414
import kotlinx.html.i
15+
import kotlinx.html.onClick
1516
import kotlinx.html.p
16-
import kotlinx.html.postButton
17+
import kotlinx.html.script
1718
import kotlinx.html.span
1819
import kotlinx.html.table
1920
import kotlinx.html.tbody
@@ -22,6 +23,8 @@ import kotlinx.html.th
2223
import kotlinx.html.thead
2324
import kotlinx.html.title
2425
import kotlinx.html.tr
26+
import kotlinx.html.unsafe
27+
import org.modelix.model.lazy.RepositoryId
2528
import org.modelix.model.server.handlers.IRepositoriesManager
2629
import org.modelix.model.server.templates.PageWithMenuBar
2730

@@ -31,7 +34,28 @@ class RepositoryOverview(private val repoManager: IRepositoriesManager) {
3134
application.routing {
3235
get("/repos") {
3336
call.respondHtmlTemplate(PageWithMenuBar("repos/", "..")) {
34-
headContent { title("Repositories") }
37+
headContent {
38+
title("Repositories")
39+
script(type = "text/javascript") {
40+
unsafe {
41+
+"""
42+
function removeBranch(repository, branch) {
43+
if (confirm('Are you sure you want to delete the branch ' + branch + ' of repository ' +repository + '?')) {
44+
fetch('../v2/repositories/' + repository + '/branches/' + branch, { method: 'DELETE'})
45+
.then( _ => location.reload())
46+
}
47+
}
48+
49+
function removeRepository(repository) {
50+
if (confirm('Are you sure you want to delete the repository ' + repository + '?')) {
51+
fetch('../v2/repositories/' + repository + '/delete', { method: 'POST'})
52+
.then( _ => location.reload())
53+
}
54+
}
55+
""".trimIndent()
56+
}
57+
}
58+
}
3559
bodyContent { buildMainPage() }
3660
}
3761
}
@@ -47,7 +71,10 @@ class RepositoryOverview(private val repoManager: IRepositoriesManager) {
4771
table {
4872
thead {
4973
tr {
50-
th { +"Repository" }
74+
th {
75+
colSpan = "2"
76+
+"Repository"
77+
}
5178
th { +"Branch" }
5279
th {
5380
colSpan = "3"
@@ -58,11 +85,16 @@ class RepositoryOverview(private val repoManager: IRepositoriesManager) {
5885
tbody {
5986
for (repository in repositories) {
6087
val branches = repoManager.getBranches(repository)
88+
val repoRowSpan = branches.size.coerceAtLeast(1).plus(1).toString()
6189
tr {
6290
td {
63-
rowSpan = branches.size.coerceAtLeast(1).plus(1).toString()
91+
rowSpan = repoRowSpan
6492
+repository.id
6593
}
94+
td {
95+
rowSpan = repoRowSpan
96+
buildDeleteRepositoryForm(repository.id)
97+
}
6698
}
6799
if (branches.isEmpty()) {
68100
tr {
@@ -80,13 +112,13 @@ class RepositoryOverview(private val repoManager: IRepositoriesManager) {
80112
}
81113
}
82114
td {
83-
buildHistoryLink(branch.repositoryId.id, branch.branchName)
115+
buildHistoryLink(repository.id, branch.branchName)
84116
}
85117
td {
86-
buildExploreLatestLink(branch.repositoryId.id, branch.branchName)
118+
buildExploreLatestLink(repository.id, branch.branchName)
87119
}
88120
td {
89-
buildDeleteForm(branch.repositoryId.id)
121+
buildDeleteBranchButton(repository.id, branch.branchName)
90122
}
91123
}
92124
}
@@ -98,24 +130,31 @@ class RepositoryOverview(private val repoManager: IRepositoriesManager) {
98130
}
99131
}
100132

101-
fun FlowOrInteractiveOrPhrasingContent.buildHistoryLink(repositoryId: String, branchName: String) {
133+
internal fun FlowOrInteractiveOrPhrasingContent.buildHistoryLink(repositoryId: String, branchName: String) {
102134
a("../history/${repositoryId.encodeURLPathPart()}/${branchName.encodeURLPathPart()}/") {
103135
+"Show History"
104136
}
105137
}
106138

107-
fun FlowOrInteractiveOrPhrasingContent.buildExploreLatestLink(repositoryId: String, branchName: String) {
139+
internal fun FlowOrInteractiveOrPhrasingContent.buildExploreLatestLink(repositoryId: String, branchName: String) {
108140
a("../content/repositories/${repositoryId.encodeURLPathPart()}/branches/${branchName.encodeURLPathPart()}/latest/") {
109141
+"Explore Latest Version"
110142
}
111143
}
112144

113-
fun FlowContent.buildDeleteForm(repositoryId: String) {
114-
form {
115-
postButton {
116-
name = "delete"
117-
formAction = "../v2/repositories/${repositoryId.encodeURLPathPart()}/delete"
118-
+"Delete Repository"
119-
}
145+
internal fun FlowContent.buildDeleteRepositoryForm(repositoryId: String) {
146+
button {
147+
name = "delete"
148+
formAction = "../v2/repositories/${repositoryId.encodeURLPathPart()}/delete"
149+
onClick = "return removeRepository('${repositoryId.encodeURLPathPart()}')"
150+
+"Delete Repository"
151+
}
152+
}
153+
154+
internal fun FlowContent.buildDeleteBranchButton(repositoryId: String, branchName: String) {
155+
if (branchName == RepositoryId.DEFAULT_BRANCH) return
156+
button {
157+
onClick = "return removeBranch('${repositoryId.encodeURLPathPart()}', '${branchName.encodeURLPathPart()}')"
158+
+"Delete Branch"
120159
}
121160
}

model-server/src/test/kotlin/org/modelix/model/server/handlers/RepositoriesManagerTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ class RepositoriesManagerTest {
2626
store.close()
2727
}
2828

29+
@Test
30+
fun `deleting default branch works`() = runTest {
31+
val repoId = RepositoryId("branch-removal")
32+
initRepository(repoId)
33+
repoManager.removeBranches(repoId, setOf("master"))
34+
val branches = repoManager.getBranches(repoId)
35+
36+
assertTrue { branches.none { it.branchName == "master" } }
37+
}
38+
2939
@Test
3040
fun `repository data is removed when removing repository`() = runTest {
3141
val repoId = RepositoryId("abc")

model-server/src/test/kotlin/org/modelix/model/server/handlers/ui/RepositoryOverviewTest.kt

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,29 +18,53 @@ package org.modelix.model.server.handlers.ui
1818

1919
import kotlinx.html.span
2020
import kotlinx.html.stream.createHTML
21+
import org.jsoup.Jsoup
2122
import kotlin.test.Test
2223
import kotlin.test.assertEquals
2324

2425
class RepositoryOverviewTest {
2526

2627
@Test
27-
fun testSlashesInPathSegmentsFromRepositoryIdAndBranchId() {
28+
fun `history link is encoded properly`() {
2829
val html = createHTML(prettyPrint = false).span {
2930
buildHistoryLink("repository/v1", "branch/v2")
31+
}
32+
val document = Jsoup.parse(html)
33+
34+
val href = document.getElementsByTag("a").first()?.attribute("href")?.value
35+
assertEquals("../history/repository%2Fv1/branch%2Fv2/", href)
36+
}
37+
38+
@Test
39+
fun `explore latest link is encoded properly`() {
40+
val html = createHTML(prettyPrint = false).span {
3041
buildExploreLatestLink("repository/v1", "branch/v2")
31-
buildDeleteForm("repository/v1")
3242
}
33-
assertEquals(
34-
"""
35-
<span>
36-
<a href="../history/repository%2Fv1/branch%2Fv2/">Show History</a>
37-
<a href="../content/repositories/repository%2Fv1/branches/branch%2Fv2/latest/">Explore Latest Version</a>
38-
<form>
39-
<button formmethod="post" name="delete" formaction="../v2/repositories/repository%2Fv1/delete">Delete Repository</button>
40-
</form>
41-
</span>
42-
""".lines().joinToString("") { it.trim() },
43-
html,
44-
)
43+
val document = Jsoup.parse(html)
44+
45+
val href = document.getElementsByTag("a").first()?.attribute("href")?.value
46+
assertEquals("../content/repositories/repository%2Fv1/branches/branch%2Fv2/latest/", href)
47+
}
48+
49+
@Test
50+
fun `delete repository button parameters encoded properly`() {
51+
val html = createHTML(prettyPrint = false).span {
52+
buildDeleteRepositoryForm("repository/v1")
53+
}
54+
55+
val document = Jsoup.parse(html)
56+
val onClick = document.getElementsByTag("button").first()?.attribute("onclick")?.value
57+
assertEquals("return removeRepository('repository%2Fv1')", onClick)
58+
}
59+
60+
@Test
61+
fun `delete branch button parameters are encoded properly`() {
62+
val html = createHTML(prettyPrint = false).span {
63+
buildDeleteBranchButton("repository/v1", "branch/v2")
64+
}
65+
66+
val document = Jsoup.parse(html)
67+
val onClick = document.getElementsByTag("button").first()?.attribute("onclick")?.value
68+
assertEquals("return removeBranch('repository%2Fv1', 'branch%2Fv2')", onClick)
4569
}
4670
}

0 commit comments

Comments
 (0)