Skip to content

Commit 66be9e0

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

File tree

6 files changed

+147
-56
lines changed

6 files changed

+147
-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: 48 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
/**
@@ -142,55 +144,78 @@ class DashboardPageLayout @Inject constructor(
142144
}
143145
}
144146

147+
fun buildHtmlResponseBody(block: TagConsumer<*>.() -> Unit): ResponseBody = object : ResponseBody {
148+
override fun writeTo(sink: BufferedSink) {
149+
sink.writeUtf8(build(block))
150+
}
151+
}
152+
145153
private fun buildMenuSections(
146154
currentPath: String,
147155
): List<MenuSection> {
148156
return transacter.transaction { session ->
149-
val runningBackfills = queryFactory.newQuery<BackfillRunQuery>()
157+
val runningBackfillsForCaller = queryFactory.newQuery<BackfillRunQuery>()
150158
.createdByUser(callerProvider.get()!!.user!!)
151159
.state(BackfillState.RUNNING)
152160
.orderByUpdatedAtDesc()
153161
.list(session)
154162

155163
// TODO get services from REgistry for user || group backfills by service
164+
val services = runningBackfillsForCaller.groupBy { it.service }
156165

157166
listOf(
158167
MenuSection(
159168
title = "Backfila",
160169
links = listOf(
170+
Link(
171+
label = "Home",
172+
href = "/",
173+
isSelected = currentPath == "/",
174+
),
161175
Link(
162176
label = "Services",
163177
href = "/services/",
164-
isSelected = currentPath.startsWith("/services/"),
178+
isSelected = currentPath == "/services/",
165179
),
166180
Link(
167181
label = "Backfills",
168182
href = "/backfills/",
169-
isSelected = currentPath.startsWith("/backfills/"),
183+
isSelected = currentPath == "/backfills/",
170184
),
171185
),
172186
),
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-
),
187+
) + if (services.isNotEmpty()) {
188+
listOf(
189+
MenuSection(
190+
title = "Your Services",
191+
links = services.map { (service, backfills) ->
192+
val variant = if (service.variant == "default") "" else service.variant
193+
Link(
194+
label = service.registry_name,
195+
href = "/services/${service.registry_name}/$variant",
196+
isSelected = currentPath.startsWith("/services/${service.registry_name}/$variant"),
197+
)
198+
},
181199
),
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-
)
200+
)
201+
} else {
202+
listOf()
203+
} + if (runningBackfillsForCaller.isNotEmpty()) {
204+
listOf(
205+
MenuSection(
206+
title = "Your Backfills",
207+
links = runningBackfillsForCaller.map { backfill ->
208+
Link(
209+
label = backfill.service.registry_name + " #" + backfill.id,
210+
href = "/backfills/${backfill.id}",
211+
isSelected = currentPath.startsWith("/backfills/${backfill.id}"),
212+
)
213+
},
214+
),
215+
)
216+
} else {
217+
listOf()
218+
}
194219
}
195220
}
196221

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 kotlinx.html.dd
8+
import javax.inject.Inject
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 {
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 Subscribers""" }
52+
this@dl.dd("mt-1 text-3xl font-semibold tracking-tight text-gray-900") { +"""71,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. Open 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. Click Rate""" }
60+
this@dl.dd("mt-1 text-3xl font-semibold tracking-tight text-gray-900") { +"""24.57%""" }
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)