diff --git a/SAS/TMSS/frontend/tmss_webapp/package.json b/SAS/TMSS/frontend/tmss_webapp/package.json index d2e77ea1090cfdd2614c1bcfbd452cab25913554..969859842dc64dc1c550025c4ae7933bad115bd8 100644 --- a/SAS/TMSS/frontend/tmss_webapp/package.json +++ b/SAS/TMSS/frontend/tmss_webapp/package.json @@ -9,6 +9,7 @@ "@apidevtools/json-schema-ref-parser": "^9.0.6", "@fortawesome/fontawesome-free": "^5.13.1", "@json-editor/json-editor": "^2.3.0", + "@kevincobain2000/json-to-html-table": "^1.0.1", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/JSONEditor/JEditor.js b/SAS/TMSS/frontend/tmss_webapp/src/components/JSONEditor/JEditor.js index 55e28fb7b7e7ed6c24d1bec42d4ff6a0bcca47bd..e9c0b245f5eaeeaf5bd579a4c42c6b67e46eae44 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/JSONEditor/JEditor.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/JSONEditor/JEditor.js @@ -195,7 +195,9 @@ function Jeditor(props) { */ function setEditorOutput(){ const editorOutput = editorRef.current.getValue(); - const formattedOutput = updateOutput(_.cloneDeep(editorOutput)); + /* Sends editor output without formatting if requested */ + const formatOutput = props.formatOutput===undefined?true:props.formatOutput; + const formattedOutput = formatOutput?updateOutput(_.cloneDeep(editorOutput)):_.cloneDeep(editorOutput); const editorValidationErrors = editorRef.current.validate(); if (props.callback) { // editorRef.current for accessing fields in parent to add classname for enabling and disabling diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_layout.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_layout.scss index 0f0e755f9b2b3bd8503ab89d0d9a195b54186fec..302208b3dad39e6c4ebf092e614e502fbd131e37 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_layout.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_layout.scss @@ -14,5 +14,6 @@ @import "./_viewtable"; @import "./_pageheader"; @import "./timeline"; -@import "./_aggrid" +@import "./_aggrid"; +@import "./suSummary"; // @import "./splitpane"; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_suSummary.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_suSummary.scss new file mode 100644 index 0000000000000000000000000000000000000000..c7311a659629c771f40e2954305dbf0151c4cc3c --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_suSummary.scss @@ -0,0 +1,18 @@ +.constraints-summary>div { + overflow: scroll; + height: 300px; + margin-bottom: 10px; +} + +.constraints-summary table { + border: 1px solid lightgray; + text-transform: capitalize; +} + +.task-summary>label { + margin-bottom: 0px; +} + +.task-summary #block_container { + margin-top: 0px; +} \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/Scheduling.Constraints.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/Scheduling.Constraints.js index a19914a42ebf219f36ff9f1c38369ff082b01eba..5de435d85b1f8e16a09f7e43c85ffaffd3da6227 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/Scheduling.Constraints.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/Scheduling.Constraints.js @@ -187,6 +187,7 @@ export default (props) => { callback: onEditForm, initValue: initialValue, disabled: props.disable, + formatOutput: props.formatOutput, parentFunction: parentFunction })} </> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js index 2c304f86ec3cd0815e33e5f057e49c7335fcf812..1916250856e66bf31b2104fffeee79dfe4c4fe94 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/summary.js @@ -1,7 +1,10 @@ import React, {Component} from 'react'; import { Link } from 'react-router-dom/cjs/react-router-dom.min'; import moment from 'moment'; +import _ from 'lodash'; import ViewTable from '../../components/ViewTable'; +import { JSONToHTMLTable } from '@kevincobain2000/json-to-html-table' +import SchedulingConstraints from './Scheduling.Constraints'; /** * Component to view summary of the scheduling unit with limited task details @@ -13,20 +16,102 @@ export class SchedulingUnitSummary extends Component { this.state = { schedulingUnit: props.schedulingUnit || null } + this.constraintsOrder = ['scheduler','time','daily','sky']; this.closeSUDets = this.closeSUDets.bind(this); + this.setConstraintsEditorOutput = this.setConstraintsEditorOutput.bind(this); } componentDidMount() {} + /** + * Function to close the summary panel and call parent callback function to close. + */ closeSUDets() { if(this.props.closeCallback) { this.props.closeCallback(); } } + /** + * Order the properties in the constraint object in the predefined order + * @param {Object} constraintsDoc + */ + getOrderedConstraints(constraintsDoc) { + let orderedConstraints = {}; + for(const constraintKey of this.constraintsOrder) { + /* Format the object to remove empty values*/ + const constraint = this.getFormattedConstraint(constraintsDoc[constraintKey]); + if (constraint) { + orderedConstraints[constraintKey] = constraint; + } + } + return orderedConstraints; + } + + /** + * Format the constraint object i.e removes the empty values to show only available values. + * @param {Object} constraint + */ + getFormattedConstraint(constraint) { + if (constraint) { + const objectType = typeof constraint; + switch(objectType) { + case "string": { + try { + const dateConstraint = moment.utc(constraint); + if (dateConstraint.isValid()) { + constraint = dateConstraint.format("YYYY-MM-DD HH:mm:ss"); + } + } catch (error) {} + break; + } + case "boolean": { + constraint = constraint?constraint:null; + break; + } + case "object": { + if (Array.isArray(constraint)) { + let newArray = [] + for (let arrayObj of constraint) { + arrayObj = this.getFormattedConstraint(arrayObj); + if (arrayObj) { + newArray.push(arrayObj); + } + } + constraint = newArray.length > 0?newArray:null; + } else { + let newObject = {}; + for (const objectKey of _.keys(constraint)) { + let object = this.getFormattedConstraint(constraint[objectKey]); + if (object) { + newObject[objectKey] = object; + } + } + constraint = (!_.isEmpty(newObject))? newObject:null; + } + break; + } + default: {} + } + } + return constraint; + } + + /** + * Gets the output from the SchedulingConstraints editor without output formatting so that the values entered in the + * editor can be shown in the summary without any conversion. + * @param {Object} jsonOutput + */ + setConstraintsEditorOutput(jsonOutput) { + this.setState({constraintsDoc: jsonOutput}); + } + render() { const schedulingUnit = this.props.schedulingUnit; const suTaskList = this.props.suTaskList; + const constraintsTemplate = this.props.constraintsTemplate; + // After receiving output from the SchedulingConstraint editor order and format it to display + let constraintsDoc = this.state.constraintsDoc?this.getOrderedConstraints(this.state.constraintsDoc):null; return ( <React.Fragment> { schedulingUnit && @@ -44,7 +129,25 @@ export class SchedulingUnitSummary extends Component { <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"> + {constraintsTemplate && schedulingUnit.suDraft.scheduling_constraints_doc && + <> + {/* SchedulingConstraints editor to pass the scheduling_constraints_doc and get the editor output to User entry format and conversions */} + <div style={{display: "none"}}> + <SchedulingConstraints constraintTemplate={constraintsTemplate} disable + formatOutput={false} initValue={schedulingUnit.suDraft.scheduling_constraints_doc} + callback={this.setConstraintsEditorOutput} /> + </div> + {/* Scheduling Constraint Display in table format */} + {constraintsDoc && + <div className="col-12 constraints-summary"> + <label>Constraints:</label> + <JSONToHTMLTable data={constraintsDoc} tableClassName="table table-sm"/> + </div> + } + </> + } + <div className="col-12 task-summary"> + <label>Tasks:</label> <ViewTable data={suTaskList} defaultcolumns={[{id: "ID", start_time:"Start Time", stop_time:"End Time", status: "Status", @@ -60,7 +163,7 @@ export class SchedulingUnitSummary extends Component { showColumnFilter={false} allowColumnSelection={false} /> - </div> + </div> </div> } </React.Fragment> 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 18ef4e0b00f6f17319b44f5b21149ce09037f933..ec2a9dcbd5b45f88cd268ea6e1c7ab96d97a66a0 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js @@ -158,6 +158,11 @@ export class TimelineView extends Component { } this.setState({suTaskList: _.sortBy(taskList, "id"), isSummaryLoading: false}) }); + // Get the scheduling constraint template of the selected SU block + ScheduleService.getSchedulingConstraintTemplate(suBlueprint.suDraft.scheduling_constraints_template_id) + .then(suConstraintTemplate => { + this.setState({suConstraintTemplate: suConstraintTemplate}); + }); } } } @@ -362,6 +367,7 @@ export class TimelineView extends Component { style={{borderLeft: "1px solid #efefef", marginTop: "0px", backgroundColor: "#f2f2f2"}}> {this.state.isSummaryLoading?<AppLoader /> : <SchedulingUnitSummary schedulingUnit={suBlueprint} suTaskList={this.state.suTaskList} + constraintsTemplate={this.state.suConstraintTemplate} closeCallback={this.closeSUDets}></SchedulingUnitSummary> } </div> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js index 4e11e19eaca14e596b889f35c882306b0352210d..407b2082cb913f462afaf660e0589e48f5adcc91 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js @@ -176,6 +176,11 @@ export class WeekTimelineView extends Component { } this.setState({suTaskList: _.sortBy(taskList, "id"), isSummaryLoading: false}) }); + // Get the scheduling constraint template of the selected SU block + ScheduleService.getSchedulingConstraintTemplate(suBlueprint.suDraft.scheduling_constraints_template_id) + .then(suConstraintTemplate => { + this.setState({suConstraintTemplate: suConstraintTemplate}); + }); } } } @@ -359,6 +364,7 @@ export class WeekTimelineView extends Component { style={{borderLeft: "1px solid #efefef", marginTop: "0px", backgroundColor: "#f2f2f2"}}> {this.state.isSummaryLoading?<AppLoader /> : <SchedulingUnitSummary schedulingUnit={suBlueprint} suTaskList={this.state.suTaskList} + constraintsTemplate={this.state.suConstraintTemplate} closeCallback={this.closeSUDets} location={this.props.location}></SchedulingUnitSummary> } 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 0b77e10fc80469dba49272d93eec6e6bc720459e..53b26626b6667fb8d0b526c0f6d28a6f63ab5768 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js @@ -219,6 +219,15 @@ const ScheduleService = { return []; }; }, + getSchedulingConstraintTemplate: async function(id){ + try { + const response = await axios.get(`/api/scheduling_constraints_template/${id}`); + return response.data; + } catch(error) { + console.error(error); + return null; + }; + }, saveSUDraftFromObservStrategy: async function(observStrategy, schedulingUnit, constraint) { try { // Create the scheduling unit draft with observation strategy and scheduling set