[web][config] move timezone component to preferences
- move timezone in modal - move state in preference - use patterfly component - update all pages Change-Id: I60bc1665c0660b86489e375c6720ad18ed940c53
This commit is contained in:
parent
da9df89591
commit
5ee9fcb734
|
@ -61,7 +61,6 @@ import {
|
|||
import AuthContainer from './containers/auth/Auth'
|
||||
import ErrorBoundary from './containers/ErrorBoundary'
|
||||
import { Fetching } from './containers/Fetching'
|
||||
import SelectTz from './containers/timezone/SelectTz'
|
||||
import ConfigModal from './containers/config/Config'
|
||||
import logo from './images/logo.svg'
|
||||
import { clearNotification } from './actions/notifications'
|
||||
|
@ -75,8 +74,8 @@ class App extends React.Component {
|
|||
notifications: PropTypes.array,
|
||||
configErrors: PropTypes.array,
|
||||
info: PropTypes.object,
|
||||
preferences: PropTypes.object,
|
||||
tenant: PropTypes.object,
|
||||
timezone: PropTypes.string,
|
||||
location: PropTypes.object,
|
||||
history: PropTypes.object,
|
||||
dispatch: PropTypes.func,
|
||||
|
@ -256,7 +255,7 @@ class App extends React.Component {
|
|||
type={notification.type}
|
||||
onDismiss={() => { this.props.dispatch(clearNotification(notification.id)) }}
|
||||
>
|
||||
<span title={moment.utc(notification.date).tz(this.props.timezone).format()}>
|
||||
<span title={moment.utc(notification.date).tz(this.props.preferences.timezone).format()}>
|
||||
{notificationBody}
|
||||
</span>
|
||||
</TimedToastNotification>
|
||||
|
@ -432,7 +431,6 @@ class App extends React.Component {
|
|||
<BellIcon />
|
||||
</NotificationBadge>
|
||||
}
|
||||
<SelectTz />
|
||||
<ConfigModal />
|
||||
|
||||
{tenant.name && (<AuthContainer />)}
|
||||
|
@ -471,8 +469,8 @@ export default withRouter(connect(
|
|||
notifications: state.notifications,
|
||||
configErrors: state.configErrors,
|
||||
info: state.info,
|
||||
preferences: state.preferences,
|
||||
tenant: state.tenant,
|
||||
timezone: state.timezone,
|
||||
user: state.user
|
||||
})
|
||||
)(App))
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
// 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
|
||||
}
|
||||
}
|
|
@ -41,7 +41,7 @@ import { buildExternalLink, ExternalLink, IconProperty } from '../../Misc'
|
|||
|
||||
import AutoholdModal from '../autohold/autoholdModal'
|
||||
|
||||
function Build({ build, tenant, timezone, user }) {
|
||||
function Build({ build, preferences, tenant, user }) {
|
||||
const [showAutoholdModal, setShowAutoholdModal] = useState(false)
|
||||
const change = build.change ? build.change : ''
|
||||
const ref = build.change ? '' : build.ref
|
||||
|
@ -173,13 +173,13 @@ function Build({ build, tenant, timezone, user }) {
|
|||
<strong>Started at </strong>
|
||||
{moment
|
||||
.utc(build.start_time)
|
||||
.tz(timezone)
|
||||
.tz(preferences.timezone)
|
||||
.format('YYYY-MM-DD HH:mm:ss')}
|
||||
<br />
|
||||
<strong>Completed at </strong>
|
||||
{moment
|
||||
.utc(build.end_time)
|
||||
.tz(timezone)
|
||||
.tz(preferences.timezone)
|
||||
.format('YYYY-MM-DD HH:mm:ss')}
|
||||
</span>
|
||||
}
|
||||
|
@ -284,14 +284,13 @@ function Build({ build, tenant, timezone, user }) {
|
|||
|
||||
Build.propTypes = {
|
||||
build: PropTypes.object,
|
||||
preferences: PropTypes.object,
|
||||
tenant: PropTypes.object,
|
||||
hash: PropTypes.array,
|
||||
timezone: PropTypes.string,
|
||||
user: PropTypes.object,
|
||||
}
|
||||
|
||||
export default connect((state) => ({
|
||||
preferences: state.preferences,
|
||||
tenant: state.tenant,
|
||||
timezone: state.timezone,
|
||||
user: state.user,
|
||||
}))(Build)
|
||||
|
|
|
@ -53,8 +53,8 @@ function BuildTable({
|
|||
builds,
|
||||
fetching,
|
||||
onClearFilters,
|
||||
preferences,
|
||||
tenant,
|
||||
timezone,
|
||||
history,
|
||||
}) {
|
||||
const columns = [
|
||||
|
@ -143,7 +143,7 @@ function BuildTable({
|
|||
{
|
||||
title: moment
|
||||
.utc(build.start_time)
|
||||
.tz(timezone)
|
||||
.tz(preferences.timezone)
|
||||
.format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
{
|
||||
|
@ -245,12 +245,12 @@ BuildTable.propTypes = {
|
|||
builds: PropTypes.array.isRequired,
|
||||
fetching: PropTypes.bool.isRequired,
|
||||
onClearFilters: PropTypes.func.isRequired,
|
||||
preferences: PropTypes.object.isRequired,
|
||||
tenant: PropTypes.object.isRequired,
|
||||
timezone: PropTypes.string.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
export default connect((state) => ({
|
||||
preferences: state.preferences,
|
||||
tenant: state.tenant,
|
||||
timezone: state.timezone,
|
||||
}))(BuildTable)
|
||||
|
|
|
@ -16,20 +16,23 @@ import { connect } from 'react-redux'
|
|||
import {
|
||||
Button,
|
||||
ButtonVariant,
|
||||
Form,
|
||||
FormGroup,
|
||||
Modal,
|
||||
ModalVariant,
|
||||
Switch
|
||||
} from '@patternfly/react-core'
|
||||
import Timezone from './Timezone'
|
||||
import { CogIcon } from '@patternfly/react-icons'
|
||||
import { setPreference } from '../../actions/preferences'
|
||||
|
||||
|
||||
class ConfigModal extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
location: PropTypes.object,
|
||||
tenant: PropTypes.object,
|
||||
preferences: PropTypes.object,
|
||||
timezone: PropTypes.string,
|
||||
remoteData: PropTypes.object,
|
||||
dispatch: PropTypes.func
|
||||
}
|
||||
|
@ -39,7 +42,9 @@ class ConfigModal extends React.Component {
|
|||
this.state = {
|
||||
isModalOpen: false,
|
||||
autoReload: false,
|
||||
timezone: null
|
||||
}
|
||||
|
||||
this.handleModalToggle = () => {
|
||||
this.setState(({ isModalOpen }) => ({
|
||||
isModalOpen: !isModalOpen
|
||||
|
@ -50,6 +55,7 @@ class ConfigModal extends React.Component {
|
|||
this.handleSave = () => {
|
||||
this.handleModalToggle()
|
||||
this.props.dispatch(setPreference('autoReload', this.state.autoReload))
|
||||
this.props.dispatch(setPreference('timezone', this.state.timezone))
|
||||
}
|
||||
|
||||
this.handleAutoReload = () => {
|
||||
|
@ -57,16 +63,21 @@ class ConfigModal extends React.Component {
|
|||
autoReload: !autoReload
|
||||
}))
|
||||
}
|
||||
|
||||
this.handleTimezone = (timezone) => {
|
||||
this.setState({ timezone })
|
||||
}
|
||||
}
|
||||
|
||||
resetState() {
|
||||
this.setState({
|
||||
autoReload: this.props.preferences.autoReload,
|
||||
timezone: this.props.preferences.timezone
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isModalOpen, autoReload } = this.state
|
||||
const { isModalOpen, autoReload, timezone } = this.state
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Button
|
||||
|
@ -91,13 +102,22 @@ class ConfigModal extends React.Component {
|
|||
>
|
||||
<div>
|
||||
<p key="info">Application settings are saved in browser local storage only. They are applied whether authenticated or not.</p>
|
||||
<Switch
|
||||
key="autoreload"
|
||||
id="autoreload"
|
||||
label="Auto reload status page"
|
||||
isChecked={autoReload}
|
||||
onChange={this.handleAutoReload}
|
||||
/>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<Switch
|
||||
key="autoreload"
|
||||
id="autoreload"
|
||||
label="Auto reload status page"
|
||||
isChecked={autoReload}
|
||||
onChange={this.handleAutoReload}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup label="Select result display timezone">
|
||||
<Timezone
|
||||
selected={timezone}
|
||||
onSelect={this.handleTimezone} />
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</div>
|
||||
</Modal>
|
||||
</React.Fragment>
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
// 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 moment from 'moment-timezone'
|
||||
import { connect } from 'react-redux'
|
||||
import { Select, SelectOption, SelectVariant } from '@patternfly/react-core'
|
||||
|
||||
class Timezone extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
selected: PropTypes.string,
|
||||
onSelect: PropTypes.func
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
const defaultSelected = this.props.selected
|
||||
|
||||
this.state = {
|
||||
options: [
|
||||
{ value: "UTC", description: "This is the default timezone." },
|
||||
...moment.tz.names().map(item => ({ value: item }))],
|
||||
isOpen: false,
|
||||
hasOnCreateOption: false,
|
||||
selected: null,
|
||||
}
|
||||
|
||||
this.onToggle = isOpen => {
|
||||
this.setState({
|
||||
isOpen
|
||||
})
|
||||
}
|
||||
|
||||
this.onSelect = (event, selection, isPlaceholder) => {
|
||||
if (isPlaceholder) this.clearSelection()
|
||||
else {
|
||||
this.setState({
|
||||
selected: selection,
|
||||
isOpen: false
|
||||
})
|
||||
this.props.onSelect(selection)
|
||||
}
|
||||
}
|
||||
|
||||
this.clearSelection = () => {
|
||||
this.setState({
|
||||
isOpen: false,
|
||||
selected: null,
|
||||
})
|
||||
this.props.onSelect(defaultSelected)
|
||||
}
|
||||
|
||||
this.getSelectOptions = options => {
|
||||
return options.map((option, index) => (
|
||||
<SelectOption
|
||||
key={index}
|
||||
value={option.value}
|
||||
{...(option.description && { description: option.description })}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
||||
this.customFilter = (_, value) => {
|
||||
let input
|
||||
try {
|
||||
input = new RegExp(value, 'i')
|
||||
} catch (err) {
|
||||
input = ''
|
||||
}
|
||||
return this.getSelectOptions(input !== ''
|
||||
? this.state.options.filter(item => input.test(item.value))
|
||||
: this.state.options)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isOpen, options, selected } = this.state
|
||||
const titleId = 'select-timezone-typeahead'
|
||||
const label = 'Timezones'
|
||||
return (
|
||||
<div>
|
||||
<Select
|
||||
variant={SelectVariant.typeahead}
|
||||
typeAheadAriaLabel={label}
|
||||
aria-labelledby={titleId}
|
||||
isOpen={isOpen}
|
||||
menuAppendTo='parent'
|
||||
onClear={this.clearSelection}
|
||||
onFilter={this.customFilter}
|
||||
onSelect={this.onSelect}
|
||||
onToggle={this.onToggle}
|
||||
placeholderText={this.props.selected}
|
||||
selections={selected}
|
||||
maxHeight="200px"
|
||||
>
|
||||
{this.getSelectOptions(options)}
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default connect()(Timezone)
|
|
@ -1,122 +0,0 @@
|
|||
// 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 { OutlinedClockIcon } from '@patternfly/react-icons'
|
||||
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()
|
||||
window.addEventListener('storage', this.loadState)
|
||||
}
|
||||
|
||||
handleChange = (selectedTz) => {
|
||||
const tz = selectedTz.value
|
||||
|
||||
localStorage.setItem('zuul_tz_string', tz)
|
||||
this.updateState(tz)
|
||||
}
|
||||
|
||||
loadState = () => {
|
||||
let tz = localStorage.getItem('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',
|
||||
padding: '6px'
|
||||
}
|
||||
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 (
|
||||
<div style={containerStyles}>
|
||||
<OutlinedClockIcon/>
|
||||
<Select
|
||||
className="zuul-select-tz"
|
||||
styles={customStyles}
|
||||
value={this.state.currentValue}
|
||||
onChange={this.handleChange}
|
||||
options={this.state.availableTz}
|
||||
noOptionsMessage={() => 'No api found'}
|
||||
placeholder={'Select Tz'}
|
||||
defaultValue={this.state.defaultValue}
|
||||
theme={(theme) => ({
|
||||
...theme,
|
||||
borderRadius: 0,
|
||||
spacing: {
|
||||
...theme.spacing,
|
||||
baseUnit: 2,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect()(SelectTz)
|
|
@ -29,10 +29,10 @@ import BuildTable from '../containers/build/BuildTable'
|
|||
|
||||
class BuildsPage extends React.Component {
|
||||
static propTypes = {
|
||||
tenant: PropTypes.object,
|
||||
timezone: PropTypes.string,
|
||||
location: PropTypes.object,
|
||||
history: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
preferences: PropTypes.object,
|
||||
tenant: PropTypes.object,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
|
@ -163,7 +163,7 @@ class BuildsPage extends React.Component {
|
|||
const { filters } = this.state
|
||||
if (
|
||||
this.props.tenant.name !== prevProps.tenant.name ||
|
||||
this.props.timezone !== prevProps.timezone
|
||||
this.props.preferences.timezone !== prevProps.preferences.timezone
|
||||
) {
|
||||
this.updateData(filters)
|
||||
}
|
||||
|
@ -215,5 +215,5 @@ class BuildsPage extends React.Component {
|
|||
|
||||
export default connect((state) => ({
|
||||
tenant: state.tenant,
|
||||
timezone: state.timezone,
|
||||
preferences: state.preferences,
|
||||
}))(BuildsPage)
|
||||
|
|
|
@ -36,7 +36,6 @@ class StatusPage extends React.Component {
|
|||
location: PropTypes.object,
|
||||
tenant: PropTypes.object,
|
||||
preferences: PropTypes.object,
|
||||
timezone: PropTypes.string,
|
||||
remoteData: PropTypes.object,
|
||||
dispatch: PropTypes.func
|
||||
}
|
||||
|
@ -182,7 +181,7 @@ class StatusPage extends React.Component {
|
|||
<p>Zuul version: <span>{status.zuul_version}</span></p>
|
||||
{status.last_reconfigured ? (
|
||||
<p>Last reconfigured: <span>
|
||||
{moment.utc(status.last_reconfigured).tz(this.props.timezone).format('llll')}
|
||||
{moment.utc(status.last_reconfigured).tz(this.props.preferences.timezone).format('llll')}
|
||||
</span></p>) : ''}
|
||||
</React.Fragment>
|
||||
)
|
||||
|
@ -250,6 +249,5 @@ class StatusPage extends React.Component {
|
|||
export default connect(state => ({
|
||||
preferences: state.preferences,
|
||||
tenant: state.tenant,
|
||||
timezone: state.timezone,
|
||||
remoteData: state.status,
|
||||
}))(StatusPage)
|
||||
|
|
|
@ -34,7 +34,6 @@ import preferences from './preferences'
|
|||
import status from './status'
|
||||
import tenant from './tenant'
|
||||
import tenants from './tenants'
|
||||
import timezone from './timezone'
|
||||
import user from './user'
|
||||
|
||||
const reducers = {
|
||||
|
@ -57,7 +56,6 @@ const reducers = {
|
|||
status,
|
||||
tenant,
|
||||
tenants,
|
||||
timezone,
|
||||
preferences,
|
||||
user,
|
||||
}
|
||||
|
|
|
@ -16,15 +16,13 @@ import {
|
|||
PREFERENCE_SET,
|
||||
} from '../actions/preferences'
|
||||
|
||||
|
||||
const stored_prefs = localStorage.getItem('preferences')
|
||||
let default_prefs
|
||||
if (stored_prefs === null) {
|
||||
default_prefs = {
|
||||
autoReload: true
|
||||
}
|
||||
} else {
|
||||
default_prefs = JSON.parse(stored_prefs)
|
||||
let default_prefs = {
|
||||
autoReload: true,
|
||||
timezone: 'UTC'
|
||||
}
|
||||
if (stored_prefs !== null) {
|
||||
Object.assign(default_prefs, JSON.parse(stored_prefs))
|
||||
}
|
||||
|
||||
export default (state = {
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
// 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 { TIMEZONE_SET } from '../actions/timezone'
|
||||
|
||||
export default (state = 'UTC', action) => {
|
||||
switch (action.type) {
|
||||
case TIMEZONE_SET:
|
||||
return action.timezone
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue