From 86eba8b05d5a3f913ba7e255565e8cd5d20fea35 Mon Sep 17 00:00:00 2001 From: Andy Ladjadj Date: Fri, 17 Apr 2020 20:48:33 +0200 Subject: Add new timezone selector in web interface - the default value keep UTC - the timezone is saved in cookie in zuul_timezone_string - the render format is YYYY-MM-DD HH:mm:ss Change-Id: Ib4ac2af4194ac2722c5574577661f4ddda8cc398 --- .../web-timezone-select-901e4f9ff3aee1f1.yaml | 3 + web/package.json | 2 + web/src/App.jsx | 15 +- web/src/actions/timezone.js | 20 ++ web/src/containers/build/Summary.jsx | 6 +- web/src/containers/timezone/SelectTz.jsx | 142 +++++++++++ web/src/pages/Builds.jsx | 17 +- web/src/pages/Status.jsx | 11 +- web/src/reducers/index.js | 12 +- web/src/reducers/timezone.js | 22 ++ web/yarn.lock | 275 ++++++++++++++++++++- 11 files changed, 505 insertions(+), 20 deletions(-) create mode 100644 releasenotes/notes/web-timezone-select-901e4f9ff3aee1f1.yaml create mode 100644 web/src/actions/timezone.js create mode 100644 web/src/containers/timezone/SelectTz.jsx create mode 100644 web/src/reducers/timezone.js diff --git a/releasenotes/notes/web-timezone-select-901e4f9ff3aee1f1.yaml b/releasenotes/notes/web-timezone-select-901e4f9ff3aee1f1.yaml new file mode 100644 index 000000000..a2afcc829 --- /dev/null +++ b/releasenotes/notes/web-timezone-select-901e4f9ff3aee1f1.yaml @@ -0,0 +1,3 @@ +--- +features: + - Add new timezone selector in web interface diff --git a/web/package.json b/web/package.json index b811c7a24..784aedbdb 100644 --- a/web/package.json +++ b/web/package.json @@ -13,6 +13,7 @@ "lodash": "^4.17.10", "moment": "^2.22.2", "moment-duration-format": "2.3.2", + "moment-timezone": "^0.5.28", "patternfly-react": "^2.13.1", "prop-types": "^15.6.2", "react": "^16.4.2", @@ -23,6 +24,7 @@ "react-router": "^4.3.1", "react-router-dom": "^4.3.1", "react-scripts": "1.1.4", + "react-select": "3.1.0", "redux": "<4.0.0", "redux-thunk": "^2.3.0", "sockette": "^2.0.0", diff --git a/web/src/App.jsx b/web/src/App.jsx index 619cc499e..1b412f572 100644 --- a/web/src/App.jsx +++ b/web/src/App.jsx @@ -31,12 +31,12 @@ import { import * as moment from 'moment' import ErrorBoundary from './containers/ErrorBoundary' +import SelectTz from './containers/timezone/SelectTz' import logo from './images/logo.png' -import { routes } from './routes' +import { clearError } from './actions/errors' import { fetchConfigErrorsAction } from './actions/configErrors' +import { routes } from './routes' import { setTenantAction } from './actions/tenant' -import { clearError } from './actions/errors' - class App extends React.Component { static propTypes = { @@ -44,6 +44,7 @@ class App extends React.Component { configErrors: PropTypes.array, info: PropTypes.object, tenant: PropTypes.object, + timezone: PropTypes.string, location: PropTypes.object, history: PropTypes.object, dispatch: PropTypes.func @@ -166,7 +167,7 @@ class App extends React.Component { type='error' onDismiss={() => {this.props.dispatch(clearError(error.id))}} > - + {error.text} ({error.status})  {error.url} @@ -273,6 +274,9 @@ class App extends React.Component { )} +
  • + +
  • {showErrors && this.renderConfigErrors(configErrors)} @@ -299,6 +303,7 @@ export default withRouter(connect( errors: state.errors, configErrors: state.configErrors, info: state.info, - tenant: state.tenant + tenant: state.tenant, + timezone: state.timezone }) )(App)) diff --git a/web/src/actions/timezone.js b/web/src/actions/timezone.js new file mode 100644 index 000000000..738eb6884 --- /dev/null +++ b/web/src/actions/timezone.js @@ -0,0 +1,20 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +export const TIMEZONE_SET = 'TIMEZONE_SET' + +export function setTimezoneAction (name) { + return { + type: TIMEZONE_SET, + timezone: name + } +} diff --git a/web/src/containers/build/Summary.jsx b/web/src/containers/build/Summary.jsx index c03f24c3c..dbccdbc59 100644 --- a/web/src/containers/build/Summary.jsx +++ b/web/src/containers/build/Summary.jsx @@ -28,6 +28,7 @@ class Summary extends React.Component { static propTypes = { build: PropTypes.object, tenant: PropTypes.object, + timezone: PropTypes.string, } render () { @@ -75,6 +76,9 @@ class Summary extends React.Component { value = 'false' } } + if (column === 'start_time' || column === 'end_time') { + value = moment.utc(value).tz(this.props.timezone).format('YYYY-MM-DD HH:mm:ss') + } if (column === 'duration') { value = moment.duration(value, 'seconds') .format('h [hr] m [min] s [sec]') @@ -124,4 +128,4 @@ class Summary extends React.Component { } -export default connect(state => ({tenant: state.tenant}))(Summary) +export default connect(state => ({tenant: state.tenant, timezone: state.timezone}))(Summary) diff --git a/web/src/containers/timezone/SelectTz.jsx b/web/src/containers/timezone/SelectTz.jsx new file mode 100644 index 000000000..78092d2ab --- /dev/null +++ b/web/src/containers/timezone/SelectTz.jsx @@ -0,0 +1,142 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +import PropTypes from 'prop-types' +import React from 'react' +import Select from 'react-select' +import moment from 'moment-timezone' +import { Icon } from 'patternfly-react' +import { connect } from 'react-redux' +import { setTimezoneAction } from '../../actions/timezone' + +class SelectTz extends React.Component { + static propTypes = { + dispatch: PropTypes.func + } + + state = { + availableTz: moment.tz.names().map(item => ({value: item, label: item})), + defaultValue: {value: 'UTC', label: 'UTC'} + } + + componentDidMount () { + this.loadState() + } + + handleChange = (selectedTz) => { + const tz = selectedTz.value + + this.setCookie('zuul_tz_string', tz) + this.updateState(tz) + } + + setCookie (name, value) { + document.cookie = name + '=' + value + '; path=/' + } + + loadState = () => { + function readCookie (name, defaultValue) { + let nameEQ = name + '=' + let ca = document.cookie.split(';') + for (let i = 0; i < ca.length; i++) { + let c = ca[i] + while (c.charAt(0) === ' ') { + c = c.substring(1, c.length) + } + if (c.indexOf(nameEQ) === 0) { + return c.substring(nameEQ.length, c.length) + } + } + return defaultValue + } + let tz = readCookie('zuul_tz_string', '') + if (tz) { + this.updateState(tz) + } + } + + updateState = (tz) => { + + this.setState({ + currentValue: {value: tz, label: tz} + }) + + let timezoneAction = setTimezoneAction(tz) + this.props.dispatch(timezoneAction) + } + + render() { + const textColor = '#d1d1d1' + const containerStyles= { + border: 'solid #2b2b2b', + borderWidth: '0 0 0 1px', + cursor: 'pointer', + display: 'initial', + fontSize: '11px', + padding: '6px' + } + const iconStyles = { + padding: '5px' + } + const customStyles = { + container: () => ({ + display: 'inline-block', + }), + control: () => ({ + width: 'auto', + display: 'flex' + }), + singleValue: () => ({ + color: textColor, + }), + input: (provided) => ({ + ...provided, + color: textColor + }), + dropdownIndicator:(provided) => ({ + ...provided, + padding: '3px' + }), + indicatorSeparator: () => {}, + menu: (provided) => ({ + ...provided, + width: 'auto', + right: '0', + top: '22px', + }) + } + return ( +
    + +