diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b2961ebe3643660bfb54d01ab6661207635133ed..850fae6adeeb3b95e23118c6222a4341b41d2da4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -465,8 +465,8 @@ deploy-tmss-ua: - cd SAS/TMSS - ssh lofarsys@tmss-ua.control.lofar "docker-compose -f docker-compose-ua.yml down" - scp docker-compose-ua.yml lofarsys@tmss-ua.control.lofar:~/ - - ssh lofarsys@tmss-ua.control.lofar "docker pull ${CI_NEXUS_REGISTRY}/tmss_django:$CI_COMMIT_SHORT_SHA" - - ssh lofarsys@tmss-ua.control.lofar "docker tag ${CI_NEXUS_REGISTRY}/tmss_django:$CI_COMMIT_SHORT_SHA ${CI_NEXUS_REGISTRY}/tmss_django:latest" + - ssh lofarsys@tmss-ua.control.lofar "docker pull ${CI_NEXUS_REGISTRY_LOCATION}/tmss_django:$CI_COMMIT_SHORT_SHA" + - ssh lofarsys@tmss-ua.control.lofar "docker tag ${CI_NEXUS_REGISTRY_LOCATION}/tmss_django:$CI_COMMIT_SHORT_SHA ${CI_NEXUS_REGISTRY_LOCATION}/tmss_django:latest" - ssh lofarsys@tmss-ua.control.lofar "docker-compose -f docker-compose-ua.yml up -d" needs: - dockerize_TMSS diff --git a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/StationCalibration.parset b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/StationCalibration.parset index e733edc7495621b2d6ff669c80a248c6fe53a081..a445be60775748adf9ec92a05182c6961e87d39c 100644 --- a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/StationCalibration.parset +++ b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/StationCalibration.parset @@ -3223,19 +3223,19 @@ PIC.Core.CS302LBA.LBA_OUTER.LBA_30_90.delay.Y = 1.225299e-05 PIC.Core.CS302LBA.LBA_OUTER.LBA_30_90.phase0.X = 0.000000e+00 PIC.Core.CS302LBA.LBA_OUTER.LBA_30_90.phase0.Y = 0.000000e+00 PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_10_70.delay.X = 1.225280e-05 -PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_10_70.delay.Y = 1.225299e-05 +PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_10_70.delay.Y = 1.226199e-05 PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_10_70.phase0.X = 0.000000e+00 PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_10_70.phase0.Y = 0.000000e+00 PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_10_90.delay.X = 1.225280e-05 -PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_10_90.delay.Y = 1.225299e-05 +PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_10_90.delay.Y = 1.226199e-05 PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_10_90.phase0.X = 0.000000e+00 PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_10_90.phase0.Y = 0.000000e+00 PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_30_70.delay.X = 1.225280e-05 -PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_30_70.delay.Y = 1.225299e-05 +PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_30_70.delay.Y = 1.226199e-05 PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_30_70.phase0.X = 0.000000e+00 PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_30_70.phase0.Y = 0.000000e+00 PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_30_90.delay.X = 1.225280e-05 -PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_30_90.delay.Y = 1.225299e-05 +PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_30_90.delay.Y = 1.226199e-05 PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_30_90.phase0.X = 0.000000e+00 PIC.Core.CS302LBA.LBA_SPARSE_EVEN.LBA_30_90.phase0.Y = 0.000000e+00 PIC.Core.CS302LBA.LBA_SPARSE_ODD.LBA_10_70.delay.X = 1.225280e-05 diff --git a/SAS/TMSS/docker-compose-ua.yml b/SAS/TMSS/docker-compose-ua.yml index 73b699d14c94f2d0606c3242d4b964f593d05b68..f4f97a5ecbf0eab8b46bf8406dff7b06f02f297c 100644 --- a/SAS/TMSS/docker-compose-ua.yml +++ b/SAS/TMSS/docker-compose-ua.yml @@ -7,20 +7,29 @@ services: ports: - 5672:5672 - 15672:15672 - oidc-provider: - image: nexus.cep4.control.lofar:18080/tmss_testprovider:latest - restart: unless-stopped - hostname: oidc-provider + django: + image: git.astron.nl:5000/ro/lofar/tmss_django:latest + container_name: django-gunicorn + restart: always + volumes: + - static_volume:/opt/lofar/staticfiles env_file: - - ./.env - ports: - - "8088:8088" - web: - image: nexus.cep4.control.lofar:18080/tmss_django:latest - hostname: tmss-ua - restart: on-failure - env_file: - - ./.env - command: bash -c 'source /opt/lofar/lofarinit.sh && ALLOWED_HOSTS=* tmss_test_environment -H 0.0.0.0 -P tmss-ua -p 8008 --data' + - ~/.lofar/.env + command: + "/bin/bash -c 'source lofarinit.sh; + gunicorn lofar.sas.tmss.tmss.wsgi --worker-class=gevent --workers=4 --bind=0.0.0.0:8001'" + depends_on: + - rabbitmq + tmss-ua.control.lofar: + image: nginx:1.17 + container_name: ngx ports: - "8008:8008" + volumes: + - static_volume:/opt/lofar/staticfiles + - ~/.lofar/etc/nginx:/etc/nginx/conf.d + depends_on: + - django + +volumes: + static_volume: \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js index 129b8232bf12d6353bc1507d27f1b17a1821b683..a35c4877a60697883530df950ccd801e272e6dbc 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js @@ -2225,7 +2225,7 @@ function ViewTable(props) { Header: 'Action', id: 'Action', accessor: props.keyaccessor, - Cell: props => <button className='p-link' onClick={navigateTo(props)} ><i className="fa fa-eye" style={{ cursor: 'pointer' }}></i></button>, + Cell: props => <Link to={{pathname: props.cell.row.values['actionpath']}}className='p-link' onClick={(e) => navigateTo(e, props)} ><i className="fa fa-eye" style={{ cursor: 'pointer' }}></i></Link>, disableFilters: true, disableSortBy: true, //isVisible: defaultdataheader.includes(props.keyaccessor), diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/components/AppMenu.js b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/AppMenu.js index c207fc50489805bfeaa1456038b65609f39b523b..59ae81de1d231ccf7ed00b118af83f7a15deb562 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/components/AppMenu.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/AppMenu.js @@ -29,6 +29,9 @@ class AppSubmenu extends Component { } onMenuItemClick(event, item, index) { + if(event.ctrlKey){ + return; + } event.preventDefault(); //avoid processing disabled items if(item.disabled) { @@ -98,7 +101,7 @@ class AppSubmenu extends Component { if (item.to) { return ( //<NavLink activeClassName="active-route" to={item.to} onClick={(e) => this.onMenuItemClick(e, item, i)} exact target={item.target}> - <a activeClassName="active-route" onClick={(e) => this.onMenuItemClick(e, item, i)} exact target={item.target}> + <a href={item.to} activeClassName="active-route" onClick={(e) => this.onMenuItemClick(e, item, i)} exact target={item.target}> {content} {/* </NavLink> */} </a> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/components/CustomDialog.js b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/CustomDialog.js index 36fdcb1febdf2569f4944e362bfb076c043ffa43..c8b2fc0f1307fb23cf51dbe4829d42abecbd4015 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/components/CustomDialog.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/CustomDialog.js @@ -23,7 +23,8 @@ export class CustomDialog extends Component { // const isError = this.props.type.toLowerCase()==='error'; let iconClass = isConfirm?"pi-question-circle pi-warning":(isWarning?"pi-info-circle pi-warning": (isSuccess?"pi-check-circle pi-success":"pi-times-circle pi-danger")); return ( - <div className={`custom-dlg p-grid`} data-testid="confirm_dialog"> + <div className={`custom-dlg p-grid`} data-testid="confirm_dialog" + style={{opacity: (this.props.opacity===undefined || this.props.opacity===null)?1:this.props.opacity}}> <Dialog header={this.props.header} visible={this.props.visible} style={{width: this.props.width?this.props.width:'25vw'}} inputId="confirm_dialog" modal={true} onHide={this.props.onClose} diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/components/PageHeader.js b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/PageHeader.js index 06521dd203b56cbe59f48143d249bf0fcca8581b..fdd553470409b4baabaa44d163371107e655918b 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/components/PageHeader.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/components/PageHeader.js @@ -21,6 +21,7 @@ export default ({ title, subTitle, actions, ...props}) => { }; const onButtonClick = (e, action) => { + e.preventDefault() if (action.actOn && action.actOn === 'click') { action.props.callback(e); } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_suSummary.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_suSummary.scss index ab73c0560d155915f052c300f41c36280658e4ac..5dd93b1f821a40844c462dde3c230dcc3e65dc8d 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_suSummary.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_suSummary.scss @@ -19,12 +19,16 @@ } .task-summary>label { + margin-top: 0.75em; margin-bottom: 0px; } .task-summary #block_container { margin-top: 0px; } +.task-summary>div { + margin-top: -2.5em; +} /* * STYLE 3 diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/edit.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/edit.js index fc08193ce91dbb54eebfc35de12f5f0c0d27b542..5c11fa8258d7dbf7be0a819e317ba904eececc47 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/edit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/edit.js @@ -403,9 +403,8 @@ export class CycleEdit extends Component { */ cancelEdit() { publish('edit-dirty', false); - this.props.history.goBack(); + this.props.history.length>1?this.props.history.goBack():this.props.history.push(`/cycle/view/${this.props.match.params.id}`); this.setState({showDialog: false}); - this.props.history.goBack(); } render() { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/view.js index 3d6b3a4e5fff34dc69d0155b71e4dbabc5589478..f1574f653ba783ced7d03bc887167c62474e1647 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/view.js @@ -35,6 +35,7 @@ export class CycleView extends Component { } else if (this.props.location.state && this.props.location.state.id) { this.state.cycleId = this.props.location.state.id; } + this.cancelView = this.cancelView.bind(this); this.state.redirect = this.state.cycleId?"":'/cycle' // If no cycle id is passed, redirect to cycle list page this.resourceUnitMap = UnitConverter.resourceUnitMap; // Resource unit conversion factor and constraints } @@ -50,6 +51,10 @@ export class CycleView extends Component { this.setState({userrole: permission}); } + cancelView(){ + this.props.history.length>1?this.props.history.goBack():this.props.history.push(`/cycle`); + } + /** * To get the cycle details from the backend using the service * @@ -110,7 +115,8 @@ export class CycleView extends Component { disabled: cycle.edit? !cycle.edit:true, props:{ pathname: `/cycle/edit/${this.state.cycle.name}`, state: {id: this.state.cycle?this.state.cycle.name:''}}}, - {icon: 'fa-window-close',link: this.props.history.goBack}]}/> + {icon: 'fa-window-close', + type: 'button', actOn: 'click', props:{ callback: this.cancelView }}]}/> { this.state.isLoading && <AppLoader /> } { this.state.cycle && <React.Fragment> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/view.js index 2bf59fe21e01e20a720199cd760caf0d0bcaf649..c929cc47ff51366a4f128600ed27d16622d3b9f0 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Project/view.js @@ -32,6 +32,7 @@ export class ProjectView extends Component { } else if (this.props.location.state && this.props.location.state.id) { this.state.projectId = this.props.location.state.id; } + this.cancelView = this.cancelView.bind(this); this.state.redirect = this.state.projectId?"":'/project' // If no project id is passed, redirect to Project list page this.resourceUnitMap = UnitConverter.resourceUnitMap; // Resource unit conversion factor and constraints } @@ -64,6 +65,9 @@ export class ProjectView extends Component { } } + cancelView(){ + this.props.history.length>1?this.props.history.goBack():this.props.history.push(`/project`); + } /** * To get the project details from the backend using the service * @@ -116,7 +120,7 @@ export class ProjectView extends Component { disabled: this.state.permissionById[this.state.projectId].edit?!this.state.permissionById[this.state.projectId].edit:true, props : { pathname: `/project/edit/${this.state.project.name}`, state: {id: this.state.project?this.state.project.name:''&& this.state.project}}}, - {icon:'fa-window-close',title: 'Click to Close Project View', link: this.props.history.goBack}]}/> + {icon:'fa-window-close',title: 'Click to Close Project View', type: 'button', actOn: 'click', props:{ callback: this.cancelView }}]}/> { this.state.isLoading && <AppLoader /> } { this.state.project && <React.Fragment> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.create.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.create.js index bfa70d3a2fd71f4aedb02a59a8540a70a0962288..9e5b75d2c0f4ca41322be9a61110f2cf864f76f7 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.create.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.create.js @@ -364,9 +364,8 @@ export class ReservationCreate extends Component { */ cancelCreate() { publish('edit-dirty', false); - this.props.history.goBack(); + this.props.history.length>1?this.props.history.goBack():this.props.history.push(`/su/timelineview`); this.setState({ showDialog: false }); - this.props.history.goBack(); } /** diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.edit.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.edit.js index 0263f87a5c5ea5823cd17c10c423b9b78fda99a8..6507035bfa8c7eb293afa9a97351c89c79fad91d 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.edit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.edit.js @@ -164,7 +164,7 @@ export class ReservationEdit extends Component { const reserId = this.props.match ? this.props.match.params.id : null; publish('edit-dirty', false); this.setState({ showDialog: false }); - this.props.history.goBack(); + this.props.history.length>1?this.props.history.goBack():this.props.history.push(`/reservation/view/${this.props.match.params.id}`); } /** diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.list.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.list.js index aff992ee5c742cd9420160633e040d3539b7581a..351f79852bb544acdd3f6ed3eb4de310fecf4e58 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.list.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.list.js @@ -132,6 +132,7 @@ export class ReservationList extends Component{ this.deleteReservations = this.deleteReservations.bind(this); this.closeDialog = this.closeDialog.bind(this); this.getReservationDialogContent = this.getReservationDialogContent.bind(this); + this.closeList = this.closeList.bind(this); } async componentDidMount() { @@ -375,6 +376,10 @@ export class ReservationList extends Component{ this.setState({dialogVisible: false}); } + closeList(){ + this.props.history.length>1?this.props.history.goBack():this.props.history.push(`/su/timelineview`); + } + /** * Create confirmation dialog details */ @@ -440,7 +445,7 @@ export class ReservationList extends Component{ title:permissions.create?'Add Reservation': "Don't have permission to add new Reservation", disabled: permissions.create? !permissions.create: true, props : { pathname: `/reservation/create`}}, - {icon: 'fa-window-close', title:'Click to close Reservation list', props : { pathname: `/su/timelineview`}}]}/> + {icon: 'fa-window-close', title:'Click to close Reservation list', type: 'button', actOn: 'click', props:{ callback: this.closeList }}]}/> {this.state.isLoading? <AppLoader /> : (this.state.reservationsList && this.state.reservationsList.length>0) ? <> {permissions.list? diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js index 990c1afc455317df16cbb52108fedb0fa2825449..2c6eaa352b1431300bf8f968aad7f296d4164652 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js @@ -701,7 +701,8 @@ class SchedulingUnitList extends Component{ /** * Check and delete the selected Scheduling Unit(s) */ - checkAndDeleteSchedulingUnit() { + checkAndDeleteSchedulingUnit(e) { + e.preventDefault(); this.pageUpdated = false; this.suDraftsList = []; this.suBlueprintList = []; @@ -936,7 +937,8 @@ class SchedulingUnitList extends Component{ } //Confirmation to Copy Scheduling Unit - confirmCopyingSU() { + confirmCopyingSU(e) { + e.preventDefault(); if (this.selectedRows.length > 0) { let dialog = this.state.dialog; dialog.type = "confirmation"; @@ -1040,7 +1042,8 @@ class SchedulingUnitList extends Component{ * If no selected scheduling unit is cancellable, show info to select a cancellable scheduling unit. * */ - confirmCancelSchedulingUnit() { + confirmCancelSchedulingUnit(e) { + e.preventDefault(); this.pageUpdated = false; let selectedBlueprints = this.selectedRows.filter(schedulingUnit => { return schedulingUnit.type === 'Blueprint' && @@ -1263,7 +1266,8 @@ class SchedulingUnitList extends Component{ /** * Function to invoke form UI to create cleanup task */ - async cleanUpSUTask() { + async cleanUpSUTask(e) { + e.preventDefault(); this.pageUpdated = false; if (this.selectedRows.length > 0) { this.suBlueprintList = _.filter(this.selectedRows, (schedulingUnit) => { return schedulingUnit.type.toLowerCase() === "blueprint"}); @@ -1365,7 +1369,8 @@ class SchedulingUnitList extends Component{ /** * Function to call the SUBCreator component's function to check and create SUBs */ - checkAndCreateSUB() { + checkAndCreateSUB(e) { + e.preventDefault(); if (this.selectedRows.length > 0) { const suBlueprintList = _.filter(this.selectedRows, (schedulingUnit) => { return schedulingUnit.type.toLowerCase() === "blueprint"}); const suDraftsList = _.filter(this.selectedRows, (schedulingUnit) => { return schedulingUnit.type.toLowerCase() === "draft"}); @@ -1414,7 +1419,8 @@ class SchedulingUnitList extends Component{ /** * Confirm to Prevent/Allow Automatic Deletion for selected SU draft(s)/blueprint(s) */ - async confirmAutoDeletion() { + async confirmAutoDeletion(e) { + e.preventDefault(); if (this.selectedRows.length > 0) { let dialog = this.state.dialog; dialog.type = "confirmation"; @@ -1530,7 +1536,7 @@ class SchedulingUnitList extends Component{ <> {this.state.suType === 'Draft' && <> - <a href="#" style={{marginLeft: "5px"}} onClick={this.checkAndCreateSUB} + <a href="#" style={{marginLeft: "5px"}} onClick={(e)=> this.checkAndCreateSUB(e)} title={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.createsub ? "Create Blueprint(s)":`${this.access_denied_message} to create Blueprint(s)`} > <i class= {this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.createsub ?"fa fa-stamp":"fa fa-disabled fa-stamp"} aria-hidden="true" ></i> @@ -1539,28 +1545,28 @@ class SchedulingUnitList extends Component{ } {this.state.suType === 'Blueprint' && <> - <a href="#" style={{marginLeft: "5px"}} onClick={this.cleanUpSUTask} + <a href="#" style={{marginLeft: "5px"}} onClick={(e) => this.cleanUpSUTask(e)} title={this.state.userrole && this.state.userrole.userRolePermission.task_blueprint && this.state.userrole.userRolePermission.task_blueprint.canceltask ? "Create Clean-up Task(s)":`${this.access_denied_message} to create Clean-up Task(s)`} > <i class={this.state.userrole && this.state.userrole.userRolePermission.task_blueprint && this.state.userrole.userRolePermission.task_blueprint.canceltask?"fa fa-recycle":"fa fa-disabled fa-recycle"} aria-hidden="true" ></i> </a> - <a href="#" style={{marginLeft: "5px"}} onClick={this.confirmCancelSchedulingUnit} + <a href="#" style={{marginLeft: "5px"}} onClick={(e) => this.confirmCancelSchedulingUnit(e)} title={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit_blueprint && this.state.userrole.userRolePermission.scheduleunit_blueprint.edit ? "Cancel selected Scheduling Unit(s)":`${this.access_denied_message} to cancel Scheduling Unit(s)`} > <i class={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit_blueprint && this.state.userrole.userRolePermission.scheduleunit_blueprint.edit?"fa fa-ban":"fa fa-disabled fa-ban"} aria-hidden="true" ></i> </a> </> } - <a href="#" style={{marginLeft: "5px"}} onClick={this.confirmAutoDeletion} + <a href="#" style={{marginLeft: "5px"}} onClick={(e) => this.confirmAutoDeletion(e)} title={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.autodeletion ? "Prevent/Allow Automatic Deletion":`${this.access_denied_message} to allow/prevent Automatic Deletion`} > <i class={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.autodeletion?"fa fa-thumbtack":"fa fa-disabled fa-thumbtack"} aria-hidden="true" ></i> </a> - <a href="#" style={{marginLeft: "5px"}} onClick={this.confirmCopyingSU} + <a href="#" style={{marginLeft: "5px"}} onClick={(e) => this.confirmCopyingSU(e)} title={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.copysu ? "Copy Scheduling Unit(s) Draft/Blueprint":`${this.access_denied_message} to copy Scheduling Unit(s)`} > <i class={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.copysu?"fa fa-copy":"fa fa-disabled fa-copy"} ></i> </a> - <a href="#" style={{marginLeft: "5px"}} onClick={this.checkAndDeleteSchedulingUnit} + <a href="#" style={{marginLeft: "5px"}} onClick={(e) => this.checkAndDeleteSchedulingUnit(e)} title={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.delete ? "Delete selected Scheduling Unit(s)":`${this.access_denied_message} to delete Scheduling Unit(s)`} > <i class={this.state.userrole && this.state.userrole.userRolePermission.scheduleunit && this.state.userrole.userRolePermission.scheduleunit.delete?"fa fa-trash":"fa fa-disabled fa fa-trash"} aria-hidden="true" ></i> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js index 521de333fb73efe8270b8aabf42c01e90d8226ea..0ba2ef4aba472b929be1553ebe02246e69ea8f87 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js @@ -203,6 +203,7 @@ class ViewSchedulingUnit extends Component { this.getCleanUpDialogContent = this.getCleanUpDialogContent.bind(this); this.cleanUpSUTask = this.cleanUpSUTask.bind(this); this.confirmAutoDeletion = this.confirmAutoDeletion.bind(this); + this.cancelView = this.cancelView.bind(this); this.getFilterOptions = this.getFilterOptions.bind(this); } @@ -262,6 +263,10 @@ class ViewSchedulingUnit extends Component { } + cancelView(){ + this.props.history.length>1?this.props.history.goBack():this.props.history.push(`/schedulingunit`); + } + toggleBySorting = (sortData) => { UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: sortData }); } @@ -316,7 +321,7 @@ class ViewSchedulingUnit extends Component { await Promise.all(tasks.map(async task => { task.status_logs = task.tasktype === "Blueprint" ? this.subtaskComponent(task) : ""; //Displaying SubTask ID of the 'control' Task - const subTaskIds = task.subTasks ? task.subTasks.filter(sTask => sTask.subTaskTemplate?(sTask.subTaskTemplate.name.indexOf('control') >= 0):false) : []; + const subTaskIds = task.subTasks ? task.subTasks.filter(sTask => sTask.primary === true) : []; const promise = []; subTaskIds.map(subTask => promise.push(ScheduleService.getSubtaskOutputDataproduct(subTask.id))); const dataProducts = promise.length > 0 ? await Promise.all(promise) : []; @@ -386,7 +391,7 @@ class ViewSchedulingUnit extends Component { disabled: userPermissions.scheduleunit && userPermissions.scheduleunit.delete?canDelete?false:true : true, type: 'button', actOn: 'click', props: { callback: this.showDeleteSUConfirmation } }); - this.actions.push({ icon: 'fa-window-close', title: 'Click to Close Scheduling Unit View', link: this.props.history.goBack }); + this.actions.push({ icon: 'fa-window-close', title: 'Click to Close Scheduling Unit View', type: 'button', actOn: 'click', props:{ callback: this.cancelView }}); if (this.props.match.params.type ==='draft') { let blueprintExist = this.state.scheduleunit && this.state.scheduleunit.scheduling_unit_blueprints && this.state.scheduleunit.scheduling_unit_blueprints.length>0; if(isIngestPresent) { @@ -1404,14 +1409,14 @@ class ViewSchedulingUnit extends Component { <> {this.props.match.params.type === 'draft' && <> - <a href="#" onClick={this.confirmCancelTasks} + <button className="p-link" href="#" onClick={this.confirmCancelTasks} title={userPermissions.task_draft.canceltask?"Cancel selected Task(s)": `${this.access_denied_message} to cancel Task(s)`}> <i class={userPermissions.task_draft.canceltask?"fa fa-ban":"fa fa-ban fa-disabled"} aria-hidden="true" ></i> - </a> - <a href="#" style={{ pointerEvents: this.props.disabled ? 'none' : 'auto' }}onClick={this.confirmDeleteTasks} + </button> + <button className="p-link" href="#" style={{ pointerEvents: this.props.disabled ? 'none' : 'auto' }}onClick={this.confirmDeleteTasks} title={userPermissions.task.delete?"Delete selected Task(s)": `${this.access_denied_message} to delete Task(s)`} > <i class={userPermissions.task.delete?"fa fa-trash":"fa fa-trash fa-disabled"} aria-hidden="true" ></i> - </a> + </button> </> } {this.props.match.params.type === 'blueprint' && diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/create.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/create.js index 069dd9273fb2ae3ae9ffad52bc369fb15a57ad63..1e057e96251ac18e891b7a9f2e7ac00600bebd8b 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/create.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/create.js @@ -449,9 +449,8 @@ export class SchedulingUnitCreate extends Component { */ cancelCreate() { publish('edit-dirty', false); - this.props.history.goBack(); + this.props.history.length>1?this.props.history.goBack():this.props.history.push('/schedulingunit'); this.setState({showDialog: false}); - this.props.history.goBack(); } constraintStrategy(e){ diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/edit.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/edit.js index 91bddd3e2f2ceaab7c338f4649272b221725f08b..a34c98651951fd8cb8bf54d08e87d4b6dda5e707 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/edit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/edit.js @@ -430,9 +430,8 @@ export class EditSchedulingUnit extends Component { */ cancelCreate() { publish('edit-dirty', false); - this.props.history.goBack(); + this.props.history.length>1?this.props.history.goBack():this.props.history.push(`/schedulingunit`); this.setState({showDialog: false}); - this.props.history.goBack(); } constraintStrategy(schema, initValue){ diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/excelview.schedulingset.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/excelview.schedulingset.js index a6461e52dfd4deb0697689f4097df9887296ad61..b43942a7d7f9bdc39da30a39da78696cef8c242c 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/excelview.schedulingset.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/excelview.schedulingset.js @@ -167,7 +167,8 @@ export class SchedulingSetCreate extends Component { this.agSUWithDefaultValue = {'id': 0, 'suname': '', 'sudesc': ''}; this.emptyAGSU = {}; this.priorityQueuelist = []; - + this.intervelTrigger = null; + this.onProjectChange = this.onProjectChange.bind(this); this.setSchedulingSetParams = this.setSchedulingSetParams.bind(this); this.onStrategyChange = this.onStrategyChange.bind(this); @@ -200,6 +201,7 @@ export class SchedulingSetCreate extends Component { this.applyChanges = this.applyChanges.bind(this); this.getSchedulingDialogContent = this.getSchedulingDialogContent.bind(this); this.setSUSet = this.setSUSet.bind(this); + this.copyClipText = this.copyClipText.bind(this); this.formRules = { // Form validation rules project: {required: true, message: "Select project to get Scheduling Sets"}, @@ -2141,7 +2143,7 @@ export class SchedulingSetCreate extends Component { */ cancelCreate() { publish('edit-dirty', false); - this.setState({redirect: '/schedulingunit'}); + this.props.history.length>1?this.props.history.goBack():this.props.history.push(`/schedulingunit`); } /** @@ -2183,14 +2185,10 @@ export class SchedulingSetCreate extends Component { * Read Data from clipboard */ async readClipBoard(){ - try{ - const queryOpts = { name: 'clipboard-read', allowWithoutGesture: true }; - // await navigator.permissions.query(queryOpts); - let data = await navigator.clipboard.readText(); - return data; - }catch(err){ - console.log("Error",err); - } + const queryOpts = { name: 'clipboard-read', allowWithoutGesture: true }; + // await navigator.permissions.query(queryOpts); + let data = await navigator.clipboard.readText(); + return data; } /** @@ -2253,52 +2251,27 @@ export class SchedulingSetCreate extends Component { this.growl.show({severity: 'error', summary: 'Warning', detail: 'Unable to copy the data to clipboard'}); } } - - /* if (navigator.userAgent.indexOf('Firefox')>=0) { - if (navigator.appVersion.indexOf("Win") != -1) { - const queryOpts = { name: 'clipboard-write', allowWithoutGesture: true }; - await navigator.permissions.query(queryOpts); - await navigator.clipboard.writeText(clipboardData); - } else { - const elem = document.createElement('textarea'); - elem.value = clipboardData; - document.body.appendChild(elem); - elem.select(); - document.execCommand('copy'); - document.body.removeChild(elem); - } - } else { - const elem = document.createElement('textarea'); - elem.value = clipboardData; - document.body.appendChild(elem); - elem.select(); - document.execCommand('copy'); - document.body.removeChild(elem); - // await navigator.clipboard.writeText(clipboardData); - } */ - const headerText = (this.state.copyHeader) ?'with Header' : ''; this.growl.show({severity: 'success', summary: '', detail: selectedRows.length+' row(s) copied to clipboard '+headerText }); } + /** * Copy data to/from clipboard * @param {*} e */ async clipboardEvent(e){ var key = e.which || e.keyCode; - var ctrl = e.ctrlKey ? e.ctrlKey : ((key === 17) ? true : false); - if ( key === 67 && ctrl ) { - //Ctrl+C + var ctrl = e.ctrlKey ? e.ctrlKey : ((key === 17 || key === 91) ? true : false); + if ( ctrl && (key === 67 || key === 45) ) { //Ctrl+C(Windows & linux) or Cmd+C(Mac) or Ctrl+Insert(windows & linux) this.copyToClipboard(); } - else if ( key === 86 && ctrl ) { // Ctrl+V - // If browser is Firefox, show the dialog to paste clipboard content and - if (navigator.appVersion.indexOf("Win") != -1 && navigator.userAgent.indexOf('Firefox')<0) { + else if ( (ctrl && key === 86) || (e.shiftKey && key === 45) ) { // Ctrl+V(windows & linux) or Cmd+V or Shift+Insert(windows & linux) + try { + var clipText = await this.readClipBoard(); + await this.setState({clipText: clipText}); this.copyFromClipboard(); - } else { - this.setState({ - confirmDialogVisible: true, - }); + } catch(error) { + //this.setState({disabled: 'none'}) this.callBackFunction = this.copyFromClipboard; this.onCancel = () => { this.setState({confirmDialogVisible: false, clipText: ''}); @@ -2313,32 +2286,15 @@ export class SchedulingSetCreate extends Component { this.dialogHeader = "Paste Copied Data"; this.dialogMsg = "Paste copied content in the field and click Ok to paste to the selected row(s)"; this.dialogContent = this.pasteTextField; - this.actions = [{id: "ok_btn", title: "Ok", callback: this.copyFromClipboard}, + this.actions = [ + {id: "ok_btn", title: "Ok", callback: this.copyFromClipboard}, {id: "cancel_btn", title: "Cancel", callback: this.onCancel}]; + await this.setState({confirmDialogVisible: true}); + if(document.getElementById("clipTextField")) { + document.getElementById("clipTextField").focus(); + } + setTimeout(this.copyFromClipboard, 1); } - /* if (navigator.userAgent.indexOf('Firefox')>=0) { - this.setState({ - confirmDialogVisible: true, - }); - this.callBackFunction = this.copyFromClipboard; - this.onCancel = () => { - this.setState({confirmDialogVisible: false, clipText: ''}); - this.actions = null; - }; - this.onClose = () => { - this.setState({confirmDialogVisible: false, clipText: ''}); - this.actions = null; - }; - this.showIcon = false; - this.dialogType = "confirmation"; - this.dialogHeader = "Paste Copied Data"; - this.dialogMsg = "Paste copied content in the field and click Ok to paste to the selected row(s)"; - this.dialogContent = this.pasteTextField; - this.actions = [{id: "ok_btn", title: "Ok", callback: this.copyFromClipboard}, - {id: "cancel_btn", title: "Cancel", callback: this.onCancel}] - } else { - this.copyFromClipboard(); - }*/ } } @@ -2347,22 +2303,25 @@ export class SchedulingSetCreate extends Component { * @returns Component */ pasteTextField() { - return <textarea value={this.state.clipText} style={{width: '100%'}} autoFocus - onChange={e => this.setState({clipText: e.target.value})}></textarea>; + return <textarea id="clipTextField" value={this.state.clipText} style={{width: '100%'}} autoFocus + onChange={(e) => {this.copyClipText(e)}} placeholder="Paste your text here..." />; } - + copyClipText(e){ + this.setState({clipText: e.target.value}) + } /** * Function to copy the data from clipboard */ async copyFromClipboard(){ try { + //this.setState({}) var selectedRows = this.state.gridApi.getSelectedNodes(); this.tmpRowData = this.state.rowData; let dataRowCount = this.state.totalCount; //Read Clipboard Data - let clipboardData = this.state.clipText?this.state.clipText:await this.readClipBoard(); + let clipboardData = this.state.clipText?this.state.clipText:null;//await this.readClipBoard(); let selectedRowIndex = 0; if (selectedRows){ await selectedRows.map(selectedRow =>{ @@ -2833,7 +2792,9 @@ export class SchedulingSetCreate extends Component { } <CustomDialog type={this.dialogType} visible={this.state.confirmDialogVisible} width={this.dialogWidth} height={this.dialogHeight} header={this.dialogHeader} message={this.dialogMsg} - content={this.dialogContent} onClose={this.onClose} onCancel={this.onCancel} onSubmit={this.callBackFunction} + opacity={this.dialogHeader.startsWith("Paste")?0:1} + content={this.dialogContent} onClose={this.onClose} + onCancel={this.onCancel} onSubmit={this.callBackFunction} showIcon={this.showIcon} actions={this.actions}> </CustomDialog> <CustomPageSpinner visible={this.state.showSpinner} /> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js index c8784e6282287e5a80e6deccb958f7e5a77e3d31..b796d2efda6991b60ac421dab6cc0acac126455d 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js @@ -188,7 +188,7 @@ export class SchedulingUnitSummary extends Component { <label>Tasks:</label> <ViewTable data={suTaskList} - defaultcolumns={[{id: "ID", name: "Name", typeValue:"Type", subTaskID: 'Control ID', start_time:"Start Time", stop_time:"End Time", status: "Status", + defaultcolumns={[{id: "ID", name: "Name", typeValue:"Type", controlId: 'Control ID', start_time:{name:"Start Time", format:UIConstants.CALENDAR_DATETIME_FORMAT}, stop_time:{name:"End Time", format:UIConstants.CALENDAR_DATETIME_FORMAT}, status: "Status", antenna_set: "Antenna Set", band: 'Band'}]} optionalcolumns={[{actionpath: "actionpath"}]} columnclassname={[{"ID": "filter-input-50","Name":"filter-input-75","Type":"filter-input-75","Control ID":"filter-input-75", "Start Time": "filter-input-75", "End Time": "filter-input-75", diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/edit.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/edit.js index a71b2eb11b091630e4c802669f30fbe498c7e6e5..4394a790448032f02b436f361f40cf5deec096fc 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/edit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/edit.js @@ -174,9 +174,9 @@ export class TaskEdit extends Component { cancelEdit() { publish('edit-dirty', false); - this.props.history.goBack(); + this.props.history.length>1?this.props.history.goBack():this.props.history.push(`/task`); this.setState({showDialog: false}); - this.props.history.goBack(); + } async componentDidMount() { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/list.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/list.js index 580724bf427183a33697b1450dc0cdd573975664..af675d1a59459e9251f0b09cf969471fe7d756db 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/list.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/list.js @@ -297,7 +297,7 @@ export class TaskList extends Component { await Promise.all(tasks.map(async task => { task.status_logs = task.tasktype === "Blueprint" ? this.subtaskComponent(task) : ""; //Displaying SubTask ID of the 'control' Task - const subTaskIds = task.subTasks ? task.subTasks.filter(sTask => sTask.subTaskTemplate.name.indexOf('control') >= 0) : []; + const subTaskIds = task.subTasks ? task.subTasks.filter(sTask => sTask.primary === true) : []; const dataProducts = subTaskIds && subTaskIds.length > 0 ? subTaskIds[0].output_dataproducts : []; task.dataProducts = []; task.size = 0; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js index 08f084530c8cdcb7a6281b004da1eeef74aeeec0..741fd04ca60ab970256eec535c09a389ba6a30e0 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js @@ -44,6 +44,7 @@ export class TaskView extends Component { this.getTaskDeleteDialogContent = this.getTaskDeleteDialogContent.bind(this); this.showCancelConfirmation = this.showCancelConfirmation.bind(this); this.cancelTask = this.cancelTask.bind(this); + this.cancelView = this.cancelView.bind(this); if (this.props.match.params.id) { @@ -126,7 +127,8 @@ export class TaskView extends Component { if (this.state.editorFunction) { this.state.editorFunction(); } - taskTemplate.schema = await UtilService.getResolvedSchema(taskTemplate.url); + // TODO: Need to make it working with resolved schema + // taskTemplate.schema = await UtilService.getResolvedSchema(taskTemplate.url); if(taskType === 'draft' && task.task_blueprints_ids && task.task_blueprints_ids.length > 0) { this.setState({hasBlueprint: true, task: task, taskTemplate: taskTemplate, isLoading: false, taskId: taskId, taskType: taskType}); } else { @@ -160,8 +162,9 @@ export class TaskView extends Component { * Prepare Task details to show on confirmation dialog */ getTaskDeleteDialogContent() { + const subTask = this.state.task.subtasks ? this.state.task.subtasks.find(sTask => sTask.primary === true): null; let selectedTasks = [{suId: this.state.schedulingUnit.id, suName: this.state.schedulingUnit.name, taskId: this.state.task.id, - controlId: this.state.task.subTaskID, taskName: this.state.task.name, status: this.state.task.status}]; + controlId: (subTask)?subTask.id: '', taskName: this.state.task.name, status: this.state.task.status}]; return <> <DataTable value={selectedTasks} resizableColumns columnResizeMode="expand" className="card" style={{paddingLeft: '0em'}}> <Column field="suId" header="Scheduling Unit Id"></Column> @@ -178,6 +181,10 @@ export class TaskView extends Component { this.setState({confirmDialogVisible: false}); } + cancelView(){ + this.props.history.length>1?this.props.history.goBack():this.props.history.push(`/project`); + } + /** * Delete Task */ @@ -251,7 +258,7 @@ export class TaskView extends Component { disabled: true, // callback: this.setEditorOutput, parentFunction: this.setEditorFunction, - resolveExtRef: false + resolveExtRef: true }); } @@ -280,7 +287,7 @@ export class TaskView extends Component { this.state.permissionById && this.state.permissionById[this.state.taskId].delete? 'Delete Task': `${this.access_denied_message} to delete`, type: 'button', disabled: this.state.hasBlueprint, actOn: 'click', props:{ callback: this.showDeleteConfirmation}}); actions.push({ icon: 'fa-window-close', link: this.props.history.goBack, - title:'Click to Close Task', props : { pathname:'/schedulingunit' }}); + title:'Click to Close Task', type: 'button', actOn: 'click', props:{ callback: this.cancelView }}); // Child component to render predecessors and successors list const TaskRelationList = ({ list }) => ( diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js index 6eea35d53141d430e5cf15e25c7d071c4f74de3d..da7b6ff5f534d7a7f87df5ab5b31170c8021c47c 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js @@ -55,11 +55,13 @@ const RESERVATION_COLORS = { "false-true": { bgColor: "#9b9999", color: "white" }, "false-false": { bgColor: "black", color: "white" } }; +const OFFSET_DATA_DAYS = 7; /** * Scheduling Unit timeline view component to view SU List and timeline */ export class TimelineView extends Component { - + timelineCommonUtils = new TimelineCommonUtils(); + constructor(props) { super(props); this.timelineUIAttributes = UtilService.localStore({ type: 'get', key: "TIMELINE_UI_ATTR" }) || {}; @@ -93,6 +95,7 @@ export class TimelineView extends Component { userrole: AuthStore.getState(), suStatusList: [], taskStatusList: [], + datasetStartTime: null, datasetEndTime:null, showDialog: false, } this.STATUS_BEFORE_SCHEDULED = ['defining', 'defined', 'schedulable']; // Statuses before scheduled to get station_group @@ -103,7 +106,8 @@ export class TimelineView extends Component { // this.menuOptions = [{ label: 'Add Reservation', icon: "fa fa-", disabled: true , command: () => { this.selectOptionMenu('Add Reservation') } }, // { label: 'Reservation List', icon: "fa fa-", command: () => { this.selectOptionMenu('Reservation List') } }, // ]; - + this.getSchedulingUnits = this.getSchedulingUnits.bind(this); + this.loadSUsAheadAndTrail = this.loadSUsAheadAndTrail.bind(this); this.showOptionMenu = this.showOptionMenu.bind(this); this.selectOptionMenu = this.selectOptionMenu.bind(this); this.onItemClick = this.onItemClick.bind(this); @@ -136,8 +140,9 @@ export class TimelineView extends Component { const timelinePermission = permission.userRolePermission.timeline; let taskTypes = [] - let menuOptions = [{ label: 'Add Reservation', icon: "fa fa-", disabled: !timelinePermission.addreservation, command: () => { this.selectOptionMenu('Add Reservation') } }, - { label: 'Reservation List', icon: "fa fa-", disabled: !timelinePermission.listreservation, command: () => { this.selectOptionMenu('Reservation List') } }, + let menuOptions = [{ label: 'Add Reservation', icon: "fa fa-", disabled: !timelinePermission.addreservation, command: (e) => { this.selectOptionMenu(e, 'Add Reservation') } }, + { label: 'Reservation List', icon: "fa fa-", disabled: !timelinePermission.listreservation, command: (e) => { + this.selectOptionMenu(e, 'Reservation List') } }, ] this.setState({menuOptions: menuOptions, loader: true }); TaskService.getTaskTypes().then(results => {taskTypes = results}); @@ -159,68 +164,44 @@ export class TimelineView extends Component { .then(suConstraintTemplates => { this.suConstraintTemplates = suConstraintTemplates; }); - this.timelineCommonUtils = new TimelineCommonUtils(); + const currentUTC = moment.utc(await UtilService.getUTC()); // Default start time, this should be updated if default view is changed. Take from localstorage if available let defaultStartTime = this.timelineUIAttributes.dateRange?moment.utc(this.timelineUIAttributes.dateRange.startTime):null; // Default end time, this should be updated if default view is changed. Take from localstorage if available let defaultEndTime = this.timelineUIAttributes.dateRange?moment.utc(this.timelineUIAttributes.dateRange.endTime):null; + + // Set default time if previous selected date range and zoom level available. + if (this.timelineUIAttributes.zoomLevel) { + const defaultZoomLevel = _.find(this.timelineCommonUtils.ZOOM_LEVELS, {name: this.timelineUIAttributes.zoomLevel}); + const rangeDuration = defaultStartTime?defaultEndTime.diff(defaultStartTime)/1000-defaultZoomLevel.value:0; + defaultStartTime = defaultStartTime?defaultStartTime.add(1 * rangeDuration/2, 'seconds'):null; + defaultStartTime = defaultStartTime?defaultStartTime:currentUTC.clone().add(-1 * defaultZoomLevel.value/2, 'seconds'); + defaultEndTime = defaultEndTime?defaultEndTime.add(-1 * rangeDuration/2, 'seconds'):null; + defaultEndTime = defaultEndTime?defaultEndTime:currentUTC.clone().add(1 * defaultZoomLevel.value/2, 'seconds'); + } else { + defaultStartTime = defaultStartTime?defaultStartTime:currentUTC.clone().add(-24, 'hours'); // Default start time, this should be updated if default view is changed. + defaultEndTime = defaultEndTime?defaultEndTime:currentUTC.clone().add(24, 'hours'); // Default end time, this should be updated if default view is changed. + } + // Fetch all details from server and prepare data to pass to timeline and table components - const promises = [ScheduleService.getExpandedSUList(), - UtilService.getUTC(), - ScheduleService.getStations('All')]; + const promises = [ScheduleService.getStations('All')]; Promise.all(promises).then(async (responses) => { this.mainStationGroupOptions = Object.keys(this.timelineCommonUtils.getMainStationGroups()).map(value => ({ value })); - const suBlueprints = _.sortBy(responses[0], 'name'); - const group = [], items = []; - const currentUTC = moment.utc(responses[1]); - // Set default time if previous selected date range and zoom level available. - if (this.timelineUIAttributes.zoomLevel) { - const defaultZoomLevel = _.find(this.timelineCommonUtils.ZOOM_LEVELS, {name: this.timelineUIAttributes.zoomLevel}); - const rangeDuration = defaultStartTime?defaultEndTime.diff(defaultStartTime)/1000-defaultZoomLevel.value:0; - defaultStartTime = defaultStartTime?defaultStartTime.add(1 * rangeDuration/2, 'seconds'):null; - defaultStartTime = defaultStartTime?defaultStartTime:currentUTC.clone().add(-1 * defaultZoomLevel.value/2, 'seconds'); - defaultEndTime = defaultEndTime?defaultEndTime.add(-1 * rangeDuration/2, 'seconds'):null; - defaultEndTime = defaultEndTime?defaultEndTime:currentUTC.clone().add(1 * defaultZoomLevel.value/2, 'seconds'); - } else { - defaultStartTime = defaultStartTime?defaultStartTime:currentUTC.clone().add(-24, 'hours'); // Default start time, this should be updated if default view is changed. - defaultEndTime = defaultEndTime?defaultEndTime:currentUTC.clone().add(24, 'hours'); // Default end time, this should be updated if default view is changed. - } - let suList = []; - for (let suBlueprint of suBlueprints){ - suBlueprint['actionpath'] = `/schedulingunit/view/blueprint/${suBlueprint.id}`; - suBlueprint.suDraft = suBlueprint.draft; - suBlueprint.project = suBlueprint.draft.scheduling_set.project.name; - suBlueprint.suSet = suBlueprint.draft.scheduling_set; - suBlueprint.durationInSec = suBlueprint.duration; - suBlueprint.duration = UnitConverter.getSecsToHHmmss(suBlueprint.duration); - suBlueprint.workflowStatus = this.timelineCommonUtils.getWorkflowStatus(suBlueprint); - suBlueprint.task_content = ""; - suBlueprint.observ_template_name = suBlueprint.draft.observation_strategy_template.name; - suBlueprint.tasks = suBlueprint.task_blueprints; - suBlueprint.stationGroupCount = this.timelineCommonUtils.getSUStationGroupCount(suBlueprint).counts; - for (let task of suBlueprint.tasks) { - const controlTask = _.find(task.subtasks, subtask => { return subtask.subtask_type === "observation"}); - task.controlId = controlTask?controlTask.id:""; - if (task.task_type === "observation") { - task.antenna_set = task.specifications_doc.antenna_set; - task.band = task.specifications_doc.filter; - } - } - } - for (const station of responses[2]['stations']) { + for (const station of responses[0]['stations']) { this.allStationsGroup.push({ id: station, title: station }); } // Set the selectedStationGroup if the previous selected station groups are stored const selectedStationGroup = this.timelineUIAttributes.stationGroups || _.keys(this.timelineCommonUtils.getMainStationGroups()); + const schedulingUnits = await this.getSchedulingUnits(defaultStartTime, defaultEndTime); this.setState({ - suBlueprints: suBlueprints, suBlueprintList: suList, + suBlueprints: schedulingUnits.original, suBlueprintList: schedulingUnits.formatted, + datasetStartTime: schedulingUnits.datasetStartTime, datasetEndTime: schedulingUnits.datasetEndTime, loader: false, taskTypes: taskTypes, - // group: group, items: items, currentUTC: currentUTC, isLoading: false, currentStartTime: defaultStartTime, currentEndTime: defaultEndTime, selectedStationGroup: selectedStationGroup }); - this.dateRangeCallback(defaultStartTime, defaultEndTime); + this.dateRangeCallback(defaultStartTime, defaultEndTime, true); }); } @@ -231,6 +212,123 @@ export class TimelineView extends Component { } + /** + * Function to get only the data for the visible timeline first and the offset data in the background. + * @param {moment} startTime - start time of the visible timeline + * @param {moment} endTime - end time of the visible timeline + * @returns Object with original SUBs, formatted SUBs, dataset start and end time properties. + */ + async getSchedulingUnits(startTime, endTime) { + let {datasetStartTime, datasetEndTime, suBlueprints, suBlueprintList} = this.state; + let schedulingUnits = {original: suBlueprints}; + // Initial loading of data. Fetches only the data for the visible timeline. + if (!datasetStartTime) { + schedulingUnits = await this.loadSchedulingUnits(startTime, endTime); + datasetStartTime = startTime.clone().add(-1 * OFFSET_DATA_DAYS, 'days'); + datasetEndTime = endTime.clone().add(OFFSET_DATA_DAYS, 'days'); + schedulingUnits.datasetStartTime = datasetStartTime; + schedulingUnits.datasetEndTime = datasetEndTime; + // Loads the offset data in the background + this.loadSUsAheadAndTrail(datasetStartTime, datasetEndTime, startTime, endTime); + } else { // When timeline range or zoom level changes + // If the new timeline start time is before the existing dataset start time, + // load only the SUBS between the new start time and old dataset start time + if (startTime.isSameOrBefore(datasetStartTime)) { + const frontFillSUs = await this.loadSchedulingUnits(startTime, datasetStartTime); + suBlueprints = suBlueprints.concat(frontFillSUs.original); + suBlueprintList = suBlueprintList.concat(frontFillSUs.formatted); + datasetStartTime = startTime.clone().add(-1 * OFFSET_DATA_DAYS, 'days'); + schedulingUnits.datasetStartTime = datasetStartTime; + schedulingUnits.original = _.uniqBy(suBlueprints, 'id'); + schedulingUnits.formatted = _.uniqBy(suBlueprintList, 'id'); + // Loads the offset data before the new start time in the background + this.loadSUsAheadAndTrail(datasetStartTime, null, startTime, null); + } + // If the new timeline end time is after the existing dataset end time, + // load only the SUBS between the new end time and old dataset end time + if (endTime.isSameOrAfter(datasetEndTime)) { + const trailFillSUs = await this.loadSchedulingUnits(endTime, datasetEndTime); + suBlueprints = suBlueprints.concat(trailFillSUs.original); + suBlueprintList = suBlueprintList.concat(trailFillSUs.formatted); + datasetEndTime = endTime.clone().add(OFFSET_DATA_DAYS, 'days'); + schedulingUnits.datasetEndTime = datasetEndTime; + schedulingUnits.original = _.uniqBy(suBlueprints, 'id'); + schedulingUnits.formatted = _.uniqBy(suBlueprintList, 'id'); + // Loads the offset data after the new end time in the background + this.loadSUsAheadAndTrail(null, datasetEndTime, null, endTime); + } + } + // Returning the data for the timeline only or the existing data. + return schedulingUnits; + } + + /** + * Function to load the data before and after the timeline with an offset period in the background. + * This improves the performance in rendering the timeline. + * @param {moment} datasetStartTime - start time of the dataset to be loaded + * @param {moment} datasetEndTime - end time of the dataset to be loaded + * @param {moment} startTime - start time visible in the timeline + * @param {moment} endTime - end time visible in the timeline + */ + async loadSUsAheadAndTrail(datasetStartTime, datasetEndTime, startTime, endTime) { + let suBlueprints = null; + let suBlueprintList = null; + // Load the SUBs from the dataset start time to timeline start time and concat to existing SUBs + if (datasetStartTime) { + const susAhead = await this.loadSchedulingUnits(datasetStartTime, startTime); + suBlueprints = this.state.suBlueprints.concat(susAhead.original); + suBlueprintList = this.state.suBlueprintList.concat(susAhead.formatted); + } + // Load the SUBs from the timeline end time to dataset end time and concat to existing SUBs + if (datasetEndTime) { + const susTrail = await this.loadSchedulingUnits(endTime, datasetEndTime); + suBlueprints = suBlueprints || this.state.suBlueprints; + suBlueprintList = suBlueprintList || this.state.suBlueprintList; + suBlueprints = suBlueprints.concat(susTrail.original); + suBlueprintList = suBlueprintList.concat(susTrail.formatted); + } + this.setState({suBlueprints: _.uniqBy(suBlueprints, 'id'), + suBlueprintList: _.uniqBy(suBlueprintList, 'id'), + datasetStartTime: datasetStartTime, + datasetEndTime: datasetEndTime + }); + } + + /** + * Fetches the SUBs with either start time or end time in the time range and formats as required for the table. + * @param {moment} startTime - start time from which SUBs should be loaded + * @param {moment} endTime - end time till which SUBs should be loaded + * @returns - Object with both original SUBs from backend and formatted SUBs for table list. + */ + async loadSchedulingUnits(startTime, endTime) { + let suBlueprints = await ScheduleService.getExpandedSUList(startTime.format(UIConstants.CALENDAR_DATETIME_FORMAT), + endTime.format(UIConstants.CALENDAR_DATETIME_FORMAT)); + suBlueprints = _.filter(suBlueprints, suBlueprint => suBlueprint.status.toLowerCase !== 'obsolete'); + let suList = []; + for (let suBlueprint of suBlueprints){ + suBlueprint['actionpath'] = `/schedulingunit/view/blueprint/${suBlueprint.id}`; + suBlueprint.suDraft = suBlueprint.draft; + suBlueprint.project = suBlueprint.draft.scheduling_set.project.name; + suBlueprint.suSet = suBlueprint.draft.scheduling_set; + suBlueprint.durationInSec = suBlueprint.duration; + suBlueprint.duration = UnitConverter.getSecsToHHmmss(suBlueprint.duration); + suBlueprint.workflowStatus = this.timelineCommonUtils.getWorkflowStatus(suBlueprint); + suBlueprint.task_content = ""; + suBlueprint.observ_template_name = suBlueprint.draft.observation_strategy_template.name; + suBlueprint.tasks = suBlueprint.task_blueprints; + suBlueprint.stationGroupCount = this.timelineCommonUtils.getSUStationGroupCount(suBlueprint).counts; + for (let task of suBlueprint.tasks) { + const controlTask = _.find(task.subtasks, subtask => { return subtask.primary}); + task.controlId = controlTask?controlTask.id:""; + if (task.task_type === "observation") { + task.antenna_set = task.specifications_doc.antenna_set; + task.band = task.specifications_doc.filter; + } + } + } + return {original: suBlueprints, formatted: suList}; + } + setSelectedStationGroup(value) { // By default all stations groups are selected. // In that case no need to store the selected group otherwise store the selected groups in local storage @@ -287,6 +385,7 @@ export class TimelineView extends Component { let items = [], itemGroup = []; const subtaskTemplates = this.subtaskTemplates; for (let task of suBlueprint.tasks) { + console.log(task); if (((!this.state.stationView && this.state.selectedTaskTypes.indexOf(task.task_type)>=0) || (this.state.stationView && task.task_type === 'observation')) && ( filteredTasks.length===0 || filteredTasks.indexOf(task.id)>=0 ) @@ -297,7 +396,7 @@ export class TimelineView extends Component { if ((start_time.isBetween(startTime, endTime) || end_time.isBetween(startTime, endTime)) || (start_time.isSameOrBefore(startTime) && end_time.isSameOrAfter(endTime))) { - const controlTask = _.find(task.subtasks, subtask => { return subtask.subtask_type === "observation"}); + const controlTask = _.find(task.subtasks, subtask => { return subtask.primary }); const controlId = controlTask?controlTask.id:""; let item = { id: `${suBlueprint.id}_${task.id}`, @@ -478,10 +577,14 @@ export class TimelineView extends Component { * @param {moment} startTime * @param {moment} endTime */ - async dateRangeCallback(startTime, endTime) { + async dateRangeCallback(startTime, endTime, loadOldData) { + let suBlueprints = this.state.suBlueprints; + if (!loadOldData) { + suBlueprints = (await this.getSchedulingUnits(startTime, endTime)).original; + } let suBlueprintList = [], group = [], items = []; if (startTime && endTime) { - for (const suBlueprint of this.state.suBlueprints) { + for (const suBlueprint of suBlueprints) { if (moment.utc(suBlueprint.start_time).isBetween(startTime, endTime) || moment.utc(suBlueprint.stop_time).isBetween(startTime, endTime) || (moment.utc(suBlueprint.start_time).isSameOrBefore(startTime) && @@ -531,6 +634,7 @@ export class TimelineView extends Component { } this.setState({ + suBlueprints: suBlueprints, suBlueprintList: _.filter(suBlueprintList, (suBlueprint) => { return suBlueprint.start_time != null }), currentStartTime: startTime, currentEndTime: endTime }); @@ -864,14 +968,26 @@ export class TimelineView extends Component { this.optionsMenu.toggle(event); } - selectOptionMenu(menuName) { + selectOptionMenu(e, menuName) { switch (menuName) { case 'Reservation List': { - this.setState({ redirect: `/reservation/list` }); + if(e.originalEvent.ctrlKey) { + window.open('/reservation/list','_blank'); + } + else { + this.props.history.push('/reservation/list'); + this.setState({ redirect: `/reservation/list` }); + } break; } case 'Add Reservation': { - this.setState({ redirect: `/reservation/create` }); + if(e.originalEvent.ctrlKey) { + window.open('/reservation/create','_blank'); + } + else { + this.props.history.push('/reservation/create') + this.setState({ redirect: `/reservation/create`}); + } break; } default: { @@ -1026,7 +1142,7 @@ export class TimelineView extends Component { suBlueprints.push(suBlueprint); // Set updated suBlueprints in the state and call the dateRangeCallback to create the timeline group and items this.setState({ suBlueprints: suBlueprints }); - this.dateRangeCallback(this.state.currentStartTime, this.state.currentEndTime); + this.dateRangeCallback(this.state.currentStartTime, this.state.currentEndTime, true); }); } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js index a7f44266cf272c610dbd6ed895f1cedd49d0ee4d..a4690fda9d0d2cf645b4dba37bddda93fe958de6 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js @@ -45,11 +45,13 @@ const RESERVATION_COLORS = { "false-true": { bgColor: "#9b9999", color: "white" }, "false-false": { bgColor: "black", color: "white" } }; +const OFFSET_DATA_DAYS = 7; + /** * Scheduling Unit timeline view component to view SU List and timeline */ export class WeekTimelineView extends Component { - + timelineCommonUtils = new TimelineCommonUtils(); constructor(props) { super(props); this.timelineUIAttributes = UtilService.localStore({ type: 'get', key: "TIMELINE_UI_ATTR" }) || {}; @@ -68,9 +70,10 @@ export class WeekTimelineView extends Component { suTaskList: [], isSummaryLoading: false, stationGroup: [], - reservationEnabled: true, + reservationEnabled: this.timelineUIAttributes.reservationEnabled===undefined?true:this.timelineUIAttributes.reservationEnabled, suStatusList: [], taskStatusList: [], + datasetStartTime: null, datasetEndTime:null, showDialog: false, userrole: AuthStore.getState(), } @@ -78,6 +81,8 @@ export class WeekTimelineView extends Component { this.reservations = []; this.reservationReasons = []; this.optionsMenu = React.createRef(); + this.getSchedulingUnits = this.getSchedulingUnits.bind(this); + this.loadSUsAheadAndTrail = this.loadSUsAheadAndTrail.bind(this); this.showOptionMenu = this.showOptionMenu.bind(this); this.selectOptionMenu = this.selectOptionMenu.bind(this); this.onItemClick = this.onItemClick.bind(this); @@ -102,9 +107,11 @@ export class WeekTimelineView extends Component { async componentDidMount() { this.getStatusList(); const permission = await AuthUtil.getUserRolePermission(); + const timelinePermission = permission.userRolePermission.timeline; const weekviewPermission = permission.userRolePermission.weekoverview; - let menuOptions = [{ label: 'Add Reservation', icon: "fa fa-", disabled: !weekviewPermission.addreservation, command: () => { this.selectOptionMenu('Add Reservation') } }, - { label: 'Reservation List', icon: "fa fa-", disabled: !weekviewPermission.listreservation, command: () => { this.selectOptionMenu('Reservation List') } }, + let menuOptions = [{ label: 'Add Reservation', icon: "fa fa-", disabled: !timelinePermission.addreservation, command: (e) => { this.selectOptionMenu(e, 'Add Reservation') } }, + { label: 'Reservation List', icon: "fa fa-", disabled: !timelinePermission.listreservation, command: (e) => { + this.selectOptionMenu(e, 'Reservation List') } }, ] this.setState({menuOptions: menuOptions, userPermission: weekviewPermission}); @@ -120,83 +127,153 @@ export class WeekTimelineView extends Component { } } }); - this.timelineCommonUtils = new TimelineCommonUtils(); + const currentUTC = moment.utc(await UtilService.getUTC()); + const defaultStartTime = moment.utc().day(-2).hour(0).minutes(0).seconds(0); + const defaultEndTime = moment.utc().day(8).hour(23).minutes(59).seconds(59); + const datasetStartTime = defaultStartTime.clone().add(-1 * OFFSET_DATA_DAYS, 'days'); + const datasetEndTime = defaultEndTime.clone().add(OFFSET_DATA_DAYS, 'days'); + // Fetch all details from server and prepare data to pass to timeline and table components - const promises = [ScheduleService.getExpandedSUList(), - UtilService.getUTC()]; - Promise.all(promises).then(async (responses) => { - const suBlueprints = _.sortBy(responses[0], 'name'); - let group = [], items = []; - const currentUTC = moment.utc(responses[1]); - const defaultStartTime = moment.utc().day(-2).hour(0).minutes(0).seconds(0); - const defaultEndTime = moment.utc().day(8).hour(23).minutes(59).seconds(59); - for (const count of _.range(11)) { - const groupDate = defaultStartTime.clone().add(count, 'days'); - group.push({ 'id': groupDate.format("MMM DD ddd"), title: groupDate.format("MMM DD - ddd"), value: groupDate }); + let group = [], items = []; + for (const count of _.range(11)) { + const groupDate = defaultStartTime.clone().add(count, 'days'); + group.push({ 'id': groupDate.format("MMM DD ddd"), title: groupDate.format("MMM DD - ddd"), value: groupDate }); + } + // Get all scheduling constraint templates + ScheduleService.getSchedulingConstraintTemplates() + .then(suConstraintTemplates => { + this.suConstraintTemplates = suConstraintTemplates; + }); + this.setState({ + suBlueprints: [], suBlueprintList: [], + group: _.sortBy(group, ['value']), + items: items, currentUTC: currentUTC, isLoading: false, + startTime: defaultStartTime, endTime: defaultEndTime + }); + let updatedItemGroupData = await this.dateRangeCallback(defaultStartTime, defaultEndTime, true); + this.timeline.updateTimeline(updatedItemGroupData); + } + + /** + * Function to get only the data for the visible timeline first and the offset data in the background. + * @param {moment} startTime - start time of the visible timeline + * @param {moment} endTime - end time of the visible timeline + * @returns Object with original SUBs, formatted SUBs, dataset start and end time properties. + */ + async getSchedulingUnits(startTime, endTime) { + let {datasetStartTime, datasetEndTime, suBlueprints, suBlueprintList} = this.state; + let schedulingUnits = {original: suBlueprints}; + // Initial loading of data. Fetches only the data for the visible timeline. + if (!datasetStartTime) { + schedulingUnits = await this.loadSchedulingUnits(startTime, endTime); + datasetStartTime = startTime.clone().add(-1 * OFFSET_DATA_DAYS, 'days'); + datasetEndTime = endTime.clone().add(OFFSET_DATA_DAYS, 'days'); + schedulingUnits.datasetStartTime = datasetStartTime; + schedulingUnits.datasetEndTime = datasetEndTime; + // Loads the offset data in the background + this.loadSUsAheadAndTrail(datasetStartTime, datasetEndTime, startTime, endTime); + } else { // When timeline range or zoom level changes + // If the new timeline start time is before the existing dataset start time, + // load only the SUBS between the new start time and old dataset start time + if (startTime.isSameOrBefore(datasetStartTime)) { + const frontFillSUs = await this.loadSchedulingUnits(startTime, datasetStartTime); + suBlueprints = suBlueprints.concat(frontFillSUs.original); + suBlueprintList = suBlueprintList.concat(frontFillSUs.formatted); + datasetStartTime = startTime.clone().add(-1 * OFFSET_DATA_DAYS, 'days'); + schedulingUnits.datasetStartTime = datasetStartTime; + schedulingUnits.original = _.uniqBy(suBlueprints, 'id'); + schedulingUnits.formatted = _.uniqBy(suBlueprintList, 'id'); + // Loads the offset data before the new start time in the background + this.loadSUsAheadAndTrail(datasetStartTime, null, startTime, null); } - let suList = []; - for (let suBlueprint of suBlueprints) { - suBlueprint['actionpath'] = `/schedulingunit/view/blueprint/${suBlueprint.id}`; - suBlueprint.suDraft = suBlueprint.draft; - suBlueprint.project = suBlueprint.draft.scheduling_set.project.name; - suBlueprint.suSet = suBlueprint.draft.scheduling_set; - suBlueprint.durationInSec = suBlueprint.duration; - suBlueprint.duration = UnitConverter.getSecsToHHmmss(suBlueprint.duration); - suBlueprint.workflowStatus = this.timelineCommonUtils.getWorkflowStatus(suBlueprint); - suBlueprint.task_content = ""; - suBlueprint.observ_template_name = suBlueprint.draft.observation_strategy_template.name; - suBlueprint.tasks = suBlueprint.task_blueprints; - suBlueprint.stationGroupCount = this.timelineCommonUtils.getSUStationGroupCount(suBlueprint).counts; - // Select only blueprints with start_time and stop_time in the default time limit - if (suBlueprint.start_time && - ((moment.utc(suBlueprint.start_time).isBetween(defaultStartTime, defaultEndTime) || - moment.utc(suBlueprint.stop_time).isBetween(defaultStartTime, defaultEndTime)) - || (moment.utc(suBlueprint.start_time).isSameOrBefore(defaultStartTime, defaultEndTime) && - moment.utc(suBlueprint.stop_time).isSameOrAfter(defaultStartTime, defaultEndTime)))) { + // If the new timeline end time is after the existing dataset end time, + // load only the SUBS between the new end time and old dataset end time + if (endTime.isSameOrAfter(datasetEndTime)) { + const trailFillSUs = await this.loadSchedulingUnits(endTime, datasetEndTime); + suBlueprints = suBlueprints.concat(trailFillSUs.original); + suBlueprintList = suBlueprintList.concat(trailFillSUs.formatted); + datasetEndTime = endTime.clone().add(OFFSET_DATA_DAYS, 'days'); + schedulingUnits.datasetEndTime = datasetEndTime; + schedulingUnits.original = _.uniqBy(suBlueprints, 'id'); + schedulingUnits.formatted = _.uniqBy(suBlueprintList, 'id'); + // Loads the offset data after the new end time in the background + this.loadSUsAheadAndTrail(null, datasetEndTime, null, endTime); + } + } + // Returning the data for the timeline only or the existing data. + return schedulingUnits; + } - const startTime = moment.utc(suBlueprint.start_time); - const endTime = moment.utc(suBlueprint.stop_time); - if (startTime.format("MM-DD-YYYY") !== endTime.format("MM-DD-YYYY")) { - let suBlueprintStart = _.cloneDeep(suBlueprint); - let suBlueprintEnd = _.cloneDeep(suBlueprint); - suBlueprintStart.stop_time = startTime.hour(23).minutes(59).seconds(59).format('YYYY-MM-DDTHH:mm:ss.00000'); - suBlueprintEnd.start_time = endTime.hour(0).minutes(0).seconds(0).format('YYYY-MM-DDTHH:mm:ss.00000'); - items.push(await this.getTimelineItem(suBlueprintStart, currentUTC)); - items.push(await this.getTimelineItem(suBlueprintEnd, currentUTC)); + /** + * Function to load the data before and after the timeline with an offset period in the background. + * This improves the performance in rendering the timeline. + * @param {moment} datasetStartTime - start time of the dataset to be loaded + * @param {moment} datasetEndTime - end time of the dataset to be loaded + * @param {moment} startTime - start time visible in the timeline + * @param {moment} endTime - end time visible in the timeline + */ + async loadSUsAheadAndTrail(datasetStartTime, datasetEndTime, startTime, endTime) { + let suBlueprints = null; + let suBlueprintList = null; + // Load the SUBs from the dataset start time to timeline start time and concat to existing SUBs + if (datasetStartTime) { + const susAhead = await this.loadSchedulingUnits(datasetStartTime, startTime); + suBlueprints = this.state.suBlueprints.concat(susAhead.original); + suBlueprintList = this.state.suBlueprintList.concat(susAhead.formatted); + } + // Load the SUBs from the timeline end time to dataset end time and concat to existing SUBs + if (datasetEndTime) { + const susTrail = await this.loadSchedulingUnits(endTime, datasetEndTime); + suBlueprints = suBlueprints || this.state.suBlueprints; + suBlueprintList = suBlueprintList || this.state.suBlueprintList; + suBlueprints = suBlueprints.concat(susTrail.original); + suBlueprintList = suBlueprintList.concat(susTrail.formatted); + } + this.setState({suBlueprints: _.uniqBy(suBlueprints, 'id'), + suBlueprintList: _.uniqBy(suBlueprintList, 'id'), + datasetStartTime: datasetStartTime, + datasetEndTime: datasetEndTime + }); + } - } else { - items.push(await this.getTimelineItem(suBlueprint, currentUTC)); - } - suList.push(suBlueprint); - } - // Add Subtask Id as control id for task if subtask type us control. Also add antenna_set & band prpoerties to the task object. - for (let task of suBlueprint.tasks) { - const controlTask = _.find(task.subtasks, subtask => { return subtask.subtask_type === "observation"}); - task.controlId = controlTask?controlTask.id:""; - if (task.task_type.toLowerCase() === "observation") { - task.antenna_set = task.specifications_doc.antenna_set; - task.band = task.specifications_doc.filter; - } + /** + * Fetches the SUBs with either start time or end time in the time range and formats as required for the table. + * @param {moment} startTime - start time from which SUBs should be loaded + * @param {moment} endTime - end time till which SUBs should be loaded + * @returns - Object with both original SUBs from backend and formatted SUBs for table list. + */ + async loadSchedulingUnits(startTime, endTime) { + let suList = []; + let suBlueprints = await ScheduleService.getExpandedSUList(startTime.format(UIConstants.CALENDAR_DATETIME_FORMAT), + endTime.format(UIConstants.CALENDAR_DATETIME_FORMAT)); + suBlueprints = _.filter(suBlueprints, suBlueprint => suBlueprint.status.toLowerCase !== 'obsolete'); + for (let suBlueprint of suBlueprints) { + suBlueprint['actionpath'] = `/schedulingunit/view/blueprint/${suBlueprint.id}`; + suBlueprint.suDraft = suBlueprint.draft; + suBlueprint.project = suBlueprint.draft.scheduling_set.project.name; + suBlueprint.suSet = suBlueprint.draft.scheduling_set; + suBlueprint.durationInSec = suBlueprint.duration; + suBlueprint.duration = UnitConverter.getSecsToHHmmss(suBlueprint.duration); + suBlueprint.workflowStatus = this.timelineCommonUtils.getWorkflowStatus(suBlueprint); + suBlueprint.task_content = ""; + suBlueprint.observ_template_name = suBlueprint.draft.observation_strategy_template.name; + suBlueprint.tasks = suBlueprint.task_blueprints; + suBlueprint.stationGroupCount = this.timelineCommonUtils.getSUStationGroupCount(suBlueprint).counts; + // Add Subtask Id as control id for task if subtask is primary. Also add antenna_set & band prpoerties to the task object. + for (let task of suBlueprint.tasks) { + const controlTask = _.find(task.subtasks, subtask => { return subtask.primary }); + task.controlId = controlTask?controlTask.id:""; + if (task.task_type.toLowerCase() === "observation") { + task.antenna_set = task.specifications_doc.antenna_set; + task.band = task.specifications_doc.filter; } - // Get stations involved for this SUB - let stations = this.timelineCommonUtils.getSUStations(suBlueprint); - suBlueprint.stations = _.uniq(stations); - } - if (this.state.reservationEnabled) { - items = this.addWeekReservations(items, defaultStartTime, defaultEndTime, currentUTC); } - // Get all scheduling constraint templates - ScheduleService.getSchedulingConstraintTemplates() - .then(suConstraintTemplates => { - this.suConstraintTemplates = suConstraintTemplates; - }); - this.setState({ - suBlueprints: suBlueprints, group: _.sortBy(group, ['value']), - suBlueprintList: suList, - items: items, currentUTC: currentUTC, isLoading: false, - startTime: defaultStartTime, endTime: defaultEndTime - }); - }); + // Get stations involved for this SUB + let stations = this.timelineCommonUtils.getSUStations(suBlueprint); + suBlueprint.stations = _.uniq(stations); + // suList.push(suBlueprint); + } + return {original: suBlueprints, formatted: suList}; } /** @@ -273,7 +350,7 @@ export class WeekTimelineView extends Component { .then(taskList => { for (let task of taskList) { //Control Task ID - const subTaskIds = (task.subTasks || []).filter(sTask => sTask.subTaskTemplate.name.indexOf('control') > 1); + const subTaskIds = (task.subTasks || []).filter(sTask => sTask.primary); task.controlId = subTaskIds.length ? subTaskIds[0].id : ''; if (task.template.type_value.toLowerCase() === "observation" && task.specifications_doc.antenna_set) { @@ -369,9 +446,12 @@ export class WeekTimelineView extends Component { * @param {moment} endTime */ async dateRangeCallback(startTime, endTime, refreshData) { + let suBlueprints = this.state.suBlueprints; let suBlueprintList = [], group = [], items = []; let currentUTC = this.state.currentUTC; if (refreshData) { + const schedulingUnits = await this.getSchedulingUnits(startTime, endTime); + suBlueprints = schedulingUnits.original; for (const count of _.range(11)) { const groupDate = startTime.clone().add(count, 'days'); group.push({ 'id': groupDate.format("MMM DD ddd"), title: groupDate.format("MMM DD - ddd"), value: groupDate }); @@ -379,7 +459,7 @@ export class WeekTimelineView extends Component { let direction = startTime.week() - this.state.startTime.week(); currentUTC = this.state.currentUTC.clone().add(direction * 7, 'days'); if (startTime && endTime) { - for (const suBlueprint of this.state.suBlueprints) { + for (const suBlueprint of suBlueprints) { if (moment.utc(suBlueprint.start_time).isBetween(startTime, endTime) || moment.utc(suBlueprint.stop_time).isBetween(startTime, endTime) || (moment.utc(suBlueprint.start_time).isSameOrBefore(startTime, endTime) && @@ -409,9 +489,15 @@ export class WeekTimelineView extends Component { items = this.state.items; } this.setState({ + suBlueprints: suBlueprints, suBlueprintList: _.filter(suBlueprintList, (suBlueprint) => { return suBlueprint.start_time != null }), group: group, items: items, currentUTC: currentUTC, startTime: startTime, endTime: endTime }); + if (!this.state.datasetStartTime) { + this.setState({datasetStartTime: schedulingUnits.datasetStartTime, + datasetEndTime: schedulingUnits.datasetEndTime, + }); + } // On range change close the Details pane // this.closeSUDets(); } else { @@ -488,14 +574,26 @@ export class WeekTimelineView extends Component { this.optionsMenu.toggle(event); } - selectOptionMenu(menuName) { + selectOptionMenu(e, menuName) { switch (menuName) { case 'Reservation List': { - this.setState({ redirect: `/reservation/list` }); + if(e.originalEvent.ctrlKey) { + window.open('/reservation/list','_blank'); + } + else { + this.props.history.push('/reservation/list'); + this.setState({ redirect: `/reservation/list` }); + } break; } case 'Add Reservation': { - this.setState({ redirect: `/reservation/create` }); + if(e.originalEvent.ctrlKey) { + window.open('/reservation/create','_blank'); + } + else { + this.props.history.push('/reservation/create') + this.setState({ redirect: `/reservation/create`}); + } break; } default: { @@ -622,7 +720,7 @@ export class WeekTimelineView extends Component { suBlueprint.stationGroupCount = this.timelineCommonUtils.getSUStationGroupCount(suBlueprint).counts; // Add Subtask Id as control id for task if subtask type us control. Also add antenna_set & band prpoerties to the task object. for (let task of suBlueprint.tasks) { - const controlTask = _.find(task.subtasks, subtask => { return subtask.subtask_type === "observation"}); + const controlTask = _.find(task.subtasks, subtask => { return subtask.primary }); task.controlId = controlTask?controlTask.id:""; if (task.task_type.toLowerCase() === "observation" && task.specifications_doc.antenna_set) { @@ -647,6 +745,8 @@ export class WeekTimelineView extends Component { await this.setState({ reservationEnabled: e.value }); let updatedItemGroupData = await this.dateRangeCallback(this.state.startTime, this.state.endTime, true); this.timeline.updateTimeline(updatedItemGroupData); + this.timelineUIAttributes.reservationEnabled = e.value; + this.storeUIAttributes(); } /** diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Workflow/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Workflow/index.js index c36001e6cd4a2d0af57f762ee13a8fd43960b1d1..43a99a7955af30d8cdc478e9eb59ece2b0454646 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Workflow/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Workflow/index.js @@ -238,6 +238,9 @@ export default (props) => { let title = getStepItems()[currentView - 1].label; return title; } + const cancelView = () => { + props.history.length>1? props.history.goBack(): props.history.push(`/su/workflow`); + } return ( @@ -246,7 +249,7 @@ export default (props) => { {currentStep && <PageHeader location={props.location} title={getTitle()} actions={[{type:'ext_link', icon:'', label: 'SDC Helpdesk', title: 'Report major issues here', props: { pathname: 'https://support.astron.nl/sdchelpdesk' } }, - {icon: 'fa-window-close', link: props.history.goBack, title: 'Click to Close Workflow', props: { pathname: '/schedulingunit/1/workflow' } }, + {icon: 'fa-window-close', title: 'Click to Close Workflow', type: 'button', actOn: 'click', props:{ callback: cancelView }}, ]} />} {loader && <AppLoader />} {!loader && schedulingUnit && diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Workflow/workflow.list.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Workflow/workflow.list.js index d0167fada5277696821f2d362d3da1270fef629d..72352a6a07c1dc2c3609b92bf570d40c38686f3f 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Workflow/workflow.list.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Workflow/workflow.list.js @@ -200,7 +200,7 @@ class WorkflowList extends Component{ * Go back to previous page */ close() { - this.props.history.goBack(); + this.props.history.push('/'); } /** @@ -308,8 +308,7 @@ class WorkflowList extends Component{ {project && project.list ? <> <PageHeader location={this.props.location} title={'Workflow - List'} - actions={[{icon: 'fa-window-close', title:'Click to Close Workflow - List', - type: 'button', actOn: 'click', props:{ callback: this.close }}]}/> + actions={[]}/> <div style={{marginTop: '15px'}}> {this.state.isLoading ? <AppLoader/> : (this.state.workflowProcessList.length>0 )? <> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js index 0bfe14f1e6965ccda55973dc287367df2e7c2465..1a10b933c1d11443932184e9f418259c5612808e 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js @@ -42,7 +42,8 @@ const SU_FETCH_FIELDS = [ "id", "task_blueprints.subtasks.output_dataproducts.size", "task_blueprints.subtasks.output_dataproducts.deleted_since", "task_blueprints.subtasks.specifications_doc", - "task_blueprints.subtasks.subtask_type" ]; + "task_blueprints.subtasks.subtask_type", + "task_blueprints.subtasks.primary" ]; const ScheduleService = { getSchedulingUnitDraft: async function (){ @@ -126,18 +127,23 @@ const ScheduleService = { return schedulingUnit; }, /** - * This function fetches all required details to be shown in the list with reference fields populated in the server side. + * This function fetches all required details in the time range to be shown in the list with reference fields populated in the server side. + * @param {string} startTime - start time of the range in 'YYYY-MM-DD HH:mm:ss' format + * @param {string} endTime - end time of the range in 'YYYY-MM-DD HH:mm:ss' format * @returns Array - List of SUB with expanded and limited fields fetched */ - getExpandedSUList: async() => { + getExpandedSUList: async(startTime, endTime) => { let blueprints = []; try { - let initialResponse = await axios.get(`/api/scheduling_unit_blueprint/?ordering=id&expand=${SU_EXPAND_FIELDS.join()}&fields=${SU_FETCH_FIELDS.join()}`); + let url = `/api/scheduling_unit_blueprint/?ordering=name&expand=${SU_EXPAND_FIELDS.join()}&fields=${SU_FETCH_FIELDS.join()}`; + url = `${url}&start_time_before=${endTime || ''}&stop_time_after=${startTime || ''}`; + let initialResponse = await axios.get(url); const totalCount = initialResponse.data.count; const initialCount = initialResponse.data.results.length blueprints = blueprints.concat(initialResponse.data.results); if (totalCount > initialCount) { - let secondResponse = await axios.get(`/api/scheduling_unit_blueprint/?ordering=id&limit=${totalCount-initialCount}&offset=${initialCount}&expand=${SU_EXPAND_FIELDS.join()}&fields=${SU_FETCH_FIELDS.join()}`); + url = `${url}&limit=${totalCount-initialCount}&offset=${initialCount}`; + let secondResponse = await axios.get(url); blueprints = blueprints.concat(secondResponse.data.results); } } catch(error) { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/task.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/task.service.js index a557442c79cf5c05cfdba508d53627af5de2aa8e..67a80523c6c95435e55440140bf06c1daed5689b 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/task.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/task.service.js @@ -29,8 +29,8 @@ const TaskService = { }, getTask : async function (taskType, taskId) { try { - const url = taskType === 'blueprint'? '/api/task_blueprint/': '/api/task_draft/'; - const response = await axios.get(url + taskId); + const url = taskType === 'blueprint'? '/api/task_blueprint/'+taskId+'?expand=subtasks': '/api/task_draft/'+taskId; + const response = await axios.get(url);// + taskId); return response.data; } catch (error) { console.error(error);