From 43c378e22423f637a0e655b654ec39441bd97ede Mon Sep 17 00:00:00 2001 From: Sartaj Singh Baveja Date: Thu, 14 Mar 2019 19:06:51 -0700 Subject: [PATCH] fix: [OS-4] [OS-6] Allow users to modify end time and description of connection (#309) * Frontend work on modifying connection description and ending time * Update configuration files * Revert package versions * Separate code into components, update description save method * Fix description save method * Revert package-lock.json * Add alert when modification is successful --- .../es/oscars/web/rest/ModifyController.java | 1 - frontend/.babelrc | 1 - frontend/package.json | 2 +- .../js/components/details/detailsButtons.js | 3 +- .../js/components/details/detailsEditForm.js | 425 ++++++++++++++++++ .../js/components/details/detailsGeneral.js | 233 +++++----- .../main/js/components/details/detailsInfo.js | 1 - .../js/components/details/detailsModal.js | 74 +++ frontend/src/main/js/stores/connsStore.js | 52 +++ frontend/src/main/js/stores/modalStore.js | 2 + frontend/tsconfig.json | 6 + 11 files changed, 666 insertions(+), 134 deletions(-) create mode 100644 frontend/src/main/js/components/details/detailsEditForm.js create mode 100644 frontend/src/main/js/components/details/detailsModal.js create mode 100644 frontend/tsconfig.json diff --git a/backend/src/main/java/net/es/oscars/web/rest/ModifyController.java b/backend/src/main/java/net/es/oscars/web/rest/ModifyController.java index 44e63abea..4d20d6c33 100644 --- a/backend/src/main/java/net/es/oscars/web/rest/ModifyController.java +++ b/backend/src/main/java/net/es/oscars/web/rest/ModifyController.java @@ -60,7 +60,6 @@ public void modifyDescription(@RequestBody DescriptionModifyRequest request) } c.setDescription(request.getDescription()); connRepo.save(c); - } diff --git a/frontend/.babelrc b/frontend/.babelrc index 9f75534b8..fc09386a8 100644 --- a/frontend/.babelrc +++ b/frontend/.babelrc @@ -4,6 +4,5 @@ "react", "stage-1" ], - "plugins": ["transform-decorators-legacy", "lodash"] } \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index ddaecb065..e4c95973e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,7 @@ "description": "OSCARS frontend", "repository": { "type": "git", - "url": "git://github.com/esnet/oscars-newtech.git" + "url": "git://github.com/esnet/oscars.git" }, "dependencies": { "bootstrap": "4.1.3", diff --git a/frontend/src/main/js/components/details/detailsButtons.js b/frontend/src/main/js/components/details/detailsButtons.js index c68dc8b1d..0804ef386 100644 --- a/frontend/src/main/js/components/details/detailsButtons.js +++ b/frontend/src/main/js/components/details/detailsButtons.js @@ -483,9 +483,10 @@ class DetailsButtons extends Component { {build} {dismantle} {release} +
{specialHeader} {regenerate} - +
{recoverSelect} ); diff --git a/frontend/src/main/js/components/details/detailsEditForm.js b/frontend/src/main/js/components/details/detailsEditForm.js new file mode 100644 index 000000000..48ca8a8d1 --- /dev/null +++ b/frontend/src/main/js/components/details/detailsEditForm.js @@ -0,0 +1,425 @@ +import React, { Component } from "react"; + +import chrono from "chrono-node"; +import { action } from "mobx"; +import { observer, inject } from "mobx-react"; +import Moment from "moment"; +import { + Alert, + Button, + Collapse, + Col, + Form, + FormFeedback, + FormGroup, + FormText, + Input, + Label, + Modal, + ModalBody, + ModalFooter, + ModalHeader +} from "reactstrap"; +import ToggleDisplay from "react-toggle-display"; + +import myClient from "../../agents/client"; +import DetailsModal from "./detailsModal"; + +@inject("connsStore", "modalStore") +@observer +class DetailsEditForm extends Component { + constructor(props) { + super(props); + } + + componentDidMount() { + const conn = this.props.connsStore.store.current; + const endingValue = this.formatSchedule(conn.archived.schedule.ending).formattedTime; + + this.props.connsStore.setParamsForEditSchedule({ + ending: { + originalTime: endingValue + }, + description: { + originalDescription: conn.description + } + }); + } + + formatSchedule(timems) { + const format = "Y/MM/DD HH:mm"; + const timeSec = Moment(timems * 1000); + const time = timeSec.format(format); + const formattedTime = time + " (" + timeSec.fromNow() + ")"; + + return { + time: time, + formattedTime: formattedTime + }; + } + + handleDescriptionCancel = () => { + const description = this.props.connsStore.editSchedule.description; + this.description.value = description.originalDescription; + + this.props.connsStore.setParamsForEditButtons({ + description: { + input: true, + buttonText: "Edit", + collapseText: true, + save: true + } + }); + + this.props.connsStore.setParamsForEditSchedule({ + description: { + acceptable: true, + validationState: "success" + } + }); + }; + + handleDescriptionChange = e => { + let value = e.target.value; + + if (value !== "") { + this.props.connsStore.setParamsForEditSchedule({ + description: { + acceptable: true, + validationState: "success", + updatedDescription: value + } + }); + + this.props.connsStore.setParamsForEditButtons({ description: { save: false } }); + } else { + this.props.connsStore.setParamsForEditSchedule({ + description: { + acceptable: false, + validationState: "error", + validationText: "Can't be empty" + } + }); + + this.props.connsStore.setParamsForEditButtons({ description: { save: true } }); + } + }; + + handleDescriptionEdit = () => { + this.props.connsStore.setParamsForEditButtons({ + description: { + input: false, + buttonText: "Cancel", + collapseText: false + } + }); + }; + + handleDescriptionSave = e => { + const conn = this.props.connsStore.store.current; + const desc = this.props.connsStore.editSchedule.description; + const modification = { + connectionId: conn.connectionId, + description: desc.updatedDescription + }; + + myClient.submitWithToken("POST", "/protected/modify/description", modification).then( + action(response => { + this.description.value = desc.updatedDescription; + + this.props.connsStore.setParamsForEditButtons({ + description: { + edit: false, + save: true, + buttonText: "Edit", + collapseText: true, + input: true + } + }); + + this.props.connsStore.setParamsForEditSchedule({ + description: { + originalDescription: desc.updatedDescription, + saved: true + } + }); + }) + ); + }; + + handleEndingCancel = () => { + const ending = this.props.connsStore.editSchedule.ending; + this.endingDate.value = ending.originalTime; + + this.props.connsStore.setParamsForEditButtons({ + ending: { + input: true, + buttonText: "Edit", + collapseText: true, + save: true + } + }); + + this.props.connsStore.setParamsForEditSchedule({ + ending: { + acceptable: true, + validationState: "success" + } + }); + }; + + handleEndingChange = e => { + let parsed = chrono.parseDate(e.target.value); + const es = this.props.connsStore.editSchedule; + + if (parsed != null) { + let currentms = parsed.getTime() / 1000; + if ( + currentms >= es.ending.validSchedule.beginning && + currentms <= es.ending.validSchedule.ending + ) { + this.props.connsStore.setParamsForEditSchedule({ + ending: { + acceptable: true, + validationState: "success", + newEndTime: parsed.getTime() / 1000, + parsedValue: parsed + } + }); + this.props.connsStore.setParamsForEditButtons({ ending: { save: false } }); + } else { + this.props.connsStore.setParamsForEditSchedule({ + ending: { + acceptable: false, + validationState: "error", + validationText: "Ending Time is not within the valid time range", + parsedValue: parsed + } + }); + + this.props.connsStore.setParamsForEditButtons({ ending: { save: true } }); + } + } else { + this.props.connsStore.setParamsForEditSchedule({ + ending: { + acceptable: false, + validationState: "error", + validationText: "Ending Time is not a valid date", + parsedValue: parsed + } + }); + + this.props.connsStore.setParamsForEditButtons({ ending: { save: true } }); + } + }; + + handleEndingEdit = () => { + const conn = this.props.connsStore.store.current; + const validRangeRequest = { + connectionId: conn.connectionId, + type: "END" + }; + + myClient.submitWithToken("POST", "/api/valid/schedule", validRangeRequest).then( + action(response => { + let status = JSON.parse(response); + + this.props.connsStore.setParamsForEditButtons({ + ending: { + input: false, + buttonText: "Cancel", + collapseText: false + } + }); + + this.props.connsStore.setParamsForEditSchedule({ + ending: { + validSchedule: { + beginning: status.floor, + ending: status.ceiling + } + } + }); + }) + ); + + // Format input box text + const splitValue = this.endingDate.value.split(" "); + const formattedEnding = splitValue[0] + " " + splitValue[1]; + this.endingDate.value = formattedEnding; + }; + + handleEndingSave = e => { + const conn = this.props.connsStore.store.current; + const ending = this.props.connsStore.editSchedule.ending; + const modification = { + connectionId: conn.connectionId, + type: "END", + timestamp: ending.newEndTime + }; + + myClient.submitWithToken("POST", "/protected/modify/schedule", modification).then( + action(response => { + let status = JSON.parse(response); + if (status.success === true) { + const newTime = this.formatSchedule(status.end).formattedTime; + this.endingDate.value = newTime; + + this.props.connsStore.setParamsForEditButtons({ + ending: { + edit: false, + save: true, + buttonText: "Edit", + collapseText: true, + input: true + } + }); + + this.props.connsStore.setParamsForEditSchedule({ + ending: { + originalTime: newTime, + saved: true + } + }); + } + }) + ); + }; + + render() { + const conn = this.props.connsStore.store.current; + + const es = this.props.connsStore.editSchedule; + const eb = this.props.connsStore.editButtons; + + const beginning = this.formatSchedule(conn.archived.schedule.beginning).formattedTime; + const validStartingTime = this.formatSchedule(es.ending.validSchedule.beginning).time; + const validEndingTime = this.formatSchedule(es.ending.validSchedule.ending).time; + + return ( +
+ + + + { + this.description = ref; + }} + disabled={eb.description.input} + invalid={es.description.validationState === "error"} + onChange={this.handleDescriptionChange} + /> + {es.description.validationText} + {es.description.saved === true ? ( + + ) : ( + "" + )} + + + + Original Description: {es.description.originalDescription} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { + this.endingDate = ref; + }} + disabled={eb.ending.input} + invalid={es.ending.validationState === "error"} + onChange={this.handleEndingChange} + /> + {es.ending.validationText} + {es.ending.saved ? : ""} + + + Original Ending Time: {es.ending.originalTime} + + Valid Range between {validStartingTime} to {validEndingTime} + + {`Parsed Value: ${es.ending.parsedValue}`} + + + + + + + + + + + + + + + +
+ ); + } +} + +export default DetailsEditForm; diff --git a/frontend/src/main/js/components/details/detailsGeneral.js b/frontend/src/main/js/components/details/detailsGeneral.js index 5545d6f74..bc4e5059b 100644 --- a/frontend/src/main/js/components/details/detailsGeneral.js +++ b/frontend/src/main/js/components/details/detailsGeneral.js @@ -1,9 +1,5 @@ import React, { Component } from "react"; - import { observer, inject } from "mobx-react"; -import Moment from "moment"; -import BootstrapTable from "react-bootstrap-table-next"; - import { Card, CardBody, @@ -20,6 +16,7 @@ import classnames from "classnames"; import DetailsButtons from "./detailsButtons"; import DetailsDrawing from "./detailsDrawing"; +import DetailsEditForm from "./detailsEditForm"; import DetailsTags from "./detailsTags"; import HelpPopover from "../helpPopover"; @@ -28,6 +25,9 @@ import HelpPopover from "../helpPopover"; class DetailsGeneral extends Component { constructor(props) { super(props); + this.state = { + tab: "" + }; } componentWillMount() { @@ -53,134 +53,109 @@ class DetailsGeneral extends Component { render() { const conn = this.props.connsStore.store.current; - //console.log(toJS(conn)); - const format = "Y/MM/DD HH:mm"; - const beg = Moment(conn.archived.schedule.beginning * 1000); - const end = Moment(conn.archived.schedule.ending * 1000); - const beginning = beg.format(format) + " (" + beg.fromNow() + ")"; - const ending = end.format(format) + " (" + end.fromNow() + ")"; - const info = [ - { - k: "Description", - v: conn.description - }, - { - k: "Username", - v: conn.username - }, - { - k: "Begins", - v: beginning - }, - { - k: "Ending", - v: ending - } - ]; - const columns = [ - { - dataField: "k", - text: "Field", - headerTitle: true - }, - { - dataField: "v", - text: "Value", - headerTitle: true - } - ]; - const phaseTexts = { - RESERVED: "Reserved", - ARCHIVED: "Archived", - HELD: "Held" - }; - const modeTexts = { - MANUAL: "Manual", - AUTOMATIC: "Scheduled" - }; - const stateTexts = { - ACTIVE: "Active", - WAITING: "Waiting", - FAILED: "Failed", - FINISHED: "Finished" - }; + if (conn.archived.schedule.beginning != null && conn.archived.schedule.ending != null) { + const phaseTexts = { + RESERVED: "Reserved", + ARCHIVED: "Archived", + HELD: "Held" + }; - let states = ( - - - Phase: {phaseTexts[conn.phase]} {this.phaseHelp(conn.phase)} - - - State: {stateTexts[conn.state]} {this.stateHelp(conn.state)} - - - Build mode: {modeTexts[conn.mode]} {this.modeHelp(conn.mode)} - - - ); + const modeTexts = { + MANUAL: "Manual", + AUTOMATIC: "Scheduled" + }; - return ( - - Info - - - - - - {states} - - - - - - - - - - - - ); + const stateTexts = { + ACTIVE: "Active", + WAITING: "Waiting", + FAILED: "Failed", + FINISHED: "Finished" + }; + + let editDetails = ( + + + + + + ); + + let states = ( + + + Phase: {phaseTexts[conn.phase]} {this.phaseHelp(conn.phase)} + + + State: {stateTexts[conn.state]} {this.stateHelp(conn.state)} + + + Build mode: {modeTexts[conn.mode]} {this.modeHelp(conn.mode)} + + + ); + + return ( + + Info + + + + +
+ {editDetails} +
+ {states} +
+ +
+ + + + + + +
+
+
+ ); + } else { + return
Loading...
; + } } phaseHelp(phase) { diff --git a/frontend/src/main/js/components/details/detailsInfo.js b/frontend/src/main/js/components/details/detailsInfo.js index 52539f711..a32255c6f 100644 --- a/frontend/src/main/js/components/details/detailsInfo.js +++ b/frontend/src/main/js/components/details/detailsInfo.js @@ -85,7 +85,6 @@ class DetailsInfo extends Component { if (typeof selected.type === "undefined") { return ; } - if (selected.type === "fixture") { return this.fixtureInfo(); } else if (selected.type === "junction") { diff --git a/frontend/src/main/js/components/details/detailsModal.js b/frontend/src/main/js/components/details/detailsModal.js new file mode 100644 index 000000000..c4dd46332 --- /dev/null +++ b/frontend/src/main/js/components/details/detailsModal.js @@ -0,0 +1,74 @@ +import React, { Component } from "react"; +import { Modal, ModalHeader, ModalBody } from "reactstrap"; +import { inject, observer } from "mobx-react"; +import PropTypes from "prop-types"; + +@inject("connsStore", "modalStore") +@observer +class DetailsModal extends Component { + constructor(props) { + super(props); + } + + componentDidMount() { + this.toggle(); + } + + closeModal = () => { + const modalName = this.props.name; + this.props.modalStore.closeModal(modalName); + }; + + updateParams = modalName => { + if (modalName === "editEnding") { + this.props.connsStore.setParamsForEditSchedule({ + ending: { + saved: false + } + }); + } else { + this.props.connsStore.setParamsForEditSchedule({ + description: { + saved: false + } + }); + } + }; + + toggle = () => { + const modalName = this.props.name; + if (this.props.modalStore.modals.get(modalName)) { + this.props.modalStore.closeModal(modalName); + this.updateParams(modalName); + } else { + this.props.modalStore.openModal(modalName); + } + }; + + render() { + const modalName = this.props.name; + let showModal = this.props.modalStore.modals.get(modalName); + return ( + + Confirmation Message + + {this.props.name === "editEnding" + ? `End time was successfully changed` + : `Description was successfully changed`} + + + ); + } +} + +DetailsModal.propTypes = { + name: PropTypes.string.isRequired +}; + +export default DetailsModal; diff --git a/frontend/src/main/js/stores/connsStore.js b/frontend/src/main/js/stores/connsStore.js index f9a904542..70a7da39d 100644 --- a/frontend/src/main/js/stores/connsStore.js +++ b/frontend/src/main/js/stores/connsStore.js @@ -25,6 +25,58 @@ class ConnectionsStore { history: new Map() }; + @observable editButtons = { + description: { + buttonText: "Edit", + edit: true, + save: true, + input: true, + collapseText: true + }, + ending: { + buttonText: "Edit", + edit: true, + save: true, + input: true, + collapseText: true + } + }; + + @observable editSchedule = { + connectionId: "", + description: { + originalDescription: "", + updatedDescription: "", + acceptable: false, + validationText: "", + validationState: "success", + saved: false + }, + ending: { + originalTime: "", + newTime: "", + acceptable: false, + validationText: "", + validationState: "success", + validSchedule: { + beginning: null, + ending: null + }, + parsedValue: null, + saved: false + } + }; + + @action + setParamsForEditSchedule(params) { + mergeWith(this.editSchedule, params); + } + + @action + setParamsForEditButtons(params) { + mergeWith(this.editButtons, params); + } + @observable drawing = { redraw: true }; diff --git a/frontend/src/main/js/stores/modalStore.js b/frontend/src/main/js/stores/modalStore.js index 8812c8b5d..2523e46e9 100644 --- a/frontend/src/main/js/stores/modalStore.js +++ b/frontend/src/main/js/stores/modalStore.js @@ -4,6 +4,8 @@ class ModalStore { @observable modals = observable.map( { + editEnding: false, + editDescription: false, editFixture: false, editJunction: false, editPipe: false, diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 000000000..852753ba5 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "allowJs": true + } +} \ No newline at end of file