diff --git a/service/src/main/kotlin/app/cash/backfila/dashboard/CancelBackfillAction.kt b/service/src/main/kotlin/app/cash/backfila/dashboard/CancelBackfillAction.kt index e5d95e506..726da21f7 100644 --- a/service/src/main/kotlin/app/cash/backfila/dashboard/CancelBackfillAction.kt +++ b/service/src/main/kotlin/app/cash/backfila/dashboard/CancelBackfillAction.kt @@ -37,8 +37,7 @@ class CancelBackfillAction @Inject constructor( transacter.transaction { session -> val backfillRun = session.load(Id(id)) - // Check if backfill can be cancelled - if (backfillRun.state == BackfillState.CANCELLED) { + if (backfillRun.state != BackfillState.PAUSED) { throw BadRequestException("Cannot cancel a ${backfillRun.state.name.lowercase()} backfill") } diff --git a/service/src/main/kotlin/app/cash/backfila/dashboard/GetBackfillRunsAction.kt b/service/src/main/kotlin/app/cash/backfila/dashboard/GetBackfillRunsAction.kt index d423a1023..a232b8406 100644 --- a/service/src/main/kotlin/app/cash/backfila/dashboard/GetBackfillRunsAction.kt +++ b/service/src/main/kotlin/app/cash/backfila/dashboard/GetBackfillRunsAction.kt @@ -60,10 +60,12 @@ class GetBackfillRunsAction @Inject constructor( @QueryParam pagination_token: String? = null, @QueryParam backfill_name: String? = null, @QueryParam created_by_user: String? = null, + @QueryParam show_deleted: Boolean = false, ): GetBackfillRunsResponse { val filterArgs = FilterArgs( backfillName = backfill_name, createdByUser = created_by_user, + showDeleted = show_deleted, ) return search(service, variant, pagination_token, filterArgs) } @@ -102,7 +104,7 @@ class GetBackfillRunsAction @Inject constructor( val (pausedBackfills, nextOffset) = queryFactory.newQuery() .serviceId(dbService.id) .stateNot(BackfillState.RUNNING) - .notSoftDeleted() + .apply { if (!filterArgs.showDeleted) notSoftDeleted() } .filterByArgs(filterArgs) .newPager( idDescPaginator(), @@ -219,5 +221,6 @@ class GetBackfillRunsAction @Inject constructor( private data class FilterArgs( val backfillName: String? = null, val createdByUser: String? = null, + val showDeleted: Boolean = false, ) } diff --git a/service/src/main/kotlin/app/cash/backfila/service/persistence/BackfillSoftDelete.kt b/service/src/main/kotlin/app/cash/backfila/service/persistence/BackfillSoftDelete.kt deleted file mode 100644 index b62dc2edf..000000000 --- a/service/src/main/kotlin/app/cash/backfila/service/persistence/BackfillSoftDelete.kt +++ /dev/null @@ -1,5 +0,0 @@ -package app.cash.backfila.service.persistence - -enum class BackfillSoftDelete { - SOFT_DELETED, -} 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 435fdc10a..c79822267 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 @@ -8,7 +8,6 @@ import app.cash.backfila.dashboard.StopBackfillAction import app.cash.backfila.dashboard.StopBackfillRequest import app.cash.backfila.dashboard.UpdateBackfillAction import app.cash.backfila.dashboard.UpdateBackfillRequest -import app.cash.backfila.service.persistence.BackfillSoftDelete import app.cash.backfila.service.persistence.BackfillState import app.cash.backfila.ui.components.AlertError import app.cash.backfila.ui.components.DashboardPageLayout @@ -59,7 +58,7 @@ class BackfillShowButtonHandlerAction @Inject constructor( BackfillState.CANCELLED.name -> { cancelBackfillAction.cancel(id.toLong()) } - BackfillSoftDelete.SOFT_DELETED.name -> { + "soft_delete" -> { softDeleteBackfillAction.softDelete(id.toLong()) } } diff --git a/service/src/main/kotlin/app/cash/backfila/ui/components/BackfillsTable.kt b/service/src/main/kotlin/app/cash/backfila/ui/components/BackfillsTable.kt index 49ba61dde..1710f764f 100644 --- a/service/src/main/kotlin/app/cash/backfila/ui/components/BackfillsTable.kt +++ b/service/src/main/kotlin/app/cash/backfila/ui/components/BackfillsTable.kt @@ -2,10 +2,15 @@ package app.cash.backfila.ui.components import app.cash.backfila.dashboard.UiBackfillRun import app.cash.backfila.ui.pages.BackfillShowAction +import kotlinx.html.InputType import kotlinx.html.TagConsumer import kotlinx.html.a import kotlinx.html.div +import kotlinx.html.form import kotlinx.html.h1 +import kotlinx.html.input +import kotlinx.html.label +import kotlinx.html.section import kotlinx.html.table import kotlinx.html.tbody import kotlinx.html.td @@ -13,13 +18,30 @@ import kotlinx.html.th import kotlinx.html.thead import kotlinx.html.tr -fun TagConsumer<*>.BackfillsTable(running: Boolean, backfills: List) { +fun TagConsumer<*>.BackfillsTable( + running: Boolean, + backfills: List, + showDeleted: Boolean = false, +) { val title = if (running) "Running" else "Paused" div("px-4 sm:px-6 lg:px-8 py-5") { - div("sm:flex sm:items-center") { - div("sm:flex-auto") { - h1("text-base font-semibold leading-6 text-gray-900") { +"""Backfills ($title)""" } + section("sm:flex sm:items-center justify-between") { + h1("text-base font-semibold leading-6 text-gray-900") { +"""Backfills ($title)""" } + + if (!running) { // Only show toggle for Paused backfills + form { + label("text-sm font-medium text-gray-600 flex items-center") { + +"Show deleted" + input(classes = "ml-2 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600") { + type = InputType.checkBox + name = "showDeleted" + value = "true" + if (showDeleted) attributes["checked"] = "checked" + attributes["onchange"] = "this.form.submit()" + } + } + } } } div("mt-8 flow-root") { 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 71d332f95..0d861e803 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 @@ -3,7 +3,6 @@ package app.cash.backfila.ui.pages import app.cash.backfila.dashboard.GetBackfillStatusAction import app.cash.backfila.dashboard.GetBackfillStatusResponse import app.cash.backfila.dashboard.ViewLogsAction -import app.cash.backfila.service.persistence.BackfillSoftDelete import app.cash.backfila.service.persistence.BackfillState import app.cash.backfila.ui.actions.BackfillShowButtonHandlerAction import app.cash.backfila.ui.components.AutoReload @@ -271,6 +270,7 @@ class BackfillShowAction @Inject constructor( label = START_STATE_BUTTON_LABEL, href = BackfillState.RUNNING.name, ) + // COMPLETE and CANCELLED represent final states. BackfillState.COMPLETE -> null BackfillState.CANCELLED -> null else -> Link( @@ -297,7 +297,7 @@ class BackfillShowAction @Inject constructor( return when (state) { BackfillState.COMPLETE, BackfillState.CANCELLED -> Link( label = DELETE_STATE_BUTTON_LABEL, - href = BackfillSoftDelete.SOFT_DELETED.name, + href = "soft_delete", ) else -> null } diff --git a/service/src/main/kotlin/app/cash/backfila/ui/pages/ServiceShowAction.kt b/service/src/main/kotlin/app/cash/backfila/ui/pages/ServiceShowAction.kt index 4aaace693..1fb670b5c 100644 --- a/service/src/main/kotlin/app/cash/backfila/ui/pages/ServiceShowAction.kt +++ b/service/src/main/kotlin/app/cash/backfila/ui/pages/ServiceShowAction.kt @@ -45,6 +45,7 @@ class ServiceShowAction @Inject constructor( @PathParam variantOrBlank: String? = "", @QueryParam offset: String? = null, @QueryParam lastOffset: String? = null, + @QueryParam showDeleted: Boolean = false, ): Response { if (service.isNullOrBlank()) { return Response( @@ -55,7 +56,12 @@ class ServiceShowAction @Inject constructor( } val variant = variantOrBlank.orEmpty().ifBlank { "default" } - val backfillRuns = getBackfillRunsAction.backfillRuns(service, variant, offset) + val backfillRuns = getBackfillRunsAction.backfillRuns( + service = service, + variant = variant, + pagination_token = offset, + show_deleted = showDeleted, + ) // TODO show default if other variants and probably link to a switcher val label = if (variant == "default") service else "$service ($variant)" @@ -70,7 +76,6 @@ class ServiceShowAction @Inject constructor( PageTitle("Service", label) { a { href = BackfillCreateServiceIndexAction.path(service, variantOrBlank) - 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") { type = ButtonType.button +"""Create""" @@ -79,7 +84,7 @@ class ServiceShowAction @Inject constructor( } BackfillsTable(true, backfillRuns.running_backfills) - BackfillsTable(false, backfillRuns.paused_backfills) + BackfillsTable(false, backfillRuns.paused_backfills, showDeleted) Pagination(backfillRuns.next_pagination_token, offset, lastOffset, path(service, variantOrBlank)) } } diff --git a/service/src/main/resources/migrations/v019__backfila.sql b/service/src/main/resources/migrations/v019__backfila.sql index 451d63e21..7b6bd1ada 100644 --- a/service/src/main/resources/migrations/v019__backfila.sql +++ b/service/src/main/resources/migrations/v019__backfila.sql @@ -1,2 +1,2 @@ ALTER TABLE backfill_runs - ADD COLUMN `deleted_at` TIMESTAMP NULL DEFAULT NULL; \ No newline at end of file + ADD COLUMN `deleted_at` timestamp(3) NULL DEFAULT NULL; \ No newline at end of file diff --git a/service/src/main/resources/migrations/v020__add_soft_delete.sql b/service/src/main/resources/migrations/v020__add_soft_delete.sql deleted file mode 100644 index 42fab51e9..000000000 --- a/service/src/main/resources/migrations/v020__add_soft_delete.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Add soft_deleted column to backfill_runs -ALTER TABLE backfill_runs - ADD COLUMN soft_deleted BOOLEAN NOT NULL DEFAULT FALSE; \ No newline at end of file