diff --git a/Tombolo/client-reactjs/src/App.js b/Tombolo/client-reactjs/src/App.js
index 5e09dc9b..079bb28d 100644
--- a/Tombolo/client-reactjs/src/App.js
+++ b/Tombolo/client-reactjs/src/App.js
@@ -1,6 +1,6 @@
import React, { Suspense } from 'react';
import { connect } from 'react-redux';
-import { Layout, ConfigProvider, Spin, Card } from 'antd';
+import { Layout, ConfigProvider, Spin, Card, Tour } from 'antd';
import { Router, Route, Switch } from 'react-router-dom';
import { Redirect } from 'react-router';
import history from './components/common/History';
@@ -71,6 +71,7 @@ import { PrivateRoute } from './components/common/PrivateRoute';
import { userActions } from './redux/actions/User';
import { checkBackendStatus } from './redux/actions/Backend';
import { store } from './redux/store/Store';
+import { applicationActions } from './redux/actions/Application';
const { Header, Content } = Layout;
@@ -78,16 +79,23 @@ const BG_COLOR = '';
class App extends React.Component {
state = {
- collapsed: true,
+ collapsed: false,
locale: 'en',
+ message: '',
+ tourOpen: false,
+ clusterTourOpen: false,
+ appLinkRef: React.createRef(),
+ clusterLinkRef: React.createRef(),
};
componentDidMount() {
//if status of backend hasn't been retrieved, check it
if (!this.props.backendStatus.statusRetrieved) {
+ this.setState({ message: 'Connecting to...' });
store.dispatch(checkBackendStatus());
} else {
if (!this.props.authWithAzure) {
+ this.setState({ message: 'Authenticating...' });
store.dispatch(userActions.validateToken());
}
@@ -100,10 +108,35 @@ class App extends React.Component {
i18next.changeLanguage(localStorage.getItem('i18nextLng'));
}
}
+
+ //listen for clicks on the document to close tour if nav link is clicked
+ document.addEventListener('click', this.handleClick);
}
+ handleClick = (e) => {
+ if (this.state.appLinkRef.current && this.state.appLinkRef.current.contains(e.target)) {
+ this.setState({ tourOpen: false });
+ }
+
+ if (this.state.clusterLinkRef.current && this.state.clusterLinkRef.current.contains(e.target)) {
+ this.setState({ clusterTourOpen: false });
+ }
+ };
+
+ //function to handle tour shown close
+ handleTourShownClose = () => {
+ this.setState({ tourOpen: false });
+ };
+
+ //function to handle tour shown close
+ handleClusterTourShownClose = () => {
+ this.setState({ clusterTourOpen: false });
+ };
+
onCollapse = (collapsed) => {
this.setState({ collapsed });
+ //set collapsed into local storage
+ localStorage.setItem('collapsed', collapsed);
};
// Setting locale for antd components.
@@ -126,6 +159,29 @@ class App extends React.Component {
const isBackendStatusRetrieved = this.props.backendStatus.statusRetrieved;
const isApplicationSet = this.props.application && this.props.application.applicationId !== '' ? true : false;
+ //if an application doesn't exist and the tour hasn't been shown, show the tour
+ if (this.props.noApplication.noApplication && !this.props.noApplication.firstTourShown && isBackendConnected) {
+ //if you're not already on the application page, show the left nav tour
+ if (window.location.pathname !== '/admin/applications') {
+ this.setState({ tourOpen: true });
+ }
+ this.props.dispatch(applicationActions.updateApplicationLeftTourShown(true));
+ }
+
+ //if an application exists, but a cluster doesn't, show the cluster tour
+ if (
+ this.props.application?.applicationId &&
+ this.props.noClusters.noClusters &&
+ !this.props.noClusters.firstTourShown
+ ) {
+ //if you're not already on the cluster page, show the left nav tour
+ if (window.location.pathname !== '/admin/clusters') {
+ this.setState({ clusterTourOpen: true });
+ }
+
+ this.props.dispatch(applicationActions.updateClustersLeftTourShown(true));
+ }
+
const dataFlowComp = () => {
let applicationId = this.props.application ? this.props.application.applicationId : '';
let applicationTitle = this.props.application ? this.props.application.applicationTitle : '';
@@ -141,13 +197,78 @@ class App extends React.Component {
}
};
+ //steps for tour
+ const steps = [
+ {
+ title: 'Welcome to Tombolo',
+ description:
+ 'There is some setup that we need to complete before being able to fully utilize Tombolo. We will unlock features as we move through this interactive tutorial.',
+ target: null,
+ },
+ {
+ title: 'Applications',
+ description: (
+ <>
+
+ It looks like you have not set up an application yet. Applications are a necessary part of Tombolos basic
+ functions, and we must set one up before unlocking the rest of the application. Click on the navigation
+ element to head to the application management screen and set one up.
+
+ Now that we have an application set up, we can connect to an hpcc systems cluster to unlock the rest of
+ the application. Click the navigation element to head to the cluster management screen and set one up.
+
Tombolo has encountered a network issue, please refresh the page. If the issue persists, contact
@@ -164,33 +285,59 @@ class App extends React.Component {
) : (
-
+
+
+
+
+
+
+
{this.state.message}
+
+
)}
) : (
- /* if backend is connected, load the app */
+ /* Now that everything is loaded, present the application */
<>
- {this.props.user && this.props.user.token ? (
-
- }
- />
-
- ) : null}
+
+ }
+ />
+
+ <>>}
+ />
+
+ {' '}
{
this.setApplications(data);
+
+ // SHOW TOUR IF NO APPLICATIONS
+ if (!this.props.noApplication.addButtonTourShown && data.length === 0) {
+ this.setState({ showTour: true });
+ this.props.dispatch(applicationActions.updateApplicationAddButtonTourShown(true));
+ }
})
.catch((error) => {
console.log(error);
@@ -95,11 +103,10 @@ class Applications extends Component {
notification.open({
message: 'Application Removed',
description: 'The application has been removed.',
- onClick: () => {
- console.log('Closed!');
- },
+ onClick: () => {},
});
this.getApplications();
+
this.props.dispatch(applicationActions.applicationDeleted(app_id));
})
.catch((error) => {
@@ -109,7 +116,12 @@ class Applications extends Component {
// ADD OR CREATE NEW APPLICATION
handleAddApplication = () => {
- this.setState({ showAddApplicationModal: true, selectedApplication: null, isCreatingNewApp: true });
+ this.setState({
+ showAddApplicationModal: true,
+ selectedApplication: null,
+ isCreatingNewApp: true,
+ showTour: false,
+ });
};
// CLOSE ADD APPLICATION MODAL
@@ -175,8 +187,22 @@ class Applications extends Component {
if (record.visibility !== 'Public' && record.creator === this.props.user.username) return true;
};
+ handleTourClose = () => {
+ this.setState({ showTour: false });
+ };
+
//JSX
render() {
+ const steps = [
+ {
+ title: 'Add Application',
+ description: 'Click here to add an application. After adding an application, we can move on to the next step. ',
+ placement: 'bottom',
+ arrow: true,
+ target: () => this.state.appAddButtonRef?.current,
+ nextButtonProps: { style: { display: 'none' }, disabled: true },
+ },
+ ];
const applicationColumns = [
{
width: '2%',
@@ -271,12 +297,13 @@ class Applications extends Component {
-