Merge branch 'master' of github.com:influxdata/chronograf

pull/10616/head
Jared Scheib 2018-05-11 11:17:00 -07:00
commit abab439eed
41 changed files with 953 additions and 465 deletions

View File

@ -5,6 +5,7 @@
1. [#3233](https://github.com/influxdata/chronograf/pull/3233): Add default retention policy field as option in source configuration for use in querying hosts from Host List page & Host pages
1. [#3290](https://github.com/influxdata/chronograf/pull/3290): Add support for PagerDuty v2 in UI
1. [#3369](https://github.com/influxdata/chronograf/pull/3369): Add support for OpsGenie v2 in UI
1. [#3386](https://github.com/influxdata/chronograf/pull/3386): Add support for Kafka in UI to configure and create alert handlers
1. [#3416](https://github.com/influxdata/chronograf/pull/3416): Allow kapacitor services to be disabled
### UI Improvements

View File

@ -22,7 +22,8 @@ type AlertNodes struct {
Alerta []*Alerta `json:"alerta"` // Alerta will send alert to all Alerta
OpsGenie []*OpsGenie `json:"opsGenie"` // OpsGenie will send alert to all OpsGenie
OpsGenie2 []*OpsGenie `json:"opsGenie2"` // OpsGenie2 will send alert to all OpsGenie v2
Talk []*Talk `json:"talk"` // Talk will send alert to all Talk
Talk []*Talk `json:"talk"` // Talk will send alert to all Talk
Kafka []*Kafka `json:"kafka"` // Kafka will send alert to all Kafka
}
// Post will POST alerts to a destination URL
@ -135,6 +136,13 @@ type OpsGenie struct {
// Talk sends alerts to Jane Talk (https://jianliao.com/site)
type Talk struct{}
// Kafka sends alerts to any Kafka brokers specified in the handler config
type Kafka struct {
Cluster string `json:"cluster"`
Topic string `json:"topic"`
Template string `json:"template"`
}
// MarshalJSON converts AlertNodes to JSON
func (n *AlertNodes) MarshalJSON() ([]byte, error) {
type Alias AlertNodes

View File

@ -410,6 +410,10 @@ func newAlertResponse(task *kapa.Task, srcID, kapaID int) *alertResponse {
res.AlertNodes.HipChat = []*chronograf.HipChat{}
}
if res.AlertNodes.Kafka == nil {
res.AlertNodes.Kafka = []*chronograf.Kafka{}
}
if res.AlertNodes.Log == nil {
res.AlertNodes.Log = []*chronograf.Log{}
}

View File

@ -132,6 +132,7 @@ func Test_KapacitorRulesGet(t *testing.T) {
OpsGenie: []*chronograf.OpsGenie{},
OpsGenie2: []*chronograf.OpsGenie{},
Talk: []*chronograf.Talk{},
Kafka: []*chronograf.Kafka{},
},
},
},

View File

@ -3677,6 +3677,7 @@
"post",
"http",
"hipchat",
"kafka",
"opsgenie",
"opsgenie2",
"pagerduty",

View File

@ -24,7 +24,7 @@ import {DEFAULT_HOME_PAGE} from 'src/shared/constants'
import * as copy from 'src/shared/copy/notifications'
import {Source, Me} from 'src/types'
import {Source, Me, Notification, NotificationFunc} from 'src/types'
interface Auth {
isUsingAuth: boolean
@ -47,7 +47,7 @@ interface Props {
router: InjectedRouter
location: Location
auth: Auth
notify: () => void
notify: (message: Notification | NotificationFunc) => void
errorThrown: () => void
}

View File

@ -8,10 +8,17 @@ import {notify as notifyAction} from 'src/shared/actions/notifications'
import {ErrorHandling} from 'src/shared/decorators/errors'
import AllUsersTable from 'src/admin/components/chronograf/AllUsersTable'
import {AuthLinks, Organization, Role, User} from 'src/types'
import {
AuthLinks,
Organization,
Role,
User,
Notification,
NotificationFunc,
} from 'src/types'
interface Props {
notify: () => void
notify: (message: Notification | NotificationFunc) => void
links: AuthLinks
meID: string
users: User[]

View File

@ -3,11 +3,6 @@ import React, {Component} from 'react'
import _ from 'lodash'
import uuid from 'uuid'
import {
CellEditorOverlayActions,
CellEditorOverlayActionsFunc,
} from 'src/types/dashboard'
import ResizeContainer from 'src/shared/components/ResizeContainer'
import QueryMaker from 'src/dashboards/components/QueryMaker'
import Visualization from 'src/dashboards/components/Visualization'
@ -15,7 +10,7 @@ import OverlayControls from 'src/dashboards/components/OverlayControls'
import DisplayOptions from 'src/dashboards/components/DisplayOptions'
import CEOBottom from 'src/dashboards/components/CEOBottom'
import * as queryModifiers from 'src/utils/queryTransitions'
import * as queryTransitions from 'src/utils/queryTransitions'
import defaultQueryConfig from 'src/utils/defaultQueryConfig'
import {buildQuery} from 'src/utils/influxql'
@ -36,6 +31,9 @@ import {
TEMP_VAR_DASHBOARD_TIME,
} from 'src/shared/constants'
import {getCellTypeColors} from 'src/dashboards/constants/cellEditor'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {
TimeRange,
Source,
@ -45,7 +43,19 @@ import {
Legend,
Status,
} from 'src/types'
import {ErrorHandling} from 'src/shared/decorators/errors'
type QueryTransitions = typeof queryTransitions
type EditRawTextAsyncFunc = (
url: string,
id: string,
text: string
) => Promise<void>
type CellEditorOverlayActionsFunc = (queryID: string, ...args: any[]) => void
type QueryActions = {
[K in keyof QueryTransitions]: CellEditorOverlayActionsFunc
}
export type CellEditorOverlayActions = QueryActions & {
editRawTextAsync: EditRawTextAsyncFunc
}
const staticLegend: Legend = {
type: 'static',
@ -248,17 +258,16 @@ class CellEditorOverlay extends Component<Props, State> {
this.overlayRef = r
}
private queryStateReducer = (queryModifier): CellEditorOverlayActionsFunc => (
queryID: string,
...payload: any[]
) => {
private queryStateReducer = (
queryTransition
): CellEditorOverlayActionsFunc => (queryID: string, ...payload: any[]) => {
const {queriesWorkingDraft} = this.state
const query = queriesWorkingDraft.find(q => q.id === queryID)
const queryWorkingDraft = queriesWorkingDraft.find(q => q.id === queryID)
const nextQuery = queryModifier(query, ...payload)
const nextQuery = queryTransition(queryWorkingDraft, ...payload)
const nextQueries = queriesWorkingDraft.map(q => {
if (q.id === query.id) {
if (q.id === queryWorkingDraft.id) {
return {...nextQuery, source: nextSource(q, nextQuery)}
}
@ -492,20 +501,12 @@ class CellEditorOverlay extends Component<Props, State> {
}
private get queryActions(): CellEditorOverlayActions {
const original = {
editRawTextAsync: () => Promise.resolve(),
...queryModifiers,
}
const mapped = _.reduce<CellEditorOverlayActions, CellEditorOverlayActions>(
original,
(acc, v, k) => {
acc[k] = this.queryStateReducer(v)
return acc
},
original
)
const mapped: QueryActions = _.mapValues<
QueryActions,
CellEditorOverlayActionsFunc
>(queryTransitions, v => this.queryStateReducer(v)) as QueryActions
const result = {
const result: CellEditorOverlayActions = {
...mapped,
editRawTextAsync: this.handleEditRawText,
}

View File

@ -1,9 +1,6 @@
import React, {SFC} from 'react'
import _ from 'lodash'
import {QueryConfig, Source, SourceLinks, TimeRange} from 'src/types'
import {CellEditorOverlayActions} from 'src/types/dashboard'
import EmptyQuery from 'src/shared/components/EmptyQuery'
import QueryTabList from 'src/shared/components/QueryTabList'
import QueryTextArea from 'src/dashboards/components/QueryTextArea'
@ -11,6 +8,9 @@ import SchemaExplorer from 'src/shared/components/SchemaExplorer'
import {buildQuery} from 'src/utils/influxql'
import {TYPE_QUERY_CONFIG, TEMPLATE_RANGE} from 'src/dashboards/constants'
import {QueryConfig, Source, SourceLinks, TimeRange} from 'src/types'
import {CellEditorOverlayActions} from 'src/dashboards/components/CellEditorOverlay'
const rawTextBinder = (
links: SourceLinks,
id: string,

View File

@ -6,24 +6,8 @@ import FunctionSelector from 'src/shared/components/FunctionSelector'
import {firstFieldName} from 'src/shared/reducers/helpers/fields'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Field {
type: string
value: string
}
import {ApplyFuncsToFieldArgs, Field, FieldFunc, FuncArg} from 'src/types'
interface FuncArg {
value: string
type: string
}
interface FieldFunc extends Field {
args: FuncArg[]
}
interface ApplyFuncsToFieldArgs {
field: Field
funcs: FuncArg[]
}
interface Props {
fieldFuncs: FieldFunc[]
isSelected: boolean

View File

@ -2,9 +2,10 @@ import React, {PureComponent} from 'react'
import QueryEditor from './QueryEditor'
import SchemaExplorer from 'src/shared/components/SchemaExplorer'
import {Source, QueryConfig} from 'src/types'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {Source, QueryConfig} from 'src/types'
const rawTextBinder = (links, id, action) => text =>
action(links.queries, id, text)

View File

@ -1,79 +0,0 @@
export const chooseNamespace = (queryID, {database, retentionPolicy}) => ({
type: 'KAPA_CHOOSE_NAMESPACE',
payload: {
queryID,
database,
retentionPolicy,
},
})
export const chooseMeasurement = (queryID, measurement) => ({
type: 'KAPA_CHOOSE_MEASUREMENT',
payload: {
queryID,
measurement,
},
})
export const chooseTag = (queryID, tag) => ({
type: 'KAPA_CHOOSE_TAG',
payload: {
queryID,
tag,
},
})
export const groupByTag = (queryID, tagKey) => ({
type: 'KAPA_GROUP_BY_TAG',
payload: {
queryID,
tagKey,
},
})
export const toggleTagAcceptance = queryID => ({
type: 'KAPA_TOGGLE_TAG_ACCEPTANCE',
payload: {
queryID,
},
})
export const toggleField = (queryID, fieldFunc) => ({
type: 'KAPA_TOGGLE_FIELD',
payload: {
queryID,
fieldFunc,
},
})
export const applyFuncsToField = (queryID, fieldFunc) => ({
type: 'KAPA_APPLY_FUNCS_TO_FIELD',
payload: {
queryID,
fieldFunc,
},
})
export const groupByTime = (queryID, time) => ({
type: 'KAPA_GROUP_BY_TIME',
payload: {
queryID,
time,
},
})
export const removeFuncs = (queryID, fields) => ({
type: 'KAPA_REMOVE_FUNCS',
payload: {
queryID,
fields,
},
})
export const timeShift = (queryID, shift) => ({
type: 'KAPA_TIME_SHIFT',
payload: {
queryID,
shift,
},
})

View File

@ -0,0 +1,183 @@
import {
ApplyFuncsToFieldArgs,
Field,
Namespace,
Tag,
TimeShift,
} from 'src/types'
interface ChooseNamespaceAction {
type: 'KAPA_CHOOSE_NAMESPACE'
payload: {
queryID: string
database: string
retentionPolicy: string
}
}
export const chooseNamespace = (
queryID: string,
{database, retentionPolicy}: Namespace
): ChooseNamespaceAction => ({
type: 'KAPA_CHOOSE_NAMESPACE',
payload: {
queryID,
database,
retentionPolicy,
},
})
interface ChooseMeasurementAction {
type: 'KAPA_CHOOSE_MEASUREMENT'
payload: {
queryID: string
measurement: string
}
}
export const chooseMeasurement = (
queryID: string,
measurement: string
): ChooseMeasurementAction => ({
type: 'KAPA_CHOOSE_MEASUREMENT',
payload: {
queryID,
measurement,
},
})
interface ChooseTagAction {
type: 'KAPA_CHOOSE_TAG'
payload: {
queryID: string
tag: Tag
}
}
export const chooseTag = (queryID: string, tag: Tag): ChooseTagAction => ({
type: 'KAPA_CHOOSE_TAG',
payload: {
queryID,
tag,
},
})
interface GroupByTagAction {
type: 'KAPA_GROUP_BY_TAG'
payload: {
queryID: string
tagKey: string
}
}
export const groupByTag = (
queryID: string,
tagKey: string
): GroupByTagAction => ({
type: 'KAPA_GROUP_BY_TAG',
payload: {
queryID,
tagKey,
},
})
interface ToggleTagAcceptanceAction {
type: 'KAPA_TOGGLE_TAG_ACCEPTANCE'
payload: {
queryID: string
}
}
export const toggleTagAcceptance = (
queryID: string
): ToggleTagAcceptanceAction => ({
type: 'KAPA_TOGGLE_TAG_ACCEPTANCE',
payload: {
queryID,
},
})
interface ToggleFieldAction {
type: 'KAPA_TOGGLE_FIELD'
payload: {
queryID: string
fieldFunc: Field
}
}
export const toggleField = (
queryID: string,
fieldFunc: Field
): ToggleFieldAction => ({
type: 'KAPA_TOGGLE_FIELD',
payload: {
queryID,
fieldFunc,
},
})
interface ApplyFuncsToFieldAction {
type: 'KAPA_APPLY_FUNCS_TO_FIELD'
payload: {
queryID: string
fieldFunc: ApplyFuncsToFieldArgs
}
}
export const applyFuncsToField = (
queryID: string,
fieldFunc: ApplyFuncsToFieldArgs
): ApplyFuncsToFieldAction => ({
type: 'KAPA_APPLY_FUNCS_TO_FIELD',
payload: {
queryID,
fieldFunc,
},
})
interface GroupByTimeAction {
type: 'KAPA_GROUP_BY_TIME'
payload: {
queryID: string
time: string
}
}
export const groupByTime = (
queryID: string,
time: string
): GroupByTimeAction => ({
type: 'KAPA_GROUP_BY_TIME',
payload: {
queryID,
time,
},
})
interface RemoveFuncsAction {
type: 'KAPA_REMOVE_FUNCS'
payload: {
queryID: string
fields: Field[]
}
}
export const removeFuncs = (
queryID: string,
fields: Field[]
): RemoveFuncsAction => ({
type: 'KAPA_REMOVE_FUNCS',
payload: {
queryID,
fields,
},
})
interface TimeShiftAction {
type: 'KAPA_TIME_SHIFT'
payload: {
queryID: string
shift: TimeShift
}
}
export const timeShift = (
queryID: string,
shift: TimeShift
): TimeShiftAction => ({
type: 'KAPA_TIME_SHIFT',
payload: {
queryID,
shift,
},
})

View File

@ -2,17 +2,7 @@ import React, {SFC} from 'react'
import AlertTabs from 'src/kapacitor/components/AlertTabs'
import {Kapacitor, Source} from 'src/types'
export interface Notification {
id?: string
type: string
icon: string
duration: number
message: string
}
type NotificationFunc = () => Notification
import {Kapacitor, Source, Notification, NotificationFunc} from 'src/types'
interface AlertOutputProps {
exists: boolean

View File

@ -19,6 +19,7 @@ import {
import {
AlertaConfig,
HipChatConfig,
KafkaConfig,
OpsGenieConfig,
PagerDutyConfig,
PushoverConfig,
@ -72,6 +73,7 @@ interface Sections {
hipchat: Section
httppost: Section
influxdb: Section
kafka: Section
mqtt: Section
opsgenie: Section
opsgenie2: Section
@ -96,6 +98,7 @@ interface Config {
interface SupportedConfig {
alerta: Config
hipchat: Config
kafka: Config
opsgenie: Config
opsgenie2: Config
pagerduty: Config
@ -222,6 +225,21 @@ class AlertTabs extends PureComponent<Props, State> {
/>
),
},
kafka: {
type: 'Kafka',
enabled: this.getEnabled(configSections, 'kafka'),
renderComponent: () => (
<KafkaConfig
onSave={this.handleSaveConfig('kafka')}
config={this.getSection(configSections, 'kafka')}
onTest={this.handleTestConfig('kafka', {
cluster: this.getProperty(configSections, 'kafka', 'id'),
})}
enabled={this.getEnabled(configSections, 'kafka')}
notify={this.props.notify}
/>
),
},
opsgenie: {
type: 'OpsGenie',
enabled: this.getEnabled(configSections, 'opsgenie'),
@ -434,6 +452,18 @@ class AlertTabs extends PureComponent<Props, State> {
)
}
private getProperty = (
sections: Sections,
section: string,
property: string
): boolean => {
return _.get(
sections,
[section, 'elements', '0', 'options', property],
null
)
}
private handleSaveConfig = (section: string) => async (
properties
): Promise<boolean> => {
@ -451,19 +481,23 @@ class AlertTabs extends PureComponent<Props, State> {
} catch ({
data: {error},
}) {
const errorMsg = _.join(_.drop(_.split(error, ': '), 2), ': ')
const errorMsg = error.split(': ').pop()
this.props.notify(notifyAlertEndpointSaveFailed(section, errorMsg))
return false
}
}
}
private handleTestConfig = (section: string) => async (
private handleTestConfig = (section: string, options?: object) => async (
e: MouseEvent<HTMLButtonElement>
): Promise<void> => {
e.preventDefault()
try {
const {data} = await testAlertOutput(this.props.kapacitor, section)
const {data} = await testAlertOutput(
this.props.kapacitor,
section,
options
)
if (data.success) {
this.props.notify(notifyTestAlertSent(section))
} else {

View File

@ -1,126 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import DatabaseList from 'src/shared/components/DatabaseList'
import MeasurementList from 'src/shared/components/MeasurementList'
import FieldList from 'src/shared/components/FieldList'
import {defaultEveryFrequency} from 'src/kapacitor/constants'
import {SourceContext} from 'src/CheckSources'
const makeQueryHandlers = (actions, query) => ({
handleChooseNamespace: namespace => {
actions.chooseNamespace(query.id, namespace)
},
handleChooseMeasurement: measurement => {
actions.chooseMeasurement(query.id, measurement)
},
handleToggleField: field => {
actions.toggleField(query.id, field)
},
handleGroupByTime: time => {
actions.groupByTime(query.id, time)
},
handleApplyFuncsToField: onAddEvery => fieldFunc => {
actions.applyFuncsToField(query.id, fieldFunc)
onAddEvery(defaultEveryFrequency)
},
handleChooseTag: tag => {
actions.chooseTag(query.id, tag)
},
handleToggleTagAcceptance: () => {
actions.toggleTagAcceptance(query.id)
},
handleGroupByTag: tagKey => {
actions.groupByTag(query.id, tagKey)
},
handleRemoveFuncs: fields => {
actions.removeFuncs(query.id, fields)
},
})
const DataSection = ({
actions,
query,
isDeadman,
isKapacitorRule,
onAddEvery,
}) => {
const {
handleChooseTag,
handleGroupByTag,
handleToggleField,
handleGroupByTime,
handleRemoveFuncs,
handleChooseNamespace,
handleApplyFuncsToField,
handleChooseMeasurement,
handleToggleTagAcceptance,
} = makeQueryHandlers(actions, query)
return (
<SourceContext.Consumer>
{source => (
<div className="rule-section">
<div className="query-builder">
<DatabaseList
query={query}
onChooseNamespace={handleChooseNamespace}
/>
<MeasurementList
query={query}
onChooseMeasurement={handleChooseMeasurement}
onChooseTag={handleChooseTag}
onGroupByTag={handleGroupByTag}
onToggleTagAcceptance={handleToggleTagAcceptance}
/>
{isDeadman ? null : (
<FieldList
query={query}
onToggleField={handleToggleField}
isKapacitorRule={isKapacitorRule}
onGroupByTime={handleGroupByTime}
removeFuncs={handleRemoveFuncs}
applyFuncsToField={handleApplyFuncsToField(onAddEvery)}
source={source}
/>
)}
</div>
</div>
)}
</SourceContext.Consumer>
)
}
const {bool, func, shape, string} = PropTypes
DataSection.propTypes = {
query: shape({
id: string.isRequired,
}).isRequired,
actions: shape({
chooseNamespace: func.isRequired,
chooseMeasurement: func.isRequired,
applyFuncsToField: func.isRequired,
chooseTag: func.isRequired,
groupByTag: func.isRequired,
toggleField: func.isRequired,
groupByTime: func.isRequired,
toggleTagAcceptance: func.isRequired,
}).isRequired,
onAddEvery: func.isRequired,
timeRange: shape({}).isRequired,
isKapacitorRule: bool,
isDeadman: bool,
}
export default DataSection

View File

@ -0,0 +1,119 @@
import React, {PureComponent} from 'react'
import DatabaseList from 'src/shared/components/DatabaseList'
import MeasurementList from 'src/shared/components/MeasurementList'
import FieldList from 'src/shared/components/FieldList'
import {defaultEveryFrequency} from 'src/kapacitor/constants'
import {SourceContext} from 'src/CheckSources'
import {
ApplyFuncsToFieldArgs,
Field,
Namespace,
QueryConfig,
Source,
TimeRange,
Tag,
} from 'src/types'
import {KapacitorQueryConfigActions} from 'src/types/actions'
interface Props {
actions: KapacitorQueryConfigActions
query: QueryConfig
isDeadman: boolean
isKapacitorRule: boolean
onAddEvery: () => void
timeRange: TimeRange
}
class DataSection extends PureComponent<Props> {
public render() {
const {query, isDeadman, isKapacitorRule, onAddEvery} = this.props
return (
<SourceContext.Consumer>
{(source: Source) => (
<div className="rule-section">
<div className="query-builder">
<DatabaseList
query={query}
onChooseNamespace={this.handleChooseNamespace}
/>
<MeasurementList
query={query}
onChooseMeasurement={this.handleChooseMeasurement}
onChooseTag={this.handleChooseTag}
onGroupByTag={this.handleGroupByTag}
onToggleTagAcceptance={this.handleToggleTagAcceptance}
isKapacitorRule={isKapacitorRule}
/>
{isDeadman ? null : (
<FieldList
query={query}
applyFuncsToField={this.handleApplyFuncsToField(onAddEvery)}
onGroupByTime={this.handleGroupByTime}
onToggleField={this.handleToggleField}
removeFuncs={this.handleRemoveFuncs}
isKapacitorRule={isKapacitorRule}
source={source}
/>
)}
</div>
</div>
)}
</SourceContext.Consumer>
)
}
private handleChooseNamespace = (namespace: Namespace): void => {
const {actions, query} = this.props
actions.chooseNamespace(query.id, namespace)
}
private handleChooseMeasurement = (measurement: string): void => {
const {actions, query} = this.props
actions.chooseMeasurement(query.id, measurement)
}
private handleToggleField = (field: Field): void => {
const {actions, query} = this.props
actions.toggleField(query.id, field)
}
private handleGroupByTime = (time: string): void => {
const {actions, query} = this.props
actions.groupByTime(query.id, time)
}
private handleApplyFuncsToField = (onAddEvery: (every: string) => void) => (
fieldFunc: ApplyFuncsToFieldArgs
): void => {
const {actions, query} = this.props
actions.applyFuncsToField(query.id, fieldFunc)
onAddEvery(defaultEveryFrequency)
}
private handleChooseTag = (tag: Tag): void => {
const {actions, query} = this.props
actions.chooseTag(query.id, tag)
}
private handleToggleTagAcceptance = (): void => {
const {actions, query} = this.props
actions.toggleTagAcceptance(query.id)
}
private handleGroupByTag = (tagKey: string): void => {
const {actions, query} = this.props
actions.groupByTag(query.id, tagKey)
}
private handleRemoveFuncs = (fields: Field[]): void => {
const {actions, query} = this.props
actions.removeFuncs(query.id, fields)
}
}
export default DataSection

View File

@ -8,6 +8,7 @@ import {
EmailHandler,
AlertaHandler,
HipchatHandler,
KafkaHandler,
OpsgenieHandler,
PagerdutyHandler,
PushoverHandler,
@ -92,6 +93,15 @@ class HandlerOptions extends Component {
validationError={validationError}
/>
)
case 'kafka':
return (
<KafkaHandler
selectedHandler={selectedHandler}
handleModifyHandler={handleModifyHandler}
onGoToConfig={onGoToConfig('kafka')}
validationError={validationError}
/>
)
case 'opsGenie':
return (
<OpsgenieHandler

View File

@ -3,19 +3,9 @@ import React, {ChangeEvent, MouseEvent, PureComponent} from 'react'
import AlertOutputs from 'src/kapacitor/components/AlertOutputs'
import Input from 'src/kapacitor/components/KapacitorFormInput'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import {Kapacitor, Source} from 'src/types'
import KapacitorFormSkipVerify from 'src/kapacitor/components/KapacitorFormSkipVerify'
export interface Notification {
id?: string
type: string
icon: string
duration: number
message: string
}
type NotificationFunc = () => Notification
import {Kapacitor, Source, Notification, NotificationFunc} from 'src/types'
interface Props {
kapacitor: Kapacitor

View File

@ -0,0 +1,261 @@
import React, {PureComponent} from 'react'
import TagInput from 'src/shared/components/TagInput'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {Notification, NotificationFunc} from 'src/types'
import {notifyInvalidBatchSizeValue} from 'src/shared/copy/notifications'
interface Properties {
brokers: string[]
timeout: string
'batch-size': number
'batch-timeout': string
'use-ssl': boolean
'ssl-ca': string
'ssl-cert': string
'ssl-key': string
'insecure-skip-verify': boolean
}
interface Config {
options: Properties & {
id: string
}
}
interface Item {
name?: string
}
interface Props {
config: Config
onSave: (properties: Properties) => void
onTest: (event: React.MouseEvent<HTMLButtonElement>) => void
enabled: boolean
notify: (message: Notification | NotificationFunc) => void
}
interface State {
currentBrokers: string[]
testEnabled: boolean
}
@ErrorHandling
class KafkaConfig extends PureComponent<Props, State> {
private id: HTMLInputElement
private timeout: HTMLInputElement
private batchSize: HTMLInputElement
private batchTimeout: HTMLInputElement
private useSSL: HTMLInputElement
private sslCA: HTMLInputElement
private sslCert: HTMLInputElement
private sslKey: HTMLInputElement
private insecureSkipVerify: HTMLInputElement
constructor(props) {
super(props)
const {brokers} = props.config.options
this.state = {
currentBrokers: brokers || [],
testEnabled: this.props.enabled,
}
}
public render() {
const {options} = this.props.config
const id = options.id
const timeout = options.timeout
const batchSize = options['batch-size']
const batchTimeout = options['batch-timeout']
const useSSL = options['use-ssl']
const sslCA = options['ssl-ca']
const sslCert = options['ssl-cert']
const sslKey = options['ssl-key']
const insecureSkipVerify = options['insecure-skip-verify']
return (
<form onSubmit={this.handleSubmit}>
<div className="form-group col-xs-12">
<label htmlFor="id">ID</label>
<input
className="form-control"
id="id"
type="text"
ref={r => (this.id = r)}
defaultValue={id || ''}
onChange={this.disableTest}
readOnly={true}
/>
</div>
<TagInput
title="Brokers"
onAddTag={this.handleAddBroker}
onDeleteTag={this.handleDeleteBroker}
tags={this.currentBrokersForTags}
disableTest={this.disableTest}
/>
<div className="form-group col-xs-12">
<label htmlFor="timeout">Timeout</label>
<input
className="form-control"
id="timeout"
type="text"
ref={r => (this.timeout = r)}
defaultValue={timeout || ''}
onChange={this.disableTest}
/>
</div>
<div className="form-group col-xs-12">
<label htmlFor="batchSize">Batch Size</label>
<input
className="form-control"
id="batchSize"
type="number"
ref={r => (this.batchSize = r)}
defaultValue={batchSize.toString() || '0'}
onChange={this.disableTest}
/>
</div>
<div className="form-group col-xs-12">
<label htmlFor="batchTimeout">Batch Timeout</label>
<input
className="form-control"
id="batchTimeout"
type="text"
ref={r => (this.batchTimeout = r)}
defaultValue={batchTimeout || ''}
onChange={this.disableTest}
/>
</div>
<div className="form-group col-xs-12">
<div className="form-control-static">
<input
id="useSSL"
type="checkbox"
defaultChecked={useSSL}
ref={r => (this.useSSL = r)}
onChange={this.disableTest}
/>
<label htmlFor="useSSL">Use SSL</label>
</div>
</div>
<div className="form-group col-xs-12">
<label htmlFor="sslCA">SSL CA</label>
<input
className="form-control"
id="sslCA"
type="text"
ref={r => (this.sslCA = r)}
defaultValue={sslCA || ''}
onChange={this.disableTest}
/>
</div>
<div className="form-group col-xs-12">
<label htmlFor="sslCert">SSL Cert</label>
<input
className="form-control"
id="sslCert"
type="text"
ref={r => (this.sslCert = r)}
defaultValue={sslCert || ''}
onChange={this.disableTest}
/>
</div>
<div className="form-group col-xs-12">
<label htmlFor="sslKey">SSL Key</label>
<input
className="form-control"
id="sslKey"
type="text"
ref={r => (this.sslKey = r)}
defaultValue={sslKey || ''}
onChange={this.disableTest}
/>
</div>
<div className="form-group col-xs-12">
<div className="form-control-static">
<input
id="insecureSkipVerify"
type="checkbox"
defaultChecked={insecureSkipVerify}
ref={r => (this.insecureSkipVerify = r)}
onChange={this.disableTest}
/>
<label htmlFor="insecureSkipVerify">Insecure Skip Verify</label>
</div>
</div>
<div className="form-group form-group-submit col-xs-12 text-center">
<button
className="btn btn-primary"
type="submit"
disabled={this.state.testEnabled}
>
<span className="icon checkmark" />
Save Changes
</button>
<button
className="btn btn-primary"
disabled={!this.state.testEnabled}
onClick={this.props.onTest}
>
<span className="icon pulse-c" />
Send Test Alert
</button>
</div>
</form>
)
}
private get currentBrokersForTags(): Item[] {
const {currentBrokers} = this.state
return currentBrokers.map(broker => ({name: broker}))
}
private handleSubmit = async e => {
e.preventDefault()
const batchSize = parseInt(this.batchSize.value, 10)
if (isNaN(batchSize)) {
this.props.notify(notifyInvalidBatchSizeValue())
return
}
const properties = {
brokers: this.state.currentBrokers,
timeout: this.timeout.value,
'batch-size': batchSize,
'batch-timeout': this.batchTimeout.value,
'use-ssl': this.useSSL.checked,
'ssl-ca': this.sslCA.value,
'ssl-cert': this.sslCert.value,
'ssl-key': this.sslKey.value,
'insecure-skip-verify': this.insecureSkipVerify.checked,
}
const success = await this.props.onSave(properties)
if (success) {
this.setState({testEnabled: true})
}
}
private disableTest = () => {
this.setState({testEnabled: false})
}
private handleAddBroker = broker => {
this.setState({currentBrokers: this.state.currentBrokers.concat(broker)})
}
private handleDeleteBroker = broker => {
this.setState({
currentBrokers: this.state.currentBrokers.filter(t => t !== broker.name),
testEnabled: false,
})
}
}
export default KafkaConfig

View File

@ -1,5 +1,6 @@
import AlertaConfig from './AlertaConfig'
import HipChatConfig from './HipChatConfig'
import KafkaConfig from './KafkaConfig'
import OpsGenieConfig from './OpsGenieConfig'
import PagerDutyConfig from './PagerDutyConfig'
import PushoverConfig from './PushoverConfig'
@ -13,6 +14,7 @@ import VictorOpsConfig from './VictorOpsConfig'
export {
AlertaConfig,
HipChatConfig,
KafkaConfig,
OpsGenieConfig,
PagerDutyConfig,
PushoverConfig,

View File

@ -0,0 +1,59 @@
import React, {SFC} from 'react'
import HandlerInput from 'src/kapacitor/components/HandlerInput'
interface Handler {
alias: string
enabled: boolean
headerKey: string
headerValue: string
headers: {
[key: string]: string
}
text: string
type: string
url: string
}
interface Props {
selectedHandler: object
handleModifyHandler: (
selectedHandler: Handler,
fieldName: string,
parseToArray: string
) => void
}
const KafkaHandler: SFC<Props> = ({selectedHandler, handleModifyHandler}) => (
<div className="endpoint-tab-contents">
<div className="endpoint-tab--parameters">
<h4>Parameters for this Alert Handler</h4>
<div className="faux-form">
<HandlerInput
selectedHandler={selectedHandler}
handleModifyHandler={handleModifyHandler}
fieldName="cluster"
fieldDisplay="Cluster"
placeholder=""
fieldColumns="col-md-12"
/>
<HandlerInput
selectedHandler={selectedHandler}
handleModifyHandler={handleModifyHandler}
fieldName="topic"
fieldDisplay="Topic"
placeholder=""
/>
<HandlerInput
selectedHandler={selectedHandler}
handleModifyHandler={handleModifyHandler}
fieldName="template"
fieldDisplay="Template"
placeholder=""
/>
</div>
</div>
</div>
)
export default KafkaHandler

View File

@ -4,6 +4,7 @@ import ExecHandler from './ExecHandler'
import LogHandler from './LogHandler'
import AlertaHandler from './AlertaHandler'
import HipchatHandler from './HipchatHandler'
import KafkaHandler from './KafkaHandler'
import OpsgenieHandler from './OpsgenieHandler'
import PagerdutyHandler from './PagerdutyHandler'
import PushoverHandler from './PushoverHandler'
@ -22,6 +23,7 @@ export {
EmailHandler,
AlertaHandler,
HipchatHandler,
KafkaHandler,
OpsgenieHandler,
PagerdutyHandler,
PushoverHandler,

View File

@ -114,6 +114,7 @@ export const MAP_KEYS_FROM_CONFIG = {
export const ALERTS_FROM_CONFIG = {
alerta: ['environment', 'origin', 'token'], // token = bool
hipChat: ['url', 'room', 'token'], // token = bool
kafka: [],
opsGenie: ['api-key', 'teams', 'recipients'], // api-key = bool
opsGenie2: ['api-key', 'teams', 'recipients'], // api-key = bool
pagerDuty: ['service-key'], // service-key = bool
@ -172,6 +173,7 @@ export const HANDLERS_TO_RULE = {
'service',
],
hipChat: ['room'],
kafka: ['cluster', 'topic', 'template'],
opsGenie: ['teams', 'recipients'],
opsGenie2: ['teams', 'recipients'],
pagerDuty: [],

View File

@ -5,7 +5,7 @@ import {bindActionCreators} from 'redux'
import {notify as notifyAction} from 'src/shared/actions/notifications'
import {Source} from 'src/types'
import {Source, Notification, NotificationFunc} from 'src/types'
import {
createKapacitor,
@ -29,16 +29,6 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
export const defaultName = 'My Kapacitor'
export const kapacitorPort = '9092'
export interface Notification {
id?: string
type: string
icon: string
duration: number
message: string
}
export type NotificationFunc = () => Notification
interface Kapacitor {
url: string
name: string

View File

@ -10,7 +10,14 @@ import {getActiveKapacitor} from 'src/shared/apis'
import {getLogStreamByRuleID, pingKapacitorVersion} from 'src/kapacitor/apis'
import {notify as notifyAction} from 'src/shared/actions/notifications'
import {Source, Kapacitor, Task, AlertRule} from 'src/types'
import {
Source,
Kapacitor,
Task,
AlertRule,
Notification,
NotificationFunc,
} from 'src/types'
import {
notifyTickscriptLoggingUnavailable,
@ -61,7 +68,7 @@ interface Props {
router: Router
params: Params
rules: AlertRule[]
notify: any
notify: (message: Notification | NotificationFunc) => void
}
interface State {

View File

@ -173,13 +173,13 @@ export function updateKapacitorConfigSection(kapacitor, section, properties) {
return AJAX(params)
}
export const testAlertOutput = async (kapacitor, outputName) => {
export const testAlertOutput = async (kapacitor, outputName, options) => {
try {
const {
data: {services},
} = await kapacitorProxy(kapacitor, 'GET', '/kapacitor/v1/service-tests')
const service = services.find(s => s.name === outputName)
return kapacitorProxy(kapacitor, 'POST', service.link.href, {})
return kapacitorProxy(kapacitor, 'POST', service.link.href, options)
} catch (error) {
console.error(error)
}

View File

@ -1,4 +1,4 @@
import React, {PureComponent} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
@ -16,9 +16,9 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
interface DatabaseListProps {
query: QueryConfig
querySource: Source
querySource?: Source
onChooseNamespace: (namespace: Namespace) => void
source: Source
source?: Source
}
interface DatabaseListState {
@ -28,7 +28,7 @@ interface DatabaseListState {
const {shape} = PropTypes
@ErrorHandling
class DatabaseList extends PureComponent<DatabaseListProps, DatabaseListState> {
class DatabaseList extends Component<DatabaseListProps, DatabaseListState> {
public static contextTypes = {
source: shape({
links: shape({}).isRequired,
@ -52,7 +52,13 @@ class DatabaseList extends PureComponent<DatabaseListProps, DatabaseListState> {
this.getDbRp()
}
public componentDidUpdate({querySource: prevSource, query: prevQuery}) {
public componentDidUpdate({
querySource: prevSource,
query: prevQuery,
}: {
querySource?: Source
query: QueryConfig
}) {
const {querySource: nextSource, query: nextQuery} = this.props
const differentSource = !_.isEqual(prevSource, nextSource)

View File

@ -1,7 +1,15 @@
import React, {PureComponent} from 'react'
import _ from 'lodash'
import {QueryConfig, GroupBy, Source, TimeShift} from 'src/types'
import {
ApplyFuncsToFieldArgs,
Field,
FieldFunc,
GroupBy,
QueryConfig,
Source,
TimeShift,
} from 'src/types'
import QueryOptions from 'src/shared/components/QueryOptions'
import FieldListItem from 'src/data_explorer/components/FieldListItem'
@ -28,39 +36,21 @@ interface Links {
proxy: string
}
interface Field {
type: string
value: string
}
interface FieldFunc extends Field {
args: FuncArg[]
}
interface FuncArg {
value: string
type: string
}
interface ApplyFuncsToFieldArgs {
field: Field
funcs: FuncArg[]
}
interface Props {
query: QueryConfig
onTimeShift: (shift: TimeShiftOption) => void
onToggleField: (field: Field) => void
addInitialField?: (field: Field, groupBy: GroupBy) => void
applyFuncsToField: (field: ApplyFuncsToFieldArgs, groupBy?: GroupBy) => void
onFill?: (fill: string) => void
onGroupByTime: (groupByOption: string) => void
onFill: (fill: string) => void
applyFuncsToField: (field: ApplyFuncsToFieldArgs, groupBy: GroupBy) => void
onTimeShift?: (shift: TimeShiftOption) => void
onToggleField: (field: Field) => void
removeFuncs: (fields: Field[]) => void
isKapacitorRule: boolean
querySource: {
querySource?: {
links: Links
}
removeFuncs: (fields: Field[]) => void
addInitialField: (field: Field, groupBy: GroupBy) => void
initialGroupByTime: string | null
isQuerySupportedByExplorer: boolean
initialGroupByTime?: string | null
isQuerySupportedByExplorer?: boolean
source: Source
}
@ -124,6 +114,7 @@ class FieldList extends PureComponent<Props, State> {
const hasAggregates = numFunctions(fields) > 0
const noDBorMeas = !database || !measurement
const isDisabled = !isKapacitorRule && !isQuerySupportedByExplorer
return (
<div className="query-builder--column">
@ -138,7 +129,7 @@ class FieldList extends PureComponent<Props, State> {
isKapacitorRule={isKapacitorRule}
onTimeShift={this.handleTimeShift}
onGroupByTime={this.handleGroupByTime}
isDisabled={!isQuerySupportedByExplorer}
isDisabled={isDisabled}
/>
) : null}
</div>
@ -174,7 +165,7 @@ class FieldList extends PureComponent<Props, State> {
fieldFuncs={fieldFuncs}
funcs={functionNames(funcs)}
isKapacitorRule={isKapacitorRule}
isDisabled={!isQuerySupportedByExplorer}
isDisabled={isDisabled}
/>
)
})}
@ -203,7 +194,9 @@ class FieldList extends PureComponent<Props, State> {
isQuerySupportedByExplorer,
} = this.props
const {fields, groupBy} = query
if (!isQuerySupportedByExplorer) {
const isDisabled = !isKapacitorRule && !isQuerySupportedByExplorer
if (isDisabled) {
return
}
const initialGroupBy = {...groupBy, time}

View File

@ -6,7 +6,7 @@ import _ from 'lodash'
import {showMeasurements} from 'src/shared/apis/metaQuery'
import showMeasurementsParser from 'src/shared/parsing/showMeasurements'
import {QueryConfig, Source} from 'src/types'
import {QueryConfig, Source, Tag} from 'src/types'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import MeasurementListFilter from 'src/shared/components/MeasurementListFilter'
@ -15,12 +15,13 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
query: QueryConfig
querySource: Source
onChooseTag: () => void
onGroupByTag: () => void
onToggleTagAcceptance: () => void
isQuerySupportedByExplorer: boolean
querySource?: Source
onChooseMeasurement: (measurement: string) => void
onChooseTag: (tag: Tag) => void
onGroupByTag: (tagKey: string) => void
onToggleTagAcceptance: () => void
isKapacitorRule?: boolean
isQuerySupportedByExplorer?: boolean
}
interface State {
@ -124,6 +125,7 @@ class MeasurementList extends PureComponent<Props, State> {
onChooseTag,
onGroupByTag,
isQuerySupportedByExplorer,
isKapacitorRule,
} = this.props
const {database, areTagsAccepted} = query
const {filtered} = this.state
@ -154,7 +156,9 @@ class MeasurementList extends PureComponent<Props, State> {
areTagsAccepted={areTagsAccepted}
onAcceptReject={this.handleAcceptReject}
isActive={measurement === query.measurement}
isQuerySupportedByExplorer={isQuerySupportedByExplorer}
isQuerySupportedByExplorer={
isKapacitorRule || isQuerySupportedByExplorer
}
numTagsActive={Object.keys(query.tags).length}
onChooseMeasurement={this.handleChoosemeasurement}
/>

View File

@ -4,6 +4,8 @@ import React, {PureComponent, MouseEvent} from 'react'
import TagList from 'src/shared/components/TagList'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {QueryConfig, Tag} from 'src/types'
interface SourceLinks {
proxy: string
}
@ -12,31 +14,15 @@ interface Source {
links: SourceLinks
}
interface GroupBy {
tags?: string[]
}
interface Tags {
[key: string]: string[]
}
interface Query {
database: string
measurement: string
retentionPolicy: string
tags: Tags
groupBy: GroupBy
}
interface Props {
query: Query
query: QueryConfig
querySource: Source
isActive: boolean
measurement: string
numTagsActive: number
areTagsAccepted: boolean
onChooseTag: () => void
onGroupByTag: () => void
onChooseTag: (tag: Tag) => void
onGroupByTag: (tagKey: string) => void
onAcceptReject: () => void
isQuerySupportedByExplorer: boolean
onChooseMeasurement: (measurement: string) => () => void

View File

@ -10,6 +10,8 @@ import showTagKeysParser from 'src/shared/parsing/showTagKeys'
import showTagValuesParser from 'src/shared/parsing/showTagValues'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {QueryConfig, Tag} from 'src/types'
const {shape} = PropTypes
interface SourceLinks {
@ -20,26 +22,11 @@ interface Source {
links: SourceLinks
}
interface GroupBy {
tags?: string[]
}
interface Tags {
[key: string]: string[]
}
interface Query {
database: string
measurement: string
retentionPolicy: string
tags: Tags
groupBy: GroupBy
}
interface Props {
query: Query
query: QueryConfig
querySource: Source
onChooseTag: () => void
onGroupByTag: () => void
onChooseTag: (tag: Tag) => void
onGroupByTag: (tagKey: string) => void
isQuerySupportedByExplorer: boolean
}

View File

@ -529,6 +529,11 @@ export const notifyTestAlertFailed = (endpoint, errorMessage) => ({
}`,
})
export const notifyInvalidBatchSizeValue = () => ({
...defaultErrorNotification,
message: 'Batch Size cannot be empty.',
})
export const notifyKapacitorConnectionFailed = () => ({
...defaultErrorNotification,
message:

View File

@ -26,6 +26,11 @@ export const numFunctions = fields => _.size(getFunctions(fields))
// functionNames returns the value of all top-level functions
export const functionNames = fields => getFunctions(fields).map(f => f.value)
/**
* getAllFields and funcs with fieldName
* @param {Field[]} fields
* @returns {Field[]}
*/
// returns a flattened list of all fieldNames in a queryConfig
export const getFieldsDeep = fields =>
_.uniqBy(

4
ui/src/types/actions.ts Normal file
View File

@ -0,0 +1,4 @@
import * as kapacitorQueryConfigActions from 'src/kapacitor/actions/queryConfigs'
type KapacitorQueryConfigActions = typeof kapacitorQueryConfigActions
export {KapacitorQueryConfigActions}

View File

@ -76,21 +76,3 @@ export interface Template {
tempVar: string
values: TemplateValue[]
}
export type CellEditorOverlayActionsFunc = (id: string, ...args: any[]) => any
export interface CellEditorOverlayActions {
chooseNamespace: (id: string) => void
chooseMeasurement: (id: string) => void
applyFuncsToField: (id: string) => void
chooseTag: (id: string) => void
groupByTag: (id: string) => void
toggleField: (id: string) => void
groupByTime: (id: string) => void
toggleTagAcceptance: (id: string) => void
fill: (id: string) => void
editRawTextAsync: (url: string, id: string, text: string) => Promise<void>
addInitialField: (id: string) => void
removeFuncs: (id: string) => void
timeShift: (id: string) => void
}

View File

@ -6,12 +6,19 @@ import {
Status,
TimeRange,
TimeShift,
ApplyFuncsToFieldArgs,
Field,
FieldFunc,
FuncArg,
Namespace,
Tag,
Tags,
TagValues,
} from './query'
import {AlertRule, Kapacitor, Task} from './kapacitor'
import {Source, SourceLinks} from './sources'
import {DropdownAction, DropdownItem} from './shared'
import {Notification} from 'src/kapacitor/components/AlertOutputs'
import {Notification, NotificationFunc} from './notifications'
export {
Me,
@ -26,8 +33,15 @@ export {
Status,
QueryConfig,
TimeShift,
ApplyFuncsToFieldArgs,
Field,
FieldFunc,
FuncArg,
GroupBy,
Namespace,
Tag,
Tags,
TagValues,
AlertRule,
Kapacitor,
Source,
@ -37,4 +51,5 @@ export {
TimeRange,
Task,
Notification,
NotificationFunc,
}

View File

@ -0,0 +1,9 @@
export interface Notification {
id?: string
type: string
icon: string
duration: number
message: string
}
export type NotificationFunc = () => Notification

View File

@ -20,14 +20,33 @@ export interface Field {
value: string
type: string
alias?: string
args?: Args[]
args?: FieldArg[]
}
export interface Args {
export interface FieldArg {
value: string
type: string
alias?: string
args?: Args[]
args?: FieldArg[]
}
export interface FieldFunc extends Field {
args: FuncArg[]
}
export interface FuncArg {
type: string
value: string
alias?: string
}
export interface ApplyFuncsToFieldArgs {
field: Field
funcs: FuncArg[]
}
export interface Tag {
key: string
value: string
}
export type TagValues = string[]
@ -37,7 +56,7 @@ export interface Tags {
}
export interface GroupBy {
time?: string
time?: string | null
tags?: string[]
}

View File

@ -1,33 +1,44 @@
import _ from 'lodash'
import defaultQueryConfig from 'utils/defaultQueryConfig'
import defaultQueryConfig from 'src/utils/defaultQueryConfig'
import {
hasField,
removeField,
getFieldsDeep,
getFuncsByFieldName,
} from 'shared/reducers/helpers/fields'
} from 'src/shared/reducers/helpers/fields'
export function editRawText(query, rawText) {
return Object.assign({}, query, {rawText})
}
import {
Field,
GroupBy,
Namespace,
QueryConfig,
Tag,
TagValues,
TimeShift,
ApplyFuncsToFieldArgs,
} from 'src/types'
export const chooseNamespace = (query, namespace, isKapacitorRule = false) => ({
export const chooseNamespace = (
query: QueryConfig,
namespace: Namespace,
isKapacitorRule: boolean = false
): QueryConfig => ({
...defaultQueryConfig({id: query.id, isKapacitorRule}),
...namespace,
})
export const chooseMeasurement = (
query,
measurement,
isKapacitorRule = false
) => ({
query: QueryConfig,
measurement: string,
isKapacitorRule: boolean = false
): QueryConfig => ({
...defaultQueryConfig({id: query.id, isKapacitorRule}),
database: query.database,
retentionPolicy: query.retentionPolicy,
measurement,
})
export const toggleKapaField = (query, field) => {
export const toggleKapaField = (query: QueryConfig, field: Field) => {
if (field.type === 'field') {
return {
...query,
@ -45,24 +56,29 @@ export const toggleKapaField = (query, field) => {
}
}
export const buildInitialField = value => [
{
type: 'func',
alias: `mean_${value}`,
args: [{value, type: 'field'}],
value: 'mean',
},
]
export const buildInitialField = (value: string): Field => ({
type: 'func',
alias: `mean_${value}`,
args: [{value, type: 'field'}],
value: 'mean',
})
export const addInitialField = (query, field, groupBy) => {
export const addInitialField = (
query: QueryConfig,
field,
groupBy
): QueryConfig => {
return {
...query,
fields: buildInitialField(field.value),
fields: [buildInitialField(field.value)],
groupBy,
}
}
export const toggleField = (query, {value}) => {
export const toggleField = (
query: QueryConfig,
{value}: Field
): QueryConfig => {
const {fields = [], groupBy} = query
const isSelected = hasField(value, fields)
const newFuncs = fields.filter(f => f.type === 'func')
@ -108,29 +124,35 @@ export const toggleField = (query, {value}) => {
}
}
export const groupByTime = (query, time) => {
return Object.assign({}, query, {
groupBy: Object.assign({}, query.groupBy, {
time,
}),
})
}
export const groupByTime = (query: QueryConfig, time: string): QueryConfig => ({
...query,
groupBy: {...query.groupBy, time},
})
export const fill = (query, value) => ({...query, fill: value})
export const fill = (query: QueryConfig, value: string): QueryConfig => ({
...query,
fill: value,
})
export const toggleTagAcceptance = query => {
return Object.assign({}, query, {
areTagsAccepted: !query.areTagsAccepted,
})
}
export const toggleTagAcceptance = (query: QueryConfig): QueryConfig => ({
...query,
areTagsAccepted: !query.areTagsAccepted,
})
export const removeFuncs = (query, fields) => ({
export const removeFuncs = (
query: QueryConfig,
fields: Field[]
): QueryConfig => ({
...query,
fields: getFieldsDeep(fields),
groupBy: {...query.groupBy, time: null},
})
export const applyFuncsToField = (query, {field, funcs = []}, groupBy) => {
export const applyFuncsToField = (
query: QueryConfig,
{field, funcs = []}: ApplyFuncsToFieldArgs,
groupBy: GroupBy
): QueryConfig => {
const nextFields = query.fields.reduce((acc, f) => {
// If there is a func applied to only one field, add it to the other fields
if (f.type === 'field') {
@ -185,13 +207,12 @@ export const applyFuncsToField = (query, {field, funcs = []}, groupBy) => {
}
}
export const updateRawQuery = (query, rawText) => {
return Object.assign({}, query, {
rawText,
})
}
export const updateRawQuery = (query: QueryConfig, rawText): QueryConfig => ({
...query,
rawText,
})
export const groupByTag = (query, tagKey) => {
export const groupByTag = (query: QueryConfig, tagKey: string): QueryConfig => {
const oldTags = query.groupBy.tags
let newTags
@ -204,27 +225,30 @@ export const groupByTag = (query, tagKey) => {
newTags = oldTags.concat(tagKey)
}
return Object.assign({}, query, {
groupBy: Object.assign({}, query.groupBy, {tags: newTags}),
})
return {
...query,
groupBy: {...query.groupBy, tags: newTags},
}
}
export const chooseTag = (query, tag) => {
export const chooseTag = (query: QueryConfig, tag: Tag): QueryConfig => {
const tagValues = query.tags[tag.key]
const shouldRemoveTag =
tagValues && tagValues.length === 1 && tagValues[0] === tag.value
if (shouldRemoveTag) {
const newTags = Object.assign({}, query.tags)
const newTags = {...query.tags}
delete newTags[tag.key]
return Object.assign({}, query, {tags: newTags})
return {...query, tags: newTags}
}
const updateTagValues = newTagValues => {
return Object.assign({}, query, {
tags: Object.assign({}, query.tags, {
const updateTagValues = (newTagValues: TagValues): QueryConfig => {
return {
...query,
tags: {
...query.tags,
[tag.key]: newTagValues,
}),
})
},
}
}
const oldTagValues = query.tags[tag.key]
@ -243,4 +267,7 @@ export const chooseTag = (query, tag) => {
return updateTagValues(query.tags[tag.key].concat(tag.value))
}
export const timeShift = (query, shift) => ({...query, shifts: [shift]})
export const timeShift = (query: QueryConfig, shift: TimeShift) => ({
...query,
shifts: [shift],
})

View File

@ -3,13 +3,7 @@ import React from 'react'
import MeasurementListItem from 'src/shared/components/MeasurementListItem'
import TagList from 'src/shared/components/TagList'
const defaultQuery = {
database: '',
measurement: 'test',
retentionPolicy: '',
tags: {},
groupBy: {},
}
import {query as defaultQuery} from 'test/resources'
const setup = (overrides = {}) => {
const props = {
@ -20,7 +14,7 @@ const setup = (overrides = {}) => {
},
},
isActive: true,
measurement: 'test',
measurement: defaultQuery.measurement,
numTagsActive: 3,
areTagsAccepted: true,
isQuerySupportedByExplorer: true,
@ -75,7 +69,7 @@ describe('MeasurementListItem', () => {
wrapper.simulate('click')
wrapper.setProps({query: {...defaultQuery, measurement}})
expect(factory).toHaveBeenCalledWith('test')
expect(factory).toHaveBeenCalledWith(defaultQuery.measurement)
expect(trigger).toHaveBeenCalled()
expect(wrapper.find(TagList).exists()).toBe(true)