From 0a1905828b7935ae8dc079c28eca61dc5f3c72fc Mon Sep 17 00:00:00 2001 From: Ian McEwen Date: Tue, 8 Oct 2024 12:48:13 -0700 Subject: [PATCH 1/6] Add a stub instant launch launcher page --- .../instantlaunches/launch/index.js | 13 ++++++++ src/pages/instantlaunch/[id]/index.js | 33 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/components/instantlaunches/launch/index.js create mode 100644 src/pages/instantlaunch/[id]/index.js diff --git a/src/components/instantlaunches/launch/index.js b/src/components/instantlaunches/launch/index.js new file mode 100644 index 000000000..c29c6829b --- /dev/null +++ b/src/components/instantlaunches/launch/index.js @@ -0,0 +1,13 @@ +/** + * @author mian + * + * The component that launches a provided instant launch, immediately. + */ +import React from "react"; + +const InstantLaunchStandalone = (props) => { + const { id: instant_launch_id } = props; + return

itsa {instant_launch_id}

; +}; + +export default InstantLaunchStandalone; diff --git a/src/pages/instantlaunch/[id]/index.js b/src/pages/instantlaunch/[id]/index.js new file mode 100644 index 000000000..de8592c32 --- /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("instantLaunches"); + + return { + props: { + title, + // "instantlaunches" already included by RequiredNamespaces + ...(await serverSideTranslations(locale, RequiredNamespaces)), + }, + }; +} From 68418a6ee76bf6c52c943d42c78bb9512fcd08aa Mon Sep 17 00:00:00 2001 From: Ian McEwen Date: Tue, 8 Oct 2024 14:53:53 -0700 Subject: [PATCH 2/6] flesh out a rudimentary instant launching launch page --- .../instantlaunches/launch/index.js | 86 ++++++++++++++++++- src/server/api/instantlaunches.js | 13 +++ src/serviceFacades/instantlaunches.js | 7 ++ 3 files changed, 102 insertions(+), 4 deletions(-) diff --git a/src/components/instantlaunches/launch/index.js b/src/components/instantlaunches/launch/index.js index c29c6829b..13e4497a7 100644 --- a/src/components/instantlaunches/launch/index.js +++ b/src/components/instantlaunches/launch/index.js @@ -3,11 +3,89 @@ * * The component that launches a provided instant launch, immediately. */ -import React from "react"; +import React, { useState } from "react"; + +import { useQuery } from "react-query"; + +import { + GET_INSTANT_LAUNCH_FULL_KEY, + getFullInstantLaunch, +} from "serviceFacades/instantlaunches"; +import { + getResourceUsageSummary, + RESOURCE_USAGE_QUERY_KEY, +} from "serviceFacades/dashboard"; +import { getUserQuota } from "common/resourceUsage"; +import { useConfig } from "contexts/config"; +import { useUserProfile } from "contexts/userProfile"; +import globalConstants from "../../../constants"; +import { useTranslation } from "i18n"; +import isQueryLoading from "components/utils/isQueryLoading"; +import InstantLaunchButtonWrapper from "components/instantlaunches/InstantLaunchButtonWrapper"; +import DELink from "components/utils/DELink"; +import withErrorAnnouncer from "components/error/withErrorAnnouncer"; const InstantLaunchStandalone = (props) => { - const { id: instant_launch_id } = props; - return

itsa {instant_launch_id}

; + const { id: instant_launch_id, showErrorAnnouncer } = props; + const [config] = useConfig(); + const [userProfile] = useUserProfile(); + const [computeLimitExceeded, setComputeLimitExceeded] = useState( + !!config?.subscriptions?.enforce + ); + 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

... loading ...

; + } else if (error) { + return ( +

+ error{" "} + {error?.response?.status === 404 ? "not found" : "unsure why"} +

+ ); + } else { + return ( + <> + {JSON.stringify(data)} +
+ ( + + )} + /> + + ); + } }; -export default InstantLaunchStandalone; +export default withErrorAnnouncer(InstantLaunchStandalone); 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. * From 21334266a12ac2ec5fc02b2248314a370435c737 Mon Sep 17 00:00:00 2001 From: Ian McEwen Date: Wed, 9 Oct 2024 13:02:39 -0700 Subject: [PATCH 3/6] Add an autolaunch feature to InstantLaunchButtonWrapper --- .../InstantLaunchButtonWrapper.js | 34 +++++++++++++++---- .../instantlaunches/launch/index.js | 22 ++++-------- src/pages/instantlaunch/[id]/index.js | 2 +- 3 files changed, 35 insertions(+), 23 deletions(-) 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)} { @@ -58,6 +57,7 @@ const InstantLaunchStandalone = (props) => { }); const isLoading = isQueryLoading([status, isFetchingUsageSummary]); + if (isLoading) { return

... loading ...

; } else if (error) { @@ -69,21 +69,11 @@ const InstantLaunchStandalone = (props) => { ); } else { return ( - <> - {JSON.stringify(data)} -
- ( - - )} - /> - + ); } }; diff --git a/src/pages/instantlaunch/[id]/index.js b/src/pages/instantlaunch/[id]/index.js index de8592c32..d36562e31 100644 --- a/src/pages/instantlaunch/[id]/index.js +++ b/src/pages/instantlaunch/[id]/index.js @@ -21,7 +21,7 @@ export default function InstantLaunch() { } export async function getServerSideProps({ locale }) { - const title = i18n.t("instantLaunches"); + const title = i18n?.t("instantLaunches") || ""; return { props: { From b49036a42bb63ebd9a8b0600e7f45b3917e89329 Mon Sep 17 00:00:00 2001 From: Ian McEwen Date: Thu, 10 Oct 2024 11:36:02 -0700 Subject: [PATCH 4/6] Use a dedicated string for instant launch standalone page, remove dev-mode workaround --- public/static/locales/en/common.json | 1 + src/pages/instantlaunch/[id]/index.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) 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/pages/instantlaunch/[id]/index.js b/src/pages/instantlaunch/[id]/index.js index d36562e31..a3289106c 100644 --- a/src/pages/instantlaunch/[id]/index.js +++ b/src/pages/instantlaunch/[id]/index.js @@ -21,7 +21,7 @@ export default function InstantLaunch() { } export async function getServerSideProps({ locale }) { - const title = i18n?.t("instantLaunches") || ""; + const title = i18n.t("instantLaunch"); return { props: { From 2726412782a4f3d6471e3ac747fcca569e777982 Mon Sep 17 00:00:00 2001 From: Ian McEwen Date: Thu, 10 Oct 2024 12:04:56 -0700 Subject: [PATCH 5/6] Use vice loading animation on instant launch page while loading (not that it takes long) --- src/components/instantlaunches/launch/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/instantlaunches/launch/index.js b/src/components/instantlaunches/launch/index.js index 529723b40..a83be0c02 100644 --- a/src/components/instantlaunches/launch/index.js +++ b/src/components/instantlaunches/launch/index.js @@ -23,6 +23,7 @@ import { useTranslation } from "i18n"; import isQueryLoading from "components/utils/isQueryLoading"; import InstantLaunchButtonWrapper from "components/instantlaunches/InstantLaunchButtonWrapper"; import withErrorAnnouncer from "components/error/withErrorAnnouncer"; +import LoadingAnimation from "components/vice/loading/LoadingAnimation"; const InstantLaunchStandalone = (props) => { const { id: instant_launch_id, showErrorAnnouncer } = props; @@ -59,7 +60,7 @@ const InstantLaunchStandalone = (props) => { const isLoading = isQueryLoading([status, isFetchingUsageSummary]); if (isLoading) { - return

... loading ...

; + return ; } else if (error) { return (

From 8e8af372010b899fea809280bffb7be4ccd002b5 Mon Sep 17 00:00:00 2001 From: Ian McEwen Date: Thu, 10 Oct 2024 13:12:35 -0700 Subject: [PATCH 6/6] Use ErrorTypography/DEErrorDialog for errors, for now anyway --- .../instantlaunches/launch/index.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/components/instantlaunches/launch/index.js b/src/components/instantlaunches/launch/index.js index a83be0c02..b23cded93 100644 --- a/src/components/instantlaunches/launch/index.js +++ b/src/components/instantlaunches/launch/index.js @@ -25,6 +25,9 @@ import InstantLaunchButtonWrapper from "components/instantlaunches/InstantLaunch import withErrorAnnouncer from "components/error/withErrorAnnouncer"; import LoadingAnimation from "components/vice/loading/LoadingAnimation"; +import ErrorTypography from "components/error/ErrorTypography"; +import DEErrorDialog from "components/error/DEErrorDialog"; + const InstantLaunchStandalone = (props) => { const { id: instant_launch_id, showErrorAnnouncer } = props; const [config] = useConfig(); @@ -32,6 +35,7 @@ const InstantLaunchStandalone = (props) => { const [computeLimitExceeded, setComputeLimitExceeded] = useState( !!config?.subscriptions?.enforce ); + const [errorDialogOpen, setErrorDialogOpen] = useState(false); const { t } = useTranslation(["instantlaunches", "common"]); const { data, status, error } = useQuery( @@ -63,10 +67,17 @@ const InstantLaunchStandalone = (props) => { return ; } else if (error) { return ( -

- error{" "} - {error?.response?.status === 404 ? "not found" : "unsure why"} -

+ <> + setErrorDialogOpen(true)} + /> + setErrorDialogOpen(false)} + /> + ); } else { return (