mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +01:00
feat(timezone): Timezone should be configurable
This commit is contained in:
parent
dedcced8d4
commit
920872512c
6
frontend/.gitignore
vendored
6
frontend/.gitignore
vendored
@ -29,6 +29,7 @@ build/Release
|
||||
# Dependency directories
|
||||
node_modules
|
||||
jspm_packages
|
||||
package-lock.json
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
@ -40,3 +41,8 @@ typings/
|
||||
|
||||
# Built
|
||||
dist
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
|
||||
.DS_Store
|
||||
|
@ -32,6 +32,7 @@
|
||||
"start": "NODE_ENV=development webpack-dev-server --progress --colors",
|
||||
"start:heroku": "UNLEASH_API=http://unleash.herokuapp.com npm run start",
|
||||
"lint": "eslint . --ext js,jsx",
|
||||
"lint:fix": "eslint . --ext js,jsx --fix",
|
||||
"test": "jest",
|
||||
"test:ci": "npm run lint && npm run build && npm run test",
|
||||
"prepublish": "npm run build"
|
||||
|
BIN
frontend/public/en-GB.png
Normal file
BIN
frontend/public/en-GB.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
BIN
frontend/public/nb-NO.png
Normal file
BIN
frontend/public/nb-NO.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
BIN
frontend/public/unkown-user.png
Normal file
BIN
frontend/public/unkown-user.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
BIN
frontend/public/us-US.png
Normal file
BIN
frontend/public/us-US.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
@ -1,7 +1,11 @@
|
||||
import { formatFullDateTime } from '../util';
|
||||
import { formatFullDateTimeWithLocale } from '../util';
|
||||
|
||||
test('formats dates correctly', () => {
|
||||
expect(formatFullDateTime(1487861809466)).toEqual('2017-02-23 14:56:49');
|
||||
expect(formatFullDateTime(1487232809466)).toEqual('2017-02-16 08:13:29');
|
||||
expect(formatFullDateTime(1477232809466)).toEqual('2016-10-23 14:26:49');
|
||||
expect(formatFullDateTimeWithLocale(1487861809466, 'nb-NO')).toEqual('2017-02-23 14:56:49');
|
||||
expect(formatFullDateTimeWithLocale(1487232809466, 'nb-NO')).toEqual('2017-02-16 08:13:29');
|
||||
expect(formatFullDateTimeWithLocale(1477232809466, 'nb-NO')).toEqual('2016-10-23 14:26:49');
|
||||
|
||||
expect(formatFullDateTimeWithLocale(1487861809466, 'us-US')).toEqual('2017-02-23 09:56:49');
|
||||
expect(formatFullDateTimeWithLocale(1487232809466, 'us-US')).toEqual('2017-02-16 03:13:29');
|
||||
expect(formatFullDateTimeWithLocale(1477232809466, 'us-US')).toEqual('2016-10-23 10:26:49');
|
||||
});
|
||||
|
@ -5,7 +5,26 @@ const dateTimeOptions = {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
timeZone: 'UTC',
|
||||
};
|
||||
// todo for a more comprehensive list use of moment.tz from https://github.com/moment/moment-timezone
|
||||
const predefinedLocale = [
|
||||
{
|
||||
locale: 'nb-NO',
|
||||
timezone: 'UTC',
|
||||
},
|
||||
{
|
||||
locale: 'us-US',
|
||||
timezone: 'America/New_York',
|
||||
},
|
||||
{
|
||||
locale: 'en-GB',
|
||||
timezone: 'Europe/London',
|
||||
},
|
||||
];
|
||||
export const formatFullDateTimeWithLocale = (v, locale) => {
|
||||
let found = predefinedLocale.find(v => v.locale === locale);
|
||||
dateTimeOptions.timeZone = found ? found.timezone : 'UTC';
|
||||
return new Date(v).toLocaleString(locale, dateTimeOptions);
|
||||
};
|
||||
|
||||
export const formatFullDateTime = v => new Date(v).toLocaleString('nb-NO', dateTimeOptions);
|
||||
export const formatFullDateTime = v => formatFullDateTimeWithLocale(v, 'nb-NO');
|
||||
|
@ -4,7 +4,7 @@ import { Grid, Cell, Icon, Chip, ChipContact } from 'react-mdl';
|
||||
import Progress from './progress';
|
||||
import { Link } from 'react-router';
|
||||
import { AppsLinkList, calc } from '../common';
|
||||
import { formatFullDateTime } from '../common/util';
|
||||
import { formatFullDateTimeWithLocale } from '../common/util';
|
||||
import styles from './metrics.scss';
|
||||
|
||||
const StrategyChipItem = ({ strategy }) => (
|
||||
@ -38,13 +38,16 @@ export default class MetricComponent extends React.Component {
|
||||
featureToggle: PropTypes.object.isRequired,
|
||||
fetchSeenApps: PropTypes.func.isRequired,
|
||||
fetchFeatureMetrics: PropTypes.func.isRequired,
|
||||
location: PropTypes.object,
|
||||
};
|
||||
|
||||
componentWillMount() {
|
||||
this.props.fetchSeenApps();
|
||||
this.props.fetchFeatureMetrics();
|
||||
}
|
||||
|
||||
formatFulldateTime(v) {
|
||||
return formatFullDateTimeWithLocale(v, this.props.location.locale);
|
||||
}
|
||||
render() {
|
||||
const { metrics = {}, featureToggle } = this.props;
|
||||
const {
|
||||
@ -107,7 +110,7 @@ export default class MetricComponent extends React.Component {
|
||||
</div>
|
||||
)}
|
||||
<AppsLinkList apps={seenApps} />
|
||||
<span>Created {formatFullDateTime(featureToggle.createdAt)}</span>
|
||||
<span>Created {this.formatFulldateTime(featureToggle.createdAt)}</span>
|
||||
</Cell>
|
||||
</Grid>
|
||||
<hr />
|
||||
|
@ -23,6 +23,7 @@ function getMetricsForToggle(state, toggleName) {
|
||||
export default connect(
|
||||
(state, props) => ({
|
||||
metrics: getMetricsForToggle(state, props.featureToggle.name),
|
||||
location: state.settings.toJS().location || {},
|
||||
}),
|
||||
{
|
||||
fetchFeatureMetrics,
|
||||
|
@ -4,7 +4,7 @@ import HistoryItemDiff from './history-item-diff';
|
||||
import HistoryItemJson from './history-item-json';
|
||||
import { Table, TableHeader } from 'react-mdl';
|
||||
import { DataTableHeader, SwitchWithLabel, styles as commonStyles } from '../common';
|
||||
import { formatFullDateTime } from '../common/util';
|
||||
import { formatFullDateTimeWithLocale } from '../common/util';
|
||||
|
||||
import styles from './history.scss';
|
||||
|
||||
@ -13,13 +13,16 @@ class HistoryList extends Component {
|
||||
title: PropTypes.string,
|
||||
history: PropTypes.array,
|
||||
settings: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
updateSetting: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
toggleShowDiff() {
|
||||
this.props.updateSetting('showData', !this.props.settings.showData);
|
||||
}
|
||||
|
||||
formatFulldateTime(v) {
|
||||
return formatFullDateTimeWithLocale(v, this.props.location.locale);
|
||||
}
|
||||
render() {
|
||||
const showData = this.props.settings.showData;
|
||||
const { history } = this.props;
|
||||
@ -62,7 +65,12 @@ class HistoryList extends Component {
|
||||
User
|
||||
</TableHeader>
|
||||
<TableHeader name="diff">Diff</TableHeader>
|
||||
<TableHeader numeric name="createdAt" cellFormatter={formatFullDateTime} style={{ width: '165px' }}>
|
||||
<TableHeader
|
||||
numeric
|
||||
name="createdAt"
|
||||
cellFormatter={this.formatFulldateTime.bind(this)}
|
||||
style={{ width: '165px' }}
|
||||
>
|
||||
Time
|
||||
</TableHeader>
|
||||
</Table>
|
||||
|
@ -4,9 +4,10 @@ import { updateSettingForGroup } from '../../store/settings/actions';
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const settings = state.settings.toJS().history || {};
|
||||
|
||||
const location = state.settings.toJS().location || {};
|
||||
return {
|
||||
settings,
|
||||
location,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -5,19 +5,36 @@ import styles from './user.scss';
|
||||
export default class ShowUserComponent extends React.Component {
|
||||
static propTypes = {
|
||||
profile: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
fetchUser: PropTypes.func.isRequired,
|
||||
updateSettingLocation: PropTypes.func.isRequired,
|
||||
};
|
||||
possibleLocales = ['nb-NO', 'us-US', 'en-GB'];
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchUser();
|
||||
}
|
||||
|
||||
updateLocale() {
|
||||
const locale = this.props.location ? this.props.location.locale : this.possibleLocales[0];
|
||||
let index = this.possibleLocales.findIndex(v => v === locale);
|
||||
index = (index + 1) % this.possibleLocales.length;
|
||||
this.props.updateSettingLocation('locale', this.possibleLocales[index]);
|
||||
}
|
||||
|
||||
render() {
|
||||
const email = this.props.profile ? this.props.profile.email : '';
|
||||
const imageUrl = email ? this.props.profile.imageUrl : '';
|
||||
const locale = this.props.location ? this.props.location.locale : this.possibleLocales[0];
|
||||
const imageUrl = email ? this.props.profile.imageUrl : 'public/unkown-user.png';
|
||||
const imageLocale = `public/${locale}.png`;
|
||||
return (
|
||||
<div className={styles.showUser}>
|
||||
<img src={imageUrl} title={email} alt={email} />
|
||||
<div className={styles.showUserSettings}>
|
||||
<div className={styles.showLocale}>
|
||||
<img src={imageLocale} title={locale} alt={locale} onClick={this.updateLocale.bind(this)} />
|
||||
</div>
|
||||
<div className={styles.showUser}>
|
||||
<img src={imageUrl} title={email} alt={email} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
import { connect } from 'react-redux';
|
||||
import ShowUserComponent from './show-user-component';
|
||||
import { fetchUser } from '../../store/user/actions';
|
||||
import { updateSettingForGroup } from '../../store/settings/actions';
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchUser,
|
||||
updateSettingLocation: updateSettingForGroup('location'),
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
profile: state.user.get('profile'),
|
||||
location: state.settings ? state.settings.toJS().location : {},
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ShowUserComponent);
|
||||
|
@ -2,4 +2,14 @@
|
||||
border-radius: 25px;
|
||||
height: 32px;
|
||||
border: 2px solid #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.showLocale img {
|
||||
border-radius: 2px;
|
||||
height: 20px;
|
||||
border: 2px solid #ffffff;
|
||||
}
|
||||
.showUserSettings {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user