@@ -10,7 +10,7 @@ import { typedjson, useTypedLoaderData } from "remix-typedjson";
10
10
import { z } from "zod" ;
11
11
import { InlineCode } from "~/components/code/InlineCode" ;
12
12
import { Button , LinkButton } from "~/components/primitives/Buttons" ;
13
- import { Callout } from "~/components/primitives/Callout" ;
13
+ import { Callout , variantClasses } from "~/components/primitives/Callout" ;
14
14
import { Checkbox } from "~/components/primitives/Checkbox" ;
15
15
import { Dialog , DialogContent , DialogHeader } from "~/components/primitives/Dialog" ;
16
16
import { Fieldset } from "~/components/primitives/Fieldset" ;
@@ -20,14 +20,17 @@ import { Hint } from "~/components/primitives/Hint";
20
20
import { Input } from "~/components/primitives/Input" ;
21
21
import { InputGroup } from "~/components/primitives/InputGroup" ;
22
22
import { Label } from "~/components/primitives/Label" ;
23
+ import { Paragraph } from "~/components/primitives/Paragraph" ;
23
24
import SegmentedControl from "~/components/primitives/SegmentedControl" ;
24
25
import { Select , SelectItem } from "~/components/primitives/Select" ;
26
+ import { InfoIconTooltip } from "~/components/primitives/Tooltip" ;
25
27
import { useOrganization } from "~/hooks/useOrganizations" ;
26
28
import { useProject } from "~/hooks/useProject" ;
27
29
import { redirectWithSuccessMessage } from "~/models/message.server" ;
28
30
import { findProjectBySlug } from "~/models/project.server" ;
29
31
import { NewAlertChannelPresenter } from "~/presenters/v3/NewAlertChannelPresenter.server" ;
30
32
import { requireUserId } from "~/services/session.server" ;
33
+ import { cn } from "~/utils/cn" ;
31
34
import { ProjectParamSchema , v3ProjectAlertsPath } from "~/utils/pathBuilder" ;
32
35
import {
33
36
CreateAlertChannelOptions ,
@@ -40,6 +43,10 @@ const FormSchema = z
40
43
. array ( z . enum ( [ "TASK_RUN_ATTEMPT" , "DEPLOYMENT_FAILURE" , "DEPLOYMENT_SUCCESS" ] ) )
41
44
. min ( 1 )
42
45
. or ( z . enum ( [ "TASK_RUN_ATTEMPT" , "DEPLOYMENT_FAILURE" , "DEPLOYMENT_SUCCESS" ] ) ) ,
46
+ environmentTypes : z
47
+ . array ( z . enum ( [ "STAGING" , "PRODUCTION" ] ) )
48
+ . min ( 1 )
49
+ . or ( z . enum ( [ "STAGING" , "PRODUCTION" ] ) ) ,
43
50
type : z . enum ( [ "WEBHOOK" , "SLACK" , "EMAIL" ] ) . default ( "EMAIL" ) ,
44
51
channelValue : z . string ( ) . nonempty ( ) ,
45
52
integrationId : z . string ( ) . optional ( ) ,
@@ -81,6 +88,9 @@ function formDataToCreateAlertChannelOptions(
81
88
alertTypes : Array . isArray ( formData . alertTypes )
82
89
? formData . alertTypes
83
90
: [ formData . alertTypes ] ,
91
+ environmentTypes : Array . isArray ( formData . environmentTypes )
92
+ ? formData . environmentTypes
93
+ : [ formData . environmentTypes ] ,
84
94
channel : {
85
95
type : "WEBHOOK" ,
86
96
url : formData . channelValue ,
@@ -93,6 +103,9 @@ function formDataToCreateAlertChannelOptions(
93
103
alertTypes : Array . isArray ( formData . alertTypes )
94
104
? formData . alertTypes
95
105
: [ formData . alertTypes ] ,
106
+ environmentTypes : Array . isArray ( formData . environmentTypes )
107
+ ? formData . environmentTypes
108
+ : [ formData . environmentTypes ] ,
96
109
channel : {
97
110
type : "EMAIL" ,
98
111
email : formData . channelValue ,
@@ -107,6 +120,9 @@ function formDataToCreateAlertChannelOptions(
107
120
alertTypes : Array . isArray ( formData . alertTypes )
108
121
? formData . alertTypes
109
122
: [ formData . alertTypes ] ,
123
+ environmentTypes : Array . isArray ( formData . environmentTypes )
124
+ ? formData . environmentTypes
125
+ : [ formData . environmentTypes ] ,
110
126
channel : {
111
127
type : "SLACK" ,
112
128
channelId,
@@ -193,20 +209,27 @@ export default function Page() {
193
209
const project = useProject ( ) ;
194
210
const [ currentAlertChannel , setCurrentAlertChannel ] = useState < string | null > ( option ?? "EMAIL" ) ;
195
211
212
+ const [ selectedSlackChannelValue , setSelectedSlackChannelValue ] = useState < string | undefined > ( ) ;
213
+
214
+ const selectedSlackChannel = slack . channels ?. find (
215
+ ( s ) => selectedSlackChannelValue === `${ s . id } /${ s . name } `
216
+ ) ;
217
+
196
218
const isLoading =
197
219
navigation . state !== "idle" &&
198
220
navigation . formMethod === "post" &&
199
221
navigation . formData ?. get ( "action" ) === "create" ;
200
222
201
- const [ form , { channelValue : channelValue , alertTypes, type, integrationId } ] = useForm ( {
202
- id : "create-alert" ,
203
- // TODO: type this
204
- lastSubmission : lastSubmission as any ,
205
- onValidate ( { formData } ) {
206
- return parse ( formData , { schema : FormSchema } ) ;
207
- } ,
208
- shouldRevalidate : "onSubmit" ,
209
- } ) ;
223
+ const [ form , { channelValue : channelValue , alertTypes, environmentTypes, type, integrationId } ] =
224
+ useForm ( {
225
+ id : "create-alert" ,
226
+ // TODO: type this
227
+ lastSubmission : lastSubmission as any ,
228
+ onValidate ( { formData } ) {
229
+ return parse ( formData , { schema : FormSchema } ) ;
230
+ } ,
231
+ shouldRevalidate : "onSubmit" ,
232
+ } ) ;
210
233
211
234
useEffect ( ( ) => {
212
235
setIsOpen ( true ) ;
@@ -271,6 +294,9 @@ export default function Page() {
271
294
dropdownIcon
272
295
variant = "tertiary/medium"
273
296
items = { slack . channels }
297
+ setValue = { ( value ) => {
298
+ typeof value === "string" && setSelectedSlackChannelValue ( value ) ;
299
+ } }
274
300
filter = { ( channel , search ) =>
275
301
channel . name ?. toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ?? false
276
302
}
@@ -290,10 +316,19 @@ export default function Page() {
290
316
</ >
291
317
) }
292
318
</ Select >
293
- < Hint className = "leading-relaxed" >
294
- If selecting a private channel, you will need to invite the bot to the channel
295
- using < InlineCode variant = "extra-small" > /invite @Trigger.dev</ InlineCode >
296
- </ Hint >
319
+ { selectedSlackChannel && selectedSlackChannel . is_private && (
320
+ < Callout
321
+ variant = "warning"
322
+ className = { cn ( "text-sm" , variantClasses . warning . textColor ) }
323
+ >
324
+ To receive alerts in the{ " " }
325
+ < InlineCode variant = "extra-small" > { selectedSlackChannel . name } </ InlineCode > { " " }
326
+ channel, you need to invite the @Trigger.dev Slack Bot. Go to the channel in
327
+ Slack and type:{ " " }
328
+ < InlineCode variant = "extra-small" > /invite @Trigger.dev</ InlineCode > .
329
+ </ Callout >
330
+ ) }
331
+
297
332
< FormError id = { channelValue . errorId } > { channelValue . error } </ FormError >
298
333
< input type = "hidden" name = "integrationId" value = { slack . integrationId } />
299
334
</ >
@@ -324,24 +359,28 @@ export default function Page() {
324
359
</ InputGroup >
325
360
) }
326
361
327
- < InputGroup fullWidth >
328
- < Label > Events</ Label >
329
-
330
- < Checkbox
331
- name = { alertTypes . name }
332
- id = "TASK_RUN_ATTEMPT"
333
- value = "TASK_RUN_ATTEMPT"
334
- variant = "simple/small"
335
- label = "Task run failure"
336
- defaultChecked
337
- />
362
+ < InputGroup >
363
+ < Label > Alert me when</ Label >
364
+
365
+ < div className = "flex items-center gap-1" >
366
+ < Checkbox
367
+ name = { alertTypes . name }
368
+ id = "TASK_RUN_ATTEMPT"
369
+ value = "TASK_RUN_ATTEMPT"
370
+ variant = "simple/small"
371
+ label = "Task run attempts fail"
372
+ defaultChecked
373
+ className = "pr-0"
374
+ />
375
+ < InfoIconTooltip content = "You'll receive an alert every time an attempt fails on a run." />
376
+ </ div >
338
377
339
378
< Checkbox
340
379
name = { alertTypes . name }
341
380
id = "DEPLOYMENT_FAILURE"
342
381
value = "DEPLOYMENT_FAILURE"
343
382
variant = "simple/small"
344
- label = "Deployment failure "
383
+ label = "Deployments fail "
345
384
defaultChecked
346
385
/>
347
386
@@ -350,13 +389,33 @@ export default function Page() {
350
389
id = "DEPLOYMENT_SUCCESS"
351
390
value = "DEPLOYMENT_SUCCESS"
352
391
variant = "simple/small"
353
- label = "Deployment success "
392
+ label = "Deployments succeed "
354
393
defaultChecked
355
394
/>
356
395
357
396
< FormError id = { alertTypes . errorId } > { alertTypes . error } </ FormError >
358
397
</ InputGroup >
398
+ < InputGroup >
399
+ < Label > Environments</ Label >
400
+ < Checkbox
401
+ name = { environmentTypes . name }
402
+ id = "PRODUCTION"
403
+ value = "PRODUCTION"
404
+ variant = "simple/small"
405
+ label = "PROD"
406
+ defaultChecked
407
+ />
408
+ < Checkbox
409
+ name = { environmentTypes . name }
410
+ id = "STAGING"
411
+ value = "STAGING"
412
+ variant = "simple/small"
413
+ label = "STAGING"
414
+ defaultChecked
415
+ />
359
416
417
+ < FormError id = { environmentTypes . errorId } > { environmentTypes . error } </ FormError >
418
+ </ InputGroup >
360
419
< FormError > { form . error } </ FormError >
361
420
< div className = "border-t border-grid-bright pt-3" >
362
421
< FormButtons
0 commit comments