diff --git a/public/static/locales/en/common.json b/public/static/locales/en/common.json index 14c8289c8..8c31ce83e 100644 --- a/public/static/locales/en/common.json +++ b/public/static/locales/en/common.json @@ -43,6 +43,7 @@ "help": "Help", "home": "Home", "instantLaunches": "Instant Launches", + "instantLaunch": "Instant Launch", "interactiveAnalysisUrl": "{{message}}. Access your running analysis here.", "intercomAriaLabel": "Chat with CyVerse support", "learnMore": "Learn More", diff --git a/src/components/instantlaunches/InstantLaunchButtonWrapper.js b/src/components/instantlaunches/InstantLaunchButtonWrapper.js index 423b09a8b..3886f7636 100644 --- a/src/components/instantlaunches/InstantLaunchButtonWrapper.js +++ b/src/components/instantlaunches/InstantLaunchButtonWrapper.js @@ -8,7 +8,7 @@ * The `onClick` function will handle launching the instant launch and * briefly displaying the Submission dialog to the user. */ -import React from "react"; +import React, { useEffect, useCallback } from "react"; import { useMutation } from "react-query"; @@ -36,12 +36,14 @@ function InstantLaunchButtonWrapper(props) { resource = {}, render, showErrorAnnouncer, + autolaunch, } = props; const output_dir = useDefaultOutputDir(); const [userProfile] = useUserProfile(); const [bootstrapInfo] = useBootstrapInfo(); const [open, setOpen] = React.useState(false); + const [hasLaunched, setHasLaunched] = React.useState(false); const [signInDlgOpen, setSignInDlgOpen] = React.useState(false); const [accessRequestDialogOpen, setAccessRequestDialogOpen] = React.useState(false); @@ -101,8 +103,11 @@ function InstantLaunchButtonWrapper(props) { }, }); - const onClick = () => { - if (userProfile?.id) { + const preferences = bootstrapInfo?.preferences; + const userId = userProfile?.id; + + const onClick = useCallback(() => { + if (userId) { if (computeLimitExceeded) { showErrorAnnouncer(t("computeLimitExceededMsg")); } else { @@ -111,17 +116,34 @@ function InstantLaunchButtonWrapper(props) { instantLaunch, resource, output_dir, - preferences: bootstrapInfo?.preferences, + preferences, }); } } else { setSignInDlgOpen(true); } - }; + }, [ + preferences, + computeLimitExceeded, + instantLaunch, + launch, + output_dir, + resource, + showErrorAnnouncer, + t, + userId, + ]); + + useEffect(() => { + if (autolaunch && !hasLaunched) { + onClick(); + setHasLaunched(true); + } + }, [autolaunch, onClick, hasLaunched, setHasLaunched]); return ( <> - {render(onClick)} + {!autolaunch && render && render(onClick)} { + const { id: instant_launch_id, showErrorAnnouncer } = props; + const [config] = useConfig(); + const [userProfile] = useUserProfile(); + const [computeLimitExceeded, setComputeLimitExceeded] = useState( + !!config?.subscriptions?.enforce + ); + const [errorDialogOpen, setErrorDialogOpen] = useState(false); + const { t } = useTranslation(["instantlaunches", "common"]); + + const { data, status, error } = useQuery( + [GET_INSTANT_LAUNCH_FULL_KEY, instant_launch_id], + () => getFullInstantLaunch(instant_launch_id) + ); + + const { isFetching: isFetchingUsageSummary } = useQuery({ + queryKey: [RESOURCE_USAGE_QUERY_KEY], + queryFn: getResourceUsageSummary, + enabled: !!config?.subscriptions?.enforce && !!userProfile?.id, + onSuccess: (respData) => { + const computeUsage = respData?.cpu_usage?.total || 0; + const subscription = respData?.subscription; + const computeQuota = getUserQuota( + globalConstants.CPU_HOURS_RESOURCE_NAME, + subscription + ); + setComputeLimitExceeded(computeUsage >= computeQuota); + }, + onError: (e) => { + showErrorAnnouncer(t("common:usageSummaryError"), e); + }, + }); + + const isLoading = isQueryLoading([status, isFetchingUsageSummary]); + + if (isLoading) { + return ; + } else if (error) { + return ( + <> + setErrorDialogOpen(true)} + /> + setErrorDialogOpen(false)} + /> + + ); + } else { + return ( + + ); + } +}; + +export default withErrorAnnouncer(InstantLaunchStandalone); diff --git a/src/pages/instantlaunch/[id]/index.js b/src/pages/instantlaunch/[id]/index.js new file mode 100644 index 000000000..a3289106c --- /dev/null +++ b/src/pages/instantlaunch/[id]/index.js @@ -0,0 +1,33 @@ +/** + * @author mian + * + * Instant launch launch page + */ +import React from "react"; + +import { serverSideTranslations } from "next-i18next/serverSideTranslations"; + +import { i18n, RequiredNamespaces } from "i18n"; + +import { useRouter } from "next/router"; + +import InstantLaunchStandalone from "components/instantlaunches/launch"; + +export default function InstantLaunch() { + const router = useRouter(); + const { id } = router.query; + + return ; +} + +export async function getServerSideProps({ locale }) { + const title = i18n.t("instantLaunch"); + + return { + props: { + title, + // "instantlaunches" already included by RequiredNamespaces + ...(await serverSideTranslations(locale, RequiredNamespaces)), + }, + }; +} diff --git a/src/server/api/instantlaunches.js b/src/server/api/instantlaunches.js index 73c0e3544..8a885930c 100644 --- a/src/server/api/instantlaunches.js +++ b/src/server/api/instantlaunches.js @@ -165,6 +165,19 @@ export default () => { }) ); + logger.info("adding the GET /instantlaunches/:id/full handler"); + api.get( + "/instantlaunches/:id/full", + auth.authnTokenMiddleware, + terrainHandler({ + method: "GET", + pathname: "/instantlaunches/:id/full", + headers: { + "Content-Type": "application/json", + }, + }) + ); + logger.info("Add the PUT /admin/instantlaunches/:id/metadata handler"); api.put( "/admin/instantlaunches/:id/metadata", diff --git a/src/serviceFacades/instantlaunches.js b/src/serviceFacades/instantlaunches.js index 845e40b0f..6baf82577 100644 --- a/src/serviceFacades/instantlaunches.js +++ b/src/serviceFacades/instantlaunches.js @@ -20,6 +20,7 @@ export const DASHBOARD_INSTANT_LAUNCHES_KEY = "dashboardInstantLaunches"; export const LIST_PUBLIC_SAVED_LAUNCHES_KEY = "listPublicSavedLaunches"; export const LIST_INSTANT_LAUNCHES_BY_METADATA_KEY = "fetchInstantLaunchesByMetadata"; +export const GET_INSTANT_LAUNCH_FULL_KEY = "fetchFullInstantLaunch"; export const getDefaultsMapping = () => callApi({ @@ -77,6 +78,12 @@ export const getInstantLaunchMetadata = (id) => method: "GET", }); +export const getFullInstantLaunch = (id) => + callApi({ + endpoint: `/api/instantlaunches/${id}/full`, + method: "GET", + }); + /** * Add or modify the AVUs associated with an instant launch. *