web UI: allow a privileged user to re-enqueue a change
Add a "re-enqueue" action command on a Buildset page, displayed only if the currently logged in user is an admin on the tenant. By clicking this command, the user can re-enqueue the change with the same parameters as the buildset. Redux: turned the API error notifications into a more generic notification system, that can now include success notifications. Change-Id: I05b6b3deb912b121df8de207944d9ec26e7c92d1
This commit is contained in:
parent
3152171aa5
commit
c82619174c
|
@ -64,7 +64,7 @@ import { Fetching } from './containers/Fetching'
|
||||||
import SelectTz from './containers/timezone/SelectTz'
|
import SelectTz from './containers/timezone/SelectTz'
|
||||||
import ConfigModal from './containers/config/Config'
|
import ConfigModal from './containers/config/Config'
|
||||||
import logo from './images/logo.svg'
|
import logo from './images/logo.svg'
|
||||||
import { clearError } from './actions/errors'
|
import { clearNotification } from './actions/notifications'
|
||||||
import { fetchConfigErrorsAction } from './actions/configErrors'
|
import { fetchConfigErrorsAction } from './actions/configErrors'
|
||||||
import { routes } from './routes'
|
import { routes } from './routes'
|
||||||
import { setTenantAction } from './actions/tenant'
|
import { setTenantAction } from './actions/tenant'
|
||||||
|
@ -72,7 +72,7 @@ import { configureAuthFromTenant, configureAuthFromInfo } from './actions/auth'
|
||||||
|
|
||||||
class App extends React.Component {
|
class App extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
errors: PropTypes.array,
|
notifications: PropTypes.array,
|
||||||
configErrors: PropTypes.array,
|
configErrors: PropTypes.array,
|
||||||
info: PropTypes.object,
|
info: PropTypes.object,
|
||||||
tenant: PropTypes.object,
|
tenant: PropTypes.object,
|
||||||
|
@ -235,21 +235,34 @@ class App extends React.Component {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
renderErrors = (errors) => {
|
renderNotifications = (notifications) => {
|
||||||
return (
|
return (
|
||||||
<ToastNotificationList>
|
<ToastNotificationList>
|
||||||
{errors.map(error => (
|
{notifications.map(notification => {
|
||||||
<TimedToastNotification
|
let notificationBody
|
||||||
key={error.id}
|
if (notification.type === 'error') {
|
||||||
type='error'
|
notificationBody = (
|
||||||
onDismiss={() => { this.props.dispatch(clearError(error.id)) }}
|
<>
|
||||||
>
|
<strong>{notification.text}</strong> {notification.status}
|
||||||
<span title={moment.utc(error.date).tz(this.props.timezone).format()}>
|
{notification.url}
|
||||||
<strong>{error.text}</strong> ({error.status})
|
</>
|
||||||
{error.url}
|
)
|
||||||
</span>
|
} else {
|
||||||
</TimedToastNotification>
|
notificationBody = (<span>{notification.text}</span>)
|
||||||
))}
|
}
|
||||||
|
return (
|
||||||
|
<TimedToastNotification
|
||||||
|
key={notification.id}
|
||||||
|
type={notification.type}
|
||||||
|
onDismiss={() => { this.props.dispatch(clearNotification(notification.id)) }}
|
||||||
|
>
|
||||||
|
<span title={moment.utc(notification.date).tz(this.props.timezone).format()}>
|
||||||
|
{notificationBody}
|
||||||
|
</span>
|
||||||
|
</TimedToastNotification>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)}
|
||||||
</ToastNotificationList>
|
</ToastNotificationList>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -318,7 +331,7 @@ class App extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isKebabDropdownOpen } = this.state
|
const { isKebabDropdownOpen } = this.state
|
||||||
const { errors, configErrors, tenant } = this.props
|
const { notifications, configErrors, tenant } = this.props
|
||||||
|
|
||||||
const nav = this.renderMenu()
|
const nav = this.renderMenu()
|
||||||
|
|
||||||
|
@ -440,7 +453,7 @@ class App extends React.Component {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{errors.length > 0 && this.renderErrors(errors)}
|
{notifications.length > 0 && this.renderNotifications(notifications)}
|
||||||
{this.renderConfigErrors(configErrors)}
|
{this.renderConfigErrors(configErrors)}
|
||||||
<Page header={pageHeader}>
|
<Page header={pageHeader}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
|
@ -455,7 +468,7 @@ class App extends React.Component {
|
||||||
// This connect the info state from the store to the info property of the App.
|
// This connect the info state from the store to the info property of the App.
|
||||||
export default withRouter(connect(
|
export default withRouter(connect(
|
||||||
state => ({
|
state => ({
|
||||||
errors: state.errors,
|
notifications: state.notifications,
|
||||||
configErrors: state.configErrors,
|
configErrors: state.configErrors,
|
||||||
info: state.info,
|
info: state.info,
|
||||||
tenant: state.tenant,
|
tenant: state.tenant,
|
||||||
|
|
|
@ -13,9 +13,14 @@
|
||||||
// under the License.
|
// under the License.
|
||||||
|
|
||||||
export const ADMIN_DEQUEUE_FAIL = 'ADMIN_DEQUEUE_FAIL'
|
export const ADMIN_DEQUEUE_FAIL = 'ADMIN_DEQUEUE_FAIL'
|
||||||
|
export const ADMIN_ENQUEUE_FAIL = 'ADMIN_ENQUEUE_FAIL'
|
||||||
|
|
||||||
export const addDequeueError = error => ({
|
export const addDequeueError = error => ({
|
||||||
type: ADMIN_DEQUEUE_FAIL,
|
type: ADMIN_DEQUEUE_FAIL,
|
||||||
error: error
|
notification: error
|
||||||
|
})
|
||||||
|
|
||||||
|
export const addEnqueueError = error => ({
|
||||||
|
type: ADMIN_ENQUEUE_FAIL,
|
||||||
|
notification: error
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,38 +12,39 @@
|
||||||
// License for the specific language governing permissions and limitations
|
// License for the specific language governing permissions and limitations
|
||||||
// under the License.
|
// under the License.
|
||||||
|
|
||||||
export const ADD_ERROR = 'ADD_ERROR'
|
export const ADD_NOTIFICATION = 'ADD_NOTIFICATION'
|
||||||
export const CLEAR_ERROR = 'CLEAR_ERROR'
|
export const CLEAR_NOTIFICATION = 'CLEAR_NOTIFICATION'
|
||||||
export const CLEAR_ERRORS = 'CLEAR_ERRORS'
|
export const CLEAR_NOTIFICATIONS = 'CLEAR_NOTIFICATIONS'
|
||||||
|
|
||||||
let errorId = 0
|
let notificationId = 0
|
||||||
|
|
||||||
export const addError = error => ({
|
export const addNotification = notification => ({
|
||||||
type: ADD_ERROR,
|
type: ADD_NOTIFICATION,
|
||||||
id: errorId++,
|
id: notificationId++,
|
||||||
error
|
notification
|
||||||
})
|
})
|
||||||
|
|
||||||
export const addApiError = error => {
|
export const addApiError = error => {
|
||||||
const d = {
|
const d = {
|
||||||
url: (error && error.request && error.request.responseURL) || error.url
|
url: (error && error.request && error.request.responseURL) || error.url,
|
||||||
|
type: 'error',
|
||||||
}
|
}
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
d.text = error.response.statusText
|
d.text = error.response.statusText
|
||||||
d.status = error.response.status
|
d.status = error.response.status
|
||||||
} else {
|
} else {
|
||||||
d.status = 'Unable to fetch URL, check your network connectivity,'
|
d.status = 'Unable to fetch URL, check your network connectivity,'
|
||||||
+ ' browser plugins, ad-blockers, or try to refresh this page'
|
+ ' browser plugins, ad-blockers, or try to refresh this page'
|
||||||
d.text = error.message
|
d.text = error.message
|
||||||
}
|
}
|
||||||
return addError(d)
|
return addNotification(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const clearError = id => ({
|
export const clearNotification = id => ({
|
||||||
type: CLEAR_ERROR,
|
type: CLEAR_NOTIFICATION,
|
||||||
id
|
id
|
||||||
})
|
})
|
||||||
|
|
||||||
export const clearErrors = () => ({
|
export const clearNotifications = () => ({
|
||||||
type: CLEAR_ERRORS
|
type: CLEAR_NOTIFICATIONS
|
||||||
})
|
})
|
|
@ -181,7 +181,7 @@ function fetchUserAuthorizations(apiPrefix, token) {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
function dequeue (apiPrefix, projectName, pipeline, change, token) {
|
function dequeue(apiPrefix, projectName, pipeline, change, token) {
|
||||||
const instance = Axios.create({
|
const instance = Axios.create({
|
||||||
baseURL: apiUrl
|
baseURL: apiUrl
|
||||||
})
|
})
|
||||||
|
@ -195,7 +195,7 @@ function dequeue (apiPrefix, projectName, pipeline, change, token) {
|
||||||
)
|
)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
function dequeue_ref (apiPrefix, projectName, pipeline, ref, token) {
|
function dequeue_ref(apiPrefix, projectName, pipeline, ref, token) {
|
||||||
const instance = Axios.create({
|
const instance = Axios.create({
|
||||||
baseURL: apiUrl
|
baseURL: apiUrl
|
||||||
})
|
})
|
||||||
|
@ -210,6 +210,37 @@ function dequeue_ref (apiPrefix, projectName, pipeline, ref, token) {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enqueue(apiPrefix, projectName, pipeline, change, token) {
|
||||||
|
const instance = Axios.create({
|
||||||
|
baseURL: apiUrl
|
||||||
|
})
|
||||||
|
instance.defaults.headers.common['Authorization'] = 'Bearer ' + token
|
||||||
|
let res = instance.post(
|
||||||
|
apiPrefix + 'project/' + projectName + '/enqueue',
|
||||||
|
{
|
||||||
|
pipeline: pipeline,
|
||||||
|
change: change,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
function enqueue_ref(apiPrefix, projectName, pipeline, ref, oldrev, newrev, token) {
|
||||||
|
const instance = Axios.create({
|
||||||
|
baseURL: apiUrl
|
||||||
|
})
|
||||||
|
instance.defaults.headers.common['Authorization'] = 'Bearer ' + token
|
||||||
|
let res = instance.post(
|
||||||
|
apiPrefix + 'project/' + projectName + '/enqueue',
|
||||||
|
{
|
||||||
|
pipeline: pipeline,
|
||||||
|
ref: ref,
|
||||||
|
oldrev: oldrev,
|
||||||
|
newrev: newrev,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
apiUrl,
|
apiUrl,
|
||||||
getHomepageUrl,
|
getHomepageUrl,
|
||||||
|
@ -235,4 +266,6 @@ export {
|
||||||
fetchUserAuthorizations,
|
fetchUserAuthorizations,
|
||||||
dequeue,
|
dequeue,
|
||||||
dequeue_ref,
|
dequeue_ref,
|
||||||
|
enqueue,
|
||||||
|
enqueue_ref,
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { connect } from 'react-redux'
|
import { connect, useDispatch } from 'react-redux'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
@ -23,6 +23,8 @@ import {
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
Title,
|
Title,
|
||||||
|
Modal,
|
||||||
|
ModalVariant,
|
||||||
} from '@patternfly/react-core'
|
} from '@patternfly/react-core'
|
||||||
import {
|
import {
|
||||||
CodeIcon,
|
CodeIcon,
|
||||||
|
@ -33,16 +35,19 @@ import {
|
||||||
StreamIcon,
|
StreamIcon,
|
||||||
OutlinedCalendarAltIcon,
|
OutlinedCalendarAltIcon,
|
||||||
OutlinedClockIcon,
|
OutlinedClockIcon,
|
||||||
|
RedoAltIcon,
|
||||||
} from '@patternfly/react-icons'
|
} from '@patternfly/react-icons'
|
||||||
import * as moment from 'moment'
|
import * as moment from 'moment'
|
||||||
import 'moment-duration-format'
|
import 'moment-duration-format'
|
||||||
|
|
||||||
import { buildExternalLink } from '../../Misc'
|
import { buildExternalLink } from '../../Misc'
|
||||||
import { BuildResultBadge, BuildResultWithIcon, IconProperty } from './Misc'
|
import { BuildResultBadge, BuildResultWithIcon, IconProperty } from './Misc'
|
||||||
|
import { enqueue, enqueue_ref } from '../../api'
|
||||||
|
import { addNotification, addApiError } from '../../actions/notifications'
|
||||||
import { ChartModal } from '../charts/ChartModal'
|
import { ChartModal } from '../charts/ChartModal'
|
||||||
import BuildsetGanttChart from '../charts/GanttChart'
|
import BuildsetGanttChart from '../charts/GanttChart'
|
||||||
|
|
||||||
function Buildset({ buildset, timezone, tenant }) {
|
function Buildset({ buildset, timezone, tenant, user }) {
|
||||||
const buildset_link = buildExternalLink(buildset)
|
const buildset_link = buildExternalLink(buildset)
|
||||||
const [isGanttChartModalOpen, setIsGanttChartModalOpen] = useState(false)
|
const [isGanttChartModalOpen, setIsGanttChartModalOpen] = useState(false)
|
||||||
|
|
||||||
|
@ -127,13 +132,92 @@ function Buildset({ buildset, timezone, tenant }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [showEnqueueModal, setShowEnqueueModal] = useState(false)
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
|
||||||
|
function renderEnqueueButton() {
|
||||||
|
const value = (<span style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: 'var(--pf-global--primary-color--100)'
|
||||||
|
}}
|
||||||
|
title="Re-enqueue this change"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
setShowEnqueueModal(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Re-enqueue buildset
|
||||||
|
</span>)
|
||||||
|
return (
|
||||||
|
<IconProperty
|
||||||
|
WrapElement={ListItem}
|
||||||
|
icon={<RedoAltIcon />}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function enqueueConfirm() {
|
||||||
|
let changeId = buildset.change ? buildset.change + ',' + buildset.patchset : buildset.newrev
|
||||||
|
setShowEnqueueModal(false)
|
||||||
|
if (/^[0-9a-f]{40}$/.test(changeId)) {
|
||||||
|
const oldrev = '0000000000000000000000000000000000000000'
|
||||||
|
enqueue_ref(tenant.apiPrefix, buildset.project, buildset.pipeline, buildset.ref, oldrev, changeId, user.token)
|
||||||
|
.then(() => {
|
||||||
|
dispatch(addNotification(
|
||||||
|
{
|
||||||
|
text: 'Change queued successfully.',
|
||||||
|
type: 'success',
|
||||||
|
status: '',
|
||||||
|
url: '',
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dispatch(addApiError(error))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
enqueue(tenant.apiPrefix, buildset.project, buildset.pipeline, changeId, user.token)
|
||||||
|
.then(() => {
|
||||||
|
dispatch(addNotification(
|
||||||
|
{
|
||||||
|
text: 'Change queued successfully.',
|
||||||
|
type: 'success',
|
||||||
|
status: '',
|
||||||
|
url: '',
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
dispatch(addApiError(error))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderEnqueueModal() {
|
||||||
|
let changeId = buildset.change ? buildset.change + ',' + buildset.patchset : buildset.newrev
|
||||||
|
const title = 'You are about to re-enqueue a change'
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
variant={ModalVariant.small}
|
||||||
|
// titleIconVariant={BullhornIcon}
|
||||||
|
isOpen={showEnqueueModal}
|
||||||
|
title={title}
|
||||||
|
onClose={() => { setShowEnqueueModal(false) }}
|
||||||
|
actions={[
|
||||||
|
<Button key="deq_confirm" variant="primary" onClick={enqueueConfirm}>Confirm</Button>,
|
||||||
|
<Button key="deq_cancel" variant="link" onClick={() => { setShowEnqueueModal(false) }}>Cancel</Button>,
|
||||||
|
]}>
|
||||||
|
<p>Please confirm that you want to re-enqueue <strong>all jobs</strong> for change <strong>{changeId}</strong> (project <strong>{buildset.project}</strong>) on pipeline <strong>{buildset.pipeline}</strong>.</p>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Title headingLevel="h2">
|
<Title headingLevel="h2">
|
||||||
<BuildResultWithIcon result={buildset.result} size="md">
|
<BuildResultWithIcon result={buildset.result} size="md">
|
||||||
Buildset result
|
Buildset result
|
||||||
</BuildResultWithIcon>
|
</BuildResultWithIcon>
|
||||||
<BuildResultBadge result={buildset.result} />
|
<BuildResultBadge result={buildset.result} />
|
||||||
</Title>
|
</Title>
|
||||||
{/* We handle the spacing for the body and the flex items by ourselves
|
{/* We handle the spacing for the body and the flex items by ourselves
|
||||||
so they go hand in hand. By default, the flex items' spacing only
|
so they go hand in hand. By default, the flex items' spacing only
|
||||||
|
@ -216,6 +300,10 @@ function Buildset({ buildset, timezone, tenant }) {
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
{(user.isAdmin && user.scope.indexOf(tenant.name) !== -1) &&
|
||||||
|
<>
|
||||||
|
{renderEnqueueButton()}
|
||||||
|
</>}
|
||||||
</List>
|
</List>
|
||||||
</FlexItem>
|
</FlexItem>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -228,6 +316,7 @@ function Buildset({ buildset, timezone, tenant }) {
|
||||||
setIsGanttChartModalOpen(false)
|
setIsGanttChartModalOpen(false)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{renderEnqueueModal()}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -236,9 +325,11 @@ Buildset.propTypes = {
|
||||||
buildset: PropTypes.object,
|
buildset: PropTypes.object,
|
||||||
tenant: PropTypes.object,
|
tenant: PropTypes.object,
|
||||||
timezone: PropTypes.string,
|
timezone: PropTypes.string,
|
||||||
|
user: PropTypes.object,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect((state) => ({
|
export default connect((state) => ({
|
||||||
tenant: state.tenant,
|
tenant: state.tenant,
|
||||||
timezone: state.timezone,
|
timezone: state.timezone,
|
||||||
|
user: state.user,
|
||||||
}))(Buildset)
|
}))(Buildset)
|
||||||
|
|
|
@ -31,7 +31,7 @@ import {
|
||||||
import { dequeue, dequeue_ref } from '../../api'
|
import { dequeue, dequeue_ref } from '../../api'
|
||||||
import { addDequeueError } from '../../actions/adminActions'
|
import { addDequeueError } from '../../actions/adminActions'
|
||||||
|
|
||||||
import { addError } from '../../actions/errors'
|
import { addNotification } from '../../actions/notifications'
|
||||||
|
|
||||||
import LineAngleImage from '../../images/line-angle.png'
|
import LineAngleImage from '../../images/line-angle.png'
|
||||||
import LineTImage from '../../images/line-t.png'
|
import LineTImage from '../../images/line-t.png'
|
||||||
|
@ -73,10 +73,11 @@ class Change extends React.Component {
|
||||||
this.props.dispatch(addDequeueError(error))
|
this.props.dispatch(addDequeueError(error))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.props.dispatch(addError({
|
this.props.dispatch(addNotification({
|
||||||
url: null,
|
url: null,
|
||||||
status: 'Invalid change ' + changeRef + ' on project ' + projectName,
|
status: 'Invalid change ' + changeRef + ' on project ' + projectName,
|
||||||
text: ''
|
text: '',
|
||||||
|
type: 'error'
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import auth from './auth'
|
||||||
import configErrors from './configErrors'
|
import configErrors from './configErrors'
|
||||||
import change from './change'
|
import change from './change'
|
||||||
import component from './component'
|
import component from './component'
|
||||||
import errors from './errors'
|
import notifications from './notifications'
|
||||||
import build from './build'
|
import build from './build'
|
||||||
import info from './info'
|
import info from './info'
|
||||||
import job from './job'
|
import job from './job'
|
||||||
|
@ -42,7 +42,7 @@ const reducers = {
|
||||||
change,
|
change,
|
||||||
component,
|
component,
|
||||||
configErrors,
|
configErrors,
|
||||||
errors,
|
notifications,
|
||||||
info,
|
info,
|
||||||
job,
|
job,
|
||||||
jobs,
|
jobs,
|
||||||
|
|
|
@ -13,34 +13,34 @@
|
||||||
// under the License.
|
// under the License.
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ADD_ERROR,
|
ADD_NOTIFICATION,
|
||||||
CLEAR_ERROR,
|
CLEAR_NOTIFICATION,
|
||||||
CLEAR_ERRORS,
|
CLEAR_NOTIFICATIONS,
|
||||||
addApiError,
|
addApiError,
|
||||||
} from '../actions/errors'
|
} from '../actions/notifications'
|
||||||
|
|
||||||
|
|
||||||
export default (state = [], action) => {
|
export default (state = [], action) => {
|
||||||
// Intercept API failure
|
// Intercept API failure
|
||||||
if (action.error && action.type.match(/.*_FETCH_FAIL$/)) {
|
if (action.notification && action.type.match(/.*_FETCH_FAIL$/)) {
|
||||||
action = addApiError(action.error)
|
action = addApiError(action.notification)
|
||||||
}
|
}
|
||||||
// Intercept Admin API failures
|
// Intercept Admin API failures
|
||||||
if (action.error && action.type.match(/ADMIN_.*_FAIL$/)) {
|
if (action.notification && action.type.match(/ADMIN_.*_FAIL$/)) {
|
||||||
action = addApiError(action.error)
|
action = addApiError(action.notification)
|
||||||
}
|
}
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ADD_ERROR:
|
case ADD_NOTIFICATION:
|
||||||
if (state.filter(error => (
|
if (state.filter(notification => (
|
||||||
error.url === action.error.url &&
|
notification.url === action.notification.url &&
|
||||||
error.status === action.error.status)).length > 0)
|
notification.status === action.notification.status)).length > 0)
|
||||||
return state
|
return state
|
||||||
return [
|
return [
|
||||||
...state,
|
...state,
|
||||||
{ ...action.error, id: action.id, date: Date.now() }]
|
{ ...action.notification, id: action.id, date: Date.now() }]
|
||||||
case CLEAR_ERROR:
|
case CLEAR_NOTIFICATION:
|
||||||
return state.filter(item => (item.id !== action.id))
|
return state.filter(item => (item.id !== action.id))
|
||||||
case CLEAR_ERRORS:
|
case CLEAR_NOTIFICATIONS:
|
||||||
return []
|
return []
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
Loading…
Reference in New Issue