Select Git revision

TMSS-2899 "Front end only save button tweak"
Reinder Kraaij authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
create.js 60.54 KiB
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import {Redirect} from 'react-router-dom';
import _ from 'lodash';
import $RefParser from "@apidevtools/json-schema-ref-parser";
import moment from 'moment';
import {publish} from '../../App';
import {Checkbox} from 'primereact/checkbox';
import {InputText} from 'primereact/inputtext';
import {InputTextarea} from 'primereact/inputtextarea';
import {Dropdown} from 'primereact/dropdown';
import {Toast} from 'primereact/toast';
import AppLoader from '../../layout/components/AppLoader';
import Jeditor from '../../components/JSONEditor/JEditor';
import UnitConversion from '../../utils/unit.converter';
import UnitConverter from '../../utils/unit.converter';
import ProjectService from '../../services/project.service';
import ScheduleService from '../../services/schedule.service';
import TaskService from '../../services/task.service';
import UIConstants from '../../utils/ui.constants';
import ParserUtility from '../../utils/parser.utility';
import PageHeader from '../../layout/components/PageHeader';
import SchedulingConstraint from './Scheduling.Constraints';
import Stations from './Stations';
import {CustomDialog} from '../../layout/components/CustomDialog';
import SchedulingSet from './schedulingset.create';
import UtilService from '../../services/util.service';
import ReactTooltip from "react-tooltip";
import AuthUtil from '../../utils/auth.util';
import {appGrowl, setAppGrowl} from '../../layout/components/AppGrowl';
import FormActionbar from '../../components/FormActionbar';
import {getConstraintTemplate, getTaskTemplateForTask} from "./create.helper";
import ObservationStrategySelector, {
queryMatchingObservationStrategyTemplates
} from "./components/ObservationStrategySelector";
import ProjectScheduleSetSelector from "./components/ProjectScheduleSetSelector";
/**
* Component to create a new SchedulingUnit from Observation strategy template
*/
export class SchedulingUnitCreate extends Component {
constructor(props) {
super(props);
this.state = {
observStrategyFilters: {
states: ['active'],
purpose: []
},
constraintValidEditor:false,
validForm:false,
validConstraints:false,
validSpecification:false,
userrole: {},
selectedProject: {},
showAddSet: false,
showDialog: false,
isDirty: false,
isLoading: true, // Flag for loading spinner
dialog: {header: '', detail: ''}, // Dialog properties
touched: {},
redirect: null, // URL to redirect
errors: [], // Form Validation errors
schedulingSets: [], // Scheduling set of the selected project
missing_StationFieldsErrors: [], // Validation for max no.of missing station
stationOptions: [],
stationGroups: {},
customSelectedStations: [], // custom stations
selectedStationsOfTask: {},
missingStationsofTask: {},
missingStnErorsofTask: {},
customStationsOfTask: {},
stationsCollapsed: false,
schedulingUnit: {
name: '',
description: '',
project: (props.match ? props.match.params.project : null) || null,
rank: '',
},
projectDisabled: (props.match ? (props.match.params.project ? true : false) : false), // Disable project selection if
observStrategy: {}, // Selected strategy to create SU
paramsSchema: null, // JSON Schema to be generated from strategy template to pass to JSOn editor
constraintSchema: null,
validEditor: false, // For JSON editor validation
validFields: {}, // For Form Validation
newSet: null,
observationIdSet: [],
createAnother: false,
multiCreateCount: 1
};
this.defaultConstraints = {}
this.taskFilters = []; // To get Short_Description details
this.priorityQueueTypes = [];
this.projects = []; // All projects to load project dropdown
this.taskTemplates = []; // All task templates to be filtered based on tasks in selected strategy template
this.tooltipOptions = UIConstants.tooltipOptions;
this.constraintTemplates = [];
this.myRoles = {};
this.nameInput = React.createRef(); // Ref to Name field for auto focus
this.formRules = { // Form validation rules
name: {required: true, message: "Name can not be empty"},
description: {required: true, message: "Description can not be empty"},
project: {required: true, message: "Select project to get Scheduling Sets"},
scheduling_set_id: {required: true, message: "Select the Scheduling Set"},
};
this.setEditorOutput = this.setEditorOutput.bind(this);
this.setConstraintsEditorOutput = this.setConstraintsEditorOutput.bind(this);
this.setConstraintEditorFun = this.setConstraintEditorFun.bind(this);
this.changeProject = this.changeProject.bind(this);
this.changeStrategy = this.changeStrategy.bind(this);
this.setStrategyConstraint = this.setStrategyConstraint.bind(this);
this.setSchedUnitParams = this.setSchedUnitParams.bind(this);
this.validateForm = this.validateForm.bind(this);
this.validateStations = this.validateStations.bind(this);
this.validateEditor = this.validateEditor.bind(this);
this.setEditorFunction = this.setEditorFunction.bind(this);
this.saveSchedulingUnit = this.saveSchedulingUnit.bind(this);
this.cancelCreate = this.cancelCreate.bind(this);
this.reset = this.reset.bind(this);
this.refreshSchedulingSet = this.refreshSchedulingSet.bind(this);
this.checkIsDirty = this.checkIsDirty.bind(this);
this.close = this.close.bind(this);
this.setSUSet = this.setSUSet.bind(this);
this.updateConstraintsParams = this.updateConstraintsParams.bind(this);
this.setCreateAnother = this.setCreateAnother.bind(this);
this.setState = this.setState.bind(this);
}
async componentDidMount() {
const permission = await AuthUtil.getUserPermissionByModule('scheduleunit_draft');
this.setState({userrole: permission});
const promises = [ProjectService.getProjectList(false, 'name,url'),
TaskService.getTaskTemplates(),
ScheduleService.getSchedulingConstraintTemplates(),
ScheduleService.getStationGroup(),
UtilService.getPriorityQueueType(),
TaskService.getTaskFilterDefinition('draft'),
ScheduleService.getStrategyPurpose(),
ScheduleService.getStrategyState(),
ProjectService.getMyRoles(),
];
Promise.all(promises).then(async (responses) => {
this.projects = responses[0];
this.taskTemplates = responses[1];
this.constraintTemplates = responses[2];
this.stations = responses[3];
this.priorityQueueTypes = responses[4];
this.taskFilters = responses[5];
this.templatePurposes = this.getStrategyFilterOptions(responses[6], 'purpose');
this.templateState = this.getStrategyFilterOptions(responses[7], 'state');
this.myRoles = responses[8];
const systemRoles = this.myRoles.system_roles.map(role => {
return role.toLowerCase()
});
const projectRoles = Object.fromEntries(
Object.entries(this.myRoles.project_roles).map(([project, roles]) => [project.toLowerCase(), roles])
);
// Users with system_roles other than superuser, operator & support can view the projects for which they have shared_support project_role
if (_.intersection(systemRoles, ['superuser', 'operator', 'support']).length < 1) {
this.projects = _.filter(this.projects, (project) => {
return _.includes(projectRoles[project.name.toLowerCase()], 'shared_support')
})
}
if (this.state.schedulingUnit.project) {
const selectedProject = _.filter(this.projects, {'name': this.state.schedulingUnit.project});
const projectSchedSets = await ScheduleService.getSchedulingSets(`project=${encodeURIComponent(this.state.schedulingUnit.project)}`);
let tmpSU = this.state.schedulingUnit;
tmpSU['output_pinned'] = selectedProject[0].auto_pin;
this.setState({
isLoading: false,
schedulingSets: projectSchedSets,
selectedProject: selectedProject,
schedulingUnit: tmpSU
});
} else {
this.setState({isLoading: false});
}
});
let observStrategyFilter = UtilService.localStore({type: 'get', key: 'strategyFilterSUCreate'});
if (Object.keys(observStrategyFilter).length === 0) {
observStrategyFilter = {states: ['active'], purpose: []}
}
this.setState({
observStrategies: await this.getMatchingObservationStrategyTemplates(observStrategyFilter),
observStrategyFilters: observStrategyFilter
});
}
async getMatchingObservationStrategyTemplates(filters) {
return await queryMatchingObservationStrategyTemplates(filters)
}
getStrategyFilterOptions(filterValues, filterId) {
if (filterValues) {
let optionList = [];
filterValues.forEach(choice => {
const tmpVar = {name: choice.value, value: choice.value};
if (filterId === 'purpose') {
optionList.push(tmpVar);
}
if (filterId === 'state' && tmpVar.value !== 'obsolete') {
optionList.push(tmpVar);
}
});
return _.sortBy(optionList, ['name']);
}
}
/**
* Function to call on change of project and reload scheduling set dropdown
* @param {string} projectName
*/
async changeProject(projectName) {
const projectSchedSets = await ScheduleService.getSchedulingSets(`project=${encodeURIComponent(projectName)}`);
let schedulingUnit = this.state.schedulingUnit;
schedulingUnit.project = projectName;
schedulingUnit.scheduling_set_id = null;
const selectedProject = _.filter(this.projects, {'name': projectName});
schedulingUnit['output_pinned'] = selectedProject[0].auto_pin;
if (!this.state.isDirty) {
publish('edit-dirty', true);
}
this.setState({
selectedProject: selectedProject,
schedulingUnit: schedulingUnit,
schedulingSets: projectSchedSets,
validForm: this.validateForm('project'),
isDirty: true
});
}
/**
* Function called when observation strategy template is changed.
* It generates the JSON schema for JSON editor and defult vales for the parameters to be captured
* @param {number} strategyId
*/
async changeStrategy(strategyId) {
const observStrategy = _.cloneDeep(_.find(this.state.observStrategies, {'id': strategyId}));
if (ParserUtility.addStationParameters(observStrategy)) {
this.setState({
observStrategy: observStrategy,
constraintSchema: null
});
}
let stationGroups = {};
const tasks = observStrategy.template.tasks;
const parameters = observStrategy.template.parameters;
let paramsOutput = {};
let schema = {
type: 'object', additionalProperties: false,
properties: {}, definitions: {}
};
let observationType = null;
let bandPassFilter = null;
// Take the constraint template schema as mentioned in the observing strategy
this.setStrategyConstraint(observStrategy.template.scheduling_constraints_template.name, observStrategy.template.scheduling_constraints_template.version);
const $strategyRefs = await $RefParser.resolve(observStrategy.template);
// TODo: This schema reference resolving code has to be moved to common file and needs to rework
for (const param of parameters) {
// TODO: make parameter handling more generic, instead of task specific.
if (!param.refs[0].startsWith("#/tasks/")) {
continue;
}
let taskPaths = param.refs[0].split("/");
const taskName = taskPaths[2];
/**
* For Short_Description, the task path length will be 4, so added below condition to get short_description details
* #/tasks/Combined Observation/short_description
*/
taskPaths = taskPaths.slice((taskPaths.length === 4 ? 3 : 4), taskPaths.length);
const task = tasks[taskName];
if (task) {
if (taskPaths.includes("station_groups")) {
stationGroups[param.name] = $strategyRefs.get(param.refs[0]);
} else {
const taskTemplate = getTaskTemplateForTask(this.taskTemplates, task);
if (['target observation', 'beamforming observation'].indexOf(taskTemplate.name.toLowerCase()) >= 0) {
observationType = taskTemplate.name;
}
// Get the default Bandpass filter and pass to the editor for frequency calculation from subband list
if (taskTemplate.type_value === 'observation' && task.specifications_doc.station_configuration?.filter) {
bandPassFilter = task.specifications_doc.station_configuration?.filter;
} else if (taskTemplate.type_value === 'observation' && taskTemplate.ref_resolved_schema.definitions.filter) {
bandPassFilter = taskTemplate.ref_resolved_schema.definitions.filter.default;
}
schema.definitions = {...schema.definitions, ...taskTemplate?.ref_resolved_schema.definitions};
taskPaths.reverse();
const paramProp = await ParserUtility.getParamProperty($strategyRefs, taskPaths, taskTemplate.ref_resolved_schema, this.taskFilters);
schema.properties[param.name] = _.cloneDeep(paramProp);
if (schema.properties[param.name]) {
schema.properties[param.name].title = param.name;
try {
schema.properties[param.name].default = $strategyRefs.get(param.refs[0]);
} catch (err) { /* empty */ }
paramsOutput[param.name] = schema.properties[param.name].default || ParserUtility.getDefaultParamValue(schema.properties[param.name].type);
}
}
}
}
if (!this.state.isDirty) {
publish('edit-dirty', true);
}
this.setState({
observStrategy: observStrategy, paramsSchema: _.cloneDeep(schema), constraintParamsOutput: null,
observationType: observationType, stationsCollapsed: false,
paramsOutput: paramsOutput, stationGroups: stationGroups,
bandPassFilter: bandPassFilter, isDirty: true
});
// Function called to clear the JSON Editor fields and reload with new schema
if (this.state.editorFunction) {
this.state.editorFunction();
}
}
/**
* This is the callback method to be passed to the JSON editor.
* JEditor will call this function when there is change in the editor.
* @param {Object} jsonOutput
* @param {Array} errors
*/
setEditorOutput(jsonOutput, errors) {
this.paramsOutput = jsonOutput;
this.validEditor = errors.length === 0;
this.updateParameters();
if (errors.length > 0 ) {
this.setState({errors: [...errors]})
}
this.setState({
paramsOutput: jsonOutput,
validEditor: errors.length === 0,
validForm: this.validateForm()
});
}
/**
* Common function validate specification
* @param {String} template Template url
* @param {Object} updatedSpecificationDoc Object which needs to be validated
*/
async validateSpecificationsDoc(updatedObservStrategy) {
if (updatedObservStrategy) {
const promises = [];
promises.push(ScheduleService.validateSpecificationsDoc(updatedObservStrategy.scheduling_unit_template, updatedObservStrategy.template))
const validation_result = await Promise.all(promises);
if (validation_result.length > 0) {
let validationMessage = validation_result[0]['message'];
if (validation_result[0]['valid']) {
validationMessage = "Task Parameters specification is valid"
}
this.setState({
validSpecification: validation_result[0]['valid'],
validSpecificationMessage: validationMessage
});
}
}
}
async validateSchedulingConstraints(updatedConstraintParmas) {
if (updatedConstraintParmas) {
let schedulingConstraintsDoc = updatedConstraintParmas.scheduling_constraints_doc;
for (const key of _.keys(schedulingConstraintsDoc.time)) {
if (!schedulingConstraintsDoc.time[key]) {
delete schedulingConstraintsDoc.time[key];
}
}
const promises = [];
promises.push(ScheduleService.validateSpecificationsDoc(updatedConstraintParmas.constraint.url, schedulingConstraintsDoc))
const validation_result = await Promise.all(promises);
if (validation_result.length > 0) {
let validationMessage = validation_result[0]['message'];
if (validation_result[0]['valid']) {
validationMessage = "Constraints specification is valid"
}
this.setState({
validConstraints: validation_result[0]['valid'],
validConstraintMessage: validationMessage
});
}
}
}
loadConstraints(properties, parentProps, defaultValues) {
parentProps = parentProps ? parentProps : _.cloneDeep(this.defaultConstraints);
for (const key in properties) {
let propertyValue = properties[key];
if (propertyValue instanceof Object && key !== "reference_pointing") {
if (defaultValues) {
parentProps[key] = propertyValue;
this.loadConstraints(propertyValue, parentProps[key], true);
} else {
this.loadConstraints(propertyValue, parentProps[key], false);
}
} else {
if (_.includes(['from', 'to'], key)) {
propertyValue = (propertyValue < 0 ? '-' : '') + UnitConverter.getSecsToHHmmss(propertyValue);
}
if (!_.includes(_.keys(parentProps), "angle1") && _.includes(['sun', 'moon', 'jupiter', 'target', 'calibrator'], key) && !defaultValues) {
propertyValue = ((propertyValue * 180) / Math.PI).toFixed(2);
}
if (_.includes(['between', 'not_between'], key)) {
propertyValue = [];
}
parentProps[key] = propertyValue;
}
}
if (defaultValues) {
this.defaultConstraints = parentProps;
} else {
this.setState({constraintInitParams: parentProps});
}
// After converting the sky values, set the value directly in the JSON editor using the function passed from the editor
if (this.state.constraintEditorFunction) {
this.state.constraintEditorFunction("setValue", [parentProps]);
}
}
setConstraintsEditorOutput(jsonOutput, errors) {
let err = [...errors];
if (jsonOutput.scheduler === 'online' || jsonOutput.scheduler === 'dynamic') {
err = err.filter(e => e.path !== 'root.time.at');
}
this.constraintParamsOutput = jsonOutput;
// When the constraint template is changed, get the default values from the template and overwrite with values from strategy
if (!this.state.constraintParamsOutput) {
if (this.state.observStrategy.template!==undefined) {
this.loadConstraints(_.cloneDeep(this.state.observStrategy.template.scheduling_constraints_doc), _.cloneDeep(jsonOutput), false);
}
}
this.constraintValidEditor = err.length === 0;
if (!this.state.isDirty && this.state.constraintParamsOutput && !_.isEqual(this.state.constraintParamsOutput, jsonOutput)) {
if (!_.isEmpty(this.state.observStrategy)) {
if (!this.state.isDirty) {
publish('edit-dirty', true);
}
this.setState({
constraintParamsOutput: jsonOutput,
constraintValidEditor: err.length === 0,
validForm: this.validateForm(),
isDirty: true
});
}
} else {
this.setState({
constraintParamsOutput: jsonOutput,
constraintValidEditor: err.length === 0,
validForm: this.validateForm()
});
}
this.updateConstraintsParams();
}
/**
* This function is mainly added for Unit Tests. If this function is removed Unit Tests will fail.
*/
validateEditor() {
return this.validEditor && this.constraintValidEditor ? true : false;
}
/**
* Function to set form values to the SU object
* @param {string} key
* @param {object} value
*/
async setSchedUnitParams(key, value) {
this.setState({
touched: {
...this.state.touched,
[key]: true
}
});
let schedulingUnit = _.cloneDeep(this.state.schedulingUnit);
if (key === 'rank') {
const previousValue = schedulingUnit.rank;
value = value.replace(/([^0-9.]+)/, "");
const match = /(\d{0,1})[^.]*((?:\.\d{0,4})?)/g.exec(value);
const val = match[1] + match[2];
if (val === '' || (val >= 0 && val <= 1)) {
schedulingUnit[key] = val === '' ? '' : Number(val);
} else {
schedulingUnit[key] = previousValue;
}
} else if (key === 'scheduling_set_id') {
schedulingUnit[key] = value;
let schedulingUnitList = await ScheduleService.getSchedulingBySet(value, 'observation_strategy_template_id');
let observationIdSet = _.uniq(_.map(schedulingUnitList, 'observation_strategy_template_id'));
this.setState({observationIdSet: observationIdSet});
} else {
schedulingUnit[key] = value;
}
if (!this.state.isDirty && !_.isEqual(this.state.schedulingUnit, schedulingUnit)) {
if (!this.state.isDirty) {
publish('edit-dirty', true);
}
this.setState({schedulingUnit: schedulingUnit});
this.setState({validForm: this.validateForm(key), validEditor: this.validateEditor(), isDirty: true});
} else {
this.setState({schedulingUnit: schedulingUnit});
this.setState({validForm: this.validateForm(key), validEditor: this.validateEditor()});
}
this.validateEditor();
}
/**
* JEditor's function that to be called when parent wants to trigger change in the JSON Editor
* @param {Function} editorFunction
*/
setEditorFunction(editorFunction) {
this.setState({editorFunction: editorFunction});
}
setConstraintEditorFun(editorFunction) {
this.setState({constraintEditorFunction: editorFunction});
}
/**
* Validation function to validate the form or field based on the form rules.
* If no argument passed for fieldName, validates all fields in the form.
* @param {string} fieldName
*/
validateForm(fieldName) {
let validForm = false;
let errors = this.state.errors;
let validFields = this.state.validFields;
if (fieldName) {
delete errors[fieldName];
delete validFields[fieldName];
if (this.formRules[fieldName]) {
const rule = this.formRules[fieldName];
const fieldValue = this.state.schedulingUnit[fieldName];
if (rule.required) {
if (!fieldValue) {
errors[fieldName] = rule.message ? rule.message : `${fieldName} is required`;
} else {
validFields[fieldName] = true;
}
}
}
} else {
errors = {};
validFields = {};
for (const fieldName in this.formRules) {
const rule = this.formRules[fieldName];
const fieldValue = this.state.schedulingUnit[fieldName];
if (rule.required) {
if (!fieldValue) {
errors[fieldName] = rule.message ? rule.message : `${fieldName} is required`;
} else {
validFields[fieldName] = true;
}
}
}
}
const updatedErrors = errors.length > 0 ? [...this.state.errors, errors] : this.state.errors
const updatedValidFields = validFields ? {...this.state.validFields, ...validFields} : this.state.validFields;
this.setState({errors: updatedErrors, validFields: updatedValidFields});
if (Object.keys(validFields).length === Object.keys(this.formRules).length) {
validForm = true;
}
const validStations = this.validateStations();
return validForm && validStations && _.sum(_.values(this.state.missingStnErorsofTask)) <= 0;
}
/**
* Checks if at least one station selected for each station_group parameter and max_nr_missing station value is entered.
* @returns Boolean
*/
validateStations() {
let validStations = false;
// Check if at least one station selected for all station groups(parameters)
// Break the loop if any of the required condition check is not satisfied
for (const stationGroup of _.keys(this.state.stationGroups)) {
// Check if the selected stations and custom stations are not empty
if (this.state.selectedStationsOfTask[stationGroup] && this.state.customStationsOfTask[stationGroup]) {
// Get the stations list for the station group(parameter)
const stationsList = this.state.selectedStationsOfTask[stationGroup].concat(this.state.customStationsOfTask[stationGroup]);
//If stationsList is empty then no station group and custom stations are selected
if (stationsList.length === 0) {
validStations = false;
break;
} else {
//If predefined station group(Dutch, Remote, etc.,) is/are selected, validate if max_nr_missing is entered
if (this.state.selectedStationsOfTask[stationGroup].length > 0) {
for (const selectedStations of this.state.selectedStationsOfTask[stationGroup]) {
const selectedGroup = this.state.missingStationsofTask[stationGroup][selectedStations]
if (selectedGroup) {
if (selectedGroup.max_nr_missing === "") {
validStations = false;
break;
} else {
validStations = true;
}
}
}
if (!validStations) {
break;
} else {
validStations = true;
}
}
//If custom station group(s) is/are selected, validate if max_nr_missing is entered
if (this.state.customStationsOfTask[stationGroup].length > 0) {
for (const customStations of this.state.customStationsOfTask[stationGroup]) {
if (customStations.max_nr_missing === "") {
validStations = false;
break;
} else {
validStations = true;
}
}
if (!validStations) {
break;
} else {
validStations = true;
}
}
}
} else {
validStations = false;
break;
}
}
return validStations;
}
/**
* Function to update task parameters
*/
async updateParameters() {
let observStrategy = _.cloneDeep(this.state.observStrategy);
const $refs = await $RefParser.resolve(observStrategy.template);
observStrategy.template.parameters.forEach(async (param, index) => {
// $refs.set(observStrategy.template.parameters[index]['refs'][0], this.state.paramsOutput[param.name]);
let refIndex = 0;
for (const ref of param.refs) {
let station_groups = [];
if (ref.includes("station_groups")) {
for (const selectedGroup of this.state.selectedStationsOfTask[param.name]) {
const stnGroup = this.state.missingStationsofTask[param.name][selectedGroup];
station_groups.push({
stations: stnGroup.stations,
max_nr_missing: parseInt(stnGroup.max_nr_missing)
});
}
for (const customGroup of this.state.customStationsOfTask[param.name]) {
station_groups.push({
stations: customGroup.stations,
max_nr_missing: parseInt(customGroup.max_nr_missing)
});
}
$refs.set(observStrategy.template.parameters[index]['refs'][refIndex], station_groups);
} else {
$refs.set(observStrategy.template.parameters[index]['refs'][refIndex], this.state.paramsOutput[param.name]);
}
refIndex++;
}
});
if (this.state.validEditor) {
this.validateSpecificationsDoc(observStrategy);
} else {
let message = 'Task Parameters specification is not valid: '
this.state.errors.forEach(error => {
message = message + error.message + "\n"
})
this.setState({
validSpecification: false,
validSpecificationMessage: message
})
}
this.setState({updatedObservStrategy: observStrategy});
}
/**
* Function to update constraints
*/
updateConstraintsParams() {
const constStrategy = _.cloneDeep(this.state.constraintParamsOutput);
for (let type in constStrategy.time) {
if (constStrategy.scheduler === 'online' || constStrategy.scheduler === 'dynamic') {
// delete constStrategy.time.at;
}
if (!constStrategy.time.after) {
delete constStrategy.time.after;
}
if (!constStrategy.time.before) {
delete constStrategy.time.before;
}
if (constStrategy.time[type] && constStrategy.time[type].length > 0) {
if (typeof constStrategy.time[type] === 'string') {
constStrategy.time[type] = `${moment(constStrategy.time[type]).format("YYYY-MM-DDTHH:mm:ss.SSSSS", {trim: false})}Z`;
} else {
constStrategy.time[type].forEach(time => {
for (let key in time) {
time[key] = `${moment(time[key]).format("YYYY-MM-DDTHH:mm:ss.SSSSS", {trim: false})}Z`;
}
})
}
}
}
if (constStrategy.sky.transit_offset) {
constStrategy.sky.transit_offset.from = UnitConversion.getHHmmssToSecs(constStrategy.sky.transit_offset.from);
constStrategy.sky.transit_offset.to = UnitConversion.getHHmmssToSecs(constStrategy.sky.transit_offset.to);
}
UnitConversion.degreeToRadians(constStrategy.sky);
const const_strategy = {
scheduling_constraints_doc: constStrategy,
id: this.state.constraintSchema.id,
constraint: this.state.constraintSchema
};
if (this.state.constraintValidEditor) {
this.validateSchedulingConstraints(const_strategy)
} else {
this.setState({validConstraints: false, validConstraintMessage: 'Constraints specification is not valid'})
}
this.setState({updatedConstraintParmas: const_strategy});
}
/**
* Function to create Scheduling unit
*/
async saveSchedulingUnit() {
await this.updateParameters();
let observStrategy = _.cloneDeep(this.state.updatedObservStrategy);
let newSpecificationDoc = {}
for (const parameter of observStrategy.template.parameters) {
for (const ref of parameter.refs) {
let paramPath = ref.replace(new RegExp('#/', "g"), "");
paramPath = paramPath.replace(new RegExp('/', "g"), '.');
const value = _.get(observStrategy.template, paramPath);
if (value !== undefined) {
newSpecificationDoc = _.set(newSpecificationDoc, paramPath, value);
}
}
}
newSpecificationDoc['scheduling_constraints_doc'] = _.cloneDeep(this.state.updatedConstraintParmas).scheduling_constraints_doc;
let schedulingUnit = this.state.schedulingUnit;
schedulingUnit.observation_strategy_template_id = observStrategy.id;
schedulingUnit.rank = schedulingUnit.rank === undefined || schedulingUnit.rank === '' ? 0 : schedulingUnit.rank.toFixed(4);
schedulingUnit = await ScheduleService.saveSchedulingUnit(schedulingUnit, newSpecificationDoc);
if (schedulingUnit.isSUUpdated) {
if (appGrowl === null) {
setAppGrowl(this.growl);
}
appGrowl.show({
severity: 'success',
summary: 'Success',
detail: 'Scheduling Unit and Tasks are created successfully'
});
if (this.state.createAnother) {
this.setState({schedulingUnit: schedulingUnit.data, isDirty: false});
this.reset();
} else {
this.setState({
schedulingUnit: schedulingUnit.data,
isDirty: false,
redirect: `/schedulingunit/view/draft/${schedulingUnit.data.id}`
});
}
publish('edit-dirty', false);
} else {
this.growl.show({
severity: 'error',
summary: 'Error Occured',
detail: schedulingUnit.message || 'Unable to save Scheduling Unit/Tasks'
});
}
}
/**
* warn before cancel the page if any changes detected
*/
checkIsDirty() {
if (this.state.isDirty) {
this.setState({showDialog: true});
} else {
this.cancelCreate();
}
}
close() {
this.setState({showDialog: false});
}
/**
* Cancel SU creation and redirect
*/
cancelCreate() {
publish('edit-dirty', false);
if (this.state.multiCreateCount > 1) {
if (this.state.isDirty) {
this.props.history.push(`/schedulingunit`);
} else {
this.props.history.go(this.state.multiCreateCount * -1);
}
} else {
this.props.history.length > 1 ? this.state.isDirty ? this.props.history.go(-2) : this.props.history.go(-1) : this.props.history.push(`/schedulingunit`);
}
this.setState({showDialog: false});
}
async setStrategyConstraint(name, version) {
let strategyConstraint = getConstraintTemplate(this.constraintTemplates, name, version)
let schedulingUnit = {...this.state.schedulingUnit};
schedulingUnit.scheduling_constraints_template_id = strategyConstraint ? strategyConstraint.id : '';
strategyConstraint.ref_resolved_schema.properties.sky.properties.transit_offset.properties.from.default = UnitConversion.getSecsToHHmmssWithSign(strategyConstraint.ref_resolved_schema.properties.sky.properties.transit_offset.properties.from.default);
strategyConstraint.ref_resolved_schema.properties.sky.properties.transit_offset.properties.to.default = UnitConversion.getSecsToHHmmssWithSign(strategyConstraint.ref_resolved_schema.properties.sky.properties.transit_offset.properties.to.default);
strategyConstraint.ref_resolved_schema.properties.sky.properties.min_distance.properties.sun.propertyOrder = 1;
strategyConstraint.ref_resolved_schema.properties.sky.properties.min_distance.properties.moon.propertyOrder = 2;
strategyConstraint.ref_resolved_schema.properties.sky.properties.min_distance.properties.jupiter.propertyOrder = 3;
this.setState({constraintSchema: strategyConstraint, schedulingUnit});
}
/**
* Reset function to be called when user wants to create new SU
*/
reset() {
const schedulingSets = this.state.schedulingSets;
this.nameInput.focus();
const selectedProject = this.props.match?.params?.project;
let multiCreateCount = this.state.multiCreateCount;
this.setState({
isDirty: false,
dialog: {header: '', detail: ''},
errors: [],
schedulingSets: this.props.match?.params?.project ? schedulingSets : [],
schedulingUnit: {
name: '',
description: '',
project: selectedProject || null,
scheduling_constraints_template_id: this.constraintTemplates[0].id,
rank: '',
output_pinned: this.props.match?.params?.project ? ((_.find(this.projects, {'name': selectedProject}))?.auto_pin) : null
},
projectDisabled: (this.props.match?.params?.project ? true : false),
observStrategy: {},
paramsSchema: null,
paramsOutput: null,
validEditor: false,
validFields: {},
constraintSchema: null,
constraintInitParams: null,
selectedStations: null,
selectedStationsOfTask: {},
customStationsOfTask: {},
touched: false,
stationGroups: {},
multiCreateCount: ++multiCreateCount
});
this.state.editorFunction();
}
onUpdateStations = (state, selectedStations, missing_StationFieldsErrors, customSelectedStations, taskName) => {
let selectedStationsOfTask = this.state.selectedStationsOfTask;
let taskStations = selectedStationsOfTask[taskName] || [];
let customStationsOfTask = this.state.customStationsOfTask;
let taskCustomStations = customStationsOfTask[taskName] || [];
let missingStationsofTask = this.state.missingStationsofTask;
let missingStnErorsofTask = this.state.missingStnErorsofTask;
selectedStationsOfTask[taskName] = selectedStations;
customStationsOfTask[taskName] = customSelectedStations;
missingStationsofTask[taskName] = state;
missingStnErorsofTask[taskName] = missing_StationFieldsErrors;
if (!this.state.isDirty) {
if (!_.isEqual(taskStations, selectedStations) || !_.isEqual(taskCustomStations, customSelectedStations)) {
this.setState({
missingStationsofTask,
selectedStationsOfTask,
missingStnErorsofTask,
customStationsOfTask
}, () => {
this.setState({validForm: this.validateForm(), isDirty: true});
if (!this.state.isDirty) {
publish('edit-dirty', true);
}
});
} else {
this.setState({
missingStationsofTask,
selectedStationsOfTask,
missingStnErorsofTask,
customStationsOfTask
}, () => {
this.setState({validForm: this.validateForm()});
});
}
} else {
this.setState({
missingStationsofTask,
selectedStationsOfTask,
missingStnErorsofTask,
customStationsOfTask
}, () => {
this.setState({validForm: this.validateForm()});
});
}
};
async refreshSchedulingSet() {
let tmpSU = _.cloneDeep(this.state.schedulingUnit);
const schedulingSets = await ScheduleService.getSchedulingSets(`project=${encodeURIComponent(this.state.schedulingUnit.project)}`);
if (this.state.newSet) {
tmpSU.scheduling_set_id = this.state.newSet.id;
this.setState({
schedulingUnit: tmpSU,
newSet: null
});
}
this.setState({saveDialogVisible: false, showAddSet: false, schedulingSets: schedulingSets});
}
/**
* Set newly created as current SU Set
* @param {SU Set Name} suSet
*/
setSUSet(suSet) {
this.setState({newSet: suSet});
}
/**
* Select/Unselect the create another check box
* @param {*} e
*/
setCreateAnother(e) {
this.setState({'createAnother': e.target.checked})
}
render() {
let isSaveEnabled = ( this.state.constraintValidEditor && this.state.validEditor && this.state.validForm && this.state.validConstraints && this.state.validSpecification)
if (this.state.redirect) {
return <Redirect to={{pathname: this.state.redirect}}></Redirect>
}
const schema = this.state.paramsSchema;
const {scheduleunit_draft} = this.state.userrole;
let jeditor = null;
if (schema) {
jeditor = React.createElement(Jeditor, {
title: "Task Parameters",
schema: schema,
initValue: this.state.paramsOutput,
callback: this.setEditorOutput,
parentFunction: this.setEditorFunction,
bandPassFilter: this.state.bandPassFilter,
observationType: this.state.observationType,
theme: 'bootstrap4'
});
}
return (
<React.Fragment>
<Toast ref={(el) => this.growl = el}/>
<PageHeader location={this.props.location} className="SchedulingUnit-PageHeader" title={'Scheduling Unit - Add'}
actions={[{
icon: 'fa-window-close', title: 'Click to close Scheduling Unit creation',
type: 'button', actOn: 'click', props: {callback: this.checkIsDirty}
}]}/>
{this.state.isLoading ? <AppLoader/> :
<>
<div>
<div className="p-fluid">
<div className="p-field p-grid">
<label htmlFor="schedUnitName" className="col-lg-2 col-md-2 col-sm-12">Name <span
style={{color: 'red'}}>*</span></label>
<div className="col-lg-3 col-md-3 col-sm-12" data-testid="namediv">
<InputText
className={(this.state.errors.name && this.state.touched.name) ? 'input-error' : ''}
id="name" data-testid="name"
tooltip="Enter name of the Scheduling Unit"
tooltipOptions={this.tooltipOptions} maxLength="128"
ref={input => {
this.nameInput = input;
}}
value={this.state.schedulingUnit.name} autoFocus
onChange={(e) => this.setSchedUnitParams('name', e.target.value)}
onBlur={(e) => this.setSchedUnitParams('name', e.target.value)}/>
<label
className={(this.state.errors.name && this.state.touched.name) ? "error" : "info"}>
{this.state.errors.name && this.state.touched.name ? this.state.errors.name : "Max 128 characters"}
</label>
</div>
<div className="col-lg-1 col-md-1 col-sm-12"></div>
<label htmlFor="description"
className="col-lg-2 col-md-2 col-sm-12">Description <span
style={{color: 'red'}}>*</span></label>
<div className="col-lg-3 col-md-3 col-sm-12">
<InputTextarea
className={(this.state.errors.description && this.state.touched.description) ? 'input-error' : ''}
rows={3} cols={30}
tooltip="Longer description of the scheduling unit"
tooltipOptions={this.tooltipOptions} maxLength="128"
data-testid="description" value={this.state.schedulingUnit.description}
onChange={(e) => this.setSchedUnitParams('description', e.target.value)}
onBlur={(e) => this.setSchedUnitParams('description', e.target.value)}/>
<label
className={(this.state.errors.description && this.state.touched.description) ? "error" : "info"}>
{(this.state.errors.description && this.state.touched.description) ? this.state.errors.description : "Max 255 characters"}
</label>
</div>
</div>
{<ProjectScheduleSetSelector
selectedProject={this.state.schedulingUnit.project}
projectDisabled={this.state.projectDisabled}
allProjects={this.projects}
projectErrors={this.state.errors.project}
touchedProject={this.state.touched.project}
changeProject={this.changeProject}
selectedSchedulingSet={this.state.schedulingUnit.scheduling_set_id}
allSchedulingSets={this.state.schedulingSets}
schedulingSetErrors={this.state.errors.scheduling_set_id}
touchedSchedulingSet={this.state.touched.scheduling_set_id}
setSchedUnitParams={this.setSchedUnitParams}
scheduleUnitDraft={scheduleunit_draft}
setState={this.setState}
/>}
<div className="p-field p-grid">
<label htmlFor="rank" className="col-lg-2 col-md-2 col-sm-12">Rank</label>
<div className="col-lg-3 col-md-3 col-sm-12" data-testid="rank">
<input type="number"
data-for="reacttooltip"
data-iscapture="true"
data-tip="Priority of this scheduling unit w.r.t. other scheduling units within the same queue and project.. Min-0.0000, Max-1.0000"
id="proj-rank" name="rank" data-testid="rank"
className="p-inputtext p-component p-filled"
value={this.state.schedulingUnit.rank}
step="0.0001"
lang="en"
onChange={(e) => this.setSchedUnitParams('rank', e.target.value)}/>
</div>
<div className="col-lg-1 col-md-1 col-sm-12"></div>
<label htmlFor="priority_queue" className="col-lg-2 col-md-2 col-sm-12">Priority
Queue</label>
<div className="col-lg-3 col-md-3 col-sm-10">
<Dropdown data-testid="priority_queue" id="priority_queue" optionLabel="value"
optionValue="value"
tooltip="Priority queue of this scheduling unit. Queues provide a strict ordering between scheduling units."
tooltipOptions={this.tooltipOptions}
value={this.state.schedulingUnit.priority_queue}
options={this.priorityQueueTypes}
onChange={(e) => {
this.setSchedUnitParams('priority_queue', e.value)
}}
placeholder="Select Priority Queue"/>
</div>
</div>
<div className="p-field p-grid">
<label htmlFor="observStrategy" className="col-lg-2 col-md-2 col-sm-12">
Observation Strategy <span style={{color: 'red'}}>*</span>
</label>
{<ObservationStrategySelector
selectedPurpose={this.state.observStrategyFilters.purpose}
allPurposes={this.templatePurposes}
selectedState={this.state.observStrategyFilters.states}
allStates={this.templateState}
selectedObservationStrategy={this.state.observStrategy}
allObservationStrategies={this.state.observStrategies}
observationIdSet={this.state.observationIdSet}
changeStrategy={this.changeStrategy}
localStoreKey={"strategyFilterSUCreate"}
setState={this.setState}
/>}
<div className="col-lg-1 col-md-1 col-sm-12"></div>
<label htmlFor="autodeletion" className="col-lg-2 col-md-2 col-sm-12">Pin
Data</label>
<div className="col-lg-3 col-md-3 col-sm-12" data-testid="autodeletion">
<Checkbox inputId="trigger" role="trigger"
tooltip="Do not delete data automatically after successful ingest"
tooltipOptions={this.tooltipOptions}
checked={this.state.schedulingUnit.output_pinned}
onChange={(e) => this.setSchedUnitParams('output_pinned', e.target.checked)}
></Checkbox>
</div>
</div>
{_.keys(this.state.stationGroups).length > 0 &&
<div className='grouping'>
<fieldset>
<h3 className="card-title level-1 je-object__title"
style={{display: "inline-block"}}>
<button type="button"
title={this.state.stationsCollapsed ? "Expand" : "Collapse"}
className="btn btn-secondary btn-sm json-editor-btn-collapse json-editor-btntype-toggle"
onClick={() => this.setState({stationsCollapsed: !this.state.stationsCollapsed})}>
<i className={this.state.stationsCollapsed ? "fas fa-caret-right" : "fas fa-caret-down"}></i>
</button>
<label>Station specification</label>
</h3>
{!this.state.stationsCollapsed &&
<>
{_.keys(this.state.stationGroups).map((stnGroup) => {
return <Stations
taskName={stnGroup}
stationGroup={this.state.stationGroups[stnGroup]}
onUpdateStations={this.onUpdateStations.bind(this)}
height={'auto'}
key={stnGroup}
/>
})}
</>
}
</fieldset>
</div>
}
</div>
{this.state.constraintSchema && <div className="p-fluid">
<fieldset className="border-style">
<div className="p-col-12" style={{width: "100%"}}>
<SchedulingConstraint
constraintTemplate={this.state.constraintSchema}
// initValue={this.state.constraintInitParams} // No need to pass this value as the values are set with callback
callback={this.setConstraintsEditorOutput}
parentFunction={this.setConstraintEditorFun}/>
</div>
</fieldset>
<div className="p-col-12"
style={{marginLeft: '0.4em', marginTop: '-10px', paddingTop: '0px'}}>
<label className={this.state.validConstraints ? "info" : "error"}>
{this.state.validConstraintMessage ? this.state.validConstraintMessage : ""}
</label>
</div>
</div>}
<div></div>
{this.state.paramsSchema &&
<div className="p-fluid">
<fieldset className="border-style">
<div style={{marginLeft: '-0.5em'}}>
<div className="p-col-12" style={{width: "100%"}}>
{jeditor}
</div>
</div>
</fieldset>
<div className="p-col-12" style={{
marginLeft: '0.4em',
marginTop: '-10px',
paddingTop: '0px',
marginBottom: "2em"
}}>
<label className={this.state.validSpecification ? "info" : "error"} data-testid="validation-spec-msg">
{this.state.validSpecificationMessage}
</label>
</div>
</div>
}
</div>
<div className="p-breadcrumb footer-long-div actions" style={{marginLeft: '2em !important'}}>
<FormActionbar createAnotherCallBack={this.setCreateAnother}
createAnother={this.state.createAnother}
tooltip="Select checkbox to create another Scheduling Unit after saving this Scheduling Unit"
submitTitle={(!this.state.constraintValidEditor || !this.state.validEditor || !this.state.validForm || !this.state.validConstraints || !this.state.validSpecification) ? "" : "Save Scheduling Unit"}
onSubmit={this.saveSchedulingUnit}
disableSaveBtn={!isSaveEnabled}
onCancel={this.checkIsDirty}/>
</div>
<ReactTooltip id="reacttooltip" place={'left'} type={'dark'} effect={'solid'} multiline={true}/>
</>
}
{/* Dialog component to show messages and get input */}
<div className="p-grid" data-testid="confirm_dialog">
{this.state.showAddSet &&
<SchedulingSet callbackFunction={this.setSUSet} project={this.state.selectedProject[0]}
onCancel={this.refreshSchedulingSet}/>
}
<CustomDialog type="confirmation" visible={this.state.showDialog} width="40vw"
header={'Add Scheduling Unit'}
message={'Do you want to discard your changes? A new Scheduling Unit will not be created'}
content={''} onClose={this.close} actions={[{
id: "yes",
title: 'Discard',
callback: this.cancelCreate,
className: 'act-btn-dispose'
},
{id: "no", title: 'Cancel', className: 'act-btn-cancel', callback: this.close}]}
>
</CustomDialog>
</div>
</React.Fragment>
);
}
}
SchedulingUnitCreate.propTypes = {
match: PropTypes.object,
history: PropTypes.object,
location: PropTypes.oneOfType([PropTypes.object, PropTypes.string])
}
export default SchedulingUnitCreate;