diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js index 706573d5aee50e6a35e41457fe2eb2e6ca8e281c..4cce86576d2feee1c0766a9b237886225ae14fb5 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js @@ -55,11 +55,13 @@ const RESERVATION_COLORS = { "false-true": { bgColor: "#9b9999", color: "white" }, "false-false": { bgColor: "black", color: "white" } }; +const OFFSET_DATA_DAYS = 7; /** * Scheduling Unit timeline view component to view SU List and timeline */ export class TimelineView extends Component { - + timelineCommonUtils = new TimelineCommonUtils(); + constructor(props) { super(props); this.timelineUIAttributes = UtilService.localStore({ type: 'get', key: "TIMELINE_UI_ATTR" }) || {}; @@ -93,6 +95,7 @@ export class TimelineView extends Component { userrole: AuthStore.getState(), suStatusList: [], taskStatusList: [], + datasetStartTime: null, datasetEndTime:null, showDialog: false, } this.STATUS_BEFORE_SCHEDULED = ['defining', 'defined', 'schedulable']; // Statuses before scheduled to get station_group @@ -103,7 +106,8 @@ export class TimelineView extends Component { // this.menuOptions = [{ label: 'Add Reservation', icon: "fa fa-", disabled: true , command: () => { this.selectOptionMenu('Add Reservation') } }, // { label: 'Reservation List', icon: "fa fa-", command: () => { this.selectOptionMenu('Reservation List') } }, // ]; - + this.getSchedulingUnits = this.getSchedulingUnits.bind(this); + this.loadSUsAheadAndTrail = this.loadSUsAheadAndTrail.bind(this); this.showOptionMenu = this.showOptionMenu.bind(this); this.selectOptionMenu = this.selectOptionMenu.bind(this); this.onItemClick = this.onItemClick.bind(this); @@ -159,68 +163,44 @@ export class TimelineView extends Component { .then(suConstraintTemplates => { this.suConstraintTemplates = suConstraintTemplates; }); - this.timelineCommonUtils = new TimelineCommonUtils(); + const currentUTC = moment.utc(await UtilService.getUTC()); // Default start time, this should be updated if default view is changed. Take from localstorage if available let defaultStartTime = this.timelineUIAttributes.dateRange?moment.utc(this.timelineUIAttributes.dateRange.startTime):null; // Default end time, this should be updated if default view is changed. Take from localstorage if available let defaultEndTime = this.timelineUIAttributes.dateRange?moment.utc(this.timelineUIAttributes.dateRange.endTime):null; + + // Set default time if previous selected date range and zoom level available. + if (this.timelineUIAttributes.zoomLevel) { + const defaultZoomLevel = _.find(this.timelineCommonUtils.ZOOM_LEVELS, {name: this.timelineUIAttributes.zoomLevel}); + const rangeDuration = defaultStartTime?defaultEndTime.diff(defaultStartTime)/1000-defaultZoomLevel.value:0; + defaultStartTime = defaultStartTime?defaultStartTime.add(1 * rangeDuration/2, 'seconds'):null; + defaultStartTime = defaultStartTime?defaultStartTime:currentUTC.clone().add(-1 * defaultZoomLevel.value/2, 'seconds'); + defaultEndTime = defaultEndTime?defaultEndTime.add(-1 * rangeDuration/2, 'seconds'):null; + defaultEndTime = defaultEndTime?defaultEndTime:currentUTC.clone().add(1 * defaultZoomLevel.value/2, 'seconds'); + } else { + defaultStartTime = defaultStartTime?defaultStartTime:currentUTC.clone().add(-24, 'hours'); // Default start time, this should be updated if default view is changed. + defaultEndTime = defaultEndTime?defaultEndTime:currentUTC.clone().add(24, 'hours'); // Default end time, this should be updated if default view is changed. + } + // Fetch all details from server and prepare data to pass to timeline and table components - const promises = [ScheduleService.getExpandedSUList(), - UtilService.getUTC(), - ScheduleService.getStations('All')]; + const promises = [ScheduleService.getStations('All')]; Promise.all(promises).then(async (responses) => { this.mainStationGroupOptions = Object.keys(this.timelineCommonUtils.getMainStationGroups()).map(value => ({ value })); - const suBlueprints = _.sortBy(responses[0], 'name'); - const group = [], items = []; - const currentUTC = moment.utc(responses[1]); - // Set default time if previous selected date range and zoom level available. - if (this.timelineUIAttributes.zoomLevel) { - const defaultZoomLevel = _.find(this.timelineCommonUtils.ZOOM_LEVELS, {name: this.timelineUIAttributes.zoomLevel}); - const rangeDuration = defaultStartTime?defaultEndTime.diff(defaultStartTime)/1000-defaultZoomLevel.value:0; - defaultStartTime = defaultStartTime?defaultStartTime.add(1 * rangeDuration/2, 'seconds'):null; - defaultStartTime = defaultStartTime?defaultStartTime:currentUTC.clone().add(-1 * defaultZoomLevel.value/2, 'seconds'); - defaultEndTime = defaultEndTime?defaultEndTime.add(-1 * rangeDuration/2, 'seconds'):null; - defaultEndTime = defaultEndTime?defaultEndTime:currentUTC.clone().add(1 * defaultZoomLevel.value/2, 'seconds'); - } else { - defaultStartTime = defaultStartTime?defaultStartTime:currentUTC.clone().add(-24, 'hours'); // Default start time, this should be updated if default view is changed. - defaultEndTime = defaultEndTime?defaultEndTime:currentUTC.clone().add(24, 'hours'); // Default end time, this should be updated if default view is changed. - } - let suList = []; - for (let suBlueprint of suBlueprints){ - suBlueprint['actionpath'] = `/schedulingunit/view/blueprint/${suBlueprint.id}`; - suBlueprint.suDraft = suBlueprint.draft; - suBlueprint.project = suBlueprint.draft.scheduling_set.project.name; - suBlueprint.suSet = suBlueprint.draft.scheduling_set; - suBlueprint.durationInSec = suBlueprint.duration; - suBlueprint.duration = UnitConverter.getSecsToHHmmss(suBlueprint.duration); - suBlueprint.workflowStatus = this.timelineCommonUtils.getWorkflowStatus(suBlueprint); - suBlueprint.task_content = ""; - suBlueprint.observ_template_name = suBlueprint.draft.observation_strategy_template.name; - suBlueprint.tasks = suBlueprint.task_blueprints; - suBlueprint.stationGroupCount = this.timelineCommonUtils.getSUStationGroupCount(suBlueprint).counts; - for (let task of suBlueprint.tasks) { - const controlTask = _.find(task.subtasks, subtask => { return subtask.primary === true}); - task.controlId = controlTask?controlTask.id:""; - if (task.task_type === "observation") { - task.antenna_set = task.specifications_doc.antenna_set; - task.band = task.specifications_doc.filter; - } - } - } - for (const station of responses[2]['stations']) { + for (const station of responses[0]['stations']) { this.allStationsGroup.push({ id: station, title: station }); } // Set the selectedStationGroup if the previous selected station groups are stored const selectedStationGroup = this.timelineUIAttributes.stationGroups || _.keys(this.timelineCommonUtils.getMainStationGroups()); + const schedulingUnits = await this.getSchedulingUnits(defaultStartTime, defaultEndTime); this.setState({ - suBlueprints: suBlueprints, suBlueprintList: suList, + suBlueprints: schedulingUnits.original, suBlueprintList: schedulingUnits.formatted, + datasetStartTime: schedulingUnits.datasetStartTime, datasetEndTime: schedulingUnits.datasetEndTime, loader: false, taskTypes: taskTypes, - // group: group, items: items, currentUTC: currentUTC, isLoading: false, currentStartTime: defaultStartTime, currentEndTime: defaultEndTime, selectedStationGroup: selectedStationGroup }); - this.dateRangeCallback(defaultStartTime, defaultEndTime); + this.dateRangeCallback(defaultStartTime, defaultEndTime, true); }); } @@ -231,6 +211,123 @@ export class TimelineView extends Component { } + /** + * Function to get only the data for the visible timeline first and the offset data in the background. + * @param {moment} startTime - start time of the visible timeline + * @param {moment} endTime - end time of the visible timeline + * @returns Object with original SUBs, formatted SUBs, dataset start and end time properties. + */ + async getSchedulingUnits(startTime, endTime) { + let {datasetStartTime, datasetEndTime, suBlueprints, suBlueprintList} = this.state; + let schedulingUnits = {original: suBlueprints}; + // Initial loading of data. Fetches only the data for the visible timeline. + if (!datasetStartTime) { + schedulingUnits = await this.loadSchedulingUnits(startTime, endTime); + datasetStartTime = startTime.clone().add(-1 * OFFSET_DATA_DAYS, 'days'); + datasetEndTime = endTime.clone().add(OFFSET_DATA_DAYS, 'days'); + schedulingUnits.datasetStartTime = datasetStartTime; + schedulingUnits.datasetEndTime = datasetEndTime; + // Loads the offset data in the background + this.loadSUsAheadAndTrail(datasetStartTime, datasetEndTime, startTime, endTime); + } else { // When timeline range or zoom level changes + // If the new timeline start time is before the existing dataset start time, + // load only the SUBS between the new start time and old dataset start time + if (startTime.isSameOrBefore(datasetStartTime)) { + const frontFillSUs = await this.loadSchedulingUnits(startTime, datasetStartTime); + suBlueprints = suBlueprints.concat(frontFillSUs.original); + suBlueprintList = suBlueprintList.concat(frontFillSUs.formatted); + datasetStartTime = startTime.clone().add(-1 * OFFSET_DATA_DAYS, 'days'); + schedulingUnits.datasetStartTime = datasetStartTime; + schedulingUnits.original = _.uniqBy(suBlueprints, 'id'); + schedulingUnits.formatted = _.uniqBy(suBlueprintList, 'id'); + // Loads the offset data before the new start time in the background + this.loadSUsAheadAndTrail(datasetStartTime, null, startTime, null); + } + // If the new timeline end time is after the existing dataset end time, + // load only the SUBS between the new end time and old dataset end time + if (endTime.isSameOrAfter(datasetEndTime)) { + const trailFillSUs = await this.loadSchedulingUnits(endTime, datasetEndTime); + suBlueprints = suBlueprints.concat(trailFillSUs.original); + suBlueprintList = suBlueprintList.concat(trailFillSUs.formatted); + datasetEndTime = endTime.clone().add(OFFSET_DATA_DAYS, 'days'); + schedulingUnits.datasetEndTime = datasetEndTime; + schedulingUnits.original = _.uniqBy(suBlueprints, 'id'); + schedulingUnits.formatted = _.uniqBy(suBlueprintList, 'id'); + // Loads the offset data after the new end time in the background + this.loadSUsAheadAndTrail(null, datasetEndTime, null, endTime); + } + } + // Returning the data for the timeline only or the existing data. + return schedulingUnits; + } + + /** + * Function to load the data before and after the timeline with an offset period in the background. + * This improves the performance in rendering the timeline. + * @param {moment} datasetStartTime - start time of the dataset to be loaded + * @param {moment} datasetEndTime - end time of the dataset to be loaded + * @param {moment} startTime - start time visible in the timeline + * @param {moment} endTime - end time visible in the timeline + */ + async loadSUsAheadAndTrail(datasetStartTime, datasetEndTime, startTime, endTime) { + let suBlueprints = null; + let suBlueprintList = null; + // Load the SUBs from the dataset start time to timeline start time and concat to existing SUBs + if (datasetStartTime) { + const susAhead = await this.loadSchedulingUnits(datasetStartTime, startTime); + suBlueprints = this.state.suBlueprints.concat(susAhead.original); + suBlueprintList = this.state.suBlueprintList.concat(susAhead.formatted); + } + // Load the SUBs from the timeline end time to dataset end time and concat to existing SUBs + if (datasetEndTime) { + const susTrail = await this.loadSchedulingUnits(endTime, datasetEndTime); + suBlueprints = suBlueprints || this.state.suBlueprints; + suBlueprintList = suBlueprintList || this.state.suBlueprintList; + suBlueprints = suBlueprints.concat(susTrail.original); + suBlueprintList = suBlueprintList.concat(susTrail.formatted); + } + this.setState({suBlueprints: _.uniqBy(suBlueprints, 'id'), + suBlueprintList: _.uniqBy(suBlueprintList, 'id'), + datasetStartTime: datasetStartTime, + datasetEndTime: datasetEndTime + }); + } + + /** + * Fetches the SUBs with either start time or end time in the time range and formats as required for the table. + * @param {moment} startTime - start time from which SUBs should be loaded + * @param {moment} endTime - end time till which SUBs should be loaded + * @returns - Object with both original SUBs from backend and formatted SUBs for table list. + */ + async loadSchedulingUnits(startTime, endTime) { + let suBlueprints = await ScheduleService.getExpandedSUList(startTime.format(UIConstants.CALENDAR_DATETIME_FORMAT), + endTime.format(UIConstants.CALENDAR_DATETIME_FORMAT)); + suBlueprints = _.filter(suBlueprints, suBlueprint => suBlueprint.status.toLowerCase !== 'obsolete'); + let suList = []; + for (let suBlueprint of suBlueprints){ + suBlueprint['actionpath'] = `/schedulingunit/view/blueprint/${suBlueprint.id}`; + suBlueprint.suDraft = suBlueprint.draft; + suBlueprint.project = suBlueprint.draft.scheduling_set.project.name; + suBlueprint.suSet = suBlueprint.draft.scheduling_set; + suBlueprint.durationInSec = suBlueprint.duration; + suBlueprint.duration = UnitConverter.getSecsToHHmmss(suBlueprint.duration); + suBlueprint.workflowStatus = this.timelineCommonUtils.getWorkflowStatus(suBlueprint); + suBlueprint.task_content = ""; + suBlueprint.observ_template_name = suBlueprint.draft.observation_strategy_template.name; + suBlueprint.tasks = suBlueprint.task_blueprints; + suBlueprint.stationGroupCount = this.timelineCommonUtils.getSUStationGroupCount(suBlueprint).counts; + for (let task of suBlueprint.tasks) { + const controlTask = _.find(task.subtasks, subtask => { return subtask.primary}); + task.controlId = controlTask?controlTask.id:""; + if (task.task_type === "observation") { + task.antenna_set = task.specifications_doc.antenna_set; + task.band = task.specifications_doc.filter; + } + } + } + return {original: suBlueprints, formatted: suList}; + } + setSelectedStationGroup(value) { // By default all stations groups are selected. // In that case no need to store the selected group otherwise store the selected groups in local storage @@ -287,8 +384,9 @@ export class TimelineView extends Component { let items = [], itemGroup = []; const subtaskTemplates = this.subtaskTemplates; for (let task of suBlueprint.tasks) { + console.log(task); if (((!this.state.stationView && this.state.selectedTaskTypes.indexOf(task.task_type)>=0) - || (this.state.stationView && task.primary === true)) + || (this.state.stationView && task.task_type === 'observation')) && ( filteredTasks.length===0 || filteredTasks.indexOf(task.id)>=0 ) && task.start_time && task.stop_time) { const antennaSet = task.specifications_doc.antenna_set; @@ -297,7 +395,7 @@ export class TimelineView extends Component { if ((start_time.isBetween(startTime, endTime) || end_time.isBetween(startTime, endTime)) || (start_time.isSameOrBefore(startTime) && end_time.isSameOrAfter(endTime))) { - const controlTask = _.find(task.subtasks, subtask => { return subtask.primary === true}); + const controlTask = _.find(task.subtasks, subtask => { return subtask.primary }); const controlId = controlTask?controlTask.id:""; let item = { id: `${suBlueprint.id}_${task.id}`, @@ -478,10 +576,14 @@ export class TimelineView extends Component { * @param {moment} startTime * @param {moment} endTime */ - async dateRangeCallback(startTime, endTime) { + async dateRangeCallback(startTime, endTime, loadOldData) { + let suBlueprints = this.state.suBlueprints; + if (!loadOldData) { + suBlueprints = (await this.getSchedulingUnits(startTime, endTime)).original; + } let suBlueprintList = [], group = [], items = []; if (startTime && endTime) { - for (const suBlueprint of this.state.suBlueprints) { + for (const suBlueprint of suBlueprints) { if (moment.utc(suBlueprint.start_time).isBetween(startTime, endTime) || moment.utc(suBlueprint.stop_time).isBetween(startTime, endTime) || (moment.utc(suBlueprint.start_time).isSameOrBefore(startTime) && @@ -531,6 +633,7 @@ export class TimelineView extends Component { } this.setState({ + suBlueprints: suBlueprints, suBlueprintList: _.filter(suBlueprintList, (suBlueprint) => { return suBlueprint.start_time != null }), currentStartTime: startTime, currentEndTime: endTime }); @@ -1026,7 +1129,7 @@ export class TimelineView extends Component { suBlueprints.push(suBlueprint); // Set updated suBlueprints in the state and call the dateRangeCallback to create the timeline group and items this.setState({ suBlueprints: suBlueprints }); - this.dateRangeCallback(this.state.currentStartTime, this.state.currentEndTime); + this.dateRangeCallback(this.state.currentStartTime, this.state.currentEndTime, true); }); } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js index 7132ad37fa3a7682b3c41825d37fbaee1b7ad8cd..14cecc28ade9d431a96f94a064f5d799490bad29 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/week.view.js @@ -45,11 +45,13 @@ const RESERVATION_COLORS = { "false-true": { bgColor: "#9b9999", color: "white" }, "false-false": { bgColor: "black", color: "white" } }; +const OFFSET_DATA_DAYS = 7; + /** * Scheduling Unit timeline view component to view SU List and timeline */ export class WeekTimelineView extends Component { - + timelineCommonUtils = new TimelineCommonUtils(); constructor(props) { super(props); this.timelineUIAttributes = UtilService.localStore({ type: 'get', key: "TIMELINE_UI_ATTR" }) || {}; @@ -68,9 +70,10 @@ export class WeekTimelineView extends Component { suTaskList: [], isSummaryLoading: false, stationGroup: [], - reservationEnabled: true, + reservationEnabled: this.timelineUIAttributes.reservationEnabled===undefined?true:this.timelineUIAttributes.reservationEnabled, suStatusList: [], taskStatusList: [], + datasetStartTime: null, datasetEndTime:null, showDialog: false, userrole: AuthStore.getState(), } @@ -78,6 +81,8 @@ export class WeekTimelineView extends Component { this.reservations = []; this.reservationReasons = []; this.optionsMenu = React.createRef(); + this.getSchedulingUnits = this.getSchedulingUnits.bind(this); + this.loadSUsAheadAndTrail = this.loadSUsAheadAndTrail.bind(this); this.showOptionMenu = this.showOptionMenu.bind(this); this.selectOptionMenu = this.selectOptionMenu.bind(this); this.onItemClick = this.onItemClick.bind(this); @@ -120,83 +125,153 @@ export class WeekTimelineView extends Component { } } }); - this.timelineCommonUtils = new TimelineCommonUtils(); + const currentUTC = moment.utc(await UtilService.getUTC()); + const defaultStartTime = moment.utc().day(-2).hour(0).minutes(0).seconds(0); + const defaultEndTime = moment.utc().day(8).hour(23).minutes(59).seconds(59); + const datasetStartTime = defaultStartTime.clone().add(-1 * OFFSET_DATA_DAYS, 'days'); + const datasetEndTime = defaultEndTime.clone().add(OFFSET_DATA_DAYS, 'days'); + // Fetch all details from server and prepare data to pass to timeline and table components - const promises = [ScheduleService.getExpandedSUList(), - UtilService.getUTC()]; - Promise.all(promises).then(async (responses) => { - const suBlueprints = _.sortBy(responses[0], 'name'); - let group = [], items = []; - const currentUTC = moment.utc(responses[1]); - const defaultStartTime = moment.utc().day(-2).hour(0).minutes(0).seconds(0); - const defaultEndTime = moment.utc().day(8).hour(23).minutes(59).seconds(59); - for (const count of _.range(11)) { - const groupDate = defaultStartTime.clone().add(count, 'days'); - group.push({ 'id': groupDate.format("MMM DD ddd"), title: groupDate.format("MMM DD - ddd"), value: groupDate }); + let group = [], items = []; + for (const count of _.range(11)) { + const groupDate = defaultStartTime.clone().add(count, 'days'); + group.push({ 'id': groupDate.format("MMM DD ddd"), title: groupDate.format("MMM DD - ddd"), value: groupDate }); + } + // Get all scheduling constraint templates + ScheduleService.getSchedulingConstraintTemplates() + .then(suConstraintTemplates => { + this.suConstraintTemplates = suConstraintTemplates; + }); + this.setState({ + suBlueprints: [], suBlueprintList: [], + group: _.sortBy(group, ['value']), + items: items, currentUTC: currentUTC, isLoading: false, + startTime: defaultStartTime, endTime: defaultEndTime + }); + let updatedItemGroupData = await this.dateRangeCallback(defaultStartTime, defaultEndTime, true); + this.timeline.updateTimeline(updatedItemGroupData); + } + + /** + * Function to get only the data for the visible timeline first and the offset data in the background. + * @param {moment} startTime - start time of the visible timeline + * @param {moment} endTime - end time of the visible timeline + * @returns Object with original SUBs, formatted SUBs, dataset start and end time properties. + */ + async getSchedulingUnits(startTime, endTime) { + let {datasetStartTime, datasetEndTime, suBlueprints, suBlueprintList} = this.state; + let schedulingUnits = {original: suBlueprints}; + // Initial loading of data. Fetches only the data for the visible timeline. + if (!datasetStartTime) { + schedulingUnits = await this.loadSchedulingUnits(startTime, endTime); + datasetStartTime = startTime.clone().add(-1 * OFFSET_DATA_DAYS, 'days'); + datasetEndTime = endTime.clone().add(OFFSET_DATA_DAYS, 'days'); + schedulingUnits.datasetStartTime = datasetStartTime; + schedulingUnits.datasetEndTime = datasetEndTime; + // Loads the offset data in the background + this.loadSUsAheadAndTrail(datasetStartTime, datasetEndTime, startTime, endTime); + } else { // When timeline range or zoom level changes + // If the new timeline start time is before the existing dataset start time, + // load only the SUBS between the new start time and old dataset start time + if (startTime.isSameOrBefore(datasetStartTime)) { + const frontFillSUs = await this.loadSchedulingUnits(startTime, datasetStartTime); + suBlueprints = suBlueprints.concat(frontFillSUs.original); + suBlueprintList = suBlueprintList.concat(frontFillSUs.formatted); + datasetStartTime = startTime.clone().add(-1 * OFFSET_DATA_DAYS, 'days'); + schedulingUnits.datasetStartTime = datasetStartTime; + schedulingUnits.original = _.uniqBy(suBlueprints, 'id'); + schedulingUnits.formatted = _.uniqBy(suBlueprintList, 'id'); + // Loads the offset data before the new start time in the background + this.loadSUsAheadAndTrail(datasetStartTime, null, startTime, null); } - let suList = []; - for (let suBlueprint of suBlueprints) { - suBlueprint['actionpath'] = `/schedulingunit/view/blueprint/${suBlueprint.id}`; - suBlueprint.suDraft = suBlueprint.draft; - suBlueprint.project = suBlueprint.draft.scheduling_set.project.name; - suBlueprint.suSet = suBlueprint.draft.scheduling_set; - suBlueprint.durationInSec = suBlueprint.duration; - suBlueprint.duration = UnitConverter.getSecsToHHmmss(suBlueprint.duration); - suBlueprint.workflowStatus = this.timelineCommonUtils.getWorkflowStatus(suBlueprint); - suBlueprint.task_content = ""; - suBlueprint.observ_template_name = suBlueprint.draft.observation_strategy_template.name; - suBlueprint.tasks = suBlueprint.task_blueprints; - suBlueprint.stationGroupCount = this.timelineCommonUtils.getSUStationGroupCount(suBlueprint).counts; - // Select only blueprints with start_time and stop_time in the default time limit - if (suBlueprint.start_time && - ((moment.utc(suBlueprint.start_time).isBetween(defaultStartTime, defaultEndTime) || - moment.utc(suBlueprint.stop_time).isBetween(defaultStartTime, defaultEndTime)) - || (moment.utc(suBlueprint.start_time).isSameOrBefore(defaultStartTime, defaultEndTime) && - moment.utc(suBlueprint.stop_time).isSameOrAfter(defaultStartTime, defaultEndTime)))) { + // If the new timeline end time is after the existing dataset end time, + // load only the SUBS between the new end time and old dataset end time + if (endTime.isSameOrAfter(datasetEndTime)) { + const trailFillSUs = await this.loadSchedulingUnits(endTime, datasetEndTime); + suBlueprints = suBlueprints.concat(trailFillSUs.original); + suBlueprintList = suBlueprintList.concat(trailFillSUs.formatted); + datasetEndTime = endTime.clone().add(OFFSET_DATA_DAYS, 'days'); + schedulingUnits.datasetEndTime = datasetEndTime; + schedulingUnits.original = _.uniqBy(suBlueprints, 'id'); + schedulingUnits.formatted = _.uniqBy(suBlueprintList, 'id'); + // Loads the offset data after the new end time in the background + this.loadSUsAheadAndTrail(null, datasetEndTime, null, endTime); + } + } + // Returning the data for the timeline only or the existing data. + return schedulingUnits; + } - const startTime = moment.utc(suBlueprint.start_time); - const endTime = moment.utc(suBlueprint.stop_time); - if (startTime.format("MM-DD-YYYY") !== endTime.format("MM-DD-YYYY")) { - let suBlueprintStart = _.cloneDeep(suBlueprint); - let suBlueprintEnd = _.cloneDeep(suBlueprint); - suBlueprintStart.stop_time = startTime.hour(23).minutes(59).seconds(59).format('YYYY-MM-DDTHH:mm:ss.00000'); - suBlueprintEnd.start_time = endTime.hour(0).minutes(0).seconds(0).format('YYYY-MM-DDTHH:mm:ss.00000'); - items.push(await this.getTimelineItem(suBlueprintStart, currentUTC)); - items.push(await this.getTimelineItem(suBlueprintEnd, currentUTC)); + /** + * Function to load the data before and after the timeline with an offset period in the background. + * This improves the performance in rendering the timeline. + * @param {moment} datasetStartTime - start time of the dataset to be loaded + * @param {moment} datasetEndTime - end time of the dataset to be loaded + * @param {moment} startTime - start time visible in the timeline + * @param {moment} endTime - end time visible in the timeline + */ + async loadSUsAheadAndTrail(datasetStartTime, datasetEndTime, startTime, endTime) { + let suBlueprints = null; + let suBlueprintList = null; + // Load the SUBs from the dataset start time to timeline start time and concat to existing SUBs + if (datasetStartTime) { + const susAhead = await this.loadSchedulingUnits(datasetStartTime, startTime); + suBlueprints = this.state.suBlueprints.concat(susAhead.original); + suBlueprintList = this.state.suBlueprintList.concat(susAhead.formatted); + } + // Load the SUBs from the timeline end time to dataset end time and concat to existing SUBs + if (datasetEndTime) { + const susTrail = await this.loadSchedulingUnits(endTime, datasetEndTime); + suBlueprints = suBlueprints || this.state.suBlueprints; + suBlueprintList = suBlueprintList || this.state.suBlueprintList; + suBlueprints = suBlueprints.concat(susTrail.original); + suBlueprintList = suBlueprintList.concat(susTrail.formatted); + } + this.setState({suBlueprints: _.uniqBy(suBlueprints, 'id'), + suBlueprintList: _.uniqBy(suBlueprintList, 'id'), + datasetStartTime: datasetStartTime, + datasetEndTime: datasetEndTime + }); + } - } else { - items.push(await this.getTimelineItem(suBlueprint, currentUTC)); - } - suList.push(suBlueprint); - } - // Add Subtask Id as control id for task if subtask type us control. Also add antenna_set & band prpoerties to the task object. - for (let task of suBlueprint.tasks) { - const controlTask = _.find(task.subtasks, subtask => { return subtask.primary === true}); - task.controlId = controlTask?controlTask.id:""; - if (task.task_type.toLowerCase() === "observation") { - task.antenna_set = task.specifications_doc.antenna_set; - task.band = task.specifications_doc.filter; - } + /** + * Fetches the SUBs with either start time or end time in the time range and formats as required for the table. + * @param {moment} startTime - start time from which SUBs should be loaded + * @param {moment} endTime - end time till which SUBs should be loaded + * @returns - Object with both original SUBs from backend and formatted SUBs for table list. + */ + async loadSchedulingUnits(startTime, endTime) { + let suList = []; + let suBlueprints = await ScheduleService.getExpandedSUList(startTime.format(UIConstants.CALENDAR_DATETIME_FORMAT), + endTime.format(UIConstants.CALENDAR_DATETIME_FORMAT)); + suBlueprints = _.filter(suBlueprints, suBlueprint => suBlueprint.status.toLowerCase !== 'obsolete'); + for (let suBlueprint of suBlueprints) { + suBlueprint['actionpath'] = `/schedulingunit/view/blueprint/${suBlueprint.id}`; + suBlueprint.suDraft = suBlueprint.draft; + suBlueprint.project = suBlueprint.draft.scheduling_set.project.name; + suBlueprint.suSet = suBlueprint.draft.scheduling_set; + suBlueprint.durationInSec = suBlueprint.duration; + suBlueprint.duration = UnitConverter.getSecsToHHmmss(suBlueprint.duration); + suBlueprint.workflowStatus = this.timelineCommonUtils.getWorkflowStatus(suBlueprint); + suBlueprint.task_content = ""; + suBlueprint.observ_template_name = suBlueprint.draft.observation_strategy_template.name; + suBlueprint.tasks = suBlueprint.task_blueprints; + suBlueprint.stationGroupCount = this.timelineCommonUtils.getSUStationGroupCount(suBlueprint).counts; + // Add Subtask Id as control id for task if subtask is primary. Also add antenna_set & band prpoerties to the task object. + for (let task of suBlueprint.tasks) { + const controlTask = _.find(task.subtasks, subtask => { return subtask.primary }); + task.controlId = controlTask?controlTask.id:""; + if (task.task_type.toLowerCase() === "observation") { + task.antenna_set = task.specifications_doc.antenna_set; + task.band = task.specifications_doc.filter; } - // Get stations involved for this SUB - let stations = this.timelineCommonUtils.getSUStations(suBlueprint); - suBlueprint.stations = _.uniq(stations); } - if (this.state.reservationEnabled) { - items = this.addWeekReservations(items, defaultStartTime, defaultEndTime, currentUTC); - } - // Get all scheduling constraint templates - ScheduleService.getSchedulingConstraintTemplates() - .then(suConstraintTemplates => { - this.suConstraintTemplates = suConstraintTemplates; - }); - this.setState({ - suBlueprints: suBlueprints, group: _.sortBy(group, ['value']), - suBlueprintList: suList, - items: items, currentUTC: currentUTC, isLoading: false, - startTime: defaultStartTime, endTime: defaultEndTime - }); - }); + // Get stations involved for this SUB + let stations = this.timelineCommonUtils.getSUStations(suBlueprint); + suBlueprint.stations = _.uniq(stations); + // suList.push(suBlueprint); + } + return {original: suBlueprints, formatted: suList}; } /** @@ -273,7 +348,7 @@ export class WeekTimelineView extends Component { .then(taskList => { for (let task of taskList) { //Control Task ID - const subTaskIds = (task.subTasks || []).filter(sTask => sTask.primary === true); + const subTaskIds = (task.subTasks || []).filter(sTask => sTask.primary); task.controlId = subTaskIds.length ? subTaskIds[0].id : ''; if (task.template.type_value.toLowerCase() === "observation" && task.specifications_doc.antenna_set) { @@ -369,9 +444,12 @@ export class WeekTimelineView extends Component { * @param {moment} endTime */ async dateRangeCallback(startTime, endTime, refreshData) { + let suBlueprints = this.state.suBlueprints; let suBlueprintList = [], group = [], items = []; let currentUTC = this.state.currentUTC; if (refreshData) { + const schedulingUnits = await this.getSchedulingUnits(startTime, endTime); + suBlueprints = schedulingUnits.original; for (const count of _.range(11)) { const groupDate = startTime.clone().add(count, 'days'); group.push({ 'id': groupDate.format("MMM DD ddd"), title: groupDate.format("MMM DD - ddd"), value: groupDate }); @@ -379,7 +457,7 @@ export class WeekTimelineView extends Component { let direction = startTime.week() - this.state.startTime.week(); currentUTC = this.state.currentUTC.clone().add(direction * 7, 'days'); if (startTime && endTime) { - for (const suBlueprint of this.state.suBlueprints) { + for (const suBlueprint of suBlueprints) { if (moment.utc(suBlueprint.start_time).isBetween(startTime, endTime) || moment.utc(suBlueprint.stop_time).isBetween(startTime, endTime) || (moment.utc(suBlueprint.start_time).isSameOrBefore(startTime, endTime) && @@ -409,9 +487,15 @@ export class WeekTimelineView extends Component { items = this.state.items; } this.setState({ + suBlueprints: suBlueprints, suBlueprintList: _.filter(suBlueprintList, (suBlueprint) => { return suBlueprint.start_time != null }), group: group, items: items, currentUTC: currentUTC, startTime: startTime, endTime: endTime }); + if (!this.state.datasetStartTime) { + this.setState({datasetStartTime: schedulingUnits.datasetStartTime, + datasetEndTime: schedulingUnits.datasetEndTime, + }); + } // On range change close the Details pane // this.closeSUDets(); } else { @@ -622,7 +706,7 @@ export class WeekTimelineView extends Component { suBlueprint.stationGroupCount = this.timelineCommonUtils.getSUStationGroupCount(suBlueprint).counts; // Add Subtask Id as control id for task if subtask type us control. Also add antenna_set & band prpoerties to the task object. for (let task of suBlueprint.tasks) { - const controlTask = _.find(task.subtasks, subtask => { return subtask.primary === true}); + const controlTask = _.find(task.subtasks, subtask => { return subtask.primary }); task.controlId = controlTask?controlTask.id:""; if (task.task_type.toLowerCase() === "observation" && task.specifications_doc.antenna_set) { @@ -647,6 +731,8 @@ export class WeekTimelineView extends Component { await this.setState({ reservationEnabled: e.value }); let updatedItemGroupData = await this.dateRangeCallback(this.state.startTime, this.state.endTime, true); this.timeline.updateTimeline(updatedItemGroupData); + this.timelineUIAttributes.reservationEnabled = e.value; + this.storeUIAttributes(); } /** diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js index 91433b8d5aa29f0bd6a2d76334181d7f6824b2b7..1a10b933c1d11443932184e9f418259c5612808e 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js @@ -127,18 +127,23 @@ const ScheduleService = { return schedulingUnit; }, /** - * This function fetches all required details to be shown in the list with reference fields populated in the server side. + * This function fetches all required details in the time range to be shown in the list with reference fields populated in the server side. + * @param {string} startTime - start time of the range in 'YYYY-MM-DD HH:mm:ss' format + * @param {string} endTime - end time of the range in 'YYYY-MM-DD HH:mm:ss' format * @returns Array - List of SUB with expanded and limited fields fetched */ - getExpandedSUList: async() => { + getExpandedSUList: async(startTime, endTime) => { let blueprints = []; try { - let initialResponse = await axios.get(`/api/scheduling_unit_blueprint/?ordering=id&expand=${SU_EXPAND_FIELDS.join()}&fields=${SU_FETCH_FIELDS.join()}`); + let url = `/api/scheduling_unit_blueprint/?ordering=name&expand=${SU_EXPAND_FIELDS.join()}&fields=${SU_FETCH_FIELDS.join()}`; + url = `${url}&start_time_before=${endTime || ''}&stop_time_after=${startTime || ''}`; + let initialResponse = await axios.get(url); const totalCount = initialResponse.data.count; const initialCount = initialResponse.data.results.length blueprints = blueprints.concat(initialResponse.data.results); if (totalCount > initialCount) { - let secondResponse = await axios.get(`/api/scheduling_unit_blueprint/?ordering=id&limit=${totalCount-initialCount}&offset=${initialCount}&expand=${SU_EXPAND_FIELDS.join()}&fields=${SU_FETCH_FIELDS.join()}`); + url = `${url}&limit=${totalCount-initialCount}&offset=${initialCount}`; + let secondResponse = await axios.get(url); blueprints = blueprints.concat(secondResponse.data.results); } } catch(error) {