diff --git a/public/static/locales/en/instantlaunches.json b/public/static/locales/en/instantlaunches.json index 36691a214..8075ca0fe 100644 --- a/public/static/locales/en/instantlaunches.json +++ b/public/static/locales/en/instantlaunches.json @@ -32,6 +32,8 @@ "instantLaunch": "Instant Launch", "instantLaunchCreationError": "Error creating Instant Launch", "instantLaunchError": "There was a problem launching your analysis.", + "launchingAppWithData": "Launching app: {{appName}} with data {{path}}", + "launchingApp": "Launching app: {{appName}}", "listDescription": "Create new Instant Launches, add them to the dashboard, or delete them entirely.", "mappingDescription": "Control which files in the data listing are associated with Instant Launches based on their filenames.", "name": "Name", diff --git a/src/components/instantlaunches/InstantLaunchButtonWrapper.js b/src/components/instantlaunches/InstantLaunchButtonWrapper.js index 5016dde90..3085e0f3e 100644 --- a/src/components/instantlaunches/InstantLaunchButtonWrapper.js +++ b/src/components/instantlaunches/InstantLaunchButtonWrapper.js @@ -10,7 +10,7 @@ */ import React, { useEffect, useCallback } from "react"; -import { useMutation } from "react-query"; +import { useMutation, useQuery } from "react-query"; import { useRouter } from "next/router"; @@ -29,7 +29,14 @@ import { useUserProfile } from "contexts/userProfile"; import { useBootstrapInfo } from "contexts/bootstrap"; import ids from "./ids"; import { InstantLaunchSubmissionDialog } from "./index"; -import { instantlyLaunch } from "serviceFacades/instantlaunches"; +import { + instantlyLaunch, + extractLaunchId, +} from "serviceFacades/instantlaunches"; +import { + SAVED_LAUNCH_APP_INFO, + getAppInfo, +} from "serviceFacades/savedLaunches"; import { useTranslation } from "i18n"; import { ERROR_CODES } from "components/error/errorCode"; @@ -48,6 +55,8 @@ function InstantLaunchButtonWrapper(props) { const [open, setOpen] = React.useState(false); const [hasLaunched, setHasLaunched] = React.useState(false); + const [appInfo, setAppInfo] = React.useState(null); + const [signInDlgOpen, setSignInDlgOpen] = React.useState(false); const [accessRequestDialogOpen, setAccessRequestDialogOpen] = React.useState(false); @@ -72,6 +81,19 @@ function InstantLaunchButtonWrapper(props) { } }, [ilUrl, landingUrl, autolaunch, router]); + const launchId = extractLaunchId(instantLaunch); + + const { isFetching: savedLaunchLoading } = useQuery({ + queryKey: [SAVED_LAUNCH_APP_INFO, { launchId }], + queryFn: () => getAppInfo({ launchId }), + enabled: open, + onSuccess: setAppInfo, + onError: (err) => { + setOpen(false); + showErrorAnnouncer(err.message, err); + }, + }); + const { mutate: launch } = useMutation(instantlyLaunch, { onSuccess: (listing) => { if (listing.analyses.length > 0) { @@ -119,45 +141,54 @@ function InstantLaunchButtonWrapper(props) { const preferences = bootstrapInfo?.preferences; const userId = userProfile?.id; + useEffect(() => { + if (open && !savedLaunchLoading) { + launch({ + instantLaunch, + resource, + output_dir, + preferences, + appInfo, + }); + } + }, [ + open, + savedLaunchLoading, + instantLaunch, + launch, + resource, + output_dir, + preferences, + appInfo, + ]); + const onClick = useCallback(() => { if (userId) { if (computeLimitExceeded) { showErrorAnnouncer(t("computeLimitExceededMsg")); } else { setOpen(true); - launch({ - instantLaunch, - resource, - output_dir, - preferences, - }); } } else { setSignInDlgOpen(true); } - }, [ - preferences, - computeLimitExceeded, - instantLaunch, - launch, - output_dir, - resource, - showErrorAnnouncer, - t, - userId, - ]); + }, [computeLimitExceeded, showErrorAnnouncer, t, userId]); useEffect(() => { if (autolaunch && !hasLaunched) { onClick(); setHasLaunched(true); } - }, [autolaunch, onClick, hasLaunched, setHasLaunched]); + }, [autolaunch, onClick, hasLaunched]); return ( <> {!autolaunch && render && render(onClick)} - + setSignInDlgOpen(false)} diff --git a/src/components/instantlaunches/index.js b/src/components/instantlaunches/index.js index 3573839ee..58f498af4 100644 --- a/src/components/instantlaunches/index.js +++ b/src/components/instantlaunches/index.js @@ -37,7 +37,11 @@ const useStyles = makeStyles()((theme) => ({ * @param {Object} props - The component props. * @param {boolean} props.open - Whether or not the dialog is open. */ -export const InstantLaunchSubmissionDialog = ({ open }) => { +export const InstantLaunchSubmissionDialog = ({ + open, + appInfo, + resource = null, +}) => { const baseID = buildID(ids.BASE, ids.SUBMISSION, ids.DIALOG); const { t } = useTranslation("instantlaunches"); const { classes } = useStyles(); @@ -57,6 +61,16 @@ export const InstantLaunchSubmissionDialog = ({ open }) => { + {appInfo && ( + + {resource + ? t("launchingAppWithData", { + appName: appInfo.name, + path: resource.path, + }) + : t("launchingApp", { appName: appInfo.name })} + + )}
{ ); }; +/** + * Utility function to extract a saved launch ID from an instant launch in either format. + * + * @param {Object} instantLaunch - the instant launch object to pull the value out of + */ +export const extractLaunchId = (instantLaunch) => { + if (instantLaunch.hasOwnProperty("default")) { + // The data window logic. + // The saved launch ID is buried in the "default" map of the object passed in + // from the data window. + return instantLaunch.default["quick_launch_id"]; + } else { + // The dashboard logic. + // The saved launch ID is a top-level property of the object passed in. + return instantLaunch.quick_launch_id; + } +}; + /** * Event handler for the button that performs the instant launch. * @@ -337,27 +355,21 @@ export const instantlyLaunch = ({ resource, output_dir, preferences, + appInfo = null, }) => { - let savedLaunchId; // The saved launch ID, used to get app information. + let savedLaunchId = extractLaunchId(instantLaunch); // The saved launch ID, used to get app information. + let savedLaunchPromise; // The promise used to get saved launch information. + let appInfoPromise; // The promise used to get app info for the saved launch. // The format of the instantLaunch object passed in by the data window is a bit different // than the format passed in by the dashboard, so a bit of normalization needs to take // place. if (instantLaunch.hasOwnProperty("default")) { - // The data window logic. - // The saved launch ID is buried in the "default" map of the object passed in - // from the data window. - savedLaunchId = instantLaunch.default["quick_launch_id"]; - // We'll need to get the saved launch info from the API since it contains the // submission, which isn't provided from the data window. savedLaunchPromise = getSavedLaunch(savedLaunchId); } else { - // The dashboard logic. - // The saved launch ID is a top-level property of the object passed in. - savedLaunchId = instantLaunch.quick_launch_id; - // Wrap the instant launch object in a promise so we don't have to branch logic // farther down. savedLaunchPromise = new Promise((resolve, reject) => { @@ -365,11 +377,16 @@ export const instantlyLaunch = ({ }); } + if (appInfo) { + appInfoPromise = new Promise((resolve, reject) => { + resolve(appInfo); + }); + } else { + appInfoPromise = getAppInfo({ launchId: savedLaunchId }); + } + // Contains the Promises that resolve to the data needed to perform a job submission. - const promiseList = [ - savedLaunchPromise, - getAppInfo({ launchId: savedLaunchId }), - ]; + const promiseList = [savedLaunchPromise, appInfoPromise]; return Promise.all(promiseList) .then((values) => {