From 700ac3345eaea5c650b441c90ab0495510e25dad Mon Sep 17 00:00:00 2001 From: "Andrew (Paradi) Alexander" Date: Mon, 27 Jan 2025 12:48:55 -0500 Subject: [PATCH] Update Backfill Show to use full width tables (#411) Update Backfill Show to use full width tables ![Screenshot 2025-01-23 at 16 11 54](https://github.com/user-attachments/assets/dfe9c559-8965-4696-9168-8133252c6560) Add custom parameters and handling of odd number of rows Screenshot 2025-01-27 at 11 52 43 --- .../ui/actions/BackfillCreateHandlerAction.kt | 2 +- .../BackfillShowButtonHandlerAction.kt | 1 + .../backfila/ui/pages/BackfillCreateAction.kt | 4 +- .../pages/BackfillCreateServiceIndexAction.kt | 4 +- .../backfila/ui/pages/BackfillIndexAction.kt | 1 - .../backfila/ui/pages/BackfillShowAction.kt | 473 ++++++++++-------- 6 files changed, 263 insertions(+), 222 deletions(-) diff --git a/service/src/main/kotlin/app/cash/backfila/ui/actions/BackfillCreateHandlerAction.kt b/service/src/main/kotlin/app/cash/backfila/ui/actions/BackfillCreateHandlerAction.kt index a7b3ac49..46664104 100644 --- a/service/src/main/kotlin/app/cash/backfila/ui/actions/BackfillCreateHandlerAction.kt +++ b/service/src/main/kotlin/app/cash/backfila/ui/actions/BackfillCreateHandlerAction.kt @@ -49,7 +49,7 @@ class BackfillCreateHandlerAction @Inject constructor( formFields[BackfillCreateField.EXTRA_SLEEP_MS.fieldId]?.ifNotBlank { createRequestBuilder.extra_sleep_ms(it.toLongOrNull()) } formFields[BackfillCreateField.BACKOFF_SCHEDULE.fieldId]?.ifNotBlank { createRequestBuilder.backoff_schedule(it) } val customParameters = formFields.filter { it.key.startsWith(BackfillCreateField.CUSTOM_PARAMETER_PREFIX.fieldId) } - .mapValues { it.value?.encodeUtf8() } + .map { it.key.removePrefix(BackfillCreateField.CUSTOM_PARAMETER_PREFIX.fieldId) to it.value?.encodeUtf8() }.toMap() if (customParameters.isNotEmpty()) { createRequestBuilder.parameter_map(customParameters) } diff --git a/service/src/main/kotlin/app/cash/backfila/ui/actions/BackfillShowButtonHandlerAction.kt b/service/src/main/kotlin/app/cash/backfila/ui/actions/BackfillShowButtonHandlerAction.kt index d7a07ab2..390c0fba 100644 --- a/service/src/main/kotlin/app/cash/backfila/ui/actions/BackfillShowButtonHandlerAction.kt +++ b/service/src/main/kotlin/app/cash/backfila/ui/actions/BackfillShowButtonHandlerAction.kt @@ -83,5 +83,6 @@ class BackfillShowButtonHandlerAction @Inject constructor( companion object { const val PATH = "/api/backfill/{id}/update" + fun path(id: String) = PATH.replace("{id}", id) } } diff --git a/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillCreateAction.kt b/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillCreateAction.kt index dcd73b83..048453c2 100644 --- a/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillCreateAction.kt +++ b/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillCreateAction.kt @@ -8,6 +8,8 @@ import app.cash.backfila.ui.actions.ServiceDataHelper import app.cash.backfila.ui.components.AlertError import app.cash.backfila.ui.components.DashboardPageLayout import app.cash.backfila.ui.components.PageTitle +import javax.inject.Inject +import javax.inject.Singleton import kotlinx.html.ButtonType import kotlinx.html.InputType import kotlinx.html.button @@ -29,8 +31,6 @@ import misk.web.ResponseBody import misk.web.ResponseContentType import misk.web.actions.WebAction import misk.web.mediatype.MediaTypes -import javax.inject.Inject -import javax.inject.Singleton @Singleton class BackfillCreateAction @Inject constructor( diff --git a/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillCreateServiceIndexAction.kt b/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillCreateServiceIndexAction.kt index 38472d09..6f107568 100644 --- a/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillCreateServiceIndexAction.kt +++ b/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillCreateServiceIndexAction.kt @@ -44,7 +44,7 @@ class BackfillCreateServiceIndexAction @Inject constructor( val newPath = BackfillCreateAction.path( service = service, variantOrBackfillNameOrId = variantOrBlank.orEmpty(), - backfillNameOrId = "" + backfillNameOrId = "", ) return Response( body = "go to $newPath".toResponseBody(), @@ -95,7 +95,7 @@ class BackfillCreateServiceIndexAction @Inject constructor( href = BackfillCreateAction.path( service = service, variantOrBackfillNameOrId = variantOrBackfillNameOrId, - backfillNameOrId = if (variantOrBackfillNameOrId == it.name) "" else it.name + backfillNameOrId = if (variantOrBackfillNameOrId == it.name) "" else it.name, ) this@ul.li("registration col-span-1 divide-y divide-gray-200 rounded-lg bg-white shadow") { diff --git a/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillIndexAction.kt b/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillIndexAction.kt index 6acda828..0c0c8149 100644 --- a/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillIndexAction.kt +++ b/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillIndexAction.kt @@ -13,7 +13,6 @@ import kotlinx.html.button import kotlinx.html.div import kotlinx.html.h3 import kotlinx.html.li -import kotlinx.html.p import kotlinx.html.role import kotlinx.html.span import kotlinx.html.ul diff --git a/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillShowAction.kt b/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillShowAction.kt index 99c43514..d5bff1b3 100644 --- a/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillShowAction.kt +++ b/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillShowAction.kt @@ -8,10 +8,12 @@ import app.cash.backfila.ui.components.AutoReload import app.cash.backfila.ui.components.DashboardPageLayout import app.cash.backfila.ui.components.PageTitle import app.cash.backfila.ui.components.ProgressBar +import app.cash.backfila.ui.pages.BackfillCreateAction.BackfillCreateField.CUSTOM_PARAMETER_PREFIX import javax.inject.Inject import javax.inject.Singleton import kotlinx.html.ButtonType import kotlinx.html.InputType +import kotlinx.html.TagConsumer import kotlinx.html.ThScope import kotlinx.html.a import kotlinx.html.button @@ -54,6 +56,14 @@ class BackfillShowAction @Inject constructor( val label = if (backfill.variant == "default") backfill.service_name else "${backfill.service_name} (${backfill.variant})" + val configurationRows = backfill.toConfigurationRows(id) + val leftColumnConfigurationRows = configurationRows.take( + configurationRows.size / 2 + + // Handles odd length of rows, chooses the odd row to be in the left column + configurationRows.size % 2, + ) + val rightColumnConfigurationRows = configurationRows.takeLast(configurationRows.size / 2) + val htmlResponseBody = dashboardPageLayout.newBuilder() .title("Backfill $id | Backfila") .breadcrumbLinks( @@ -62,8 +72,8 @@ class BackfillShowAction @Inject constructor( label, ServiceShowAction.path( service = backfill.service_name, - variantOrBlank = if (backfill.variant != "default") backfill.variant else "" - ) + variantOrBlank = if (backfill.variant != "default") backfill.variant else "", + ), ), Link("Backfill #$id", path(id)), ) @@ -74,7 +84,7 @@ class BackfillShowAction @Inject constructor( href = BackfillCreateAction.path( service = backfill.service_name, variantOrBackfillNameOrId = if (backfill.variant != "default") backfill.variant else id, - backfillNameOrId = if (backfill.variant != "default") id else "" + backfillNameOrId = if (backfill.variant != "default") id else "", ) button(classes = "rounded-full bg-indigo-600 px-3 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600") { @@ -84,229 +94,134 @@ class BackfillShowAction @Inject constructor( } } - div("mx-auto max-w-7xl px-4 py-16 sm:px-6 lg:px-8") { - div("mx-auto grid max-w-2xl grid-cols-1 grid-rows-1 items-start gap-x-8 gap-y-8 lg:mx-0 lg:max-w-none lg:grid-cols-3") { -// +"""""" - div("lg:col-start-3 lg:row-end-1") { - div("rounded-lg bg-gray-50 shadow-sm ring-1 ring-gray-900/5 p-6") { - h2("text-base font-semibold leading-6 text-gray-900") { +"""Configuration""" } - dl("divide-y divide-gray-100") { - backfill.toRows(id).map { - div("px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0") { - attributes["data-controller"] = "toggle" - - this@dl.dt("text-sm font-medium leading-6 text-gray-900") { +it.label } - this@dl.dd("mt-1 flex text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0") { - span("flex-grow") { - attributes["data-toggle-target"] = "toggleable" - attributes["data-css-class"] = "hidden" - - +it.description - } - it.button?.let { button -> - if (button.label == UPDATE_BUTTON_LABEL) { - // Initial Update Button to toggle showing form - span("ml-4 flex-shrink-0") { - attributes["data-toggle-target"] = "toggleable" - attributes["data-css-class"] = "hidden" - - button( - classes = "mt-1 rounded-md font-medium text-indigo-600 hover:text-indigo-500", - ) { - attributes["data-action"] = "toggle#toggle" - type = ButtonType.button - +button.label - } - } - - // Have initial click reveal the update form with editable input - form(classes = "flex-grow hidden") { - attributes["data-toggle-target"] = "toggleable" - attributes["data-css-class"] = "hidden" - - action = BackfillShowButtonHandlerAction.PATH.replace("{id}", id) - - it.updateFieldId?.let { updateFieldId -> - input { - type = InputType.hidden - name = "field_id" - value = updateFieldId - } - - div { - div("flex rounded-md shadow-sm") { - div("relative flex flex-grow items-stretch focus-within:z-10") { - input(classes = "block w-full rounded-none rounded-l-md border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6") { - name = "field_value" - value = it.description - } - } - button(classes = "relative -ml-px inline-flex items-center gap-x-1.5 rounded-r-md px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50") { - type = ButtonType.submit - +"""Update""" - } - } - } - } - } - - // Cancel Button to hide form - span("hidden ml-4 flex-shrink-0") { - attributes["data-toggle-target"] = "toggleable" - attributes["data-css-class"] = "hidden" - - button( - classes = "mt-1 rounded-md font-medium text-indigo-600 hover:text-indigo-500", - ) { - attributes["data-action"] = "toggle#toggle" - type = ButtonType.button - +"Cancel" - } - } - } else { - span("ml-4 flex-shrink-0") { - // Button when clicked updates without additional form - form { - action = BackfillShowButtonHandlerAction.PATH.replace("{id}", id) - - it.updateFieldId?.let { - input { - type = InputType.hidden - name = "field_id" - value = it - } - - input { - type = InputType.hidden - name = "field_value" - value = button.href - } - } + Card { + div("mx-auto grid max-w-2xl grid-cols-1 grid-rows-1 items-start gap-x-24 gap-y-8 lg:mx-0 lg:max-w-none lg:grid-cols-2") { + // """ + div("") { + h2("text-base font-semibold leading-6 text-gray-900") { +"""Configuration""" } + dl("divide-y divide-gray-100") { + leftColumnConfigurationRows.map { + ConfigurationRows(id, it) + } + } + } - button( - classes = "rounded-md font-medium text-indigo-600 hover:text-indigo-500", - ) { - type = ButtonType.submit - +button.label - } - } - } - } - } - } - } - } + // """ + div("divide-x divide-gray-100") { + dl("divide-y divide-gray-100") { + rightColumnConfigurationRows.map { + ConfigurationRows(id, it) } } } + } + } -// +"""""" - // TODO shrink padding so it shows full width - div("-mx-4 px-4 py-8 overflow-x-auto shadow-sm ring-1 ring-gray-900/5 sm:mx-0 sm:rounded-lg sm:px-8 sm:pb-14 lg:col-span-2 lg:row-span-2 lg:row-end-2 xl:px-16 xl:pb-20 xl:pt-16") { - // Partitions - h2("text-base font-semibold leading-6 text-gray-900") { +"""Partitions""" } - table("my-8 whitespace-nowrap text-left text-sm leading-6") { - thead("border-b border-gray-200 text-gray-900") { - tr { - th(classes = "px-0 py-3 font-semibold") { - scope = ThScope.col - +"""Name""" - } - th(classes = "hidden py-3 pl-8 pr-0 text-right font-semibold sm:table-cell") { - scope = ThScope.col - +"""State""" - } - th(classes = "hidden py-3 pl-8 pr-0 text-right font-semibold sm:table-cell") { - scope = ThScope.col - +"""Cursor""" - } - th(classes = "py-3 pl-8 pr-0 text-right font-semibold") { - scope = ThScope.col - +"""Range""" - } - th(classes = "py-3 pl-8 pr-0 text-right font-semibold") { - scope = ThScope.col - +"""Progress""" - } - th(classes = "py-3 pl-8 pr-0 text-right font-semibold") { - scope = ThScope.col - +"""Progress (%)""" - } - th(classes = "py-3 pl-8 pr-0 text-right font-semibold") { - scope = ThScope.col - +"""Rate""" - } - th(classes = "py-3 pl-8 pr-0 text-right font-semibold") { - scope = ThScope.col - +"""ETA""" - } - } + Card { + // Partitions + h2("text-base font-semibold leading-6 text-gray-900") { +"""Partitions""" } + table("my-8 whitespace-nowrap text-left text-sm leading-6") { + thead("border-b border-gray-200 text-gray-900") { + tr { + th(classes = "px-0 py-3 font-semibold") { + scope = ThScope.col + +"""Name""" } - tbody { - backfill.partitions.map { partition -> - tr("border-b border-gray-100") { - td("max-w-[24px] px-0 py-5 align-top") { - div("truncate font-medium text-gray-900") { +partition.name } - } - td("hidden py-5 pl-8 pr-0 text-right align-top text-gray-700 sm:table-cell") { +partition.state.name } - td("hidden py-5 pl-8 pr-0 text-right align-top tabular-nums text-gray-700 sm:table-cell") { - +(partition.pkey_cursor ?: "") - } - td("hidden py-5 pl-8 pr-0 text-right align-top text-gray-700 sm:table-cell") { +"""${partition.pkey_start} to ${partition.pkey_end}""" } - td("hidden py-5 pl-8 pr-0 text-right align-top text-gray-700 sm:table-cell") { +"""${partition.backfilled_matching_record_count} / ${partition.computed_matching_record_count}""" } - td("hidden py-5 pl-8 pr-0 text-right align-top tabular-nums text-gray-700 sm:table-cell") { - ProgressBar( - partition.backfilled_matching_record_count, - partition.computed_matching_record_count, - ) - } - td("hidden py-5 pl-8 pr-0 text-right align-top tabular-nums text-gray-700 sm:table-cell") { +"""${partition.matching_records_per_minute} #/m""" } - // TODO properly calculate the ETA until finished - td("py-5 pl-8 pr-0 text-right align-top tabular-nums text-gray-700") { +"""ETA TODO""" } - } + th(classes = "hidden py-3 pl-8 pr-0 text-right font-semibold sm:table-cell") { + scope = ThScope.col + +"""State""" + } + th(classes = "hidden py-3 pl-8 pr-0 text-right font-semibold sm:table-cell") { + scope = ThScope.col + +"""Cursor""" + } + th(classes = "py-3 pl-8 pr-0 text-right font-semibold") { + scope = ThScope.col + +"""Range""" + } + th(classes = "py-3 pl-8 pr-0 text-right font-semibold") { + scope = ThScope.col + +"""Progress""" + } + th(classes = "py-3 pl-8 pr-0 text-right font-semibold") { + scope = ThScope.col + +"""Progress (%)""" + } + th(classes = "py-3 pl-8 pr-0 text-right font-semibold") { + scope = ThScope.col + +"""Rate""" + } + th(classes = "py-3 pl-8 pr-0 text-right font-semibold") { + scope = ThScope.col + +"""ETA""" + } + } + } + tbody { + backfill.partitions.map { partition -> + tr("border-b border-gray-100") { + td("max-w-[24px] px-0 py-5 align-top") { + div("truncate font-medium text-gray-900") { +partition.name } + } + td("hidden py-5 pl-8 pr-0 text-right align-top text-gray-700 sm:table-cell") { +partition.state.name } + td("hidden py-5 pl-8 pr-0 text-right align-top tabular-nums text-gray-700 sm:table-cell") { + +(partition.pkey_cursor ?: "") } + td("hidden py-5 pl-8 pr-0 text-right align-top text-gray-700 sm:table-cell") { +"""${partition.pkey_start} to ${partition.pkey_end}""" } + td("hidden py-5 pl-8 pr-0 text-right align-top text-gray-700 sm:table-cell") { +"""${partition.backfilled_matching_record_count} / ${partition.computed_matching_record_count}""" } + td("hidden py-5 pl-8 pr-0 text-right align-top tabular-nums text-gray-700 sm:table-cell") { + ProgressBar( + partition.backfilled_matching_record_count, + partition.computed_matching_record_count, + ) + } + td("hidden py-5 pl-8 pr-0 text-right align-top tabular-nums text-gray-700 sm:table-cell") { +"""${partition.matching_records_per_minute} #/m""" } + // TODO properly calculate the ETA until finished + td("py-5 pl-8 pr-0 text-right align-top tabular-nums text-gray-700") { +"""ETA TODO""" } } } + } + } + } - // Logs - h2("text-base font-semibold leading-6 text-gray-900 pt-8") { +"""Logs""" } - table("my-8 text-left text-sm leading-6") { - thead("border-b border-gray-200 text-gray-900") { - tr { - th(classes = "px-0 py-3 font-semibold") { - scope = ThScope.col - +"""Time""" - } - th(classes = "hidden py-3 pl-8 pr-0 font-semibold sm:table-cell") { - scope = ThScope.col - +"""User""" - } - th(classes = "hidden py-3 pl-8 pr-0 font-semibold sm:table-cell") { - scope = ThScope.col - +"""Partition""" - } - th(classes = "py-3 pl-8 pr-0 font-semibold") { - scope = ThScope.col - +"""Event""" - } - th(classes = "py-3 pl-8 pr-0 font-semibold") { - scope = ThScope.col - +"""More Data""" - } - } + Card { + // Logs + h2("text-base font-semibold leading-6 text-gray-900") { +"""Logs""" } + table("my-8 text-left text-sm leading-6") { + thead("border-b border-gray-200 text-gray-900") { + tr { + th(classes = "px-0 py-3 font-semibold") { + scope = ThScope.col + +"""Time""" } - tbody { - backfill.event_logs.map { log -> - tr("border-b border-gray-100") { - td("hidden py-5 pl-8 pr-0 align-top text-wrap text-gray-700 sm:table-cell") { - +log.occurred_at.toString().replace("T", " ").dropLast(5) - } - td("hidden py-5 pl-8 pr-0 align-top text-gray-700 sm:table-cell") { log.user?.let { +it } } - td("hidden py-5 pl-8 pr-0 align-top text-gray-700 sm:table-cell") { log.partition_name?.let { +it } } - td("hidden py-5 pl-8 pr-0 align-top max-w-2 text-wrap text-gray-700 sm:table-cell") { +log.message } - td("hidden py-5 pl-8 pr-0 align-top max-w-2 text-wrap text-gray-700 sm:table-cell") { log.extra_data?.let { +it } } - } + th(classes = "hidden py-3 pl-8 pr-0 font-semibold sm:table-cell") { + scope = ThScope.col + +"""User""" + } + th(classes = "hidden py-3 pl-8 pr-0 font-semibold sm:table-cell") { + scope = ThScope.col + +"""Partition""" + } + th(classes = "py-3 pl-8 pr-0 font-semibold") { + scope = ThScope.col + +"""Event""" + } + th(classes = "py-3 pl-8 pr-0 font-semibold") { + scope = ThScope.col + +"""More Data""" + } + } + } + tbody { + backfill.event_logs.map { log -> + tr("border-b border-gray-100") { + td("hidden py-5 pl-8 pr-0 align-top text-wrap text-gray-700 sm:table-cell") { + +log.occurred_at.toString().replace("T", " ").dropLast(5) } + td("hidden py-5 pl-8 pr-0 align-top text-gray-700 sm:table-cell") { log.user?.let { +it } } + td("hidden py-5 pl-8 pr-0 align-top text-gray-700 sm:table-cell") { log.partition_name?.let { +it } } + td("hidden py-5 pl-8 pr-0 align-top max-w-2 text-wrap text-gray-700 sm:table-cell") { +log.message } + td("hidden py-5 pl-8 pr-0 align-top max-w-2 text-wrap text-gray-700 sm:table-cell") { log.extra_data?.let { +it } } } } } @@ -341,7 +256,7 @@ class BackfillShowAction @Inject constructor( } } - private fun GetBackfillStatusResponse.toRows(id: String) = listOf( + private fun GetBackfillStatusResponse.toConfigurationRows(id: String) = listOf( DescriptionListRow( label = "State", description = state.name, @@ -404,12 +319,138 @@ class BackfillShowAction @Inject constructor( DescriptionListRow( label = "Logs", description = "", + // TODO add link real URL button = Link( label = "View", href = "#", ), ), - ) + ) + if (parameters?.isNotEmpty() == true) { + listOf( + DescriptionListRow( + label = "Custom Parameters", + description = "", + ), + ) + + parameters.map { (key, value) -> + DescriptionListRow( + label = key.removePrefix(CUSTOM_PARAMETER_PREFIX.fieldId), + description = value, + ) + } + } else listOf() + + private fun TagConsumer<*>.Card(block: TagConsumer<*>.() -> Unit) { + div("-mx-4 mb-8 px-4 py-8 overflow-x-auto shadow-sm ring-1 ring-gray-900/5 sm:mx-0 sm:rounded-lg sm:px-8 lg:col-span-2 lg:row-span-2 lg:row-end-2") { + block() + } + } + + private fun TagConsumer<*>.ConfigurationRows(id: String, it: DescriptionListRow) { + div("px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0") { + attributes["data-controller"] = "toggle" + + dt("text-sm font-medium leading-6 text-gray-900") { +it.label } + dd("mt-1 flex text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0") { + span("flex-grow") { + attributes["data-toggle-target"] = "toggleable" + attributes["data-css-class"] = "hidden" + + +it.description + } + it.button?.let { button -> + if (button.label == UPDATE_BUTTON_LABEL) { + // Initial Update Button to toggle showing form + span("ml-4 flex-shrink-0") { + attributes["data-toggle-target"] = "toggleable" + attributes["data-css-class"] = "hidden" + + button( + classes = "mt-1 rounded-md font-medium text-indigo-600 hover:text-indigo-500", + ) { + attributes["data-action"] = "toggle#toggle" + type = ButtonType.button + +button.label + } + } + + // Have initial click reveal the update form with editable input + form(classes = "flex-grow hidden") { + attributes["data-toggle-target"] = "toggleable" + attributes["data-css-class"] = "hidden" + + action = BackfillShowButtonHandlerAction.path(id) + + it.updateFieldId?.let { updateFieldId -> + input { + type = InputType.hidden + name = "field_id" + value = updateFieldId + } + + div { + div("flex rounded-md shadow-sm") { + div("relative flex flex-grow items-stretch focus-within:z-10") { + input(classes = "block w-full rounded-none rounded-l-md border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6") { + name = "field_value" + value = it.description + } + } + button(classes = "relative -ml-px inline-flex items-center gap-x-1.5 rounded-r-md px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50") { + type = ButtonType.submit + +"""Update""" + } + } + } + } + } + + // Cancel Button to hide form + span("hidden ml-4 flex-shrink-0") { + attributes["data-toggle-target"] = "toggleable" + attributes["data-css-class"] = "hidden" + + button( + classes = "mt-1 rounded-md font-medium text-indigo-600 hover:text-indigo-500", + ) { + attributes["data-action"] = "toggle#toggle" + type = ButtonType.button + +"Cancel" + } + } + } else { + span("ml-4 flex-shrink-0") { + // Button when clicked updates without additional form + form { + action = BackfillShowButtonHandlerAction.path(id) + + it.updateFieldId?.let { + input { + type = InputType.hidden + name = "field_id" + value = it + } + + input { + type = InputType.hidden + name = "field_value" + value = button.href + } + } + + button( + classes = "rounded-md font-medium text-indigo-600 hover:text-indigo-500", + ) { + type = ButtonType.submit + +button.label + } + } + } + } + } + } + } + } companion object { private const val PATH = "/backfills/{id}"