Skip to content

Commit

Permalink
Merge pull request #599 from ianmcorvidae/instant-launch-badges
Browse files Browse the repository at this point in the history
Display information about the app and any files/folders in the instant launch dialog
  • Loading branch information
ianmcorvidae authored Oct 17, 2024
2 parents 70aa6e0 + fdee800 commit eb897a3
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 36 deletions.
2 changes: 2 additions & 0 deletions public/static/locales/en/instantlaunches.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
73 changes: 52 additions & 21 deletions src/components/instantlaunches/InstantLaunchButtonWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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";

Expand All @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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)}
<InstantLaunchSubmissionDialog open={open} />
<InstantLaunchSubmissionDialog
open={open}
appInfo={appInfo}
resource={resource}
/>
<SignInDialog
open={signInDlgOpen}
handleClose={() => setSignInDlgOpen(false)}
Expand Down
16 changes: 15 additions & 1 deletion src/components/instantlaunches/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -57,6 +61,16 @@ export const InstantLaunchSubmissionDialog = ({ open }) => {
</DialogTitle>

<DialogContent>
{appInfo && (
<Typography variant="body1">
{resource
? t("launchingAppWithData", {
appName: appInfo.name,
path: resource.path,
})
: t("launchingApp", { appName: appInfo.name })}
</Typography>
)}
<div
className={classes.progress}
id={buildID(baseID, ids.PROGRESS)}
Expand Down
45 changes: 31 additions & 14 deletions src/serviceFacades/instantlaunches.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,24 @@ export const deleteInstantLaunchHandler = async (id) => {
);
};

/**
* 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.
*
Expand All @@ -337,39 +355,38 @@ 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) => {
resolve(instantLaunch);
});
}

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) => {
Expand Down

0 comments on commit eb897a3

Please sign in to comment.