diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e8e336c5fc789a9bc47574134599f41dea94eef5..7f96632c36e9b1caa7887e0fb323adc3fcf69678 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -265,6 +265,7 @@ integration_test_TMSS: RABBITMQ_DEFAULT_PASS: guest LOFAR_DEFAULT_BROKER: 'rabbitmq' # override default 'localhost' which does not work for CI service rabbitmq. needs: + - build_TMSS - unit_test_TMSS artifacts: name: integration-test-report @@ -289,6 +290,7 @@ integration_test_RAServices: - cd build/gnucxx11_opt - SKIP_INTEGRATION_TESTS=false SKIP_UNIT_TESTS=true ctest needs: + - build_RAServices - unit_test_RAServices artifacts: name: integration-test-report @@ -313,6 +315,7 @@ integration_test_LTAIngest: RABBITMQ_DEFAULT_PASS: guest LOFAR_DEFAULT_BROKER: 'rabbitmq' # override default 'localhost' which does not work for CI service rabbitmq. needs: + - build_LTAIngest - unit_test_LTAIngest artifacts: name: integration-test-report diff --git a/Docker/lofar-ci/Dockerfile_ci_sas b/Docker/lofar-ci/Dockerfile_ci_sas index 527639e256c50c98b1ef0550b41a7cbf69b3e1c3..1aa8f6689b56f7529d3a0a17e0022128a9ab2bbc 100644 --- a/Docker/lofar-ci/Dockerfile_ci_sas +++ b/Docker/lofar-ci/Dockerfile_ci_sas @@ -16,7 +16,7 @@ RUN yum erase -y postgresql postgresql-server postgresql-devel && \ cd /bin && ln -s /usr/pgsql-9.6/bin/initdb && ln -s /usr/pgsql-9.6/bin/postgres ENV PATH /usr/pgsql-9.6/bin:$PATH -RUN pip3 install cython kombu lxml requests pygcn xmljson mysql-connector-python python-dateutil Django==3.0.9 djangorestframework==3.11.1 djangorestframework-xml ldap==1.0.2 flask fabric coverage python-qpid-proton PyGreSQL numpy h5py psycopg2 testing.postgresql Flask-Testing scipy Markdown django-filter python-ldap python-ldap-test ldap3 django-jsonforms django-json-widget django-jsoneditor drf-yasg flex swagger-spec-validator django-auth-ldap mozilla-django-oidc jsonschema comet pyxb==1.2.5 graphviz isodate astropy packaging +RUN pip3 install cython kombu lxml requests pygcn xmljson mysql-connector-python python-dateutil Django==3.0.9 djangorestframework==3.11.1 djangorestframework-xml ldap==1.0.2 flask fabric coverage python-qpid-proton PyGreSQL numpy h5py psycopg2 testing.postgresql Flask-Testing scipy Markdown django-filter python-ldap python-ldap-test ldap3 django-jsonforms django-json-widget django-jsoneditor drf-yasg flex swagger-spec-validator django-auth-ldap mozilla-django-oidc jsonschema comet pyxb==1.2.5 graphviz isodate astropy packaging django-debug-toolbar #Viewflow package RUN pip3 install django-material django-viewflow diff --git a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/PVSS-feedback.parset b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/PVSS-feedback.parset index 4e062233e380cd0bc57b388f0ecec42bf1ccfe33..3f8654919634d6601b5d6c9bb635a067058ed53f 100644 --- a/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/PVSS-feedback.parset +++ b/RTCP/Cobalt/GPUProc/etc/parset-additions.d/default/PVSS-feedback.parset @@ -4,4 +4,4 @@ # If empty, data points are never sent. # One can also start a PVSSGateway_Stub # on localhost, which writes to a file. -Cobalt.PVSSGateway.host = ccu001 +Cobalt.PVSSGateway.host = diff --git a/SAS/TMSS/docker-compose-ua.yml b/SAS/TMSS/docker-compose-ua.yml index 74752f8596f9daa35763a85b7f5e355288b38cbd..73b699d14c94f2d0606c3242d4b964f593d05b68 100644 --- a/SAS/TMSS/docker-compose-ua.yml +++ b/SAS/TMSS/docker-compose-ua.yml @@ -17,9 +17,10 @@ services: - "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 && python3 lib64/python3.6/site-packages/lofar/sas/tmss/manage.py runserver 0.0.0.0:8008' + command: bash -c 'source /opt/lofar/lofarinit.sh && ALLOWED_HOSTS=* tmss_test_environment -H 0.0.0.0 -P tmss-ua -p 8008 --data' ports: - "8008:8008" diff --git a/SAS/TMSS/frontend/tmss_webapp/debug.log b/SAS/TMSS/frontend/tmss_webapp/debug.log new file mode 100644 index 0000000000000000000000000000000000000000..2d8c637aed6551186839ffb67b3120ab5e4487b9 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/debug.log @@ -0,0 +1,2 @@ +[1013/111617.035:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) +[1015/122332.151:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3) diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js b/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js index 1825ef55a1a8191016e852cba1b9206e0b884c2b..ba27d387d396ba1292a334e6907d58f0f6f91561 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/Timeline/CalendarTimeline.js @@ -18,6 +18,7 @@ import UtilService from '../../services/util.service'; import 'react-calendar-timeline/lib/Timeline.css'; import { Calendar } from 'primereact/calendar'; +import { Checkbox } from 'primereact/checkbox'; // Label formats for day headers based on the interval label width const DAY_HEADER_FORMATS = [{ name: "longer", minWidth: 300, maxWidth: 50000, format: "DD dddd, MMMM YYYY"}, @@ -30,7 +31,8 @@ const DAY_HEADER_FORMATS = [{ name: "longer", minWidth: 300, maxWidth: 50000, fo {name: "nano", minWidth: 0, maxWidth: 0, format: ""}]; //>>>>>> Constants for date/time formats, zoom level definition & defaults -const UTC_DISPLAY_FORMAT = "YYYY-MM-DDTHH:mm:ss"; +const UTC_DATE_FORMAT = "YYYY-MM-DD"; +const UTC_TIME_FORMAT = "HH:mm:ss"; const UTC_LST_KEY_FORMAT = "YYYY-MM-DDTHH:mm:00"; const UTC_LST_HOUR_FORMAT = "YYYY-MM-DDTHH:00:00"; const UTC_LST_DAY_FORMAT = "YYYY-MM-DDT00:00:00"; @@ -95,7 +97,8 @@ export class CalendarTimeline extends Component { lstDateHeaderUnit: 'hour', // Unit to be considered for the LST axis header based on the visible duration isLSTDateHeaderLoading: true, dayHeaderVisible: true, // To control the Day header visibility based on the zoom level - weekHeaderVisible: false // To control the Week header visibility based on the zoom level + weekHeaderVisible: false, // To control the Week header visibility based on the zoom level + isLive: false } this.itemClickCallback = props.itemClickCallback; // Pass timeline item click event back to parent @@ -125,6 +128,10 @@ export class CalendarTimeline extends Component { this.zoomOut = this.zoomOut.bind(this); this.setZoomRange = this.setZoomRange.bind(this); //<<<<<< Functions of this component + + //>>>>>> Public functions of the component + this.updateTimeline = this.updateTimeline.bind(this); + //<<<<<< Public functions of the component } componentDidMount() { @@ -158,12 +165,17 @@ export class CalendarTimeline extends Component { const currentUTC = moment.utc(utcString); this.setState({currentUTC: currentUTC}); let currentLST = await UtilService.getLST(utcString); - this.setState({currentLST: moment(currentUTC.format('DD-MMM-YYYY ') + currentLST)}) + this.setState({currentLST: moment(currentUTC.format('DD-MMM-YYYY ') + currentLST.split('.')[0], 'DD-MMM-YYYY HH:mm:ss')}) } ); } else { this.setState({currentUTC: this.state.currentUTC.add(1, 'second'), currentLST: this.state.currentLST?this.state.currentLST.add(1, 'second'):null}); } + if (this.state.isLive) { + this.props.dateRangeCallback(this.state.defaultStartTime.add(1, 'second'), this.state.defaultEndTime.add(1, 'second')); + // const result = this.props.dateRangeCallback(this.state.defaultStartTime.add(1, 'second'), this.state.defaultEndTime.add(1, 'second')); + // let group = DEFAULT_GROUP.concat(result.group); + } } /** @@ -183,7 +195,7 @@ export class CalendarTimeline extends Component { const formattedColUTC = colUTC.format(lstDateHeaderUnit==="hour"?UTC_LST_HOUR_FORMAT:UTC_LST_DAY_FORMAT); // if (!lstDateHeaderMap[formattedColUTC]) { const lst = await UtilService.getLST(formattedColUTC); - const lstDate = moment(colUTC.format(`DD-MMM-YYYY ${lst}`)).add(30, 'minutes'); + const lstDate = moment(colUTC.format(`MM-DD-YYYY ${lst.split('.')[0]}`), 'MM-DD-YYYY HH:mm:ss').add(30, 'minutes'); lstDateHeaderMap[formattedColUTC] = lstDateHeaderUnit==="hour"?lstDate.format('HH'):lstDate.format('DD'); // } } @@ -506,6 +518,7 @@ export class CalendarTimeline extends Component { onTimeChange(visibleTimeStart, visibleTimeEnd, updateScrollCanvas) { this.loadLSTDateHeaderMap(moment(visibleTimeStart).utc(), moment(visibleTimeEnd).utc(), this.state.lstDateHeaderUnit); updateScrollCanvas(visibleTimeStart, visibleTimeEnd); + this.props.dateRangeCallback(moment(visibleTimeStart).utc(), moment(visibleTimeEnd).utc()); this.setState({defaultStartTime: moment(visibleTimeStart), defaultEndTime: moment(visibleTimeEnd)}) } @@ -570,8 +583,11 @@ export class CalendarTimeline extends Component { let visibleTimeEnd = this.state.defaultEndTime; const visibleTimeDiff = visibleTimeEnd.valueOf()-visibleTimeStart.valueOf(); const secondsToMove = visibleTimeDiff / 1000 / 10 ; + const result = this.props.dateRangeCallback(visibleTimeStart, visibleTimeEnd); + let group = DEFAULT_GROUP.concat(result.group); this.setState({defaultStartTime: visibleTimeStart.add(-1 * secondsToMove, 'seconds'), - defaultEndTime: visibleTimeEnd.add(-1 * secondsToMove, 'seconds')}); + defaultEndTime: visibleTimeEnd.add(-1 * secondsToMove, 'seconds'), + group: group, items: result.items}); } /** @@ -582,22 +598,17 @@ export class CalendarTimeline extends Component { let visibleTimeEnd = this.state.defaultEndTime; const visibleTimeDiff = visibleTimeEnd.valueOf()-visibleTimeStart.valueOf(); const secondsToMove = visibleTimeDiff / 1000 / 10 ; + const result = this.props.dateRangeCallback(visibleTimeStart, visibleTimeEnd); + let group = DEFAULT_GROUP.concat(result.group); this.setState({defaultStartTime: visibleTimeStart.add(1 * secondsToMove, 'seconds'), - defaultEndTime: visibleTimeEnd.add(1 * secondsToMove, 'seconds')}); + defaultEndTime: visibleTimeEnd.add(1 * secondsToMove, 'seconds'), + group: group, items: result.items}); } /** * Zooms In to the next pre-defined zoom level */ zoomIn() { - /*let visibleTimeStart = this.state.defaultStartTime; - let visibleTimeEnd = this.state.defaultEndTime; - const visibleTimeDiff = visibleTimeEnd.valueOf()-visibleTimeStart.valueOf(); - if (visibleTimeDiff > this.state.minZoom) { - const secondsToZoom = visibleTimeDiff / 1000 / 2 / 4 * 3 ; - this.setState({defaultStartTime: visibleTimeStart.add(1*secondsToZoom, 'seconds'), - defaultEndTime: visibleTimeEnd.add(-1*secondsToZoom, 'seconds')}); - }*/ let prevZoomLevel = this.state.zoomLevel; const prevZoomObject = _.find(ZOOM_LEVELS, {'name': prevZoomLevel}); const prevZoomIndex = ZOOM_LEVELS.indexOf(prevZoomObject); @@ -610,14 +621,6 @@ export class CalendarTimeline extends Component { * Zooms out to the next pre-defined zoom level */ zoomOut() { - /*let visibleTimeStart = this.state.defaultStartTime; - let visibleTimeEnd = this.state.defaultEndTime; - const visibleTimeDiff = visibleTimeEnd.valueOf()-visibleTimeStart.valueOf(); - if (visibleTimeDiff < this.state.maxZoom) { - const secondsToZoom = visibleTimeDiff / 1000 * 3 / 2; - this.setState({defaultStartTime: visibleTimeStart.add(-1*secondsToZoom, 'seconds'), - defaultEndTime: visibleTimeEnd.add(1*secondsToZoom, 'seconds')}); - }*/ let prevZoomLevel = this.state.zoomLevel; const prevZoomObject = _.find(ZOOM_LEVELS, {'name': prevZoomLevel}); const prevZoomIndex = ZOOM_LEVELS.indexOf(prevZoomObject); @@ -663,15 +666,27 @@ export class CalendarTimeline extends Component { } } + /** + * Public function that can be called by its implementation class or function to pass required data and parameters + * as objects + * @param {Object} props + */ + updateTimeline(props) { + this.setState({group: DEFAULT_GROUP.concat(props.group), items: props.items}); + } + render() { return ( <React.Fragment> {/* Toolbar for the timeline */} <div className="p-fluid p-grid timeline-toolbar"> {/* Clock Display */} - <div className="p-col-3" style={{padding: '0px 0px 0px 10px'}}> + <div className="p-col-2" style={{padding: '0px 0px 0px 10px'}}> <div style={{marginTop: "0px"}}> - <label style={{marginBottom: "0px"}}>UTC:</label><span>{this.state.currentUTC.format(UTC_DISPLAY_FORMAT)}</span> + <label style={{marginBottom: "0px"}}>Date:</label><span>{this.state.currentUTC.format(UTC_DATE_FORMAT)}</span> + </div> + <div style={{marginTop: "0px"}}> + <label style={{marginBottom: "0px"}}>UTC:</label><span>{this.state.currentUTC.format(UTC_TIME_FORMAT)}</span> </div> {this.state.currentLST && <div style={{marginTop: "0px"}}> @@ -679,8 +694,12 @@ export class CalendarTimeline extends Component { </div> } </div> + <div className="p-col-1 timeline-filters"> + <label style={{paddingRight: "3px"}}>Live </label> + <Checkbox checked={this.state.isLive} label="Live" onChange={(e) => { this.setState({'isLive': e.checked})}} ></Checkbox> + </div> {/* Date Range Selection */} - <div className="p-col-4"> + <div className="p-col-4 timeline-filters"> {/* <span className="p-float-label"> */} <Calendar id="range" placeholder="Select Date Range" selectionMode="range" showIcon={!this.state.zoomRange} value={this.state.zoomRange} onChange={(e) => this.setZoomRange( e.value )} readOnlyInput /> @@ -690,11 +709,11 @@ export class CalendarTimeline extends Component { onClick={() => {this.setZoomRange( null)}}></i>} </div> {/* Reset to default zoom and current timeline */} - <div className="p-col-1" style={{padding: '5px 0px'}}> + <div className="p-col-1 timeline-button" > <Button label="" icon="pi pi-arrow-down" className="p-button-rounded p-button-success" id="now-btn" onClick={this.resetToCurrentTime} title="Rest Zoom & Move to Current Time"/> </div> {/* Zoom Select */} - <div className="p-col-2" style={{paddingRight: '0px'}}> + <div className="p-col-2 timeline-filters" style={{paddingRight: '0px'}}> <Dropdown optionLabel="name" optionValue="name" style={{fontSize: '10px'}} value={this.state.zoomLevel} @@ -704,7 +723,7 @@ export class CalendarTimeline extends Component { placeholder="Zoom"/> </div> {/* Zoom and Move Action */} - <div className="p-col-2 timeline-actionbar"> + <div className="p-col-2 timeline-actionbar timeline-filters"> <button className="p-link" title="Move Left" onClick={e=> { this.moveLeft() }}><i className="pi pi-angle-left"></i></button> <button className="p-link" title="Zoom Out" onClick={e=> { this.zoomOut() }} disabled={this.state.zoomLevel.startsWith('Custom')}><i className="pi pi-minus-circle"></i></button> <button className="p-link" title="Zoom In" onClick={e=> { this.zoomIn() }} disabled={this.state.zoomLevel.startsWith('Custom')}><i className="pi pi-plus-circle"></i></button> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js index 21f9326233262c6e69a655eb877a7868d4ca4d9b..904166532404bbea9b00a73c540a276b9e8e5783 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js @@ -11,10 +11,14 @@ import {Paginator} from 'primereact/paginator'; import { Button } from "react-bootstrap"; import { InputNumber } from "primereact/inputnumber"; -let tbldata =[]; +let tbldata =[], filteredData = [] ; let isunittest = false; let showTopTotal = true; +let showGlobalFilter = true; +let showColumnFilter = true; +let allowColumnSelection = true; let columnclassname =[]; +let parentCallbackFunction; // Define a default UI for filtering function GlobalFilter({ @@ -39,13 +43,13 @@ function GlobalFilter({ // Define a default UI for filtering function DefaultColumnFilter({ - column: { filterValue, preFilteredRows, setFilter }, + column: { filterValue, preFilteredRows, setFilter, filteredRows }, }) { return ( <input value={filterValue || ''} onChange={e => { - setFilter(e.target.value || undefined) // Set undefined to remove the filter entirely + setFilter(e.target.value || undefined); // Set undefined to remove the filter entirely }} /> ) @@ -291,7 +295,7 @@ function Table({ columns, data, defaultheader, optionalheader, defaultSortColumn useGlobalFilter, useSortBy, usePagination - ) + ); React.useEffect(() => { setHiddenColumns( columns.filter(column => !column.isVisible).map(column => column.accessor) @@ -345,11 +349,18 @@ function Table({ columns, data, defaultheader, optionalheader, defaultSortColumn }) localStorage.setItem(tablename,JSON.stringify(lsToggleColumns)) } + + filteredData = _.map(rows, 'values'); + if (parentCallbackFunction) { + parentCallbackFunction(filteredData); + } + return ( <> <div id="block_container"> + { allowColumnSelection && <div style={{textAlign:'left', marginRight:'30px'}}> - <i className="fa fa-columns col-filter-btn" label="Toggle Columns" onClick={(e) => op.current.toggle(e)} /> + <i className="fa fa-columns col-filter-btn" label="Toggle Columns" onClick={(e) => op.current.toggle(e)} /> <OverlayPanel ref={op} id="overlay_panel" showCloseIcon={false} > <div> <div style={{textAlign: 'center'}}> @@ -377,9 +388,9 @@ function Table({ columns, data, defaultheader, optionalheader, defaultSortColumn </div> </OverlayPanel> </div> - + } <div style={{textAlign:'right'}}> - {tbldata.length>0 && !isunittest && + {tbldata.length>0 && !isunittest && showGlobalFilter && <GlobalFilter preGlobalFilteredRows={preGlobalFilteredRows} globalFilter={state.globalFilter} @@ -470,10 +481,14 @@ filterGreaterThan.autoRemove = val => typeof val !== 'number' function ViewTable(props) { const history = useHistory(); // Data to show in table - tbldata = props.data; + tbldata = props.data; + parentCallbackFunction = props.filterCallback; isunittest = props.unittest; columnclassname = props.columnclassname; - showTopTotal = props.showTopTotal==='false'? false:true; + showTopTotal = props.showTopTotal===undefined?true:props.showTopTotal; + showGlobalFilter = props.showGlobalFilter===undefined?true:props.showGlobalFilter; + showColumnFilter = props.showColumnFilter===undefined?true:props.showColumnFilter; + allowColumnSelection = props.allowColumnSelection===undefined?true:props.allowColumnSelection; // Default Header to show in table and other columns header will not show until user action on UI let defaultheader = props.defaultcolumns; let optionalheader = props.optionalcolumns; @@ -519,8 +534,8 @@ function ViewTable(props) { Header: isString ? defaultheader[0][header] : defaultheader[0][header].name, id: header, accessor: header, - filter: (!isString && defaultheader[0][header].filter=== 'date') ? 'includes' : 'fuzzyText', - Filter: isString ? DefaultColumnFilter : (filterTypes[defaultheader[0][header].filter] ? filterTypes[defaultheader[0][header].filter] : DefaultColumnFilter), + filter: (showColumnFilter?((!isString && defaultheader[0][header].filter=== 'date') ? 'includes' : 'fuzzyText'):""), + Filter: (showColumnFilter?(isString ? DefaultColumnFilter : (filterTypes[defaultheader[0][header].filter] ? filterTypes[defaultheader[0][header].filter] : DefaultColumnFilter)):""), isVisible: true, Cell: props => <div> {updatedCellvalue(header, props.value)} </div>, }) @@ -534,8 +549,8 @@ optionaldataheader.forEach(header => { Header: isString ? optionalheader[0][header] : optionalheader[0][header].name, id: isString ? header : optionalheader[0][header].name, accessor: header, - filter: (!isString && optionalheader[0][header].filter=== 'date') ? 'includes' : 'fuzzyText', - Filter: isString ? DefaultColumnFilter : (filterTypes[optionalheader[0][header].filter] ? filterTypes[optionalheader[0][header].filter] : DefaultColumnFilter), + filter: (showColumnFilter?((!isString && optionalheader[0][header].filter=== 'date') ? 'includes' : 'fuzzyText'):""), + Filter: (showColumnFilter?(isString ? DefaultColumnFilter : (filterTypes[optionalheader[0][header].filter] ? filterTypes[optionalheader[0][header].filter] : DefaultColumnFilter)):""), isVisible: false, Cell: props => <div> {updatedCellvalue(header, props.value)} </div>, }) diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/_overrides.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/_overrides.scss index 03b42a5aeb25770e4dc82919a4ee85d33cec416d..ea8fa6ac620c972f2b45914a621415afda28ee3f 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/_overrides.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/_overrides.scss @@ -51,3 +51,5 @@ } } + + diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss index c2b32f1d6f45d477613e4d68d7257c3fedaff4ab..44fd846f2ef832ee7249f06e5f1d732764fb9ecf 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_timeline.scss @@ -19,6 +19,7 @@ .timeline-actionbar button { padding-top: 3px; font-size: 1.0rem; + padding-left: 3px; // float: right; } @@ -31,8 +32,23 @@ white-space: nowrap; } +.timeline-filters,.timeline-bottom { + padding-top: 25px; +} + +.timeline-button { + padding-bottom: 5px; + padding-left: 0px; + padding-right: 0px; + padding-top: 25px; +} + +.timeline-details-pane { + font-size: 14px; +} + #now-btn { - margin-left: 20px; + margin-left: 10px; } .resize-div, diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_viewtable.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_viewtable.scss index 47803d3ad81264ab7634da3e4b9194d89af2b6f8..45b581f6c23bd3bb129886a494ba5305c6ffcc76 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_viewtable.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_viewtable.scss @@ -102,27 +102,27 @@ body .p-paginator { max-width: 175px; } -.filter-input-50 input{ +.filter-input-50,.filter-input-50 input{ width: 50px; } -.filter-input-75 input{ +.filter-input-75,.filter-input-75 input{ width: 75px; } -.filter-input-100 input{ +.filter-input-100,.filter-input-100 input{ width: 100px; } -.filter-input-125 input{ +.filter-input-125,.filter-input-125 input{ width: 125px; } -.filter-input-150 input{ +.filter-input-150,.filter-input-150 input{ width: 150px; } -.filter-input-175 input{ +.filter-input-175,.filter-input-175 input{ width: 175px; } 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 6cec1f3d60c3f628370e79557f3566549969dc22..2105eadd46a6b1c5e51bb5b336573d95c48a2aae 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/SchedulingUnitList.js @@ -25,15 +25,17 @@ class SchedulingUnitList extends Component{ "requirements_template_id": "Template", "start_time":"Start Time", "stop_time":"End time", - "duration":"Duration (HH:mm:ss)" + "duration":"Duration (HH:mm:ss)", + "status":"Status" }], optionalcolumns: [{ "actionpath":"actionpath", }], columnclassname: [{ "Template":"filter-input-50", - "Duration":"filter-input-50", - "Type": "filter-input-75" + "Duration (HH:mm:ss)":"filter-input-75", + "Type": "filter-input-75", + "Status":"filter-input-100" }], defaultSortColumn: [{id: "Name", desc: false}], } @@ -57,7 +59,7 @@ class SchedulingUnitList extends Component{ for( const scheduleunit of scheduleunits){ const blueprintdata = bluePrint.data.results.filter(i => i.draft_id === scheduleunit.id); blueprintdata.map(blueP => { - blueP.duration = moment.utc(blueP.duration*1000).format('HH:mm:ss'); + blueP.duration = moment.utc((blueP.duration || 0)*1000).format('HH:mm:ss'); blueP.type="Blueprint"; blueP['actionpath'] ='/schedulingunit/view/blueprint/'+blueP.id; return blueP; @@ -65,7 +67,7 @@ class SchedulingUnitList extends Component{ output.push(...blueprintdata); scheduleunit['actionpath']='/schedulingunit/view/draft/'+scheduleunit.id; scheduleunit['type'] = 'Draft'; - scheduleunit['duration'] = moment.utc(scheduleunit.duration*1000).format('HH:mm:ss'); + scheduleunit['duration'] = moment.utc((scheduleunit.duration || 0)*1000).format('HH:mm:ss'); output.push(scheduleunit); } this.setState({ 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 c57445b61784420eceb8a9f538d5c113fea84909..5b35acb3ea87b6311ca3277ea549930f28e171bb 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/ViewSchedulingUnit.js @@ -32,6 +32,7 @@ class ViewSchedulingUnit extends Component{ "start_time":"Start Time", "stop_time":"End Time", "duration":"Duration (HH:mm:ss)", + "status":"Status" }], optionalcolumns: [{ "relative_start_time":"Relative Start Time (HH:mm:ss)", @@ -50,14 +51,15 @@ class ViewSchedulingUnit extends Component{ "BluePrint / Task Draft link": "filter-input-100", "Relative Start Time (HH:mm:ss)": "filter-input-75", "Relative End Time (HH:mm:ss)": "filter-input-75", + "Status":"filter-input-100" }] } this.actions = [ {icon: 'fa-window-close',title:'Click to Close Scheduling Unit View', link: this.props.history.goBack} ]; if (this.props.match.params.type === 'draft') { - this.actions.unshift({icon: 'fa-edit', title: 'Click to edit', props : { pathname:`/schedulingunit/edit/${ this.props.match.params.id}` - } }); + this.actions.unshift({icon: 'fa-edit', title: 'Click to edit', props : { pathname:`/schedulingunit/edit/${ this.props.match.params.id}`} + }); } else { this.actions.unshift({icon: 'fa-lock', title: 'Cannot edit blueprint'}); } @@ -67,7 +69,7 @@ class ViewSchedulingUnit extends Component{ if (this.props.match.params.type) { this.state.scheduleunitType = this.props.match.params.type; } - } + } componentDidMount(){ let schedule_id = this.state.scheduleunitId; @@ -78,12 +80,6 @@ class ViewSchedulingUnit extends Component{ if (schedulingUnit) { this.getScheduleUnitTasks(schedule_type, schedulingUnit) .then(tasks =>{ - /* tasks.map(task => { - task.duration = moment.utc(task.duration*1000).format('HH:mm:ss'); - task.relative_start_time = moment.utc(task.relative_start_time*1000).format('HH:mm:ss'); - task.relative_stop_time = moment.utc(task.relative_stop_time*1000).format('HH:mm:ss'); - return task; - });*/ this.setState({ scheduleunit : schedulingUnit, schedule_unit_task : tasks, @@ -111,8 +107,8 @@ class ViewSchedulingUnit extends Component{ else return ScheduleService.getSchedulingUnitBlueprintById(id) } - - render(){ + + render(){ return( <> {/*} <div className="p-grid"> @@ -136,36 +132,42 @@ class ViewSchedulingUnit extends Component{ { this.state.isLoading ? <AppLoader/> :this.state.scheduleunit && <> <div className="main-content"> - <div className="p-grid"> - <label className="col-lg-2 col-md-2 col-sm-12">Name</label> - <span className="p-col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.name}</span> - <label className="col-lg-2 col-md-2 col-sm-12">Description</label> - <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.description}</span> - </div> - <div className="p-grid"> - <label className="col-lg-2 col-md-2 col-sm-12">Created At</label> - <span className="col-lg-4 col-md-4 col-sm-12">{moment(this.state.scheduleunit.created_at).format("YYYY-MMM-DD HH:mm:SS")}</span> - <label className="col-lg-2 col-md-2 col-sm-12">Updated At</label> - <span className="col-lg-4 col-md-4 col-sm-12">{moment(this.state.scheduleunit.updated_at).format("YYYY-MMM-DD HH:mm:SS")}</span> - </div> - <div className="p-grid"> - <label className="col-lg-2 col-md-2 col-sm-12">Start Time</label> - <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.start_time && moment(this.state.scheduleunit.start_time).format("YYYY-MMM-DD HH:mm:SS")}</span> - <label className="col-lg-2 col-md-2 col-sm-12">End Time</label> - <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.stop_time && moment(this.state.scheduleunit.stop_time).format("YYYY-MMM-DD HH:mm:SS")}</span> - </div> - <div className="p-grid"> - <label className="col-lg-2 col-md-2 col-sm-12">Template ID</label> - <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.requirements_template_id}</span> - <label className="col-lg-2 col-md-2 col-sm-12">Scheduling set</label> - <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.scheduling_set_object.name}</span> - </div> - <div className="p-grid"> - <label className="col-lg-2 col-md-2 col-sm-12">Duration (HH:mm:ss)</label> - <span className="col-lg-4 col-md-4 col-sm-12">{moment.utc(this.state.scheduleunit.duration*1000).format('HH:mm:ss')}</span> + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Name</label> + <span className="p-col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.name}</span> + <label className="col-lg-2 col-md-2 col-sm-12">Description</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.description}</span> + </div> + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Created At</label> + <span className="col-lg-4 col-md-4 col-sm-12">{moment(this.state.scheduleunit.created_at).format("YYYY-MMM-DD HH:mm:SS")}</span> + <label className="col-lg-2 col-md-2 col-sm-12">Updated At</label> + <span className="col-lg-4 col-md-4 col-sm-12">{moment(this.state.scheduleunit.updated_at).format("YYYY-MMM-DD HH:mm:SS")}</span> + </div> + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Start Time</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.start_time && moment(this.state.scheduleunit.start_time).format("YYYY-MMM-DD HH:mm:SS")}</span> + <label className="col-lg-2 col-md-2 col-sm-12">End Time</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.stop_time && moment(this.state.scheduleunit.stop_time).format("YYYY-MMM-DD HH:mm:SS")}</span> + </div> + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Template ID</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.requirements_template_id}</span> + <label className="col-lg-2 col-md-2 col-sm-12">Scheduling set</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.scheduleunit.scheduling_set_object.name}</span> + </div> + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12" >Duration (HH:mm:ss)</label> + <span className="col-lg-4 col-md-4 col-sm-12">{moment.utc((this.state.scheduleunit.duration?this.state.scheduleunit.duration:0)*1000).format('HH:mm:ss')}</span> + {this.props.match.params.type === 'blueprint' && + <label className="col-lg-2 col-md-2 col-sm-12 ">Status</label> } + {this.props.match.params.type === 'blueprint' && + <span className="col-lg-2 col-md-2 col-sm-12">{this.state.scheduleunit.status}</span>} + </div> + <div className="p-grid"> <label className="col-lg-2 col-md-2 col-sm-12">Tags</label> <Chips className="p-col-4 chips-readonly" disabled value={this.state.scheduleunit.tags}></Chips> - </div> + </div> </div> </> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js new file mode 100644 index 0000000000000000000000000000000000000000..e5ca99a752c5d3e59b739ecb292b5658b4f65bfd --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js @@ -0,0 +1,71 @@ +import React, {Component} from 'react'; +import { Link } from 'react-router-dom/cjs/react-router-dom.min'; +import moment from 'moment'; +import ViewTable from '../../components/ViewTable'; + +/** + * Component to view summary of the scheduling unit with limited task details + */ +export class SchedulingUnitSummary extends Component { + + constructor(props) { + super(props); + this.state = { + schedulingUnit: props.schedulingUnit || null + } + this.closeSUDets = this.closeSUDets.bind(this); + } + + componentDidMount() {} + + closeSUDets() { + if(this.props.closeCallback) { + this.props.closeCallback(); + } + } + + render() { + const schedulingUnit = this.props.schedulingUnit; + const suTaskList = this.props.suTaskList; + return ( + <React.Fragment> + { schedulingUnit && + <div className="p-grid timeline-details-pane" style={{marginTop: '10px'}}> + <h6 className="col-lg-10 col-sm-10">Details</h6> + <Link to={`/schedulingunit/view/blueprint/${schedulingUnit.id}`} title="View Full Details"><i className="fa fa-eye"></i></Link> + <Link to={`/su/timelineview`} onClick={this.closeSUDets} title="Close Details"><i className="fa fa-times"></i></Link> + <div className="col-4"><label>Name:</label></div> + <div className="col-8">{schedulingUnit.name}</div> + <div className="col-4"><label>Project:</label></div> + <div className="col-8">{schedulingUnit.project.name}</div> + <div className="col-4"><label>Start Time:</label></div> + <div className="col-8">{moment.utc(schedulingUnit.start_time).format("DD-MMM-YYYY HH:mm:ss")}</div> + <div className="col-4"><label>Stop Time:</label></div> + <div className="col-8">{moment.utc(schedulingUnit.stop_time).format("DD-MMM-YYYY HH:mm:ss")}</div> + <div className="col-4"><label>Status:</label></div> + <div className="col-8">{schedulingUnit.status}</div> + <div className="col-12"> + <ViewTable + data={suTaskList} + defaultcolumns={[{id: "ID", start_time:"Start Time", stop_time:"End Time", status: "Status", + antenna_set: "Antenna Set", band: 'Band'}]} + optionalcolumns={[{actionpath: "actionpath"}]} + columnclassname={[{"ID": "filter-input-50", "Start Time": "filter-input-75", "End Time": "filter-input-75", + "Status": "filter-input-75", "Antenna Set": "filter-input-75", "Band": "filter-input-75"}]} + defaultSortColumn= {[{id: "ID", desc: false}]} + showaction="false" + tablename="timeline_su_taskslist" + showTopTotal={false} + showGlobalFilter={false} + showColumnFilter={false} + allowColumnSelection={false} + /> + </div> + </div> + } + </React.Fragment> + ); + } +} + +export default SchedulingUnitSummary; \ No newline at end of file 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 7b1577973c9b081ff5534cb129c013846e890b5b..3d4ad6a1a31004c9c31dcb1b9f77adcf94cc15bc 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js @@ -15,6 +15,7 @@ import ScheduleService from '../../services/schedule.service'; import UtilService from '../../services/util.service'; import UnitConverter from '../../utils/unit.converter'; +import SchedulingUnitSummary from '../Scheduling/summary'; // Color constant for status const STATUS_COLORS = { "ERROR": "FF0000", "CANCELLED": "#00FF00", "DEFINED": "#00BCD4", @@ -39,13 +40,16 @@ export class TimelineView extends Component { isSUDetsVisible: false, canExtendSUList: true, canShrinkSUList: false, - selectedItem: null + selectedItem: null, + suTaskList:[], + isSummaryLoading: false } this.onItemClick = this.onItemClick.bind(this); this.closeSUDets = this.closeSUDets.bind(this); this.dateRangeCallback = this.dateRangeCallback.bind(this); this.resizeSUList = this.resizeSUList.bind(this); + this.suListFilterCallback = this.suListFilterCallback.bind(this); } async componentDidMount() { @@ -126,7 +130,24 @@ export class TimelineView extends Component { if (this.state.isSUDetsVisible && item.id===this.state.selectedItem.id) { this.closeSUDets(); } else { - this.setState({selectedItem: item, isSUDetsVisible: true, canExtendSUList: false, canShrinkSUList:false}); + const fetchDetails = !this.state.selectedItem || item.id!==this.state.selectedItem.id + this.setState({selectedItem: item, isSUDetsVisible: true, + isSummaryLoading: fetchDetails, + suTaskList: !fetchDetails?this.state.suTaskList:[], + canExtendSUList: false, canShrinkSUList:false}); + if (fetchDetails) { + const suBlueprint = _.find(this.state.suBlueprints, {id: item.id}); + ScheduleService.getTaskBlueprintsBySchedulingUnit(suBlueprint, true) + .then(taskList => { + for (let task of taskList) { + if (task.template.type_value.toLowerCase() === "observation") { + task.antenna_set = task.specifications_doc.antenna_set; + task.band = task.specifications_doc.filter; + } + } + this.setState({suTaskList: _.sortBy(taskList, "id"), isSummaryLoading: false}) + }); + } } } @@ -162,7 +183,7 @@ export class TimelineView extends Component { } this.setState({suBlueprintList: _.filter(suBlueprintList, (suBlueprint) => {return suBlueprint.start_time!=null})}); // On range change close the Details pane - this.closeSUDets(); + // this.closeSUDets(); return {group: group, items: items}; } @@ -185,6 +206,25 @@ export class TimelineView extends Component { this.setState({canExtendSUList: canExtendSUList, canShrinkSUList: canShrinkSUList}); } + /** + * Callback function to pass to the ViewTable component to pass back filtered data + * @param {Array} filteredData + */ + suListFilterCallback(filteredData) { + let group=[], items = []; + const suBlueprints = this.state.suBlueprints; + for (const data of filteredData) { + const suBlueprint = _.find(suBlueprints, {actionpath: data.actionpath}); + items.push(this.getTimelineItem(suBlueprint)); + if (!_.find(group, {'id': suBlueprint.suDraft.id})) { + group.push({'id': suBlueprint.suDraft.id, title: suBlueprint.suDraft.name}); + } + } + if (this.timeline) { + this.timeline.updateTimeline({group: group, items: items}); + } + } + render() { if (this.state.redirect) { return <Redirect to={ {pathname: this.state.redirect} }></Redirect> @@ -192,21 +232,15 @@ export class TimelineView extends Component { const isSUDetsVisible = this.state.isSUDetsVisible; const canExtendSUList = this.state.canExtendSUList; const canShrinkSUList = this.state.canShrinkSUList; + let suBlueprint = null; + if (isSUDetsVisible) { + suBlueprint = _.find(this.state.suBlueprints, {id: this.state.selectedItem.id}); + } return ( <React.Fragment> <PageHeader location={this.props.location} title={'Scheduling Units - Timeline View'} /> { this.state.isLoading ? <AppLoader /> : <div className="p-grid"> - {/* <SplitPane split="vertical" defaultSize={600} style={{height: 'auto'}} primary="second"> */} - {/* <div className={isSUDetsVisible || (canExtendSUList && !canShrinkSUList)?"resize-div-min col-lg-4 col-md-4 col-sm-12":((canExtendSUList && canShrinkSUList)?"resize-div-avg col-lg-5 col-md-5 col-sm-12":"resize-div-max col-lg-6 col-md-6 col-sm-12")}> - <button className="p-link resize-btn" disabled={!this.state.canExtendSUList} - onClick={(e)=> { this.resizeSUList(1)}}> - <i className="pi pi-step-forward"></i> - </button> - <button className="p-link resize-btn" disabled={!this.state.canShrinkSUList} - onClick={(e)=> { this.resizeSUList(-1)}}> - <i className="pi pi-step-backward"></i> - </button></div> */} {/* SU List Panel */} <div className={isSUDetsVisible || (canExtendSUList && !canShrinkSUList)?"col-lg-4 col-md-4 col-sm-12":((canExtendSUList && canShrinkSUList)?"col-lg-5 col-md-5 col-sm-12":"col-lg-6 col-md-6 col-sm-12")} style={{position: "inherit", borderRight: "5px solid #efefef", paddingTop: "10px"}}> @@ -221,10 +255,11 @@ export class TimelineView extends Component { showaction="true" tablename="timeline_scheduleunit_list" showTopTotal="false" + filterCallback={this.suListFilterCallback} /> </div> {/* Timeline Panel */} - <div className={isSUDetsVisible || (!canExtendSUList && canShrinkSUList)?"col-lg-6 col-md-6 col-sm-12":((canExtendSUList && canShrinkSUList)?"col-lg-7 col-md-7 col-sm-12":"col-lg-8 col-md-8 col-sm-12")}> + <div className={isSUDetsVisible || (!canExtendSUList && canShrinkSUList)?"col-lg-5 col-md-5 col-sm-12":((canExtendSUList && canShrinkSUList)?"col-lg-7 col-md-7 col-sm-12":"col-lg-8 col-md-8 col-sm-12")}> {/* Panel Resize buttons */} <div className="resize-div"> <button className="p-link resize-btn" disabled={!this.state.canShrinkSUList} @@ -245,21 +280,14 @@ export class TimelineView extends Component { rowHeight={30} itemClickCallback={this.onItemClick} dateRangeCallback={this.dateRangeCallback}></Timeline> </div> - {/* </SplitPane> */} {/* Details Panel */} {this.state.isSUDetsVisible && - <div className="col-lg-2 col-md-2 col-sm-12" + <div className="col-lg-3 col-md-3 col-sm-12" style={{borderLeft: "1px solid #efefef", marginTop: "0px", backgroundColor: "#f2f2f2"}}> - <div className="p-grid" style={{marginTop: '10px'}}> - <h6 className="col-lg-10 col-sm-10">Details</h6> - <button className="p-link" onClick={this.closeSUDets}><i className="fa fa-times"></i></button> - - <div className="col-12"> - {this.state.selectedItem.title} - </div> - - <div className="col-12">Still In Development</div> - </div> + {this.state.isSummaryLoading?<AppLoader /> : + <SchedulingUnitSummary schedulingUnit={suBlueprint} suTaskList={this.state.suTaskList} + closeCallback={this.closeSUDets}></SchedulingUnitSummary> + } </div> } 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 30a2c0db09bf506a44b47f156ba4f8b26ec2c3f6..f8d5b8bdb035b4812b683f863ff01bdd0a778d52 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js @@ -52,29 +52,31 @@ const ScheduleService = { return null; } }, - getTaskBlueprintById: async function(id){ - let res = []; - await axios.get('/api/task_blueprint/'+id) - .then(response => { - res= response; - }).catch(function(error) { + getTaskBlueprintById: async function(id, loadTemplate){ + let result; + try { + result = await axios.get('/api/task_blueprint/'+id); + if (result.data && loadTemplate) { + result.data.template = await TaskService.getTaskTemplate(result.data.specifications_template_id); + } + } catch(error) { console.error('[schedule.services.getTaskBlueprintById]',error); - }); - return res; + } + return result; }, - getTaskBlueprintsBySchedulingUnit: async function(scheduleunit){ + getTaskBlueprintsBySchedulingUnit: async function(scheduleunit, loadTemplate){ // there no single api to fetch associated task_blueprint, so iteare the task_blueprint id to fetch associated task_blueprint let taskblueprintsList = []; if(scheduleunit.task_blueprints_ids){ for(const id of scheduleunit.task_blueprints_ids){ - await this.getTaskBlueprintById(id).then(response =>{ + await this.getTaskBlueprintById(id, loadTemplate).then(response =>{ let taskblueprint = response.data; taskblueprint['tasktype'] = 'Blueprint'; taskblueprint['actionpath'] = '/task/view/blueprint/'+taskblueprint['id']; taskblueprint['blueprint_draft'] = taskblueprint['draft']; taskblueprint['relative_start_time'] = 0; taskblueprint['relative_stop_time'] = 0; - taskblueprint.duration = moment.utc(taskblueprint.duration*1000).format('HH:mm:ss'); + taskblueprint.duration = moment.utc((taskblueprint.duration || 0)*1000).format('HH:mm:ss'); taskblueprintsList.push(taskblueprint); }) } @@ -85,9 +87,9 @@ const ScheduleService = { let scheduletasklist=[]; // let taskblueprints = []; // Common keys for Task and Blueprint - let commonkeys = ['id','created_at','description','name','tags','updated_at','url','do_cancel','relative_start_time','relative_stop_time','start_time','stop_time','duration']; + let commonkeys = ['id','created_at','description','name','tags','updated_at','url','do_cancel','relative_start_time','relative_stop_time','start_time','stop_time','duration','status']; // await this.getTaskBlueprints().then( blueprints =>{ - // taskblueprints = blueprints.data.results; + // taskblueprints = blueprints.data.results;' // }); await this.getTasksDraftBySchedulingUnitId(id) .then(async(response) =>{ @@ -96,12 +98,13 @@ const ScheduleService = { scheduletask['tasktype'] = 'Draft'; scheduletask['actionpath'] = '/task/view/draft/'+task['id']; scheduletask['blueprint_draft'] = task['task_blueprints']; + scheduletask['status'] = task['status']; //fetch task draft details for(const key of commonkeys){ scheduletask[key] = task[key]; } - scheduletask.duration = moment.utc(scheduletask.duration*1000).format('HH:mm:ss'); + scheduletask.duration = moment.utc((scheduletask.duration || 0)*1000).format('HH:mm:ss'); scheduletask.relative_start_time = moment.utc(scheduletask.relative_start_time*1000).format('HH:mm:ss'); scheduletask.relative_stop_time = moment.utc(scheduletask.relative_stop_time*1000).format('HH:mm:ss'); //Fetch blueprint details for Task Draft @@ -115,10 +118,12 @@ const ScheduleService = { taskblueprint['tasktype'] = 'Blueprint'; taskblueprint['actionpath'] = '/task/view/blueprint/'+blueprint['id']; taskblueprint['blueprint_draft'] = blueprint['draft']; + taskblueprint['status'] = blueprint['status']; + for(const key of commonkeys){ taskblueprint[key] = blueprint[key]; } - taskblueprint.duration = moment.utc(taskblueprint.duration*1000).format('HH:mm:ss'); + taskblueprint.duration = moment.utc((taskblueprint.duration || 0)*1000).format('HH:mm:ss'); taskblueprint.relative_start_time = moment.utc(taskblueprint.relative_start_time*1000).format('HH:mm:ss'); taskblueprint.relative_stop_time = moment.utc(taskblueprint.relative_stop_time*1000).format('HH:mm:ss'); @@ -272,12 +277,12 @@ const ScheduleService = { for(const suDraft of suDraftList){ suDraft['actionpath']='/schedulingunit/view/draft/'+suDraft.id; suDraft['type'] = 'Draft'; - suDraft['duration'] = moment.utc(suDraft.duration*1000).format('HH:mm:ss'); + suDraft['duration'] = moment.utc((suDraft.duration || 0)*1000).format('HH:mm:ss'); schedulingunitlist = schedulingunitlist.concat(suDraft); //Fetch SU Blue prints for the SU Draft await this.getBlueprintsByschedulingUnitId(suDraft.id).then(suBlueprintList =>{ for(const suBlueprint of suBlueprintList.data.results){ - suBlueprint.duration = moment.utc(suBlueprint.duration*1000).format('HH:mm:ss'); + suBlueprint.duration = moment.utc((suBlueprint.duration || 0)*1000).format('HH:mm:ss'); suBlueprint.type="Blueprint"; suBlueprint['actionpath'] = '/schedulingunit/view/blueprint/'+suBlueprint.id; schedulingunitlist = schedulingunitlist.concat(suBlueprint); diff --git a/SAS/TMSS/src/tmss/CMakeLists.txt b/SAS/TMSS/src/tmss/CMakeLists.txt index a38c2b149ed20a69a4ae3376365d869db9c1990e..3e7754777f2f6d34a58352c9d78765303dd9cfa4 100644 --- a/SAS/TMSS/src/tmss/CMakeLists.txt +++ b/SAS/TMSS/src/tmss/CMakeLists.txt @@ -13,3 +13,4 @@ python_install(${_py_files} DESTINATION lofar/sas/tmss/tmss) add_subdirectory(tmssapp) +add_subdirectory(workflowapp) diff --git a/SAS/TMSS/src/tmss/settings.py b/SAS/TMSS/src/tmss/settings.py index 3fcb6ea5e997dfabaa0357e8d62c9da6b4a54cac..97b14e0609ec957a3553493dec1a668033b7a841 100644 --- a/SAS/TMSS/src/tmss/settings.py +++ b/SAS/TMSS/src/tmss/settings.py @@ -120,8 +120,17 @@ INSTALLED_APPS = [ 'material.frontend', 'viewflow', 'viewflow.frontend', + 'lofar.sas.tmss.tmss.workflowapp', + 'debug_toolbar', ] +def show_debug_toolbar(*args, **kwargs): + return os.environ.get('SHOW_DJANGO_DEBUG_TOOLBAR', False) + +DEBUG_TOOLBAR_CONFIG = { + 'SHOW_TOOLBAR_CALLBACK': show_debug_toolbar +} + MIDDLEWARE = [ 'django.middleware.gzip.GZipMiddleware', 'django.middleware.security.SecurityMiddleware', @@ -133,6 +142,7 @@ MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware' ] + ROOT_URLCONF = 'lofar.sas.tmss.tmss.urls' TEMPLATES = [ diff --git a/SAS/TMSS/src/tmss/tmssapp/CMakeLists.txt b/SAS/TMSS/src/tmss/tmssapp/CMakeLists.txt index e24af6998d0ad9240a454cd41fdb389a38cb4208..58c545f7ed434d8c05064e1fad48ebf0c93d821a 100644 --- a/SAS/TMSS/src/tmss/tmssapp/CMakeLists.txt +++ b/SAS/TMSS/src/tmss/tmssapp/CMakeLists.txt @@ -23,5 +23,3 @@ add_subdirectory(serializers) add_subdirectory(viewsets) add_subdirectory(adapters) add_subdirectory(schemas) -add_subdirectory(workflows) - diff --git a/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py b/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py index daa63f9369488f5e160485fbfec01af9cdb5121b..45b53b6b1f17acc72ba81dedbfe5036d1b420889 100644 --- a/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py +++ b/SAS/TMSS/src/tmss/tmssapp/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.0.9 on 2020-09-24 15:47 +# Generated by Django 3.0.9 on 2020-09-30 09:15 from django.conf import settings import django.contrib.postgres.fields @@ -14,7 +14,6 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('viewflow', '0008_jsonfield_and_artifact'), ] operations = [ @@ -492,26 +491,6 @@ class Migration(migrations.Migration): 'abstract': False, }, ), - migrations.CreateModel( - name='SchedulingUnitDemo', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=50)), - ('state', models.IntegerField()), - ], - ), - migrations.CreateModel( - name='SchedulingUnitDemoProcess', - fields=[ - ('process_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='viewflow.Process')), - ('text', models.CharField(max_length=150)), - ('approved', models.BooleanField(default=False)), - ], - options={ - 'abstract': False, - }, - bases=('viewflow.process',), - ), migrations.CreateModel( name='SchedulingUnitDraft', fields=[ @@ -764,19 +743,6 @@ class Migration(migrations.Migration): 'abstract': False, }, ), - migrations.CreateModel( - name='HelloWorldProcess', - fields=[ - ], - options={ - 'verbose_name': 'World Request', - 'verbose_name_plural': 'World Requests', - 'proxy': True, - 'indexes': [], - 'constraints': [], - }, - bases=('viewflow.process',), - ), migrations.CreateModel( name='Setting', fields=[ @@ -1093,11 +1059,6 @@ class Migration(migrations.Migration): name='scheduling_set', field=models.ForeignKey(help_text='Set to which this scheduling unit draft belongs.', on_delete=django.db.models.deletion.CASCADE, related_name='scheduling_unit_drafts', to='tmssapp.SchedulingSet'), ), - migrations.AddField( - model_name='schedulingunitdemoprocess', - name='su', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='tmssapp.SchedulingUnitDemo'), - ), migrations.AddField( model_name='schedulingunitblueprint', name='draft', diff --git a/SAS/TMSS/src/tmss/tmssapp/models/CMakeLists.txt b/SAS/TMSS/src/tmss/tmssapp/models/CMakeLists.txt index 2ac64b115ecf2f4bc700c614a3ba9572f3af6aa6..7598bc12c79161c19b95275e001a28adb92d3b56 100644 --- a/SAS/TMSS/src/tmss/tmssapp/models/CMakeLists.txt +++ b/SAS/TMSS/src/tmss/tmssapp/models/CMakeLists.txt @@ -5,8 +5,6 @@ set(_py_files __init__.py specification.py scheduling.py - helloworldflow.py - schedulingunitdemoflow.py ) python_install(${_py_files} diff --git a/SAS/TMSS/src/tmss/tmssapp/models/__init__.py b/SAS/TMSS/src/tmss/tmssapp/models/__init__.py index be7a174d740d60b255c47117cb8abfc657cc9bde..93f3c7e6d54f95c40d6d9484aad802b13f9991ba 100644 --- a/SAS/TMSS/src/tmss/tmssapp/models/__init__.py +++ b/SAS/TMSS/src/tmss/tmssapp/models/__init__.py @@ -1,4 +1,2 @@ from .specification import * -from .scheduling import * -from .helloworldflow import * -from .schedulingunitdemoflow import * \ No newline at end of file +from .scheduling import * \ No newline at end of file diff --git a/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py b/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py index a3ebd865de710e9df320248e1614f9ba4f5344da..0ca0fd3810e9afb9ceeb5ad0249f0673e9557f40 100644 --- a/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py +++ b/SAS/TMSS/src/tmss/tmssapp/models/scheduling.py @@ -149,7 +149,7 @@ class Subtask(BasicCommon): super().__init__(*args, **kwargs) # keep original state for logging - self.__original_state = self.state + self.__original_state_id = self.state_id @staticmethod def _send_state_change_event_message(subtask_id:int, old_state: str, new_state: str): @@ -189,7 +189,7 @@ class Subtask(BasicCommon): annotate_validate_add_defaults_to_doc_using_template(self, 'specifications_doc', 'specifications_template') - if self.state.value == SubtaskState.Choices.SCHEDULED.value and self.__original_state.value == SubtaskState.Choices.SCHEDULING.value: + if self.state.value == SubtaskState.Choices.SCHEDULED.value and self.__original_state_id == SubtaskState.Choices.SCHEDULING.value: if self.start_time is None: if self.predecessors.all().count() == 0: raise SubtaskSchedulingException("Cannot schedule subtask id=%s when start time is 'None'." % (self.pk, )) @@ -202,12 +202,12 @@ class Subtask(BasicCommon): super().save(force_insert, force_update, using, update_fields) # log if either state update or new entry: - if self.state != self.__original_state or creating == True: + if self.state_id != self.__original_state_id or creating == True: if self.created_or_updated_by_user is None: identifier = None else: identifier = self.created_or_updated_by_user.email - log_entry = SubtaskStateLog(subtask=self, old_state=self.__original_state, new_state=self.state, + log_entry = SubtaskStateLog(subtask=self, old_state=SubtaskState.objects.get(value=self.__original_state_id), new_state=self.state, user=self.created_or_updated_by_user, user_identifier=identifier) log_entry.save() @@ -217,7 +217,7 @@ class Subtask(BasicCommon): logger.error("Could not send state change to messagebus: %s", e) # update the previous state value - self.__original_state = self.state + self.__original_state_id = self.state_id class SubtaskStateLog(BasicCommon): """ diff --git a/SAS/TMSS/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/src/tmss/tmssapp/models/specification.py index f6665e3da24db201901f135fc5708efe6e8f0caa..3caf5c6a31395acee91640096b4c8438ca496eaf 100644 --- a/SAS/TMSS/src/tmss/tmssapp/models/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/models/specification.py @@ -19,7 +19,7 @@ import json import jsonschema from django.urls import reverse as revese_url from collections import Counter - +from django.utils.functional import cached_property # # Common @@ -41,7 +41,7 @@ class BasicCommon(Model): class NamedCommon(BasicCommon): name = CharField(max_length=128, help_text='Human-readable name of this object.', null=False) # todo: check if we want to have this primary_key=True - description = CharField(max_length=255, help_text='A longer description of this object.') + description = CharField(max_length=255, help_text='A longer description of this object.', blank=True) def __str__(self): return self.name @@ -412,7 +412,7 @@ class Cycle(NamedCommonPK): start = DateTimeField(help_text='Moment at which the cycle starts, that is, when its projects can run.') stop = DateTimeField(help_text='Moment at which the cycle officially ends.') - @property + @cached_property def duration(self) -> datetime.timedelta: '''the duration of the cycle (stop-start date)''' return self.stop - self.start @@ -553,19 +553,19 @@ class SchedulingUnitDraft(NamedCommon): validate_json_against_schema(self.observation_strategy_template.template, self.requirements_template.schema) if self.scheduling_constraints_doc is not None and self.scheduling_constraints_template_id and self.scheduling_constraints_template.schema is not None: - validate_json_against_schema(self.scheduling_constraints_doc, self.scheduling_constraints_template.schema) + validate_json_against_schema(self.scheduling_constraints_doc, self.scheduling_constraints_template.schema) annotate_validate_add_defaults_to_doc_using_template(self, 'requirements_doc', 'requirements_template') annotate_validate_add_defaults_to_doc_using_template(self, 'scheduling_constraints_doc', 'scheduling_constraints_template') super().save(force_insert, force_update, using, update_fields) - @property + @cached_property def duration(self) -> datetime.timedelta: '''return the overall duration of all tasks of this scheduling unit ''' return self.relative_stop_time - self.relative_start_time - @property + @cached_property def relative_start_time(self) -> datetime.timedelta: '''return the earliest relative start time of all tasks of this scheduling unit ''' @@ -575,7 +575,7 @@ class SchedulingUnitDraft(NamedCommon): else: return datetime.timedelta(seconds=0) - @property + @cached_property def relative_stop_time(self) -> datetime.timedelta: '''return the latest relative stop time of all tasks of this scheduling unit ''' @@ -597,7 +597,7 @@ class SchedulingUnitBlueprint(NamedCommon): super().save(force_insert, force_update, using, update_fields) - @property + @cached_property def duration(self) -> datetime.timedelta: '''return the overall duration of all tasks of this scheduling unit ''' @@ -606,7 +606,7 @@ class SchedulingUnitBlueprint(NamedCommon): else: return self.stop_time - self.start_time # <- todo: do we ever want this? - @property + @cached_property def relative_start_time(self) -> datetime.timedelta: '''return the earliest relative start time of all tasks of this scheduling unit ''' @@ -616,7 +616,7 @@ class SchedulingUnitBlueprint(NamedCommon): else: return datetime.timedelta(seconds=0) - @property + @cached_property def relative_stop_time(self) -> datetime.timedelta: '''return the latest relative stop time of all tasks of this scheduling unit ''' @@ -626,7 +626,7 @@ class SchedulingUnitBlueprint(NamedCommon): else: return datetime.timedelta(seconds=0) - @property + @cached_property def start_time(self) -> datetime or None: '''return the earliest start time of all tasks of this scheduling unit ''' @@ -636,7 +636,7 @@ class SchedulingUnitBlueprint(NamedCommon): else: return None - @property + @cached_property def stop_time(self) -> datetime or None: '''return the latest stop time of all tasks of this scheduling unit ''' @@ -758,7 +758,7 @@ class TaskDraft(NamedCommon): annotate_validate_add_defaults_to_doc_using_template(self, 'specifications_doc', 'specifications_template') super().save(force_insert, force_update, using, update_fields) - @property + @cached_property def successors(self) -> QuerySet: '''return the connect successor taskdraft(s) as queryset (over which you can perform extended queries, or return via the serializers/viewsets) If you want the result, add .all() like so: my_taskdraft.successors.all() @@ -768,7 +768,7 @@ class TaskDraft(NamedCommon): "INNER JOIN tmssapp_taskrelationdraft as task_rel on task_rel.consumer_id = successor_task.id\n" "WHERE task_rel.producer_id = %s", params=[self.id])) - @property + @cached_property def predecessors(self) -> QuerySet: '''return the connect predecessor taskdraft(s) as queryset (over which you can perform extended queries, or return via the serializers/viewsets) If you want the result, add .all() like so: my_taskdraft.predecessors.all() @@ -778,26 +778,26 @@ class TaskDraft(NamedCommon): "INNER JOIN tmssapp_taskrelationdraft as task_rel on task_rel.producer_id = successor_task.id\n" "WHERE task_rel.consumer_id = %s", params=[self.id])) - @property + @cached_property def duration(self) -> datetime.timedelta: '''returns the overall duration of this task ''' return self.relative_stop_time - self.relative_start_time - @property + @cached_property def relative_start_time(self) -> datetime.timedelta: '''return the earliest relative start time of all subtasks of this task ''' scheduling_relations = list(self.first_to_connect.all()) + list(self.second_to_connect.all()) for scheduling_relation in scheduling_relations: - if scheduling_relation.first.id == self.id and scheduling_relation.placement.value == "after": + if scheduling_relation.first.id == self._id and scheduling_relation.placement_id == "after": previous_related_task_draft = TaskDraft.objects.get(id=scheduling_relation.second.id) time_offset = scheduling_relation.time_offset # todo: max of several relations if previous_related_task_draft.relative_stop_time: return previous_related_task_draft.relative_stop_time + datetime.timedelta(seconds=time_offset) - if scheduling_relation.second.id == self.id and scheduling_relation.placement.value == "before": + if scheduling_relation.second.id == self._id and scheduling_relation.placement_id == "before": previous_related_task_draft = TaskDraft.objects.get(id=scheduling_relation.first.id) time_offset = scheduling_relation.time_offset # todo: max of several relations @@ -805,7 +805,7 @@ class TaskDraft(NamedCommon): return previous_related_task_draft.relative_stop_time + datetime.timedelta(seconds=time_offset) return datetime.timedelta(seconds=0) - @property + @cached_property def relative_stop_time(self) -> datetime.timedelta: '''return the latest relative stop time of all subtasks of this task ''' @@ -825,7 +825,7 @@ class TaskDraft(NamedCommon): # Only on the blueprints, we also aggregate start_stop times as they are in the system # I'll leave these code bits here for now, until we made up our minds about this, but this can probably be removed # - # @property + # @cached_property # def duration(self) -> datetime.timedelta: # '''returns the overall duration in seconds of all blueprints of this task # # todo: is this the wanted behavior? Do you want to consider all the blueprints created from your draft or do you want to preview a new blueprint? @@ -836,7 +836,7 @@ class TaskDraft(NamedCommon): # else: # return self.stop_time - self.start_time # - # @property + # @cached_property # def start_time(self) -> datetime or None: # '''return the earliest start time of all blueprints of this task # # todo: is this the wanted behavior? Do you want to consider all the blueprints created from your draft or do you want to preview a new blueprint? @@ -848,7 +848,7 @@ class TaskDraft(NamedCommon): # # todo: calculate? # return None # - # @property + # @cached_property # def stop_time(self) -> datetime or None: # '''return the latest stop time of all blueprints of this task # # todo: is this the wanted behavior? Do you want to consider all the blueprints created from your draft or do you want to preview a new blueprint? @@ -872,7 +872,7 @@ class TaskBlueprint(NamedCommon): annotate_validate_add_defaults_to_doc_using_template(self, 'specifications_doc', 'specifications_template') super().save(force_insert, force_update, using, update_fields) - @property + @cached_property def successors(self) -> QuerySet: '''return the connect successor taskblueprint(s) as queryset (over which you can perform extended queries, or return via the serializers/viewsets) If you want the result, add .all() like so: my_taskblueprint.successors.all() @@ -882,7 +882,7 @@ class TaskBlueprint(NamedCommon): "INNER JOIN tmssapp_taskrelationblueprint as task_rel on task_rel.consumer_id = successor_task.id\n" "WHERE task_rel.producer_id = %s", params=[self.id])) - @property + @cached_property def predecessors(self) -> QuerySet: '''return the connect predecessor taskblueprint(s) as queryset (over which you can perform extended queries, or return via the serializers/viewsets) If you want the result, add .all() like so: my_taskblueprint.predecessors.all() @@ -892,7 +892,7 @@ class TaskBlueprint(NamedCommon): "INNER JOIN tmssapp_taskrelationblueprint as task_rel on task_rel.producer_id = predecessor_task.id\n" "WHERE task_rel.consumer_id = %s", params=[self.id])) - @property + @cached_property def duration(self) -> datetime.timedelta: '''return the overall duration of this task ''' @@ -901,20 +901,20 @@ class TaskBlueprint(NamedCommon): else: return self.stop_time - self.start_time - @property + @cached_property def relative_start_time(self) -> datetime.timedelta: '''return the earliest relative start time of all subtasks of this task ''' scheduling_relations = list(self.first_to_connect.all()) + list(self.second_to_connect.all()) for scheduling_relation in scheduling_relations: - if scheduling_relation.first.id == self.id and scheduling_relation.placement.value == "after": + if scheduling_relation.first.id == self._id and scheduling_relation.placement_id == "after": # self.id and placement.value will hit the db, this does not previous_related_task_blueprint = TaskBlueprint.objects.get(id=scheduling_relation.second.id) time_offset = scheduling_relation.time_offset # todo: max of several relations if previous_related_task_blueprint.relative_stop_time: return previous_related_task_blueprint.relative_stop_time + datetime.timedelta(seconds=time_offset) - if scheduling_relation.second.id == self.id and scheduling_relation.placement.value == "before": + if scheduling_relation.second.id == self._id and scheduling_relation.placement_id == "before": # self.id and placement.value will hit the db, this does not previous_related_task_blueprint = TaskBlueprint.objects.get(id=scheduling_relation.first.id) time_offset = scheduling_relation.time_offset # todo: max of several relations @@ -922,7 +922,7 @@ class TaskBlueprint(NamedCommon): return previous_related_task_blueprint.relative_stop_time + datetime.timedelta(seconds=time_offset) return datetime.timedelta(seconds=666660) - @property + @cached_property def relative_stop_time(self) -> datetime.timedelta: '''return the latest relative stop time of all subtasks of this task ''' @@ -934,7 +934,7 @@ class TaskBlueprint(NamedCommon): pass return self.relative_start_time - @property + @cached_property def start_time(self) -> datetime or None: '''return the earliest start time of all subtasks of this task ''' @@ -944,7 +944,7 @@ class TaskBlueprint(NamedCommon): else: return None - @property + @cached_property def stop_time(self) -> datetime or None: '''return the latest stop time of all subtasks of this task ''' diff --git a/SAS/TMSS/src/tmss/tmssapp/populate.py b/SAS/TMSS/src/tmss/tmssapp/populate.py index d9f964aae6c3d0b9ac70e02c653edcce27eb4c2a..b786248f34773046434364d3ddc887ecd6d59e3a 100644 --- a/SAS/TMSS/src/tmss/tmssapp/populate.py +++ b/SAS/TMSS/src/tmss/tmssapp/populate.py @@ -55,6 +55,12 @@ def populate_test_data(): from lofar.sas.tmss.test.tmss_test_data_django_models import SchedulingSet_test_data, SchedulingUnitDraft_test_data from lofar.sas.tmss.tmss.tmssapp.tasks import create_task_blueprints_and_subtasks_from_scheduling_unit_draft, create_task_blueprints_and_subtasks_and_schedule_subtasks_from_scheduling_unit_draft from lofar.sas.tmss.tmss.tmssapp.subtasks import schedule_subtask + from lofar.common.json_utils import get_default_json_object_for_schema + + constraints_template = models.SchedulingConstraintsTemplate.objects.get(name="constraints") + constraints_spec = get_default_json_object_for_schema(constraints_template.schema) + + strategy_template = models.SchedulingUnitObservingStrategyTemplate.objects.get(name="UC1 CTC+pipelines") # create a Test Scheduling Set UC1 under project TMSS-Commissioning tmss_project = models.Project.objects.get(name="TMSS-Commissioning") @@ -67,8 +73,6 @@ def populate_test_data(): logger.info('created test scheduling_set: %s', scheduling_set.name) for unit_nr in range(5): - strategy_template = models.SchedulingUnitObservingStrategyTemplate.objects.get(name="UC1 CTC+pipelines") - # the 'template' in the strategy_template is a predefined json-data blob which validates against the given scheduling_unit_template # a user might 'upload' a partial json-data blob, so add all the known defaults @@ -79,7 +83,9 @@ def populate_test_data(): scheduling_set=scheduling_set, requirements_template=strategy_template.scheduling_unit_template, requirements_doc=scheduling_unit_spec, - observation_strategy_template=strategy_template) + observation_strategy_template=strategy_template, + scheduling_constraints_doc=constraints_spec, + scheduling_constraints_template=constraints_template) scheduling_unit_draft.tags = ["TEST", "UC1"] scheduling_unit_draft.save() diff --git a/SAS/TMSS/src/tmss/tmssapp/schemas/scheduling_constraints_template-constraints-1.json b/SAS/TMSS/src/tmss/tmssapp/schemas/scheduling_constraints_template-constraints-1.json index 04bb208f0b4deff2d4a7d0491ef4108afe335922..77a916705c8df50c069f5929e11fc03d5586acf7 100644 --- a/SAS/TMSS/src/tmss/tmssapp/schemas/scheduling_constraints_template-constraints-1.json +++ b/SAS/TMSS/src/tmss/tmssapp/schemas/scheduling_constraints_template-constraints-1.json @@ -1,13 +1,32 @@ { + "$id":"http://tmss.lofar.org/api/schemas/schedulingconstraintstemplate/constraints/1#", "$schema": "http://json-schema.org/draft-06/schema#", - "title": "Constraints", - "description": "This schema defines the constraints for a scheduling unit", + "title": "constraints", + "description": "This schema defines the scheduling constraints for a scheduling unit", "version": 1, "definitions": { "timestamp": { + "description": "A timestamp defined in UTC", "type": "string", - "pattern": "\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+Z", - "format": "datetime" + "pattern": "\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d(\\.\\d+)?Z", + "format": "date-time" + }, + "timewindow": { + "type": "object", + "description": "A timewindow interval: [from, to)", + "properties": { + "from": { + "$ref": "#/definitions/timestamp" + }, + "to": { + "$ref": "#/definitions/timestamp" + } + }, + "additionalProperties": false, + "required": [ + "from", + "to" + ] }, "distance_on_sky": { "type": "number", @@ -24,7 +43,7 @@ "properties": { "scheduler": { "name": "Scheduler", - "description": "Which scheduling system wiil schedule this", + "description": "Which scheduling system will schedule this", "type": "string", "enum": [ "manual", @@ -34,6 +53,7 @@ }, "time": { "type": "object", + "default": {}, "properties": { "at": { "description": "Start at this moment", @@ -51,41 +71,28 @@ "description": "Run within one of these time windows", "type": "array", "items": { - "from": { - "$ref": "#/definitions/timestamp" - }, - "to": { - "$ref": "#/definitions/timestamp" - }, - "required": [ - "from", - "to" - ] + "$ref": "#/definitions/timewindow" }, - "additionalItems": false + "minItems":0, + "uniqueItems":true, + "default": [] }, "not_between": { - "description": "NOT run within one of these time windows", + "description": "Do NOT run within any of these time windows", "type": "array", "items": { - "from": { - "$ref": "#/definitions/timestamp" - }, - "to": { - "$ref": "#/definitions/timestamp" - }, - "required": [ - "from", - "to" - ] + "$ref": "#/definitions/timewindow" }, - "additionalItems": false + "minItems":0, + "uniqueItems":true, + "default": [] } }, "additionalProperties": false }, "daily": { "type": "object", + "default": {}, "properties": { "require_night": { "description": "Must run at night", @@ -107,6 +114,7 @@ }, "sky": { "type": "object", + "default": {}, "properties": { "min_calibrator_elevation": { "description": "Minimum elevation for all calibrator sources", @@ -123,14 +131,14 @@ "type": "object", "properties": { "from": { - "type": "integer", - "minimum": -43200, - "maximum": 43200 + "type": "number", + "minimum": -0.20943951, + "maximum": 0.20943951 }, "to": { - "type": "integer", - "minimum": -43200, - "maximum": 43200 + "type": "number", + "minimum": -0.20943951, + "maximum": 0.20943951 } }, "additionalProperties": false @@ -157,7 +165,6 @@ "additionalProperties": false } }, - "additionalProperties": false, "required": [ "scheduler" ] diff --git a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py index dc0f28d734c988f0343ccd657564d789ca62e9ee..bf250c5a51a2781970924e9ec30eb415d147b9fe 100644 --- a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py @@ -288,6 +288,7 @@ class SchedulingSetSerializer(RelationalHyperlinkedModelSerializer): class SchedulingUnitDraftSerializer(RelationalHyperlinkedModelSerializer): requirements_doc = JSONEditorField(schema_source="requirements_template.schema") + scheduling_constraints_doc = JSONEditorField(schema_source="scheduling_constraints_template.schema") duration = FloatDurationField(read_only=True) class Meta: diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/CMakeLists.txt b/SAS/TMSS/src/tmss/tmssapp/viewsets/CMakeLists.txt index 445e0bbe4672e5cdad3a5a41be8575dbf2169ff0..fc0325a523508e371b2456d96b3467274dae748d 100644 --- a/SAS/TMSS/src/tmss/tmssapp/viewsets/CMakeLists.txt +++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/CMakeLists.txt @@ -6,8 +6,6 @@ set(_py_files lofar_viewset.py specification.py scheduling.py - helloworldflow.py - schedulingunitdemoflow.py ) python_install(${_py_files} diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/__init__.py b/SAS/TMSS/src/tmss/tmssapp/viewsets/__init__.py index 882458975ee4be50507620471ed1026433ddf589..93f3c7e6d54f95c40d6d9484aad802b13f9991ba 100644 --- a/SAS/TMSS/src/tmss/tmssapp/viewsets/__init__.py +++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/__init__.py @@ -1,3 +1,2 @@ from .specification import * -from .scheduling import * -from .schedulingunitdemoflow import * \ No newline at end of file +from .scheduling import * \ No newline at end of file diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py b/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py index af49948d1615bac654f06ea03f55f8b09f679d6a..731dea891e03bf199867741111d93e58a53910ed 100644 --- a/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py +++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/scheduling.py @@ -144,6 +144,8 @@ class SubtaskViewSet(LOFARViewSet): filter_class = SubTaskFilter ordering = ('start_time',) + queryset = queryset.prefetch_related('state') + @swagger_auto_schema(auto_schema=TextPlainAutoSchema, responses={200: 'A LOFAR parset for this subtask (as plain text)', 403: 'forbidden', diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py index ce3fa163142398bbaf6ae6bf7e197b33b6311cd2..242f06d05a6084ad6f0aa64a46eec8ce75ac164d 100644 --- a/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/viewsets/specification.py @@ -307,6 +307,16 @@ class SchedulingUnitDraftViewSet(LOFARViewSet): queryset = models.SchedulingUnitDraft.objects.all() serializer_class = serializers.SchedulingUnitDraftSerializer + # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries + queryset = queryset.prefetch_related('copied_from') \ + .prefetch_related('scheduling_unit_blueprints')\ + .prefetch_related('task_drafts') + + # preselect all references to other models to avoid even more duplicate queries + queryset = queryset.select_related('copies') \ + .select_related('copy_reason') \ + .select_related('scheduling_set') + @swagger_auto_schema(responses={201: 'The Created SchedulingUnitBlueprint, see Location in Response header', 403: 'forbidden'}, operation_description="Carve SchedulingUnitDraft in stone, and make an (uneditable) blueprint out of it.") @@ -580,6 +590,9 @@ class SchedulingUnitBlueprintViewSet(LOFARViewSet): queryset = models.SchedulingUnitBlueprint.objects.all() serializer_class = serializers.SchedulingUnitBlueprintSerializer + # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries + queryset = queryset.prefetch_related('task_blueprints') + @swagger_auto_schema(responses={201: "This SchedulingUnitBlueprint, with references to its created TaskBlueprints and (scheduled) Subtasks.", 403: 'forbidden'}, operation_description="Create TaskBlueprint(s) for this scheduling unit, create subtasks, and schedule the ones that are not dependend on predecessors.") @@ -633,6 +646,22 @@ class TaskDraftViewSet(LOFARViewSet): queryset = models.TaskDraft.objects.all() serializer_class = serializers.TaskDraftSerializer + # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries + queryset = queryset.prefetch_related('first_to_connect') \ + .prefetch_related('second_to_connect')\ + .prefetch_related('produced_by')\ + .prefetch_related('consumed_by')\ + .prefetch_related('task_blueprints')\ + .prefetch_related('copied_from') + + # prefetch nested references in reverse models to avoid duplicate lookup queries + queryset = queryset.prefetch_related('first_to_connect__placement') \ + .prefetch_related('second_to_connect__placement') + + # select all references to other models to avoid even more duplicate queries + queryset = queryset.select_related('copies') \ + .select_related('copy_reason') + @swagger_auto_schema(responses={201: 'The created task blueprint, see Location in Response header', 403: 'forbidden'}, operation_description="Carve this draft task specification in stone, and make an (uneditable) blueprint out of it.") @@ -726,6 +755,17 @@ class TaskBlueprintViewSet(LOFARViewSet): queryset = models.TaskBlueprint.objects.all() serializer_class = serializers.TaskBlueprintSerializer + # prefetch all reverse related references from other models on their related_name to avoid a ton of duplicate queries + queryset = queryset.prefetch_related('first_to_connect')\ + .prefetch_related('second_to_connect')\ + .prefetch_related('produced_by')\ + .prefetch_related('consumed_by')\ + .prefetch_related('subtasks') + + # prefetch nested references in reverse models to avoid duplicate lookup queries + queryset = queryset.prefetch_related('first_to_connect__placement') \ + .prefetch_related('second_to_connect__placement') + @swagger_auto_schema(responses={201: "This TaskBlueprint, with it is created subtasks", 403: 'forbidden'}, operation_description="Create subtasks.") diff --git a/SAS/TMSS/src/tmss/urls.py b/SAS/TMSS/src/tmss/urls.py index 37d9d081a3a3ecbee61e876ea3ee365b7c111ace..0776702860d8c041eb193e83f2ba72b78d1d8031 100644 --- a/SAS/TMSS/src/tmss/urls.py +++ b/SAS/TMSS/src/tmss/urls.py @@ -23,7 +23,7 @@ from django.views.generic.base import TemplateView, RedirectView from collections import OrderedDict from rest_framework import routers, permissions -from .tmssapp import viewsets, models, serializers, views, workflows +from .tmssapp import viewsets, models, serializers, views from rest_framework.documentation import include_docs_urls from drf_yasg.views import get_schema_view from drf_yasg import openapi @@ -33,6 +33,8 @@ from datetime import datetime from material.frontend import urls as frontend_urls from viewflow.flow.viewset import FlowViewSet +import debug_toolbar + # # Django style patterns # @@ -63,7 +65,8 @@ urlpatterns = [ path('schemas/<str:template>/<str:name>/<str:version>', views.get_template_json_schema, name='get_template_json_schema'), #TODO: how to make trailing slash optional? path('schemas/<str:template>/<str:name>/<str:version>/', views.get_template_json_schema, name='get_template_json_schema'), path(r'util/utc', views.utc, name="system-utc"), - path(r'util/lst', views.lst, name="conversion-lst") + path(r'util/lst', views.lst, name="conversion-lst"), + path('__debug__/', include(debug_toolbar.urls)), ] diff --git a/SAS/TMSS/src/tmss/workflowapp/CMakeLists.txt b/SAS/TMSS/src/tmss/workflowapp/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..94b72e83e35a77ab9b16f84b7647f8ab0c8af94a --- /dev/null +++ b/SAS/TMSS/src/tmss/workflowapp/CMakeLists.txt @@ -0,0 +1,17 @@ + +include(PythonInstall) + +set(_py_files + __init__.py + admin.py + apps.py + tests.py + ) + +python_install(${_py_files} + DESTINATION lofar/sas/tmss/tmss/workflowapp) + +add_subdirectory(migrations) +add_subdirectory(models) +add_subdirectory(flows) +add_subdirectory(viewsets) diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/helloworldflow.py b/SAS/TMSS/src/tmss/workflowapp/__init__.py similarity index 100% rename from SAS/TMSS/src/tmss/tmssapp/viewsets/helloworldflow.py rename to SAS/TMSS/src/tmss/workflowapp/__init__.py diff --git a/SAS/TMSS/src/tmss/workflowapp/admin.py b/SAS/TMSS/src/tmss/workflowapp/admin.py new file mode 100644 index 0000000000000000000000000000000000000000..8c38f3f3dad51e4585f3984282c2a4bec5349c1e --- /dev/null +++ b/SAS/TMSS/src/tmss/workflowapp/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/SAS/TMSS/src/tmss/workflowapp/apps.py b/SAS/TMSS/src/tmss/workflowapp/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..d70dc7921a32145aa2a76285c3362041e091a358 --- /dev/null +++ b/SAS/TMSS/src/tmss/workflowapp/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class WorkflowappConfig(AppConfig): + name = 'workflowapp' diff --git a/SAS/TMSS/src/tmss/tmssapp/workflows/CMakeLists.txt b/SAS/TMSS/src/tmss/workflowapp/flows/CMakeLists.txt similarity index 72% rename from SAS/TMSS/src/tmss/tmssapp/workflows/CMakeLists.txt rename to SAS/TMSS/src/tmss/workflowapp/flows/CMakeLists.txt index 474aada33041160e598ac2b1a126d68971d75afd..769f922e4781a912f1c0488c3655f6ab61363d3a 100644 --- a/SAS/TMSS/src/tmss/tmssapp/workflows/CMakeLists.txt +++ b/SAS/TMSS/src/tmss/workflowapp/flows/CMakeLists.txt @@ -8,4 +8,4 @@ set(_py_files ) python_install(${_py_files} - DESTINATION lofar/sas/tmss/tmss/tmssapp/workflows) + DESTINATION lofar/sas/tmss/tmss/workflowapp/flows) diff --git a/SAS/TMSS/src/tmss/tmssapp/workflows/__init__.py b/SAS/TMSS/src/tmss/workflowapp/flows/__init__.py similarity index 100% rename from SAS/TMSS/src/tmss/tmssapp/workflows/__init__.py rename to SAS/TMSS/src/tmss/workflowapp/flows/__init__.py diff --git a/SAS/TMSS/src/tmss/tmssapp/workflows/helloworldflow.py b/SAS/TMSS/src/tmss/workflowapp/flows/helloworldflow.py similarity index 97% rename from SAS/TMSS/src/tmss/tmssapp/workflows/helloworldflow.py rename to SAS/TMSS/src/tmss/workflowapp/flows/helloworldflow.py index d3307efe5f773359de58c89bea4a8728fa809c05..cd7ee660823074d4a00e5dca9e87e240098442c9 100644 --- a/SAS/TMSS/src/tmss/tmssapp/workflows/helloworldflow.py +++ b/SAS/TMSS/src/tmss/workflowapp/flows/helloworldflow.py @@ -5,9 +5,7 @@ from viewflow.base import this, Flow from viewflow.compat import _ from viewflow.flow import views as flow_views - -from lofar.sas.tmss.tmss.tmssapp import models - +from .. import models @frontend.register diff --git a/SAS/TMSS/src/tmss/tmssapp/workflows/schedulingunitdemoflow.py b/SAS/TMSS/src/tmss/workflowapp/flows/schedulingunitdemoflow.py similarity index 99% rename from SAS/TMSS/src/tmss/tmssapp/workflows/schedulingunitdemoflow.py rename to SAS/TMSS/src/tmss/workflowapp/flows/schedulingunitdemoflow.py index a35c72db8e9430b929e9ada4f424bbf6a58527c9..0a2882d7a4550ef3ff8e60b190c4074f60356795 100644 --- a/SAS/TMSS/src/tmss/tmssapp/workflows/schedulingunitdemoflow.py +++ b/SAS/TMSS/src/tmss/workflowapp/flows/schedulingunitdemoflow.py @@ -8,7 +8,7 @@ from viewflow.activation import FuncActivation, ViewActivation from viewflow.flow.nodes import Signal from viewflow import mixins -from lofar.sas.tmss.tmss.tmssapp import models +from .. import models from viewflow import frontend, ThisObject from viewflow.activation import STATUS diff --git a/SAS/TMSS/src/tmss/workflowapp/migrations/0001_initial.py b/SAS/TMSS/src/tmss/workflowapp/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..2e95b97379265e5eb14cfd44e85357218eb63948 --- /dev/null +++ b/SAS/TMSS/src/tmss/workflowapp/migrations/0001_initial.py @@ -0,0 +1,50 @@ +# Generated by Django 3.0.9 on 2020-10-01 12:30 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('viewflow', '0008_jsonfield_and_artifact'), + ] + + operations = [ + migrations.CreateModel( + name='SchedulingUnitDemo', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50)), + ('state', models.IntegerField()), + ], + ), + migrations.CreateModel( + name='HelloWorldProcess', + fields=[ + ], + options={ + 'verbose_name': 'World Request', + 'verbose_name_plural': 'World Requests', + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('viewflow.process',), + ), + migrations.CreateModel( + name='SchedulingUnitDemoProcess', + fields=[ + ('process_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='viewflow.Process')), + ('text', models.CharField(max_length=150)), + ('approved', models.BooleanField(default=False)), + ('su', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='workflowapp.SchedulingUnitDemo')), + ], + options={ + 'abstract': False, + }, + bases=('viewflow.process',), + ), + ] diff --git a/SAS/TMSS/src/tmss/workflowapp/migrations/CMakeLists.txt b/SAS/TMSS/src/tmss/workflowapp/migrations/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..158ea7946445fcee8e52b00447df80a873e98ec2 --- /dev/null +++ b/SAS/TMSS/src/tmss/workflowapp/migrations/CMakeLists.txt @@ -0,0 +1,8 @@ + +include(PythonInstall) + + +FILE(GLOB _py_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.py) + +python_install(${_py_files} + DESTINATION lofar/sas/tmss/tmss/workflowapp/migrations) \ No newline at end of file diff --git a/SAS/TMSS/src/tmss/workflowapp/migrations/__init__.py b/SAS/TMSS/src/tmss/workflowapp/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/SAS/TMSS/src/tmss/workflowapp/models/CMakeLists.txt b/SAS/TMSS/src/tmss/workflowapp/models/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..1c94f0a15d5ade684111945ce5bb79dfe25f7a91 --- /dev/null +++ b/SAS/TMSS/src/tmss/workflowapp/models/CMakeLists.txt @@ -0,0 +1,11 @@ + +include(PythonInstall) + +set(_py_files + __init__.py + helloworldflow.py + schedulingunitdemoflow.py + ) + +python_install(${_py_files} + DESTINATION lofar/sas/tmss/tmss/workflowapp/models) diff --git a/SAS/TMSS/src/tmss/workflowapp/models/__init__.py b/SAS/TMSS/src/tmss/workflowapp/models/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..45516795a25730483ebfa40c1fbdb5f533df8ebe --- /dev/null +++ b/SAS/TMSS/src/tmss/workflowapp/models/__init__.py @@ -0,0 +1,2 @@ +from .helloworldflow import * +from .schedulingunitdemoflow import * \ No newline at end of file diff --git a/SAS/TMSS/src/tmss/tmssapp/models/helloworldflow.py b/SAS/TMSS/src/tmss/workflowapp/models/helloworldflow.py similarity index 100% rename from SAS/TMSS/src/tmss/tmssapp/models/helloworldflow.py rename to SAS/TMSS/src/tmss/workflowapp/models/helloworldflow.py diff --git a/SAS/TMSS/src/tmss/tmssapp/models/schedulingunitdemoflow.py b/SAS/TMSS/src/tmss/workflowapp/models/schedulingunitdemoflow.py similarity index 100% rename from SAS/TMSS/src/tmss/tmssapp/models/schedulingunitdemoflow.py rename to SAS/TMSS/src/tmss/workflowapp/models/schedulingunitdemoflow.py diff --git a/SAS/TMSS/src/tmss/workflowapp/tests.py b/SAS/TMSS/src/tmss/workflowapp/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6 --- /dev/null +++ b/SAS/TMSS/src/tmss/workflowapp/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/SAS/TMSS/src/tmss/workflowapp/viewsets/CMakeLists.txt b/SAS/TMSS/src/tmss/workflowapp/viewsets/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..7adc12fcf7a85912784409d17f37177986c94298 --- /dev/null +++ b/SAS/TMSS/src/tmss/workflowapp/viewsets/CMakeLists.txt @@ -0,0 +1,10 @@ + +include(PythonInstall) + +set(_py_files + __init__.py + schedulingunitdemoflow.py + ) + +python_install(${_py_files} + DESTINATION lofar/sas/tmss/tmss/workflowapp/viewsets) diff --git a/SAS/TMSS/src/tmss/workflowapp/viewsets/__init__.py b/SAS/TMSS/src/tmss/workflowapp/viewsets/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b77c70aeb959e9d4f63c395fd1079cfbbe3bc078 --- /dev/null +++ b/SAS/TMSS/src/tmss/workflowapp/viewsets/__init__.py @@ -0,0 +1 @@ +from .schedulingunitdemoflow import * \ No newline at end of file diff --git a/SAS/TMSS/src/tmss/tmssapp/viewsets/schedulingunitdemoflow.py b/SAS/TMSS/src/tmss/workflowapp/viewsets/schedulingunitdemoflow.py similarity index 92% rename from SAS/TMSS/src/tmss/tmssapp/viewsets/schedulingunitdemoflow.py rename to SAS/TMSS/src/tmss/workflowapp/viewsets/schedulingunitdemoflow.py index ea117c0f9c27a4324fe76c77fe1256e1b1eca446..da3dc24e15ff6f3bd93da9037101a718f4ebed66 100644 --- a/SAS/TMSS/src/tmss/tmssapp/viewsets/schedulingunitdemoflow.py +++ b/SAS/TMSS/src/tmss/workflowapp/viewsets/schedulingunitdemoflow.py @@ -3,7 +3,7 @@ from rest_framework import viewsets from rest_framework.response import Response from rest_framework.decorators import action from rest_framework.serializers import ModelSerializer -from lofar.sas.tmss.tmss.tmssapp import models +from lofar.sas.tmss.tmss.workflowapp import models # Create your views here. diff --git a/SAS/TMSS/test/t_tmssapp_specification_REST_API.py b/SAS/TMSS/test/t_tmssapp_specification_REST_API.py index 5bb9e175d4324f9ecdaa40176243dde8fa0da040..018c985f4b69f7b626564bda91f076dcc49591b9 100755 --- a/SAS/TMSS/test/t_tmssapp_specification_REST_API.py +++ b/SAS/TMSS/test/t_tmssapp_specification_REST_API.py @@ -1335,11 +1335,10 @@ class SchedulingUnitDraftTestCase(unittest.TestCase): GET_OK_and_assert_equal_expected_response(self, url, test_data) def test_GET_SchedulingUnitDraft_list_view_shows_entry(self): - test_data_1 = SchedulingUnitDraft_test_data("scheduler unit draft one") - models.SchedulingUnitDraft.objects.create(**test_data_1) + obj = models.SchedulingUnitDraft.objects.create(**test_data_1) nbr_results = models.SchedulingUnitDraft.objects.count() - GET_and_assert_in_expected_response_result_list(self, BASE_URL + '/scheduling_unit_draft/', test_data_1, nbr_results) + GET_and_assert_in_expected_response_result_list(self, BASE_URL + '/scheduling_unit_draft/', test_data_1, nbr_results, obj.id) def test_GET_SchedulingUnitDraft_view_returns_correct_entry(self): @@ -1385,8 +1384,8 @@ class SchedulingUnitDraftTestCase(unittest.TestCase): # setup test_data_1 = SchedulingUnitDraft_test_data("scheduler unit draft one") - tdt_test_data_1 = TaskDraft_test_data("task draft one") - tdt_test_data_2 = TaskDraft_test_data("task draft two") + tdt_test_data_1 = TaskDraft_test_data("task draft one of su1") + tdt_test_data_2 = TaskDraft_test_data("task draft two of su2 ") scheduling_unit_draft = models.SchedulingUnitDraft.objects.create(**test_data_1) task_draft_1 = models.TaskDraft.objects.create(**tdt_test_data_1) task_draft_1.scheduling_unit_draft = scheduling_unit_draft @@ -1522,9 +1521,9 @@ class TaskDraftTestCase(unittest.TestCase): def test_GET_TaskDraft_list_view_shows_entry(self): test_data_1 = TaskDraft_test_data("task draft") - models.TaskDraft.objects.create(**test_data_1) + obj = models.TaskDraft.objects.create(**test_data_1) nbr_results = models.TaskDraft.objects.count() - GET_and_assert_in_expected_response_result_list(self, BASE_URL + '/task_draft/', test_data_1, nbr_results) + GET_and_assert_in_expected_response_result_list(self, BASE_URL + '/task_draft/', test_data_1, nbr_results, obj.id) def test_GET_TaskDraft_view_returns_correct_entry(self): @@ -1540,7 +1539,7 @@ class TaskDraftTestCase(unittest.TestCase): def test_nested_TaskDraft_are_filtered_according_to_SchedulingUnitDraft(self): # setup - test_data_1 = TaskDraft_test_data("task draft one") + test_data_1 = TaskDraft_test_data("task draft three") sudt_test_data_1 = SchedulingUnitDraft_test_data("scheduling unit draft one") scheduling_unit_draft_1 = models.SchedulingUnitDraft.objects.create(**sudt_test_data_1) test_data_1 = dict(test_data_1) @@ -1552,7 +1551,7 @@ class TaskDraftTestCase(unittest.TestCase): def test_TaskDraft_contains_list_of_related_TaskBlueprint(self): # setup - test_data_1 = TaskDraft_test_data("task draft one") + test_data_1 = TaskDraft_test_data("task draft four") tbt_test_data_1 = TaskBlueprint_test_data() tbt_test_data_2 = TaskBlueprint_test_data() task_draft = models.TaskDraft.objects.create(**test_data_1) @@ -1569,7 +1568,7 @@ class TaskDraftTestCase(unittest.TestCase): def test_TaskDraft_contains_lists_of_related_TaskRelationDraft(self): # setup - test_data_1 = TaskDraft_test_data("task draft one") + test_data_1 = TaskDraft_test_data("task draft five") task_draft = models.TaskDraft.objects.create(**test_data_1) trdt_test_data_1 = TaskRelationDraft_test_data() diff --git a/SAS/TMSS/test/test_utils.py b/SAS/TMSS/test/test_utils.py index 52e18d0a8a10191285b7daaf6266fdd00768a4bc..2edeaae66b24887a9491527b23bbe6518f4456ae 100644 --- a/SAS/TMSS/test/test_utils.py +++ b/SAS/TMSS/test/test_utils.py @@ -214,7 +214,7 @@ class TMSSDjangoServerInstance(): # wait for server to be up and running.... # or exit via TimeoutError - self.check_running_server(timeout=30) + self.check_running_server(timeout=60) def stop(self): ''' diff --git a/SAS/TMSS/test/tmss_test_environment_unittest_setup.py b/SAS/TMSS/test/tmss_test_environment_unittest_setup.py index dc4f72644bf2b40058a6eb6571218f7cf6fd3d89..04b9882454838c474e06523d8017ebc9320aca07 100644 --- a/SAS/TMSS/test/tmss_test_environment_unittest_setup.py +++ b/SAS/TMSS/test/tmss_test_environment_unittest_setup.py @@ -134,7 +134,7 @@ def GET_and_assert_equal_expected_code(test_instance, url, expected_code): return r_dict -def GET_and_assert_in_expected_response_result_list(test_instance, url, expected_content, expected_nbr_results): +def GET_and_assert_in_expected_response_result_list(test_instance, url, expected_content, expected_nbr_results, expected_id=None): """ GET from url and assert the expected code is returned and the expected content is in the response content Use this check when multiple results (list) are returned @@ -159,7 +159,17 @@ def GET_and_assert_in_expected_response_result_list(test_instance, url, expected test_instance.assertIn(key, item.keys()) if url_check: - assertDataWithUrls(test_instance, r_dict['results'][expected_nbr_results-1], expected_content) + # Find the expected id in result list if parameter is given (of curse for just one it does not make sense) + # There was an 'old' assumption that the last one should taken, but that is not reliable + if expected_id is not None: + for idx in range(0, expected_nbr_results): + if r_dict['results'][idx]['id'] == expected_id: + expected_idx = idx + break + else: + # this is the 'old' assumption that last object added will also be the last one in the result dict + expected_idx = expected_nbr_results-1 + assertDataWithUrls(test_instance, r_dict['results'][expected_idx], expected_content) return r_dict