diff --git a/src/App.js b/src/App.js index d9772b345..c5a909752 100644 --- a/src/App.js +++ b/src/App.js @@ -129,8 +129,6 @@ const App = compose( Component: VariantDb, loggedInUser, WrapperPage: FixedFooterPage, - isAdmin: state.isAdmin, - loggedInUserToken: state.loggedInUserToken, ...props, }) } diff --git a/src/common/injectGlobals.ts b/src/common/injectGlobals.ts index e8ff2c446..cd352b9fb 100644 --- a/src/common/injectGlobals.ts +++ b/src/common/injectGlobals.ts @@ -92,6 +92,7 @@ export const reactApiDataVersionApi: string = getApplicationEnvVar('DATA_VERSION export const reactApiDataVersionFallback: string = getApplicationEnvVar('DATA_VERSION_FALLBACK') || ''; export const reactApiSearchMembersApi = getApplicationEnvVar('SEARCH_MEMBERS_API') || null; +export const kfVariantCluster = getApplicationEnvVar('VARIANT_CLUSTER_API') || null; // Public Stats export const publicStatsApiRoot = getApplicationEnvVar('PUBLIC_STATS_ROOT') || ''; diff --git a/src/components/VariantDb/LaunchClusterCard.tsx b/src/components/VariantDb/LaunchClusterCard.tsx new file mode 100644 index 000000000..b42273e3c --- /dev/null +++ b/src/components/VariantDb/LaunchClusterCard.tsx @@ -0,0 +1,91 @@ +import azicon from '../../assets/appache-zeppelin.png'; +import { Button, Col, Modal, Row } from 'antd'; +import { DeleteOutlined, LoadingOutlined, RocketOutlined, ToolOutlined } from '@ant-design/icons'; +import * as React from 'react'; +import { clusterStatus, isInterimState, canBeDeleted } from './store'; + +interface Props { + status: string; + modalVisible: boolean; + hideModalOk: (e: React.MouseEvent) => void; + hideModal: (e: React.MouseEvent) => void; + showModal: (e: React.MouseEvent) => void; + handleClick: (e: React.MouseEvent) => void; +} + +const LaunchClusterCard = (props: Props) => { + const { status, modalVisible, hideModalOk, hideModal, showModal, handleClick } = props; + + let buttonText; + let buttonIcon; + switch (status) { + case clusterStatus.createComplete: + buttonText = 'Launch your SPARK cluster'; + buttonIcon = ; + break; + case clusterStatus.createInProgress: + buttonText = 'Building your SPARK cluster'; + buttonIcon = ; + break; + case clusterStatus.deleteInProgress: + buttonText = 'Deleting your SPARK cluster'; + buttonIcon = ; + break; + case clusterStatus.rollback: + buttonText = 'Error, please delete the cluster'; + buttonIcon = ; + break; + default: + buttonText = 'Build a SPARK cluster'; + buttonIcon = ; + } + return ( +
+ AppacheZeppelin +
+ Kids First is providing members with their own SPARK cluster running a web-based Zeppelin + notrebooks dansbox to explore, query and visualize its germline variant datasets. Using + Zeppelin, bioinformaticians can create interactive data analytics and collaborative + documents with SQL, Scala, Python, and more.. +
+ + + + + + {canBeDeleted(status) ? ( +
+ + +

You want to delete this cluster?

+
+
+ ) : ( + '' + )} + +
+
+ ); +}; + +export default LaunchClusterCard; diff --git a/src/components/VariantDb/fetchVariantCluster.tsx b/src/components/VariantDb/fetchVariantCluster.tsx index 354584fb1..68a615b75 100644 --- a/src/components/VariantDb/fetchVariantCluster.tsx +++ b/src/components/VariantDb/fetchVariantCluster.tsx @@ -1,36 +1,20 @@ -export const launchCluster = async (api: Function) => { - let response; - const url = 'https://kf-api-variant-cluster-qa.kidsfirstdrc.org/stack'; +import { kfVariantCluster } from 'common/injectGlobals'; - try { - response = await api({ - method: 'POST', - url: url, - headers: { - Authorization: - 'Bearer eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1ODg1OTYzODEsImV4cCI6MTU4ODY4Mjc4MSwic3ViIjoiYWRkNzQxMDItMjRmOS00YTZjLThjYzEtZWRlMTM5NGQ3MGJmIiwiaXNzIjoiZWdvIiwiYXVkIjpbXSwianRpIjoiNmUyYjE4MzEtOTdlMi00MTI0LWE0MGItYTVkMjFhNDg3MDE2IiwiY29udGV4dCI6eyJ1c2VyIjp7Im5hbWUiOiJhZHJpYW5wYXVsY2h1QGdtYWlsLmNvbSIsImVtYWlsIjoiYWRyaWFucGF1bGNodUBnbWFpbC5jb20iLCJzdGF0dXMiOiJBcHByb3ZlZCIsImZpcnN0TmFtZSI6IkFkcmlhbiIsImxhc3ROYW1lIjoiUGF1bCIsImNyZWF0ZWRBdCI6IjIwMTktMTItMDYgMDg6MTg6MTUiLCJsYXN0TG9naW4iOiIyMDIwLTA1LTA0IDEyOjQ2OjIxIiwicHJlZmVycmVkTGFuZ3VhZ2UiOm51bGwsInJvbGVzIjpbIkFETUlOIl0sImdyb3VwcyI6W10sInBlcm1pc3Npb25zIjpbXX19fQ.dWcmQRe2Fw95k7FMxjSOaPO29_wot6f8Olvm53PpXTFTtQ3VyzlbUHgQCX6-ZAmsppPQ1vJQz7ucFcQDFkNofbjyyyouqNZyO5URHes2LnzNj8pq1C8RwusstLSvToSprujQkiHFEFLMHyAAJdGZz11rzWVHQb8ZlDvqPcwArCX_f8oh9-K_TQKnkHsME16Bld4opm3WQR7sTe23-pgOc_dFn4mFEgzx2VFw2q4yDoXXmN89MXo7a3chXLvqsvAnz9wb9iKjhAPTHxOW9NMjqSjurYak16031BKkkELhju5jxGkh-YxsiPBp6VBPYSkdKiOitOxQK1JWqQz-TgBJaQ', - }, - }); - } catch (err) { - throw new Error(err); - } - return response; -}; +export const launchCluster = async (api: Function) => + await api({ + method: 'POST', + url: kfVariantCluster, + }); -export const getStatus = async (api: Function) => { - let response; - const url = 'https://kf-api-variant-cluster-qa.kidsfirstdrc.org/stack'; - try { - response = await api({ - method: 'GET', - url: url, - headers: { - Authorization: - 'Bearer eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1ODg1OTYzODEsImV4cCI6MTU4ODY4Mjc4MSwic3ViIjoiYWRkNzQxMDItMjRmOS00YTZjLThjYzEtZWRlMTM5NGQ3MGJmIiwiaXNzIjoiZWdvIiwiYXVkIjpbXSwianRpIjoiNmUyYjE4MzEtOTdlMi00MTI0LWE0MGItYTVkMjFhNDg3MDE2IiwiY29udGV4dCI6eyJ1c2VyIjp7Im5hbWUiOiJhZHJpYW5wYXVsY2h1QGdtYWlsLmNvbSIsImVtYWlsIjoiYWRyaWFucGF1bGNodUBnbWFpbC5jb20iLCJzdGF0dXMiOiJBcHByb3ZlZCIsImZpcnN0TmFtZSI6IkFkcmlhbiIsImxhc3ROYW1lIjoiUGF1bCIsImNyZWF0ZWRBdCI6IjIwMTktMTItMDYgMDg6MTg6MTUiLCJsYXN0TG9naW4iOiIyMDIwLTA1LTA0IDEyOjQ2OjIxIiwicHJlZmVycmVkTGFuZ3VhZ2UiOm51bGwsInJvbGVzIjpbIkFETUlOIl0sImdyb3VwcyI6W10sInBlcm1pc3Npb25zIjpbXX19fQ.dWcmQRe2Fw95k7FMxjSOaPO29_wot6f8Olvm53PpXTFTtQ3VyzlbUHgQCX6-ZAmsppPQ1vJQz7ucFcQDFkNofbjyyyouqNZyO5URHes2LnzNj8pq1C8RwusstLSvToSprujQkiHFEFLMHyAAJdGZz11rzWVHQb8ZlDvqPcwArCX_f8oh9-K_TQKnkHsME16Bld4opm3WQR7sTe23-pgOc_dFn4mFEgzx2VFw2q4yDoXXmN89MXo7a3chXLvqsvAnz9wb9iKjhAPTHxOW9NMjqSjurYak16031BKkkELhju5jxGkh-YxsiPBp6VBPYSkdKiOitOxQK1JWqQz-TgBJaQ', - }, - }); - } catch (err) { - throw new Error(err); - } - return response; -}; +export const getStatus = async (api: Function) => + await api({ + method: 'GET', + // url: kfVariantCluster, + url: 'https://kf-api-variant-cluster-qa.kidsfirstdrc.org/sstack', + }); + +export const deleteCluster = async (api: Function) => + await api({ + method: 'DELETE', + url: kfVariantCluster, + }); diff --git a/src/components/VariantDb/index.tsx b/src/components/VariantDb/index.tsx index 986091a35..9e810dc75 100644 --- a/src/components/VariantDb/index.tsx +++ b/src/components/VariantDb/index.tsx @@ -1,31 +1,27 @@ import * as React from 'react'; -import { Button, Col, List, Row } from 'antd'; -import { Typography } from 'antd'; -import { RocketOutlined, LoadingOutlined } from '@ant-design/icons'; -import azicon from 'assets/appache-zeppelin.png'; -import { launchCluster, getStatus } from './fetchVariantCluster'; +import { Button, Col, List, notification, Row, Typography } from 'antd'; +import { deleteCluster, getStatus, launchCluster } from './fetchVariantCluster'; +import { MAX_MINUTES_TRY, INCREMENT, clusterStatus, isInterimState } from './store'; import './index.css'; +import LaunchClusterCard from './LaunchClusterCard'; const { Title } = Typography; -const MAX_MINUTES_TRY = 10; //minutes -const INCREMENT = 3000; type VariantDbProps = { api: Function; isAdmin: boolean; - loggedInUserToken: string; }; type VariantDbState = { - clusterStared: boolean; - isFetching: boolean; + status: string; + modalVisible: boolean; }; class VariantDb extends React.Component { state = { - clusterStared: false, - isFetching: false, + status: '', + modalVisible: false, }; data = [ @@ -51,60 +47,138 @@ class VariantDb extends React.Component { }, ]; - getCluster = (api: Function) => { + errorNotification = (message: string, decription: string) => { + notification.error({ + message: message, + description: decription, + }); + }; + + async componentDidMount() { + const { api } = this.props; + try { + const res = await getStatus(api); + + if (isInterimState(res.status)) { + this.resolveInterimState(api); + } else if (res.status === clusterStatus.rollback) { + await deleteCluster(api); + this.resolveInterimState(api); + } + + this.setState({ + status: res.status, + }); + } catch (e) { + this.errorNotification(`Error ${e.response.status}`, 'Cannot connect with cluster'); + } + } + + resolveInterimState = (api: Function) => { let interval: NodeJS.Timeout; let counter = 1; - const verifyStatus = () => { + const verifyStatus = async () => { if (counter * INCREMENT > MAX_MINUTES_TRY * 60 * 1000) { this.setState({ - clusterStared: false, - isFetching: false, + status: clusterStatus.stopped, }); - clearInterval(interval); + clearInterval(interval); // throw timeout exception } - getStatus(api).then((res) => { - if (res.status === 'CREATE_COMPLETE') { + try { + const res = await getStatus(api); + + if (!isInterimState(res.status)) { this.setState({ - clusterStared: true, - isFetching: false, + status: res.status, }); - console.log(res, counter); clearInterval(interval); } else { - console.log(res, counter); counter++; } - }); + } catch (e) { + this.errorNotification(`Error ${e.response.status}`, 'Cannot connect with cluster'); + } }; interval = setInterval(verifyStatus, INCREMENT); }; - handleClick = () => { - const { clusterStared, isFetching } = this.state; + handleClick = async () => { + const { status } = this.state; const { api } = this.props; - if (!isFetching && !clusterStared) { + if (status === clusterStatus.stopped) { this.setState({ - isFetching: true, - }); - launchCluster(api).then((res) => { - // if (res.state === 'CREATE_IN_PROGRESS') this.getCluster(api); - console.log(res.status, 'STATUS b4'); - this.getCluster(api); + status: clusterStatus.createInProgress, }); - } else if (clusterStared) { - console.log('Launching cluster'); - //launch cluster - } else { - console.log('Waiting fo cluster the be build'); + + this.openNotification(); + + try { + await launchCluster(api); + this.resolveInterimState(api); + } catch (e) { + this.errorNotification(`Error ${e.response.status}`, 'Cannot launch cluster'); + } } + // else if (status === clusterStatus.createComplete) { + // //launch cluster + // } + }; + + openNotification = () => { + const key = `open${Date.now()}`; + const btn = ( + + ); + notification.open({ + message: 'Building the cluster, please wait', + description: + 'The Spark cluster is being build. This can take up to 10 min to complete. ' + + 'You can continue using the portal and come back to this page once this operation ' + + 'has completed', + duration: 15, + btn, + key, + }); + }; + + showModal = () => { + this.setState({ + modalVisible: true, + }); + }; + + hideModal = () => { + this.setState({ + modalVisible: false, + }); + }; + + hideModalOk = async () => { + const { api } = this.props; + this.setState({ + status: clusterStatus.deleteInProgress, + }); + try { + await deleteCluster(api); + } catch (e) { + this.errorNotification(`Error ${e.response.status}`, 'Error deleting the cluster'); + } + + this.setState({ + modalVisible: false, + }); + + this.resolveInterimState(api); }; render() { - const { clusterStared, isFetching } = this.state; + const { status, modalVisible } = this.state; return (
@@ -121,25 +195,14 @@ class VariantDb extends React.Component { -
- AppacheZeppelin -
- Kids First is providing members with their own SPARK cluster running a web-based - Zeppelin notrebooks dansbox to explore, query and visualize its germline variant - datasets. Using Zeppelin, bioinformaticians can create interactive data analytics - and collaborative documents with SQL, Scala, Python, and more.. -
- -
+
+ status === clusterStatus.createInProgress || status === clusterStatus.deleteInProgress; + +export const canBeDeleted = (status: string) => + status === clusterStatus.createInProgress || + status === clusterStatus.createComplete || + status === clusterStatus.rollback;