diff --git a/SAS/TMSS/frontend/tmss_webapp/src/authenticate/permission.stack.handler.js b/SAS/TMSS/frontend/tmss_webapp/src/authenticate/permission.stack.handler.js index 14a8bdddfd9a3526662a003c79ae4817b2577c75..de7b00d33d26afb14cf16301b52043f22bdfbe1f 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/authenticate/permission.stack.handler.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/authenticate/permission.stack.handler.js @@ -45,7 +45,8 @@ const PermissionStackUtil = { reservation: 'reservation', task_relation_draft: 'task_relation_draft', task_relation_blueprint: 'task_relation_blueprint', - dynamicScheduler: 'setting/dynamic_scheduling_enabled' + dynamicScheduler: 'setting/dynamic_scheduling_enabled', + systemevent: 'system_event', } const modules = Object.keys(module_url); for(const module of modules) { @@ -117,6 +118,21 @@ const PermissionStackUtil = { delete: allowedPermission?(_.includes(allowedPermission, 'DELETE')):false }; } + else if(module === 'systemevent') { + let getAccesss = allowedPermission?(_.includes(allowedPermission, 'GET')):false; + let postAccess = allowedPermission?(_.includes(allowedPermission, 'POST')):false; + permissionStack['timeline']['addsystemevent'] = postAccess; + permissionStack['timeline']['listsystemevent'] = getAccesss; + permissionStack['weekoverview']['addsystemevent'] = postAccess; + permissionStack['weekoverview']['listsystemevent'] = getAccesss; + + permissionStack['systemevent'] = { + create: postAccess, + list: getAccesss, + edit: allowedPermission?(_.includes(allowedPermission, 'PATCH')):false, + delete: allowedPermission?(_.includes(allowedPermission, 'DELETE')):false + }; + } else if (module === 'dynamicScheduler') { permissionStack[module] = { setting: allowedPermission?(_.includes(allowedPermission, 'PATCH')):false, diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/_overrides.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/_overrides.scss index f05db634874e71d162dbc225b5da90e93211ddf8..717d2654bf3844d0241220bb2292da3176528f8b 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/_overrides.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/_overrides.scss @@ -364,4 +364,9 @@ h3 + div + p { /** - JSON Editor - End - */ \ No newline at end of file + */ + + // Show title case in dropdown value and option in System Event page + .systemEvent{ + text-transform:capitalize; + } \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/SystemEvent/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/SystemEvent/index.js new file mode 100644 index 0000000000000000000000000000000000000000..adc86c658575d893ceb0c03572e08b8819e0d85f --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/SystemEvent/index.js @@ -0,0 +1,6 @@ +import { SystemEventCreate } from './system.event.create'; +import { SystemEventList } from './system.event.list'; +import { SystemEventView } from './system.event.view'; +import { SystemEventEdit } from './system.event.edit'; + +export {SystemEventCreate, SystemEventList, SystemEventView, SystemEventEdit}; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/SystemEvent/system.event.create.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/SystemEvent/system.event.create.js new file mode 100644 index 0000000000000000000000000000000000000000..57d07246c5101e80599f6d1fffc78a3ba6412530 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/SystemEvent/system.event.create.js @@ -0,0 +1,680 @@ +import React, { Component } from 'react'; +import { Redirect } from 'react-router-dom'; +import _ from 'lodash'; +import { publish } from '../../App'; +import moment from 'moment'; +import { appGrowl } from '../../layout/components/AppGrowl'; +import { MultiSelect } from 'primereact/multiselect'; +import { Dropdown } from 'primereact/dropdown'; +import { InputText } from 'primereact/inputtext'; +import { InputTextarea } from 'primereact/inputtextarea'; +import { Button } from 'primereact/button'; +import { Dialog } from 'primereact/components/dialog/Dialog'; +import Flatpickr from "react-flatpickr"; +import AppLoader from '../../layout/components/AppLoader'; +import PageHeader from '../../layout/components/PageHeader'; +import UIConstants from '../../utils/ui.constants'; +import { CustomDialog } from '../../layout/components/CustomDialog'; + +import SystemEventService from '../../services/system.event.service'; +import TaskService from '../../services/task.service'; +import Jeditor from '../../components/JSONEditor/JEditor'; +import UtilService from '../../services/util.service'; + +import "flatpickr/dist/flatpickr.css"; + +/** + * Component to create a new System Event + */ +export class SystemEventCreate extends Component { + constructor(props) { + super(props); + this.state = { + showDialog: false, + isDirty: false, + isLoading: true, + redirect: null, + paramsSchema: null, // JSON Schema to be generated from strategy template to pass to JSON editor + dialog: { header: '', detail: '' }, // Dialog properties + touched: { + name: '', + }, + seAffectedHardwareTemplate: {}, + systemevent: { + name: '', + description: '', + affected_tasks: [], + tasksId: '', + issue_subtype: '', + issue_subtype: '', + jira_url: '', + notes: '', + severity: '', + start: null, + stop: null, + status: '', + affected_hardware_template: '', + affected_hardware_doc: '', + }, + errors: {}, // Validation Errors + validFields: {}, // For Validation + validForm: false, // To enable Save Button + validEditor: false + }; + this.regex = "/^[-,0-9 ]+$/"; + this.seAffectedHardwareTemplateList = null; + this.seAffectedTaskList = null; + this.seIssueTypeList = null; + this.seIssueSubTypeList = null; + this.seSeverityList = null; + this.seStatusList = null; + this.affectedHardwareTemplates = []; + + // Validateion Rules + this.formRules = { + name: { required: true, message: "Name can not be empty" }, + issue_type: {required: true, message: "Issue Type can not be empty"}, + issue_subtype: { required: true, message: "Issue SubtType can not be empty" }, + severity: {required: true, message: "Severity can not be empty"}, + jira_url: {required: true, message: "Jira URL can not be empty"}, + notes: {required: true, message: "Notes can not be empty"}, + status: {required: true, message: "Status can not be empty"}, + start: { required: true, message: "Start Time can not be empty" }, + }; + this.tooltipOptions = UIConstants.tooltipOptions; + this.onTemplateChanged = this.onTemplateChanged.bind(this); + this.setEditorOutput = this.setEditorOutput.bind(this); + this.saveSystemEvent = this.saveSystemEvent.bind(this); + this.reset = this.reset.bind(this); + this.cancelCreate = this.cancelCreate.bind(this); + this.checkIsDirty = this.checkIsDirty.bind(this); + this.close = this.close.bind(this); + this.initSystemEvent = this.initSystemEvent.bind(this); + this.setEditorFunction = this.setEditorFunction.bind(this); + } + + async componentDidMount() { + await this.initSystemEvent(); + } + + /** + * Initialize the system event details + */ + async initSystemEvent() { + const promises = [ + SystemEventService.getSEAffectedHardwareTemplates(), + TaskService.getTaskBlueprintList(), + SystemEventService.getSEIssueTypes(), + SystemEventService.getSEIssueSubTypes(), + SystemEventService.getSESeverity(), + SystemEventService.getSEStatus(), + UtilService.getUTC(), + ]; + Promise.all(promises).then(responses => { + this.seAffectedHardwareTemplateList = responses[0]; + this.seAffectedTaskList = responses[1]; + this.seIssueTypeList = responses[2]; + this.seIssueSubTypeList = responses[3]; + this.seSeverityList = responses[4]; + this.seStatusList = responses[5]; + let systemTime = moment.utc(responses[6]); + let seAffectedHardwareTemplate = this.seAffectedHardwareTemplateList.length>0?this.seAffectedHardwareTemplateList[0]:null; + let schema = { + properties: {} + }; + if (seAffectedHardwareTemplate) { + schema = seAffectedHardwareTemplate.schema; + } + this.setState({ + paramsSchema: _.cloneDeep(schema), + isLoading: false, + seAffectedHardwareTemplate: seAffectedHardwareTemplate, + systemTime: systemTime + }); + }); + } + + /** + * + * @param {Id} templateId - id value of affected hardware template + */ + async onTemplateChanged(templateId) { + this.setState({ isLoading: true }); + const changedTemplate = _.find(this.seAffectedHardwareTemplateList, { 'id': templateId }); + this.setState({ + isLoading: false, + seAffectedHardwareTemplate: changedTemplate, + seAffectedHardwareTemplate: changedTemplate, + paramsSchema: _.cloneDeep(changedTemplate.schema), + isDirty: true + }); + if (this.state.editorFunction) { + if (this.state.seAffectedHardwareTemplate !== changedTemplate) { + this.state.editorFunction(); + } + } + } + + /** + * Function to set form values to the system event object + * @param {string} key + * @param {object} value + */ + setSystemEventParams(key, value) { + let systemevent = _.cloneDeep(this.state.systemevent); + systemevent[key] = value; + if (!this.state.isDirty && !_.isEqual(this.state.systemevent, systemevent)) { + this.setState({ + systemevent: systemevent, validForm: this.validateForm(key), validEditor: this.validateEditor(), touched: { + ...this.state.touched, + [key]: true + }, isDirty: true + }); + publish('edit-dirty', true); + } else { + this.setState({ + systemevent: systemevent, validForm: this.validateForm(key), validEditor: this.validateEditor(), touched: { + ...this.state.touched, + [key]: true + } + }); + } + } + + /** + * This function is mainly added for Unit Tests. If this function is removed Unit Tests will fail. + */ + validateEditor() { + return this.validEditor; + } + + /** + * Function to call on change and blur events from input components + * @param {string} key + * @param {any} value + */ + setParams(key, value, type) { + let systemevent = this.state.systemevent; + switch (type) { + case 'NUMBER': { + systemevent[key] = value ? parseInt(value) : 0; + break; + } + default: { + systemevent[key] = value; + break; + } + } + this.setState({ systemevent: systemevent, validForm: this.validateForm(key), isDirty: true }); + publish('edit-dirty', true); + } + + /** + * 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.systemevent[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.systemevent[fieldName]; + if (rule.required) { + if (!fieldValue) { + errors[fieldName] = rule.message ? rule.message : `${fieldName} is required`; + } else { + validFields[fieldName] = true; + } + } + } + } + this.setState({ errors: errors, validFields: validFields }); + if (Object.keys(validFields).length === Object.keys(this.formRules).length) { + validForm = true; + delete errors['start']; + delete errors['stop']; + } + if (!this.validateDates(this.state.systemevent.start, this.state.systemevent.stop)) { + validForm = false; + if (!fieldName || fieldName === 'start') { + errors['start'] = "Start Time cannot be same or after End Time"; + delete errors['stop']; + } + if (!fieldName || fieldName === 'stop') { + errors['stop'] = "End Time cannot be same or before Start Time"; + delete errors['start']; + } + this.setState({ errors: errors }); + } + + return validForm; + } + + /** + * Function to validate if stop is always later than start if exists. + * @param {Date} start + * @param {Date} stop + * @returns boolean + */ + validateDates(start, stop) { + if (start && stop && moment(stop).isSameOrBefore(moment(start))) { + return false; + } + return true; + } + + /** + * Function used to set property values + * @param {*} jsonOutput + * @param {*} errors + */ + setEditorOutput(jsonOutput, errors) { + this.paramsOutput = jsonOutput; + this.validEditor = errors.length === 0; + if (!this.state.isDirty && this.state.paramsOutput && !_.isEqual(this.state.paramsOutput, jsonOutput)) { + this.setState({ + paramsOutput: jsonOutput, + validEditor: errors.length === 0, + validForm: this.validateForm(), + isDirty: true + }); + publish('edit-dirty', true); + } else { + this.setState({ + paramsOutput: jsonOutput, + validEditor: errors.length === 0, + validForm: this.validateForm() + }); + } + } + + /** + * Create new system event + */ + async saveSystemEvent() { + let systemevent = this.state.systemevent; + systemevent['start'] = moment(systemevent['start']).format(UIConstants.CALENDAR_DATETIME_FORMAT); + systemevent['stop'] = systemevent['stop'] ? moment(systemevent['stop_time']).format(UIConstants.CALENDAR_DATETIME_FORMAT) : null; + systemevent['affected_hardware_template'] = this.state.seAffectedHardwareTemplate.url; + systemevent['affected_hardware_doc'] = this.paramsOutput; + const taskIds = _.split(systemevent.tasksId, ','); + let affected_tasks = []; + for (const id of taskIds) { + if (id !== '') { + affected_tasks.push(`/api/task_blueprint/${id}`); + } + } + systemevent['affected_tasks'] = affected_tasks; + systemevent = await SystemEventService.saveSystemEvent(systemevent); + if (systemevent && systemevent.id) { + const dialog = { header: 'Success', detail: 'System Event is created successfully. Do you want to create another System Event?' }; + this.setState({ dialogVisible: true, dialog: dialog, paramsOutput: {}, showDialog: false, isDirty: false }) + publish('edit-dirty', false); + } else { + appGrowl.show({severity: 'error', summary: 'Error', detail: 'Unable to add new System Event'}); + } + } + + /** + * Reset function to be called when user wants to create new system event + */ + reset() { + let tmpSystemevent = { + name: '', + description: '', + affected_tasks: [], + tasksId: '', + issue_subtype: '', + issue_subtype: '', + jira_url: '', + notes: '', + severity: '', + start: null, + stop: null, + status: '', + affected_hardware_template: '', + affected_hardware_doc: '' + } + this.setState({ + dialogVisible: false, + dialog: { header: '', detail: '' }, + errors: [], + systemevent: tmpSystemevent, + paramsSchema: null, + paramsOutput: null, + validEditor: false, + validFields: {}, + touched: false, + stationGroup: [], + showDialog: false, + isDirty: false + }); + this.initSystemEvent(); + } + + /** + * Cancel System Event creation and redirect + */ + cancelCreate() { + publish('edit-dirty', false); + this.props.history.push(`/systemevent/list`); + this.setState({ showDialog: false }); + } + + /** + * 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 }); + } + + /** + * 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 }); + } + + render() { + if (this.state.redirect) { + return <Redirect to={{ pathname: this.state.redirect }}></Redirect> + } + const schema = this.state.paramsSchema; + let jeditor = null; + if (schema) { + if (this.state.systemevent.affected_hardware_doc) { + delete this.state.systemevent.affected_hardware_doc.$id; + delete this.state.systemevent.affected_hardware_doc.$schema; + } + jeditor = React.createElement(Jeditor, { + title: "System Event Parameters", + schema: schema, + initValue: this.state.paramsOutput, + callback: this.setEditorOutput, + parentFunction: this.setEditorFunction + }); + } + return ( + <React.Fragment> + <PageHeader location={this.props.location} title={'System Event - Add'} + actions={[{ + icon: 'fa-window-close', title: 'Click to close System Event 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="systemEventName" 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"> + <InputText className={(this.state.errors.name && this.state.touched.name) ? 'input-error' : ''} id="systemEventName" data-testid="name" + tooltip="Enter name of the System Event Name" tooltipOptions={this.tooltipOptions} maxLength="128" + ref={input => { this.nameInput = input; }} + value={this.state.systemevent.name} autoFocus + onChange={(e) => this.setSystemEventParams('name', e.target.value)} + onBlur={(e) => this.setSystemEventParams('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</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 System Event" + tooltipOptions={this.tooltipOptions} + maxLength="128" + data-testid="description" + value={this.state.systemevent.description} + onChange={(e) => this.setSystemEventParams('description', e.target.value)} + onBlur={(e) => this.setSystemEventParams('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> + <div className="p-field p-grid"> + <label htmlFor="projCat" className="col-lg-2 col-md-2 col-sm-12">Issue Type <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12 systemEvent" data-testid="projCat" > + <Dropdown inputId="projCat" optionLabel="value" optionValue="url" + tooltip="Issue Type" tooltipOptions={this.tooltipOptions} + value={this.state.systemevent.issue_type} + options={this.seIssueTypeList} + onChange={(e) => {this.setSystemEventParams('issue_type', e.value)}} + placeholder="Select Issue Type" /> + </div> + <div className="col-lg-1 col-md-1 col-sm-12"></div> + <label htmlFor="periodCategory" className="col-lg-2 col-md-2 col-sm-12">Issue Subtype <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12 systemEvent"> + <Dropdown data-testid="period-cat" id="period-cat" optionLabel="value" optionValue="url" + tooltip="Issue Subtype" tooltipOptions={this.tooltipOptions} + value={this.state.systemevent.issue_subtype} + options={this.seIssueSubTypeList} + onChange={(e) => {this.setSystemEventParams('issue_subtype',e.value)}} + placeholder="Select Issue Subtype" /> + </div> + </div> + <div className="p-field p-grid"> + <label htmlFor="systemEventName" className="col-lg-2 col-md-2 col-sm-12">Jira URL <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <InputText className={(this.state.errors.jira_url && this.state.touched.jira_url) ? 'input-error' : ''} id="systemEventName" data-testid="name" + tooltip="Enter Jira URL" tooltipOptions={this.tooltipOptions} maxLength="255" + ref={input => { this.nameInput = input; }} + value={this.state.systemevent.jira_url} + onChange={(e) => this.setSystemEventParams('jira_url', e.target.value)} + onBlur={(e) => this.setSystemEventParams('jira_url', e.target.value)} + /> + <label className={(this.state.errors.jira_url && this.state.touched.jira_url) ? "error" : "info"}> + {this.state.errors.jira_url && this.state.touched.jira_url ? this.state.errors.jira_url : "Max 255 characters"} + </label> + </div> + <div className="col-lg-1 col-md-1 col-sm-12"></div> + <label htmlFor="notes" className="col-lg-2 col-md-2 col-sm-12">Notes <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <InputTextarea className={(this.state.errors.notes && this.state.touched.notes) ? 'input-error' : ''} rows={3} cols={30} + tooltip="Notes of the System Event" + tooltipOptions={this.tooltipOptions} + maxLength="255" + data-testid="notes" + value={this.state.systemevent.notes} + onChange={(e) => this.setSystemEventParams('notes', e.target.value)} + onBlur={(e) => this.setSystemEventParams('notes', e.target.value)} + /> + <label className={(this.state.errors.notes && this.state.touched.notes) ? "error" : "info"}> + {(this.state.errors.notes && this.state.touched.notes) ? this.state.errors.notes : "Max 255 characters"} + </label> + </div> + </div> + <div className="p-field p-grid"> + <label htmlFor="severity" className="col-lg-2 col-md-2 col-sm-12">Severity <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12 systemEvent"> + <Dropdown data-testid="severity" id="severity" optionLabel="value" optionValue="url" + tooltip="Status" tooltipOptions={this.tooltipOptions} + value={this.state.systemevent.severity} + options={this.seSeverityList} + onChange={(e) => {this.setSystemEventParams('severity',e.value)}} + placeholder="Select System Event Severity" /> + </div> + <div className="col-lg-1 col-md-1 col-sm-12"></div> + <label htmlFor="seat" className="col-lg-2 col-md-2 col-sm-12">Affected Tasks</label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <InputText keyfilter={/[\d\,]/} validateOnly={false} placeholder="Enter task id, for more id use ',' seperater" + onChange={(e) => this.setSystemEventParams('tasksId', e.target.value)} + onBlur={(e) => this.setSystemEventParams('tasksId', e.target.value)} + value={this.state.systemevent.tasksId}/> + <label className={(this.state.errors.jira_url && this.state.touched.jira_url) ? "error" : "info"}> + {this.state.errors.jira_url && this.state.touched.jira_url ? this.state.errors.jira_url : ""} + </label> + </div> + </div> + <div className="p-field p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Start Time <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <Flatpickr data-enable-time data-input + options={{ + "inlineHideInput": true, + "wrap": true, + "enableSeconds": true, + "time_24hr": true, + "minuteIncrement": 1, + "allowInput": true, + "defaultDate": this.state.systemTime.format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT), + "defaultHour": this.state.systemTime.hours(), + "defaultMinute": this.state.systemTime.minutes() + }} + title="Start of this system event" + value={this.state.systemevent.start} + onChange={value => { + this.setParams('start', value[0] ? value[0] : this.state.systemevent.start); + this.setSystemEventParams('start', value[0] ? value[0] : this.state.systemevent.start) + }} > + <input type="text" data-input className={`p-inputtext p-component ${this.state.errors.start && this.state.touched.start ? 'input-error' : ''}`} /> + <i className="fa fa-calendar" data-toggle style={{ position: "absolute", marginLeft: '-25px', marginTop: '5px', cursor: 'pointer' }} ></i> + <i className="fa fa-times" style={{ position: "absolute", marginLeft: '-50px', marginTop: '5px', cursor: 'pointer' }} + onClick={e => { this.setParams('start', ''); this.setSystemEventParams('start', '') }}></i> + </Flatpickr> + <label className={this.state.errors.start && this.state.touched.start ? "error" : "info"}> + {this.state.errors.start && this.state.touched.start ? this.state.errors.start : ""} + </label> + </div> + <div className="col-lg-1 col-md-1 col-sm-12"></div> + <label className="col-lg-2 col-md-2 col-sm-12">End Time</label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <Flatpickr data-enable-time data-input + options={{ + "inlineHideInput": true, + "wrap": true, + "enableSeconds": true, + "time_24hr": true, + "minuteIncrement": 1, + "allowInput": true, + "minDate": this.state.systemevent.start ? this.state.systemevent.start.toDate : '', + "defaultDate": this.state.systemTime.format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT), + "defaultHour": this.state.systemTime.hours(), + "defaultMinute": this.state.systemTime.minutes() + }} + title="End of this system event." + value={this.state.systemevent.stop} + onChange={value => { + this.setParams('stop', value[0] ? value[0] : this.state.systemevent.stop); + this.setSystemEventParams('stop', value[0] ? value[0] : this.state.systemevent.stop) + }} > + <input type="text" data-input className={`p-inputtext p-component ${this.state.errors.stop && this.state.touched.stop ? 'input-error' : ''}`} /> + <i className="fa fa-calendar" data-toggle style={{ position: "absolute", marginLeft: '-25px', marginTop: '5px', cursor: 'pointer' }} ></i> + <i className="fa fa-times" style={{ position: "absolute", marginLeft: '-50px', marginTop: '5px', cursor: 'pointer' }} + onClick={e => { this.setParams('stop', ''); this.setSystemEventParams('stop', '') }}></i> + </Flatpickr> + <label className={this.state.errors.stop && this.state.touched.stop ? "error" : "info"}> + {this.state.errors.stop && this.state.touched.stop ? this.state.errors.stop : ""} + </label> + </div> + </div> + <div className="p-field p-grid"> + <label htmlFor="strategy" className="col-lg-2 col-md-2 col-sm-12">Affected Hardware Template <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12 systemEvent" data-testid="strategy" > + <Dropdown inputId="strategy" optionLabel="name" optionValue="id" + tooltip="Choose Affected Hardware Template to create System Event" tooltipOptions={this.tooltipOptions} + value={this.state.seAffectedHardwareTemplate.id} + options={this.seAffectedHardwareTemplateList} + onChange={(e) => { this.onTemplateChanged(e.value) }} + placeholder="Select Affected Hardware" /> + <label className={(this.state.errors.seAffectedHardwareTemplate && this.state.touched.seAffectedHardwareTemplate) ? "error" : "info"}> + {(this.state.errors.seAffectedHardwareTemplate && this.state.touched.seAffectedHardwareTemplate) ? this.state.errors.seAffectedHardwareTemplate : "Select System Event Affected Hardware Template"} + </label> + </div> + <div className="col-lg-1 col-md-1 col-sm-12"></div> + <label htmlFor="status" className="col-lg-2 col-md-2 col-sm-12">Status <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12 systemEvent"> + <Dropdown data-testid="status" id="status" optionLabel="value" optionValue="url" + tooltip="Status" tooltipOptions={this.tooltipOptions} + value={this.state.systemevent.status} + options={this.seStatusList} + onChange={(e) => {this.setSystemEventParams('status',e.value)}} + placeholder="Select System Event Status" /> + </div> + </div> + + <div className="p-grid"> + <fieldset className="border-style"> + <div className="p-col-12"> + {this.state.paramsSchema ? jeditor : ""} + </div> + </fieldset> + </div> + </div> + + + <div className="p-grid p-justify-start"> + <div className="p-col-1"> + <Button label="Save" className="p-button-primary" icon="pi pi-check" onClick={this.saveSystemEvent} + disabled={!this.state.validEditor || !this.state.validForm} data-testid="save-btn" /> + </div> + <div className="p-col-1"> + <Button label="Cancel" className="p-button-danger" icon="pi pi-times" onClick={this.checkIsDirty} /> + </div> + </div> + </div> + </> + } + + {/* Dialog component to show messages and get input */} + <div className="p-grid" data-testid="confirm_dialog"> + <Dialog header={this.state.dialog.header} visible={this.state.dialogVisible} style={{ width: '25vw' }} inputId="confirm_dialog" + modal={true} onHide={() => { this.setState({ dialogVisible: false }) }} + footer={<div> + <Button key="back" onClick={() => { this.setState({ dialogVisible: false, redirect: `/systemevent/list` }); }} label="No" /> + <Button key="submit" type="primary" onClick={this.reset} label="Yes" /> + </div> + } > + <div className="p-grid"> + <div className="col-lg-2 col-md-2 col-sm-2" style={{ margin: 'auto' }}> + <i className="pi pi-check-circle pi-large pi-success"></i> + </div> + <div className="col-lg-10 col-md-10 col-sm-10"> + {this.state.dialog.detail} + </div> + </div> + </Dialog> + + <CustomDialog type="confirmation" visible={this.state.showDialog} width="40vw" + header={'Add System Event'} message={'Do you want to leave this page? Your changes may not be saved.'} + content={''} onClose={this.close} onCancel={this.close} onSubmit={this.cancelCreate}> + </CustomDialog> + </div> + </React.Fragment> + ); + } +} + +export default SystemEventCreate; \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/SystemEvent/system.event.edit.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/SystemEvent/system.event.edit.js new file mode 100644 index 0000000000000000000000000000000000000000..88420a7b6f5c3929a96f622e12ea85ccae39abe9 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/SystemEvent/system.event.edit.js @@ -0,0 +1,581 @@ +import React, { Component } from 'react'; +import { Redirect } from 'react-router-dom' + +import { Button } from 'primereact/button'; +import { Dropdown } from 'primereact/dropdown'; +import { InputText } from 'primereact/inputtext'; +import { InputTextarea } from 'primereact/inputtextarea'; +import moment from 'moment'; +import _ from 'lodash'; +import Flatpickr from "react-flatpickr"; + +import { publish } from '../../App'; +import { CustomDialog } from '../../layout/components/CustomDialog'; +import { appGrowl } from '../../layout/components/AppGrowl'; +import AppLoader from '../../layout/components/AppLoader'; +import PageHeader from '../../layout/components/PageHeader'; +import Jeditor from '../../components/JSONEditor/JEditor'; +import UIConstants from '../../utils/ui.constants'; +import TaskService from '../../services/task.service'; +import SystemEventService from '../../services/system.event.service'; +import UtilService from '../../services/util.service'; + +export class SystemEventEdit extends Component { + constructor(props) { + super(props); + this.state = { + isLoading: true, + isDirty: false, + errors: {}, // Validation Errors + validFields: {}, // For Validation + validForm: false, // To enable Save Button + validEditor: false, + }; + this.seAffectedHardwareTemplateList = []; + this.seAffectedTaskList = []; + this.seIssueTypeList = []; + this.seIssueSubTypeList = []; + this.seSeverityList = []; + this.seStatusList = []; + this.affectedHardwareTemplates = []; + this.systemeventTemplate = []; + + this.tooltipOptions = UIConstants.tooltipOptions; + this.setEditorOutput = this.setEditorOutput.bind(this); + this.setEditorFunction = this.setEditorFunction.bind(this); + this.checkIsDirty = this.checkIsDirty.bind(this); + this.updateSystemEvent = this.updateSystemEvent.bind(this); + this.close = this.close.bind(this); + this.cancelEdit = this.cancelEdit.bind(this); + + // Validateion Rules + this.formRules = { + name: { required: true, message: "Name can not be empty" }, + issue_type: {required: true, message: "Issue Type can not be empty"}, + issue_subtype: { required: true, message: "Issue SubtType can not be empty" }, + severity: {required: true, message: "Severity can not be empty"}, + jira_url: {required: true, message: "Jira URL can not be empty"}, + notes: {required: true, message: "Notes can not be empty"}, + status: {required: true, message: "Status can not be empty"}, + start: { required: true, message: "Start Time can not be empty" }, + }; + } + + componentDidMount() { + const promises = [ + SystemEventService.getSEAffectedHardwareTemplates(), + TaskService.getTaskBlueprintList(), + SystemEventService.getSEIssueTypes(), + SystemEventService.getSEIssueSubTypes(), + SystemEventService.getSESeverity(), + SystemEventService.getSEStatus(), + UtilService.getUTC(), + ]; + Promise.all(promises).then(responses => { + this.seAffectedHardwareTemplateList = responses[0]; + this.seAffectedTaskList = responses[1]; + this.seIssueTypeList = responses[2]; + this.seIssueSubTypeList = responses[3]; + this.seSeverityList = responses[4]; + this.seStatusList = responses[5]; + let systemTime = moment.utc(responses[6]); + this.setState({ + systemTime: systemTime + }); + }); + // Get system event details + this.getSystemEventDetails(); + } + + /** + * 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 }); + } + + /** + * To get the system event details from the backend using the service + * + */ + getSystemEventDetails() { + const systemeventId = this.props.match ? this.props.match.params.id : null; + if (systemeventId) { + SystemEventService.getSystemEvent(systemeventId) + .then((systemevent) => { + if (systemevent) { + SystemEventService.getSystemEventTemplate(systemevent.affected_hardware_template_id) + .then((systemeventTemplate) => { + systemevent['tasksId'] = systemevent.affected_tasks_ids.length>0?systemevent.affected_tasks_ids.join():''; + this.setState({ + redirect: null, + systemevent: systemevent, + isLoading: false, + systemeventTemplate: systemeventTemplate, + paramsSchema: systemeventTemplate.schema, + }); + }); + } else { + this.setState({redirect: "/not-found"}); + } + }); + } else { + this.setState({redirect: "/not-found"}); + } + } + + close() { + this.setState({ showDialog: false }); + } + + /** + * Cancel edit and redirect to system event View page + */ + cancelEdit() { + publish('edit-dirty', false); + this.setState({ showDialog: false }); + this.props.history.push(`/systemevent/view/${this.props.match.params.id}`); + } + + /** + * warn before cancel this page if any changes detected + */ + checkIsDirty() { + if (this.state.isDirty) { + this.setState({ showDialog: true }); + } else { + this.cancelEdit(); + } + } + + /** + * 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.systemevent[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.systemevent[fieldName]; + if (rule.required) { + if (!fieldValue) { + errors[fieldName] = rule.message ? rule.message : `${fieldName} is required`; + } else { + validFields[fieldName] = true; + } + } + } + } + this.setState({ errors: errors, validFields: validFields }); + if (Object.keys(validFields).length === Object.keys(this.formRules).length) { + validForm = true; + delete errors['start']; + delete errors['stop']; + } + if (!this.validateDates(this.state.systemevent.start, this.state.systemevent.stop)) { + validForm = false; + if (!fieldName || fieldName === 'start') { + errors['start'] = "Start Time cannot be same or after End Time"; + delete errors['stop']; + } + if (!fieldName || fieldName === 'stop') { + errors['stop'] = "End Time cannot be same or before Start Time"; + delete errors['start']; + } + this.setState({ errors: errors }); + } + + return validForm; + } + + /** + * Function to validate if stop is always later than start if exists. + * @param {Date} start + * @param {Date} stop + * @returns boolean + */ + validateDates(start, stop) { + if (start && stop && moment(stop).isSameOrBefore(moment(start))) { + return false; + } + return true; + } + + /** + * This function is mainly added for Unit Tests. If this function is removed Unit Tests will fail. + */ + validateEditor() { + return this.validEditor; + } + + /** + * Function to call on change and blur events from input components + * @param {string} key + * @param {any} value + */ + setParams(key, value, type) { + let systemevent = this.state.systemevent; + switch (type) { + case 'NUMBER': { + systemevent[key] = value ? parseInt(value) : 0; + break; + } + default: { + systemevent[key] = value; + break; + } + } + this.setState({ systemevent: systemevent, validForm: this.validateForm(key), isDirty: true }); + publish('edit-dirty', true); + } + + /** + * Set JEditor output + * @param {*} jsonOutput + * @param {*} errors + */ + setEditorOutput(jsonOutput, errors) { + this.paramsOutput = jsonOutput; + this.validEditor = errors.length === 0; + if (!this.state.isDirty && this.state.paramsOutput && !_.isEqual(this.state.paramsOutput, jsonOutput)) { + this.setState({ + paramsOutput: jsonOutput, + validEditor: errors.length === 0, + validForm: this.validateForm(), + isDirty: true + }); + publish('edit-dirty', true); + } else { + this.setState({ + paramsOutput: jsonOutput, + validEditor: errors.length === 0, + validForm: this.validateForm() + }); + } + } + + /** + * Function to set form values to the system svent object + * @param {string} key + * @param {object} value + */ + setSystemEventParams(key, value) { + let systemevent = _.cloneDeep(this.state.systemevent); + systemevent[key] = value; + if (!this.state.isDirty && !_.isEqual(this.state.systemevent, systemevent)) { + this.setState({ + systemevent: systemevent, validForm: this.validateForm(key), validEditor: this.validateEditor(), touched: { + ...this.state.touched, + [key]: true + }, isDirty: true + }); + publish('edit-dirty', true); + } else { + this.setState({ + systemevent: systemevent, validForm: this.validateForm(key), validEditor: this.validateEditor(), touched: { + ...this.state.touched, + [key]: true + } + }); + } + } + + /** + * Update system event + */ + async updateSystemEvent() { + let systemevent = this.state.systemevent; + systemevent['start'] = moment(systemevent['start']).format(UIConstants.CALENDAR_DATETIME_FORMAT); + systemevent['stop'] = (systemevent['stop'] && systemevent['stop'] !== 'Invalid date') ? moment(systemevent['stop']).format(UIConstants.CALENDAR_DATETIME_FORMAT) : null; + systemevent['affected_hardware_doc'] = this.paramsOutput; + const taskIds = _.split(systemevent.tasksId, ','); + let affected_tasks = []; + for (const id of taskIds) { + if (id !== '') { + affected_tasks.push(`/api/task_blueprint/${id}`); + } + } + systemevent['affected_tasks'] = affected_tasks; + systemevent = await SystemEventService.updateSystemEvent(systemevent); + if (systemevent && systemevent.id) { + appGrowl.show({ severity: 'success', summary: 'Success', detail: 'System Event updated successfully.' }); + this.props.history.push({ + pathname: `/systemevent/view/${this.props.match.params.id}`, + }); + publish('edit-dirty', false); + } else { + appGrowl.show({ severity: 'error', summary: 'Error Occured', detail: 'Unable to update System Event', showDialog: false, isDirty: false }); + } + } + + render() { + if (this.state.redirect) { + return <Redirect to={{ pathname: this.state.redirect }}></Redirect> + } + let jeditor = null; + if (this.state.systemeventTemplate) { + if (this.state.systemevent.affected_hardware_doc.$id) { + delete this.state.systemevent.affected_hardware_doc.$id; + delete this.state.systemevent.affected_hardware_doc.$schema; + } + jeditor = React.createElement(Jeditor, { + title: "System Event Parameters", + schema: this.state.systemeventTemplate.schema, + initValue: this.state.systemevent.affected_hardware_doc, + disabled: false, + callback: this.setEditorOutput, + parentFunction: this.setEditorFunction + }); + } + + return ( + <React.Fragment> + <PageHeader location={this.props.location} title={'System Event - Edit'} actions={[{ + icon: 'fa-window-close', + title: 'Click to Close System Event - Edit', type: 'button', actOn: 'click', props: { callback: this.checkIsDirty } + }]} /> + + { this.state.isLoading ? <AppLoader /> : this.state.systemevent && + <React.Fragment> + <div> + <div className="p-fluid"> + <div className="p-field p-grid"> + <label htmlFor="systemeventname" 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"> + <InputText className={(this.state.errors.name && this.state.touched.name) ? 'input-error' : ''} id="systemeventname" data-testid="name" + tooltip="Enter name of the System Event" tooltipOptions={this.tooltipOptions} maxLength="128" + ref={input => { this.nameInput = input; }} + value={this.state.systemevent.name} autoFocus + onChange={(e) => this.setSystemEventParams('name', e.target.value)} + onBlur={(e) => this.setSystemEventParams('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 System Event" + tooltipOptions={this.tooltipOptions} + maxLength="128" + data-testid="description" + value={this.state.systemevent.description} + onChange={(e) => this.setSystemEventParams('description', e.target.value)} + onBlur={(e) => this.setSystemEventParams('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> + + <div className="p-field p-grid"> + <label htmlFor="projCat" className="col-lg-2 col-md-2 col-sm-12">Issue Type <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12 systemEvent" data-testid="projCat" > + <Dropdown inputId="projCat" optionLabel="value" optionValue="url" + tooltip="Issue Type" tooltipOptions={this.tooltipOptions} + value={this.state.systemevent.issue_type} + options={this.seIssueTypeList} + onChange={(e) => {this.setSystemEventParams('issue_type', e.value)}} + placeholder="Select Issue Type" /> + </div> + <div className="col-lg-1 col-md-1 col-sm-12"></div> + <label htmlFor="periodCategory" className="col-lg-2 col-md-2 col-sm-12">Issue Subtype <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12 systemEvent"> + <Dropdown data-testid="period-cat" id="period-cat" optionLabel="value" optionValue="url" + tooltip="Issue Subtype" tooltipOptions={this.tooltipOptions} + value={this.state.systemevent.issue_subtype} + options={this.seIssueSubTypeList} + onChange={(e) => {this.setSystemEventParams('issue_subtype',e.value)}} + placeholder="Select Issue Subtype" /> + </div> + </div> + <div className="p-field p-grid"> + <label htmlFor="systemEventName" className="col-lg-2 col-md-2 col-sm-12">Jira URL <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <InputText className={(this.state.errors.jira_url && this.state.touched.jira_url) ? 'input-error' : ''} id="systemEventName" data-testid="name" + tooltip="Enter Jira URL" tooltipOptions={this.tooltipOptions} maxLength="255" + ref={input => { this.nameInput = input; }} + value={this.state.systemevent.jira_url} + onChange={(e) => this.setSystemEventParams('jira_url', e.target.value)} + onBlur={(e) => this.setSystemEventParams('jira_url', e.target.value)} + /> + <label className={(this.state.errors.jira_url && this.state.touched.jira_url) ? "error" : "info"}> + {this.state.errors.jira_url && this.state.touched.jira_url ? this.state.errors.jira_url : "Max 255 characters"} + </label> + </div> + <div className="col-lg-1 col-md-1 col-sm-12"></div> + <label htmlFor="notes" className="col-lg-2 col-md-2 col-sm-12">Notes <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <InputTextarea className={(this.state.errors.notes && this.state.touched.notes) ? 'input-error' : ''} rows={3} cols={30} + tooltip="Notes of the System Event" + tooltipOptions={this.tooltipOptions} + maxLength="255" + data-testid="notes" + value={this.state.systemevent.notes} + onChange={(e) => this.setSystemEventParams('notes', e.target.value)} + onBlur={(e) => this.setSystemEventParams('notes', e.target.value)} + /> + <label className={(this.state.errors.notes && this.state.touched.notes) ? "error" : "info"}> + {(this.state.errors.notes && this.state.touched.notes) ? this.state.errors.notes : "Max 255 characters"} + </label> + </div> + </div> + <div className="p-field p-grid"> + <label htmlFor="severity" className="col-lg-2 col-md-2 col-sm-12">Severity <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12 systemEvent"> + <Dropdown data-testid="severity" id="severity" optionLabel="value" optionValue="url" + tooltip="Status" tooltipOptions={this.tooltipOptions} + value={this.state.systemevent.severity} + options={this.seSeverityList} + onChange={(e) => {this.setSystemEventParams('severity',e.value)}} + placeholder="Select System Event Severity" /> + </div> + <div className="col-lg-1 col-md-1 col-sm-12"></div> + <label htmlFor="seat" className="col-lg-2 col-md-2 col-sm-12">Affected Tasks</label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <InputText keyfilter={/[\d\,]/} validateOnly={false} placeholder="Enter task id, for more id use ',' seperater" + onChange={(e) => this.setSystemEventParams('tasksId', e.target.value)} + onBlur={(e) => this.setSystemEventParams('tasksId', e.target.value)} + value={this.state.systemevent.tasksId}/> + <label className={(this.state.errors.jira_url && this.state.touched.jira_url) ? "error" : "info"}> + {this.state.errors.jira_url && this.state.touched.jira_url ? this.state.errors.jira_url : ""} + </label> + </div> + </div> + <div className="p-field p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Start Time <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <Flatpickr data-enable-time data-input + options={{ + "inlineHideInput": true, + "wrap": true, + "enableSeconds": true, + "time_24hr": true, + "minuteIncrement": 1, + "allowInput": true, + }} + title="Start of this system event" + value={this.state.systemevent.start} + onChange={value => { + this.setParams('start', value[0] ? value[0] : this.state.systemevent.start); + this.setSystemEventParams('start', value[0] ? value[0] : this.state.systemevent.start) + }} > + <input type="text" data-input className={`p-inputtext p-component ${this.state.errors.start && this.state.touched.start ? 'input-error' : ''}`} /> + <i className="fa fa-calendar" data-toggle style={{ position: "absolute", marginLeft: '-25px', marginTop: '5px', cursor: 'pointer' }} ></i> + <i className="fa fa-times" style={{ position: "absolute", marginLeft: '-50px', marginTop: '5px', cursor: 'pointer' }} + onClick={e => { this.setParams('start', ''); this.setSystemEventParams('start', '') }}></i> + </Flatpickr> + <label className={this.state.errors.start && this.state.touched.start ? "error" : "info"}> + {this.state.errors.start && this.state.touched.start ? this.state.errors.start : ""} + </label> + </div> + <div className="col-lg-1 col-md-1 col-sm-12"></div> + <label className="col-lg-2 col-md-2 col-sm-12">End Time</label> + <div className="col-lg-3 col-md-3 col-sm-12"> + <Flatpickr data-enable-time data-input + options={{ + "inlineHideInput": true, + "wrap": true, + "enableSeconds": true, + "time_24hr": true, + "minuteIncrement": 1, + "allowInput": true, + "minDate": this.state.systemevent.start ? this.state.systemevent.start.toDate : '', + }} + title="End of this system event." + value={this.state.systemevent.stop} + onChange={value => { + this.setParams('stop', value[0] ? value[0] : this.state.systemevent.stop); + this.setSystemEventParams('stop', value[0] ? value[0] : this.state.systemevent.stop) + }} > + <input type="text" data-input className={`p-inputtext p-component ${this.state.errors && this.state.errors.stop && this.state.touched && this.state.touched.stop ? 'input-error' : ''}`} /> + <i className="fa fa-calendar" data-toggle style={{ position: "absolute", marginLeft: '-25px', marginTop: '5px', cursor: 'pointer' }} ></i> + <i className="fa fa-times" style={{ position: "absolute", marginLeft: '-50px', marginTop: '5px', cursor: 'pointer' }} + onClick={e => { this.setParams('stop', ''); this.setSystemEventParams('stop', '') }}></i> + </Flatpickr> + <label className={this.state.errors && this.state.errors.stop && this.state.touched && this.state.touched.stop ? "error" : "info"}> + {this.state.errors && this.state.errors.stop && this.state.touched && this.state.touched.stop ? this.state.errors.stop : ""} + </label> + </div> + </div> + + <div className="p-field p-grid"> + <label htmlFor="strategy" className="col-lg-2 col-md-2 col-sm-12">Affected Hardware Template <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12 systemEvent" data-testid="strategy" > + <Dropdown inputId="strategy" optionLabel="name" optionValue="id" + tooltip="Choose Affected Hardware Template to create System Event" tooltipOptions={this.tooltipOptions} + value={this.state.systemevent.affected_hardware_template_id} + disabled={this.state.systemevent.affected_hardware_doc?true:false} + options={this.seAffectedHardwareTemplateList} + onChange={(e) => { this.onTemplateChanged(e.value) }} + placeholder="Select Affected Hardware" + /> + <label style={{display: this.state.systemevent.affected_hardware_doc?'none':''}} className={(this.state.errors.seAffectedHardwareTemplate && this.state.touched.seAffectedHardwareTemplate) ? "error" : "info"}> + {(this.state.errors.seAffectedHardwareTemplate && this.state.touched.seAffectedHardwareTemplate) ? this.state.errors.seAffectedHardwareTemplate : "Select System Event Affected Hardware Template"} + </label> + </div> + <div className="col-lg-1 col-md-1 col-sm-12"></div> + <label htmlFor="status" className="col-lg-2 col-md-2 col-sm-12">Status <span style={{ color: 'red' }}>*</span></label> + <div className="col-lg-3 col-md-3 col-sm-12 systemEvent"> + <Dropdown data-testid="status" id="status" optionLabel="value" optionValue="url" + tooltip="Status" tooltipOptions={this.tooltipOptions} + value={this.state.systemevent.status} + options={this.seStatusList} + onChange={(e) => {this.setSystemEventParams('status',e.value)}} + placeholder="Select System Event Status" /> + </div> + </div> + <div className="p-grid"> + <fieldset className="border-style"> + <div className="p-col-12"> + {this.state.paramsSchema ? jeditor : ""} + </div> + </fieldset> + </div> + </div> + + <div className="p-grid p-justify-start"> + <div className="p-col-1"> + <Button label="Save" className="p-button-primary" icon="pi pi-check" onClick={this.updateSystemEvent} + disabled={!this.state.validEditor || !this.state.validForm} data-testid="save-btn" /> + </div> + <div className="p-col-1"> + <Button label="Cancel" className="p-button-danger" icon="pi pi-times" onClick={this.checkIsDirty} /> + </div> + </div> + </div> + </React.Fragment> + } + + <CustomDialog type="confirmation" visible={this.state.showDialog} width="40vw" + header={'Edit System Event'} message={'Do you want to leave this page? Your changes may not be saved.'} + content={''} onClose={this.close} onCancel={this.close} onSubmit={this.cancelEdit}> + </CustomDialog> + </React.Fragment> + ); + } +} \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/SystemEvent/system.event.list.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/SystemEvent/system.event.list.js new file mode 100644 index 0000000000000000000000000000000000000000..018218e8d8c61240baa3560a502fe730a05d020c --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/SystemEvent/system.event.list.js @@ -0,0 +1,521 @@ +import React, { Component } from 'react'; +import _ from 'lodash'; +import moment from 'moment'; +import "flatpickr/dist/flatpickr.css"; +import { Link } from 'react-router-dom'; +import AppLoader from "../../layout/components/AppLoader"; +import ViewTable from '../../components/ViewTable'; +import PageHeader from '../../layout/components/PageHeader'; +import UIConstants from '../../utils/ui.constants'; +import UnitConverter from '../../utils/unit.converter'; +import SystemEventService from '../../services/system.event.service'; +import UtilService from '../../services/util.service'; +import AuthStore from '../../authenticate/auth.store'; +import AuthUtil from '../../utils/auth.util'; +import AccessDenied from '../../layout/components/AccessDenied'; + +export class SystemEventList extends Component{ + SYSTEM_EVENT_LIST_TABLE_NAME = 'system_event_list'; + lsKeySortColumn='systemeventSortData'; + constructor(props){ + super(props); + this.state = { + paths: [{ + "View": "/systemevent/view", + }], + systemEventList: null, + defaultcolumns: [{ + id: {name:"System Event Id"}, + created_at: { + name: "Created_At", + filter: "flatpickrDateRange", + format:UIConstants.CALENDAR_DATETIME_FORMAT + }, + name: {name:"Name"}, + description: {name:"Description"}, + start: { + name: "Start Time", + filter: "flatpickrDateRange", + format:UIConstants.CALENDAR_DATETIME_FORMAT + }, + stop: { + name: "End Time", + filter: "flatpickrDateRange", + format:UIConstants.CALENDAR_DATETIME_FORMAT + }, + issue_type_value: { + name:"Issue Type", + filter: "multiselect-filter" + }, + issue_subtype_value: { + name:"Issue Subtype", + filter: "multiselect-filter" + }, + severity_value: { + name: "Severity", + filter: "multiselect-filter" + }, + notes:{ + name: "Notes", + }, + status_value: { + name: "Status", + filter: "multiselect-filter" + }, + affected_hardware_template_id:{ + name:"Affected Hardware Template Id", + }, + affected_hardware_template_name:{ + name:"Template Name", + filter: "multiselect-filter" + }, + affected_hardware_doc_stations:{ + name:"Affected Hardware Stations", + filter:"multiselect" + }, + affected_tasks_ids: { + name:"Affected Task Id", + }, + jira_url: { + name: "Jira URL", + }, + actionpath: "actionpath" + }], + optionalcolumns: [{}], + columnclassname: [{}], + defaultSortColumn: [{id: "System Id", desc: false}], + columnclassname: [{ + "System Event Id":"filter-input-50", + "Affected Hardware Template Id":"filter-input-50", + }], + isLoading: true, + userrole: AuthStore.getState() + } + this.filterQry = ''; + this.orderBy = ''; + this.limit = 10; + this.offset = 0; + this.currentPageSize = 10; + this.systemEvents= []; + this.totalPage = 0; + this.pageUpdated = true; + + this.seAffectedHardwareTemplateList = []; + this.seAffectedHardwareTemplateNameList = []; + this.seAffectedTaskList = []; + this.seIssueTypeList = []; + this.seIssueSubTypeList = []; + this.seSeverityList = []; + this.seStatusList = []; + this.seHardwareStationsList = []; + this.fetchTableData = this.fetchTableData.bind(this); + this.getFilterOptions = this.getFilterOptions.bind(this); + this.closeList = this.closeList.bind(this); + } + + componentDidMount() { + this.getFilterColumns(); + } + + toggleBySorting(sortData) { + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: [{...sortData}] }); + } + + setToggleBySorting() { + let sortData = UtilService.localStore({ type: 'get', key: this.lsKeySortColumn }); + if(sortData){ + if(Object.prototype.toString.call(sortData) === '[object Array]'){ + this.defaultSortColumn = sortData; + } + else{ + this.defaultSortColumn = [{...sortData}]; + } + } + this.defaultSortColumn = this.defaultSortColumn || []; + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn, value: [...this.defaultSortColumn] }); + } + + /** + * Get server side filter column details form API + */ + async getFilterColumns() { + const apiFilters = await SystemEventService.getSystemEventFilterDefinition(); + this.columnMap = []; + let tmpDefaulColumns = _.cloneDeep(this.state.defaultcolumns[0]); + let tmpOptionalColumns = _.cloneDeep(this.state.optionalcolumns[0]); + let tmpColumnOrders = _.cloneDeep(this.state.columnOrders); + + if(apiFilters) { + this.getDropDownOptionList(apiFilters); + tmpDefaulColumns = this.getAPIFilter(apiFilters, tmpDefaulColumns); + tmpOptionalColumns = this.getAPIFilter(apiFilters, tmpOptionalColumns); + await this.setState({tmpDefaulcolumns: [tmpDefaulColumns], tmpOptionalcolumns:[tmpOptionalColumns], tmpColumnOrders: tmpColumnOrders, columnMap: this.columnMap}) + } + } + + /** + * Get Status list frol filter + * @param {Array} suFilters + */ + getDropDownOptionList(apiFilters) { + this.getOptionList('Affected Hardware Stations', apiFilters); + const promises = [ + SystemEventService.getSEAffectedHardwareTemplates(), + SystemEventService.getSEIssueTypes(), + SystemEventService.getSEIssueSubTypes(), + SystemEventService.getSESeverity(), + SystemEventService.getSEStatus(), + ]; + Promise.all(promises).then(responses => { + this.seAffectedHardwareTemplateList = responses[0]; + this.getOptionList('Template Name', this.seAffectedHardwareTemplateList); + this.getOptionList('Issue Type', responses[1]); + this.getOptionList('Issue Subtype', responses[2]); + this.getOptionList('Severity', responses[3]); + this.getOptionList('Status', responses[4]); + //this.getOptionList('Affected Hardware Stations', apiFilters); + this.setState({isLoading: false, systemEventList: []}); + }); + } + + getOptionList(type, listObj) { + if (type === 'Template Name') { + this.seAffectedHardwareTemplateNameList = []; + listObj.forEach(obj => { + const tmpVar = {name: _.startCase(obj.name), value: obj.name}; + this.seAffectedHardwareTemplateNameList.push(tmpVar); + }); + } else if (type === 'Issue Type') { + this.seIssueTypeList = this.getOptionsValues(listObj, this.seIssueTypeList); + } else if (type === 'Issue Subtype') { + this.seIssueSubTypeList = this.getOptionsValues(listObj, this.seIssueSubTypeList); + } else if (type === 'Severity') { + this.seSeverityList = this.getOptionsValues(listObj, this.seSeverityList); + } else if (type === 'Status') { + this.seStatusList = this.getOptionsValues(listObj, this.seStatusList); + } else if (type === 'Affected Hardware Stations') { + this.seHardwareStationsList = []; + listObj.data.filters.affected_hardware_doc_stations_any.choices.forEach(obj => { + const tmpVar = {name: _.startCase(obj.value), value: obj.value}; + this.seHardwareStationsList.push(tmpVar); + }); + } + } + + /** + * update the option list values + * @param {*} listObj + * @param {*} list + * @returns + */ + getOptionsValues(listObj, list) { + list = []; + listObj.forEach(obj => { + const tmpVar = {name: _.startCase(obj.value), value: obj.value}; + list.push(tmpVar); + }); + return list; + } + + /** + * Get Option-list values for Select Dropdown filter in 'Viewtable' + * @param {String} id : Column id + * @returns + */ + getFilterOptions(id) { + let options = null; + if(id && id === 'Status') { + options = this.seStatusList; + } else if (id && id === 'Issue Type') { + options = this.seIssueTypeList; + } else if (id === 'Issue Subtype') { + options = this.seIssueSubTypeList; + } else if (id === 'Severity') { + options = this.seSeverityList; + } else if (id === 'Template Name') { + options = this.seAffectedHardwareTemplateNameList; + } else if (id === 'Affected Hardware Stations') { + options = _.orderBy(this.seHardwareStationsList, ['name'], ['asc']); + } + return options; + } + + /** + * Prepare API FIlter column to view table component + * @param {*} apiFilters + * @param {*} columnDef + * @returns + */ + getAPIFilter(apiFilters, columnDef) { + const defaultColKeys = Object.keys(columnDef); + defaultColKeys.forEach(key => { + let tmpColMap = {}; + let tempKey = key; + tmpColMap['orgField'] = tempKey; + tmpColMap['tmpField'] = tempKey; + if(columnDef[key]) { + tmpColMap['displayName'] = columnDef[key]['name']; + } + this.columnMap.push(tmpColMap); + //Set Enable/Disable the Filter & SortBy in each column + if(apiFilters.data.filters[tempKey]) { + columnDef[key]['disableSortBy'] = !_.includes(apiFilters.data.ordering, tempKey); + columnDef[key]['disableFilters'] = false; + if( (tempKey !== 'start' && tempKey !== 'stop') && UIConstants.FILTER_MAP[apiFilters.data.filters[tempKey].type]) { + columnDef[key]['filter'] = UIConstants.FILTER_MAP[apiFilters.data.filters[tempKey].type]; + } + } else if (key === 'issue_subtype_value' && apiFilters.data.filters['issue_subtype']) { + columnDef[key]['disableSortBy'] = !_.includes(apiFilters.data.ordering, 'issue_subtype'); + columnDef[key]['disableFilters'] = false; + } else if (key === 'issue_type_value' && apiFilters.data.filters['issue_type']) { + columnDef[key]['disableSortBy'] = !_.includes(apiFilters.data.ordering, 'issue_type'); + columnDef[key]['disableFilters'] = false; + } else if (key === 'status_value' && apiFilters.data.filters['status']) { + columnDef[key]['disableSortBy'] = !_.includes(apiFilters.data.ordering, 'status'); + columnDef[key]['disableFilters'] = false; + } else if (key === 'affected_hardware_template_id' && apiFilters.data.filters['affected_hardware_template']) { + columnDef[key]['disableSortBy'] = !_.includes(apiFilters.data.ordering, 'affected_hardware_template'); + columnDef[key]['disableFilters'] = false; + } else if (key === 'severity_value' && apiFilters.data.filters['severity']) { + columnDef[key]['disableSortBy'] = !_.includes(apiFilters.data.ordering, 'severity'); + columnDef[key]['disableFilters'] = false; + } else if (key === 'affected_tasks_ids' && apiFilters.data.filters['affected_tasks']) { + columnDef[key]['disableSortBy'] = !_.includes(apiFilters.data.ordering, 'affected_tasks'); + columnDef[key]['disableFilters'] = false; + } else if (key === 'affected_hardware_doc_stations' + && (apiFilters.data.filters['affected_hardware_doc_stations_any'] || apiFilters.data.filters['affected_hardware_doc_stations_all'])) { + columnDef[key]['disableSortBy'] = apiFilters.data.filters['affected_hardware_doc_stations_any'] + ?!_.includes(apiFilters.data.ordering, 'affected_hardware_doc_stations_any') + : apiFilters.data.filters['affected_hardware_doc_stations_all'] + ? !_.includes(apiFilters.data.ordering, 'affected_hardware_doc_stations_all') + :true; + columnDef[key]['disableFilters'] = false; + } + }); + return columnDef; + } + + /** + * Fetch data from server side - while doing pagination, filtering, sorting + * @param {Table State} Table props state + * @returns + */ + async fetchTableData(state) { + this.filterQry = ''; + this.orderBy = ''; + this.pageUpdated = true; + this.setState({loadingStatus:true}); + let filters = UtilService.localStore({ type: 'get', key: this.SYSTEM_EVENT_LIST_TABLE_NAME}); + const sortByValue = UtilService.localStore({ type: 'get', key: this.lsKeySortColumn}); + if(filters.length > 0 ) { + for( const filter of filters) { + if (filter.id === 'Created_At') { + const values = filter.value; + if (values[0] && values[0] !== '') { + this.filterQry += 'created_at_after='+ moment(new Date(values[0])).format("YYYY-MM-DD HH:mm:SS&"); + } + if (values[1] && values[1] !== '') { + this.filterQry += 'created_at_before='+moment(new Date(values[1])).format("YYYY-MM-DD HH:mm:ss&"); + } + } else if (filter.id === 'Start Time') { + const values = filter.value; + if (values[0] && values[0] !== '') { + this.filterQry += 'start_after='+ moment(new Date(values[0])).format("YYYY-MM-DD HH:mm:SS&"); + } + if (values[1] && values[1] !== '') { + this.filterQry += 'start_before='+moment(new Date(values[1])).format("YYYY-MM-DD HH:mm:ss&"); + } + } else if (filter.id === 'End Time') { + const values = filter.value; + if (values[0] && values[0] !== '') { + this.filterQry += 'stop_after='+ moment(new Date(values[0])).format("YYYY-MM-DD 00:00:00&"); + } + if (values[1] && values[1] !== '') { + this.filterQry += 'stop_before='+moment(new Date(values[1])).format("YYYY-MM-DD 23:59:59&"); + } + } else if (filter.id === 'Affected Hardware Stations') { + const stationFilterType = _.find(filters, {id:'stationFilterType'}); + if(filter.value.length>0) { + const values = _.split(filter.value, ","); + for ( const value of values) { + if(stationFilterType && stationFilterType.value === 'All') { + this.filterQry += 'affected_hardware_doc_stations_all='+value+"&"; + } else { + this.filterQry += 'affected_hardware_doc_stations_any='+value+"&"; + } + } + } + } else if (filter.id === 'Status') { + const values = filter.value; + if (values[0] && values[0] !== '') { + for ( const value of values) { + this.filterQry += 'status='+value+'&'; + } + } + } else if (filter.id === 'Issue Type') { + const values = filter.value; + if (values[0] && values[0] !== '') { + for ( const value of values) { + this.filterQry += 'issue_type='+value+'&'; + } + } + } else if (filter.id === 'Issue Subtype') { + const values = filter.value; + if (values[0] && values[0] !== '') { + for ( const value of values) { + this.filterQry += 'issue_subtype='+value+'&'; + } + } + } else if (filter.id === 'Affected Hardware Template Id') { + if (filter.value && filter.value !== '') { + this.filterQry += 'affected_hardware_template='+filter.value+'&'; + } + } else if (filter.id === 'Severity') { + const values = filter.value; + if (values[0] && values[0] !== '') { + for ( const value of values) { + this.filterQry += 'severity='+value+'&'; + } + } + } else if (filter.id === 'Affected Task Id') { + const values = UnitConverter.getSubbandOutput(filter.value); + for ( const value of values) { + this.filterQry += 'affected_tasks='+value+'&'; + } + } else if (filter.id === 'System Event Id') { + const values = UnitConverter.getSubbandOutput(filter.value); + this.filterQry += 'id_min='+values[0]+'&'; + this.filterQry += 'id_max='+values[values.length-1]+'&'; + } else { + let columnDetails = _.find(this.state.columnMap, {displayName:filter.id}); + if(columnDetails) { + this.filterQry += columnDetails.orgField +'='+filter.value+'&' + } + } + } + } + + let sortBy = state && state.sortBy?state.sortBy[0]:(sortByValue)?sortByValue:null; + if (sortBy) { + this.defaultSortColumn = sortBy; + //this.setState({defaultSortColumn: [sortBy]}); + UtilService.localStore({ type: 'set', key: this.lsKeySortColumn ,value:sortBy}); + let columnDetails = _.find(this.state.columnMap, {displayName:sortBy.id}); + if(columnDetails) { + this.orderBy = 'ordering='+((sortBy.desc)?'-':'')+columnDetails.orgField; + } + } + this.filterQry = this.filterQry.substring(0,this.filterQry.length-1); + this.currentPageSize = (state && state.pageSize) ? state.pageSize : this.currentPageSize; + let offset = (state && state.pageIndex) ? state.pageIndex*this.currentPageSize : 0; + await this.getSystemEventList(this.filterQry, this.orderBy, this.currentPageSize, offset); + return [this.state.systemEventList, this.totalPage]; + } + + /** + * Get system events for given paramter + * @param {*} filterQry + * @param {*} orderBy + * @param {*} limit + * @param {*} offset + */ + async getSystemEventList(filterQry, orderBy, limit, offset) { + const promises = [ + AuthUtil.getUserRolePermission(), + SystemEventService.getSystemEventWithFilter(filterQry, orderBy, limit, offset), + ]; + this.systemEvents = []; + await Promise.all(promises).then(responses => { + let systemEvent = {}; + this.setState({userrole: responses[0]}); + this.totalPage = responses[1] && responses[1].data?responses[1].data.count:1; + if (responses[1] && responses[1].data) { + for( const response of responses[1].data.results){ + systemEvent = response; + if (response.affected_hardware_doc.stations ) { + systemEvent['affected_hardware_doc_stations'] = response.affected_hardware_doc.stations.join(', '); + } else { + systemEvent['affected_hardware_doc_stations'] = ''; + } + if(systemEvent.stop === null || systemEvent.stop === ''){ + systemEvent['stop'] = 'Unknown'; + } else { + systemEvent['stop'] = moment(systemEvent['stop']).format(UIConstants.CALENDAR_DATETIME_FORMAT); + } + systemEvent['start']= moment(systemEvent['start']).format(UIConstants.CALENDAR_DATETIME_FORMAT); + systemEvent['actionpath'] = `/systemevent/view/${systemEvent.id}`; + systemEvent['affected_tasks_ids'] = this.getLinksList(systemEvent.affected_tasks_ids); + const template = _.find(this.seAffectedHardwareTemplateList, {id: systemEvent.affected_hardware_template_id}); + systemEvent['affected_hardware_template_name'] = (template)?_.startCase(template.name):''; + this.systemEvents.push(systemEvent); + } + } + this.pageUpdated = true; + this.setState({ + isLoading: false, + systemEventList: this.systemEvents, + }); + }); + } + + /** + * Function to get a component with list of links to a list of ids + * @param {Array} linkedItems - list of ids + * @param {String} type - blueprint or draft + */ + getLinksList = (linkedItems, type) => { + return ( + <> + {linkedItems.length>0 && linkedItems.map((item, index) => ( + <Link style={{paddingRight: '3px'}} to={`/task/view/blueprint/${item}`}>{item}</Link> + ))} + </> + ); + } + + closeList(){ + this.props.history.length>1?this.props.history.goBack():this.props.history.push(`/su/timelineview`); + } + + render() { + const permissions = this.state.userrole.userRolePermission.systemevent; + return( + <React.Fragment> + <PageHeader location={this.props.location} title={'System Issues - List'} + actions={[{icon: 'fa-plus-square', + title:permissions.create?'Add System Event': "Don't have permission to add new System Event", + disabled: permissions.create? !permissions.create: true, + props : { pathname: `/systemevent/create`}}, + {icon: 'fa-window-close', title:'Click to close System Issues list', type: 'button', + actOn: 'click', props:{ callback: this.closeList }}]}/> + {this.state.isLoading? <AppLoader /> : (permissions.list) ? + <> + {this.state.systemEventList? + <> + <ViewTable + data={this.state.systemEventList} + defaultcolumns={this.state.tmpDefaulcolumns ? this.state.tmpDefaulcolumns : this.state.defaultcolumns} + optionalcolumns={this.state.tmpOptionalcolumns ? this.state.tmpOptionalcolumns : this.state.optionalcolumns} + columnclassname={this.state.columnclassname} + defaultSortColumn={this.defaultSortColumn} + showaction="true" + paths={this.state.paths} + tablename={this.SYSTEM_EVENT_LIST_TABLE_NAME} + showCSV= {true} + allowRowSelection={false} + toggleBySorting={(sortData) => this.toggleBySorting(sortData)} + lsKeySortColumn={this.lsKeySortColumn} + pageUpdated={this.pageUpdated} + storeFilter={true} + callBackFunction={this.fetchTableData} + showFilterOption={this.getFilterOptions} + totalPage={this.totalPage} + /> + </>: <div>No Reservation found </div>} + </> + : <AccessDenied/> + } + </React.Fragment> + ); + } +} \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/SystemEvent/system.event.view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/SystemEvent/system.event.view.js new file mode 100644 index 0000000000000000000000000000000000000000..d2fef286daf954d5bafd36902935124c2644ab8b --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/SystemEvent/system.event.view.js @@ -0,0 +1,146 @@ +import React, { Component } from 'react'; +import { Redirect } from 'react-router-dom' +import moment from 'moment'; +import _ from 'lodash'; +import Jeditor from '../../components/JSONEditor/JEditor'; +import { Link } from 'react-router-dom'; + +import UIConstants from '../../utils/ui.constants'; +import AppLoader from '../../layout/components/AppLoader'; +import PageHeader from '../../layout/components/PageHeader'; +import SystemEventService from '../../services/system.event.service'; +import AuthStore from '../../authenticate/auth.store'; +import AuthUtil from '../../utils/auth.util'; +import AccessDenied from '../../layout/components/AccessDenied'; + +export class SystemEventView extends Component { + constructor(props) { + super(props); + this.state = { + isLoading: true, + userrole: AuthStore.getState() + }; + if (this.props.match.params.id) { + this.state.systemeventId = this.props.match.params.id; + } + } + + async componentDidMount() { + const permission = await AuthUtil.getUserRolePermission(); + this.setState({userrole: permission}) + const systemeventId = this.props.match?this.props.match.params.id: null; + this.getSystemEventDetails(systemeventId); + } + + /** + * To get the system event details from the backend using the service + * @param {number} SystemEvent Id + */ + getSystemEventDetails(id) { + if (id) { + SystemEventService.getSystemEvent(id) + .then((systemevent) => { + if (systemevent) { + SystemEventService.getSystemEventTemplate(systemevent.affected_hardware_template_id) + .then((systemeventTemplate) => { + this.setState({redirect: null, systemevent: systemevent, isLoading: false, systemeventTemplate: systemeventTemplate}); + }); + } else { + this.setState({redirect: "/not-found"}); + } + }); + } else { + this.setState({redirect: "/not-found"}); + } + } + + render() { + const permissions = this.state.userrole.userRolePermission.systemevent; + if (this.state.redirect) { + return <Redirect to={ {pathname: this.state.redirect} }></Redirect> + } + let jeditor = null; + if (this.state.systemeventTemplate) { + if (this.state.systemevent.affected_hardware_doc && this.state.systemevent.affected_hardware_doc.$id) { + delete this.state.systemevent.affected_hardware_doc.$id; + delete this.state.systemevent.affected_hardware_doc.$schema; + } + jeditor = React.createElement(Jeditor, {title: "Affected Hardware Doc Parameters", + schema: this.state.systemeventTemplate.schema, + initValue: this.state.systemevent.affected_hardware_doc, + disabled: true, + }); + } + + let actions = [ ]; + actions.push({ icon: 'fa-edit', + title: permissions.edit?'Click to Edit System Event': "Don't have permission to edit", + disabled: permissions.edit? !permissions.edit : true, + props : { pathname:`/systemevent/edit/${this.state.systemevent?this.state.systemevent.id:null}`}}); + actions.push({ icon: 'fa-window-close', link: this.props.history.goBack, + title:'Click to Close System Event', props : { pathname:'/systemevent/list' }}); + return ( + <React.Fragment> + {permissions.list ? + <> + <PageHeader location={this.props.location} title={'System Event – Details'} actions={actions}/> + { this.state.isLoading? <AppLoader /> : this.state.systemevent && + <React.Fragment> + <div className="main-content"> + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Name</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.systemevent.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.systemevent.description}</span> + </div> + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Issue Type</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.systemevent.issue_type_value}</span> + <label className="col-lg-2 col-md-2 col-sm-12">Issue Subtype</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.systemevent.issue_subtype_value}</span> + </div> + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Jira URL</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.systemevent.jira_url}</span> + <label className="col-lg-2 col-md-2 col-sm-12">Notes</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.systemevent.notes}</span> + </div> + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Severity</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.systemevent.severity_value}</span> + <label className="col-lg-2 col-md-2 col-sm-12">Affected Tasks</label> + <span className="col-lg-4 col-md-4 col-sm-12"> + {(this.state.systemevent.affected_tasks_ids || []).map(id => ( + <Link to={{ pathname: `/task/view/blueprint/${id}` }} style={{marginRight: '5px'}}>{id}</Link> + ))} + </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">{moment.utc(this.state.systemevent.start).format(UIConstants.CALENDAR_DATETIME_FORMAT)}</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.systemevent.stop && this.state.systemevent.stop !== 'Unknown')?moment.utc(this.state.systemevent.stop).format(UIConstants.CALENDAR_DATETIME_FORMAT): 'Unknown'}</span> + </div> + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Affected Hardware Template</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.systemeventTemplate.name}</span> + <label className="col-lg-2 col-md-2 col-sm-12">Status</label> + <span className="col-lg-4 col-md-4 col-sm-12">{this.state.systemevent.status_value}</span> + </div> + <div className="p-fluid"> + <div className="p-grid"> + <fieldset className="border-style"> + <div className="p-col-12"> + {this.state.systemeventTemplate?jeditor:""} + </div> + </fieldset> + </div> + </div> + </div> + </React.Fragment> + } + </>: <AccessDenied/>} + </React.Fragment> + ); + } +} \ 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 8e709b1c4bc94a5c394a8f25f8637585988c77a9..700ba9c6a53a4394bff6618490954e210e1ff6ab 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/view.js @@ -143,9 +143,15 @@ export class TimelineView extends Component { const timelinePermission = permission.userRolePermission.timeline; let taskTypes = [] - let menuOptions = [{ label: 'Add Reservation', icon: "fa fa-", disabled: !timelinePermission.addreservation, command: (e) => { this.selectOptionMenu(e, 'Add Reservation') } }, - { label: 'Reservation List', icon: "fa fa-", disabled: !timelinePermission.listreservation, command: (e) => { - this.selectOptionMenu(e, 'Reservation List') } }, + let menuOptions = [ + { label: 'Add Reservation', icon: "fa fa-", disabled: !timelinePermission.addreservation, + command: (e) => { this.selectOptionMenu(e, 'Add Reservation') } }, + { label: 'Reservation List', icon: "fa fa-", disabled: !timelinePermission.listreservation, + command: (e) => { this.selectOptionMenu(e, 'Reservation List') } }, + { label: 'Add System Event', icon: "fa fa-", disabled: !timelinePermission.addsystemevent, + command: (e) => { this.selectOptionMenu(e, 'Add System Event') } }, + { label: 'System Issues', icon: "fa fa-", disabled: !timelinePermission.listsystemevent, + command: (e) => { this.selectOptionMenu(e, 'System Issues') } }, ] this.setState({menuOptions: menuOptions, loader: true }); TaskService.getTaskTypes().then(results => {taskTypes = results}); @@ -1049,6 +1055,26 @@ export class TimelineView extends Component { } break; } + case 'System Issues': { + if(e.originalEvent.ctrlKey) { + window.open('/systemevent/list','_blank'); + } + else { + this.props.history.push('/systemevent/list'); + this.setState({ redirect: `/systemevent/list` }); + } + break; + } + case 'Add System Event': { + if(e.originalEvent.ctrlKey) { + window.open('/systemevent/create','_blank'); + } + else { + this.props.history.push('/systemevent/create') + this.setState({ redirect: `/systemevent/create`}); + } + break; + } default: { break; } 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 a8aca7e145674ffcb6bd3d5884c0ca4221700f08..d2d7739bbe0476dce7638c0bac3660fb88170931 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 @@ -111,9 +111,15 @@ export class WeekTimelineView extends Component { const permission = await AuthUtil.getUserRolePermission(); const timelinePermission = permission.userRolePermission.timeline; const weekviewPermission = permission.userRolePermission.weekoverview; - let menuOptions = [{ label: 'Add Reservation', icon: "fa fa-", disabled: !timelinePermission.addreservation, command: (e) => { this.selectOptionMenu(e, 'Add Reservation') } }, - { label: 'Reservation List', icon: "fa fa-", disabled: !timelinePermission.listreservation, command: (e) => { - this.selectOptionMenu(e, 'Reservation List') } }, + let menuOptions = [ + { label: 'Add Reservation', icon: "fa fa-", disabled: !timelinePermission.addreservation, + command: (e) => { this.selectOptionMenu(e, 'Add Reservation') } }, + { label: 'Reservation List', icon: "fa fa-", disabled: !timelinePermission.listreservation, + command: (e) => { this.selectOptionMenu(e, 'Reservation List') } }, + { label: 'Add System Event', icon: "fa fa-", disabled: !timelinePermission.addsystemevent, + command: (e) => { this.selectOptionMenu(e, 'Add System Event') } }, + { label: 'System Issues', icon: "fa fa-", disabled: !timelinePermission.listsystemevent, + command: (e) => { this.selectOptionMenu(e, 'System Issues') } }, ] this.setState({menuOptions: menuOptions, userPermission: weekviewPermission}); @@ -672,6 +678,26 @@ export class WeekTimelineView extends Component { } break; } + case 'System Issues': { + if(e.originalEvent.ctrlKey) { + window.open('/systemevent/list','_blank'); + } + else { + this.props.history.push('/systemevent/list'); + this.setState({ redirect: `/systemevent/list` }); + } + break; + } + case 'Add System Event': { + if(e.originalEvent.ctrlKey) { + window.open('/systemevent/create','_blank'); + } + else { + this.props.history.push('/systemevent/create') + this.setState({ redirect: `/systemevent/create`}); + } + break; + } default: { break; } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js index b838150d01a5cfe62cd5cb7e695b12fd04e150f2..37192d9c0daa964acc32e02b972ec944b8d94a60 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js @@ -17,6 +17,7 @@ import EditSchedulingUnit from './Scheduling/edit'; import { CycleList, CycleCreate, CycleView, CycleEdit } from './Cycle'; import { TimelineView, WeekTimelineView} from './Timeline'; import { ReservationCreate, ReservationList, ReservationView, ReservationEdit } from './Reservation'; +import { SystemEventCreate, SystemEventList, SystemEventView, SystemEventEdit } from './SystemEvent'; import { FindObjectResult } from './Search/' import SchedulingSetCreate from './Scheduling/excelview.schedulingset'; import Workflow from './Workflow'; @@ -239,7 +240,35 @@ export const routes = [ component: ReportHome, name: 'Reports', title: 'Reports' - } + }, + { + path: "/systemevent/list", + component: SystemEventList, + name: 'System Issues List', + title:'System Issues', + permissions: ['systemevent', 'list'] + }, + { + path: "/systemevent/create", + component: SystemEventCreate, + name: 'System Event Add', + title: 'System Event - Add', + permissions: ['systemevent', 'create'] + }, + { + path: "/systemevent/view/:id", + component: SystemEventView, + name: 'System Event View', + title: 'System Event - View', + permissions: ['systemevent', 'list'] + }, + { + path: "/systemevent/edit/:id", + component: SystemEventEdit, + name: 'System Event Edit', + title: 'System Event - Edit', + permissions: ['systemevent', 'edit'] + }, ]; export const RoutedContent = () => { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/system.event.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/system.event.service.js new file mode 100644 index 0000000000000000000000000000000000000000..3cd2b78c7c0f7074c6b746ef5bddd68f09cc6423 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/system.event.service.js @@ -0,0 +1,108 @@ +const axios = require('axios'); + +const SystemEventService = { + getSEAffectedHardwareTemplates: async function () { + try { + const url = `/api/system_event_template`; + const response = await axios.get(url); + return response.data.results; + } catch (error) { + console.error('[system.event.services.getSEAffectedHardwareTemplates]',error); + } + }, + getSystemEventTemplate: async function(templateId) { + try { + const response = await axios.get('/api/system_event_template/' + templateId); + return response.data; + } catch (error) { + console.error('[system.event.services.getSystemEventTemplate]',error); + } + }, + getSEIssueTypes: async function () { + try { + const url = `/api/system_event_type`; + const response = await axios.get(url); + return response.data.results; + } catch (error) { + console.error('[system.event.services.getSEIssueTypes]',error); + } + }, + getSEIssueSubTypes: async function () { + try { + const url = `/api/system_event_subtype`; + const response = await axios.get(url); + return response.data.results; + } catch (error) { + console.error('[system.event.services.getSEIssueSubTypes]',error); + } + }, + getSESeverity: async function () { + try { + const url = `/api/system_event_severity`; + const response = await axios.get(url); + return response.data.results; + } catch (error) { + console.error('[system.event.services.getSESeverity]',error); + } + }, + getSEStatus: async function () { + try { + const url = `/api/system_event_status`; + const response = await axios.get(url); + return response.data.results; + } catch (error) { + console.error('[system.event.services.getSEStatus]',error); + } + }, + saveSystemEvent: async function (systemevent) { + try { + const response = await axios.post(('/api/system_event/'), systemevent); + return response.data; + } catch (error) { + console.error('[system.event.services.saveSystemEvent]',error); + return null; + } + }, + updateSystemEvent: async function (systemevent) { + try { + const response = await axios.put((`/api/system_event/${systemevent.id}/`), systemevent); + return response.data; + } catch (error) { + console.error('[system.event.services.updateSystemEvent]',error); + return null; + } + }, + getSystemEventWithFilter: async function (filters, orderBy, limit, offset){ + let response = null; + try { + let api = `/api/system_event/?`; + api += (filters === '')? '' : filters+'&'; + api += (orderBy === '')? '' : orderBy+'&'; + api += 'limit='+limit+'&offset=' + api += (offset === '') ? 0 : offset; + response = await axios.get(api); + } catch(error) { + console.error('[system.event.services.getSystemEventWithFilter]',error); + } + return response; + }, + getSystemEventFilterDefinition: async function () { + let res = []; + try { + res = await axios.options(`/api/system_event/`); + } catch(error) { + console.error('[system.event.services.getSystemEventFilterDefinition]',error); + } + return res; + }, + getSystemEvent: async function (id) { + try { + const response =await axios.get(`/api/system_event/${id}`); + return response.data; + } catch (error) { + console.error('[system.event.services.getSystemEvent]',error); + return null; + } + } +} +export default SystemEventService;