Replace addFlashMessage with publishNotification

Also refactoring App.js into a SFC
pull/10616/head
Alex P 2018-03-03 17:08:50 -08:00 committed by Andrew Watkins
parent 9f85542d19
commit a14f860ef1
13 changed files with 479 additions and 82 deletions

View File

@ -1,43 +1,20 @@
import React, {Component} from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import SideNav from 'src/side_nav'
import Notifications from 'shared/components/Notifications'
import {publishNotification as publishNotificationAction} from 'shared/actions/notifications'
const App = ({children}) =>
<div className="chronograf-root">
<Notifications />
<SideNav />
{children}
</div>
class App extends Component {
notify = notification => {
const {publishNotification} = this.props
publishNotification(notification)
}
render() {
return (
<div className="chronograf-root">
<Notifications />
<SideNav />
{this.props.children &&
React.cloneElement(this.props.children, {
addFlashMessage: this.notify,
})}
</div>
)
}
}
const {func, node} = PropTypes
const {node} = PropTypes
App.propTypes = {
children: node.isRequired,
publishNotification: func.isRequired,
}
const mapDispatchToProps = dispatch => ({
publishNotification: bindActionCreators(publishNotificationAction, dispatch),
})
export default connect(null, mapDispatchToProps)(App)
export default App

View File

@ -12,6 +12,7 @@ import ManualRefresh from 'src/shared/components/ManualRefresh'
import {getCpuAndLoadForHosts, getLayouts, getAppsForHosts} from '../apis'
import {getEnv} from 'src/shared/apis/env'
import {setAutoRefresh} from 'shared/actions/app'
import {publishNotification as publishNotificationAction} from 'shared/actions/notifications'
class HostsPage extends Component {
constructor(props) {
@ -25,7 +26,7 @@ class HostsPage extends Component {
}
async fetchHostsData() {
const {source, links, addFlashMessage} = this.props
const {source, links, publishNotification} = this.props
const {telegrafSystemInterval} = await getEnv(links.environment)
const hostsError = 'Unable to get hosts'
try {
@ -51,7 +52,12 @@ class HostsPage extends Component {
})
} catch (error) {
console.error(error)
addFlashMessage({type: 'error', text: hostsError})
publishNotification({
icon: 'alert-triangle',
type: 'danger',
duration: 5000,
message: hostsError,
})
this.setState({
hostsError,
hostsLoading: false,
@ -60,14 +66,19 @@ class HostsPage extends Component {
}
async componentDidMount() {
const {addFlashMessage, autoRefresh} = this.props
const {publishNotification, autoRefresh} = this.props
this.setState({hostsLoading: true}) // Only print this once
const {data} = await getLayouts()
this.layouts = data.layouts
if (!this.layouts) {
const layoutError = 'Unable to get apps for hosts'
addFlashMessage({type: 'error', text: layoutError})
publishNotification({
icon: 'alert-triangle',
type: 'danger',
duration: 5000,
message: layoutError,
})
this.setState({
hostsError: layoutError,
hostsLoading: false,
@ -169,11 +180,11 @@ HostsPage.propTypes = {
links: shape({
environment: string.isRequired,
}),
addFlashMessage: func,
autoRefresh: number.isRequired,
manualRefresh: number,
onChooseAutoRefresh: func.isRequired,
onManualRefresh: func.isRequired,
publishNotification: func.isRequired,
}
HostsPage.defaultProps = {
@ -182,6 +193,7 @@ HostsPage.defaultProps = {
const mapDispatchToProps = dispatch => ({
onChooseAutoRefresh: bindActionCreators(setAutoRefresh, dispatch),
publishNotification: bindActionCreators(publishNotificationAction, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(

View File

@ -176,7 +176,7 @@ export const deleteRule = rule => dispatch => {
})
.catch(() => {
dispatch(
publishNotification('error', `${rule.name} could not be deleted`)
publishNotification('danger', `${rule.name} could not be deleted`)
)
})
}
@ -190,7 +190,7 @@ export const updateRuleStatus = (rule, status) => dispatch => {
})
.catch(() => {
dispatch(
publishNotification('error', `${rule.name} could not be ${status}`)
publishNotification('danger', `${rule.name} could not be ${status}`)
)
})
}

View File

@ -1,6 +1,10 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import _ from 'lodash'
import {publishNotification as publishNotificationAction} from 'shared/actions/notifications'
import {Tab, Tabs, TabPanel, TabPanels, TabList} from 'shared/components/Tabs'
import {
@ -48,8 +52,10 @@ class AlertTabs extends Component {
this.setState({configSections: sections})
} catch (error) {
this.setState({configSections: null})
this.props.addFlashMessage({
type: 'error',
this.props.publishNotification({
type: 'danger',
icon: 'alert-triangle',
duration: 10000,
text: 'There was an error getting the Kapacitor config',
})
}
@ -81,15 +87,19 @@ class AlertTabs extends Component {
propsToSend
)
this.refreshKapacitorConfig(this.props.kapacitor)
this.props.addFlashMessage({
this.props.publishNotification({
type: 'success',
icon: 'checkmark',
duration: 5000,
text: `Alert configuration for ${section} successfully saved.`,
})
return true
} catch ({data: {error}}) {
const errorMsg = _.join(_.drop(_.split(error, ': '), 2), ': ')
this.props.addFlashMessage({
type: 'error',
this.props.publishNotification({
type: 'danger',
icon: 'alert-triangle',
duration: 10000,
text: `There was an error saving the alert configuration for ${section}: ${errorMsg}`,
})
return false
@ -103,19 +113,25 @@ class AlertTabs extends Component {
try {
const {data} = await testAlertOutput(this.props.kapacitor, section)
if (data.success) {
this.props.addFlashMessage({
this.props.publishNotification({
type: 'success',
icon: 'checkmark',
duration: 5000,
text: `Successfully triggered an alert to ${section}. If the alert does not reach its destination, please check your configuration settings.`,
})
} else {
this.props.addFlashMessage({
type: 'error',
this.props.publishNotification({
type: 'danger',
icon: 'alert-triangle',
duration: 10000,
text: `There was an error sending an alert to ${section}: ${data.message}`,
})
}
} catch (error) {
this.props.addFlashMessage({
type: 'error',
this.props.publishNotification({
type: 'danger',
icon: 'alert-triangle',
duration: 10000,
text: `There was an error sending an alert to ${section}.`,
})
}
@ -329,8 +345,12 @@ AlertTabs.propTypes = {
proxy: string.isRequired,
}).isRequired,
}),
addFlashMessage: func.isRequired,
publishNotification: func.isRequired,
hash: string.isRequired,
}
export default AlertTabs
const mapDispatchToProps = dispatch => ({
publishNotification: bindActionCreators(publishNotificationAction, dispatch),
})
export default connect(null, mapDispatchToProps)(AlertTabs)

View File

@ -97,7 +97,6 @@ DataSection.propTypes = {
query: shape({
id: string.isRequired,
}).isRequired,
addFlashMessage: func,
actions: shape({
chooseNamespace: func.isRequired,
chooseMeasurement: func.isRequired,

View File

@ -0,0 +1,161 @@
import React, {Component, PropTypes} from 'react'
import AlertTabs from 'src/kapacitor/components/AlertTabs'
import FancyScrollbar from 'shared/components/FancyScrollbar'
class KapacitorForm extends Component {
render() {
const {
onInputChange,
onChangeUrl,
onReset,
kapacitor,
onSubmit,
exists,
} = this.props
const {url, name, username, password} = kapacitor
return (
<div className="page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">{`${exists
? 'Configure'
: 'Add a New'} Kapacitor Connection`}</h1>
</div>
</div>
</div>
<FancyScrollbar className="page-contents">
<div className="container-fluid">
<div className="row">
<div className="col-md-3">
<div className="panel">
<div className="panel-heading">
<h2 className="panel-title">Connection Details</h2>
</div>
<div className="panel-body">
<form onSubmit={onSubmit}>
<div>
<div className="form-group">
<label htmlFor="kapaUrl">Kapacitor URL</label>
<input
className="form-control"
id="kapaUrl"
name="kapaUrl"
placeholder={url}
value={url}
onChange={onChangeUrl}
spellCheck="false"
/>
</div>
<div className="form-group">
<label htmlFor="name">Name</label>
<input
className="form-control"
id="name"
name="name"
placeholder={name}
value={name}
onChange={onInputChange}
spellCheck="false"
maxLength="33"
/>
</div>
<div className="form-group">
<label htmlFor="username">Username</label>
<input
className="form-control"
id="username"
name="username"
placeholder="username"
value={username || ''}
onChange={onInputChange}
spellCheck="false"
/>
</div>
<div className="form-group">
<label htmlFor="password">Password</label>
<input
className="form-control"
id="password"
type="password"
name="password"
placeholder="password"
value={password || ''}
onChange={onInputChange}
spellCheck="false"
/>
</div>
</div>
<div className="form-group form-group-submit col-xs-12 text-center">
<button
className="btn btn-default"
type="button"
onClick={onReset}
>
Reset
</button>
<button className="btn btn-success" type="submit">
{exists ? 'Update' : 'Connect'}
</button>
</div>
</form>
</div>
</div>
</div>
<div className="col-md-9">
{this.renderAlertOutputs()}
</div>
</div>
</div>
</FancyScrollbar>
</div>
)
}
// TODO: move these to another page. they dont belong on this page
renderAlertOutputs() {
const {exists, kapacitor, source, hash} = this.props
if (exists) {
return <AlertTabs source={source} kapacitor={kapacitor} hash={hash} />
}
return (
<div className="panel">
<div className="panel-heading">
<h2 className="panel-title">Configure Alert Endpoints</h2>
</div>
<div className="panel-body">
<div className="generic-empty-state">
<h4 className="no-user-select">
Connect to an active Kapacitor instance to configure alerting
endpoints
</h4>
</div>
</div>
</div>
)
}
}
const {func, shape, string, bool} = PropTypes
KapacitorForm.propTypes = {
onSubmit: func.isRequired,
onInputChange: func.isRequired,
onChangeUrl: func.isRequired,
onReset: func.isRequired,
kapacitor: shape({
url: string.isRequired,
name: string.isRequired,
username: string,
password: string,
}).isRequired,
source: shape({}).isRequired,
exists: bool.isRequired,
hash: string.isRequired,
}
export default KapacitorForm

View File

@ -1,5 +1,7 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import NameSection from 'src/kapacitor/components/NameSection'
import ValuesSection from 'src/kapacitor/components/ValuesSection'
@ -12,6 +14,7 @@ import {createRule, editRule} from 'src/kapacitor/apis'
import buildInfluxQLQuery from 'utils/influxql'
import {timeRanges} from 'shared/data/timeRanges'
import {DEFAULT_RULE_ID} from 'src/kapacitor/constants'
import {publishNotification as publishNotificationAction} from 'shared/actions/notifications'
class KapacitorRule extends Component {
constructor(props) {
@ -28,7 +31,7 @@ class KapacitorRule extends Component {
handleCreate = pathname => {
const {
addFlashMessage,
publishNotification,
queryConfigs,
rule,
source,
@ -44,18 +47,25 @@ class KapacitorRule extends Component {
createRule(kapacitor, newRule)
.then(() => {
router.push(pathname || `/sources/${source.id}/alert-rules`)
addFlashMessage({type: 'success', text: 'Rule successfully created'})
publishNotification({
type: 'success',
icon: 'checkmark',
duration: 5000,
message: 'Rule successfully created',
})
})
.catch(() => {
addFlashMessage({
type: 'error',
publishNotification({
type: 'danger',
icon: 'alert-triangle',
duration: 10000,
text: 'There was a problem creating the rule',
})
})
}
handleEdit = pathname => {
const {addFlashMessage, queryConfigs, rule, router, source} = this.props
const {publishNotification, queryConfigs, rule, router, source} = this.props
const updatedRule = Object.assign({}, rule, {
query: queryConfigs[rule.queryID],
})
@ -63,14 +73,18 @@ class KapacitorRule extends Component {
editRule(updatedRule)
.then(() => {
router.push(pathname || `/sources/${source.id}/alert-rules`)
addFlashMessage({
publishNotification({
type: 'success',
icon: 'checkmark',
duration: 5000,
text: `${rule.name} successfully saved!`,
})
})
.catch(e => {
addFlashMessage({
type: 'error',
publishNotification({
type: 'danger',
icon: 'alert-triangle',
duration: 10000,
text: `There was a problem saving ${rule.name}: ${e.data.message}`,
})
})
@ -234,7 +248,7 @@ KapacitorRule.propTypes = {
queryConfigs: shape({}).isRequired,
queryConfigActions: shape({}).isRequired,
ruleActions: shape({}).isRequired,
addFlashMessage: func.isRequired,
publishNotification: func.isRequired,
ruleID: string.isRequired,
handlersFromConfig: arrayOf(shape({})).isRequired,
router: shape({
@ -244,4 +258,8 @@ KapacitorRule.propTypes = {
configLink: string.isRequired,
}
export default KapacitorRule
const mapDispatchToProps = dispatch => ({
publishNotification: bindActionCreators(publishNotificationAction, dispatch),
})
export default connect(null, mapDispatchToProps)(KapacitorRule)

View File

@ -0,0 +1,202 @@
import React, {Component, PropTypes} from 'react'
import {withRouter} from 'react-router'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {publishNotification as publishNotificationAction} from 'shared/actions/notifications'
import {
getKapacitor,
createKapacitor,
updateKapacitor,
pingKapacitor,
} from 'shared/apis'
import KapacitorForm from '../components/KapacitorForm'
const defaultName = 'My Kapacitor'
const kapacitorPort = '9092'
class KapacitorPage extends Component {
constructor(props) {
super(props)
this.state = {
kapacitor: {
url: this._parseKapacitorURL(),
name: defaultName,
username: '',
password: '',
},
exists: false,
}
}
componentDidMount() {
const {source, params: {id}} = this.props
if (!id) {
return
}
getKapacitor(source, id).then(kapacitor => {
this.setState({kapacitor})
this.checkKapacitorConnection(kapacitor)
})
}
checkKapacitorConnection = async kapacitor => {
try {
await pingKapacitor(kapacitor)
this.setState({exists: true})
} catch (error) {
this.setState({exists: false})
this.props.publishNotification({
type: 'danger',
icon: 'alert-triangle',
duration: 10000,
text: 'Could not connect to Kapacitor. Check settings.',
})
}
}
handleInputChange = e => {
const {value, name} = e.target
this.setState(prevState => {
const update = {[name]: value}
return {kapacitor: {...prevState.kapacitor, ...update}}
})
}
handleChangeUrl = ({value}) => {
this.setState({kapacitor: {...this.state.kapacitor, url: value}})
}
handleSubmit = e => {
e.preventDefault()
const {
publishNotification,
source,
source: {kapacitors = []},
params,
router,
} = this.props
const {kapacitor} = this.state
kapacitor.name = kapacitor.name.trim()
const isNameTaken = kapacitors.some(k => k.name === kapacitor.name)
const isNew = !params.id
if (isNew && isNameTaken) {
publishNotification({
type: 'danger',
icon: 'alert-triangle',
duration: 10000,
text: `There is already a Kapacitor configuration named "${kapacitor.name}"`,
})
return
}
if (params.id) {
updateKapacitor(kapacitor)
.then(({data}) => {
this.setState({kapacitor: data})
this.checkKapacitorConnection(data)
publishNotification({
type: 'success',
icon: 'checkmark',
duration: 5000,
message: 'Kapacitor Updated!',
})
})
.catch(() => {
publishNotification({
type: 'danger',
icon: 'alert-triangle',
duration: 10000,
text: 'There was a problem updating the Kapacitor record',
})
})
} else {
createKapacitor(source, kapacitor)
.then(({data}) => {
// need up update kapacitor with info from server to AlertOutputs
this.setState({kapacitor: data})
this.checkKapacitorConnection(data)
router.push(`/sources/${source.id}/kapacitors/${data.id}/edit`)
publishNotification({
type: 'success',
icon: 'checkmark',
duration: 5000,
text: 'Kapacitor Created! Configuring endpoints is optional.',
})
})
.catch(() => {
publishNotification({
type: 'danger',
icon: 'alert-triangle',
duration: 10000,
text: 'There was a problem creating the Kapacitor record',
})
})
}
}
handleResetToDefaults = e => {
e.preventDefault()
const defaultState = {
url: this._parseKapacitorURL(),
name: defaultName,
username: '',
password: '',
}
this.setState({kapacitor: {...defaultState}})
}
_parseKapacitorURL = () => {
const parser = document.createElement('a')
parser.href = this.props.source.url
return `${parser.protocol}//${parser.hostname}:${kapacitorPort}`
}
render() {
const {source, location, params} = this.props
const hash = (location && location.hash) || (params && params.hash) || ''
const {kapacitor, exists} = this.state
return (
<KapacitorForm
onSubmit={this.handleSubmit}
onInputChange={this.handleInputChange}
onChangeUrl={this.handleChangeUrl}
onReset={this.handleResetToDefaults}
kapacitor={kapacitor}
source={source}
exists={exists}
hash={hash}
/>
)
}
}
const {array, func, shape, string} = PropTypes
KapacitorPage.propTypes = {
publishNotification: func.isRequired,
params: shape({
id: string,
}).isRequired,
router: shape({
push: func.isRequired,
}).isRequired,
source: shape({
id: string.isRequired,
url: string.isRequired,
kapacitors: array,
}),
location: shape({pathname: string, hash: string}).isRequired,
}
const mapDispatchToProps = dispatch => ({
publishNotification: bindActionCreators(publishNotificationAction, dispatch),
})
export default connect(null, mapDispatchToProps)(withRouter(KapacitorPage))

View File

@ -10,6 +10,7 @@ import {getActiveKapacitor, getKapacitorConfig} from 'shared/apis/index'
import {DEFAULT_RULE_ID} from 'src/kapacitor/constants'
import KapacitorRule from 'src/kapacitor/components/KapacitorRule'
import parseHandlersFromConfig from 'src/shared/parsing/parseHandlersFromConfig'
import {publishNotification as publishNotificationAction} from 'shared/actions/notifications'
class KapacitorRulePage extends Component {
constructor(props) {
@ -22,7 +23,7 @@ class KapacitorRulePage extends Component {
}
async componentDidMount() {
const {params, source, ruleActions, addFlashMessage} = this.props
const {params, source, ruleActions, publishNotification} = this.props
if (params.ruleID === 'new') {
ruleActions.loadDefaultRule()
@ -32,8 +33,10 @@ class KapacitorRulePage extends Component {
const kapacitor = await getActiveKapacitor(this.props.source)
if (!kapacitor) {
return addFlashMessage({
type: 'error',
return publishNotification({
type: 'danger',
icon: 'alert-triangle',
duration: 10000,
text: "We couldn't find a configured Kapacitor for this source", // eslint-disable-line quotes
})
}
@ -43,8 +46,10 @@ class KapacitorRulePage extends Component {
const handlersFromConfig = parseHandlersFromConfig(kapacitorConfig)
this.setState({kapacitor, handlersFromConfig})
} catch (error) {
addFlashMessage({
type: 'error',
publishNotification({
type: 'danger',
icon: 'alert-triangle',
duration: 10000,
text: 'There was a problem communicating with Kapacitor',
})
console.error(error)
@ -60,7 +65,6 @@ class KapacitorRulePage extends Component {
router,
ruleActions,
queryConfigs,
addFlashMessage,
queryConfigActions,
} = this.props
const {handlersFromConfig, kapacitor} = this.state
@ -79,7 +83,6 @@ class KapacitorRulePage extends Component {
queryConfigs={queryConfigs}
queryConfigActions={queryConfigActions}
ruleActions={ruleActions}
addFlashMessage={addFlashMessage}
handlersFromConfig={handlersFromConfig}
ruleID={params.ruleID}
router={router}
@ -99,7 +102,7 @@ KapacitorRulePage.propTypes = {
self: string.isRequired,
}),
}),
addFlashMessage: func,
publishNotification: func,
rules: shape({}).isRequired,
queryConfigs: shape({}).isRequired,
ruleActions: shape({
@ -128,6 +131,7 @@ const mapStateToProps = ({rules, kapacitorQueryConfigs: queryConfigs}) => ({
const mapDispatchToProps = dispatch => ({
ruleActions: bindActionCreators(kapacitorRuleActionCreators, dispatch),
publishNotification: bindActionCreators(publishNotificationAction, dispatch),
queryConfigActions: bindActionCreators(
kapacitorQueryConfigActionCreators,
dispatch

View File

@ -78,7 +78,6 @@ KapacitorRulesPage.propTypes = {
deleteRule: func.isRequired,
updateRuleStatus: func.isRequired,
}).isRequired,
addFlashMessage: func,
}
const mapStateToProps = state => {

View File

@ -12,7 +12,6 @@ export const KapacitorTasksPage = React.createClass({
kapacitors: PropTypes.string.isRequired,
}).isRequired,
}).isRequired,
addFlashMessage: PropTypes.func,
},
getInitialState() {

View File

@ -72,7 +72,7 @@ export const removeAndLoadSources = source => async dispatch => {
dispatch(loadSources(newSources))
} catch (err) {
dispatch(
publishNotification('error', 'Internal Server Error. Check API Logs')
publishNotification('danger', 'Internal Server Error. Check API Logs')
)
}
}
@ -84,7 +84,7 @@ export const fetchKapacitorsAsync = source => async dispatch => {
} catch (err) {
dispatch(
publishNotification(
'error',
'danger',
`Internal Server Error. Could not retrieve kapacitors for source ${source.id}.`
)
)
@ -105,7 +105,7 @@ export const deleteKapacitorAsync = kapacitor => async dispatch => {
} catch (err) {
dispatch(
publishNotification(
'error',
'danger',
'Internal Server Error. Could not delete Kapacitor config.'
)
)

View File

@ -9,6 +9,7 @@ import {
setActiveKapacitorAsync,
deleteKapacitorAsync,
} from 'shared/actions/sources'
import {publishNotification as publishNotificationAction} from 'shared/actions/notifications'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import SourceIndicator from 'shared/components/SourceIndicator'
@ -36,18 +37,22 @@ class ManageSources extends Component {
}
handleDeleteSource = source => () => {
const {addFlashMessage} = this.props
const {publishNotification} = this.props
try {
this.props.removeAndLoadSources(source)
addFlashMessage({
publishNotification({
type: 'success',
text: `Deleted source ${source.name}`,
icon: 'checkmark',
duration: 5000,
message: `Deleted source ${source.name}`,
})
} catch (e) {
addFlashMessage({
type: 'error',
text: 'Could not remove source from Chronograf',
publishNotification({
type: 'danger',
icon: 'alert-triangle',
duration: 10000,
message: 'Could not remove source from Chronograf',
})
}
}
@ -101,7 +106,7 @@ ManageSources.propTypes = {
}),
}),
sources: array,
addFlashMessage: func,
publishNotification: func.isRequired,
removeAndLoadSources: func.isRequired,
fetchKapacitors: func.isRequired,
setActiveKapacitor: func.isRequired,
@ -117,6 +122,7 @@ const mapDispatchToProps = dispatch => ({
fetchKapacitors: bindActionCreators(fetchKapacitorsAsync, dispatch),
setActiveKapacitor: bindActionCreators(setActiveKapacitorAsync, dispatch),
deleteKapacitor: bindActionCreators(deleteKapacitorAsync, dispatch),
publishNotification: bindActionCreators(publishNotificationAction, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(ManageSources)