Skip to content

Commit

Permalink
feat: unify startq session flow
Browse files Browse the repository at this point in the history
  • Loading branch information
andre-code committed Nov 10, 2022
1 parent 21b94af commit 10015fa
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 101 deletions.
97 changes: 9 additions & 88 deletions client/src/notebooks/NotebookStart.present.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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";
Expand All @@ -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;
Expand Down Expand Up @@ -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();

Expand All @@ -136,7 +135,11 @@ function StartNotebookServer(props) {

// Show fetching status when auto-starting
if (autoStarting)
return (<StartNotebookAutostart {...props} />);
return (<StartNotebookAutostartLoader {...props} />);

// show loader when is starting a session
if (justStarted)
return (<StartNotebookLoader backUrl={defaultBackButton} />);

const ciStatus = NotebooksHelper.checkCiStatus(ci);
const fetching = {
Expand Down Expand Up @@ -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 = (
<GoBackButton label={`Cancel Session Start & back to ${pathWithNamespace}`} url={backUrl} />);
return (
<>
{backButtonSessions}
<div className="progress-box-small">
<ProgressStepsIndicator
description="Checking current status to start your session"
type={ProgressType.Determinate}
style={ProgressStyle.Light}
title="Step 1 of 2: Checking if launch is possible"
status={steps}
/>
</div>
</>
);
}

class StartNotebookBranches extends Component {
render() {
const { branches } = this.props.data;
Expand Down
24 changes: 13 additions & 11 deletions client/src/notebooks/Notebooks.container.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
}
});
Expand Down
143 changes: 143 additions & 0 deletions client/src/notebooks/components/StartSessionLoader.tsx
Original file line number Diff line number Diff line change
@@ -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<string, any>
};
data: Record<string, any>;
notebooks: Record<string, any>;
options: Record<string, any>;
backUrl: string;
}
function StartNotebookAutostartLoader(props: StartNotebookAutostartLoaderProps) {
const { ci, data, notebooks, options, backUrl } = props;
const ciStatus = NotebooksHelper.checkCiStatus(ci) as CIStatus;
const [fetching, setFetching] = useState<StatusFetching>({
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 = (
<GoBackButton label={`Cancel Session Start & back to ${pathWithNamespace}`} url={backUrl} />);
return (
<>
{backButtonSessions}
<div className="progress-box-small">
<ProgressStepsIndicator
description="Checking current status to start your session"
type={ProgressType.Determinate}
style={ProgressStyle.Light}
title="Step 1 of 2: Checking if launch is possible"
status={steps}
/>
</div>
</>
);
}

interface StartNotebookLoaderProps {
backUrl: string;
}
function StartNotebookLoader({ backUrl }: StartNotebookLoaderProps) {
return (
<>
{backUrl}
<div className="progress-box-small">
<ProgressStepsIndicator
description="Checking current status to start your session"
type={ProgressType.Determinate}
style={ProgressStyle.Light}
title="Step 1 of 2: Checking if launch is possible"
status={[{
id: 0,
status: StatusStepProgressBar.EXECUTING,
step: "Checking current sessions"
}]}
/>
</div>
</>
);
}

export { StartNotebookLoader, StartNotebookAutostartLoader };
5 changes: 3 additions & 2 deletions client/src/utils/components/buttons/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down

0 comments on commit 10015fa

Please sign in to comment.