aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTed Humphrey <Thomas.Humphrey@att.com>2020-07-06 16:59:47 -0400
committerTed Humphrey <Thomas.Humphrey@att.com>2020-07-08 08:52:58 -0400
commit4fb32395392c3c65ad8dcadc42d7804d2e656c63 (patch)
tree415efefee1058f2c30c56260d9f0d7cc0651e9cc
parent0a796ee7cd7aaea4225d4d8b137f5056e40f9ec9 (diff)
add framework for blocking user interaction
this adds two new methods to LoopUI.js, setBusyLoading and clearBusyLoading, and one new state variable, busyLoadingCount, for supporting the blocking of user clicking during async back end calls that might take a bit of time to return. Blocking the user from clicking on a component box is implemented as an important first case use, as well as all PerformAction calls. Issue-ID: CLAMP-894 Change-Id: I28660afe26b6cc8184b9392aee42157f44601bf6 Signed-off-by: Ted Humphrey <Thomas.Humphrey@att.com>
-rw-r--r--ui-react/src/LoopUI.js155
-rw-r--r--ui-react/src/__snapshots__/LoopUI.test.js.snap4
-rw-r--r--ui-react/src/__snapshots__/OnapClamp.test.js.snap4
-rw-r--r--ui-react/src/components/dialogs/PerformActions.js52
-rw-r--r--ui-react/src/components/dialogs/PerformActions.test.js8
-rw-r--r--ui-react/src/components/loop_viewer/svg/SvgGenerator.js16
6 files changed, 188 insertions, 51 deletions
diff --git a/ui-react/src/LoopUI.js b/ui-react/src/LoopUI.js
index 8624726be..0ee6e6e24 100644
--- a/ui-react/src/LoopUI.js
+++ b/ui-react/src/LoopUI.js
@@ -52,6 +52,7 @@ import PerformAction from './components/dialogs/PerformActions';
import RefreshStatus from './components/dialogs/RefreshStatus';
import DeployLoopModal from './components/dialogs/Loop/DeployLoopModal';
import Alert from 'react-bootstrap/Alert';
+import Spinner from 'react-bootstrap/Spinner';
import { Link } from 'react-router-dom';
@@ -59,6 +60,11 @@ const StyledMainDiv = styled.div`
background-color: ${props => props.theme.backgroundColor};
`
+const StyledSpinnerDiv = styled.div`
+ justify-content: center !important;
+ display: flex !important;
+`;
+
const ProjectNameStyled = styled.a`
vertical-align: middle;
padding-left: 30px;
@@ -108,7 +114,8 @@ export default class LoopUI extends React.Component {
loopName: OnapConstants.defaultLoopName,
loopCache: new LoopCache({}),
showSucAlert: false,
- showFailAlert: false
+ showFailAlert: false,
+ busyLoadingCount: 0
};
constructor() {
@@ -120,6 +127,9 @@ export default class LoopUI extends React.Component {
this.showSucAlert = this.showSucAlert.bind(this);
this.showFailAlert = this.showFailAlert.bind(this);
this.disableAlert = this.disableAlert.bind(this);
+ this.setBusyLoading = this.setBusyLoading.bind(this);
+ this.clearBusyLoading = this.clearBusyLoading.bind(this);
+ this.isBusyLoading = this.isBusyLoading.bind(this);
}
componentWillMount() {
@@ -191,7 +201,7 @@ export default class LoopUI extends React.Component {
renderLoopViewBody() {
return (
<LoopViewBodyDivStyled>
- <SvgGenerator loopCache={this.state.loopCache} clickable={true} generatedFrom={SvgGenerator.GENERATED_FROM_INSTANCE}/>
+ <SvgGenerator loopCache={this.state.loopCache} clickable={true} generatedFrom={SvgGenerator.GENERATED_FROM_INSTANCE} isBusyLoading={this.isBusyLoading}/>
<LoopStatus loopCache={this.state.loopCache}/>
<LoopLogs loopCache={this.state.loopCache} />
</LoopViewBodyDivStyled>
@@ -225,53 +235,160 @@ export default class LoopUI extends React.Component {
showFailAlert(message) {
this.setState ({ showFailAlert: true, showMessage:message });
}
-
+
disableAlert() {
this.setState ({ showSucAlert: false, showFailAlert: false });
}
loadLoop(loopName) {
+ this.setBusyLoading();
LoopService.getLoop(loopName).then(loop => {
console.debug("Updating loopCache");
LoopActionService.refreshStatus(loopName).then(data => {
this.updateLoopCache(data);
+ this.clearBusyLoading();
this.props.history.push('/');
})
.catch(error => {
this.updateLoopCache(loop);
+ this.clearBusyLoading();
this.props.history.push('/');
});
});
}
+ setBusyLoading() {
+ this.setState((state,props) => ({ busyLoadingCount: ++state.busyLoadingCount }));
+ }
+
+ clearBusyLoading() {
+ this.setState((state,props) => ({ busyLoadingCount: --state.busyLoadingCount }));
+ }
+
+ isBusyLoading() {
+ if (this.state.busyLoadingCount === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
closeLoop() {
this.setState({ loopCache: new LoopCache({}), loopName: OnapConstants.defaultLoopName });
this.props.history.push('/');
}
- render() {
- return (
- <StyledMainDiv id="main_div">
+ renderRoutes() {
+ return(
+ <React.Fragment>
<Route path="/uploadToscaPolicyModal" render={(routeProps) => (<UploadToscaPolicyModal {...routeProps} />)} />
<Route path="/viewToscaPolicyModal" render={(routeProps) => (<ViewToscaPolicyModal {...routeProps} />)} />
<Route path="/ViewLoopTemplatesModal" render={(routeProps) => (<ViewLoopTemplatesModal {...routeProps} />)} />
<Route path="/ManageDictionaries" render={(routeProps) => (<ManageDictionaries {...routeProps} />)} />
- <Route path="/policyModal/:policyInstanceType/:policyName" render={(routeProps) => (<PolicyModal {...routeProps} loopCache={this.getLoopCache()} loadLoopFunction={this.loadLoop}/>)} />
- <Route path="/createLoop" render={(routeProps) => (<CreateLoopModal {...routeProps} loadLoopFunction={this.loadLoop} />)} />
- <Route path="/openLoop" render={(routeProps) => (<OpenLoopModal {...routeProps} loadLoopFunction={this.loadLoop} />)} />
- <Route path="/loopProperties" render={(routeProps) => (<LoopPropertiesModal {...routeProps} loopCache={this.getLoopCache()} loadLoopFunction={this.loadLoop}/>)} />
- <Route path="/modifyLoop" render={(routeProps) => (<ModifyLoopModal {...routeProps} loopCache={this.getLoopCache()} loadLoopFunction={this.loadLoop}/>)} />
+
+ <Route path="/policyModal/:policyInstanceType/:policyName" render={(routeProps) => (<PolicyModal {...routeProps}
+ loopCache={this.getLoopCache()}
+ loadLoopFunction={this.loadLoop}/>)}
+ />
+ <Route path="/createLoop" render={(routeProps) => (<CreateLoopModal {...routeProps}
+ loadLoopFunction={this.loadLoop} />)}
+ />
+ <Route path="/openLoop" render={(routeProps) => (<OpenLoopModal {...routeProps}
+ loadLoopFunction={this.loadLoop} />)}
+ />
+ <Route path="/loopProperties" render={(routeProps) => (<LoopPropertiesModal {...routeProps}
+ loopCache={this.getLoopCache()}
+ loadLoopFunction={this.loadLoop}/>)}
+ />
+ <Route path="/modifyLoop" render={(routeProps) => (<ModifyLoopModal {...routeProps}
+ loopCache={this.getLoopCache()}
+ loadLoopFunction={this.loadLoop}/>)}
+ />
<Route path="/userInfo" render={(routeProps) => (<UserInfoModal {...routeProps} />)} />
<Route path="/closeLoop" render={this.closeLoop} />
- <Route path="/submit" render={(routeProps) => (<PerformAction {...routeProps} loopAction="submit" loopCache={this.getLoopCache()} updateLoopFunction={this.updateLoopCache} showSucAlert={this.showSucAlert} showFailAlert={this.showFailAlert}/>)} />
- <Route path="/stop" render={(routeProps) => (<PerformAction {...routeProps} loopAction="stop" loopCache={this.getLoopCache()} updateLoopFunction={this.updateLoopCache} showSucAlert={this.showSucAlert} showFailAlert={this.showFailAlert}/>)} />
- <Route path="/restart" render={(routeProps) => (<PerformAction {...routeProps} loopAction="restart" loopCache={this.getLoopCache()} updateLoopFunction={this.updateLoopCache} showSucAlert={this.showSucAlert} showFailAlert={this.showFailAlert}/>)} />
- <Route path="/delete" render={(routeProps) => (<PerformAction {...routeProps} loopAction="delete" loopCache={this.getLoopCache()} updateLoopFunction={this.updateLoopCache} showSucAlert={this.showSucAlert} showFailAlert={this.showFailAlert}/>)} />
- <Route path="/undeploy" render={(routeProps) => (<PerformAction {...routeProps} loopAction="undeploy" loopCache={this.getLoopCache()} updateLoopFunction={this.updateLoopCache} showSucAlert={this.showSucAlert} showFailAlert={this.showFailAlert}/>)} />
- <Route path="/deploy" render={(routeProps) => (<DeployLoopModal {...routeProps} loopCache={this.getLoopCache()} updateLoopFunction={this.updateLoopCache} showSucAlert={this.showSucAlert} showFailAlert={this.showFailAlert}/>)} />
- <Route path="/refreshStatus" render={(routeProps) => (<RefreshStatus {...routeProps} loopCache={this.getLoopCache()} updateLoopFunction={this.updateLoopCache} showSucAlert={this.showSucAlert} showFailAlert={this.showFailAlert}/>)} />
- <GlobalClampStyle />
+
+ <Route path="/submit" render={(routeProps) => (<PerformAction {...routeProps}
+ loopAction="submit"
+ loopCache={this.getLoopCache()}
+ updateLoopFunction={this.updateLoopCache}
+ showSucAlert={this.showSucAlert}
+ showFailAlert={this.showFailAlert}
+ setBusyLoading={this.setBusyLoading}
+ clearBusyLoading={this.clearBusyLoading}/>)}
+ />
+ <Route path="/stop" render={(routeProps) => (<PerformAction {...routeProps}
+ loopAction="stop"
+ loopCache={this.getLoopCache()}
+ updateLoopFunction={this.updateLoopCache}
+ showSucAlert={this.showSucAlert}
+ showFailAlert={this.showFailAlert}
+ setBusyLoading={this.setBusyLoading}
+ clearBusyLoading={this.clearBusyLoading}/>)}
+ />
+ <Route path="/restart" render={(routeProps) => (<PerformAction {...routeProps}
+ loopAction="restart"
+ loopCache={this.getLoopCache()}
+ updateLoopFunction={this.updateLoopCache}
+ showSucAlert={this.showSucAlert}
+ showFailAlert={this.showFailAlert}
+ setBusyLoading={this.setBusyLoading}
+ clearBusyLoading={this.clearBusyLoading}/>)}
+ />
+ <Route path="/delete" render={(routeProps) => (<PerformAction {...routeProps}
+ loopAction="delete"
+ loopCache={this.getLoopCache()}
+ updateLoopFunction={this.updateLoopCache}
+ showSucAlert={this.showSucAlert}
+ showFailAlert={this.showFailAlert}
+ setBusyLoading={this.setBusyLoading}
+ clearBusyLoading={this.clearBusyLoading}/>)}
+ />
+ <Route path="/undeploy" render={(routeProps) => (<PerformAction {...routeProps}
+ loopAction="undeploy"
+ loopCache={this.getLoopCache()}
+ updateLoopFunction={this.updateLoopCache}
+ showSucAlert={this.showSucAlert}
+ showFailAlert={this.showFailAlert}
+ setBusyLoading={this.setBusyLoading}
+ clearBusyLoading={this.clearBusyLoading}/>)}
+ />
+ <Route path="/deploy" render={(routeProps) => (<DeployLoopModal {...routeProps}
+ loopCache={this.getLoopCache()}
+ updateLoopFunction={this.updateLoopCache}
+ showSucAlert={this.showSucAlert}
+ showFailAlert={this.showFailAlert}/>)}
+ />
+ <Route path="/refreshStatus" render={(routeProps) => (<RefreshStatus {...routeProps}
+ loopCache={this.getLoopCache()}
+ updateLoopFunction={this.updateLoopCache}
+ showSucAlert={this.showSucAlert}
+ showFailAlert={this.showFailAlert}/>)}
+ />
+ </React.Fragment>
+ );
+ }
+
+ renderSpinner() {
+ if (this.isBusyLoading()) {
+ return (
+ <StyledSpinnerDiv>
+ <Spinner animation="border" role="status">
+ <span className="sr-only">Loading...</span>
+ </Spinner>
+ </StyledSpinnerDiv>
+ );
+ } else {
+ return (<div></div>);
+ }
+ }
+
+ render() {
+ return (
+ <StyledMainDiv id="main_div">
+ <GlobalClampStyle />
+ {this.renderRoutes()}
+ {this.renderSpinner()}
{this.renderAlertBar()}
{this.renderNavBar()}
{this.renderLoopViewer()}
diff --git a/ui-react/src/__snapshots__/LoopUI.test.js.snap b/ui-react/src/__snapshots__/LoopUI.test.js.snap
index 2dfa48091..cae9182ff 100644
--- a/ui-react/src/__snapshots__/LoopUI.test.js.snap
+++ b/ui-react/src/__snapshots__/LoopUI.test.js.snap
@@ -4,6 +4,7 @@ exports[`Verify LoopUI Test the render method 1`] = `
<styled.div
id="main_div"
>
+ <GlobalStyleComponent />
<Route
path="/uploadToscaPolicyModal"
render={[Function]}
@@ -76,7 +77,7 @@ exports[`Verify LoopUI Test the render method 1`] = `
path="/refreshStatus"
render={[Function]}
/>
- <GlobalStyleComponent />
+ <div />
<div>
<Alert
closeLabel="Close alert"
@@ -166,6 +167,7 @@ exports[`Verify LoopUI Test the render method 1`] = `
<withRouter(SvgGenerator)
clickable={true}
generatedFrom="INSTANCE"
+ isBusyLoading={[Function]}
loopCache={
LoopCache {
"loopJsonCache": Object {},
diff --git a/ui-react/src/__snapshots__/OnapClamp.test.js.snap b/ui-react/src/__snapshots__/OnapClamp.test.js.snap
index 56d022fc6..d4573b3d1 100644
--- a/ui-react/src/__snapshots__/OnapClamp.test.js.snap
+++ b/ui-react/src/__snapshots__/OnapClamp.test.js.snap
@@ -31,6 +31,7 @@ exports[`Verify OnapClamp Test the render method 1`] = `
<styled.div
id="main_div"
>
+ <GlobalStyleComponent />
<Route
path="/uploadToscaPolicyModal"
render={[Function]}
@@ -103,7 +104,7 @@ exports[`Verify OnapClamp Test the render method 1`] = `
path="/refreshStatus"
render={[Function]}
/>
- <GlobalStyleComponent />
+ <div />
<div>
<Alert
closeLabel="Close alert"
@@ -191,6 +192,7 @@ exports[`Verify OnapClamp Test the render method 1`] = `
<withRouter(SvgGenerator)
clickable={true}
generatedFrom="INSTANCE"
+ isBusyLoading={[Function]}
loopCache={
LoopCache {
"loopJsonCache": Object {},
diff --git a/ui-react/src/components/dialogs/PerformActions.js b/ui-react/src/components/dialogs/PerformActions.js
index cf5a3c20e..f6001e21f 100644
--- a/ui-react/src/components/dialogs/PerformActions.js
+++ b/ui-react/src/components/dialogs/PerformActions.js
@@ -22,24 +22,19 @@
*/
import React from 'react';
import LoopActionService from '../../api/LoopActionService';
-import Spinner from 'react-bootstrap/Spinner'
-import styled from 'styled-components';
-const StyledSpinnerDiv = styled.div`
- justify-content: center !important;
- display: flex !important;
-`;
export default class PerformActions extends React.Component {
state = {
loopName: this.props.loopCache.getLoopName(),
loopAction: this.props.loopAction
};
+
constructor(props, context) {
super(props, context);
-
this.refreshStatus = this.refreshStatus.bind(this);
}
+
componentWillReceiveProps(newProps) {
this.setState({
loopName: newProps.loopCache.getLoopName(),
@@ -51,35 +46,50 @@ export default class PerformActions extends React.Component {
const action = this.state.loopAction;
const loopName = this.state.loopName;
- LoopActionService.performAction(loopName, action).then(pars => {
+ if (action === 'delete') {
+ if (window.confirm('You are about to remove Control Loop Model "' + loopName +
+ '". Select OK to continue with deletion or Cancel to keep the model.') === false) {
+ return;
+ }
+ }
+
+ this.props.setBusyLoading(); // Alert top level to start block user clicks
+
+ LoopActionService.performAction(loopName, action)
+ .then(pars => {
this.props.showSucAlert("Action " + action + " successfully performed");
- // refresh status and update loop logs
- this.refreshStatus(loopName);
+ if (action === 'delete') {
+ this.props.updateLoopFunction(null);
+ this.props.history.push('/');
+ } else {
+ // refresh status and update loop logs
+ this.refreshStatus(loopName);
+ }
})
.catch(error => {
this.props.showFailAlert("Action " + action + " failed");
// refresh status and update loop logs
this.refreshStatus(loopName);
- });
-
+ })
+ .finally(() => this.props.clearBusyLoading());
}
refreshStatus(loopName) {
- LoopActionService.refreshStatus(loopName).then(data => {
+
+ this.props.setBusyLoading();
+
+ LoopActionService.refreshStatus(loopName)
+ .then(data => {
this.props.updateLoopFunction(data);
this.props.history.push('/');
})
- .catch(error => {
+ .catch(error => {
this.props.history.push('/');
- });
+ })
+ .finally(() => this.props.clearBusyLoading());
}
render() {
- return (
- <StyledSpinnerDiv>
- <Spinner animation="border" role="status">
- </Spinner>
- </StyledSpinnerDiv>
- );
+ return null;
}
}
diff --git a/ui-react/src/components/dialogs/PerformActions.test.js b/ui-react/src/components/dialogs/PerformActions.test.js
index b833a929d..c91c2f675 100644
--- a/ui-react/src/components/dialogs/PerformActions.test.js
+++ b/ui-react/src/components/dialogs/PerformActions.test.js
@@ -38,6 +38,8 @@ describe('Verify PerformActions', () => {
const updateLoopFunction = jest.fn();
const showSucAlert = jest.fn();
const showFailAlert = jest.fn();
+ const setBusyLoading = jest.fn();
+ const clearBusyLoading = jest.fn();
LoopActionService.refreshStatus = jest.fn().mockImplementation(() => {
return Promise.resolve({
@@ -47,7 +49,7 @@ describe('Verify PerformActions', () => {
});
});
const component = shallow(<PerformActions loopCache={loopCache}
- loopAction="submit" history={historyMock} updateLoopFunction={updateLoopFunction} showSucAlert={showSucAlert} showFailAlert={showFailAlert} />)
+ loopAction="submit" history={historyMock} updateLoopFunction={updateLoopFunction} showSucAlert={showSucAlert} showFailAlert={showFailAlert} setBusyLoading={setBusyLoading} clearBusyLoading={clearBusyLoading}/>)
await flushPromises();
component.update();
@@ -60,6 +62,8 @@ describe('Verify PerformActions', () => {
const updateLoopFunction = jest.fn();
const showSucAlert = jest.fn();
const showFailAlert = jest.fn();
+ const setBusyLoading = jest.fn();
+ const clearBusyLoading = jest.fn();
LoopActionService.performAction = jest.fn().mockImplementation(() => {
return Promise.resolve({
@@ -76,7 +80,7 @@ describe('Verify PerformActions', () => {
});
});
const component = shallow(<PerformActions loopCache={loopCache}
- loopAction="submit" history={historyMock} updateLoopFunction={updateLoopFunction} showSucAlert={showSucAlert} showFailAlert={showFailAlert} />)
+ loopAction="submit" history={historyMock} updateLoopFunction={updateLoopFunction} showSucAlert={showSucAlert} showFailAlert={showFailAlert} setBusyLoading={setBusyLoading} clearBusyLoading={clearBusyLoading}/>)
await flushPromises();
component.update();
diff --git a/ui-react/src/components/loop_viewer/svg/SvgGenerator.js b/ui-react/src/components/loop_viewer/svg/SvgGenerator.js
index d718c2e44..7070455e7 100644
--- a/ui-react/src/components/loop_viewer/svg/SvgGenerator.js
+++ b/ui-react/src/components/loop_viewer/svg/SvgGenerator.js
@@ -70,13 +70,15 @@ class SvgGenerator extends React.Component {
}
handleSvgClick(event) {
- if (this.state.clickable) {
- console.debug("svg click event received");
- var elementName = event.target.parentNode.getAttribute('policyId');
- console.info("SVG element clicked", elementName);
- if (elementName !== null) {
- this.props.history.push("/policyModal/"+event.target.parentNode.getAttribute('policyType')+"/"+elementName);
- }
+ console.debug("svg click event received");
+ if (this.state.clickable) {
+ var elementName = event.target.parentNode.getAttribute('policyId');
+ console.info("SVG element clicked", elementName);
+ // Only allow movement to policy editing IF there busyLoadingCOunt is 0,
+ // meaning we are not waiting for refreshStatus to complete, for example
+ if (elementName !== null && !this.props.isBusyLoading()) {
+ this.props.history.push("/policyModal/"+event.target.parentNode.getAttribute('policyType')+"/"+elementName);
+ }
}
}