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/package.json b/SAS/TMSS/frontend/tmss_webapp/package.json index 36c5a54e803ee8f26341c1f4dbe90fdd65c31e3d..b2ab4d9cd7dbf242a366dea5e6ebbb6e42a5fc04 100644 --- a/SAS/TMSS/frontend/tmss_webapp/package.json +++ b/SAS/TMSS/frontend/tmss_webapp/package.json @@ -36,6 +36,7 @@ "prop-types": "^15.7.2", "react": "^16.13.1", "react-app-polyfill": "^1.0.6", + "react-beautiful-dnd": "^13.1.0", "react-beforeunload": "^2.4.0", "react-bootstrap": "^1.0.1", "react-bootstrap-datetimepicker": "0.0.22", diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js index c8284ef231045cc6499bcbb23e4e61bbdc49ff18..2a5d2d7be67294483afdb5266b35b65319019439 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js @@ -23,7 +23,7 @@ import "flatpickr/dist/flatpickr.css"; import Flatpickr from "react-flatpickr"; import confirmDatePlugin from "flatpickr/dist/plugins/confirmDate/confirmDate"; import shortcutButtonsPlugin from "shortcut-buttons-flatpickr"; - +import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; import UtilService from '../../src/services/util.service' import Papa from "papaparse"; import JsPDF from "jspdf"; @@ -56,8 +56,22 @@ let tmpTableData = null; let currentTableName = null; let storeFilter = false; let storage_array = []; +let dragged = null; +let reorder = []; //let confirmDatePlugin = new confirmDatePlugin(); // Define a default UI for filtering + +const getItemStyle = ({ isDragging, isDropAnimating }, draggableStyle) => ({ + ...draggableStyle, + // some basic styles to make the items look a bit nicer + userSelect: "none", + cursor: isDragging? 'move': 'move', + ...(!isDragging && { transform: "translate(0,0)" }), + ...(isDropAnimating && { transitionDuration: "0.001s" }) + + // styles we need to apply on draggables +}); + function GlobalFilter({ preGlobalFilteredRows, globalFilter, @@ -1832,6 +1846,7 @@ function Table(props) { useColumnOrder, useExportData ); + const currentColOrder = React.useRef(); let pageCount = doServersideFilter?controlledPageCount:data.length; tmpTableData = data; // while siwtch the task type or Su type, this will set the relavent default sort column @@ -1856,11 +1871,17 @@ function Table(props) { columns.filter(column => !column.isVisible).map(column => column.id) ); // console.log('columns List', visibleColumns.map((d) => d.id)); - if (columnOrders && columnOrders.length) { + const storedColOrder = UtilService.localStore({ type: 'get', key: tablename+'colOrder'}); + if (storedColOrder) { + setColumnOrder(storedColOrder) + } + else if (columnOrders && columnOrders.length) { if (showAction === 'true') { setColumnOrder(['Select', 'Action', ...columnOrders]); + UtilService.localStore({ type: 'set', key: tablename+'colOrder', value: ['Select', 'Action', ...columnOrders]}); } else { setColumnOrder(['Select', ...columnOrders]); + UtilService.localStore({ type: 'set', key: tablename+'colOrder', value: ['Select', ...columnOrders]}); } } setLoading(dataFetchStatus); @@ -2072,31 +2093,74 @@ function Table(props) { <table {...getTableProps()} data-testid="viewtable" className="viewtable" > <thead> {headerGroups.map(headerGroup => ( - <tr {...headerGroup.getHeaderGroupProps()}> - {headerGroup.headers.map(column => ( - <th onClick={() => { - if(!doServersideFilter) { - toggleBySorting({ 'id': column.id, desc: (column.isSortedDesc != undefined ? !column.isSortedDesc : false) }) - } + <DragDropContext + onDragStart={() => { + currentColOrder.current = allColumns.map(column => column.id); + }} + onDragUpdate={(dragUpdateObj, b) => { + const colOrder = [...currentColOrder.current]; + const sIndex = dragUpdateObj.source.index; + const dIndex = dragUpdateObj.destination && dragUpdateObj.destination.index; + if(typeof sIndex === 'number' && typeof dIndex === "number") { + colOrder.splice(sIndex, 1); + colOrder.splice(dIndex, 0, dragUpdateObj.draggableId); + setColumnOrder(colOrder); + UtilService.localStore({ type: 'set', key: tablename+'colOrder', value: colOrder}); + } + }} + > + <Droppable droppableId="droppable" direction="horizontal"> + {(droppableProvided, snapshot) => ( + <tr {...headerGroup.getHeaderGroupProps()} + ref={droppableProvided.innerRef}> + {headerGroup.headers.map((column,index) => ( + <Draggable + key={column.id} + draggableId={column.id} + index={index} + isDragDisabled={_.includes(['Select', 'Action', 'Status Logs'], column.id)? true: false}> + {(provided, snapshot) => { + return ( + <th onClick={() => { + if(!doServersideFilter) { + toggleBySorting({ 'id': column.id, desc: (column.isSortedDesc != undefined ? !column.isSortedDesc : false) }); + } }}> - <div {...column.getHeaderProps(column.getSortByToggleProps())} > - {column.Header !== 'actionpath' && column.render('Header')} - {column.Header !== 'Action' ? - column.isSorted ? (column.isSortedDesc ? <i className="pi pi-sort-down" aria-hidden="true"></i> : <i className="pi pi-sort-up" aria-hidden="true"></i>) : "" - : "" - } - </div> - - {/* Render the columns filter UI */} - {column.Header !== 'actionpath' && - <div className={columnclassname[0][column.Header]} > - {column.canFilter && column.Header !== 'Action' ? column.render('Filter') : null} - - </div> - } - </th> - ))} - </tr> + <div {...column.getHeaderProps(column.getSortByToggleProps())} > + <div + {...provided.draggableProps} + {...provided.dragHandleProps} + // {...extraProps} + ref={provided.innerRef} + style={{ + ...getItemStyle( + snapshot, + provided.draggableProps.style + ) + // ...style + }}> + {column.Header !== 'actionpath' && column.render('Header')} + {column.Header !== 'Action' ? + column.isSorted ? (column.isSortedDesc ? <i className="pi pi-sort-down" aria-hidden="true"></i> : <i className="pi pi-sort-up" aria-hidden="true"></i>) : "" + : "" + } + </div> + </div> + {/* Render the columns filter UI */} + {column.Header !== 'actionpath' && + <div className={columnclassname[0][column.Header]}> + {column.canFilter && column.Header !== 'Action' ? column.render('Filter') : null} + </div> + } + </th> + ); + }} + </Draggable> + ))} + </tr> + )} + </Droppable> + </DragDropContext> ))} {(doServersideFilter && loading) && <tr style={{height: "5px"}}><td colSpan={columns.length} style={{padding: "0px"}}> @@ -2252,7 +2316,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/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..111cbfb24c68fcf5188034ef4caec1b7f22dcf7a 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js @@ -18,6 +18,7 @@ import UtilService from '../../services/util.service'; import { Dropdown } from 'primereact/dropdown'; import SUBCreator from './sub.create'; import AuthUtil from '../../utils/auth.util'; +import TimelineCommonUtils from '../Timeline/common.utils'; class SchedulingUnitList extends Component{ @@ -432,18 +433,15 @@ class SchedulingUnitList extends Component{ const output = []; if (isInitial) { this.mainStationGroups = null; - let workflowProcesses = null; const promises = [ ScheduleService.getMainGroupStations(), - WorkflowService.getWorkflowProcesses(), ]; await Promise.all(promises).then(async responses => { if(responses) { this.mainStationGroups = responses[0]; - workflowProcesses = responses[1]; } }); - this.setState({scheduleunitDrafts: [], workflowProcesses: workflowProcesses}); + this.setState({scheduleunitDrafts: []}); } else { let scheduleunits = []; let expand = suType.toLowerCase() === 'draft' ? this.SU_DRAFT_EXPAND: this.SU_BLUEPRINT_EXPAND; @@ -530,8 +528,7 @@ class SchedulingUnitList extends Component{ }; scheduleunit['scheduling_set'] = scheduleunit.draft.scheduling_set.name; scheduleunit['workflowStatus'] = null; - const workflowProcess = _.find(this.state.workflowProcesses, ['su', scheduleunit.id]); - scheduleunit['workflowStatus'] = workflowProcess?workflowProcess.status: null; + scheduleunit['workflowStatus'] = this.timelineCommonUtils.getWorkflowStatus(scheduleunit); scheduleunit.duration = moment.utc((scheduleunit.duration || 0)*1000).format('HH:mm:ss'); scheduleunit.type= 'Blueprint'; scheduleunit['actionpath'] ='/schedulingunit/view/blueprint/'+scheduleunit.id; @@ -616,6 +613,7 @@ class SchedulingUnitList extends Component{ this.getFilterColumns(this.state.suType.toLowerCase()); this.getSchedulingUnitList(true, this.state.suType, this.filterQry, this.orderBy, this.limit, this.offset); this.setToggleBySorting(); + this.timelineCommonUtils = new TimelineCommonUtils(); } /** @@ -701,7 +699,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 +935,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 +1040,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 +1264,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 +1367,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 +1417,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 +1534,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 +1543,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 7fb8a366271baa5d7ccc256c3778af6ed8a64c6a..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 }); } @@ -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/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/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js index 85606583904ec2b28c4ee37837fbaa453d8ae965..0f01ea8a1c0998cf744babb322b8b774a518cf06 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js @@ -95,6 +95,8 @@ export class TaskView extends Component { this.showCancelConfirmation = this.showCancelConfirmation.bind(this); this.cancelTask = this.cancelTask.bind(this); this.getFilterOptions = this.getFilterOptions.bind(this); + this.cancelView = this.cancelView.bind(this); + if (this.props.match.params.id) { this.state.taskId = this.props.match.params.id; @@ -271,6 +273,10 @@ export class TaskView extends Component { this.setState({confirmDialogVisible: false}); } + cancelView(){ + this.props.history.length>1?this.props.history.goBack():this.props.history.push(`/task`); + } + /** * Delete Task */ @@ -400,7 +406,7 @@ export class TaskView extends Component { type: 'button', disabled: this.state.hasBlueprint ? true: this.state.permissionById && this.state.permissionById[this.state.taskId].delete? false: true, 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 706573d5aee50e6a35e41457fe2eb2e6ca8e281c..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.primary === true}); - 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,8 +385,9 @@ 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.primary === true)) + || (this.state.stationView && task.task_type === 'observation')) && ( filteredTasks.length===0 || filteredTasks.indexOf(task.id)>=0 ) && task.start_time && task.stop_time) { const antennaSet = task.specifications_doc.antenna_set; @@ -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.primary === true}); + 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 7132ad37fa3a7682b3c41825d37fbaee1b7ad8cd..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.primary === true}); - 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.primary === true); + 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.primary === true}); + 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/qa.reporting.template.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Workflow/qa.reporting.template.js index 68d40a07a634460d61a09992464cf9ef85d1b979..42f38d150d284dbb4457e0aa9b84b37e8aa79a74 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Workflow/qa.reporting.template.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Workflow/qa.reporting.template.js @@ -29,7 +29,11 @@ const QaReportingTemplate = { let taskDataproducts = _.find(dataproducts.task_blueprints, {'id': task.id}) for(const successor of taskSuccessors) { if(successor.task_type === "pipeline") { - observation.pipeline.push(successor.id); + successor.subtasks.map(successorSubtask => { + if(successorSubtask.primary === true){ + observation.pipeline.push(successorSubtask.id); + } + }) } } let taskDP = []; @@ -69,13 +73,14 @@ const QaReportingTemplate = { } let tableBody = '' - for (const observation of observations) { + const sortedObservations = _.orderBy(observations,['startTime'], ['desc']) + for (const observation of sortedObservations) { tableBody = tableBody + ` <tr> <td><a href="https://proxy.lofar.eu/inspect/HTML/${observation.controlId}/index.html">${observation.controlId}</a></td> <td>${observation.scheduleUnitId}</td> <td>${observation.taskId}</td> - <td>${observation.pipeline.join(",")}</td> + <td>${observation.pipeline.join(", ")}</td> <td>${observation.project}</td> <td>${observation.targetname}</td> <td><a href="https://proxy.lofar.eu/inspect/HTML/${observation.controlId}/Stations/station_beamlets.html">BST</a></td> @@ -128,7 +133,7 @@ const QaReportingTemplate = { <strong>Archiving:</strong> <strong style='display:inline'>Remarks:</strong><p style= "display:inline; position: relative"> Please analyse the validation plots at <a href="https://proxy.lofar.eu/inspect/HTML/">https://proxy.lofar.eu/inspect/HTML/</a> within 24 hours after this notification and submit your - findings using the form at <a href="http://localhost:3000/schedulingunit/${id}/workflow">http://localhost:3000/schedulingunit/${id}/workflow </a>. + findings using the form at <a href="/schedulingunit/${id}/workflow">${window.location.origin}/schedulingunit/${id}/workflow </a>. After this time window has passed, we will assume that your judgement is that the observation was successful and we will complete the actions described above ourselves to support your run.</p> <br/> <p>From the moment the data are made available to you at the LTA you have four weeks to check their quality and to report any problems to the 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 91433b8d5aa29f0bd6a2d76334181d7f6824b2b7..1a10b933c1d11443932184e9f418259c5612808e 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js @@ -127,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 ae003e58651a18d7c786813d45ccf694b0d6c9a5..975a98d6cad27318c10ca731d4da3c08646ae747 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/task.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/task.service.js @@ -153,7 +153,7 @@ const TaskService = { }, getTaskSuccessors: async function(type, id) { try { - const url = type === 'blueprint'? `/api/task_blueprint/${id}/successors`: `/api/task_draft/${id}/successors/`; + const url = type === 'blueprint'? `/api/task_blueprint/${id}/successors/?expand=subtasks`: `/api/task_draft/${id}/successors/`; const response = await axios.get(url); return response.data; } catch (error) {