Skip to content

Commit e8512ea

Browse files
committed
Add index, fix links
1 parent 726af0c commit e8512ea

File tree

6 files changed

+148
-56
lines changed

6 files changed

+148
-56
lines changed

service/src/main/kotlin/app/cash/backfila/ui/UiModule.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package app.cash.backfila.ui
33
import app.cash.backfila.ui.actions.BackfillShowButtonHandlerAction
44
import app.cash.backfila.ui.actions.ServiceAutocompleteAction
55
import app.cash.backfila.ui.pages.BackfillShowAction
6+
import app.cash.backfila.ui.pages.IndexAction
67
import app.cash.backfila.ui.pages.ServiceIndexAction
78
import app.cash.backfila.ui.pages.ServiceShowAction
89
import misk.inject.KAbstractModule
@@ -11,6 +12,7 @@ import misk.web.WebActionModule
1112
class UiModule : KAbstractModule() {
1213
override fun configure() {
1314
// Pages
15+
install(WebActionModule.create<IndexAction>())
1416
install(WebActionModule.create<ServiceShowAction>())
1517
install(WebActionModule.create<ServiceIndexAction>())
1618
install(WebActionModule.create<BackfillShowAction>())

service/src/main/kotlin/app/cash/backfila/ui/components/DashboardPageLayout.kt

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ import misk.tailwind.Link
2020
import misk.tailwind.pages.MenuSection
2121
import misk.tailwind.pages.Navbar
2222
import misk.web.HttpCall
23+
import misk.web.ResponseBody
2324
import misk.web.dashboard.DashboardHomeUrl
2425
import misk.web.dashboard.DashboardNavbarItem
2526
import misk.web.dashboard.DashboardTab
2627
import misk.web.dashboard.HtmlLayout
28+
import okio.BufferedSink
2729
import wisp.deployment.Deployment
2830

2931
/**
@@ -113,6 +115,7 @@ class DashboardPageLayout @Inject constructor(
113115
menuSections = buildMenuSections(
114116
currentPath = path,
115117
),
118+
sortedMenuLinks = false,
116119
) {
117120
div("py-10") {
118121
main {
@@ -142,55 +145,78 @@ class DashboardPageLayout @Inject constructor(
142145
}
143146
}
144147

148+
fun buildHtmlResponseBody(block: TagConsumer<*>.() -> Unit): ResponseBody = object : ResponseBody {
149+
override fun writeTo(sink: BufferedSink) {
150+
sink.writeUtf8(build(block))
151+
}
152+
}
153+
145154
private fun buildMenuSections(
146155
currentPath: String,
147156
): List<MenuSection> {
148157
return transacter.transaction { session ->
149-
val runningBackfills = queryFactory.newQuery<BackfillRunQuery>()
158+
val runningBackfillsForCaller = queryFactory.newQuery<BackfillRunQuery>()
150159
.createdByUser(callerProvider.get()!!.user!!)
151160
.state(BackfillState.RUNNING)
152161
.orderByUpdatedAtDesc()
153162
.list(session)
154163

155164
// TODO get services from REgistry for user || group backfills by service
165+
val services = runningBackfillsForCaller.groupBy { it.service }
156166

157167
listOf(
158168
MenuSection(
159169
title = "Backfila",
160170
links = listOf(
171+
Link(
172+
label = "Home",
173+
href = "/",
174+
isSelected = currentPath == "/",
175+
),
161176
Link(
162177
label = "Services",
163178
href = "/services/",
164-
isSelected = currentPath.startsWith("/services/"),
179+
isSelected = currentPath == "/services/",
165180
),
166181
Link(
167182
label = "Backfills",
168183
href = "/backfills/",
169-
isSelected = currentPath.startsWith("/backfills/"),
184+
isSelected = currentPath == "/backfills/",
170185
),
171186
),
172187
),
173-
MenuSection(
174-
title = "Your Services",
175-
links = listOf(
176-
Link(
177-
label = "Fine Dining",
178-
href = "/services/?q=FineDining",
179-
isSelected = currentPath.startsWith("/services/?q=FindDining"),
180-
),
188+
) + if (services.isNotEmpty()) {
189+
listOf(
190+
MenuSection(
191+
title = "Your Services",
192+
links = services.map { (service, backfills) ->
193+
val variant = if (service.variant == "default") "" else service.variant
194+
Link(
195+
label = service.registry_name,
196+
href = "/services/${service.registry_name}/$variant",
197+
isSelected = currentPath.startsWith("/services/${service.registry_name}/$variant"),
198+
)
199+
},
181200
),
182-
),
183-
MenuSection(
184-
title = "Your Backfills",
185-
links = runningBackfills.map { backfill ->
186-
Link(
187-
label = backfill.service.registry_name + " #" + backfill.id,
188-
href = "/backfills/${backfill.id}",
189-
isSelected = currentPath.startsWith("/backfills/${backfill.id}"),
190-
)
191-
},
192-
),
193-
)
201+
)
202+
} else {
203+
listOf()
204+
} + if (runningBackfillsForCaller.isNotEmpty()) {
205+
listOf(
206+
MenuSection(
207+
title = "Your Backfills",
208+
links = runningBackfillsForCaller.map { backfill ->
209+
Link(
210+
label = backfill.service.registry_name + " #" + backfill.id,
211+
href = "/backfills/${backfill.id}",
212+
isSelected = currentPath.startsWith("/backfills/${backfill.id}"),
213+
)
214+
},
215+
),
216+
)
217+
} else {
218+
listOf()
219+
}
194220
}
195221
}
196222

service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillShowAction.kt

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import app.cash.backfila.service.persistence.BackfillState
77
import app.cash.backfila.ui.actions.BackfillShowButtonHandlerAction
88
import app.cash.backfila.ui.components.AlertError
99
import app.cash.backfila.ui.components.AlertSupport
10-
import app.cash.backfila.ui.components.DashboardLayout
10+
import app.cash.backfila.ui.components.DashboardPageLayout
1111
import app.cash.backfila.ui.components.PageTitle
1212
import app.cash.backfila.ui.components.ProgressBar
1313
import javax.inject.Inject
@@ -30,7 +30,6 @@ import kotlinx.html.td
3030
import kotlinx.html.th
3131
import kotlinx.html.thead
3232
import kotlinx.html.tr
33-
import misk.hotwire.buildHtmlResponseBody
3433
import misk.security.authz.Authenticated
3534
import misk.tailwind.Link
3635
import misk.web.Get
@@ -45,6 +44,7 @@ import misk.web.mediatype.MediaTypes
4544
class BackfillShowAction @Inject constructor(
4645
private val config: BackfilaConfig,
4746
private val getBackfillStatusAction: GetBackfillStatusAction,
47+
private val dashboardPageLayout: DashboardPageLayout,
4848
) : WebAction {
4949
@Get(PATH)
5050
@ResponseContentType(MediaTypes.TEXT_HTML)
@@ -54,25 +54,20 @@ class BackfillShowAction @Inject constructor(
5454
): Response<ResponseBody> {
5555
if (id.toLongOrNull() == null) {
5656
return Response(
57-
buildHtmlResponseBody {
58-
DashboardLayout(
59-
title = "Backfill $id | Backfila",
60-
path = PATH,
61-
) {
57+
dashboardPageLayout.newBuilder()
58+
.title("Backfill $id | Backfila")
59+
.buildHtmlResponseBody {
6260
PageTitle("Backfill", id)
6361
AlertError("Invalid Backfill Id [id=$id], must be of type Long.")
6462
AlertSupport(config.support_button_label, config.support_button_url)
65-
}
66-
},
63+
},
6764
)
6865
}
6966
val backfill = getBackfillStatusAction.status(id.toLong())
7067

71-
val htmlResponseBody = buildHtmlResponseBody {
72-
DashboardLayout(
73-
title = "Backfill $id | Backfila",
74-
path = PATH,
75-
) {
68+
val htmlResponseBody = dashboardPageLayout.newBuilder()
69+
.title("Backfill $id | Backfila")
70+
.buildHtmlResponseBody {
7671
PageTitle("Backfill", id)
7772

7873
// TODO add Header buttons / metrics
@@ -306,7 +301,6 @@ class BackfillShowAction @Inject constructor(
306301

307302
AlertSupport(config.support_button_label, config.support_button_url)
308303
}
309-
}
310304

311305
return Response(htmlResponseBody)
312306
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package app.cash.backfila.ui.pages
2+
3+
import app.cash.backfila.service.BackfilaConfig
4+
import app.cash.backfila.ui.actions.ServiceAutocompleteAction
5+
import app.cash.backfila.ui.components.AlertSupport
6+
import app.cash.backfila.ui.components.DashboardPageLayout
7+
import javax.inject.Inject
8+
import kotlinx.html.dd
9+
import kotlinx.html.div
10+
import kotlinx.html.dl
11+
import kotlinx.html.dt
12+
import kotlinx.html.h1
13+
import kotlinx.html.h3
14+
import kotlinx.html.span
15+
import misk.MiskCaller
16+
import misk.scope.ActionScoped
17+
import misk.security.authz.Authenticated
18+
import misk.web.Get
19+
import misk.web.QueryParam
20+
import misk.web.ResponseContentType
21+
import misk.web.actions.WebAction
22+
import misk.web.mediatype.MediaTypes
23+
24+
class IndexAction @Inject constructor(
25+
private val config: BackfilaConfig,
26+
private val serviceAutocompleteAction: ServiceAutocompleteAction,
27+
private val dashboardPageLayout: DashboardPageLayout,
28+
private val callerProvider: ActionScoped<MiskCaller?>,
29+
) : WebAction {
30+
@Get(PATH)
31+
@ResponseContentType(MediaTypes.TEXT_HTML)
32+
@Authenticated(capabilities = ["users"])
33+
fun get(
34+
@QueryParam sc: String?,
35+
): String = dashboardPageLayout
36+
.newBuilder()
37+
.title("Backfila Home")
38+
.build {
39+
h1("text-2xl") {
40+
+"""Welcome to Backfila, """
41+
span("font-bold font-mono") { +"""${callerProvider.get()?.user}""" }
42+
+"""!"""
43+
}
44+
45+
// Stats
46+
47+
div("py-10") {
48+
h3("text-base font-semibold text-gray-900") { +"""Last 30 days""" }
49+
dl("mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3") {
50+
div("overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6") {
51+
this@dl.dt("truncate text-sm font-medium text-gray-500") { +"""Total Records""" }
52+
this@dl.dd("mt-1 text-3xl font-semibold tracking-tight text-gray-900") { +"""1,271,897""" }
53+
}
54+
div("overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6") {
55+
this@dl.dt("truncate text-sm font-medium text-gray-500") { +"""Avg. Progress Rate""" }
56+
this@dl.dd("mt-1 text-3xl font-semibold tracking-tight text-gray-900") { +"""58.16%""" }
57+
}
58+
div("overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6") {
59+
this@dl.dt("truncate text-sm font-medium text-gray-500") { +"""Avg. Records Per Second""" }
60+
this@dl.dd("mt-1 text-3xl font-semibold tracking-tight text-gray-900") { +"""2457""" }
61+
}
62+
}
63+
}
64+
65+
// Running Backfills
66+
67+
AlertSupport(config.support_button_label, config.support_button_url)
68+
}
69+
70+
companion object {
71+
const val PATH = "/"
72+
}
73+
}

service/src/main/kotlin/app/cash/backfila/ui/pages/ServiceIndexAction.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,6 @@ class ServiceIndexAction @Inject constructor(
129129
}
130130

131131
companion object {
132-
const val PATH = "/"
132+
const val PATH = "/services/"
133133
}
134134
}

service/src/main/kotlin/app/cash/backfila/ui/pages/ServiceShowAction.kt

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ import app.cash.backfila.dashboard.GetBackfillRunsAction
44
import app.cash.backfila.service.BackfilaConfig
55
import app.cash.backfila.ui.components.AlertSupport
66
import app.cash.backfila.ui.components.BackfillsTable
7-
import app.cash.backfila.ui.components.DashboardLayout
7+
import app.cash.backfila.ui.components.DashboardPageLayout
88
import app.cash.backfila.ui.components.PageTitle
99
import java.net.HttpURLConnection
1010
import javax.inject.Inject
1111
import javax.inject.Singleton
1212
import kotlinx.html.role
1313
import kotlinx.html.ul
14-
import misk.hotwire.buildHtmlResponseBody
1514
import misk.security.authz.Authenticated
1615
import misk.web.Get
16+
import misk.web.PathParam
1717
import misk.web.QueryParam
1818
import misk.web.Response
1919
import misk.web.ResponseBody
@@ -27,34 +27,32 @@ import okhttp3.Headers
2727
class ServiceShowAction @Inject constructor(
2828
private val config: BackfilaConfig,
2929
private val getBackfillRunsAction: GetBackfillRunsAction,
30+
private val dashboardPageLayout: DashboardPageLayout,
3031
) : WebAction {
3132
@Get(PATH)
3233
@ResponseContentType(MediaTypes.TEXT_HTML)
3334
@Authenticated(capabilities = ["users"])
3435
fun get(
35-
@QueryParam s: String?,
36+
@PathParam service: String?,
37+
@PathParam variantOrBlank: String = "",
3638
@QueryParam("experimental") experimental: Boolean? = false,
3739
): Response<ResponseBody> {
38-
if (s.isNullOrBlank()) {
40+
if (service.isNullOrBlank()) {
3941
return Response(
4042
body = "go to /".toResponseBody(),
4143
statusCode = HttpURLConnection.HTTP_MOVED_TEMP,
4244
headers = Headers.headersOf("Location", "/"),
4345
)
4446
}
47+
val variant = variantOrBlank.ifBlank { "default" }
4548

46-
val serviceName = s.split("/").first()
47-
val variant = s.split("/").last()
49+
val backfillRuns = getBackfillRunsAction.backfillRuns(service, variant)
4850

49-
val backfillRuns = getBackfillRunsAction.backfillRuns(serviceName, variant)
50-
51-
val htmlResponseBody = buildHtmlResponseBody {
52-
// TODO show default if other variants and probably link to a switcher
53-
val label = if (variant == "default") serviceName else "$serviceName ($variant)"
54-
DashboardLayout(
55-
title = "$label | Backfila",
56-
path = PATH,
57-
) {
51+
// TODO show default if other variants and probably link to a switcher
52+
val label = if (variant == "default") service else "$service ($variant)"
53+
val htmlResponseBody = dashboardPageLayout.newBuilder()
54+
.title("$label | Backfila")
55+
.buildHtmlResponseBody {
5856
PageTitle("Service", label)
5957

6058
// TODO Add completed table
@@ -68,12 +66,11 @@ class ServiceShowAction @Inject constructor(
6866

6967
AlertSupport(config.support_button_label, config.support_button_url)
7068
}
71-
}
7269

7370
return Response(htmlResponseBody)
7471
}
7572

7673
companion object {
77-
const val PATH = "/services/"
74+
const val PATH = "/services/{service}/{variantOrBlank}"
7875
}
7976
}

0 commit comments

Comments
 (0)