Merge pull request #5805 from influxdata/5547/templated_task_readOnly
fix(kapacitor): make templated kapacitor task read-onlypull/5812/head
commit
b0458a3a95
|
@ -9,6 +9,7 @@
|
||||||
1. [#5796](https://github.com/influxdata/chronograf/pull/5796): Avoid useless browser history change.
|
1. [#5796](https://github.com/influxdata/chronograf/pull/5796): Avoid useless browser history change.
|
||||||
1. [#5803](https://github.com/influxdata/chronograf/pull/5803): Repair time rendering in horizontal table.
|
1. [#5803](https://github.com/influxdata/chronograf/pull/5803): Repair time rendering in horizontal table.
|
||||||
1. [#5804](https://github.com/influxdata/chronograf/pull/5804): Name tickscript after a `name` task variable, when defined.
|
1. [#5804](https://github.com/influxdata/chronograf/pull/5804): Name tickscript after a `name` task variable, when defined.
|
||||||
|
1. [#5805](https://github.com/influxdata/chronograf/pull/5805): Make templated tasks read-only.
|
||||||
1. [#5806](https://github.com/influxdata/chronograf/pull/5806): Repair paginated retrival of flux tasks.
|
1. [#5806](https://github.com/influxdata/chronograf/pull/5806): Repair paginated retrival of flux tasks.
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
|
@ -8,6 +8,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/influxdata/kapacitor/client/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// General errors.
|
// General errors.
|
||||||
|
@ -274,24 +276,26 @@ type DBRP struct {
|
||||||
|
|
||||||
// AlertRule represents rules for building a tickscript alerting task
|
// AlertRule represents rules for building a tickscript alerting task
|
||||||
type AlertRule struct {
|
type AlertRule struct {
|
||||||
ID string `json:"id,omitempty"` // ID is the unique ID of the alert
|
ID string `json:"id,omitempty"` // ID is the unique ID of the alert
|
||||||
TICKScript TICKScript `json:"tickscript"` // TICKScript is the raw tickscript associated with this Alert
|
TICKScript TICKScript `json:"tickscript"` // TICKScript is the raw tickscript associated with this Alert
|
||||||
Query *QueryConfig `json:"query"` // Query is the filter of data for the alert.
|
Query *QueryConfig `json:"query"` // Query is the filter of data for the alert.
|
||||||
Every string `json:"every"` // Every how often to check for the alerting criteria
|
Every string `json:"every"` // Every how often to check for the alerting criteria
|
||||||
AlertNodes AlertNodes `json:"alertNodes"` // AlertNodes defines the destinations for the alert
|
AlertNodes AlertNodes `json:"alertNodes"` // AlertNodes defines the destinations for the alert
|
||||||
Message string `json:"message"` // Message included with alert
|
Message string `json:"message"` // Message included with alert
|
||||||
Details string `json:"details"` // Details is generally used for the Email alert. If empty will not be added.
|
Details string `json:"details"` // Details is generally used for the Email alert. If empty will not be added.
|
||||||
Trigger string `json:"trigger"` // Trigger is a type that defines when to trigger the alert
|
Trigger string `json:"trigger"` // Trigger is a type that defines when to trigger the alert
|
||||||
TriggerValues TriggerValues `json:"values"` // Defines the values that cause the alert to trigger
|
TriggerValues TriggerValues `json:"values"` // Defines the values that cause the alert to trigger
|
||||||
Name string `json:"name"` // Name is the user-defined name for the alert
|
Name string `json:"name"` // Name is the user-defined name for the alert
|
||||||
Type string `json:"type"` // Represents the task type where stream is data streamed to kapacitor and batch is queried by kapacitor
|
Type string `json:"type"` // Represents the task type where stream is data streamed to kapacitor and batch is queried by kapacitor
|
||||||
DBRPs []DBRP `json:"dbrps"` // List of database retention policy pairs the task is allowed to access
|
DBRPs []DBRP `json:"dbrps"` // List of database retention policy pairs the task is allowed to access
|
||||||
Status string `json:"status"` // Represents if this rule is enabled or disabled in kapacitor
|
Status string `json:"status"` // Represents if this rule is enabled or disabled in kapacitor
|
||||||
Executing bool `json:"executing"` // Whether the task is currently executing
|
Executing bool `json:"executing"` // Whether the task is currently executing
|
||||||
Error string `json:"error"` // Any error encountered when kapacitor executes the task
|
Error string `json:"error"` // Any error encountered when kapacitor executes the task
|
||||||
Created time.Time `json:"created"` // Date the task was first created
|
Created time.Time `json:"created"` // Date the task was first created
|
||||||
Modified time.Time `json:"modified"` // Date the task was last modified
|
Modified time.Time `json:"modified"` // Date the task was last modified
|
||||||
LastEnabled time.Time `json:"last-enabled,omitempty"` // Date the task was last set to status enabled
|
LastEnabled time.Time `json:"last-enabled,omitempty"` // Date the task was last set to status enabled
|
||||||
|
Vars map[string]client.Var `json:"vars,omitempty"` // task variables when defined
|
||||||
|
TemplateID string `json:"template-id,omitempty"` // task template id when created from template
|
||||||
}
|
}
|
||||||
|
|
||||||
// TICKScript task to be used by kapacitor
|
// TICKScript task to be used by kapacitor
|
||||||
|
|
|
@ -80,18 +80,22 @@ func NewTask(task *client.Task) *Task {
|
||||||
}
|
}
|
||||||
|
|
||||||
script := chronograf.TICKScript(task.TICKscript)
|
script := chronograf.TICKScript(task.TICKscript)
|
||||||
rule, err := Reverse(script)
|
var rule chronograf.AlertRule = chronograf.AlertRule{Query: nil}
|
||||||
if err != nil {
|
if task.TemplateID == "" {
|
||||||
// try to parse Name from a line such as: `var name = 'Rule Name'
|
// try to parse chronograf rule, tasks created from template cannot be chronograf rules
|
||||||
name := task.ID
|
if parsedRule, err := Reverse(script); err == nil {
|
||||||
if matches := reTaskName.FindStringSubmatch(task.TICKscript); matches != nil {
|
rule = parsedRule
|
||||||
name = matches[1]
|
|
||||||
}
|
|
||||||
rule = chronograf.AlertRule{
|
|
||||||
Name: name,
|
|
||||||
Query: nil,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if rule.Name == "" {
|
||||||
|
// try to parse Name from a line such as: `var name = 'Rule Name'
|
||||||
|
if matches := reTaskName.FindStringSubmatch(task.TICKscript); matches != nil {
|
||||||
|
rule.Name = matches[1]
|
||||||
|
} else {
|
||||||
|
rule.Name = task.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// #5403 override name when defined in a variable
|
// #5403 override name when defined in a variable
|
||||||
if nameVar, exists := task.Vars["name"]; exists {
|
if nameVar, exists := task.Vars["name"]; exists {
|
||||||
if val, isString := nameVar.Value.(string); isString && val != "" {
|
if val, isString := nameVar.Value.(string); isString && val != "" {
|
||||||
|
@ -99,6 +103,8 @@ func NewTask(task *client.Task) *Task {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rule.Vars = task.Vars
|
||||||
|
rule.TemplateID = task.TemplateID
|
||||||
rule.ID = task.ID
|
rule.ID = task.ID
|
||||||
rule.TICKScript = script
|
rule.TICKScript = script
|
||||||
rule.Type = task.Type.String()
|
rule.Type = task.Type.String()
|
||||||
|
@ -320,6 +326,7 @@ func (c *Client) Get(ctx context.Context, id string) (*Task, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, chronograf.ErrAlertNotFound
|
return nil, chronograf.ErrAlertNotFound
|
||||||
}
|
}
|
||||||
|
fmt.Println("!!!", task.ID, task.TemplateID)
|
||||||
|
|
||||||
return NewTask(&task), nil
|
return NewTask(&task), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4631,6 +4631,30 @@
|
||||||
"description": "Date the task was last set to status enabled",
|
"description": "Date the task was last set to status enabled",
|
||||||
"readOnly": true
|
"readOnly": true
|
||||||
},
|
},
|
||||||
|
"template-id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Template ID when created from template",
|
||||||
|
"readOnly": true
|
||||||
|
},
|
||||||
|
"vars": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "task external variables",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Must be either a string, bool, numeric or a list of Var objects."
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"links": {
|
"links": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["self", "kapacitor"],
|
"required": ["self", "kapacitor"],
|
||||||
|
|
|
@ -38,13 +38,18 @@ export function fetchRule(source, ruleID) {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
getActiveKapacitor(source).then(kapacitor => {
|
getActiveKapacitor(source).then(kapacitor => {
|
||||||
getRuleAJAX(kapacitor, ruleID).then(({data: rule}) => {
|
getRuleAJAX(kapacitor, ruleID).then(({data: rule}) => {
|
||||||
|
if (rule.query) {
|
||||||
|
rule = Object.assign(rule, {queryID: rule.query.id})
|
||||||
|
}
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'LOAD_RULE',
|
type: 'LOAD_RULE',
|
||||||
payload: {
|
payload: {
|
||||||
rule: Object.assign(rule, {queryID: rule.query.id}),
|
rule,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
dispatch(loadQuery(rule.query))
|
if (rule.query) {
|
||||||
|
dispatch(loadQuery(rule.query))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,11 +70,13 @@ class Tickscript extends PureComponent<Props> {
|
||||||
/>
|
/>
|
||||||
<TickscriptEditor
|
<TickscriptEditor
|
||||||
script={task.tickscript}
|
script={task.tickscript}
|
||||||
|
readOnly={!!task.templateID}
|
||||||
onChangeScript={onChangeScript}
|
onChangeScript={onChangeScript}
|
||||||
/>
|
/>
|
||||||
<TickscriptEditorConsole
|
<TickscriptEditorConsole
|
||||||
consoleMessage={consoleMessage}
|
consoleMessage={consoleMessage}
|
||||||
unsavedChanges={unsavedChanges}
|
unsavedChanges={unsavedChanges}
|
||||||
|
task={task}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{this.logsTable}
|
{this.logsTable}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
interface Props {
|
interface Props {
|
||||||
onChangeScript: (tickscript: string) => void
|
onChangeScript: (tickscript: string) => void
|
||||||
script: string
|
script: string
|
||||||
|
readOnly?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const NOOP = () => {}
|
const NOOP = () => {}
|
||||||
|
@ -19,13 +20,13 @@ class TickscriptEditor extends Component<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {script} = this.props
|
const {script, readOnly} = this.props
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
theme: 'tickscript',
|
theme: 'tickscript',
|
||||||
tabIndex: 1,
|
tabIndex: 1,
|
||||||
readonly: false,
|
readOnly: !!readOnly,
|
||||||
mode: 'tickscript',
|
mode: 'tickscript',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import React, {FunctionComponent} from 'react'
|
import React, {FunctionComponent} from 'react'
|
||||||
|
import {Task} from 'src/types'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
consoleMessage: string
|
consoleMessage: string
|
||||||
unsavedChanges: boolean
|
unsavedChanges: boolean
|
||||||
|
task: Task
|
||||||
}
|
}
|
||||||
|
|
||||||
const TickscriptEditorConsole: FunctionComponent<Props> = ({
|
const TickscriptEditorConsole: FunctionComponent<Props> = ({
|
||||||
consoleMessage,
|
consoleMessage,
|
||||||
unsavedChanges,
|
unsavedChanges,
|
||||||
|
task,
|
||||||
}) => {
|
}) => {
|
||||||
let consoleOutput = 'TICKscript is valid'
|
let consoleOutput = 'TICKscript is valid'
|
||||||
let consoleClass = 'tickscript-console--valid'
|
let consoleClass = 'tickscript-console--valid'
|
||||||
|
@ -22,7 +25,24 @@ const TickscriptEditorConsole: FunctionComponent<Props> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tickscript-console">
|
<div className="tickscript-console">
|
||||||
<p className={consoleClass}>{consoleOutput}</p>
|
{task.templateID ? (
|
||||||
|
<p className={consoleClass}>
|
||||||
|
TickScript is READ-ONLY, it was created from template{' '}
|
||||||
|
{task.templateID}.
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p className={consoleClass}>{consoleOutput}</p>
|
||||||
|
)}
|
||||||
|
{task.vars && Object.keys(task.vars).length ? (
|
||||||
|
<p>
|
||||||
|
Variables:{' '}
|
||||||
|
{Object.keys(task.vars).map(name => (
|
||||||
|
<span key={name}>
|
||||||
|
{name}:{task.vars[name].type}={String(task.vars[name].value)}{' '}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</p>
|
||||||
|
) : undefined}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,14 +27,16 @@ class TickscriptEditorControls extends Component<Props> {
|
||||||
return (
|
return (
|
||||||
<div className="tickscript-controls">
|
<div className="tickscript-controls">
|
||||||
{this.tickscriptID}
|
{this.tickscriptID}
|
||||||
<div className="tickscript-controls--right">
|
{!task.name || task.templateID ? undefined : (
|
||||||
<TickscriptType type={task.type} onChangeType={onChangeType} />
|
<div className="tickscript-controls--right">
|
||||||
<MultiSelectDBDropdown
|
<TickscriptType type={task.type} onChangeType={onChangeType} />
|
||||||
selectedItems={this.addName(task.dbrps)}
|
<MultiSelectDBDropdown
|
||||||
onApply={onSelectDbrps}
|
selectedItems={this.addName(task.dbrps)}
|
||||||
rightAligned={true}
|
onApply={onSelectDbrps}
|
||||||
/>
|
rightAligned={true}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,12 +39,14 @@ class TickscriptHeader extends Component<Props> {
|
||||||
areLogsVisible={areLogsVisible}
|
areLogsVisible={areLogsVisible}
|
||||||
onToggleLogsVisibility={onToggleLogsVisibility}
|
onToggleLogsVisibility={onToggleLogsVisibility}
|
||||||
/>
|
/>
|
||||||
<TickscriptSave
|
{task.templateID ? undefined : (
|
||||||
task={task}
|
<TickscriptSave
|
||||||
onSave={onSave}
|
task={task}
|
||||||
unsavedChanges={unsavedChanges}
|
onSave={onSave}
|
||||||
isNewTickscript={isNewTickscript}
|
unsavedChanges={unsavedChanges}
|
||||||
/>
|
isNewTickscript={isNewTickscript}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{this.saveButton}
|
{this.saveButton}
|
||||||
</Page.Header.Right>
|
</Page.Header.Right>
|
||||||
</Page.Header>
|
</Page.Header>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
export interface Task {
|
export interface Task {
|
||||||
dbrps: DBRP[]
|
dbrps: DBRP[]
|
||||||
id: string
|
id: string
|
||||||
|
templateID?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SaveProps {
|
interface SaveProps {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, {Component} from 'react'
|
import React, {Component} from 'react'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import {InjectedRouter} from 'react-router'
|
import {InjectedRouter, Link} from 'react-router'
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
|
|
||||||
import PageSpinner from 'src/shared/components/PageSpinner'
|
import PageSpinner from 'src/shared/components/PageSpinner'
|
||||||
|
@ -36,6 +36,7 @@ import {
|
||||||
KapacitorQueryConfigActions,
|
KapacitorQueryConfigActions,
|
||||||
KapacitorRuleActions,
|
KapacitorRuleActions,
|
||||||
} from 'src/types/actions'
|
} from 'src/types/actions'
|
||||||
|
import {Page} from 'src/reusable_ui'
|
||||||
|
|
||||||
interface Params {
|
interface Params {
|
||||||
ruleID: string
|
ruleID: string
|
||||||
|
@ -111,6 +112,24 @@ class KapacitorRulePage extends Component<Props, State> {
|
||||||
const rule = this.rule
|
const rule = this.rule
|
||||||
const query = rule && queryConfigs[rule.queryID]
|
const query = rule && queryConfigs[rule.queryID]
|
||||||
|
|
||||||
|
if (rule && rule['template-id'] && kapacitor) {
|
||||||
|
return (
|
||||||
|
<Page>
|
||||||
|
<Page.Contents>
|
||||||
|
<div>
|
||||||
|
This rule was created from a template. It cannot be edited in
|
||||||
|
chronograf, see its{' '}
|
||||||
|
<Link
|
||||||
|
to={`sources/${source.id}/kapacitors/${kapacitor.id}/tickscripts/${rule.id}`}
|
||||||
|
>
|
||||||
|
TICKScript
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</div>
|
||||||
|
</Page.Contents>
|
||||||
|
</Page>
|
||||||
|
)
|
||||||
|
}
|
||||||
if (!query) {
|
if (!query) {
|
||||||
return <PageSpinner />
|
return <PageSpinner />
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,11 +140,29 @@ export class TickscriptPage extends PureComponent<Props, State> {
|
||||||
|
|
||||||
if (this.isEditing) {
|
if (this.isEditing) {
|
||||||
await kapacitorActions.getRule(kapacitor, ruleID)
|
await kapacitorActions.getRule(kapacitor, ruleID)
|
||||||
const {id, name, tickscript, status, dbrps, type} = this.props.rules.find(
|
const {
|
||||||
r => r.id === ruleID
|
id,
|
||||||
)
|
name,
|
||||||
|
tickscript,
|
||||||
|
status,
|
||||||
|
dbrps,
|
||||||
|
type,
|
||||||
|
'template-id': templateID,
|
||||||
|
vars,
|
||||||
|
} = this.props.rules.find(r => r.id === ruleID)
|
||||||
|
|
||||||
this.setState({task: {tickscript, dbrps, type, status, name, id}})
|
this.setState({
|
||||||
|
task: {
|
||||||
|
tickscript,
|
||||||
|
dbrps,
|
||||||
|
type,
|
||||||
|
status,
|
||||||
|
name,
|
||||||
|
id,
|
||||||
|
templateID,
|
||||||
|
vars,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchChunkedLogs(kapacitor, ruleID)
|
this.fetchChunkedLogs(kapacitor, ruleID)
|
||||||
|
|
|
@ -44,7 +44,7 @@ $tickscript-controls-height: 60px;
|
||||||
> * {margin-left: 8px;}
|
> * {margin-left: 8px;}
|
||||||
}
|
}
|
||||||
.tickscript-console {
|
.tickscript-console {
|
||||||
align-items: flex-start;
|
display: block;
|
||||||
height: $tickscript-controls-height * 2.25;
|
height: $tickscript-controls-height * 2.25;
|
||||||
border-top: 2px solid $g3-castle;
|
border-top: 2px solid $g3-castle;
|
||||||
background-color: $g0-obsidian;
|
background-color: $g0-obsidian;
|
||||||
|
|
|
@ -55,6 +55,8 @@ export interface AlertRule {
|
||||||
modified: string
|
modified: string
|
||||||
queryID?: string
|
queryID?: string
|
||||||
'last-enabled'?: string
|
'last-enabled'?: string
|
||||||
|
'template-id'?: string
|
||||||
|
vars?: Record<string, {type: string; value: unknown}>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Task {
|
export interface Task {
|
||||||
|
@ -64,6 +66,8 @@ export interface Task {
|
||||||
tickscript: string
|
tickscript: string
|
||||||
dbrps: DBRP[]
|
dbrps: DBRP[]
|
||||||
type: string
|
type: string
|
||||||
|
templateID?: string
|
||||||
|
vars?: Record<string, {type: string; value: unknown}>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TaskStatusType = 'active' | 'inactive'
|
export type TaskStatusType = 'active' | 'inactive'
|
||||||
|
|
Loading…
Reference in New Issue