feat: add a dashboard button to retrigger a branch workflow (#46626)

## Problem

When workflow runs for branches fail due to stuck tasks, they can sit
idle indefinitely. Users are unaware that they need to make a new commit
to retrigger the workflow — leading to confusion, wasted time, and
silent failures going unnoticed.

## Solution

Provide a button that allows users to retrigger a workflow when it is
not being removed.

## How to test

- Create a branch
- Wait for its row to appear on the branch management page
- Click the _View logs_ button
- You should see a _Retrigger_ button
- Clicking it should make a new row appear in those logs

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Retrigger previously executed workflows directly from branch preview
actions.
  * Confirmation dialog added for retrigger actions.

* **Improvements**
  * Toast-style error notifications when retriggering fails.
* Workflow run list layout updated for a more flexible horizontal
display.
* After retriggering, workflow and action lists refresh so updates
appear promptly.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Gildas Garcia authored and GitHub committed 2026-06-04 15:34:59 +02:00
1 parent 1673012bc7
commit 375cbcf4a2
3 files changed
+85 -24

No files matched your search

@@ -8,6 +8,7 @@ import {
Infinity,
MoreVertical,
Pencil,
Redo,
RefreshCw,
Shield,
Trash2,
@@ -30,6 +31,7 @@ import { EditBranchModal } from './EditBranchModal'
import { PreviewBranchesEmptyState } from './EmptyStates'
import { DropdownMenuItemTooltip } from '@/components/ui/DropdownMenuItemTooltip'
import { TextConfirmModal } from '@/components/ui/TextConfirmModalWrapper'
import { useBranchPushMutation } from '@/data/branches/branch-push-mutation'
import { useBranchQuery } from '@/data/branches/branch-query'
import { useBranchResetMutation } from '@/data/branches/branch-reset-mutation'
import { useBranchRestoreMutation } from '@/data/branches/branch-restore-mutation'
@@ -262,6 +264,7 @@ const PreviewBranchActions = ({
setShowPersistentBranchDeleteConfirmationModal,
] = useState(false)
const [showEditBranchModal, setShowEditBranchModal] = useState(false)
const [showConfirmRetriggersModal, setShowConfirmRetriggersModal] = useState(false)
const { mutate: resetBranch, isPending: isResetting } = useBranchResetMutation({
onSuccess() {
@@ -286,6 +289,20 @@ const PreviewBranchActions = ({
},
})
const { mutate: branchPushMutate, isPending: isRetriggering } = useBranchPushMutation({
onSuccess() {
toast.success('Success! Please allow a few minutes for the branch to update.')
setShowConfirmRetriggersModal(false)
},
onError: (data) => {
toast.error(`Failed to trigger workflow: ${data.message}`)
},
})
const onRetriggerBranch = () => {
branchPushMutate({ branchRef, projectRef })
}
const onRestoreBranch = () => {
restoreBranch({ branchRef, projectRef })
}
@@ -345,28 +362,54 @@ const PreviewBranchActions = ({
</DropdownMenuItemTooltip>
{!branch.deletion_scheduled_at && (
<DropdownMenuItemTooltip
className="gap-x-2"
disabled={isResetting || !isBranchActiveHealthy}
onSelect={(e) => {
e.stopPropagation()
setShowConfirmResetModal(true)
}}
onClick={(e) => {
e.stopPropagation()
setShowConfirmResetModal(true)
}}
tooltip={{
content: {
side: 'left',
text: !isBranchActiveHealthy
? 'Branch is still initializing. Please wait for it to become healthy before resetting.'
: undefined,
},
}}
>
<RefreshCw size={14} /> Reset branch
</DropdownMenuItemTooltip>
<>
<DropdownMenuItemTooltip
className="gap-x-2"
disabled={!canUpdateBranches || !isBranchActiveHealthy || isUpdatingBranch}
onSelect={(e) => {
e.stopPropagation()
setShowConfirmRetriggersModal(true)
}}
onClick={(e) => {
e.stopPropagation()
setShowConfirmRetriggersModal(true)
}}
tooltip={{
content: {
side: 'left',
text: !canUpdateBranches
? `You need additional permissions to ${branch.git_branch ? 'resync' : 'rebase'} branches`
: !isBranchActiveHealthy
? `Branch is still initializing. Please wait for it to become healthy before ${branch.git_branch ? 'resyncing' : 'rebasing'}.`
: undefined,
},
}}
>
<Redo size={14} /> {branch.git_branch ? 'Resync branch' : 'Rebase branch'}
</DropdownMenuItemTooltip>
<DropdownMenuItemTooltip
className="gap-x-2"
disabled={isResetting || !isBranchActiveHealthy}
onSelect={(e) => {
e.stopPropagation()
setShowConfirmResetModal(true)
}}
onClick={(e) => {
e.stopPropagation()
setShowConfirmResetModal(true)
}}
tooltip={{
content: {
side: 'left',
text: !isBranchActiveHealthy
? 'Branch is still initializing. Please wait for it to become healthy before resetting.'
: undefined,
},
}}
>
<RefreshCw size={14} /> Reset branch
</DropdownMenuItemTooltip>
</>
)}
{!branch.deletion_scheduled_at && (
@@ -497,10 +540,26 @@ const PreviewBranchActions = ({
</p>
</ConfirmationModal>
<ConfirmationModal
variant="default"
visible={showConfirmRetriggersModal}
confirmLabel={branch.git_branch ? 'Resync' : 'Rebase'}
title={branch.git_branch ? 'Confirm branch resync' : 'Confirm branch rebase'}
loading={isRetriggering}
onCancel={() => setShowConfirmRetriggersModal(false)}
onConfirm={onRetriggerBranch}
>
<p className="text-sm text-foreground-light">
{branch.git_branch
? 'This will re-run all steps of the workflow based on the latest git branch state.'
: 'This will re-run all steps of the workflow based on the latest dashboard state.'}
</p>
</ConfirmationModal>
<ConfirmationModal
variant="warning"
visible={showPersistentBranchDeleteConfirmationModal}
confirmLabel={'Switch to preview'}
confirmLabel="Switch to preview"
title="Branch must be switched to preview before deletion"
loading={isUpdatingBranch}
onCancel={() => setShowPersistentBranchDeleteConfirmationModal(false)}
@@ -111,7 +111,7 @@ export const WorkflowLogs = ({ branch }: WorkflowLogsProps) => {
(workflowRuns.length > 0 ? (
<ul className="divide-y">
{workflowRuns.map((workflowRun) => (
<li key={workflowRun.id} className="px-4 py-3">
<li key={workflowRun.id} className="flex justify-between px-4 py-3 gap-2">
<button
type="button"
disabled={workflowRun.id === projectRef}
@@ -1,6 +1,7 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { actionKeys } from '../actions/keys'
import { branchKeys } from './keys'
import { handleError, post } from '@/data/fetchers'
import type { ResponseError, UseCustomMutationOptions } from '@/types'
@@ -36,6 +37,7 @@ export const useBranchPushMutation = ({
async onSuccess(data, variables, context) {
const { projectRef } = variables
await queryClient.invalidateQueries({ queryKey: branchKeys.list(projectRef) })
await queryClient.invalidateQueries({ queryKey: actionKeys.list(projectRef) })
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {