From 10015faba57343fe9ca63a1c625e0922168b755a Mon Sep 17 00:00:00 2001 From: Andrea Cordoba Date: Wed, 9 Nov 2022 12:38:31 +0100 Subject: [PATCH] feat: unify startq session flow --- client/src/notebooks/NotebookStart.present.js | 97 ++---------- client/src/notebooks/Notebooks.container.js | 24 +-- .../components/StartSessionLoader.tsx | 143 ++++++++++++++++++ client/src/utils/components/buttons/Button.js | 5 +- 4 files changed, 168 insertions(+), 101 deletions(-) create mode 100644 client/src/notebooks/components/StartSessionLoader.tsx diff --git a/client/src/notebooks/NotebookStart.present.js b/client/src/notebooks/NotebookStart.present.js index e0fa611653..2d3326fff3 100644 --- a/client/src/notebooks/NotebookStart.present.js +++ b/client/src/notebooks/NotebookStart.present.js @@ -16,7 +16,7 @@ * limitations under the License. */ -import React, { Component, Fragment, useEffect, useState } from "react"; +import React, { Component, Fragment, useState } from "react"; import { Link, useLocation } from "react-router-dom"; import { Badge, Button, ButtonGroup, Col, Collapse, DropdownItem, Form, FormGroup, FormText, Input, Label, @@ -30,7 +30,7 @@ import { } from "@fortawesome/free-solid-svg-icons"; import { ErrorAlert, InfoAlert, SuccessAlert, WarnAlert } from "../utils/components/Alert"; -import { ButtonWithMenu, GoBackButton } from "../utils/components/buttons/Button"; +import { ButtonWithMenu } from "../utils/components/buttons/Button"; import { Clipboard } from "../utils/components/Clipboard"; import { ExternalLink } from "../utils/components/ExternalLinks"; import { JupyterIcon } from "../utils/components/Icon"; @@ -46,10 +46,9 @@ import Time from "../utils/helpers/Time"; import LaunchErrorAlert from "./components/LaunchErrorAlert"; import { NotebooksHelper } from "./index"; import { ObjectStoresConfigurationButton, ObjectStoresConfigurationModal } from "./ObjectStoresConfig.present"; -import { ProgressStyle, ProgressType } from "../utils/components/progress/Progress"; import EnvironmentVariables from "./components/EnviromentVariables"; import { useSelector } from "react-redux"; -import ProgressStepsIndicator, { StatusStepProgressBar } from "../utils/components/progress/ProgressSteps"; +import { StartNotebookAutostartLoader, StartNotebookLoader } from "./components/StartSessionLoader"; function ProjectSessionLockAlert({ lockStatus }) { if (lockStatus == null) return null; @@ -122,7 +121,7 @@ function StartNotebookAdvancedOptions(props) { // * StartNotebookServer code * // function StartNotebookServer(props) { - const { autosaves, autoStarting, ci, message, defaultBackButton } = props; + const { autosaves, autoStarting, ci, message, defaultBackButton, justStarted } = props; const { branch, commit, namespace, project } = props.filters; const location = useLocation(); @@ -136,7 +135,11 @@ function StartNotebookServer(props) { // Show fetching status when auto-starting if (autoStarting) - return (); + return (); + + // show loader when is starting a session + if (justStarted) + return (); const ciStatus = NotebooksHelper.checkCiStatus(ci); const fetching = { @@ -296,88 +299,6 @@ function AutosavesInfoAlert({ autosaves, autosavesId, autosavesWrong, currentId, ); } -function StartNotebookAutostart(props) { - const { ci, data, notebooks, options, backUrl } = props; - const ciStatus = NotebooksHelper.checkCiStatus(ci); - const [fetching, setFetching] = useState({ - ci: ciStatus.ongoing === false, - data: !!data.fetched, - options: !!options.fetched - }); - const [steps, setSteps] = useState([ - { - id: 0, - status: StatusStepProgressBar.EXECUTING, - step: "Checking project data" - }, - { - id: 1, - status: StatusStepProgressBar.WAITING, - step: "Checking GitLab jobs" - }, - { - id: 2, - status: StatusStepProgressBar.WAITING, - step: "Checking RenkuLab status" - }, - { - id: 3, - status: StatusStepProgressBar.WAITING, - step: "Checking existing sessions" - } - ]); - - // Compute fetching status, but ignore notebooks.fetched since it may be unreliable - let fetched = Object.keys(fetching).filter(k => !!fetching[k]); - if (!notebooks.fetched) - fetched = false; - - useEffect(() => { - if (!fetched.length) - return; - const multiplier = Object.keys(fetching).length + 1; - const currentProgress = fetched.length * 100 / multiplier; - const statuses = steps; - if (fetching.ci) - statuses[0].status = StatusStepProgressBar.READY; - else if (fetching.options) - statuses[1].status = StatusStepProgressBar.READY; - else if (fetching.notebooks) - statuses[2].status = StatusStepProgressBar.READY; - - if (currentProgress === 100) - statuses[3].status = StatusStepProgressBar.READY; - - setSteps(statuses); - }, [fetched.length, fetching]); //eslint-disable-line - - useEffect(() => { - setFetching({ - ci: ciStatus.ongoing === false, - data: !!data.fetched, - options: !!options.fetched - }); - }, [ ciStatus.ongoing, data.fetched, options.fetched ]); - - const pathWithNamespace = useSelector((state) => state.stateModel.project?.metadata.pathWithNamespace); - const backButtonSessions = ( - ); - return ( - <> - {backButtonSessions} -
- -
- - ); -} - class StartNotebookBranches extends Component { render() { const { branches } = this.props.data; diff --git a/client/src/notebooks/Notebooks.container.js b/client/src/notebooks/Notebooks.container.js index f5a7d4454a..f1c5696ead 100644 --- a/client/src/notebooks/Notebooks.container.js +++ b/client/src/notebooks/Notebooks.container.js @@ -720,6 +720,16 @@ class StartNotebookServer extends Component { this.coordinator.setObjectStoresConfiguration(value); } + redirectToShowSession(data, state, history) { + const annotations = NotebooksHelper.cleanAnnotations(data.annotations, "renku.io"); + const localUrl = Url.get(Url.pages.project.session.show, { + namespace: annotations["namespace"], + path: annotations["projectName"], + server: data.name, + }); + history.push({ pathname: localUrl, search: "", state: { ...state, redirectFromStartServer: true } }); + } + internalStartServer() { // The core internal logic extracted here for re-use const { location, history } = this.props; @@ -730,23 +740,15 @@ class StartNotebookServer extends Component { return; if (location.state && location.state.successUrl && history.location.pathname === location.pathname) { if (this.autostart && !this.state.autostartTried) { - // Derive the local Url and connect to the notebook - const annotations = NotebooksHelper.cleanAnnotations(data.annotations, "renku.io"); - const localUrl = Url.get(Url.pages.project.session.show, { - namespace: annotations["namespace"], - path: annotations["projectName"], - server: data.name, - }); - const state = { filePath: this.customNotebookFilePath, redirectFromStartServer: true }; - + const state = { filePath: this.customNotebookFilePath }; // ? Start with a short delay to prevent missing server information from "GET /servers" API setTimeout(() => { this.setState({ autostartTried: true }); - history.push({ pathname: localUrl, search: "", state }); + this.redirectToShowSession(data, state, history); }, 3000); } else { - history.push(location.state.successUrl); + this.redirectToShowSession(data, {}, history); } } }); diff --git a/client/src/notebooks/components/StartSessionLoader.tsx b/client/src/notebooks/components/StartSessionLoader.tsx new file mode 100644 index 0000000000..d36ca69e02 --- /dev/null +++ b/client/src/notebooks/components/StartSessionLoader.tsx @@ -0,0 +1,143 @@ +/*! + * Copyright 2022 - Swiss Data Science Center (SDSC) + * A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and + * Eidgenössische Technische Hochschule Zürich (ETHZ). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NotebooksHelper } from "../Notebooks.state"; +import React, { useEffect, useState } from "react"; +import ProgressStepsIndicator, { StatusStepProgressBar } from "../../utils/components/progress/ProgressSteps"; +import { RootStateOrAny, useSelector } from "react-redux"; +import { GoBackButton } from "../../utils/components/buttons/Button"; +import { ProgressStyle, ProgressType } from "../../utils/components/progress/Progress"; + +interface CIStatus { + ongoing: boolean; +} + +interface StatusFetching { + ci: boolean; + data: boolean; + options: boolean; +} + +interface StartNotebookAutostartLoaderProps { + ci: { + stage: Record + }; + data: Record; + notebooks: Record; + options: Record; + backUrl: string; +} +function StartNotebookAutostartLoader(props: StartNotebookAutostartLoaderProps) { + const { ci, data, notebooks, options, backUrl } = props; + const ciStatus = NotebooksHelper.checkCiStatus(ci) as CIStatus; + const [fetching, setFetching] = useState({ + ci: !ciStatus.ongoing, + data: !!data.fetched, + options: !!options.fetched + }); + const [steps, setSteps] = useState([ + { + id: 0, + status: StatusStepProgressBar.EXECUTING, + step: "Checking project data" + }, + { + id: 1, + status: StatusStepProgressBar.WAITING, + step: "Checking GitLab jobs" + }, + { + id: 2, + status: StatusStepProgressBar.WAITING, + step: "Checking existing sessions" + } + ]); + + useEffect(() => { + let fetched = !notebooks.fetched ? [] : Object.keys(fetching).filter((k ) => { + const key = k as keyof StatusFetching; + return fetching[key]; + }); + if (!fetched.length) + return; + const statuses = steps; + if (fetching.ci) + statuses[0].status = StatusStepProgressBar.READY; + + if (fetching.options) + statuses[1].status = StatusStepProgressBar.READY; + + if (notebooks.fetched !== false) + statuses[2].status = StatusStepProgressBar.READY; + + setSteps(statuses); + }, [fetching]); //eslint-disable-line + + useEffect(() => { + setFetching({ + ci: !ciStatus.ongoing, + data: !!data.fetched, + options: !!options.fetched + }); + }, [ ciStatus.ongoing, data.fetched, options.fetched ]); + + const pathWithNamespace = useSelector( (state: RootStateOrAny) => + state.stateModel.project?.metadata.pathWithNamespace); + const backButtonSessions = ( + ); + return ( + <> + {backButtonSessions} +
+ +
+ + ); +} + +interface StartNotebookLoaderProps { + backUrl: string; +} +function StartNotebookLoader({ backUrl }: StartNotebookLoaderProps) { + return ( + <> + {backUrl} +
+ +
+ + ); +} + +export { StartNotebookLoader, StartNotebookAutostartLoader }; diff --git a/client/src/utils/components/buttons/Button.js b/client/src/utils/components/buttons/Button.js index 652b02d216..000d0f9300 100644 --- a/client/src/utils/components/buttons/Button.js +++ b/client/src/utils/components/buttons/Button.js @@ -100,9 +100,10 @@ function RefreshButton(props) { * * @param {string} props.url url to go back to * @param {string} props.label text next to the arrow + * @param {string} props.activeClassName personalize class to attach */ -function GoBackButton({ className, label, url }) { - +function GoBackButton(props) { + const { className = "", label, url } = props; const linkClasses = (className) ? className + " link-rk-text text-decoration-none" : "link-rk-text text-decoration-none";