Refactor some notifications to use consolidated dispatcher in Admin and Dashboards (#1116)

* Separate notification dispatchers from actions, share constants, simplify, clean up

* Replace 'addFlashMessage' with 'notify' pattern throughout Admin and Dashboards, clean up

* Remove semicolons

* Notify success upon AJAX success
pull/10616/head
Jared Scheib 2017-03-29 16:06:31 -07:00 committed by GitHub
parent 4459200ab8
commit 635ebc5dd2
11 changed files with 81 additions and 79 deletions

View File

@ -20,8 +20,10 @@ import {
killQuery as killQueryProxy,
} from 'shared/apis/metaQuery'
import {publishNotification} from 'src/shared/actions/notifications'
import {ADMIN_NOTIFICATION_DELAY} from 'src/admin/constants'
import {publishNotification} from 'shared/actions/notifications'
import {publishAutoDismissingNotification} from 'shared/dispatchers'
import {REVERT_STATE_DELAY} from 'shared/constants'
export const loadUsers = ({users}) => ({
type: 'LOAD_USERS',
@ -241,24 +243,24 @@ export const loadDBsAndRPsAsync = (url) => async (dispatch) => {
export const createUserAsync = (url, user) => async (dispatch) => {
try {
const {data} = await createUserAJAX(url, user)
dispatch(publishNotification('success', 'User created successfully'))
dispatch(publishAutoDismissingNotification('success', 'User created successfully'))
dispatch(syncUser(user, data))
} catch (error) {
// undo optimistic update
dispatch(publishNotification('error', `Failed to create user: ${error.data.message}`))
setTimeout(() => dispatch(deleteUser(user)), ADMIN_NOTIFICATION_DELAY)
setTimeout(() => dispatch(deleteUser(user)), REVERT_STATE_DELAY)
}
}
export const createRoleAsync = (url, role) => async (dispatch) => {
try {
const {data} = await createRoleAJAX(url, role)
dispatch(publishNotification('success', 'Role created successfully'))
dispatch(publishAutoDismissingNotification('success', 'Role created successfully'))
dispatch(syncRole(role, data))
} catch (error) {
// undo optimistic update
dispatch(publishNotification('error', `Failed to create role: ${error.data.message}`))
setTimeout(() => dispatch(deleteRole(role)), ADMIN_NOTIFICATION_DELAY)
setTimeout(() => dispatch(deleteRole(role)), REVERT_STATE_DELAY)
}
}
@ -266,23 +268,23 @@ export const createDatabaseAsync = (url, database) => async (dispatch) => {
try {
const {data} = await createDatabaseAJAX(url, database)
dispatch(syncDatabase(database, data))
dispatch(publishNotification('success', 'Database created successfully'))
dispatch(publishAutoDismissingNotification('success', 'Database created successfully'))
} catch (error) {
// undo optimistic update
dispatch(publishNotification('error', `Failed to create database: ${error.data.message}`))
setTimeout(() => dispatch(removeDatabase(database)), ADMIN_NOTIFICATION_DELAY)
setTimeout(() => dispatch(removeDatabase(database)), REVERT_STATE_DELAY)
}
}
export const createRetentionPolicyAsync = (database, retentionPolicy) => async (dispatch) => {
try {
const {data} = await createRetentionPolicyAJAX(database.links.retentionPolicies, retentionPolicy)
dispatch(publishNotification('success', 'Retention policy created successfully'))
dispatch(publishAutoDismissingNotification('success', 'Retention policy created successfully'))
dispatch(syncRetentionPolicy(database, retentionPolicy, data))
} catch (error) {
// undo optimistic update
dispatch(publishNotification('error', `Failed to create retention policy: ${error.data.message}`))
setTimeout(() => dispatch(removeRetentionPolicy(database, retentionPolicy)), ADMIN_NOTIFICATION_DELAY)
setTimeout(() => dispatch(removeRetentionPolicy(database, retentionPolicy)), REVERT_STATE_DELAY)
}
}
@ -290,7 +292,7 @@ export const updateRetentionPolicyAsync = (database, retentionPolicy, updates) =
try {
dispatch(editRetentionPolicy(database, retentionPolicy, updates))
const {data} = await updateRetentionPolicyAJAX(retentionPolicy.links.self, updates)
dispatch(publishNotification('success', 'Retention policy updated successfully'))
dispatch(publishAutoDismissingNotification('success', 'Retention policy updated successfully'))
dispatch(syncRetentionPolicy(database, retentionPolicy, data))
} catch (error) {
dispatch(publishNotification('error', `Failed to update retention policy: ${error.data.message}`))
@ -306,27 +308,31 @@ export const killQueryAsync = (source, queryID) => (dispatch) => {
killQueryProxy(source, queryID)
}
export const deleteRoleAsync = (role, addFlashMessage) => (dispatch) => {
// optimistic update
export const deleteRoleAsync = (role) => async (dispatch) => {
dispatch(deleteRole(role))
// delete role on server
deleteRoleAJAX(role.links.self, addFlashMessage, role.name)
try {
await deleteRoleAJAX(role.links.self)
dispatch(publishAutoDismissingNotification('success', 'Role deleted'))
} catch (error) {
dispatch(publishNotification('error', `Failed to delete role: ${error.data.message}`))
}
}
export const deleteUserAsync = (user, addFlashMessage) => (dispatch) => {
// optimistic update
export const deleteUserAsync = (user) => async (dispatch) => {
dispatch(deleteUser(user))
// delete user on server
deleteUserAJAX(user.links.self, addFlashMessage, user.name)
try {
await deleteUserAJAX(user.links.self)
dispatch(publishAutoDismissingNotification('success', 'User deleted'))
} catch (error) {
dispatch(publishNotification('error', `Failed to delete user: ${error.data.message}`))
}
}
export const deleteDatabaseAsync = (database) => async (dispatch) => {
dispatch(removeDatabase(database))
dispatch(publishNotification('success', 'Database deleted'))
try {
await deleteDatabaseAJAX(database.links.self)
dispatch(publishAutoDismissingNotification('success', 'Database deleted'))
} catch (error) {
dispatch(publishNotification('error', `Failed to delete database: ${error.data.message}`))
}
@ -334,9 +340,9 @@ export const deleteDatabaseAsync = (database) => async (dispatch) => {
export const deleteRetentionPolicyAsync = (database, retentionPolicy) => async (dispatch) => {
dispatch(removeRetentionPolicy(database, retentionPolicy))
dispatch(publishNotification('success', `Retention policy ${retentionPolicy.name} deleted`))
try {
await deleteRetentionPolicyAJAX(retentionPolicy.links.self)
dispatch(publishAutoDismissingNotification('success', `Retention policy ${retentionPolicy.name} deleted`))
} catch (error) {
dispatch(publishNotification('error', `Failed to delete retentionPolicy: ${error.data.message}`))
}
@ -345,7 +351,7 @@ export const deleteRetentionPolicyAsync = (database, retentionPolicy) => async (
export const updateRoleUsersAsync = (role, users) => async (dispatch) => {
try {
const {data} = await updateRoleAJAX(role.links.self, users, role.permissions)
dispatch(publishNotification('success', 'Role users updated'))
dispatch(publishAutoDismissingNotification('success', 'Role users updated'))
dispatch(syncRole(role, data))
} catch (error) {
dispatch(publishNotification('error', `Failed to update role: ${error.data.message}`))
@ -355,7 +361,7 @@ export const updateRoleUsersAsync = (role, users) => async (dispatch) => {
export const updateRolePermissionsAsync = (role, permissions) => async (dispatch) => {
try {
const {data} = await updateRoleAJAX(role.links.self, role.users, permissions)
dispatch(publishNotification('success', 'Role permissions updated'))
dispatch(publishAutoDismissingNotification('success', 'Role permissions updated'))
dispatch(syncRole(role, data))
} catch (error) {
dispatch(publishNotification('error', `Failed to updated role: ${error.data.message}`))
@ -365,7 +371,7 @@ export const updateRolePermissionsAsync = (role, permissions) => async (dispatch
export const updateUserPermissionsAsync = (user, permissions) => async (dispatch) => {
try {
const {data} = await updateUserAJAX(user.links.self, user.roles, permissions)
dispatch(publishNotification('success', 'User permissions updated'))
dispatch(publishAutoDismissingNotification('success', 'User permissions updated'))
dispatch(syncUser(user, data))
} catch (error) {
dispatch(publishNotification('error', `Failed to updated user: ${error.data.message}`))
@ -375,7 +381,7 @@ export const updateUserPermissionsAsync = (user, permissions) => async (dispatch
export const updateUserRolesAsync = (user, roles) => async (dispatch) => {
try {
const {data} = await updateUserAJAX(user.links.self, roles, user.permissions)
dispatch(publishNotification('success', 'User roles updated'))
dispatch(publishAutoDismissingNotification('success', 'User roles updated'))
dispatch(syncUser(user, data))
} catch (error) {
dispatch(publishNotification('error', `Failed to updated user: ${error.data.message}`))

View File

@ -107,43 +107,27 @@ export const deleteRetentionPolicy = async (url) => {
}
}
export const deleteRole = async (url, addFlashMessage, rolename) => {
export const deleteRole = async (url) => {
try {
const response = await AJAX({
return await AJAX({
method: 'DELETE',
url,
})
addFlashMessage({
type: 'success',
text: `${rolename} successfully deleted.`,
})
return response
} catch (error) {
console.error(error)
addFlashMessage({
type: 'error',
text: `Error deleting: ${rolename}.`,
})
throw error
}
}
export const deleteUser = async (url, addFlashMessage, username) => {
export const deleteUser = async (url) => {
try {
const response = await AJAX({
return await AJAX({
method: 'DELETE',
url,
})
addFlashMessage({
type: 'success',
text: `${username} successfully deleted.`,
})
return response
} catch (error) {
console.error(error)
addFlashMessage({
type: 'error',
text: `Error deleting: ${username}.`,
})
throw error
}
}

View File

@ -8,8 +8,6 @@ export const TIMES = [
{test: /^\d*h\d*m\d*s/, magnitude: 5},
]
export const ADMIN_NOTIFICATION_DELAY = 1500 // milliseconds
export const NEW_DEFAULT_USER = {
name: '',
password: '',
@ -49,4 +47,3 @@ export const NEW_DEFAULT_DATABASE = {
retentionPolicies: [NEW_DEFAULT_RP],
links: {self: ''},
}

View File

@ -25,6 +25,8 @@ import {
import AdminTabs from 'src/admin/components/AdminTabs'
import {publishAutoDismissingNotification} from 'shared/dispatchers'
const isValidUser = (user) => {
const minLen = 3
return (user.name.length >= minLen && user.password.length >= minLen)
@ -81,8 +83,9 @@ class AdminPage extends Component {
}
async handleSaveUser(user) {
const {notify} = this.props
if (!isValidUser(user)) {
this.props.addFlashMessage({type: 'error', text: 'Username and/or password too short'})
notify('error', 'Username and/or password too short')
return
}
if (user.isNew) {
@ -93,8 +96,9 @@ class AdminPage extends Component {
}
async handleSaveRole(role) {
const {notify} = this.props
if (!isValidRole(role)) {
this.props.addFlashMessage({type: 'error', text: 'Role name too short'})
notify('error', 'Role name too short')
return
}
if (role.isNew) {
@ -114,11 +118,11 @@ class AdminPage extends Component {
}
handleDeleteRole(role) {
this.props.deleteRole(role, this.props.addFlashMessage)
this.props.deleteRole(role)
}
handleDeleteUser(user) {
this.props.deleteUser(user, this.props.addFlashMessage)
this.props.deleteUser(user)
}
handleUpdateRoleUsers(role, users) {
@ -223,13 +227,13 @@ AdminPage.propTypes = {
createRole: func,
deleteRole: func,
deleteUser: func,
addFlashMessage: func,
filterRoles: func,
filterUsers: func,
updateRoleUsers: func,
updateRolePermissions: func,
updateUserPermissions: func,
updateUserRoles: func,
notify: func,
}
const mapStateToProps = ({admin: {users, roles, permissions}}) => ({
@ -258,6 +262,7 @@ const mapDispatchToProps = (dispatch) => ({
updateRolePermissions: bindActionCreators(updateRolePermissionsAsync, dispatch),
updateUserPermissions: bindActionCreators(updateUserPermissionsAsync, dispatch),
updateUserRoles: bindActionCreators(updateUserRolesAsync, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(AdminPage)

View File

@ -2,9 +2,10 @@ import React, {PropTypes, Component} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import * as adminActionCreators from 'src/admin/actions'
import DatabaseManager from 'src/admin/components/DatabaseManager'
import {publishNotification} from 'src/shared/actions/notifications'
import * as adminActionCreators from 'src/admin/actions'
import {publishAutoDismissingNotification} from 'shared/dispatchers'
class DatabaseManagerPage extends Component {
constructor(props) {
@ -141,7 +142,7 @@ const mapStateToProps = ({admin: {databases, retentionPolicies}}) => ({
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators(adminActionCreators, dispatch),
notify: bindActionCreators(publishNotification, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(DatabaseManagerPage)

View File

@ -20,6 +20,8 @@ import {
killQueryAsync,
} from 'src/admin/actions'
import {publishAutoDismissingNotification} from 'shared/dispatchers'
class QueriesPage extends Component {
constructor(props) {
super(props)
@ -47,11 +49,11 @@ class QueriesPage extends Component {
}
updateQueries() {
const {source, addFlashMessage, loadQueries} = this.props
const {source, notify, loadQueries} = this.props
showDatabases(source.links.proxy).then((resp) => {
const {databases, errors} = showDatabasesParser(resp.data)
if (errors.length) {
errors.forEach((message) => addFlashMessage({type: 'error', text: message}))
errors.forEach((message) => notify('error', message))
return
}
@ -62,7 +64,7 @@ class QueriesPage extends Component {
queryResponses.forEach((queryResponse) => {
const result = showQueriesParser(queryResponse.data)
if (result.errors.length) {
result.erorrs.forEach((message) => this.props.addFlashMessage({type: 'error', text: message}))
result.errors.forEach((message) => notify('error', message))
}
allQueries.push(...result.queries)
@ -113,11 +115,11 @@ QueriesPage.propTypes = {
}),
}),
queries: arrayOf(shape()),
addFlashMessage: func,
loadQueries: func,
queryIDToKill: string,
setQueryToKill: func,
killQuery: func,
notify: func,
}
const mapStateToProps = ({admin: {queries, queryIDToKill}}) => ({
@ -129,6 +131,7 @@ const mapDispatchToProps = (dispatch) => ({
loadQueries: bindActionCreators(loadQueriesAction, dispatch),
setQueryToKill: bindActionCreators(setQueryToKillAction, dispatch),
killQuery: bindActionCreators(killQueryAsync, dispatch),
notify: bindActionCreators(publishAutoDismissingNotification, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(QueriesPage)

View File

@ -7,9 +7,9 @@ import {
deleteDashboardCell as deleteDashboardCellAJAX,
} from 'src/dashboards/apis'
import {publishNotification, delayDismissNotification} from 'src/shared/actions/notifications'
import {publishNotification} from 'shared/actions/notifications'
import {publishAutoDismissingNotification} from 'shared/dispatchers'
import {SHORT_NOTIFICATION_DISAPPEARING_DELAY} from 'shared/constants'
import {NEW_DEFAULT_DASHBOARD_CELL} from 'src/dashboards/constants'
export const loadDashboards = (dashboards, dashboardID) => ({
@ -135,8 +135,7 @@ export const deleteDashboardAsync = (dashboard) => async (dispatch) => {
dispatch(deleteDashboard(dashboard))
try {
await deleteDashboardAJAX(dashboard)
dispatch(publishNotification('success', 'Dashboard deleted successfully.'))
dispatch(delayDismissNotification('success', SHORT_NOTIFICATION_DISAPPEARING_DELAY))
dispatch(publishAutoDismissingNotification('success', 'Dashboard deleted successfully.'))
} catch (error) {
dispatch(deleteDashboardFailed(dashboard))
dispatch(publishNotification('error', `Failed to delete dashboard: ${error.data.message}.`))

View File

@ -32,7 +32,6 @@ const DashboardsPage = React.createClass({
router: shape({
push: func.isRequired,
}).isRequired,
addFlashMessage: func,
handleGetDashboards: func.isRequired,
handleDeleteDashboard: func.isRequired,
dashboards: arrayOf(shape()),

View File

@ -17,12 +17,6 @@ export function dismissNotification(type) {
}
}
export function delayDismissNotification(type, wait) {
return (dispatch) => {
setTimeout(() => dispatch(dismissNotification(type)), wait)
}
}
export function dismissAllNotifications() {
return {
type: 'ALL_NOTIFICATIONS_DISMISSED',

View File

@ -471,7 +471,9 @@ export const STROKE_WIDTH = {
export const PRESENTATION_MODE_ANIMATION_DELAY = 0 // In milliseconds.
export const PRESENTATION_MODE_NOTIFICATION_DELAY = 2000 // In milliseconds.
export const SHORT_NOTIFICATION_DISAPPEARING_DELAY = 1500 // in milliseconds
export const SHORT_NOTIFICATION_DISMISS_DELAY = 1500 // in milliseconds
export const REVERT_STATE_DELAY = 1500 // ms
export const RES_UNAUTHORIZED = 401

View File

@ -1,9 +1,21 @@
import {publishNotification, dismissNotification} from 'shared/actions/notifications'
import {delayEnablePresentationMode} from 'shared/actions/app'
import {publishNotification, delayDismissNotification} from 'shared/actions/notifications'
import {PRESENTATION_MODE_NOTIFICATION_DELAY} from 'shared/constants'
import {SHORT_NOTIFICATION_DISMISS_DELAY} from 'shared/constants'
export function delayDismissNotification(type, delay) {
return (dispatch) => {
setTimeout(() => dispatch(dismissNotification(type)), delay)
}
}
export const publishAutoDismissingNotification = (type, message, delay = SHORT_NOTIFICATION_DISMISS_DELAY) => (dispatch) => {
dispatch(publishNotification(type, message))
dispatch(delayDismissNotification(type, delay))
}
export const presentationButtonDispatcher = (dispatch) => () => {
dispatch(delayEnablePresentationMode())
dispatch(publishNotification('success', 'Press ESC to disable presentation mode.'))
dispatch(delayDismissNotification('success', PRESENTATION_MODE_NOTIFICATION_DELAY))
dispatch(publishAutoDismissingNotification('success', 'Press ESC to disable presentation mode.', PRESENTATION_MODE_NOTIFICATION_DELAY))
}