diff --git a/antarest/core/config.py b/antarest/core/config.py index f93301613a..3847ada139 100644 --- a/antarest/core/config.py +++ b/antarest/core/config.py @@ -391,8 +391,8 @@ def get_time_limit(self, launcher: str) -> int: launcher_config = config_map.get(launcher) if launcher_config is None: raise InvalidConfigurationError(launcher) - # The default time limit is not available for the local launcher - return getattr(launcher_config, "default_time_limit", 3600) + # Default to 172800 (48 hours) for local launcher. + return getattr(launcher_config, "default_time_limit", 172800) @dataclass(frozen=True) diff --git a/webapp/src/components/App/Studies/LauncherDialog.tsx b/webapp/src/components/App/Studies/LauncherDialog.tsx index 4c3f928613..e1e2ba4287 100644 --- a/webapp/src/components/App/Studies/LauncherDialog.tsx +++ b/webapp/src/components/App/Studies/LauncherDialog.tsx @@ -42,7 +42,8 @@ import SwitchFE from "../../common/fieldEditors/SwitchFE"; import moment from "moment"; const DEFAULT_NB_CPU = 22; -const DEFAULT_TIME_LIMIT = 240 * 3600; // 240 hours in seconds +const MIN_TIME_LIMIT = 1 * 3600; // 1 hour in seconds. +const MAX_TIME_LIMIT = 240 * 3600; // 240 hours in seconds. interface Props { open: boolean; @@ -58,7 +59,7 @@ function LauncherDialog(props: Readonly) { const [options, setOptions] = useState({ nb_cpu: DEFAULT_NB_CPU, auto_unzip: true, - time_limit: undefined, + time_limit: MIN_TIME_LIMIT, }); const [solverVersion, setSolverVersion] = useState(); const [isLaunching, setIsLaunching] = useState(false); @@ -68,7 +69,7 @@ function LauncherDialog(props: Readonly) { shallowEqual, ); - const res = usePromiseWithSnackbarError( + const launcherCores = usePromiseWithSnackbarError( () => getLauncherCores().then((cores) => { setOptions((prevOptions) => { @@ -84,18 +85,22 @@ function LauncherDialog(props: Readonly) { }, ); - const { data: launcherTimeLimit } = usePromiseWithSnackbarError( - async () => { - return await getLauncherTimeLimit(); - }, + const launcherTimeLimit = usePromiseWithSnackbarError( + () => + getLauncherTimeLimit().then((timeLimit) => { + setOptions((prevOptions) => { + return { + ...prevOptions, + time_limit: timeLimit, + }; + }); + return timeLimit; + }), { errorMessage: t("study.error.launcherTimeLimit"), }, ); - const minSeconds = 3600; - const maxSeconds = launcherTimeLimit ?? DEFAULT_TIME_LIMIT; - const { data: outputList } = usePromiseWithSnackbarError( () => Promise.all(studyIds.map((sid) => getStudyOutputs(sid))), { errorMessage: t("study.error.listOutputs"), deps: [studyIds] }, @@ -110,7 +115,7 @@ function LauncherDialog(props: Readonly) { // Event Handlers //////////////////////////////////////////////////////////////// - const handleLaunchClick = async () => { + const handleLaunchClick = () => { if (studyIds.length > 0) { setIsLaunching(true); Promise.all( @@ -195,7 +200,7 @@ function LauncherDialog(props: Readonly) { */ const parseHoursToSeconds = (hourString: string): number => { const seconds = moment.duration(hourString, "hours").asSeconds(); - return Math.max(minSeconds, Math.min(seconds, maxSeconds)); + return Math.max(MIN_TIME_LIMIT, Math.min(seconds, MAX_TIME_LIMIT)); }; //////////////////////////////////////////////////////////////// @@ -218,7 +223,7 @@ function LauncherDialog(props: Readonly) { sx={{ mx: 2 }} color="primary" variant="contained" - disabled={isLaunching || !res.isResolved} + disabled={isLaunching || !launcherCores.isResolved} onClick={handleLaunchClick} > {t("global.launch")} @@ -279,34 +284,42 @@ function LauncherDialog(props: Readonly) { width: "50%", }} /> - - handleChange("time_limit", parseHoursToSeconds(e.target.value)) - } - InputLabelProps={{ - shrink: true, - }} - inputProps={{ - min: minSeconds / 3600, - max: maxSeconds / 3600, - step: 1, - }} - sx={{ - minWidth: "125px", - }} + + ( + { + handleChange( + "time_limit", + parseHoursToSeconds(e.target.value), + ); + }} + InputLabelProps={{ + shrink: true, + }} + inputProps={{ + min: MIN_TIME_LIMIT, + max: MAX_TIME_LIMIT, + step: 1, + }} + sx={{ + minWidth: "125px", + }} + /> + )} + ifPending={() => } + ifRejected={() => } /> + ( ) { name: o.name, }))} disabled={!!options.xpansion_r_version || !options.xpansion} - data={options.xpansion?.output_id ?? ""} + data={options.xpansion?.output_id || ""} setValue={(data: string) => handleObjectChange("xpansion", { output_id: data, diff --git a/webapp/src/services/api/study.ts b/webapp/src/services/api/study.ts index 27ba032431..cd3fcf8c6f 100644 --- a/webapp/src/services/api/study.ts +++ b/webapp/src/services/api/study.ts @@ -206,7 +206,7 @@ export const exportStudy = async ( export const getExportUrl = (sid: string, skipOutputs = false): string => `${ - getConfig().downloadHostUrl ?? + getConfig().downloadHostUrl || getConfig().baseUrl + getConfig().restEndpoint }/v1/studies/${sid}/export?no_output=${skipOutputs}`; @@ -226,7 +226,7 @@ export const importStudy = async ( if (onProgress) { options.onUploadProgress = (progressEvent): void => { const percentCompleted = Math.round( - (progressEvent.loaded * 100) / (progressEvent.total ?? 1), + (progressEvent.loaded * 100) / (progressEvent.total || 1), ); onProgress(percentCompleted); }; @@ -253,7 +253,7 @@ export const importFile = async ( if (onProgress) { options.onUploadProgress = (progressEvent): void => { const percentCompleted = Math.round( - (progressEvent.loaded * 100) / (progressEvent.total ?? 1), + (progressEvent.loaded * 100) / (progressEvent.total || 1), ); onProgress(percentCompleted); }; @@ -305,9 +305,10 @@ export const getLauncherCores = async (): Promise> => { }; /** - * Time limit for SLURM jobs (in seconds). + * Time limit for SLURM jobs. * If a jobs exceed this time limit, SLURM kills the job and it is considered failed. - * Often used value: 172800 (48 hours) + * + * @returns The time limit in seconds, Often used value: 172800 (48 hours). */ export const getLauncherTimeLimit = async (): Promise => { const res = await client.get("/v1/launcher/time-limit"); @@ -336,7 +337,7 @@ export const mapLaunchJobDTO = (j: LaunchJobDTO): LaunchJob => ({ exitCode: j.exit_code, }); -export const getStudyJobs = async ( +export const getStudyJobs = ( studyId?: string, filterOrphans = true, latest = false,