Skip to content
This repository has been archived by the owner on Mar 13, 2024. It is now read-only.

Timezone feature #696

Merged
merged 9 commits into from
Apr 2, 2018
Merged

Conversation

csduarte
Copy link
Contributor

@csduarte csduarte commented Feb 1, 2018

Summary

  1. Update timezone both manually and automatic
  2. If chosen to update automatically, it updates after log in or when new session begins
  3. List of SupportedTimezones are pulled from Config

mattermost/mattermost-mobile#1456
mattermost/mattermost#8185

Ticket Link

PLT-541 Add a "Time zone" field to account settings - Mattermost

Checklist

  • Ran make check-style to check for style errors (required for all pull requests)
  • Ran make test to ensure unit and component tests passed
  • Has server changes (please link)
  • Has UI changes
  • Includes text changes and localization file (.../i18n/en.json) updates

Screenshots

manual timezone selection

screen shot 2018-02-01 at 11 57 15 am

automatic timezone selection

manual

Feature in action

manual

set_manual_timezone

automatic

set_automatic_timezone

@jasonblais jasonblais added 1: PM Review Requires review by a product manager Setup Old Test Server Triggers the creation of a test server labels Feb 2, 2018
@miguelespinoza
Copy link

Depends on this server PR: mattermost/mattermost#8185

@esethna esethna added the Do Not Merge/Awaiting PR Awaiting another pull request before merging (e.g. server changes) label Feb 7, 2018
@miguelespinoza miguelespinoza force-pushed the timezone-feature branch 2 times, most recently from 3b816f6 to ae2ac79 Compare February 13, 2018 16:03
@miguelespinoza
Copy link

screen shot 2018-02-06 at 5 27 22 pm

Latest changes include:

  • localized post timestamps
  • user local time in profile pop-over

Copy link
Member

@jwilander jwilander left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good for the most part, just a few comments

return dateTimeFormat.resolvedOptions().timeZone;
}

export function autoUpdateTimezone(userTimezone) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple suggestions/comments here:

  1. Is any of this logic shared by the mobile app? Wonder if it would make sense to put it into mattermost-redux
  2. Since an action is getting called here, I'd prefer if we just turned this function into an action itself

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doable, I currently have a PR for timezone support so adding selectors/actions for this won't be hard.

I agree this logic is shared with both webapp and mobile

@@ -459,6 +459,22 @@ class UserStoreClass extends EventEmitter {
return this.getStatuses()[id] || UserStatuses.OFFLINE;
}

getTimezone(id) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you turn this into a selector instead? Preferably in mattermost-redux since I figure the mobile apps are going to need this as well

Then the timezone can get passed in as a prop to the components that need it instead of importing UserStore (which we're trying to reduce usage of so we can remove it)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would definitely make sense as a selector. Something like this would be nice and simple.

const getCurrentTimezone = createSelector(
    getCurrentUser,
    (currentUser) => {
        ...
    }
);

If we ever care about the timezone of another user, we could add one that's more detailed.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

import React from 'react';
import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';

import {updateUser} from 'actions/user_actions.jsx';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big deal, but it would be a bit nicer to pass in the mattermost-redux update user action through an index.js container so we can eventually get rid of the wrapper actions like this one

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwilander does the redux library support migrating away from these wrapper actions?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, missed your comment. Yeah if the actions are already in mattermost-redux then you can just pass it in directly and if you need to get results right away from the action, just await on it instead of passing callbacks like we do for the wrapper functions

useAutomaticTimezone: PropTypes.bool.isRequired,
automaticTimezone: PropTypes.string.isRequired,
manualTimezone: PropTypes.string.isRequired,
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind defining the propTypes at the top of the class like so:

export default class ManageTimezones extends React.Component {
  static propTypes = {
     // props here
  }
  // other stuff
}

const dateTimeFormat = new Intl.DateTimeFormat();

export function getSupportedTimezones() {
if (global.mm_config && global.mm_config.SupportedTimezones) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are removing all global.mm_config usage in the application, you should use something like const config = getConfig(store.getState())

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although getConfig(store.getState()) is a correct workaround given that global.mm_config is now gone, note that it won't connect a component to future changes in the SupportedTimezones. We're aiming to eventually remove the required refresh when a configuration change occurs.

If possible, pass in the config.supportedTimezones to this method and invoke it from the connector. If that's not straightforward, the approach @jespino describes will suffice for now :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be turned into a selector in the redux library

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been moved to a selector 👍

@@ -45,17 +52,35 @@ export default class PostTime extends React.PureComponent {
};
}

getCurrentUserTimezone = () => {
const userId = UserStore.getCurrentId();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CurrentUserId is better passed through the redux connect, as a property.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In particular, this may not trigger a re-render when the time zone changes, unless the PostTime is re-rendered for another reason.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. These should be passed through from the redux store. We shouldn't be adding new code that uses the old stores as long as we can help it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm noticing a pattern with classes that seem to be getting deprecated, examples:
user_store, user_actions.

Is there a document that describes which files to stay away from? that way contributors are able to understand which files are getting deprecated and can work with new best practices

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's kind of documented here but we should definitely do a better job of it. Would adding comments at the top of these files help?

let minute = date.getMinutes();
minute = minute >= 10 ? minute : ('0' + minute);
let time = '';
// const militaryTime = this.props.useMilitaryTime;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commented lines can be removed because the military time is already solved by the toLocaleString.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if (this.props.enableTimezone) {
const currentTimezone = getCurrentTimezone(this.props.user.timezone);
if (currentTimezone) {
const useMilitaryTime = PreferenceStore.getBool(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably is better option to receive this from the redux connect as a property.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -45,17 +52,35 @@ export default class PostTime extends React.PureComponent {
};
}

getCurrentUserTimezone = () => {
const userId = UserStore.getCurrentId();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In particular, this may not trigger a re-render when the time zone changes, unless the PostTime is re-rendered for another reason.

/*
* Flag for enabling the timezone feature
*/
enableTimezone: PropTypes.bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mark .isRequired?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

const useMilitaryTime = PreferenceStore.getBool(Constants.Preferences.CATEGORY_DISPLAY_SETTINGS, 'use_military_time');
const date = new Date();
dataContent.push(
<span>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to factor out a <DateTime> component that connects the useMilitaryTime and timezone preferences vs. having to pass them into every component that needs to render a time?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that would be a nice refactor 👍

i18n/en.json Outdated
@@ -2785,6 +2786,9 @@
"user.settings.import_theme.submitError": "Invalid format, please try copying and pasting in again.",
"user.settings.languages.change": "Change interface language",
"user.settings.languages.promote": "Select which language Mattermost displays in the user interface.<br /><br />Would like to help with translations? Join the <a href='https://translate.mattermost.com/' target='_blank'>Mattermost Translation Server</a> to contribute.",
"user.settings.timezones.automatic": "Set automatically",
"user.settings.timezones.change": "Change timezone",
"user.settings.timezones.promote": "Select the time zone used for timestamps in the user interface and email notifications.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

time zone vs. timezone? I think they are both fungible, but perhaps we converge on one usage?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch, I'll fix that. We can go with timezone to be consistent

const dateTimeFormat = new Intl.DateTimeFormat();

export function getSupportedTimezones() {
if (global.mm_config && global.mm_config.SupportedTimezones) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although getConfig(store.getState()) is a correct workaround given that global.mm_config is now gone, note that it won't connect a component to future changes in the SupportedTimezones. We're aiming to eventually remove the required refresh when a configuration change occurs.

If possible, pass in the config.supportedTimezones to this method and invoke it from the connector. If that's not straightforward, the approach @jespino describes will suffice for now :)

}

export function getBrowserTimezone() {
return dateTimeFormat.resolvedOptions().timeZone;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did some quick tests with this JSFiddle and IE 11.15 displays "undefined" for this JS API so we'll need a fallback to support it.

@jasonblais Can you please confirm/disconfirm support for IE 11.15?

latest-ie11

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Martin. I had missed your note until now. I'll queue for our PM meeting to discuss this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed and we officially support IE11, hence IE 11.15 is included

import SuggestionList from 'components/suggestion/suggestion_list.jsx';
import TimezoneProvider from 'components/suggestion/timezone_provider.jsx';

export default class ManageTimezones extends React.Component {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a PureComponent

} = this.state;

const timezone = {
useAutomaticTimezone: useAutomaticTimezone.toString(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why store this as a string version of the boolean? That seems like something that'll be easy to get mixed up

if (profile && profile.timezone) {
return {
...profile.timezone,
useAutomaticTimezone: profile.timezone.useAutomaticTimezone === 'true',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mentioned this above, but why switch types here?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hmhealey this is a good point to address the convention MM uses for storing booleans in postgres.

From my investigation, MM has been storing booleans as strings. See: https://github.com/mattermost/mattermost-webapp/blob/master/components/user_settings/notifications/user_settings_notifications.jsx#L138

Looking at the database this is what the notify_props looks like:

{ 
   "channel":"true",
   "comments":"never",
   "desktop":"mention",
   "desktop_duration":"5",
   "desktop_sound":"true",
   "email":"true",
   "first_name":"false",
   "mention_keys":"admin",
   "push":"mention",
   "push_status":"away"
}

I'm following the same convention MM has been using for storing booleans. If this is not the preferred convention, let us know what's the best way to store booleans.

@@ -459,6 +459,22 @@ class UserStoreClass extends EventEmitter {
return this.getStatuses()[id] || UserStatuses.OFFLINE;
}

getTimezone(id) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would definitely make sense as a selector. Something like this would be nice and simple.

const getCurrentTimezone = createSelector(
    getCurrentUser,
    (currentUser) => {
        ...
    }
);

If we ever care about the timezone of another user, we could add one that's more detailed.

};
}

return {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we care whether the timezone is automatic or manual in most places? It might make sense to just have a function that returns the timezone string without the rest of the object.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is useful for the settings where we do need reference to the entire object, I agree that it would be great to only return a timezone string.

That's what the selector will be used for. Maybe it would be great to distinguish between settingsTimezone object and userTimezone string in the function name

const dateTimeFormat = new Intl.DateTimeFormat();

export function getSupportedTimezones() {
if (global.mm_config && global.mm_config.SupportedTimezones) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be turned into a selector in the redux library

}
}

export function getCurrentTimezone(userTimezone) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, could this be turned into a selector, possibly combined with the one I was talking about before? Outside of the settings modal, I can't think of any code that will care about the automatic vs manual timezone, so I don't think that needs to be passed around

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, this will be turned into a selector 👍

@hmhealey
Copy link
Member

@miguelespinoza You've got some style issues

$ eslint --ignore-pattern node_modules --ignore-pattern non_npm_dependencies --ignore-pattern dist --ext .js --ext .jsx . --quiet

/home/ubuntu/jenkins/workspace/mattermost-webapp_PR-696-OEE243NI6PR4ZUMUJ7SYBS2Y3FEIJCBHYFSQEGT7HFMZN5HQ633Q/components/local_date_time/index.js
  8:31  error  Unable to resolve path to module 'mattermost-redux/selectors/entities/timezone'  import/no-unresolved
  9:38  error  Unable to resolve path to module 'mattermost-redux/utils/timezone_utils'         import/no-unresolved

/home/ubuntu/jenkins/workspace/mattermost-webapp_PR-696-OEE243NI6PR4ZUMUJ7SYBS2Y3FEIJCBHYFSQEGT7HFMZN5HQ633Q/components/logged_in/index.js
  6:34  error  Unable to resolve path to module 'mattermost-redux/actions/timezone'  import/no-unresolved

/home/ubuntu/jenkins/workspace/mattermost-webapp_PR-696-OEE243NI6PR4ZUMUJ7SYBS2Y3FEIJCBHYFSQEGT7HFMZN5HQ633Q/components/suggestion/timezone_provider.jsx
  6:33  error  Unable to resolve path to module 'mattermost-redux/utils/timezone_utils'  import/no-unresolved

/home/ubuntu/jenkins/workspace/mattermost-webapp_PR-696-OEE243NI6PR4ZUMUJ7SYBS2Y3FEIJCBHYFSQEGT7HFMZN5HQ633Q/components/user_settings/display/index.js
  10:31  error  Unable to resolve path to module 'mattermost-redux/selectors/entities/timezone'  import/no-unresolved
  11:38  error  Unable to resolve path to module 'mattermost-redux/utils/timezone_utils'         import/no-unresolved

/home/ubuntu/jenkins/workspace/mattermost-webapp_PR-696-OEE243NI6PR4ZUMUJ7SYBS2Y3FEIJCBHYFSQEGT7HFMZN5HQ633Q/components/user_settings/display/manage_timezones.jsx
  6:33  error  Unable to resolve path to module 'mattermost-redux/utils/timezone_utils'  import/no-unresolved

/home/ubuntu/jenkins/workspace/mattermost-webapp_PR-696-OEE243NI6PR4ZUMUJ7SYBS2Y3FEIJCBHYFSQEGT7HFMZN5HQ633Q/components/user_settings/display/user_settings_display.jsx
  6:33  error  Unable to resolve path to module 'mattermost-redux/utils/timezone_utils'  import/no-unresolved

✖ 8 problems (8 errors, 0 warnings)

@miguelespinoza
Copy link

@esethna here's the server PR for reference: mattermost/mattermost#8560

@esethna esethna removed the Setup Old Test Server Triggers the creation of a test server label Mar 30, 2018
@mattermost mattermost deleted a comment from mattermod Mar 30, 2018
@mattermost mattermost deleted a comment from mattermod Mar 30, 2018
@mattermost mattermost deleted a comment from mattermod Mar 30, 2018
@esethna esethna added the Setup Old Test Server Triggers the creation of a test server label Mar 30, 2018
@miguelespinoza
Copy link

Cool, we will need a dev to switch to ExperimentalTimezone: true to test timezones properly

@GoldUniform
Copy link
Contributor

I've changed that ExperimentalTimezone setting for this spinmint and restarted it.

@esethna
Copy link
Contributor

esethna commented Mar 30, 2018

Hitting an issue with the timezone setting disappearing after setting it. @miguelespinoza looking into it.

mar-30-2018 12-57-17

@miguelespinoza
Copy link

Done, it was a mistake on my part.
It was hiding if the timezone was not set automatically, which didn't account for manual timezone 🤦‍♂️

That change was made after the error that popped up yesterday. For fresh new users, their timezone was never getting automatically set, so I call autoUpdateTimezone one last time in the settings screen to ensure it gets another chance.

@esethna tested with setting automatic and manual timezones.

@jasonblais jasonblais added this to the v4.9.0 milestone Apr 2, 2018
@mattermost mattermost deleted a comment from mattermod Apr 2, 2018
@mattermost mattermost deleted a comment from mattermod Apr 2, 2018
@esethna esethna added Setup Old Test Server Triggers the creation of a test server and removed Setup Old Test Server Triggers the creation of a test server labels Apr 2, 2018
@mattermod
Copy link
Contributor

Setup Test Server label detected. Spinmint test server created if build succeeds (checks pass and no conflicts with base branch).

@esethna
Copy link
Contributor

esethna commented Apr 2, 2018

Thanks @miguelespinoza, testing now

@mattermod
Copy link
Contributor

Spinmint test server created at: https://i-099c369abdaee52e7.spinmint.com

Test Admin Account: Username: sysadmin | Password: sysadmin

Test User Account: Username: user-1 | Password: user-1

Instance ID: i-099c369abdaee52e7

Copy link
Contributor

@esethna esethna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @miguelespinoza!

@esethna esethna removed 1: PM Review Requires review by a product manager Setup Old Test Server Triggers the creation of a test server labels Apr 2, 2018
@hmhealey hmhealey removed the 2: Dev Review Requires review by a core commiter label Apr 2, 2018
@hmhealey hmhealey merged commit 431051f into mattermost:master Apr 2, 2018
@amyblais amyblais added Changelog/Done Required changelog entry has been written Docs/Done Required documentation has been written labels Apr 4, 2018
@lindalumitchell lindalumitchell added the Tests/Done Release tests have been written label Apr 5, 2018
hmhealey pushed a commit that referenced this pull request Aug 28, 2020
- Minor authorship updates
- `serialize-error` adopted a differently formatted version of the MIT license text
hmhealey pushed a commit that referenced this pull request Mar 17, 2021
- Minor authorship updates
- `serialize-error` adopted a differently formatted version of the MIT license text
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Changelog/Done Required changelog entry has been written Docs/Done Required documentation has been written Tests/Done Release tests have been written
Projects
None yet
Development

Successfully merging this pull request may close these issues.