From 11db3ecb20c393bb5aed40a70c0831b6650a76ac Mon Sep 17 00:00:00 2001 From: Daniel Jurek Date: Tue, 17 Apr 2018 16:13:06 -0700 Subject: [PATCH 1/9] SQUISH ME --- src/components/TopNav/Preferences.js | 13 +++++++++++++ src/components/elements/TimeDisplay.js | 14 ++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/components/elements/TimeDisplay.js diff --git a/src/components/TopNav/Preferences.js b/src/components/TopNav/Preferences.js index 1da10d9..cb165ed 100644 --- a/src/components/TopNav/Preferences.js +++ b/src/components/TopNav/Preferences.js @@ -1,6 +1,8 @@ import React from 'react' import { connect } from 'react-redux' import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton' +import DropDownMenu from 'material-ui/DropDownMenu' +import MenuItem from 'material-ui/MenuItem' import * as signalRActions from 'actions/signalRActions' @@ -26,8 +28,19 @@ export const EventFilterPreferences = ({ ) } + +

Time Preferences:

+ Display times in timezone: + + + + + +let handleDropdownChange = (event, index, value) => this.setState({value}) + export const mapStateToEventFilterPreferencesProps = (state) => ({ currentEventFilterObject: state.events.filter, currentEventFilterPreference: state.signalR.filterPreferences.eventFilterType diff --git a/src/components/elements/TimeDisplay.js b/src/components/elements/TimeDisplay.js new file mode 100644 index 0000000..633909b --- /dev/null +++ b/src/components/elements/TimeDisplay.js @@ -0,0 +1,14 @@ +import { connect } from 'react-redux' +import React from 'react' + +const TimeDisplay = ({time, timezone}) => ( + Time... enough +) + +const mapStateToProps = (state, ownProps) => { + return { + timezone: 'lol jk' + } +} + +export default connect(mapStateToProps)(TimeDisplay) From 9670a79ddaefbf0597684a09fd0b978c27d3c8ba Mon Sep 17 00:00:00 2001 From: Daniel Jurek Date: Wed, 18 Apr 2018 15:33:55 -0700 Subject: [PATCH 2/9] Tiny progress --- src/actions/timezoneDisplayOptionsActions.js | 0 src/components/elements/TimeDisplay.js | 8 ++++++-- src/helpers/timeFormattedToMultipleZones.js | 2 +- src/reducers/timezoneDisplayOptionsReducer.js | 0 4 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 src/actions/timezoneDisplayOptionsActions.js create mode 100644 src/reducers/timezoneDisplayOptionsReducer.js diff --git a/src/actions/timezoneDisplayOptionsActions.js b/src/actions/timezoneDisplayOptionsActions.js new file mode 100644 index 0000000..e69de29 diff --git a/src/components/elements/TimeDisplay.js b/src/components/elements/TimeDisplay.js index 633909b..e4f876e 100644 --- a/src/components/elements/TimeDisplay.js +++ b/src/components/elements/TimeDisplay.js @@ -1,13 +1,17 @@ import { connect } from 'react-redux' import React from 'react' +import { DateTime } from 'luxon' const TimeDisplay = ({time, timezone}) => ( - Time... enough +
+
Passed in: {time.toString()}
+
Configured ({timezone}): {time.setZone(timezone).toString()}
+
) const mapStateToProps = (state, ownProps) => { return { - timezone: 'lol jk' + timezone: 'UTC' } } diff --git a/src/helpers/timeFormattedToMultipleZones.js b/src/helpers/timeFormattedToMultipleZones.js index efd1b5f..56464d7 100644 --- a/src/helpers/timeFormattedToMultipleZones.js +++ b/src/helpers/timeFormattedToMultipleZones.js @@ -6,7 +6,7 @@ import _ from 'underscore' const zones = [ { shortname: 'PT', ianaZone: 'America/Los_Angeles' }, { shortname: 'IST', ianaZone: 'Asia/Kolkata' }, - { shortname: 'UTC', ianaZone: 'Etc/GMT' } + { shortname: 'UTC', ianaZone: 'UTC' } ] const dateFormat = { diff --git a/src/reducers/timezoneDisplayOptionsReducer.js b/src/reducers/timezoneDisplayOptionsReducer.js new file mode 100644 index 0000000..e69de29 From d7c6d3c243a5bb97d338f30d660c7e5cd646c9f1 Mon Sep 17 00:00:00 2001 From: Daniel Jurek Date: Fri, 20 Apr 2018 08:50:20 -0700 Subject: [PATCH 3/9] Little of consequence --- src/components/Timeline/Event/EventCard.js | 8 +++++++- src/components/TopNav/Preferences.js | 4 +--- src/components/elements/TimeDisplay.js | 17 +++++++++++++---- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/components/Timeline/Event/EventCard.js b/src/components/Timeline/Event/EventCard.js index ae2c27f..bfef43e 100644 --- a/src/components/Timeline/Event/EventCard.js +++ b/src/components/Timeline/Event/EventCard.js @@ -9,6 +9,7 @@ import Avatar from 'material-ui/Avatar' import Playbook from 'components/Timeline/Playbook/Playbook' import { LoadTextFromEvent } from 'services/playbookService' import timeFormattedToMultipleZones from 'helpers/timeFormattedToMultipleZones' +import TimeDisplay from 'components/elements/TimeDisplay' export const EventCard = ({ event, @@ -21,7 +22,7 @@ export const EventCard = ({ > + event.occurred || event.Occurred + ? + : null + export const mapStateToEventCardProps = (state, ownProps) => { const { event, ticketId } = ownProps const eventType = state.eventTypes.records[event.eventTypeId] diff --git a/src/components/TopNav/Preferences.js b/src/components/TopNav/Preferences.js index cb165ed..c69e230 100644 --- a/src/components/TopNav/Preferences.js +++ b/src/components/TopNav/Preferences.js @@ -32,15 +32,13 @@ export const EventFilterPreferences = ({

Time Preferences:

Display times in timezone: + > -let handleDropdownChange = (event, index, value) => this.setState({value}) - export const mapStateToEventFilterPreferencesProps = (state) => ({ currentEventFilterObject: state.events.filter, currentEventFilterPreference: state.signalR.filterPreferences.eventFilterType diff --git a/src/components/elements/TimeDisplay.js b/src/components/elements/TimeDisplay.js index e4f876e..68e78c7 100644 --- a/src/components/elements/TimeDisplay.js +++ b/src/components/elements/TimeDisplay.js @@ -2,16 +2,25 @@ import { connect } from 'react-redux' import React from 'react' import { DateTime } from 'luxon' -const TimeDisplay = ({time, timezone}) => ( +const TimeDisplay = ({time, timezone}) => { + return (
Passed in: {time.toString()}
-
Configured ({timezone}): {time.setZone(timezone).toString()}
+
+ Configured ({timezone}): { + DateTime + .fromISO(time) + .setZone(timezone) + .toLocaleString(DateTime.DATETIME_SHORT_WITH_SECONDS) + } +
-) + ) +} const mapStateToProps = (state, ownProps) => { return { - timezone: 'UTC' + timezone: 'America/Los_Angeles' } } From b434923d8a79ffc15740f038c7cb28cf0a537c0f Mon Sep 17 00:00:00 2001 From: Daniel Jurek Date: Fri, 20 Apr 2018 15:17:15 -0700 Subject: [PATCH 4/9] Basic reducer functionality and default state --- src/actions/timePreferencesActions.js | 12 +++++++++ src/actions/timezoneDisplayOptionsActions.js | 0 src/components/elements/TimeDisplay.js | 21 +++++++++------ src/reducers/index.js | 4 ++- src/reducers/timePreferencesReducer.js | 27 +++++++++++++++++++ src/reducers/timezoneDisplayOptionsReducer.js | 0 6 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 src/actions/timePreferencesActions.js delete mode 100644 src/actions/timezoneDisplayOptionsActions.js create mode 100644 src/reducers/timePreferencesReducer.js delete mode 100644 src/reducers/timezoneDisplayOptionsReducer.js diff --git a/src/actions/timePreferencesActions.js b/src/actions/timePreferencesActions.js new file mode 100644 index 0000000..b70c81b --- /dev/null +++ b/src/actions/timePreferencesActions.js @@ -0,0 +1,12 @@ +export const ADD_DISPLAY_TIMEZONE = 'ADD_DISPLAY_TIMEZONE' +export const REMOVE_DISPLAY_TIMEZONE = 'REMOVE_DISPLAY_TIMEZONE' + +export const addDisplayTimezone = (timezoneIanaName) => ({ + type: ADD_DISPLAY_TIMEZONE, + timezoneIanaName +}) + +export const removeDisplaytimezone = (timezoneIanaName) => ({ + type: REMOVE_DISPLAY_TIMEZONE, + timezoneIanaName +}) \ No newline at end of file diff --git a/src/actions/timezoneDisplayOptionsActions.js b/src/actions/timezoneDisplayOptionsActions.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/elements/TimeDisplay.js b/src/components/elements/TimeDisplay.js index 68e78c7..7c72164 100644 --- a/src/components/elements/TimeDisplay.js +++ b/src/components/elements/TimeDisplay.js @@ -2,17 +2,21 @@ import { connect } from 'react-redux' import React from 'react' import { DateTime } from 'luxon' -const TimeDisplay = ({time, timezone}) => { +const TimeDisplay = ({time, timezones}) => { return (
Passed in: {time.toString()}
- Configured ({timezone}): { - DateTime - .fromISO(time) - .setZone(timezone) - .toLocaleString(DateTime.DATETIME_SHORT_WITH_SECONDS) - } + {timezones.map(timezone => ( +
+ Configured ({timezone}): { + DateTime + .fromISO(time) + .setZone(timezone) + .toLocaleString(DateTime.DATETIME_SHORT_WITH_SECONDS) + } +
+ ))}
) @@ -20,7 +24,8 @@ const TimeDisplay = ({time, timezone}) => { const mapStateToProps = (state, ownProps) => { return { - timezone: 'America/Los_Angeles' + timezones: state.timePreferences, + time: ownProps.time } } diff --git a/src/reducers/index.js b/src/reducers/index.js index a500d14..a23ea78 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -9,6 +9,7 @@ import forms from 'reducers/formReducer' import eventTypes from 'reducers/eventTypeReducer' import globalActions from 'reducers/globalActionReducer' import notifications from 'reducers/notificationReducer' +import timePreferences from 'reducers/timePreferencesReducer' const rootReducer = (filters, defaultEventFilterPreference) => combineReducers({ incidents, @@ -19,7 +20,8 @@ const rootReducer = (filters, defaultEventFilterPreference) => combineReducers({ signalR: signalR(defaultEventFilterPreference), eventTypes, globalActions, - notifications + notifications, + timePreferences }) export default rootReducer diff --git a/src/reducers/timePreferencesReducer.js b/src/reducers/timePreferencesReducer.js new file mode 100644 index 0000000..3d6d5a6 --- /dev/null +++ b/src/reducers/timePreferencesReducer.js @@ -0,0 +1,27 @@ + +import * as timePreferencesActions from 'actions/timePreferencesActions' + +export const defaultAvailableTimezones = [ + { displayName: 'Pacific Time', ianaZoneName: 'America/Los_Angeles' }, + { displayName: 'India Standard Time', ianaZoneName: 'Asia/Kolkata' }, + { displayName: 'UTC', ianaZoneName: 'UTC' } +] + +const defaultDisplayTimezones = ['UTC'] + +export const timePreferencesReducer = (state = defaultDisplayTimezones, action) => { + switch (action.type) { + case timePreferencesActions.ADD_DISPLAY_TIMEZONE: + return state.includes(action.timezoneIanaName) + ? state + : state.concat(action.timezoneIanaName) + case timePreferencesActions.REMOVE_DISPLAY_TIMEZONE: + return state.includes(action.timezoneIanaName) + ? state.filter(timezoneIanaName => timezoneIanaName !== action.timezoneIanaName) + : state + default: + return state + } +} + +export default timePreferencesReducer diff --git a/src/reducers/timezoneDisplayOptionsReducer.js b/src/reducers/timezoneDisplayOptionsReducer.js deleted file mode 100644 index e69de29..0000000 From a781f465b2f38d940d0ac971af9be25523873660 Mon Sep 17 00:00:00 2001 From: Daniel Jurek Date: Fri, 20 Apr 2018 16:33:25 -0700 Subject: [PATCH 5/9] Toggling works, needs persistence --- src/actions/timePreferencesActions.js | 14 +++------ src/components/TopNav/Preferences.js | 32 +++++++++++++++------ src/helpers/timeFormattedToMultipleZones.js | 2 +- src/reducers/timePreferencesReducer.js | 22 ++++++-------- 4 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/actions/timePreferencesActions.js b/src/actions/timePreferencesActions.js index b70c81b..e8681af 100644 --- a/src/actions/timePreferencesActions.js +++ b/src/actions/timePreferencesActions.js @@ -1,12 +1,6 @@ -export const ADD_DISPLAY_TIMEZONE = 'ADD_DISPLAY_TIMEZONE' -export const REMOVE_DISPLAY_TIMEZONE = 'REMOVE_DISPLAY_TIMEZONE' +export const SET_DISPLAY_TIMEZONES = 'SET_DISPLAY_TIMEZONES' -export const addDisplayTimezone = (timezoneIanaName) => ({ - type: ADD_DISPLAY_TIMEZONE, - timezoneIanaName -}) - -export const removeDisplaytimezone = (timezoneIanaName) => ({ - type: REMOVE_DISPLAY_TIMEZONE, - timezoneIanaName +export const setDisplayTimezones = (timezoneIanaNames) => ({ + type: SET_DISPLAY_TIMEZONES, + timezoneIanaNames }) \ No newline at end of file diff --git a/src/components/TopNav/Preferences.js b/src/components/TopNav/Preferences.js index c69e230..b6d044e 100644 --- a/src/components/TopNav/Preferences.js +++ b/src/components/TopNav/Preferences.js @@ -1,15 +1,18 @@ import React from 'react' import { connect } from 'react-redux' import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton' -import DropDownMenu from 'material-ui/DropDownMenu' +import Menu from 'material-ui/Menu' import MenuItem from 'material-ui/MenuItem' +import { zones } from 'helpers/timeFormattedToMultipleZones' import * as signalRActions from 'actions/signalRActions' +import * as timePreferencesActions from 'actions/timePreferencesActions' export const EventFilterPreferences = ({ currentEventFilterObject, currentEventFilterPreference, - dispatch + dispatch, + selectedTimezones }) =>

Event Filter Preferences:

Time Preferences: Display times in timezone: - - - - - + + {zones.map(zone => ( + + ))} +
+const onTimezoneClick = (dispatch) => (event, value) => + dispatch(timePreferencesActions.setDisplayTimezones(value)) + +const isTimezoneChecked = (ianaZoneName, selectedTimezones) => + selectedTimezones.includes(ianaZoneName) + export const mapStateToEventFilterPreferencesProps = (state) => ({ currentEventFilterObject: state.events.filter, - currentEventFilterPreference: state.signalR.filterPreferences.eventFilterType + currentEventFilterPreference: state.signalR.filterPreferences.eventFilterType, + selectedTimezones: state.timePreferences }) export const ConnectedEventFilterPreferences = connect(mapStateToEventFilterPreferencesProps)(EventFilterPreferences) diff --git a/src/helpers/timeFormattedToMultipleZones.js b/src/helpers/timeFormattedToMultipleZones.js index 56464d7..d378403 100644 --- a/src/helpers/timeFormattedToMultipleZones.js +++ b/src/helpers/timeFormattedToMultipleZones.js @@ -3,7 +3,7 @@ import _ from 'underscore' // PST https://en.wikipedia.org/wiki/List_of_tz_database_time_zones // India's IANA zone https://en.wikipedia.org/wiki/Time_in_India -const zones = [ +export const zones = [ { shortname: 'PT', ianaZone: 'America/Los_Angeles' }, { shortname: 'IST', ianaZone: 'Asia/Kolkata' }, { shortname: 'UTC', ianaZone: 'UTC' } diff --git a/src/reducers/timePreferencesReducer.js b/src/reducers/timePreferencesReducer.js index 3d6d5a6..809faa8 100644 --- a/src/reducers/timePreferencesReducer.js +++ b/src/reducers/timePreferencesReducer.js @@ -1,24 +1,20 @@ +import { persistReducer } from 'redux-persist' +import storage from 'redux-persist/lib/storage' // default: localStorage if web, AsyncStorage if react-native import * as timePreferencesActions from 'actions/timePreferencesActions' -export const defaultAvailableTimezones = [ - { displayName: 'Pacific Time', ianaZoneName: 'America/Los_Angeles' }, - { displayName: 'India Standard Time', ianaZoneName: 'Asia/Kolkata' }, - { displayName: 'UTC', ianaZoneName: 'UTC' } -] +// export const defaultAvailableTimezones = [ +// { displayName: 'Pacific Time', ianaZoneName: 'America/Los_Angeles' }, +// { displayName: 'India Standard Time', ianaZoneName: 'Asia/Kolkata' }, +// { displayName: 'UTC', ianaZoneName: 'UTC' } +// ] const defaultDisplayTimezones = ['UTC'] export const timePreferencesReducer = (state = defaultDisplayTimezones, action) => { switch (action.type) { - case timePreferencesActions.ADD_DISPLAY_TIMEZONE: - return state.includes(action.timezoneIanaName) - ? state - : state.concat(action.timezoneIanaName) - case timePreferencesActions.REMOVE_DISPLAY_TIMEZONE: - return state.includes(action.timezoneIanaName) - ? state.filter(timezoneIanaName => timezoneIanaName !== action.timezoneIanaName) - : state + case timePreferencesActions.SET_DISPLAY_TIMEZONES: + return action.timezoneIanaNames default: return state } From c13a5e2d9c2a3d0d456bd5b09818719b69304165 Mon Sep 17 00:00:00 2001 From: Daniel Jurek Date: Tue, 24 Apr 2018 15:56:54 -0700 Subject: [PATCH 6/9] Persistance works... partially --- src/components/TopNav/Preferences.js | 5 ++-- src/components/elements/TimeDisplay.js | 39 +++++++++++++------------- src/reducers/timePreferencesReducer.js | 12 +++++++- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/components/TopNav/Preferences.js b/src/components/TopNav/Preferences.js index b6d044e..35961ff 100644 --- a/src/components/TopNav/Preferences.js +++ b/src/components/TopNav/Preferences.js @@ -50,12 +50,13 @@ const onTimezoneClick = (dispatch) => (event, value) => dispatch(timePreferencesActions.setDisplayTimezones(value)) const isTimezoneChecked = (ianaZoneName, selectedTimezones) => - selectedTimezones.includes(ianaZoneName) + selectedTimezones.includes && selectedTimezones.includes(ianaZoneName) export const mapStateToEventFilterPreferencesProps = (state) => ({ currentEventFilterObject: state.events.filter, currentEventFilterPreference: state.signalR.filterPreferences.eventFilterType, - selectedTimezones: state.timePreferences + selectedTimezones: Object.values(state.timePreferences) + .filter(item => typeof item === 'string') }) export const ConnectedEventFilterPreferences = connect(mapStateToEventFilterPreferencesProps)(EventFilterPreferences) diff --git a/src/components/elements/TimeDisplay.js b/src/components/elements/TimeDisplay.js index 7c72164..835acdd 100644 --- a/src/components/elements/TimeDisplay.js +++ b/src/components/elements/TimeDisplay.js @@ -4,29 +4,30 @@ import { DateTime } from 'luxon' const TimeDisplay = ({time, timezones}) => { return ( -
-
Passed in: {time.toString()}
- {timezones.map(timezone => ( -
- Configured ({timezone}): { - DateTime - .fromISO(time) - .setZone(timezone) - .toLocaleString(DateTime.DATETIME_SHORT_WITH_SECONDS) - } -
- ))} +
Passed in: {time.toString()}
+
+ {timezones + .map(timezone => ( +
+ Configured ({timezone}): { + DateTime + .fromISO(time) + .setZone(timezone) + .toLocaleString(DateTime.DATETIME_SHORT_WITH_SECONDS) + } +
+ )) + } +
-
) } -const mapStateToProps = (state, ownProps) => { - return { - timezones: state.timePreferences, - time: ownProps.time - } -} +const mapStateToProps = (state, ownProps) => ({ + timezones: Object.values(state.timePreferences) + .filter(item => typeof item === 'string'), + time: ownProps.time +}) export default connect(mapStateToProps)(TimeDisplay) diff --git a/src/reducers/timePreferencesReducer.js b/src/reducers/timePreferencesReducer.js index 809faa8..7a07fc2 100644 --- a/src/reducers/timePreferencesReducer.js +++ b/src/reducers/timePreferencesReducer.js @@ -3,6 +3,7 @@ import storage from 'redux-persist/lib/storage' // default: localStorage if web, import * as timePreferencesActions from 'actions/timePreferencesActions' +// FYI from timeFormattedToMultipleZones.js // export const defaultAvailableTimezones = [ // { displayName: 'Pacific Time', ianaZoneName: 'America/Los_Angeles' }, // { displayName: 'India Standard Time', ianaZoneName: 'Asia/Kolkata' }, @@ -14,10 +15,19 @@ const defaultDisplayTimezones = ['UTC'] export const timePreferencesReducer = (state = defaultDisplayTimezones, action) => { switch (action.type) { case timePreferencesActions.SET_DISPLAY_TIMEZONES: + // Require that one timezone is always configured. Force the default + // timezone if the user deselects all timezones. + if (action.timezoneIanaNames.length <= 0) { + return defaultDisplayTimezones + } return action.timezoneIanaNames default: return state } } -export default timePreferencesReducer +const persistConfigs = { + key: 'timePreferences', + storage +} +export default persistReducer(persistConfigs, timePreferencesReducer) From d74342c48de206923d552c82a89a7e715b53c1fe Mon Sep 17 00:00:00 2001 From: Daniel Jurek Date: Wed, 25 Apr 2018 10:13:39 -0700 Subject: [PATCH 7/9] store the array nested in a combined reducer --- src/components/TopNav/Preferences.js | 5 ++--- src/components/elements/TimeDisplay.js | 3 +-- src/reducers/timePreferencesReducer.js | 6 +++++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/TopNav/Preferences.js b/src/components/TopNav/Preferences.js index 35961ff..814e409 100644 --- a/src/components/TopNav/Preferences.js +++ b/src/components/TopNav/Preferences.js @@ -50,13 +50,12 @@ const onTimezoneClick = (dispatch) => (event, value) => dispatch(timePreferencesActions.setDisplayTimezones(value)) const isTimezoneChecked = (ianaZoneName, selectedTimezones) => - selectedTimezones.includes && selectedTimezones.includes(ianaZoneName) + selectedTimezones.includes(ianaZoneName) export const mapStateToEventFilterPreferencesProps = (state) => ({ currentEventFilterObject: state.events.filter, currentEventFilterPreference: state.signalR.filterPreferences.eventFilterType, - selectedTimezones: Object.values(state.timePreferences) - .filter(item => typeof item === 'string') + selectedTimezones: state.timePreferences.timePreference }) export const ConnectedEventFilterPreferences = connect(mapStateToEventFilterPreferencesProps)(EventFilterPreferences) diff --git a/src/components/elements/TimeDisplay.js b/src/components/elements/TimeDisplay.js index 835acdd..a36acb7 100644 --- a/src/components/elements/TimeDisplay.js +++ b/src/components/elements/TimeDisplay.js @@ -25,8 +25,7 @@ const TimeDisplay = ({time, timezones}) => { } const mapStateToProps = (state, ownProps) => ({ - timezones: Object.values(state.timePreferences) - .filter(item => typeof item === 'string'), + timezones: state.timePreferences.timePreference, time: ownProps.time }) diff --git a/src/reducers/timePreferencesReducer.js b/src/reducers/timePreferencesReducer.js index 7a07fc2..8cf9dbd 100644 --- a/src/reducers/timePreferencesReducer.js +++ b/src/reducers/timePreferencesReducer.js @@ -1,3 +1,4 @@ +import { combineReducers } from 'redux' import { persistReducer } from 'redux-persist' import storage from 'redux-persist/lib/storage' // default: localStorage if web, AsyncStorage if react-native @@ -30,4 +31,7 @@ const persistConfigs = { key: 'timePreferences', storage } -export default persistReducer(persistConfigs, timePreferencesReducer) +export default persistReducer( + persistConfigs, + combineReducers({timePreference: timePreferencesReducer}) +) From 2b3d2a3ce05d3a7dec7e2bcbf1de417353b34d0a Mon Sep 17 00:00:00 2001 From: Daniel Jurek Date: Wed, 25 Apr 2018 14:11:10 -0700 Subject: [PATCH 8/9] More progress --- src/components/Timeline/Event/EventCard.js | 2 - src/components/TopNav/Preferences.js | 2 + src/components/elements/ErrorMessage.js | 8 +-- src/components/elements/TimeDisplay.js | 36 ++++++---- src/helpers/timeFormattedToMultipleZones.js | 33 +-------- src/reducers/timePreferencesReducer.js | 7 -- test/components/TopNav/PreferencesTest.js | 3 + test/components/elements/TimeDisplayTest.js | 0 .../timeFormattedToMultipleZonesTest.js | 68 ------------------- 9 files changed, 29 insertions(+), 130 deletions(-) create mode 100644 test/components/elements/TimeDisplayTest.js delete mode 100644 test/helpers/timeFormattedToMultipleZonesTest.js diff --git a/src/components/Timeline/Event/EventCard.js b/src/components/Timeline/Event/EventCard.js index bfef43e..ebf8bfc 100644 --- a/src/components/Timeline/Event/EventCard.js +++ b/src/components/Timeline/Event/EventCard.js @@ -1,14 +1,12 @@ import React from 'react' import PropTypes from 'prop-types' import { connect } from 'react-redux' -import { DateTime } from 'luxon' import { Card, CardHeader, CardActions } from 'material-ui/Card' import * as Icons from 'material-ui/svg-icons' import Avatar from 'material-ui/Avatar' import Playbook from 'components/Timeline/Playbook/Playbook' import { LoadTextFromEvent } from 'services/playbookService' -import timeFormattedToMultipleZones from 'helpers/timeFormattedToMultipleZones' import TimeDisplay from 'components/elements/TimeDisplay' export const EventCard = ({ diff --git a/src/components/TopNav/Preferences.js b/src/components/TopNav/Preferences.js index 814e409..d658654 100644 --- a/src/components/TopNav/Preferences.js +++ b/src/components/TopNav/Preferences.js @@ -50,6 +50,8 @@ const onTimezoneClick = (dispatch) => (event, value) => dispatch(timePreferencesActions.setDisplayTimezones(value)) const isTimezoneChecked = (ianaZoneName, selectedTimezones) => + selectedTimezones && + selectedTimezones.includes && selectedTimezones.includes(ianaZoneName) export const mapStateToEventFilterPreferencesProps = (state) => ({ diff --git a/src/components/elements/ErrorMessage.js b/src/components/elements/ErrorMessage.js index be5fd2c..c00036d 100644 --- a/src/components/elements/ErrorMessage.js +++ b/src/components/elements/ErrorMessage.js @@ -1,10 +1,8 @@ import React from 'react' -import { DateTime } from 'luxon' import ErrorIcon from 'material-ui/svg-icons/alert/error' import { Card, CardHeader } from 'material-ui/Card' import { RetryButton } from 'components/elements/Buttons' - -import timeFormattedToMultipleZones from 'helpers/timeFormattedToMultipleZones' +import TimeDisplay from 'components/elements/TimeDisplay' export const ErrorMessage = ({ message, @@ -12,8 +10,6 @@ export const ErrorMessage = ({ time = null, backgroundColor = null }) => { - const errorMessageTime = time && time instanceof DateTime ? time.toLocal().toFormat(DateTime.TIME_WITH_SECONDS) : null - return } /> { actionForRetry ? : null } diff --git a/src/components/elements/TimeDisplay.js b/src/components/elements/TimeDisplay.js index a36acb7..ecd69ac 100644 --- a/src/components/elements/TimeDisplay.js +++ b/src/components/elements/TimeDisplay.js @@ -2,28 +2,34 @@ import { connect } from 'react-redux' import React from 'react' import { DateTime } from 'luxon' +import { timeAndDateFormat } from 'helpers/timeFormattedToMultipleZones' + const TimeDisplay = ({time, timezones}) => { + if (!time) { + return null + } + return (
-
Passed in: {time.toString()}
-
- {timezones - .map(timezone => ( -
- Configured ({timezone}): { - DateTime - .fromISO(time) - .setZone(timezone) - .toLocaleString(DateTime.DATETIME_SHORT_WITH_SECONDS) - } -
- )) - } -
+ {timezones + .map(timezone => ( +
({timezone}): {convertedTime(time, timezone)}
+ )) + }
) } +export const convertedTime = (time, timezone) => + parseTime(time) + .setZone(timezone) + .toLocaleString(timeAndDateFormat) + +export const parseTime = (time) => + time instanceof DateTime + ? time + : DateTime.fromISO(time) + const mapStateToProps = (state, ownProps) => ({ timezones: state.timePreferences.timePreference, time: ownProps.time diff --git a/src/helpers/timeFormattedToMultipleZones.js b/src/helpers/timeFormattedToMultipleZones.js index d378403..790952a 100644 --- a/src/helpers/timeFormattedToMultipleZones.js +++ b/src/helpers/timeFormattedToMultipleZones.js @@ -1,5 +1,4 @@ import { DateTime } from 'luxon' -import _ from 'underscore' // PST https://en.wikipedia.org/wiki/List_of_tz_database_time_zones // India's IANA zone https://en.wikipedia.org/wiki/Time_in_India @@ -15,34 +14,4 @@ const dateFormat = { day: '2-digit' } -const timeAndDateformat = Object.assign(dateFormat, DateTime.TIME_24_WITH_SECONDS) - -export const timeFormattedToMultipleZones = (time, timezones = zones) => { - if (!time) { return '' } - - let convertTimeToZone = (timepoint) => { - timepoint.timeInZone = time.setZone(timepoint.ianaZone) - return timepoint - } - - let timeAndDate = (timepoint, index) => { - let format = index === 0 ? timeAndDateformat : DateTime.TIME_24_WITH_SECONDS - - return timepoint.timeInZone.toLocaleString(format) + ' ' + timepoint.shortname - } - - let transformToTimeAndDateString = (dateGroup) => _.map(dateGroup, timeAndDate).join(', ') - let dateForTimepoint = (timepoint) => timepoint.timeInZone.toLocaleString(DateTime.DATE_SHORT) - - let timeInMultipleZones = _.chain(timezones) - .map(convertTimeToZone) - .groupBy(dateForTimepoint) - .map(transformToTimeAndDateString) - .compact() - .value() - .join('; ') - - return timeInMultipleZones -} - -export default timeFormattedToMultipleZones +export const timeAndDateformat = Object.assign(dateFormat, DateTime.TIME_24_WITH_SECONDS) diff --git a/src/reducers/timePreferencesReducer.js b/src/reducers/timePreferencesReducer.js index 8cf9dbd..00775e2 100644 --- a/src/reducers/timePreferencesReducer.js +++ b/src/reducers/timePreferencesReducer.js @@ -4,13 +4,6 @@ import storage from 'redux-persist/lib/storage' // default: localStorage if web, import * as timePreferencesActions from 'actions/timePreferencesActions' -// FYI from timeFormattedToMultipleZones.js -// export const defaultAvailableTimezones = [ -// { displayName: 'Pacific Time', ianaZoneName: 'America/Los_Angeles' }, -// { displayName: 'India Standard Time', ianaZoneName: 'Asia/Kolkata' }, -// { displayName: 'UTC', ianaZoneName: 'UTC' } -// ] - const defaultDisplayTimezones = ['UTC'] export const timePreferencesReducer = (state = defaultDisplayTimezones, action) => { diff --git a/test/components/TopNav/PreferencesTest.js b/test/components/TopNav/PreferencesTest.js index 79c59dc..b347718 100644 --- a/test/components/TopNav/PreferencesTest.js +++ b/test/components/TopNav/PreferencesTest.js @@ -61,6 +61,9 @@ describe('Preferences', function () { filterPreferences: { eventFilterType: expectedFilterType } + }, + timePreferences: { + timePreference: ['UTC'] } } diff --git a/test/components/elements/TimeDisplayTest.js b/test/components/elements/TimeDisplayTest.js new file mode 100644 index 0000000..e69de29 diff --git a/test/helpers/timeFormattedToMultipleZonesTest.js b/test/helpers/timeFormattedToMultipleZonesTest.js deleted file mode 100644 index 2cec621..0000000 --- a/test/helpers/timeFormattedToMultipleZonesTest.js +++ /dev/null @@ -1,68 +0,0 @@ -'use strict' -import { expect } from 'chai' -import { DateTime } from 'luxon'; -import timeFormattedToMultipleZones from 'helpers/timeFormattedToMultipleZones' - -describe('timeFormattedToMultipleZones', function test () { - let pacific = 'PT' - let india = 'IST' - let utc = 'UTC' - let yesterday = '1969-12-31' - let today = '1970-01-01' - let tomorrow = '1970-01-02' - - context('over yesterday and today', () => { - let time = DateTime.utc(1970, 1, 1, 0, 0) - - it('defaults to displaying time for Pacific, India, and UTC', () => { - expect(timeFormattedToMultipleZones(time)).to.contain(pacific, india, utc) - }) - - it('has the days the time occured in', () => { - expect(timeFormattedToMultipleZones(time)).to.contain(today, yesterday) - }) - - it('groups times by day', () => { - let expected = '1969-12-31 16:00:00 PT; 1970-01-01 05:30:00 IST, 00:00:00 UTC' - - expect(timeFormattedToMultipleZones(time)).to.eql(expected) - }) - }) - - context('In a single day', () => { - let time = DateTime.utc(1970, 1, 1, 0, 0).plus({ hours: 8 }) - - it('defaults to displaying time for Pacific, India, and UTC', () => { - expect(timeFormattedToMultipleZones(time)).to.contain(pacific, india, utc) - }) - - it('has only the ay the time occured in', () => { - expect(timeFormattedToMultipleZones(time)).to.contain(today) - expect(timeFormattedToMultipleZones(time)).to.not.contain(yesterday, tomorrow) - }) - - it('groups times by day', () => { - let expected = '1970-01-01 00:00:00 PT, 13:30:00 IST, 08:00:00 UTC' - - expect(timeFormattedToMultipleZones(time)).to.eql(expected) - }) - }) - - context('over today and tomorrow', () => { - let time = DateTime.utc(1970, 1, 1, 0, 0).plus({ hours: 18, minutes: 30 }) - - it('defaults to displaying time for Pacific, India, and UTC', () => { - expect(timeFormattedToMultipleZones(time)).to.contain(pacific, india, utc) - }) - - it('has the days the time occured in', () => { - expect(timeFormattedToMultipleZones(time)).to.contain(today, tomorrow) - }) - - it('groups times by day', () => { - let expected = '1970-01-01 10:30:00 PT, 18:30:00 UTC; 1970-01-02 00:00:00 IST' - - expect(timeFormattedToMultipleZones(time)).to.eql(expected) - }) - }) -}) From 1996353ad8a7666980685465901d318aff79b353 Mon Sep 17 00:00:00 2001 From: Daniel Jurek Date: Wed, 25 Apr 2018 16:18:43 -0700 Subject: [PATCH 9/9] Passing tests, cleaned up ErrorMessage tests to use snapshots --- src/actions/timePreferencesActions.js | 2 +- src/components/TopNav/Preferences.js | 2 +- src/components/elements/TimeDisplay.js | 11 +- src/helpers/timeFormattedToMultipleZones.js | 2 +- src/reducers/timePreferencesReducer.js | 4 +- test/components/elements/ErrorMessageTest.js | 83 ++++++-------- .../elements/ErrorMessageTest.js.snap | 102 ++++++++++++++++++ test/components/elements/TimeDisplayTest.js | 94 ++++++++++++++++ .../elements/TimeDisplayTest.js.snap | 16 +++ 9 files changed, 258 insertions(+), 58 deletions(-) create mode 100644 test/components/elements/ErrorMessageTest.js.snap create mode 100644 test/components/elements/TimeDisplayTest.js.snap diff --git a/src/actions/timePreferencesActions.js b/src/actions/timePreferencesActions.js index e8681af..d054b55 100644 --- a/src/actions/timePreferencesActions.js +++ b/src/actions/timePreferencesActions.js @@ -3,4 +3,4 @@ export const SET_DISPLAY_TIMEZONES = 'SET_DISPLAY_TIMEZONES' export const setDisplayTimezones = (timezoneIanaNames) => ({ type: SET_DISPLAY_TIMEZONES, timezoneIanaNames -}) \ No newline at end of file +}) diff --git a/src/components/TopNav/Preferences.js b/src/components/TopNav/Preferences.js index d658654..dc0f30f 100644 --- a/src/components/TopNav/Preferences.js +++ b/src/components/TopNav/Preferences.js @@ -57,7 +57,7 @@ const isTimezoneChecked = (ianaZoneName, selectedTimezones) => export const mapStateToEventFilterPreferencesProps = (state) => ({ currentEventFilterObject: state.events.filter, currentEventFilterPreference: state.signalR.filterPreferences.eventFilterType, - selectedTimezones: state.timePreferences.timePreference + selectedTimezones: state.timePreferences.displayTimezones }) export const ConnectedEventFilterPreferences = connect(mapStateToEventFilterPreferencesProps)(EventFilterPreferences) diff --git a/src/components/elements/TimeDisplay.js b/src/components/elements/TimeDisplay.js index ecd69ac..3596b66 100644 --- a/src/components/elements/TimeDisplay.js +++ b/src/components/elements/TimeDisplay.js @@ -4,7 +4,7 @@ import { DateTime } from 'luxon' import { timeAndDateFormat } from 'helpers/timeFormattedToMultipleZones' -const TimeDisplay = ({time, timezones}) => { +export const TimeDisplayComponent = ({time, timezones}) => { if (!time) { return null } @@ -20,7 +20,8 @@ const TimeDisplay = ({time, timezones}) => { ) } -export const convertedTime = (time, timezone) => +// TODO: Test convertedTime, especially w/ invalid DateTime values +const convertedTime = (time, timezone) => parseTime(time) .setZone(timezone) .toLocaleString(timeAndDateFormat) @@ -30,9 +31,9 @@ export const parseTime = (time) => ? time : DateTime.fromISO(time) -const mapStateToProps = (state, ownProps) => ({ - timezones: state.timePreferences.timePreference, +export const mapStateToProps = (state, ownProps) => ({ + timezones: state.timePreferences.displayTimezones, time: ownProps.time }) -export default connect(mapStateToProps)(TimeDisplay) +export default connect(mapStateToProps)(TimeDisplayComponent) diff --git a/src/helpers/timeFormattedToMultipleZones.js b/src/helpers/timeFormattedToMultipleZones.js index 790952a..1856ffd 100644 --- a/src/helpers/timeFormattedToMultipleZones.js +++ b/src/helpers/timeFormattedToMultipleZones.js @@ -14,4 +14,4 @@ const dateFormat = { day: '2-digit' } -export const timeAndDateformat = Object.assign(dateFormat, DateTime.TIME_24_WITH_SECONDS) +export const timeAndDateFormat = Object.assign(dateFormat, DateTime.TIME_24_WITH_SECONDS) diff --git a/src/reducers/timePreferencesReducer.js b/src/reducers/timePreferencesReducer.js index 00775e2..6f804e2 100644 --- a/src/reducers/timePreferencesReducer.js +++ b/src/reducers/timePreferencesReducer.js @@ -6,7 +6,7 @@ import * as timePreferencesActions from 'actions/timePreferencesActions' const defaultDisplayTimezones = ['UTC'] -export const timePreferencesReducer = (state = defaultDisplayTimezones, action) => { +export const displayTimezonesReducer = (state = defaultDisplayTimezones, action) => { switch (action.type) { case timePreferencesActions.SET_DISPLAY_TIMEZONES: // Require that one timezone is always configured. Force the default @@ -26,5 +26,5 @@ const persistConfigs = { } export default persistReducer( persistConfigs, - combineReducers({timePreference: timePreferencesReducer}) + combineReducers({displayTimezones: displayTimezonesReducer}) ) diff --git a/test/components/elements/ErrorMessageTest.js b/test/components/elements/ErrorMessageTest.js index 8a8b68e..d735f50 100644 --- a/test/components/elements/ErrorMessageTest.js +++ b/test/components/elements/ErrorMessageTest.js @@ -1,77 +1,64 @@ 'use strict' +import React from 'react' import { expect } from 'chai' import { DateTime } from 'luxon' - -import createComponent from 'test/helpers/shallowRenderHelper' +import { shallow } from 'enzyme' +import 'test/helpers/configureEnzyme' +import toJson from 'enzyme-to-json' +import { describeSnapshot } from 'test/helpers/describeSnapshot' import ErrorMessage from 'components/elements/ErrorMessage' -import { Card, CardHeader } from 'material-ui/Card' -import ErrorIcon from 'material-ui/svg-icons/alert/error' -import { RetryButton } from 'components/elements/Buttons' describe('ErrorMessage', function () { context('when inputs are valid', function () { describe('when given only a message', function () { - const testObject = createComponent(ErrorMessage, {message: 'TestMessage'}) - - it('Should render a Card with an ErrorIcon icon and a CardHeader with the given message', function () { - expect(testObject.type).to.equal(Card) - expect(testObject.props.children[0].type).to.equal(ErrorIcon) - expect(testObject.props.children[1].type).to.equal(CardHeader) - expect(testObject.props.children[1].props.title).to.equal('TestMessage') - }) + const testObject = shallow() - it('should render a card with a CardHeader with an empty subtitle', function () { - expect(testObject.props.children[1].props.subtitle).to.be.empty + describeSnapshot(function () { + it('should match snapshot', function () { + expect(toJson(testObject)).to.matchSnapshot() + }) }) }) describe('when given a message and provided an action for retry', function () { - const testObject = createComponent(ErrorMessage, {message: 'TestMessage', actionForRetry: 'TestAction'}) + const testObject = shallow() - it('Should additionally render a retry button', function () { - expect(testObject.props.children[2].type).to.equal(RetryButton) - expect(testObject.props.children[2].props.actionForRetry).to.equal('TestAction') + describeSnapshot(function () { + it('should match snapshot', function () { + expect(toJson(testObject)).to.matchSnapshot() + }) }) }) describe('when given a time', function () { - const testTime = DateTime.utc() - const testObject = createComponent(ErrorMessage, {time: testTime}) - - it('should render a card with a CardHeader with the given time as a subtitle', function () { - expect(testObject.props.children[1].props.subtitle).to.equal(testTime.toLocal().toFormat(DateTime.TIME_WITH_SECONDS)) - }) - }) - - describe('when given a background color', function () { - const backgroundColor = 'Purple' - const testObject = createComponent(ErrorMessage, {backgroundColor}) - - it('should render a card styled with that background color', function () { - expect(testObject.props.style).to.deep.equal({ backgroundColor: 'Purple' }) + const testTime = DateTime.fromObject({ + year: 2018, + month: 4, + day: 25, + hour: 15, + minute: 39, + second: 22, + zone: 'America/Los_Angeles' }) - }) - }) - context('when inputs are not valid', function () { - describe('when time is undefined', function () { - const testTime = undefined - const testObject = createComponent(ErrorMessage, {time: testTime}) + const testObject = shallow() - it('should return an empty subtitle', function () { - expect(testObject.props.children[1].props.subtitle).to.be.empty + describeSnapshot(function () { + it('should match snapshot', function () { + expect(toJson(testObject)).to.matchSnapshot() + }) }) }) - describe('when time is null', function () { - const testTime = undefined - const testObject = createComponent(ErrorMessage, {time: testTime}) - - it('should return an empty subtitle', function () { - expect(testObject.props.children[1].props.subtitle).to.be.empty + describe('when given a background color', function () { + const backgroundColor = 'Purple' + const testObject = shallow() + describeSnapshot(function () { + it('should match snapshot', function () { + expect(toJson(testObject)).to.matchSnapshot() + }) }) }) }) - }) diff --git a/test/components/elements/ErrorMessageTest.js.snap b/test/components/elements/ErrorMessageTest.js.snap new file mode 100644 index 0000000..13d42cf --- /dev/null +++ b/test/components/elements/ErrorMessageTest.js.snap @@ -0,0 +1,102 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ErrorMessage when inputs are valid when given a background color snapshot should match snapshot 1`] = ` + + + + } + /> + +`; + +exports[`ErrorMessage when inputs are valid when given a message and provided an action for retry snapshot should match snapshot 1`] = ` + + + + } + title="TestMessage" + /> + + +`; + +exports[`ErrorMessage when inputs are valid when given a time snapshot should match snapshot 1`] = ` + + + + } + /> + +`; + +exports[`ErrorMessage when inputs are valid when given only a message snapshot should match snapshot 1`] = ` + + + + } + title="TestMessage" + /> + +`; diff --git a/test/components/elements/TimeDisplayTest.js b/test/components/elements/TimeDisplayTest.js index e69de29..e52cbca 100644 --- a/test/components/elements/TimeDisplayTest.js +++ b/test/components/elements/TimeDisplayTest.js @@ -0,0 +1,94 @@ +import React from 'react' +import { expect } from 'chai' +import { shallow } from 'enzyme' +import toJson from 'enzyme-to-json' +import { DateTime } from 'luxon' + + +import { describeSnapshot } from 'test/helpers/describeSnapshot' + +import { + TimeDisplayComponent, + parseTime, + mapStateToProps +} from 'components/elements/TimeDisplay' + +describe('TimeDisplay', function () { + const validTime = DateTime.fromObject({ + year: 2018, + month: 4, + day: 25, + hour: 15, + minute: 39, + second: 22, + zone: 'America/Los_Angeles' + }) + + describeSnapshot(function () { + it('should match snapshot', function () { + expect(toJson(shallow())) + .to.matchSnapshot() + }) + }) + + describe('when time is null', function () { + it('should return null', function () { + expect(toJson(shallow())) + .to.matchSnapshot() + }) + }) + + describe('parseTime', function () { + describe('when input is valid', function () { + it('should return value when given a DateTime', function () { + expect(parseTime(DateTime.utc(2018, 4, 25)).isValid).to.be.true + }) + + it('should return parsed ISO string when given ISO string', function () { + const validIsoTime = '2008-09-15T15:53:00+05:00' + + expect(parseTime(validIsoTime).isValid).to.be.true + }) + }) + + describe('when input is invalid', function () { + it('isValid should be false when given an integer', function () { + expect(parseTime(123).isValid).to.be.false + }) + + it('isValid should be false when given a non-ISO time string', function () { + expect(parseTime('not an ISO string').isValid).to.be.false + }) + }) + }) + + describe('mapStateToProps', function () { + const validStateObject = { + timePreferences: { + displayTimezones: ['UTC', 'America/Los_Angeles', 'other', 'timezones'] + } + } + const validOwnProps = { + time: 'time object' + } + + it('should return the correct timezones', function () { + const testState = mapStateToProps( + validStateObject, + validOwnProps + ) + + expect(testState.timezones).to.be.an('array') + .to.have.same.members(validStateObject.timePreferences.displayTimezones) + }) + + it('should get time from ownProps', function () { + const testState = mapStateToProps( + validStateObject, + validOwnProps + ) + + expect(testState.time).to.equal(validOwnProps.time) + }) + }) +}) diff --git a/test/components/elements/TimeDisplayTest.js.snap b/test/components/elements/TimeDisplayTest.js.snap new file mode 100644 index 0000000..1968656 --- /dev/null +++ b/test/components/elements/TimeDisplayTest.js.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TimeDisplay snapshot should match snapshot 1`] = ` +
+
+ ( + UTC + ): + 04/25/2018, 22:39:22 +
+
+`; + +exports[`TimeDisplay snapshot should match snapshot 2`] = `""`;