11<script setup lang="ts">
2+ import { faSquare } from " @fortawesome/free-solid-svg-icons" ;
23import { FontAwesomeIcon } from " @fortawesome/vue-fontawesome" ;
3- import { computed } from " vue" ;
4+ import { computed , ref } from " vue" ;
45
5- import type { JobBaseModel } from " @/api/jobs" ;
6+ import { type JobBaseModel , NON_TERMINAL_STATES } from " @/api/jobs" ;
7+ import { useToast } from " @/composables/toast" ;
68import { getHeaderClass , iconClasses } from " @/composables/useInvocationGraph" ;
79
10+ import GButton from " ../BaseComponents/GButton.vue" ;
11+
812const props = defineProps <{
913 job: JobBaseModel ;
1014}>();
@@ -17,11 +21,110 @@ const badgeClass = computed(() => {
1721});
1822
1923const stateIcon = computed (() => iconClasses [props .job .state ] || null );
24+
25+ const Toast = useToast ();
26+
27+ /** Whether the current user owns the job (can stop it) */
28+ const userOwnsJob = computed (() => {
29+ return true ; // TODO: For testing i have true, will have proper implementation later
30+ });
31+
32+ /** Whether to render this button
33+ * 1. If the job is not the user's own
34+ * 2. Job is already in a terminal state
35+ */
36+ const canStopJob = computed (() => ! userOwnsJob .value || NON_TERMINAL_STATES .includes (props .job .state ));
37+
38+ /** Whether the stop job action is currently being performed */
39+ const stopping = ref (false );
40+
41+ async function stopJob() {
42+ stopping .value = true ;
43+ try {
44+ // TODO: To be implemented: Call the API to stop the job, and handle any errors that may occur.
45+ // Ideally, we would want a job object returned from the API after stopping,
46+ // and we would want to update the job state in the UI accordingly. For now, I am just
47+ // using a Toast.
48+
49+ Toast .success (" Job stopped successfully." );
50+
51+ // TODO: Handle job state returned by the API
52+ } catch (error ) {
53+ // TODO: Handle string errorMessageAsString, just casting to string for now
54+ Toast .error (error as string , " Failed to stop the job." );
55+ } finally {
56+ stopping .value = false ;
57+ }
58+ }
2059 </script >
2160
2261<template >
23- <span class =" rounded px-2 py-1" :class =" badgeClass" >
24- <FontAwesomeIcon v-if =" stateIcon" :icon =" stateIcon.icon" :spin =" stateIcon.spin" />
62+ <span class =" job-state-badge rounded px-2 py-1" :class =" badgeClass" >
63+ <FontAwesomeIcon
64+ v-if =" stateIcon"
65+ :icon =" stateIcon.icon"
66+ :spin =" stateIcon.spin"
67+ :class =" { 'hoverable-icon': canStopJob }" />
68+ <GButton
69+ v-if =" canStopJob"
70+ transparent
71+ inline
72+ icon-only
73+ pill
74+ size =" small"
75+ class =" stop-job-btn"
76+ title =" Stop the execution of this job"
77+ :disabled =" stopping"
78+ disabled-title =" Stopping job..."
79+ @click =" stopJob" >
80+ <FontAwesomeIcon :icon =" faSquare" />
81+ </GButton >
2582 {{ props.job.state }}
2683 </span >
2784</template >
85+
86+ <style lang="scss" scoped>
87+ // If the job is in a stoppable state, we want to make the default state
88+ // icon hide on hover, and show the stop button instead.
89+ .job-state-badge {
90+ position : relative ;
91+
92+ & :hover {
93+ .hoverable-icon {
94+ visibility : hidden ;
95+ }
96+ .stop-job-btn {
97+ display : inline-block ;
98+ }
99+ }
100+ }
101+
102+ .stop-job-btn {
103+ display : none ;
104+ position : absolute ;
105+ left : 0.5rem ;
106+ top : 50% ;
107+ transform : translateY (-50% );
108+ height : 1em ;
109+ width : 1em ;
110+ line-height : 1 ;
111+ padding : 0 !important ;
112+ min-height : unset !important ;
113+ }
114+
115+ .hoverable-icon {
116+ display : inline-block ;
117+ height : 1em ;
118+ width : 1em ;
119+ line-height : 1 ;
120+ }
121+
122+ // When canStopJob is true, ensure only one shows at a time
123+ .job-state-badge :not (:hover ) .stop-job-btn {
124+ display : none ;
125+ }
126+
127+ .job-state-badge :hover .hoverable-icon {
128+ visibility : hidden ;
129+ }
130+ </style >
0 commit comments