From 45c4c0b59f26610a83bbd61ccf9e9e4a3ee2b1c8 Mon Sep 17 00:00:00 2001 From: Jeff Hwang Date: Mon, 28 Apr 2025 13:12:52 -0400 Subject: [PATCH 1/7] add cancel button and triggr dbpartition state to cancelled --- .../proto/app/cash/backfila/service.proto | 1 + .../backfila/api/CheckBackfillStatusAction.kt | 1 + .../dashboard/CancelBackfillAction.kt | 63 +++++++++++++++++++ .../service/listener/AuditClientListener.kt | 14 +++++ .../service/listener/BackfillRunListener.kt | 1 + .../backfila/service/listener/SlackHelper.kt | 9 +++ .../service/persistence/BackfillState.kt | 1 + .../service/persistence/DbBackfillRun.kt | 6 +- .../BackfillShowButtonHandlerAction.kt | 16 +++-- .../backfila/ui/pages/BackfillShowAction.kt | 48 +++++++++++++- .../resources/migrations/v017__backfila.sql | 2 + 11 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 service/src/main/kotlin/app/cash/backfila/dashboard/CancelBackfillAction.kt create mode 100644 service/src/main/resources/migrations/v017__backfila.sql diff --git a/client/src/main/proto/app/cash/backfila/service.proto b/client/src/main/proto/app/cash/backfila/service.proto index ee43d3048..a4659464c 100644 --- a/client/src/main/proto/app/cash/backfila/service.proto +++ b/client/src/main/proto/app/cash/backfila/service.proto @@ -102,5 +102,6 @@ message CheckBackfillStatusResponse { PAUSED = 1; RUNNING = 2; COMPLETE = 3; + CANCELLED = 4; } } \ No newline at end of file diff --git a/service/src/main/kotlin/app/cash/backfila/api/CheckBackfillStatusAction.kt b/service/src/main/kotlin/app/cash/backfila/api/CheckBackfillStatusAction.kt index ba1ce553f..4614616e2 100644 --- a/service/src/main/kotlin/app/cash/backfila/api/CheckBackfillStatusAction.kt +++ b/service/src/main/kotlin/app/cash/backfila/api/CheckBackfillStatusAction.kt @@ -37,6 +37,7 @@ class CheckBackfillStatusAction @Inject constructor( BackfillState.PAUSED -> Status.PAUSED BackfillState.RUNNING -> Status.RUNNING BackfillState.COMPLETE -> Status.COMPLETE + BackfillState.CANCELLED -> Status.CANCELLED } } } diff --git a/service/src/main/kotlin/app/cash/backfila/dashboard/CancelBackfillAction.kt b/service/src/main/kotlin/app/cash/backfila/dashboard/CancelBackfillAction.kt new file mode 100644 index 000000000..63c538096 --- /dev/null +++ b/service/src/main/kotlin/app/cash/backfila/dashboard/CancelBackfillAction.kt @@ -0,0 +1,63 @@ +package app.cash.backfila.dashboard + +import app.cash.backfila.service.listener.BackfillRunListener +import app.cash.backfila.service.persistence.BackfilaDb +import app.cash.backfila.service.persistence.BackfillState +import app.cash.backfila.service.persistence.DbBackfillRun +import app.cash.backfila.service.persistence.DbEventLog +import javax.inject.Inject +import misk.MiskCaller +import misk.exceptions.BadRequestException +import misk.hibernate.Id +import misk.hibernate.Query +import misk.hibernate.Transacter +import misk.hibernate.load +import misk.scope.ActionScoped +import misk.security.authz.Authenticated +import misk.web.PathParam +import misk.web.Post +import misk.web.RequestContentType +import misk.web.ResponseContentType +import misk.web.actions.WebAction +import misk.web.mediatype.MediaTypes + +class CancelBackfillAction @Inject constructor( + @BackfilaDb private val transacter: Transacter, + private val queryFactory: Query.Factory, + private val backfillRunListeners: Set, + private val caller: @JvmSuppressWildcards ActionScoped, +) : WebAction { + @Post("/backfill/cancel/{id}") + @RequestContentType(MediaTypes.APPLICATION_JSON) + @ResponseContentType(MediaTypes.APPLICATION_JSON) + @Authenticated + fun cancel( + @PathParam id: Long, + ) { + transacter.transaction { session -> + val backfillRun = session.load(Id(id)) + + // Check if backfill can be cancelled + if (backfillRun.state == BackfillState.CANCELLED) { + throw BadRequestException("Cannot cancel a ${backfillRun.state.name.lowercase()} backfill") + } + + // Update state in run_partitions table + backfillRun.setState(session, queryFactory, BackfillState.CANCELLED) + + // Log the cancellation event + session.save( + DbEventLog( + backfillRun.id, + partition_id = null, + user = caller.get()?.user, + type = DbEventLog.Type.STATE_CHANGE, + message = "backfill cancelled", + ), + ) + } + + // Notify listeners + backfillRunListeners.forEach { it.runCancelled(Id(id), caller.get()?.principal ?: "") } + } +} diff --git a/service/src/main/kotlin/app/cash/backfila/service/listener/AuditClientListener.kt b/service/src/main/kotlin/app/cash/backfila/service/listener/AuditClientListener.kt index de96f6ff6..ed9a23eb6 100644 --- a/service/src/main/kotlin/app/cash/backfila/service/listener/AuditClientListener.kt +++ b/service/src/main/kotlin/app/cash/backfila/service/listener/AuditClientListener.kt @@ -71,6 +71,20 @@ internal class AuditClientListener @Inject constructor( ) } + override fun runCancelled(id: Id, user: String) { + val (backfillName, serviceName, description) = transacter.transaction { session -> + val run = session.load(id) + AuditEventInputs(run.registered_backfill.name, serviceName(run), "Backfill cancelled by $user ${dryRunPrefix(run)}${nameAndId(run)}") + } + auditClient.logEvent( + target = backfillName, + description = description, + requestorLDAP = user, + applicationName = serviceName, + detailURL = idUrl(id), + ) + } + private fun serviceName(run: DbBackfillRun) = if (run.service.variant == "default") { run.service.registry_name } else { diff --git a/service/src/main/kotlin/app/cash/backfila/service/listener/BackfillRunListener.kt b/service/src/main/kotlin/app/cash/backfila/service/listener/BackfillRunListener.kt index 0d345b299..03506c240 100644 --- a/service/src/main/kotlin/app/cash/backfila/service/listener/BackfillRunListener.kt +++ b/service/src/main/kotlin/app/cash/backfila/service/listener/BackfillRunListener.kt @@ -8,4 +8,5 @@ interface BackfillRunListener { fun runPaused(id: Id, user: String) fun runErrored(id: Id) fun runCompleted(id: Id) + fun runCancelled(id: Id, user: String) } diff --git a/service/src/main/kotlin/app/cash/backfila/service/listener/SlackHelper.kt b/service/src/main/kotlin/app/cash/backfila/service/listener/SlackHelper.kt index 53c945bc9..28b2754c1 100644 --- a/service/src/main/kotlin/app/cash/backfila/service/listener/SlackHelper.kt +++ b/service/src/main/kotlin/app/cash/backfila/service/listener/SlackHelper.kt @@ -52,6 +52,15 @@ class SlackHelper @Inject constructor( slackClient.postMessage("Backfila", ":backfila:", message, channel) } + override fun runCancelled(id: Id, user: String) { + val (message, channel) = transacter.transaction { session -> + val run = session.load(id) + val message = ":backfila_cancel:${dryRunEmoji(run)} ${nameAndId(run)} canceled by @$user" + message to run.service.slack_channel + } + slackClient.postMessage("Backfila", ":backfila:", message, channel) + } + private fun nameAndId(run: DbBackfillRun) = "[${deployment.name}] ${run.service.registry_name} (${run.service.variant}) `${run.registered_backfill.name}` " + "(${idLink(run.id)})" diff --git a/service/src/main/kotlin/app/cash/backfila/service/persistence/BackfillState.kt b/service/src/main/kotlin/app/cash/backfila/service/persistence/BackfillState.kt index 1d16ffb5d..8dcaaae5e 100644 --- a/service/src/main/kotlin/app/cash/backfila/service/persistence/BackfillState.kt +++ b/service/src/main/kotlin/app/cash/backfila/service/persistence/BackfillState.kt @@ -4,4 +4,5 @@ enum class BackfillState { PAUSED, RUNNING, COMPLETE, + CANCELLED, } diff --git a/service/src/main/kotlin/app/cash/backfila/service/persistence/DbBackfillRun.kt b/service/src/main/kotlin/app/cash/backfila/service/persistence/DbBackfillRun.kt index 3bc8239e1..f8e9b1f0b 100644 --- a/service/src/main/kotlin/app/cash/backfila/service/persistence/DbBackfillRun.kt +++ b/service/src/main/kotlin/app/cash/backfila/service/persistence/DbBackfillRun.kt @@ -1,6 +1,5 @@ package app.cash.backfila.service.persistence -import com.google.common.base.Preconditions.checkState import java.time.Instant import javax.persistence.Column import javax.persistence.Entity @@ -134,7 +133,10 @@ class DbBackfillRun() : DbUnsharded, DbTimestampedEntity { fun setState(session: Session, queryFactory: Query.Factory, state: BackfillState) { // State can't be changed after being completed. - checkState(this.state != BackfillState.COMPLETE) + if (this.state == BackfillState.COMPLETE) { + return + } + this.state = state // Set the state of all the partitions that are not complete val query = session.hibernateSession.createQuery( 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 8bd47e3e4..426734dae 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 @@ -1,5 +1,6 @@ package app.cash.backfila.ui.actions +import app.cash.backfila.dashboard.CancelBackfillAction import app.cash.backfila.dashboard.StartBackfillAction import app.cash.backfila.dashboard.StartBackfillRequest import app.cash.backfila.dashboard.StopBackfillAction @@ -31,6 +32,7 @@ class BackfillShowButtonHandlerAction @Inject constructor( private val startBackfillAction: StartBackfillAction, private val stopBackfillAction: StopBackfillAction, private val updateBackfillAction: UpdateBackfillAction, + private val cancelBackfillAction: CancelBackfillAction, ) : WebAction { @Get(PATH) @ResponseContentType(MediaTypes.TEXT_HTML) @@ -44,10 +46,16 @@ class BackfillShowButtonHandlerAction @Inject constructor( if (!field_id.isNullOrBlank()) { when (field_id) { "state" -> { - if (field_value == BackfillState.PAUSED.name) { - stopBackfillAction.stop(id.toLong(), StopBackfillRequest()) - } else if (field_value == BackfillState.RUNNING.name) { - startBackfillAction.start(id.toLong(), StartBackfillRequest()) + when (field_value) { + BackfillState.PAUSED.name -> { + stopBackfillAction.stop(id.toLong(), StopBackfillRequest()) + } + BackfillState.RUNNING.name -> { + startBackfillAction.start(id.toLong(), StartBackfillRequest()) + } + BackfillState.CANCELLED.name -> { + cancelBackfillAction.cancel(id.toLong()) + } } } 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 2761b8b5d..b3d88c5bd 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 @@ -259,6 +259,7 @@ class BackfillShowAction @Inject constructor( /* Value of the button click is provided through the button.href field. */ val button: Link? = null, val updateFieldId: String? = null, + val secondaryButton: Link? = null, ) private fun getStateButton(state: BackfillState): Link? { @@ -267,8 +268,8 @@ class BackfillShowAction @Inject constructor( label = START_STATE_BUTTON_LABEL, href = BackfillState.RUNNING.name, ) - BackfillState.COMPLETE -> null + BackfillState.CANCELLED -> null else -> Link( label = PAUSE_STATE_BUTTON_LABEL, href = BackfillState.PAUSED.name, @@ -276,12 +277,27 @@ class BackfillShowAction @Inject constructor( } } + private fun getCancelButton(state: BackfillState): Link? { + return when (state) { + BackfillState.PAUSED -> Link( + label = CANCEL_STATE_BUTTON_LABEL, + href = BackfillState.CANCELLED.name, + ) + BackfillState.COMPLETE -> Link( + label = CANCEL_STATE_BUTTON_LABEL, + href = BackfillState.CANCELLED.name, + ) + else -> null + } + } + private fun GetBackfillStatusResponse.toConfigurationRows(id: Long) = listOf( DescriptionListRow( label = "State", description = state.name, button = getStateButton(state), updateFieldId = "state", + secondaryButton = getCancelButton(state), ), DescriptionListRow( label = "Dry Run", @@ -490,6 +506,35 @@ class BackfillShowAction @Inject constructor( } } } + // Add secondary button if present + it.secondaryButton?.let { secondaryButton -> + span("ml-2") { + 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 = secondaryButton.href + } + } + + button( + classes = "rounded-full bg-red-600 px-3 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600", + ) { + type = ButtonType.submit + +secondaryButton.label + } + } + } + } } } } @@ -538,6 +583,7 @@ class BackfillShowAction @Inject constructor( const val START_STATE_BUTTON_LABEL = "Start" const val PAUSE_STATE_BUTTON_LABEL = "Pause" + const val CANCEL_STATE_BUTTON_LABEL = "Cancel" const val UPDATE_BUTTON_LABEL = "Update" const val VIEW_LOGS_BUTTON_LABEL = "View Logs" } diff --git a/service/src/main/resources/migrations/v017__backfila.sql b/service/src/main/resources/migrations/v017__backfila.sql new file mode 100644 index 000000000..608482323 --- /dev/null +++ b/service/src/main/resources/migrations/v017__backfila.sql @@ -0,0 +1,2 @@ +ALTER TABLE run_partitions + MODIFY COLUMN `run_state` enum('PAUSED','RUNNING','COMPLETE','CANCELLED') NOT NULL; \ No newline at end of file From 5fde36b2e0961011840f9683acb0cacaf08bf851 Mon Sep 17 00:00:00 2001 From: Jeff Hwang Date: Mon, 28 Apr 2025 14:52:25 -0400 Subject: [PATCH 2/7] remove cancel from completed backfills --- .../backfila/service/runner/BackfillRunner.kt | 7 ++- .../backfila/ui/pages/BackfillShowAction.kt | 51 ++++++++++--------- .../resources/migrations/v017__backfila.sql | 4 +- .../resources/migrations/v018__backfila.sql | 2 + 4 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 service/src/main/resources/migrations/v018__backfila.sql diff --git a/service/src/main/kotlin/app/cash/backfila/service/runner/BackfillRunner.kt b/service/src/main/kotlin/app/cash/backfila/service/runner/BackfillRunner.kt index 5a86c2752..93dd5fd1c 100644 --- a/service/src/main/kotlin/app/cash/backfila/service/runner/BackfillRunner.kt +++ b/service/src/main/kotlin/app/cash/backfila/service/runner/BackfillRunner.kt @@ -171,7 +171,12 @@ class BackfillRunner private constructor( // Now that state is stored, check if we should exit. if (dbRunPartition.run_state != BackfillState.RUNNING) { - logger.info { "Backfill is no longer in RUNNING state, stopping runner ${logLabel()}" } + val stateChange = when (dbRunPartition.run_state) { + BackfillState.PAUSED -> "paused" + BackfillState.CANCELLED -> "cancelled" + else -> dbRunPartition.run_state.name.lowercase() + } + logger.info { "Backfill is no longer in RUNNING state (now $stateChange), stopping runner ${logLabel()}" } running = false return@transaction } 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 b3d88c5bd..b589f71a0 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 @@ -503,34 +503,35 @@ class BackfillShowAction @Inject constructor( +button.label } } - } - } - } - // Add secondary button if present - it.secondaryButton?.let { secondaryButton -> - span("ml-2") { - form { - action = BackfillShowButtonHandlerAction.path(id) - it.updateFieldId?.let { - input { - type = InputType.hidden - name = "field_id" - value = it - } + // Add secondary button if present + it.secondaryButton?.let { secondaryButton -> + span("ml-2") { + 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 = secondaryButton.href - } - } + input { + type = InputType.hidden + name = "field_value" + value = secondaryButton.href + } + } - button( - classes = "rounded-full bg-red-600 px-3 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600", - ) { - type = ButtonType.submit - +secondaryButton.label + button( + classes = "rounded-full bg-red-600 px-3 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600", + ) { + type = ButtonType.submit + +secondaryButton.label + } + } + } } } } diff --git a/service/src/main/resources/migrations/v017__backfila.sql b/service/src/main/resources/migrations/v017__backfila.sql index 608482323..bc0e3be3e 100644 --- a/service/src/main/resources/migrations/v017__backfila.sql +++ b/service/src/main/resources/migrations/v017__backfila.sql @@ -1,2 +1,2 @@ -ALTER TABLE run_partitions - MODIFY COLUMN `run_state` enum('PAUSED','RUNNING','COMPLETE','CANCELLED') NOT NULL; \ No newline at end of file +ALTER TABLE backfill_runs + MODIFY COLUMN `state` enum('PAUSED','RUNNING','COMPLETE','CANCELLED') NOT NULL; \ No newline at end of file diff --git a/service/src/main/resources/migrations/v018__backfila.sql b/service/src/main/resources/migrations/v018__backfila.sql new file mode 100644 index 000000000..608482323 --- /dev/null +++ b/service/src/main/resources/migrations/v018__backfila.sql @@ -0,0 +1,2 @@ +ALTER TABLE run_partitions + MODIFY COLUMN `run_state` enum('PAUSED','RUNNING','COMPLETE','CANCELLED') NOT NULL; \ No newline at end of file From b4d48a9506cc6d573c231908761ef7e77efaaad3 Mon Sep 17 00:00:00 2001 From: Jeff Hwang Date: Mon, 28 Apr 2025 14:58:57 -0400 Subject: [PATCH 3/7] check condition --- .../app/cash/backfila/service/persistence/DbBackfillRun.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/service/src/main/kotlin/app/cash/backfila/service/persistence/DbBackfillRun.kt b/service/src/main/kotlin/app/cash/backfila/service/persistence/DbBackfillRun.kt index f8e9b1f0b..2e6d4fea8 100644 --- a/service/src/main/kotlin/app/cash/backfila/service/persistence/DbBackfillRun.kt +++ b/service/src/main/kotlin/app/cash/backfila/service/persistence/DbBackfillRun.kt @@ -1,5 +1,6 @@ package app.cash.backfila.service.persistence +import com.google.common.base.Preconditions.checkState import java.time.Instant import javax.persistence.Column import javax.persistence.Entity @@ -133,9 +134,7 @@ class DbBackfillRun() : DbUnsharded, DbTimestampedEntity { fun setState(session: Session, queryFactory: Query.Factory, state: BackfillState) { // State can't be changed after being completed. - if (this.state == BackfillState.COMPLETE) { - return - } + checkState(this.state != BackfillState.COMPLETE) this.state = state // Set the state of all the partitions that are not complete From 6a5f032559ade79c7c6d3a1051191e256c13bee2 Mon Sep 17 00:00:00 2001 From: Jeff Hwang Date: Mon, 28 Apr 2025 16:03:26 -0400 Subject: [PATCH 4/7] lint --- .../app/cash/backfila/ui/pages/BackfillShowAction.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 b589f71a0..68aae084f 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 @@ -259,7 +259,7 @@ class BackfillShowAction @Inject constructor( /* Value of the button click is provided through the button.href field. */ val button: Link? = null, val updateFieldId: String? = null, - val secondaryButton: Link? = null, + val cancelButton: Link? = null, ) private fun getStateButton(state: BackfillState): Link? { @@ -297,7 +297,7 @@ class BackfillShowAction @Inject constructor( description = state.name, button = getStateButton(state), updateFieldId = "state", - secondaryButton = getCancelButton(state), + cancelButton = getCancelButton(state), ), DescriptionListRow( label = "Dry Run", @@ -505,7 +505,7 @@ class BackfillShowAction @Inject constructor( } // Add secondary button if present - it.secondaryButton?.let { secondaryButton -> + it.cancelButton?.let { cancelButton -> span("ml-2") { form { action = BackfillShowButtonHandlerAction.path(id) @@ -520,7 +520,7 @@ class BackfillShowAction @Inject constructor( input { type = InputType.hidden name = "field_value" - value = secondaryButton.href + value = cancelButton.href } } @@ -528,7 +528,7 @@ class BackfillShowAction @Inject constructor( classes = "rounded-full bg-red-600 px-3 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600", ) { type = ButtonType.submit - +secondaryButton.label + +cancelButton.label } } } From 0942614eebbd565be03a216457e5383bd3807323 Mon Sep 17 00:00:00 2001 From: Jeff Hwang Date: Mon, 28 Apr 2025 16:18:09 -0400 Subject: [PATCH 5/7] lint --- .../kotlin/app/cash/backfila/ui/pages/BackfillShowAction.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 68aae084f..58ee88d37 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 @@ -283,10 +283,6 @@ class BackfillShowAction @Inject constructor( label = CANCEL_STATE_BUTTON_LABEL, href = BackfillState.CANCELLED.name, ) - BackfillState.COMPLETE -> Link( - label = CANCEL_STATE_BUTTON_LABEL, - href = BackfillState.CANCELLED.name, - ) else -> null } } @@ -504,7 +500,7 @@ class BackfillShowAction @Inject constructor( } } - // Add secondary button if present + // Add cancel button if present it.cancelButton?.let { cancelButton -> span("ml-2") { form { From e85972b4eceb431fbc28108b4a22a678be134266 Mon Sep 17 00:00:00 2001 From: Jeff Hwang Date: Tue, 29 Apr 2025 11:01:37 -0400 Subject: [PATCH 6/7] add users to authenticattion --- .../kotlin/app/cash/backfila/dashboard/CancelBackfillAction.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 63c538096..e5d95e506 100644 --- a/service/src/main/kotlin/app/cash/backfila/dashboard/CancelBackfillAction.kt +++ b/service/src/main/kotlin/app/cash/backfila/dashboard/CancelBackfillAction.kt @@ -30,7 +30,7 @@ class CancelBackfillAction @Inject constructor( @Post("/backfill/cancel/{id}") @RequestContentType(MediaTypes.APPLICATION_JSON) @ResponseContentType(MediaTypes.APPLICATION_JSON) - @Authenticated + @Authenticated(capabilities = ["users"]) fun cancel( @PathParam id: Long, ) { From ddd4562859619068007f2595a3cfd409bca61820 Mon Sep 17 00:00:00 2001 From: Jeff Hwang Date: Wed, 30 Apr 2025 13:20:41 -0400 Subject: [PATCH 7/7] address comments --- .../kotlin/app/cash/backfila/dashboard/CancelBackfillAction.kt | 3 +-- .../kotlin/app/cash/backfila/ui/pages/BackfillShowAction.kt | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) 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/ui/pages/BackfillShowAction.kt b/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillShowAction.kt index 58ee88d37..6645d8d1b 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 @@ -268,6 +268,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(