Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class GetBackfillRunsAction @Inject constructor(
val runningBackfills = queryFactory.newQuery<BackfillRunQuery>()
.serviceId(dbService.id)
.state(BackfillState.RUNNING)
.notSoftDeleted()
.orderByIdDesc()
.filterByArgs(filterArgs)
.list(session)
Expand All @@ -102,6 +103,7 @@ class GetBackfillRunsAction @Inject constructor(
val (pausedBackfills, nextOffset) = queryFactory.newQuery<BackfillRunQuery>()
.serviceId(dbService.id)
.stateNot(BackfillState.RUNNING)
.notSoftDeleted()
.filterByArgs(filterArgs)
.newPager(
idDescPaginator(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ data class GetBackfillStatusResponse(
val backoff_schedule: String?,
val partitions: List<UiPartition>,
val event_logs: List<UiEventLog>,
val soft_deleted: Boolean,
)

class GetBackfillStatusAction @Inject constructor(
Expand Down Expand Up @@ -98,6 +99,7 @@ class GetBackfillStatusAction @Inject constructor(
run.backoff_schedule,
partitions.map { dbToUi(it) },
events(session, run, partitions),
run.soft_deleted,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package app.cash.backfila.dashboard

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.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 SoftDeleteBackfillAction @Inject constructor(
@BackfilaDb private val transacter: Transacter,
private val caller: @JvmSuppressWildcards ActionScoped<MiskCaller?>,
) : WebAction {
@Post("/backfill/delete/{id}")
@RequestContentType(MediaTypes.APPLICATION_JSON)
@ResponseContentType(MediaTypes.APPLICATION_JSON)
@Authenticated
fun softDelete(
@PathParam id: Long,
) {
transacter.transaction { session ->
val backfillRun = session.load<DbBackfillRun>(Id(id))

// Only allow soft delete for COMPLETE or CANCELLED backfills
if (backfillRun.state != BackfillState.COMPLETE && backfillRun.state != BackfillState.CANCELLED) {
throw BadRequestException("Can only delete completed or cancelled backfills")
}

backfillRun.soft_deleted = true

// Log the deletion event
session.save(
DbEventLog(
backfillRun.id,
partition_id = null,
user = caller.get()?.principal ?: "",
type = DbEventLog.Type.STATE_CHANGE,
message = "backfill soft deleted",
),
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ interface BackfillRunQuery : Query<DbBackfillRun> {

@Order("updated_at", asc = false)
fun orderByUpdatedAtDesc(): BackfillRunQuery

@Constraint("soft_deleted", Operator.EQ)
fun notSoftDeleted(softDeleted: Boolean = false): BackfillRunQuery
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package app.cash.backfila.service.persistence

enum class BackfillSoftDelete {
SOFT_DELETED,
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ class DbBackfillRun() : DbUnsharded<DbBackfillRun>, DbTimestampedEntity {
@Column(nullable = false)
var dry_run: Boolean = false

@Column(nullable = false)
var soft_deleted: Boolean = false

/** Comma separated list of delays for consecutive retries in milliseconds, e.g. 1000,2000 */
@Column
var backoff_schedule: String? = null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package app.cash.backfila.ui.actions

import app.cash.backfila.dashboard.CancelBackfillAction
import app.cash.backfila.dashboard.SoftDeleteBackfillAction
import app.cash.backfila.dashboard.StartBackfillAction
import app.cash.backfila.dashboard.StartBackfillRequest
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
Expand All @@ -33,6 +35,7 @@ class BackfillShowButtonHandlerAction @Inject constructor(
private val stopBackfillAction: StopBackfillAction,
private val updateBackfillAction: UpdateBackfillAction,
private val cancelBackfillAction: CancelBackfillAction,
private val softDeleteBackfillAction: SoftDeleteBackfillAction,
) : WebAction {
@Get(PATH)
@ResponseContentType(MediaTypes.TEXT_HTML)
Expand All @@ -56,6 +59,9 @@ class BackfillShowButtonHandlerAction @Inject constructor(
BackfillState.CANCELLED.name -> {
cancelBackfillAction.cancel(id.toLong())
}
BackfillSoftDelete.SOFT_DELETED.name -> {
softDeleteBackfillAction.softDelete(id.toLong())
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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
Expand Down Expand Up @@ -260,6 +261,7 @@ class BackfillShowAction @Inject constructor(
val button: Link? = null,
val updateFieldId: String? = null,
val cancelButton: Link? = null,
val deleteButton: Link? = null,
)

private fun getStateButton(state: BackfillState): Link? {
Expand Down Expand Up @@ -287,13 +289,27 @@ class BackfillShowAction @Inject constructor(
}
}

private fun getDeleteButton(state: BackfillState, softDeleted: Boolean): Link? {
if (softDeleted) {
return null
}
return when (state) {
BackfillState.COMPLETE, BackfillState.CANCELLED -> Link(
label = DELETE_STATE_BUTTON_LABEL,
href = BackfillSoftDelete.SOFT_DELETED.name,
)
else -> null
}
}

private fun GetBackfillStatusResponse.toConfigurationRows(id: Long) = listOf(
DescriptionListRow(
label = "State",
description = state.name,
button = getStateButton(state),
updateFieldId = "state",
cancelButton = getCancelButton(state),
deleteButton = getDeleteButton(state, soft_deleted),
),
DescriptionListRow(
label = "Dry Run",
Expand Down Expand Up @@ -532,6 +548,35 @@ class BackfillShowAction @Inject constructor(
}
}
}

it.deleteButton?.let { deleteButton ->
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 = deleteButton.href
}
}

button(
classes = "rounded-full bg-gray-600 px-3 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-600",
) {
type = ButtonType.submit
+deleteButton.label
}
}
}
}
}
}
}
Expand Down Expand Up @@ -581,6 +626,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 DELETE_STATE_BUTTON_LABEL = "Delete"
const val UPDATE_BUTTON_LABEL = "Update"
const val VIEW_LOGS_BUTTON_LABEL = "View Logs"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- Add soft_deleted column to backfill_runs
ALTER TABLE backfill_runs
ADD COLUMN soft_deleted BOOLEAN NOT NULL DEFAULT FALSE;