Skip to content

Commit

Permalink
Merge pull request #438 from GRIDAPPSD/feature/upload-simulation-conf…
Browse files Browse the repository at this point in the history
…ig-file

Feature/upload simulation config file
  • Loading branch information
ShuhaoBai authored Jun 21, 2022
2 parents 3dbab51 + 1b62461 commit f8f60b1
Show file tree
Hide file tree
Showing 12 changed files with 710 additions and 170 deletions.
11 changes: 8 additions & 3 deletions client/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { FeederModel } from '@client:common/topology';
import { ThreeDots } from '@client:common/three-dots';
import { AuthenticatorContainer } from '@client:common/authenticator';
import { TabGroup, Tab } from '@client:common/tabs';
import { DateTimeService } from '@client:common/DateTimeService';

import { AvailableApplicationsAndServicesContainer } from './available-applications-and-services';
import { DataBrowser } from './data-browser';
Expand Down Expand Up @@ -50,13 +51,15 @@ interface Props {
export function App(props: Props) {
const tabGroupRef = useRef<TabGroup>(null);
const stateStore = StateStore.getInstance();
const dateTimeService = DateTimeService.getInstance();
const navigate = useNavigate();
const [simulationRequest, setSimulationRequest] = useState(null);

const onShowSimulationConfigForm = (config: SimulationConfiguration) => {
const onShowSimulationConfigForm = (config: SimulationConfiguration, isUploaded: boolean) => {
const portalRenderer = new PortalRenderer();
portalRenderer.mount(
<SimulationConfigurationEditor
isUploaded={isUploaded}
feederModel={props.feederModel}
onSubmit={updatedConfig => {
setSimulationRequest(updatedConfig);
Expand Down Expand Up @@ -93,9 +96,11 @@ export function App(props: Props) {
};

const downloadSimulationConfiguration = () => {
const reshapedStartTime = (new Date(simulationRequest.simulation_config.start_time).getTime() / 1000).toString();
// convert start_time to GMT and save it to the exported configuration file.
const convertStartTimeToGMT = dateTimeService.convertToGMT(simulationRequest.simulation_config.start_time).toString();
// When other users in different time zones, recevied this file, they only need to convert the GMT to their time zone time
// eslint-disable-next-line camelcase
const simulationConfigRequestToDownload = {...simulationRequest, simulation_config:{...simulationRequest.simulation_config, start_time: reshapedStartTime}};
const simulationConfigRequestToDownload = {...simulationRequest, simulation_config:{...simulationRequest.simulation_config, start_time: convertStartTimeToGMT}};
download('simulationRequestConfig', JSON.stringify(simulationConfigRequestToDownload), DownloadType.JSON);
};

Expand Down
61 changes: 59 additions & 2 deletions client/src/app/common/DateTimeService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { timeFormat, timeParse } from 'd3-time-format';

export const enum TimeZone {
EDT = 'EDT',
EST = 'EST',
PDT = 'PDT',
PST = 'PST',
UTC = 'UTC',
LOCAL = 'LOCAL'
Expand All @@ -17,6 +19,7 @@ export class DateTimeService {

private _timeZone = this._restoreSavedTimeZone();
private _timeZoneOffsetInMilliseconds = 0;
private _timeZoneOffsetGMTInHours = 0;

private _restoreSavedTimeZone() {
const savedTimeZone = localStorage.getItem('timeZone') as TimeZone || TimeZone.LOCAL;
Expand All @@ -35,16 +38,31 @@ export class DateTimeService {
switch (timeZone) {
case TimeZone.LOCAL:
this._timeZoneOffsetInMilliseconds = 0;
this._timeZoneOffsetGMTInHours = new Date().getTimezoneOffset() / 60;
break;
case TimeZone.UTC:
this._timeZoneOffsetInMilliseconds = new Date().getTimezoneOffset() * 60 * 1000;
this._timeZoneOffsetGMTInHours = 0;
break;
case TimeZone.EDT:
// Eastern Daylight time is 4 hours behind UTC
this._timeZoneOffsetInMilliseconds = new Date().getTimezoneOffset() * 60 * 1000 - 4 * 60 * 60 * 1000;
this._timeZoneOffsetGMTInHours = 4;
break;
case TimeZone.PDT:
// Pacific Daylight time is 7 hours behind UTC
this._timeZoneOffsetInMilliseconds = new Date().getTimezoneOffset() * 60 * 1000 - 7 * 60 * 60 * 1000;
this._timeZoneOffsetGMTInHours = 7;
break;
case TimeZone.EST:
this._timeZoneOffsetInMilliseconds = new Date().getTimezoneOffset() * 60 * 1000 - 5 * 60 * 60 * 1000;
this._timeZoneOffsetGMTInHours = 8;
break;
case TimeZone.PST: {
const offsetFromGmt = new Date().getTimezoneOffset() * 60 * 1000;
// We want to find the offset difference between user's time zone
// and EST. GMT is 5 hours ahead of EST, and 8 hours ahead of PST
this._timeZoneOffsetInMilliseconds = offsetFromGmt - (timeZone === TimeZone.EST ? 5 : 8) * 60 * 60 * 1000;
this._timeZoneOffsetInMilliseconds = new Date().getTimezoneOffset() * 60 * 1000 - 8 * 60 * 60 * 1000;
this._timeZoneOffsetGMTInHours = 5;
}
}
}
Expand Down Expand Up @@ -77,4 +95,43 @@ export class DateTimeService {
return parsedDateTime ? (parsedDateTime.getTime() - this._timeZoneOffsetInMilliseconds) / 1000 : null;
}

convertToGMT(str: string) {
const parsedDateTime = !str.includes('.') ? this._parserRegular(str) : this._parserWithMilliseconds(str);
const offsetFromGmtInHours = this._timeZoneOffsetGMTInHours;
return parsedDateTime ? (parsedDateTime.getTime() + offsetFromGmtInHours * 60 * 60 * 1000) / 1000 : null;
}

/**
* Parse the given Epoch time number and return Date in user's time zone, YYYY-MM-DD HH:MM:SS
*
* @param epochTime Epoch time
*/
parseEpoch(epochTime: number) {
const userLocalEpochTime = epochTime - this._timeZoneOffsetGMTInHours * 60 * 60;
const baseTime = new Date(userLocalEpochTime * 1000);
const year = baseTime.getFullYear().toString();
let month = (baseTime.getMonth() + 1).toString();
let day = baseTime.getDate().toString();
let hour = baseTime.getHours().toString();
let minute = baseTime.getMinutes().toString();
let second = baseTime.getSeconds().toString();
if (month.length === 1) {
month = '0' + month;
}
if (day.length === 1) {
day = '0' + day;
}
if (hour.length === 1) {
hour = '0' + hour;
}
if (minute.length === 1) {
minute = '0' + minute;
}
if (second.length === 1) {
second = '0' + second;
}
const dateTime = year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
return dateTime;
}

}
23 changes: 17 additions & 6 deletions client/src/app/navigation/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ExpectedResultComparisonType } from '@client:common/ExpectedResultCompa
import { StompClientConnectionStatus } from '@client:common/StompClientService';
import { SimulationConfiguration, Simulation } from '@client:common/simulation';
import { Restricted } from '@client:common/authenticator';
import { FilePicker } from '@client:common/file-picker';

import { Drawer } from './views/drawer/Drawer';
import { ToolBar } from './views/tool-bar/ToolBar';
Expand All @@ -22,7 +23,8 @@ interface Props {
stompClientConnectionStatus: StompClientConnectionStatus;
version: string;
activeSimulationIds: string[];
onShowSimulationConfigForm: (config: SimulationConfiguration) => void;
onShowSimulationConfigForm: (config: SimulationConfiguration, isUploaded: boolean) => void;
onShowUploadSimulationConfigFile: () => void;
onLogout: () => void;
onJoinActiveSimulation: (simulationId: string) => void;
onSelectExpectedResultComparisonType: (selectedType: ExpectedResultComparisonType) => void;
Expand Down Expand Up @@ -77,10 +79,19 @@ export class Navigation extends Component<Props, unknown> {
</DrawerItemGroup>
}
<Restricted roles={['testmanager']}>
<DrawerItem onClick={() => this.props.onShowSimulationConfigForm(null)}>
<DrawerItemIcon icon='assignment' />
<DrawerItemLabel value='Configure New Simulation' />
</DrawerItem>
<DrawerItemGroup
header='Configure New Simulation'
icon='assignment'>
<DrawerItem onClick={() => this.props.onShowSimulationConfigForm(null, false)}>
<DrawerItemIcon icon='assignment' />
<DrawerItemLabel value='Configure New Simulation' />
</DrawerItem>
<DrawerItem onClick={() => this.props.onShowUploadSimulationConfigFile()}>
<DrawerItemIcon icon='cloud_upload' />
<DrawerItemLabel value='Upload A Simulation Configuration File' />
</DrawerItem>
<FilePicker />
</DrawerItemGroup>
</Restricted>
<DrawerItemGroup
header='Select Comparison Type'
Expand Down Expand Up @@ -108,7 +119,7 @@ export class Navigation extends Component<Props, unknown> {
this.props.previousSimulations.map(simulation => (
<DrawerItem
key={simulation.id}
onClick={() => this.props.onShowSimulationConfigForm(simulation.config)}>
onClick={() => this.props.onShowSimulationConfigForm(simulation.config, false)}>
<strong>Name:&nbsp;</strong>
{simulation.name}
&nbsp;&mdash;&nbsp;
Expand Down
21 changes: 20 additions & 1 deletion client/src/app/navigation/NavigationContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import { ExpectedResultComparisonType } from '@client:common/ExpectedResultCompa
import { StompClientConnectionStatus, StompClientService } from '@client:common/StompClientService';
import { Simulation, SimulationQueue, SimulationConfiguration } from '@client:common/simulation';
import { ConfigurationManager } from '@client:common/ConfigurationManager';
import { FilePickerService } from '@client:common/file-picker';
import { Notification } from '@client:common/overlay/notification';

import { Navigation } from './Navigation';

interface Props {
onShowSimulationConfigForm: (config: SimulationConfiguration) => void;
onShowSimulationConfigForm: (config: SimulationConfiguration, isUploaded: boolean) => void;
onLogout: () => void;
onJoinActiveSimulation: (simulationId: string) => void;
onShowExpectedResultViewer: () => void;
Expand All @@ -32,6 +34,7 @@ export class NavigationContainer extends Component<Props, State> {
private readonly _stompClientService = StompClientService.getInstance();
private readonly _configurationManager = ConfigurationManager.getInstance();
private readonly _unsubscriber = new Subject<void>();
private readonly _filePickerService = FilePickerService.getInstance();

constructor(props: Props) {
super(props);
Expand All @@ -44,6 +47,7 @@ export class NavigationContainer extends Component<Props, State> {
};

this.onSelectExpectedResultComparisonType = this.onSelectExpectedResultComparisonType.bind(this);
this.onShowUploadSimulationConfigFile = this.onShowUploadSimulationConfigFile.bind(this);
}

componentDidMount() {
Expand Down Expand Up @@ -101,6 +105,7 @@ export class NavigationContainer extends Component<Props, State> {
previousSimulations={this.state.previousSimulations}
activeSimulationIds={this.state.activeSimulationIds}
onShowSimulationConfigForm={this.props.onShowSimulationConfigForm}
onShowUploadSimulationConfigFile={this.onShowUploadSimulationConfigFile}
onLogout={this.props.onLogout}
onJoinActiveSimulation={this.props.onJoinActiveSimulation}
onSelectExpectedResultComparisonType={this.onSelectExpectedResultComparisonType}>
Expand All @@ -116,4 +121,18 @@ export class NavigationContainer extends Component<Props, State> {
this.props.onShowExpectedResultViewer();
}

onShowUploadSimulationConfigFile() {
this._filePickerService.open()
.readFileAsJson<any>()
.subscribe({
next: fileContent => {
this.props.onShowSimulationConfigForm(fileContent, true);
this._filePickerService.clearSelection();
},
error: errorMessage => {
Notification.open(errorMessage);
this._filePickerService.clearSelection();
}
});
}
}
2 changes: 1 addition & 1 deletion client/src/app/navigation/views/drawer/DrawerItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function DrawerItem(props: DrawerItemProps) {
}

interface DrawerItemIconProps {
icon: 'assignment' | 'storage' | 'search' | 'laptop' | 'memory' | 'power_settings_new' | 'merge_type' | 'compare_arrows';
icon: 'assignment' | 'storage' | 'search' | 'laptop' | 'memory' | 'power_settings_new' | 'merge_type' | 'compare_arrows' | 'cloud_upload';
}

export function DrawerItemIcon(props: DrawerItemIconProps) {
Expand Down
2 changes: 1 addition & 1 deletion client/src/app/navigation/views/drawer/DrawerItemGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import './DrawerItemGroup.dark.scss';

interface Props {
header: string;
icon: 'memory' | 'merge_type' | 'compare_arrows';
icon: 'memory' | 'merge_type' | 'compare_arrows' | 'assignment';
}

interface State {
Expand Down
4 changes: 3 additions & 1 deletion client/src/app/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ export class Settings extends Component<Props, State> {
this.state = {
showSettingsMenu: false,
timeZoneOptionBuilder: new SelectionOptionBuilder([
TimeZone.EST,
TimeZone.LOCAL,
TimeZone.EDT,
TimeZone.EST,
TimeZone.PDT,
TimeZone.PST,
TimeZone.UTC
])
Expand Down
Loading

0 comments on commit f8f60b1

Please sign in to comment.