@@ -54,7 +54,7 @@ import {
5454 TestTaskPresenter ,
5555} from "~/presenters/v3/TestTaskPresenter.server" ;
5656import { logger } from "~/services/logger.server" ;
57- import { requireUserId } from "~/services/session.server" ;
57+ import { requireUser } from "~/services/session.server" ;
5858import { cn } from "~/utils/cn" ;
5959import { docsPath , v3RunSpanPath , v3TaskParamsSchema , v3TestPath } from "~/utils/pathBuilder" ;
6060import { TestTaskService } from "~/v3/services/testTask.server" ;
@@ -75,22 +75,23 @@ import { DialogClose, DialogDescription } from "@radix-ui/react-dialog";
7575import { FormButtons } from "~/components/primitives/FormButtons" ;
7676import { $replica } from "~/db.server" ;
7777import { clickhouseClient } from "~/services/clickhouseInstance.server" ;
78+ import { RegionsPresenter , type Region } from "~/presenters/v3/RegionsPresenter.server" ;
7879
7980type FormAction = "create-template" | "delete-template" | "run-scheduled" | "run-standard" ;
8081
8182export const loader = async ( { request, params } : LoaderFunctionArgs ) => {
82- const userId = await requireUserId ( request ) ;
83+ const user = await requireUser ( request ) ;
8384 const { projectParam, organizationSlug, envParam, taskParam } = v3TaskParamsSchema . parse ( params ) ;
8485
85- const project = await findProjectBySlug ( organizationSlug , projectParam , userId ) ;
86+ const project = await findProjectBySlug ( organizationSlug , projectParam , user . id ) ;
8687 if ( ! project ) {
8788 throw new Response ( undefined , {
8889 status : 404 ,
8990 statusText : "Project not found" ,
9091 } ) ;
9192 }
9293
93- const environment = await findEnvironmentBySlug ( project . id , envParam , userId ) ;
94+ const environment = await findEnvironmentBySlug ( project . id , envParam , user . id ) ;
9495 if ( ! environment ) {
9596 throw new Response ( undefined , {
9697 status : 404 ,
@@ -100,14 +101,21 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
100101
101102 const presenter = new TestTaskPresenter ( $replica , clickhouseClient ) ;
102103 try {
103- const result = await presenter . call ( {
104- userId,
105- projectId : project . id ,
106- taskIdentifier : taskParam ,
107- environment : environment ,
108- } ) ;
109-
110- return typedjson ( result ) ;
104+ const [ result , regionsResult ] = await Promise . all ( [
105+ presenter . call ( {
106+ userId : user . id ,
107+ projectId : project . id ,
108+ taskIdentifier : taskParam ,
109+ environment : environment ,
110+ } ) ,
111+ new RegionsPresenter ( ) . call ( {
112+ userId : user . id ,
113+ projectSlug : projectParam ,
114+ isAdmin : user . admin || user . isImpersonating ,
115+ } ) ,
116+ ] ) ;
117+
118+ return typedjson ( { ...result , regions : regionsResult . regions } ) ;
111119 } catch ( error ) {
112120 return redirectWithErrorMessage (
113121 v3TestPath ( { slug : organizationSlug } , { slug : projectParam } , environment ) ,
@@ -118,15 +126,15 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
118126} ;
119127
120128export const action : ActionFunction = async ( { request, params } ) => {
121- const userId = await requireUserId ( request ) ;
129+ const user = await requireUser ( request ) ;
122130 const { organizationSlug, projectParam, envParam } = v3TaskParamsSchema . parse ( params ) ;
123131
124- const project = await findProjectBySlug ( organizationSlug , projectParam , userId ) ;
132+ const project = await findProjectBySlug ( organizationSlug , projectParam , user . id ) ;
125133 if ( ! project ) {
126134 return redirectBackWithErrorMessage ( request , "Project not found" ) ;
127135 }
128136
129- const environment = await findEnvironmentBySlug ( project . id , envParam , userId ) ;
137+ const environment = await findEnvironmentBySlug ( project . id , envParam , user . id ) ;
130138
131139 if ( ! environment ) {
132140 return redirectBackWithErrorMessage ( request , "Environment not found" ) ;
@@ -290,6 +298,7 @@ export default function Page() {
290298 templates = { result . taskRunTemplates }
291299 disableVersionSelection = { result . disableVersionSelection }
292300 allowArbitraryQueues = { result . allowArbitraryQueues }
301+ regions = { result . regions }
293302 />
294303 ) ;
295304 }
@@ -304,6 +313,7 @@ export default function Page() {
304313 possibleTimezones = { result . possibleTimezones }
305314 disableVersionSelection = { result . disableVersionSelection }
306315 allowArbitraryQueues = { result . allowArbitraryQueues }
316+ regions = { result . regions }
307317 />
308318 ) ;
309319 }
@@ -324,6 +334,7 @@ function StandardTaskForm({
324334 templates,
325335 disableVersionSelection,
326336 allowArbitraryQueues,
337+ regions,
327338} : {
328339 task : StandardTaskResult [ "task" ] ;
329340 queues : Required < StandardTaskResult > [ "queue" ] [ ] ;
@@ -332,6 +343,7 @@ function StandardTaskForm({
332343 templates : RunTemplate [ ] ;
333344 disableVersionSelection : boolean ;
334345 allowArbitraryQueues : boolean ;
346+ regions : Region [ ] ;
335347} ) {
336348 const environment = useEnvironment ( ) ;
337349 const { value, replace } = useSearchParams ( ) ;
@@ -373,6 +385,12 @@ function StandardTaskForm({
373385 ) ;
374386 const [ queueValue , setQueueValue ] = useState < string | undefined > ( lastRun ?. queue ) ;
375387 const [ machineValue , setMachineValue ] = useState < string | undefined > ( lastRun ?. machinePreset ) ;
388+ const isDev = environment . type === "DEVELOPMENT" ;
389+ const defaultRegion = regions . find ( ( r ) => r . isDefault ) ;
390+ const [ regionValue , setRegionValue ] = useState < string | undefined > (
391+ isDev ? undefined : defaultRegion ?. name
392+ ) ;
393+
376394 const [ maxAttemptsValue , setMaxAttemptsValue ] = useState < number | undefined > (
377395 lastRun ?. maxAttempts
378396 ) ;
@@ -381,6 +399,12 @@ function StandardTaskForm({
381399 ) ;
382400 const [ tagsValue , setTagsValue ] = useState < string [ ] > ( lastRun ?. runTags ?? [ ] ) ;
383401
402+ const regionItems = regions . map ( ( r ) => ( {
403+ value : r . name ,
404+ label : r . description ? `${ r . name } — ${ r . description } ` : r . name ,
405+ isDefault : r . isDefault ,
406+ } ) ) ;
407+
384408 const queueItems = queues . map ( ( q ) => ( {
385409 value : q . type === "task" ? `task/${ q . name } ` : q . name ,
386410 label : q . name ,
@@ -409,6 +433,7 @@ function StandardTaskForm({
409433 tags,
410434 version,
411435 machine,
436+ region,
412437 prioritySeconds,
413438 } ,
414439 ] = useForm ( {
@@ -580,6 +605,45 @@ function StandardTaskForm({
580605 ) }
581606 < FormError id = { version . errorId } > { version . error } </ FormError >
582607 </ InputGroup >
608+ { regionItems . length > 1 && (
609+ < InputGroup >
610+ < Label htmlFor = { region . id } variant = "small" >
611+ Region
612+ </ Label >
613+ { /* Our Select primitive uses Ariakit under the hood, which treats
614+ value={undefined} as uncontrolled, keeping stale internal state when
615+ switching environments. The key forces a remount so it reinitializes
616+ with the correct defaultValue. */ }
617+ < Select
618+ key = { `region-${ environment . id } ` }
619+ { ...conform . select ( region ) }
620+ variant = "tertiary/small"
621+ placeholder = { isDev ? "–" : undefined }
622+ dropdownIcon
623+ items = { regionItems }
624+ defaultValue = { isDev ? undefined : defaultRegion ?. name }
625+ value = { isDev ? undefined : regionValue }
626+ setValue = { isDev ? undefined : ( e ) => {
627+ if ( Array . isArray ( e ) ) return ;
628+ setRegionValue ( e ) ;
629+ } }
630+ disabled = { isDev }
631+ >
632+ { regionItems . map ( ( r ) => (
633+ < SelectItem key = { r . value } value = { r . value } >
634+ { r . label }
635+ { r . isDefault ? " (default)" : "" }
636+ </ SelectItem >
637+ ) ) }
638+ </ Select >
639+ { isDev ? (
640+ < Hint > Region is not available in the development environment.</ Hint >
641+ ) : (
642+ < Hint > Overrides the region for this run.</ Hint >
643+ ) }
644+ < FormError id = { region . errorId } > { region . error } </ FormError >
645+ </ InputGroup >
646+ ) }
583647 < InputGroup >
584648 < Label htmlFor = { queue . id } variant = "small" >
585649 Queue
@@ -803,6 +867,7 @@ function ScheduledTaskForm({
803867 templates,
804868 disableVersionSelection,
805869 allowArbitraryQueues,
870+ regions,
806871} : {
807872 task : ScheduledTaskResult [ "task" ] ;
808873 runs : ScheduledRun [ ] ;
@@ -812,6 +877,7 @@ function ScheduledTaskForm({
812877 templates : RunTemplate [ ] ;
813878 disableVersionSelection : boolean ;
814879 allowArbitraryQueues : boolean ;
880+ regions : Region [ ] ;
815881} ) {
816882 const environment = useEnvironment ( ) ;
817883
@@ -833,6 +899,12 @@ function ScheduledTaskForm({
833899 ) ;
834900 const [ queueValue , setQueueValue ] = useState < string | undefined > ( lastRun ?. queue ) ;
835901 const [ machineValue , setMachineValue ] = useState < string | undefined > ( lastRun ?. machinePreset ) ;
902+ const isDev = environment . type === "DEVELOPMENT" ;
903+ const defaultRegion = regions . find ( ( r ) => r . isDefault ) ;
904+ const [ regionValue , setRegionValue ] = useState < string | undefined > (
905+ isDev ? undefined : defaultRegion ?. name
906+ ) ;
907+
836908 const [ maxAttemptsValue , setMaxAttemptsValue ] = useState < number | undefined > (
837909 lastRun ?. maxAttempts
838910 ) ;
@@ -843,6 +915,12 @@ function ScheduledTaskForm({
843915
844916 const [ showTemplateCreatedSuccessMessage , setShowTemplateCreatedSuccessMessage ] = useState ( false ) ;
845917
918+ const regionItems = regions . map ( ( r ) => ( {
919+ value : r . name ,
920+ label : r . description ? `${ r . name } — ${ r . description } ` : r . name ,
921+ isDefault : r . isDefault ,
922+ } ) ) ;
923+
846924 const queueItems = queues . map ( ( q ) => ( {
847925 value : q . type === "task" ? `task/${ q . name } ` : q . name ,
848926 label : q . name ,
@@ -879,6 +957,7 @@ function ScheduledTaskForm({
879957 tags,
880958 version,
881959 machine,
960+ region,
882961 prioritySeconds,
883962 } ,
884963 ] = useForm ( {
@@ -1101,6 +1180,45 @@ function ScheduledTaskForm({
11011180 ) }
11021181 < FormError id = { version . errorId } > { version . error } </ FormError >
11031182 </ InputGroup >
1183+ { regionItems . length > 1 && (
1184+ < InputGroup >
1185+ < Label htmlFor = { region . id } variant = "small" >
1186+ Region
1187+ </ Label >
1188+ { /* Our Select primitive uses Ariakit under the hood, which treats
1189+ value={undefined} as uncontrolled, keeping stale internal state when
1190+ switching environments. The key forces a remount so it reinitializes
1191+ with the correct defaultValue. */ }
1192+ < Select
1193+ key = { `region-${ environment . id } ` }
1194+ { ...conform . select ( region ) }
1195+ variant = "tertiary/small"
1196+ placeholder = { isDev ? "–" : undefined }
1197+ dropdownIcon
1198+ items = { regionItems }
1199+ defaultValue = { isDev ? undefined : defaultRegion ?. name }
1200+ value = { isDev ? undefined : regionValue }
1201+ setValue = { isDev ? undefined : ( e ) => {
1202+ if ( Array . isArray ( e ) ) return ;
1203+ setRegionValue ( e ) ;
1204+ } }
1205+ disabled = { isDev }
1206+ >
1207+ { regionItems . map ( ( r ) => (
1208+ < SelectItem key = { r . value } value = { r . value } >
1209+ { r . label }
1210+ { r . isDefault ? " (default)" : "" }
1211+ </ SelectItem >
1212+ ) ) }
1213+ </ Select >
1214+ { isDev ? (
1215+ < Hint > Region is not available in the development environment.</ Hint >
1216+ ) : (
1217+ < Hint > Overrides the region for this run.</ Hint >
1218+ ) }
1219+ < FormError id = { region . errorId } > { region . error } </ FormError >
1220+ </ InputGroup >
1221+ ) }
11041222 < InputGroup >
11051223 < Label htmlFor = { queue . id } variant = "small" >
11061224 Queue
0 commit comments