Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes remaining bugs with multi slack handlers #3482

Merged
merged 1 commit into from
May 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions kapacitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ type Slack struct {
Channel string `json:"channel"` // Slack channel in which to post messages.
Username string `json:"username"` // Username of the Slack bot.
IconEmoji string `json:"iconEmoji"` // IconEmoji is an emoji name surrounded in ':' characters; The emoji image will replace the normal user icon for the slack bot.
Workspace string `json:"workspace"` // Workspace is the slack workspace for the alert handler
}

// Telegram sends alerts to telegram.org
Expand Down
2 changes: 1 addition & 1 deletion server/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -3438,7 +3438,7 @@
"name": {
"type": "string",
"description":
"Name of the kapacitor property e.g. channel for a slack ndoe"
"Name of the kapacitor property e.g. channel for a slack node"
},
"args": {
"type": "array",
Expand Down
13 changes: 10 additions & 3 deletions ui/src/kapacitor/components/AlertTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -516,9 +516,16 @@ class AlertTabs extends PureComponent<Props, State> {
this.refreshKapacitorConfig(this.props.kapacitor)
this.props.notify(notifyAlertEndpointSaved(section))
return true
} catch ({
data: {error},
}) {
} catch (err) {
const {data} = err
const {error} = data
if (!error) {
this.props.notify(
notifyAlertEndpointSaveFailed(section, 'Cannot save configuration')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason why it can't save? this is more of a ux thing, but i could see a user not understanding why it couldn't save, so if there's any further specificity here possible, what do you think about adding it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we just get a 502 back from Kapa with no additional information.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

word

)

return false
}
const errorMsg = error.split(': ').pop()
this.props.notify(notifyAlertEndpointSaveFailed(section, errorMsg))
return false
Expand Down
33 changes: 18 additions & 15 deletions ui/src/kapacitor/components/HandlerTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,24 @@ const HandlerTabs: SFC<Props> = ({
}) =>
handlersOnThisAlert.length ? (
<ul className="endpoint-tabs">
{handlersOnThisAlert.map(ep => (
<li
key={uuid.v4()}
className={classnames('endpoint-tab', {
active: ep.alias === (selectedHandler && selectedHandler.alias),
})}
onClick={handleChooseHandler(ep)}
>
{ep.text}
<button
className="endpoint-tab--delete"
onClick={handleRemoveHandler(ep)}
/>
</li>
))}
{handlersOnThisAlert.map(endpoint => {
return (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

word. i'm preferring this, much like getting rid of ternaries.

<li
key={uuid.v4()}
className={classnames('endpoint-tab', {
active:
endpoint.alias === (selectedHandler && selectedHandler.alias),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i appreciate this clarification of the var name.

})}
onClick={handleChooseHandler(endpoint)}
>
{endpoint.text}
<button
className="endpoint-tab--delete"
onClick={handleRemoveHandler(endpoint)}
/>
</li>
)
})}
</ul>
) : null

Expand Down
103 changes: 68 additions & 35 deletions ui/src/kapacitor/components/config/SlackConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import RedactedInput from 'src/kapacitor/components/config/RedactedInput'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {SlackProperties} from 'src/types/kapacitor'

interface Options {
url: boolean
channel: string
workspace: string
}

interface Config {
options: {
url: boolean
channel: string
workspace: string
}
options: Options
}

interface Props {
Expand All @@ -33,32 +35,26 @@ interface Props {
interface State {
testEnabled: boolean
enabled: boolean
workspace: string
}

@ErrorHandling
class SlackConfig extends PureComponent<Props, State> {
private url: HTMLInputElement
private channel: HTMLInputElement
private workspace: HTMLInputElement

constructor(props) {
super(props)
this.state = {
testEnabled: this.props.enabled,
enabled: _.get(this.props, 'config.options.enabled', false),
workspace: _.get(this.props, 'config.options.workspace') || '',
}
}

public render() {
const {
config: {
options: {url, channel, workspace},
},
isNewConfig,
} = this.props
const {testEnabled, enabled} = this.state

const isNickNameEnabled = isNewConfig && !testEnabled
const {url, channel} = this.options
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice/interesting -- i kind of like this pattern of refactoring this into a getter, though it does introduce some boilerplate

const {testEnabled, enabled, workspace} = this.state

return (
<form onSubmit={this.handleSubmit}>
Expand All @@ -71,10 +67,9 @@ class SlackConfig extends PureComponent<Props, State> {
id={`${this.workspaceID}-nickname`}
type="text"
placeholder={this.nicknamePlaceholder}
ref={r => (this.workspace = r)}
defaultValue={workspace || ''}
onChange={this.disableTest}
disabled={!isNickNameEnabled}
value={workspace}
onChange={this.handleWorkspaceChange}
disabled={!this.isNewConfig && !testEnabled}
/>
</div>
<div className="form-group col-xs-12">
Expand Down Expand Up @@ -127,31 +122,57 @@ class SlackConfig extends PureComponent<Props, State> {
<button
className="btn btn-primary"
type="submit"
disabled={testEnabled}
disabled={testEnabled || this.isWorkspaceEmpty}
>
<span className="icon checkmark" />
Save Changes
</button>
<button
className="btn btn-primary"
disabled={!testEnabled || !enabled}
disabled={this.isTestDisabled}
onClick={this.handleTest}
>
<span className="icon pulse-c" />
Send Test Alert
</button>
{!this.isDefaultConfig && (
<button className="btn btn-danger" onClick={this.handleDelete}>
<span className="icon trash" />
Delete
</button>
)}
{this.deleteButton}
<hr />
</div>
</form>
)
}

private handleWorkspaceChange = (e: ChangeEvent<HTMLInputElement>) => {
this.setState({workspace: e.target.value})
}

private get isNewConfig(): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting to see all of these as getters. i'm conflicted about it, as mentioned above, since it's more boilerplate but also more modular. no action requested, just digesting a new pattern.

const {isNewConfig} = this.props

return isNewConfig
}

private get options(): Options {
const {
config: {options},
} = this.props

return options
}

private get deleteButton(): JSX.Element {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice! i like this patter of jsx elements as getters

if (this.isDefaultConfig) {
return null
}

return (
<button className="btn btn-danger" onClick={this.handleDelete}>
<span className="icon trash" />
Delete
</button>
)
}

private get nicknamePlaceholder(): string {
if (this.isDefaultConfig) {
return 'Only for additional Configurations'
Expand All @@ -167,6 +188,16 @@ class SlackConfig extends PureComponent<Props, State> {
return this.props.workspaceID
}

private get isWorkspaceEmpty(): boolean {
const {workspace} = this.state
return workspace === '' && !this.isDefaultConfig
}

private get isTestDisabled(): boolean {
const {testEnabled, enabled} = this.state
return !testEnabled || !enabled || this.isWorkspaceEmpty
}

private handleTest = (e: MouseEvent<HTMLButtonElement>) => {
const {
onTest,
Expand All @@ -183,29 +214,31 @@ class SlackConfig extends PureComponent<Props, State> {
}

private handleSubmit = async e => {
const {isNewConfig} = this.props
e.preventDefault()

const {workspace} = this.state
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you share your thoughts on why this would be from state as opposed to other state destructuring that has been modularized into getters?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

State is a little different to me than props, since there is no computed value needed I felt fine about destructuring from state.

const {isNewConfig} = this.props

const properties: SlackProperties = {
url: this.url.value,
channel: this.channel.value,
enabled: this.state.enabled,
}

if (isNewConfig) {
properties.workspace = this.workspace.value
properties.workspace = workspace
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. a bit strange seeing a value on an object being assigned directly -- could a spread operator be used here?
  2. also in the context of all of these getters, this reads a bit weird to me -- what do you think about workspace --> this.workspace and putting workspace behind a getter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we only want to send the workspace on create, for updates we want to omit the key altogether.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}
const success = await this.props.onSave(
properties,
isNewConfig,
this.workspace.value
)

const success = await this.props.onSave(properties, isNewConfig, workspace)
if (success) {
this.setState({testEnabled: true})
}
}

private handleDelete = async e => {
e.preventDefault()
await this.props.onDelete(this.workspace.value, this.workspaceID)
const {workspace} = this.state
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see question above about a possible getter

await this.props.onDelete(workspace, this.workspaceID)
}

private disableTest = () => {
Expand Down
21 changes: 18 additions & 3 deletions ui/src/kapacitor/components/config/SlackConfigs.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, {PureComponent, MouseEvent} from 'react'
import _ from 'lodash'
import {get} from 'src/utils/wrappers'

import {ErrorHandling} from 'src/shared/decorators/errors'
Expand Down Expand Up @@ -48,7 +49,7 @@ class SlackConfigs extends PureComponent<Props, State> {

public render() {
const {configs} = this.state
const {onSave, onTest, onEnabled, isMultipleConfigsSupported} = this.props
const {onSave, onTest, onEnabled} = this.props

return (
<div>
Expand All @@ -73,7 +74,7 @@ class SlackConfigs extends PureComponent<Props, State> {
/>
)
})}
{isMultipleConfigsSupported && (
{this.isAddingConfigsAllowed && (
<div className="form-group col-xs-12 text-center">
<button className="btn btn-md btn-default" onClick={this.addConfig}>
<span className="icon plus" /> Add Another Config
Expand All @@ -88,6 +89,14 @@ class SlackConfigs extends PureComponent<Props, State> {
return this.state.configs
}

private get isAddingConfigsAllowed(): boolean {
const {isMultipleConfigsSupported} = this.props
const {configs} = this.state
const isAllConfigsPersisted = _.every(configs, c => !this.isNewConfig(c))

return isMultipleConfigsSupported && isAllConfigsPersisted
}

private isNewConfig = (config: Config): boolean => {
return get(config, 'isNewConfig', false)
}
Expand All @@ -99,7 +108,13 @@ class SlackConfigs extends PureComponent<Props, State> {
private getWorkspace = (config: Config): string => {
const {isMultipleConfigsSupported} = this.props
if (isMultipleConfigsSupported) {
return get(config, 'options.workspace', 'new')
const workspace = _.get(config, 'options', {workspace: null}).workspace
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting


if (workspace !== null) {
return workspace
}

return 'new'
}
return ''
}
Expand Down
18 changes: 9 additions & 9 deletions ui/src/types/kapacitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,30 +263,30 @@ export interface FieldsFromAllAlerts extends FieldsFromConfigAlerts {
log: string[]
}

interface pagerDutyConfigKeyMap {
interface PagerDutyConfigKeyMap {
'service-key': string
}

interface pushoverConfigKeyMap {
'user-key': String
interface PushoverConfigKeyMap {
'user-key': string
}

interface telegramConfigKeyMap {
interface TelegramConfigKeyMap {
'chat-id': string
'parse-mode': string
'disable-web-page-preview': string
'disable-notification': string
}

interface victorOpsConfigKeyMap {
interface VictorOpsConfigKeyMap {
'routing-key': string
}

export type ConfigKeyMaps =
| pagerDutyConfigKeyMap
| pushoverConfigKeyMap
| telegramConfigKeyMap
| victorOpsConfigKeyMap
| PagerDutyConfigKeyMap
| PushoverConfigKeyMap
| TelegramConfigKeyMap
| VictorOpsConfigKeyMap
| {}

export interface AlertaProperties {
Expand Down