summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSorin Sbarnea <ssbarnea@redhat.com>2020-07-06 12:21:00 +0100
committerSorin Sbarnea <ssbarnea@redhat.com>2020-07-30 13:51:09 +0100
commit1ecbe5847455c9cd5123f1e2c7109c56bc850be1 (patch)
tree272ef13707c76809495fb781c17bcf4aab92b5e3
parent9c623851c422f29f641a71fe90cf91c1cf1a1274 (diff)
downloadzuul-1ecbe5847455c9cd5123f1e2c7109c56bc850be1.tar.gz
Add user preferences dialog
In order to avoid cluttering Zuul UI with user preferences in various places, we introduce a simple config dialog. This moves "auto reload" option to the preferences panel. Change-Id: I7201d9e021c99f8e5a936dc98efe3c9d563fbcbc Co-Authored-By: James E. Blair <jeblair@redhat.com>
-rw-r--r--web/src/App.jsx2
-rw-r--r--web/src/actions/preferences.js23
-rw-r--r--web/src/containers/config/Config.jsx110
-rw-r--r--web/src/pages/Status.jsx23
-rw-r--r--web/src/reducers/index.js2
-rw-r--r--web/src/reducers/preferences.js44
6 files changed, 192 insertions, 12 deletions
diff --git a/web/src/App.jsx b/web/src/App.jsx
index 5ed512a07..6bfc755f4 100644
--- a/web/src/App.jsx
+++ b/web/src/App.jsx
@@ -66,6 +66,7 @@ import {
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 { clearError } from './actions/errors'
import { fetchConfigErrorsAction } from './actions/configErrors'
@@ -385,6 +386,7 @@ class App extends React.Component {
</NotificationBadge>
}
<SelectTz/>
+ <ConfigModal/>
</PageHeaderTools>
)
diff --git a/web/src/actions/preferences.js b/web/src/actions/preferences.js
new file mode 100644
index 000000000..03e599323
--- /dev/null
+++ b/web/src/actions/preferences.js
@@ -0,0 +1,23 @@
+// Copyright 2020 Red Hat, Inc
+//
+// 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 PREFERENCE_SET = 'PREFERENCE_SET'
+
+export function setPreference (key, value) {
+ return {
+ type: PREFERENCE_SET,
+ key,
+ value,
+ }
+}
diff --git a/web/src/containers/config/Config.jsx b/web/src/containers/config/Config.jsx
new file mode 100644
index 000000000..36de8fd5e
--- /dev/null
+++ b/web/src/containers/config/Config.jsx
@@ -0,0 +1,110 @@
+// 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 { connect } from 'react-redux'
+import {
+ Button,
+ ButtonVariant,
+ Modal,
+ ModalVariant,
+ Switch
+} from '@patternfly/react-core'
+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
+ }
+
+ constructor(props) {
+ super(props)
+ this.state = {
+ isModalOpen: false,
+ autoReload: false,
+ }
+ this.handleModalToggle = () => {
+ this.setState(({ isModalOpen }) => ({
+ isModalOpen: !isModalOpen
+ }))
+ this.resetState()
+ }
+
+ this.handleSave = () => {
+ this.handleModalToggle()
+ this.props.dispatch(setPreference('autoReload', this.state.autoReload))
+ }
+
+ this.handleAutoReload = () => {
+ this.setState(({ autoReload }) => ({
+ autoReload: !autoReload
+ }))
+ }
+ }
+
+ resetState() {
+ this.setState({
+ autoReload: this.props.preferences.autoReload,
+ })
+ }
+
+ render() {
+ const { isModalOpen, autoReload } = this.state
+ return (
+ <React.Fragment>
+ <Button
+ variant={ButtonVariant.plain}
+ key="cog"
+ onClick={this.handleModalToggle}>
+ <CogIcon />
+ </Button>
+ <Modal
+ variant={ModalVariant.small}
+ title="Preferences"
+ isOpen={isModalOpen}
+ onClose={this.handleModalToggle}
+ actions={[
+ <Button key="confirm" variant="primary" onClick={this.handleSave}>
+ Confirm
+ </Button>,
+ <Button key="cancel" variant="link" onClick={this.handleModalToggle}>
+ Cancel
+ </Button>
+ ]}
+ >
+ <div>
+ <p key="info">User configurable settings are saved in browser local storage only.</p>
+ <Switch
+ key="autoreload"
+ id="autoreload"
+ label="Auto reload status page"
+ isChecked={autoReload}
+ onChange={this.handleAutoReload}
+ />
+ </div>
+ </Modal>
+ </React.Fragment>
+ )
+ }
+}
+
+export default connect(state => ({
+ preferences: state.preferences,
+ }))(ConfigModal)
diff --git a/web/src/pages/Status.jsx b/web/src/pages/Status.jsx
index 7588ea162..7e33928b9 100644
--- a/web/src/pages/Status.jsx
+++ b/web/src/pages/Status.jsx
@@ -34,6 +34,7 @@ class StatusPage extends React.Component {
static propTypes = {
location: PropTypes.object,
tenant: PropTypes.object,
+ preferences: PropTypes.object,
timezone: PropTypes.string,
remoteData: PropTypes.object,
dispatch: PropTypes.func
@@ -42,7 +43,6 @@ class StatusPage extends React.Component {
state = {
filter: null,
expanded: false,
- autoReload: true
}
visibilityListener = () => {
@@ -80,9 +80,9 @@ class StatusPage extends React.Component {
}
updateData = (force) => {
- if (force || (this.visible && this.state.autoReload)) {
+ if (force || (this.visible && this.props.preferences.autoReload)) {
this.props.dispatch(fetchStatusIfNeeded(this.props.tenant))
- .then(() => {if (this.state.autoReload && this.visible) {
+ .then(() => {if (this.props.preferences.autoReload && this.visible) {
this.timer = setTimeout(this.updateData, 5000)
}})
}
@@ -97,14 +97,18 @@ class StatusPage extends React.Component {
document.title = 'Zuul Status'
this.loadState()
if (this.props.tenant.name) {
- this.updateData()
+ this.updateData(true)
}
window.addEventListener('storage', this.loadState)
}
componentDidUpdate (prevProps) {
if (this.props.tenant.name !== prevProps.tenant.name) {
- this.updateData()
+ this.updateData(true)
+ }
+ // If the user just enabled auto-reload
+ if (this.props.preferences.autoReload && !this.timer) {
+ this.updateData(true)
}
}
@@ -185,7 +189,7 @@ class StatusPage extends React.Component {
render () {
const { remoteData } = this.props
- const { autoReload, filter, expanded } = this.state
+ const { filter, expanded } = this.state
const status = remoteData.status
if (this.filter && !this.filterLoaded && filter) {
this.filterLoaded = true
@@ -227,12 +231,6 @@ class StatusPage extends React.Component {
isFetching={remoteData.isFetching}
fetchCallback={this.updateData}
/>
- <Checkbox
- defaultChecked={autoReload}
- onChange={(e) => {this.setState({autoReload: e.target.checked})}}
- style={{marginTop: '0px', marginLeft: '10px'}}>
- auto reload
- </Checkbox>
</div>
{status && this.renderStatusHeader(status)}
@@ -253,6 +251,7 @@ class StatusPage extends React.Component {
}
export default connect(state => ({
+ preferences: state.preferences,
tenant: state.tenant,
timezone: state.timezone,
remoteData: state.status,
diff --git a/web/src/reducers/index.js b/web/src/reducers/index.js
index 84fe8e6fd..faee01bcb 100644
--- a/web/src/reducers/index.js
+++ b/web/src/reducers/index.js
@@ -27,6 +27,7 @@ import nodes from './nodes'
import openapi from './openapi'
import project from './project'
import projects from './projects'
+import preferences from './preferences'
import status from './status'
import tenant from './tenant'
import tenants from './tenants'
@@ -50,6 +51,7 @@ const reducers = {
tenant,
tenants,
timezone,
+ preferences,
}
export default combineReducers(reducers)
diff --git a/web/src/reducers/preferences.js b/web/src/reducers/preferences.js
new file mode 100644
index 000000000..860e91a78
--- /dev/null
+++ b/web/src/reducers/preferences.js
@@ -0,0 +1,44 @@
+// Copyright 2020 Red Hat, Inc
+//
+// 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 update from 'immutability-helper'
+
+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)
+}
+
+export default (state = {
+ ...default_prefs
+}, action) => {
+ let newstate
+ switch (action.type) {
+ case PREFERENCE_SET:
+ newstate = update(state, {$merge: {[action.key]: action.value}})
+ localStorage.setItem('preferences', JSON.stringify(newstate))
+ return newstate
+ default:
+ return state
+ }
+}