11import DeleteIcon from "@mui/icons-material/Delete" ;
2+ import ReplayIcon from "@mui/icons-material/Replay" ;
3+ import StopIcon from "@mui/icons-material/Stop" ;
24import { LoadingButton } from "@mui/lab" ;
35import {
46 Box ,
@@ -15,33 +17,25 @@ import {
1517import { keepPreviousData , useMutation , useQuery } from "@tanstack/react-query" ;
1618import { useConfirm } from "material-ui-confirm" ;
1719import { useSnackbar } from "notistack" ;
18- import React , { useCallback } from "react" ;
20+ import React , { useCallback , useState } from "react" ;
1921import { Link as RouterLink , useNavigate , useParams } from "react-router" ;
2022import { ErrorAlert } from "../../components/ErrorAlert" ;
23+ import { ReasonDialog } from "../../components/ReasonDialog" ;
2124import { AutoRefreshButton } from "../../components/RefreshButton" ;
2225import { useApiClient } from "../../hooks/useApiClient" ;
2326import { useQueryState } from "../../hooks/useQueryState" ;
2427import { useRefreshInterval } from "../../hooks/useRefreshInterval" ;
2528import { ExecutionsList } from "./ExecutionsList" ;
2629import { HistoryTable } from "./HistoryTable" ;
2730import { RaiseEvent } from "./RaiseEvent" ;
28- import { Rewind } from "./Rewind" ;
2931import { State } from "./State" ;
30- import { Terminate } from "./Terminate" ;
3132
3233type RouteParams = {
3334 instanceId : string ;
3435 executionId : string ;
3536} ;
3637
37- type TabValue =
38- | "state"
39- | "history"
40- | "executions"
41- | "raise_event"
42- | "terminate"
43- | "rewind"
44- | "json" ;
38+ type TabValue = "state" | "history" | "executions" | "raise_event" | "json" ;
4539
4640export function Orchestration ( ) {
4741 const [ tab , setTab ] = useQueryState < TabValue > ( "tab" , "state" ) ;
@@ -91,29 +85,29 @@ export function Orchestration() {
9185 mutationFn : ( args ) => apiClient . purgeOrchestration ( ...args ) ,
9286 } ) ;
9387
94- const handlePurgeClick = useCallback ( ( ) => {
95- confirm ( {
88+ const handlePurgeClick = useCallback ( async ( ) => {
89+ const { confirmed } = await confirm ( {
9690 description :
9791 "This action is irreversible. Do you confirm the purge of this instance?" ,
98- } ) . then ( async ( ) => {
99- try {
100- await purgeMutation . mutateAsync ( [ instanceId ] ) ;
101- enqueueSnackbar ( "Instance purged" , {
102- variant : "success" ,
103- } ) ;
104- navigate ( `/orchestrations` ) ;
105- } catch ( error ) {
106- enqueueSnackbar ( String ( error ) , {
107- variant : "error" ,
108- persist : true ,
109- action : ( key ) => (
110- < Button color = "inherit" onClick = { ( ) => closeSnackbar ( key ) } >
111- Dismiss
112- </ Button >
113- ) ,
114- } ) ;
115- }
11692 } ) ;
93+ if ( ! confirmed ) return ;
94+ try {
95+ await purgeMutation . mutateAsync ( [ instanceId ] ) ;
96+ enqueueSnackbar ( "Instance purged" , {
97+ variant : "success" ,
98+ } ) ;
99+ navigate ( `/orchestrations` ) ;
100+ } catch ( error ) {
101+ enqueueSnackbar ( String ( error ) , {
102+ variant : "error" ,
103+ persist : true ,
104+ action : ( key ) => (
105+ < Button color = "inherit" onClick = { ( ) => closeSnackbar ( key ) } >
106+ Dismiss
107+ </ Button >
108+ ) ,
109+ } ) ;
110+ }
117111 } , [
118112 closeSnackbar ,
119113 confirm ,
@@ -123,6 +117,11 @@ export function Orchestration() {
123117 purgeMutation ,
124118 ] ) ;
125119
120+ const [ reasonDialog , setReasonDialog ] = useState < {
121+ action : string ;
122+ fn : ( reason : string ) => Promise < void > ;
123+ } | null > ( null ) ;
124+
126125 return (
127126 < div >
128127 < Box marginBottom = { 1 } >
@@ -154,20 +153,69 @@ export function Orchestration() {
154153 setRefreshInterval = { setRefreshInterval }
155154 />
156155 </ Box >
157- { apiClient . isAuthorized ( "OrchestrationsPurgeInstance" ) &&
158- stateQuery . isSuccess && (
159- < Box >
156+ { stateQuery . isSuccess && (
157+ < Stack direction = "row" spacing = { 1 } >
158+ { apiClient . isAuthorized ( "OrchestrationsTerminate" ) && (
159+ < Button
160+ variant = "outlined"
161+ startIcon = { < StopIcon /> }
162+ size = "small"
163+ onClick = { ( ) =>
164+ setReasonDialog ( {
165+ action : "Terminate" ,
166+ fn : async ( reason ) => {
167+ await apiClient . terminateOrchestration ( instanceId , {
168+ reason,
169+ } ) ;
170+ enqueueSnackbar ( "Termination requested" , {
171+ variant : "success" ,
172+ } ) ;
173+ stateQuery . refetch ( ) ;
174+ } ,
175+ } )
176+ }
177+ >
178+ Terminate
179+ </ Button >
180+ ) }
181+ { apiClient . hasFeature ( "Rewind" ) &&
182+ apiClient . isAuthorized ( "OrchestrationsRewind" ) && (
183+ < Button
184+ variant = "outlined"
185+ startIcon = { < ReplayIcon /> }
186+ size = "small"
187+ onClick = { ( ) =>
188+ setReasonDialog ( {
189+ action : "Rewind" ,
190+ fn : async ( reason ) => {
191+ await apiClient . rewindOrchestration ( instanceId , {
192+ reason,
193+ } ) ;
194+ enqueueSnackbar ( "Failures rewound" , {
195+ variant : "success" ,
196+ } ) ;
197+ stateQuery . refetch ( ) ;
198+ } ,
199+ } )
200+ }
201+ >
202+ Rewind
203+ </ Button >
204+ ) }
205+ { apiClient . isAuthorized ( "OrchestrationsPurgeInstance" ) && (
160206 < LoadingButton
161207 variant = "outlined"
208+ color = "error"
162209 startIcon = { < DeleteIcon /> }
163210 loading = { purgeMutation . isPending }
164211 onClick = { handlePurgeClick }
165212 size = "small"
166213 >
167214 Purge
168215 </ LoadingButton >
169- </ Box >
170- ) }
216+ ) }
217+ </ Stack >
218+ ) }
171219 </ Stack >
172220 < Box height = { 4 } marginTop = { 0.5 } marginBottom = { 0.5 } >
173221 { ( stateQuery . isFetching || historyQuery . isFetching ) && (
@@ -197,13 +245,6 @@ export function Orchestration() {
197245 { apiClient . isAuthorized ( "OrchestrationsRaiseEvent" ) && (
198246 < Tab value = "raise_event" label = "Raise Event" />
199247 ) }
200- { apiClient . isAuthorized ( "OrchestrationsTerminate" ) && (
201- < Tab value = "terminate" label = "Terminate" />
202- ) }
203- { apiClient . hasFeature ( "Rewind" ) &&
204- apiClient . isAuthorized ( "OrchestrationsRewind" ) && (
205- < Tab value = "rewind" label = "Rewind" />
206- ) }
207248 < Tab value = "json" label = "Json" />
208249 </ Tabs >
209250 { stateQuery . data ? (
@@ -237,25 +278,6 @@ export function Orchestration() {
237278 />
238279 </ Box >
239280 ) }
240- { apiClient . isAuthorized ( "OrchestrationsTerminate" ) &&
241- tab === "terminate" && (
242- < Box padding = { 2 } >
243- < Terminate
244- instanceId = { instanceId }
245- onTerminate = { stateQuery . refetch }
246- />
247- </ Box >
248- ) }
249- { apiClient . hasFeature ( "Rewind" ) &&
250- apiClient . isAuthorized ( "OrchestrationsRewind" ) &&
251- tab === "rewind" && (
252- < Box padding = { 2 } >
253- < Rewind
254- instanceId = { instanceId }
255- onRewind = { stateQuery . refetch }
256- />
257- </ Box >
258- ) }
259281 { tab === "json" && (
260282 < Box padding = { 2 } >
261283 < pre style = { { whiteSpace : "pre-wrap" , wordBreak : "break-all" } } >
@@ -270,6 +292,31 @@ export function Orchestration() {
270292 </ >
271293 ) : null }
272294 </ Paper >
295+ < ReasonDialog
296+ open = { reasonDialog !== null }
297+ title = { reasonDialog ?. action ?? "" }
298+ description = { `Provide a reason for the ${ reasonDialog ?. action . toLowerCase ( ) } .` }
299+ onClose = { ( ) => setReasonDialog ( null ) }
300+ onConfirm = { async ( reason ) => {
301+ const dialog = reasonDialog ;
302+ setReasonDialog ( null ) ;
303+ if ( dialog ) {
304+ try {
305+ await dialog . fn ( reason ) ;
306+ } catch ( error ) {
307+ enqueueSnackbar ( String ( error ) , {
308+ variant : "error" ,
309+ persist : true ,
310+ action : ( key ) => (
311+ < Button color = "inherit" onClick = { ( ) => closeSnackbar ( key ) } >
312+ Dismiss
313+ </ Button >
314+ ) ,
315+ } ) ;
316+ }
317+ }
318+ } }
319+ />
273320 </ div >
274321 ) ;
275322}
0 commit comments