diff options
8 files changed, 160 insertions, 31 deletions
diff --git a/ui-react/src/LoopUI.js b/ui-react/src/LoopUI.js index 0ee6e6e24..5491ab187 100644 --- a/ui-react/src/LoopUI.js +++ b/ui-react/src/LoopUI.js @@ -130,6 +130,8 @@ export default class LoopUI extends React.Component { this.setBusyLoading = this.setBusyLoading.bind(this); this.clearBusyLoading = this.clearBusyLoading.bind(this); this.isBusyLoading = this.isBusyLoading.bind(this); + this.renderGlobalStyle = this.renderGlobalStyle.bind(this); + this.renderSvg = this.renderSvg.bind(this); } componentWillMount() { @@ -198,10 +200,15 @@ export default class LoopUI extends React.Component { ); } + renderSvg() { + return ( + <SvgGenerator loopCache={this.state.loopCache} clickable={true} generatedFrom={SvgGenerator.GENERATED_FROM_INSTANCE} isBusyLoading={this.isBusyLoading}/> + ) + } renderLoopViewBody() { return ( <LoopViewBodyDivStyled> - <SvgGenerator loopCache={this.state.loopCache} clickable={true} generatedFrom={SvgGenerator.GENERATED_FROM_INSTANCE} isBusyLoading={this.isBusyLoading}/> + {this.renderSvg()} <LoopStatus loopCache={this.state.loopCache}/> <LoopLogs loopCache={this.state.loopCache} /> </LoopViewBodyDivStyled> @@ -223,10 +230,20 @@ export default class LoopUI extends React.Component { } updateLoopCache(loopJson) { - this.setState({ loopCache: new LoopCache(loopJson) }); - this.setState({ loopName: this.state.loopCache.getLoopName() }); + + // If call with an empty object for loopJson, this is a reset to empty + // from someplace like PerformActions for the case where we are "deleting" + // a Control Loop model. Set the loopName to the default. + + if (loopJson === null) { + this.setState({ loopName: OnapConstants.defaultLoopName }); + this.setState({ loopCache: new LoopCache({}) }); + } else { + this.setState({ loopCache: new LoopCache(loopJson) }); + this.setState({ loopName: this.state.loopCache.getLoopName() }); + } console.info(this.state.loopName+" loop loaded successfully"); - } + } showSucAlert(message) { this.setState ({ showSucAlert: true, showMessage:message }); @@ -369,6 +386,13 @@ export default class LoopUI extends React.Component { ); } + renderGlobalStyle() { + return ( + <GlobalClampStyle /> + ); + }; + + renderSpinner() { if (this.isBusyLoading()) { return ( @@ -386,7 +410,7 @@ export default class LoopUI extends React.Component { render() { return ( <StyledMainDiv id="main_div"> - <GlobalClampStyle /> + {this.renderGlobalStyle()} {this.renderRoutes()} {this.renderSpinner()} {this.renderAlertBar()} diff --git a/ui-react/src/api/TemplateService.js b/ui-react/src/api/TemplateService.js index 3a780dd65..7d8a3400e 100644 --- a/ui-react/src/api/TemplateService.js +++ b/ui-react/src/api/TemplateService.js @@ -22,6 +22,23 @@ export default class TemplateService { + static getLoopNames() { + return fetch('/restservices/clds/v2/loop/getAllNames', { method: 'GET', credentials: 'same-origin' }) + .then(function (response) { + console.debug("getLoopNames response received: ", response.status); + if (response.ok) { + return response.json(); + } else { + console.error("getLoopNames query failed"); + return {}; + } + }) + .catch(function (error) { + console.error("getLoopNames error received", error); + return {}; + }); + } + static getAllLoopTemplates() { return fetch('restservices/clds/v2/templates', { method: 'GET', credentials: 'same-origin', }) .then(function (response) { diff --git a/ui-react/src/components/dialogs/Loop/CreateLoopModal.js b/ui-react/src/components/dialogs/Loop/CreateLoopModal.js index a8e8dee1f..5663360a0 100644 --- a/ui-react/src/components/dialogs/Loop/CreateLoopModal.js +++ b/ui-react/src/components/dialogs/Loop/CreateLoopModal.js @@ -37,6 +37,10 @@ const ModalStyled = styled(Modal)` background-color: transparent; ` +const ErrMsgStyled = styled.div` + color: red; +` + export default class CreateLoopModal extends React.Component { constructor(props, context) { super(props, context); @@ -46,17 +50,20 @@ export default class CreateLoopModal extends React.Component { this.handleModelName = this.handleModelName.bind(this); this.handleClose = this.handleClose.bind(this); this.handleDropDownListChange = this.handleDropDownListChange.bind(this); + this.renderSvg = this.renderSvg.bind(this); this.state = { show: true, chosenTemplateName: '', + modelInputErrMsg: '', modelName: '', templateNames: [], fakeLoopCacheWithTemplate: new LoopCache({}) }; } - componentWillMount() { - this.getAllLoopTemplates(); + async componentDidMount() { + await this.getAllLoopTemplates(); + await this.getModelNames(); } handleClose() { @@ -87,6 +94,17 @@ export default class CreateLoopModal extends React.Component { }); } + getModelNames() { + TemplateService.getLoopNames().then(loopNames => { + if (!loopNames) { + loopNames = []; + } + // Remove LOOP_ prefix + let trimmedLoopNames = loopNames.map(str => str.replace('LOOP_', '')); + this.setState({ modelNames: trimmedLoopNames }); + }); + } + handleCreate() { if (!this.state.modelName) { alert("A model name is required"); @@ -109,10 +127,25 @@ export default class CreateLoopModal extends React.Component { }); } - handleModelName = event => { - this.setState({ - modelName: event.target.value - }) + handleModelName(event) { + if (this.state.modelNames.includes(event.target.value)) { + this.setState({ + modelInputErrMsg: 'A model named "' + event.target.value + '" already exists. Please pick another name.', + modelName: event.target.value + }); + return; + } else { + this.setState({ + modelInputErrMsg: '', + modelName: event.target.value + }); + } + } + + renderSvg() { + return ( + <SvgGenerator loopCache={this.state.fakeLoopCacheWithTemplate} clickable={false} generatedFrom={SvgGenerator.GENERATED_FROM_TEMPLATE}/> + ); } render() { @@ -131,15 +164,20 @@ export default class CreateLoopModal extends React.Component { <Form.Group as={Row} style={{alignItems: 'center'}} controlId="formSvgPreview"> <Form.Label column sm="2">Model Preview:</Form.Label> <Col sm="10"> - <SvgGenerator loopCache={this.state.fakeLoopCacheWithTemplate} clickable={false} generatedFrom={SvgGenerator.GENERATED_FROM_TEMPLATE}/> + {this.renderSvg()} </Col> </Form.Group> <Form.Group as={Row} controlId="formPlaintextEmail"> <Form.Label column sm="2">Model Name:</Form.Label> - <input type="text" style={{width: '50%', marginLeft: '1em' }} + <input sm="5" type="text" style={{width: '50%', marginLeft: '1em' }} value={this.state.modelName} onChange={this.handleModelName} /> + <span sm="5"/> + </Form.Group> + <Form.Group as={Row} controlId="formPlaintextEmail"> + <Form.Label column sm="2"> </Form.Label> + <ErrMsgStyled>{this.state.modelInputErrMsg}</ErrMsgStyled> </Form.Group> </Modal.Body> <Modal.Footer> diff --git a/ui-react/src/components/dialogs/Loop/CreateLoopModal.test.js b/ui-react/src/components/dialogs/Loop/CreateLoopModal.test.js index 5b6ea9e62..1caa22dc7 100644 --- a/ui-react/src/components/dialogs/Loop/CreateLoopModal.test.js +++ b/ui-react/src/components/dialogs/Loop/CreateLoopModal.test.js @@ -29,16 +29,18 @@ import TemplateService from '../../../api/TemplateService'; describe('Verify CreateLoopModal', () => { it('Test the render method', async () => { - const flushPromises = () => new Promise(setImmediate); + const flushPromises = () => new Promise(setImmediate); TemplateService.getAllLoopTemplates = jest.fn().mockImplementation(() => { - return Promise.resolve([{"name":"template1"},{"name":"template2"}]); - }); - - const component = shallow(<CreateLoopModal/>); + return Promise.resolve([{"name":"template1"},{"name":"template2"}]); + }); + TemplateService.getLoopNames = jest.fn().mockImplementation(() => { + return Promise.resolve([]); + }); + + const component = shallow(<CreateLoopModal/>); expect(component).toMatchSnapshot(); - await flushPromises(); - component.update(); - + await flushPromises(); + component.update(); expect(component.state('templateNames')).toStrictEqual([{"label": "template1", "value": "template1", "templateObject": {"name": "template1"}}, {"label": "template2", "value": "template2","templateObject": {"name": "template2"}}]); }); @@ -61,17 +63,22 @@ describe('Verify CreateLoopModal', () => { expect(component.state('fakeLoopCacheWithTemplate').getLoopName()).toEqual("fakeLoop"); }); - - - it('handleModelName event', () => { + it('handleModelName event', async () => { + const flushPromises = () => new Promise(setImmediate); + TemplateService.getAllLoopTemplates = jest.fn().mockImplementation(() => { + return Promise.resolve([{"name":"template1"},{"name":"template2"}]); + }); + TemplateService.getLoopNames = jest.fn().mockImplementation(() => { + return Promise.resolve([]); + }); const event = {target: {value : "model1"} }; const component = shallow(<CreateLoopModal/>); + await flushPromises(); component.find('input').simulate('change', event); component.update(); expect(component.state('modelName')).toEqual("model1"); }); - it('Test handleClose', () => { const historyMock = { push: jest.fn() }; const handleClose = jest.spyOn(CreateLoopModal.prototype,'handleClose'); diff --git a/ui-react/src/components/dialogs/Loop/OpenLoopModal.js b/ui-react/src/components/dialogs/Loop/OpenLoopModal.js index 7ca90b493..b45df6502 100644 --- a/ui-react/src/components/dialogs/Loop/OpenLoopModal.js +++ b/ui-react/src/components/dialogs/Loop/OpenLoopModal.js @@ -48,7 +48,8 @@ export default class OpenLoopModal extends React.Component { this.handleOpen = this.handleOpen.bind(this); this.handleClose = this.handleClose.bind(this); this.handleDropDownListChange = this.handleDropDownListChange.bind(this); - this.showReadOnly = props.showReadOnly ? props.showReadOnly : true; + this.renderSvg = this.renderSvg.bind(this); + this.showReadOnly = props.showReadOnly !== undefined ? props.showReadOnly : true; this.state = { show: true, chosenLoopName: '', @@ -90,6 +91,12 @@ export default class OpenLoopModal extends React.Component { this.props.loadLoopFunction(this.state.chosenLoopName); } + renderSvg() { + return( + <SvgGenerator loopCache={this.state.loopCacheOpened} clickable={false} generatedFrom={SvgGenerator.GENERATED_FROM_INSTANCE}/> + ); + } + render() { return ( <ModalStyled size="xl" show={this.state.show} onHide={this.handleClose} backdrop="static" keyboard={false} > @@ -107,7 +114,7 @@ export default class OpenLoopModal extends React.Component { <Form.Group as={Row} style={{alignItems: 'center'}} controlId="formSvgPreview"> <Form.Label column sm="2">Model Preview:</Form.Label> <Col sm="10"> - <SvgGenerator loopCache={this.state.loopCacheOpened} clickable={false} generatedFrom={SvgGenerator.GENERATED_FROM_INSTANCE}/> + {this.renderSvg()} </Col> </Form.Group> {this.showReadOnly === true ? diff --git a/ui-react/src/components/dialogs/Loop/__snapshots__/CreateLoopModal.test.js.snap b/ui-react/src/components/dialogs/Loop/__snapshots__/CreateLoopModal.test.js.snap index e69b809c7..b05781641 100644 --- a/ui-react/src/components/dialogs/Loop/__snapshots__/CreateLoopModal.test.js.snap +++ b/ui-react/src/components/dialogs/Loop/__snapshots__/CreateLoopModal.test.js.snap @@ -107,6 +107,7 @@ exports[`Verify CreateLoopModal Test the render method 1`] = ` </FormLabel> <input onChange={[Function]} + sm="5" style={ Object { "marginLeft": "1em", @@ -116,6 +117,30 @@ exports[`Verify CreateLoopModal Test the render method 1`] = ` type="text" value="" /> + <span + sm="5" + /> + </FormGroup> + <FormGroup + as={ + Object { + "$$typeof": Symbol(react.forward_ref), + "defaultProps": Object { + "noGutters": false, + }, + "render": [Function], + } + } + controlId="formPlaintextEmail" + > + <FormLabel + column={true} + sm="2" + srOnly={false} + > + + </FormLabel> + <styled.div /> </FormGroup> </ModalBody> <ModalFooter> diff --git a/ui-react/src/components/dialogs/Tosca/ViewLoopTemplatesModal.js b/ui-react/src/components/dialogs/Tosca/ViewLoopTemplatesModal.js index 962ab4bda..10473944f 100644 --- a/ui-react/src/components/dialogs/Tosca/ViewLoopTemplatesModal.js +++ b/ui-react/src/components/dialogs/Tosca/ViewLoopTemplatesModal.js @@ -92,6 +92,7 @@ export default class ViewLoopTemplatesModal extends React.Component { constructor(props, context) { super(props, context); this.handleClose = this.handleClose.bind(this); + this.renderSvg = this.renderSvg.bind(this); this.getLoopTemplate = this.getLoopTemplate.bind(this); this.getAllLoopTemplates(); } @@ -120,6 +121,12 @@ export default class ViewLoopTemplatesModal extends React.Component { this.props.history.push('/') } + renderSvg() { + return( + <SvgGenerator loopCache={this.state.fakeLoopCacheWithTemplate} clickable={false} generatedFrom={SvgGenerator.GENERATED_FROM_TEMPLATE}/> + ) + } + render() { return ( <ModalStyled size="xl" show={this.state.show} onHide={this.handleClose} backdrop="static" keyboard={false}> @@ -139,7 +146,7 @@ export default class ViewLoopTemplatesModal extends React.Component { }) }} /> - <SvgGenerator loopCache={this.state.fakeLoopCacheWithTemplate} clickable={false} generatedFrom={SvgGenerator.GENERATED_FROM_TEMPLATE}/> + {this.renderSvg()} </Modal.Body> <Modal.Footer> <Button variant="secondary" onClick={this.handleClose}>Close</Button> diff --git a/ui-react/src/components/loop_viewer/svg/SvgGenerator.js b/ui-react/src/components/loop_viewer/svg/SvgGenerator.js index 7070455e7..f5f5047ba 100644 --- a/ui-react/src/components/loop_viewer/svg/SvgGenerator.js +++ b/ui-react/src/components/loop_viewer/svg/SvgGenerator.js @@ -28,11 +28,12 @@ import OnapConstant from '../../../utils/OnapConstants'; const DivStyled = styled.div` overflow-x: scroll; + display: flex; width: 100%; height: 100%; ` -const emptySvg = (<svg> <text x="20" y="40">No LOOP (SVG)</text> </svg>); +const emptySvg = (<svg> <text x="60" y="40">No LOOP (SVG)</text> </svg>); class SvgGenerator extends React.Component { boxWidth = 200; @@ -228,11 +229,14 @@ class SvgGenerator extends React.Component { render() { var allTheElements = this.renderSvg(); var svgWidth = this.boxWidth*allTheElements.length; - var svgHeight = this.boxHeight+100; + var svgHeight = this.boxHeight+50; return ( + <DivStyled onClick={this.handleSvgClick} > - <svg height={svgHeight} width={svgWidth} preserveAspectRatio="none"> + <svg height={svgHeight} width={svgWidth} viewBox="0,0,{svgWidth},{svgHeight}" preserveAspectRatio="none"> + <svg x="-50" y="25"> {allTheElements} + </svg> </svg> </DivStyled> ); |