11import { useMutation , useQueryClient } from "@tanstack/react-query" ;
22import { useNavigate } from "@tanstack/react-router" ;
33import { AlertCircle , CheckCircle , Loader2 , SendHorizonal } from "lucide-react" ;
4- import { useCallback , useRef , useState } from "react" ;
4+ import { useCallback , useMemo , useRef , useState } from "react" ;
55
6+ import type { TaskSpecOutput } from "@/api/types.gen" ;
67import { useAwaitAuthorization } from "@/components/shared/Authentication/useAwaitAuthorization" ;
78import { useBetaFlagValue } from "@/components/shared/Settings/useBetaFlags" ;
89import { Button } from "@/components/ui/button" ;
9- import { SidebarMenuButton } from "@/components/ui/sidebar" ;
10+ import { Icon } from "@/components/ui/icon" ;
11+ import { InlineStack } from "@/components/ui/layout" ;
1012import useCooldownTimer from "@/hooks/useCooldownTimer" ;
1113import useToastNotification from "@/hooks/useToastNotification" ;
1214import { cn } from "@/lib/utils" ;
@@ -18,6 +20,9 @@ import { submitPipelineRun } from "@/utils/submitPipeline";
1820
1921import { isAuthorizationRequired } from "../../Authentication/helpers" ;
2022import { useAuthLocalStorage } from "../../Authentication/useAuthLocalStorage" ;
23+ import TooltipButton from "../../Buttons/TooltipButton" ;
24+ import { SubmitTaskArgumentsDialog } from "./components/SubmitTaskArgumentsDialog" ;
25+
2126interface OasisSubmitterProps {
2227 componentSpec ?: ComponentSpec ;
2328 onSubmitComplete ?: ( ) => void ;
@@ -36,10 +41,12 @@ function useSubmitPipeline() {
3641 return useMutation ( {
3742 mutationFn : async ( {
3843 componentSpec,
44+ taskArguments,
3945 onSuccess,
4046 onError,
4147 } : {
4248 componentSpec : ComponentSpec ;
49+ taskArguments ?: TaskSpecOutput [ "arguments" ] ;
4350 onSuccess : ( data : PipelineRun ) => void ;
4451 onError : ( error : Error | string ) => void ;
4552 } ) => {
@@ -54,6 +61,7 @@ function useSubmitPipeline() {
5461 return new Promise < PipelineRun > ( ( resolve , reject ) => {
5562 submitPipelineRun ( componentSpec , backendUrl , {
5663 authorizationToken : authorizationToken . current ,
64+ taskArguments,
5765 onSuccess : ( data ) => {
5866 resolve ( data ) ;
5967 onSuccess ( data ) ;
@@ -84,6 +92,7 @@ const OasisSubmitter = ({
8492 const isAutoRedirect = useBetaFlagValue ( "redirect-on-new-pipeline-run" ) ;
8593
8694 const [ submitSuccess , setSubmitSuccess ] = useState < boolean | null > ( null ) ;
95+ const [ isArgumentsDialogOpen , setIsArgumentsDialogOpen ] = useState ( false ) ;
8796 const { cooldownTime, setCooldownTime } = useCooldownTimer ( 0 ) ;
8897 const notify = useToastNotification ( ) ;
8998 const navigate = useNavigate ( ) ;
@@ -159,29 +168,50 @@ const OasisSubmitter = ({
159168 [ handleError , setCooldownTime ] ,
160169 ) ;
161170
162- const handleSubmit = useCallback ( async ( ) => {
163- if ( ! componentSpec ) {
164- handleError ( "No pipeline to submit" ) ;
165- return ;
166- }
171+ const handleSubmit = useCallback (
172+ async ( taskArguments ?: Record < string , string > ) => {
173+ if ( ! componentSpec ) {
174+ handleError ( "No pipeline to submit" ) ;
175+ return ;
176+ }
167177
168- if ( ! isComponentTreeValid ) {
169- handleError (
170- `Pipeline validation failed. Refer to details panel for more info.` ,
171- ) ;
172- return ;
173- }
178+ if ( ! isComponentTreeValid ) {
179+ handleError (
180+ `Pipeline validation failed. Refer to details panel for more info.` ,
181+ ) ;
182+ return ;
183+ }
174184
175- setSubmitSuccess ( null ) ;
176- submit ( { componentSpec, onSuccess, onError } ) ;
177- } , [
178- handleError ,
179- submit ,
180- componentSpec ,
181- isComponentTreeValid ,
182- onSuccess ,
183- onError ,
184- ] ) ;
185+ setSubmitSuccess ( null ) ;
186+ submit ( {
187+ componentSpec,
188+ taskArguments,
189+ onSuccess,
190+ onError,
191+ } ) ;
192+ } ,
193+ [
194+ handleError ,
195+ submit ,
196+ componentSpec ,
197+ isComponentTreeValid ,
198+ onSuccess ,
199+ onError ,
200+ ] ,
201+ ) ;
202+
203+ const handleSubmitWithArguments = useCallback (
204+ ( args : Record < string , string > ) => {
205+ setIsArgumentsDialogOpen ( false ) ;
206+ handleSubmit ( args ) ;
207+ } ,
208+ [ handleSubmit ] ,
209+ ) ;
210+
211+ const hasConfigurableInputs = useMemo (
212+ ( ) => ( componentSpec ?. inputs ?. length ?? 0 ) > 0 ,
213+ [ componentSpec ?. inputs ] ,
214+ ) ;
185215
186216 const getButtonText = ( ) => {
187217 if ( cooldownTime > 0 ) {
@@ -203,6 +233,8 @@ const OasisSubmitter = ({
203233 ( "graph" in componentSpec . implementation &&
204234 Object . keys ( componentSpec . implementation . graph . tasks ) . length === 0 ) ;
205235
236+ const isArgumentsButtonVisible = hasConfigurableInputs && ! isButtonDisabled ;
237+
206238 const getButtonIcon = ( ) => {
207239 if ( isSubmitting ) {
208240 return < Loader2 className = "animate-spin" /> ;
@@ -217,42 +249,60 @@ const OasisSubmitter = ({
217249 } ;
218250
219251 return (
220- < SidebarMenuButton
221- asChild
222- tooltip = "Submit Run"
223- forceTooltip
224- tooltipPosition = "right"
225- >
226- < Button
227- onClick = { handleSubmit }
228- className = "w-full justify-start"
229- variant = "ghost"
230- disabled = { isButtonDisabled || ! available }
231- >
232- { getButtonIcon ( ) }
233- < span className = "font-normal text-xs" > { getButtonText ( ) } </ span >
234- { ! isComponentTreeValid && (
235- < div
236- className = { cn (
237- "text-xs font-light -ml-1" ,
238- configured ? "text-red-700" : "text-yellow-700" ,
239- ) }
240- >
241- (has validation issues)
242- </ div >
243- ) }
244- { ! available && (
245- < div
246- className = { cn (
247- "text-xs font-light -ml-1" ,
248- configured ? "text-red-700" : "text-yellow-700" ,
249- ) }
252+ < >
253+ < InlineStack align = "space-between" className = "pr-2.5" >
254+ < Button
255+ onClick = { ( ) => handleSubmit ( ) }
256+ className = "flex-1 justify-start"
257+ variant = "ghost"
258+ disabled = { isButtonDisabled || ! available }
259+ >
260+ { getButtonIcon ( ) }
261+ < span className = "font-normal text-xs" > { getButtonText ( ) } </ span >
262+ { ! isComponentTreeValid && (
263+ < div
264+ className = { cn (
265+ "text-xs font-light -ml-1" ,
266+ configured ? "text-red-700" : "text-yellow-700" ,
267+ ) }
268+ >
269+ (has validation issues)
270+ </ div >
271+ ) }
272+ { ! available && (
273+ < div
274+ className = { cn (
275+ "text-xs font-light -ml-1" ,
276+ configured ? "text-red-700" : "text-yellow-700" ,
277+ ) }
278+ >
279+ { `(backend ${ configured ? "unavailable" : "unconfigured" } )` }
280+ </ div >
281+ ) }
282+ </ Button >
283+ { isArgumentsButtonVisible && (
284+ < TooltipButton
285+ tooltip = "Submit run with arguments"
286+ variant = "ghost"
287+ size = "icon"
288+ data-testid = "run-with-arguments-button"
289+ onClick = { ( ) => setIsArgumentsDialogOpen ( true ) }
290+ disabled = { ! available }
250291 >
251- { `(backend ${ configured ? "unavailable" : "unconfigured" } )` }
252- </ div >
292+ < Icon name = "Split" className = "rotate-90" />
293+ </ TooltipButton >
253294 ) }
254- </ Button >
255- </ SidebarMenuButton >
295+ </ InlineStack >
296+
297+ { componentSpec && (
298+ < SubmitTaskArgumentsDialog
299+ open = { isArgumentsDialogOpen }
300+ onCancel = { ( ) => setIsArgumentsDialogOpen ( false ) }
301+ onConfirm = { handleSubmitWithArguments }
302+ componentSpec = { componentSpec }
303+ />
304+ ) }
305+ </ >
256306 ) ;
257307} ;
258308
0 commit comments