1
1
import { StopIcon } from "@heroicons/react/24/outline" ;
2
- import { CheckIcon } from "@heroicons/react/24/solid" ;
2
+ import { BeakerIcon , BookOpenIcon , CheckIcon } from "@heroicons/react/24/solid" ;
3
3
import { User } from "@trigger.dev/database" ;
4
4
import { useOrganization } from "~/hooks/useOrganizations" ;
5
5
import { useProject } from "~/hooks/useProject" ;
6
- import { RunListItem } from "~/presenters/v3/RunListPresenter.server" ;
7
- import { v3RunPath } from "~/utils/pathBuilder" ;
6
+ import { RunListAppliedFilters , RunListItem } from "~/presenters/v3/RunListPresenter.server" ;
7
+ import { docsPath , v3RunPath , v3TestPath } from "~/utils/pathBuilder" ;
8
8
import { EnvironmentLabel } from "../../environments/EnvironmentLabel" ;
9
9
import { DateTime } from "../../primitives/DateTime" ;
10
10
import { Paragraph } from "../../primitives/Paragraph" ;
@@ -15,16 +15,24 @@ import {
15
15
TableBody ,
16
16
TableCell ,
17
17
TableCellChevron ,
18
+ TableCellMenu ,
18
19
TableHeader ,
19
20
TableHeaderCell ,
20
21
TableRow ,
21
22
} from "../../primitives/Table" ;
22
23
import { formatDuration } from "@trigger.dev/core/v3" ;
23
24
import { TaskRunStatusCombo } from "./TaskRunStatus" ;
25
+ import { useEnvironments } from "~/hooks/useEnvironments" ;
26
+ import { Button , LinkButton } from "~/components/primitives/Buttons" ;
27
+ import { StopCircleIcon } from "@heroicons/react/20/solid" ;
28
+ import { Dialog , DialogTrigger } from "~/components/primitives/Dialog" ;
29
+ import { CancelRunDialog } from "./CancelRunDialog" ;
30
+ import { useLocation } from "@remix-run/react" ;
24
31
25
32
type RunsTableProps = {
26
33
total : number ;
27
34
hasFilters : boolean ;
35
+ filters : RunListAppliedFilters ;
28
36
showJob ?: boolean ;
29
37
runs : RunListItem [ ] ;
30
38
isLoading ?: boolean ;
@@ -34,12 +42,14 @@ type RunsTableProps = {
34
42
export function TaskRunsTable ( {
35
43
total,
36
44
hasFilters,
45
+ filters,
37
46
runs,
38
47
isLoading = false ,
39
48
currentUser,
40
49
} : RunsTableProps ) {
41
50
const organization = useOrganization ( ) ;
42
51
const project = useProject ( ) ;
52
+ const location = useLocation ( ) ;
43
53
44
54
return (
45
55
< Table >
@@ -65,9 +75,7 @@ export function TaskRunsTable({
65
75
{ ! isLoading && < NoRuns title = "No runs found" /> }
66
76
</ TableBlankRow >
67
77
) : runs . length === 0 ? (
68
- < TableBlankRow colSpan = { 9 } >
69
- { ! isLoading && < NoRuns title = "No runs match your filters" /> }
70
- </ TableBlankRow >
78
+ < BlankState isLoading = { isLoading } filters = { filters } />
71
79
) : (
72
80
runs . map ( ( run ) => {
73
81
const path = v3RunPath ( organization , project , run ) ;
@@ -102,7 +110,23 @@ export function TaskRunsTable({
102
110
< TableCell to = { path } >
103
111
{ run . createdAt ? < DateTime date = { run . createdAt } /> : "–" }
104
112
</ TableCell >
105
- < TableCellChevron to = { path } isSticky />
113
+ { run . isCancellable ? (
114
+ < TableCellMenu isSticky >
115
+ < Dialog >
116
+ < DialogTrigger asChild >
117
+ < Button variant = "small-menu-item" LeadingIcon = { StopCircleIcon } >
118
+ Cancel run
119
+ </ Button >
120
+ </ DialogTrigger >
121
+ < CancelRunDialog
122
+ runFriendlyId = { run . friendlyId }
123
+ redirectPath = { `${ location . pathname } ${ location . search } ` }
124
+ />
125
+ </ Dialog >
126
+ </ TableCellMenu >
127
+ ) : (
128
+ < TableCell to = { path } > { "" } </ TableCell >
129
+ ) }
106
130
</ TableRow >
107
131
) ;
108
132
} )
@@ -127,3 +151,62 @@ function NoRuns({ title }: { title: string }) {
127
151
</ div >
128
152
) ;
129
153
}
154
+
155
+ function BlankState ( { isLoading, filters } : Pick < RunsTableProps , "isLoading" | "filters" > ) {
156
+ const organization = useOrganization ( ) ;
157
+ const project = useProject ( ) ;
158
+ const envs = useEnvironments ( ) ;
159
+ if ( isLoading ) return < TableBlankRow colSpan = { 9 } > </ TableBlankRow > ;
160
+
161
+ const { environments, tasks, from, to, ...otherFilters } = filters ;
162
+
163
+ if (
164
+ filters . environments . length === 1 &&
165
+ filters . tasks . length === 1 &&
166
+ filters . from === undefined &&
167
+ filters . to === undefined &&
168
+ Object . values ( otherFilters ) . every ( ( filterArray ) => filterArray . length === 0 )
169
+ ) {
170
+ const environment = envs ?. find ( ( env ) => env . id === filters . environments [ 0 ] ) ;
171
+ return (
172
+ < TableBlankRow colSpan = { 9 } >
173
+ < div className = "py-14" >
174
+ < Paragraph className = "w-auto" variant = "base/bright" spacing >
175
+ There are no runs for { filters . tasks [ 0 ] }
176
+ { environment ? (
177
+ < >
178
+ { " " }
179
+ in < EnvironmentLabel environment = { environment } size = "large" />
180
+ </ >
181
+ ) : null }
182
+ </ Paragraph >
183
+ < div className = "flex items-center justify-center gap-2" >
184
+ < LinkButton
185
+ to = { v3TestPath ( organization , project ) }
186
+ variant = "primary/small"
187
+ LeadingIcon = { BeakerIcon }
188
+ className = "inline-flex"
189
+ >
190
+ Create a test run
191
+ </ LinkButton >
192
+ < Paragraph variant = "small" > or</ Paragraph >
193
+ < LinkButton
194
+ to = { docsPath ( "v3/triggering" ) }
195
+ variant = "primary/small"
196
+ LeadingIcon = { BookOpenIcon }
197
+ className = "inline-flex"
198
+ >
199
+ Triggering a task docs
200
+ </ LinkButton >
201
+ </ div >
202
+ </ div >
203
+ </ TableBlankRow >
204
+ ) ;
205
+ }
206
+
207
+ return (
208
+ < TableBlankRow colSpan = { 9 } >
209
+ < NoRuns title = "No runs match your filters" />
210
+ </ TableBlankRow >
211
+ ) ;
212
+ }
0 commit comments