diff --git a/CHANGELOG.md b/CHANGELOG.md index 940cba152..8c37461a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ 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. [#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. ### Features diff --git a/chronograf.go b/chronograf.go index 685662bbd..28b5e6942 100644 --- a/chronograf.go +++ b/chronograf.go @@ -8,6 +8,8 @@ import ( "net/http" "strconv" "time" + + "github.com/influxdata/kapacitor/client/v1" ) // General errors. @@ -274,24 +276,26 @@ type DBRP struct { // AlertRule represents rules for building a tickscript alerting task type AlertRule struct { - 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 - 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 - AlertNodes AlertNodes `json:"alertNodes"` // AlertNodes defines the destinations for the 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. - 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 - 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 - 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 - Executing bool `json:"executing"` // Whether the task is currently executing - Error string `json:"error"` // Any error encountered when kapacitor executes the task - Created time.Time `json:"created"` // Date the task was first created - 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 + 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 + 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 + AlertNodes AlertNodes `json:"alertNodes"` // AlertNodes defines the destinations for the 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. + 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 + 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 + 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 + Executing bool `json:"executing"` // Whether the task is currently executing + Error string `json:"error"` // Any error encountered when kapacitor executes the task + Created time.Time `json:"created"` // Date the task was first created + 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 + 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 diff --git a/kapacitor/client.go b/kapacitor/client.go index 9dfa90272..167486127 100644 --- a/kapacitor/client.go +++ b/kapacitor/client.go @@ -80,18 +80,22 @@ func NewTask(task *client.Task) *Task { } script := chronograf.TICKScript(task.TICKscript) - rule, err := Reverse(script) - if err != nil { - // try to parse Name from a line such as: `var name = 'Rule Name' - name := task.ID - if matches := reTaskName.FindStringSubmatch(task.TICKscript); matches != nil { - name = matches[1] - } - rule = chronograf.AlertRule{ - Name: name, - Query: nil, + var rule chronograf.AlertRule = chronograf.AlertRule{Query: nil} + if task.TemplateID == "" { + // try to parse chronograf rule, tasks created from template cannot be chronograf rules + if parsedRule, err := Reverse(script); err == nil { + rule = parsedRule } } + 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 if nameVar, exists := task.Vars["name"]; exists { 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.TICKScript = script rule.Type = task.Type.String() @@ -320,6 +326,7 @@ func (c *Client) Get(ctx context.Context, id string) (*Task, error) { if err != nil { return nil, chronograf.ErrAlertNotFound } + fmt.Println("!!!", task.ID, task.TemplateID) return NewTask(&task), nil } diff --git a/server/swagger.json b/server/swagger.json index 2f6086aaa..e4cbf8827 100644 --- a/server/swagger.json +++ b/server/swagger.json @@ -4631,6 +4631,30 @@ "description": "Date the task was last set to status enabled", "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": { "type": "object", "required": ["self", "kapacitor"], diff --git a/ui/src/kapacitor/actions/view/index.js b/ui/src/kapacitor/actions/view/index.js index be512bc1b..95cad18c0 100644 --- a/ui/src/kapacitor/actions/view/index.js +++ b/ui/src/kapacitor/actions/view/index.js @@ -38,13 +38,18 @@ export function fetchRule(source, ruleID) { return dispatch => { getActiveKapacitor(source).then(kapacitor => { getRuleAJAX(kapacitor, ruleID).then(({data: rule}) => { + if (rule.query) { + rule = Object.assign(rule, {queryID: rule.query.id}) + } dispatch({ type: 'LOAD_RULE', payload: { - rule: Object.assign(rule, {queryID: rule.query.id}), + rule, }, }) - dispatch(loadQuery(rule.query)) + if (rule.query) { + dispatch(loadQuery(rule.query)) + } }) }) } diff --git a/ui/src/kapacitor/components/Tickscript.tsx b/ui/src/kapacitor/components/Tickscript.tsx index 5e347d8b5..c48fffd17 100644 --- a/ui/src/kapacitor/components/Tickscript.tsx +++ b/ui/src/kapacitor/components/Tickscript.tsx @@ -70,11 +70,13 @@ class Tickscript extends PureComponent { /> {this.logsTable} diff --git a/ui/src/kapacitor/components/TickscriptEditor.tsx b/ui/src/kapacitor/components/TickscriptEditor.tsx index 4a5b1a14d..7568fbc0c 100644 --- a/ui/src/kapacitor/components/TickscriptEditor.tsx +++ b/ui/src/kapacitor/components/TickscriptEditor.tsx @@ -8,6 +8,7 @@ import {ErrorHandling} from 'src/shared/decorators/errors' interface Props { onChangeScript: (tickscript: string) => void script: string + readOnly?: boolean } const NOOP = () => {} @@ -19,13 +20,13 @@ class TickscriptEditor extends Component { } public render() { - const {script} = this.props + const {script, readOnly} = this.props const options = { lineNumbers: true, theme: 'tickscript', tabIndex: 1, - readonly: false, + readOnly: !!readOnly, mode: 'tickscript', } diff --git a/ui/src/kapacitor/components/TickscriptEditorConsole.tsx b/ui/src/kapacitor/components/TickscriptEditorConsole.tsx index ccf8d44c6..417d3a4df 100644 --- a/ui/src/kapacitor/components/TickscriptEditorConsole.tsx +++ b/ui/src/kapacitor/components/TickscriptEditorConsole.tsx @@ -1,13 +1,16 @@ import React, {FunctionComponent} from 'react' +import {Task} from 'src/types' interface Props { consoleMessage: string unsavedChanges: boolean + task: Task } const TickscriptEditorConsole: FunctionComponent = ({ consoleMessage, unsavedChanges, + task, }) => { let consoleOutput = 'TICKscript is valid' let consoleClass = 'tickscript-console--valid' @@ -22,7 +25,24 @@ const TickscriptEditorConsole: FunctionComponent = ({ return (
-

{consoleOutput}

+ {task.templateID ? ( +

+ TickScript is READ-ONLY, it was created from template{' '} + {task.templateID}. +

+ ) : ( +

{consoleOutput}

+ )} + {task.vars && Object.keys(task.vars).length ? ( +

+ Variables:{' '} + {Object.keys(task.vars).map(name => ( + + {name}:{task.vars[name].type}={String(task.vars[name].value)}{' '} + + ))} +

+ ) : undefined}
) } diff --git a/ui/src/kapacitor/components/TickscriptEditorControls.tsx b/ui/src/kapacitor/components/TickscriptEditorControls.tsx index 103de3433..cc2673c99 100644 --- a/ui/src/kapacitor/components/TickscriptEditorControls.tsx +++ b/ui/src/kapacitor/components/TickscriptEditorControls.tsx @@ -27,14 +27,16 @@ class TickscriptEditorControls extends Component { return (
{this.tickscriptID} -
- - -
+ {!task.name || task.templateID ? undefined : ( +
+ + +
+ )}
) } diff --git a/ui/src/kapacitor/components/TickscriptHeader.tsx b/ui/src/kapacitor/components/TickscriptHeader.tsx index 348653f01..e67ab0198 100644 --- a/ui/src/kapacitor/components/TickscriptHeader.tsx +++ b/ui/src/kapacitor/components/TickscriptHeader.tsx @@ -39,12 +39,14 @@ class TickscriptHeader extends Component { areLogsVisible={areLogsVisible} onToggleLogsVisibility={onToggleLogsVisibility} /> - + {task.templateID ? undefined : ( + + )} {this.saveButton} diff --git a/ui/src/kapacitor/components/TickscriptSave.tsx b/ui/src/kapacitor/components/TickscriptSave.tsx index 5750cd7e2..72ae9125f 100644 --- a/ui/src/kapacitor/components/TickscriptSave.tsx +++ b/ui/src/kapacitor/components/TickscriptSave.tsx @@ -5,6 +5,7 @@ import {ErrorHandling} from 'src/shared/decorators/errors' export interface Task { dbrps: DBRP[] id: string + templateID?: string } interface SaveProps { diff --git a/ui/src/kapacitor/containers/KapacitorRulePage.tsx b/ui/src/kapacitor/containers/KapacitorRulePage.tsx index 46f4baae8..c3b22c2a1 100644 --- a/ui/src/kapacitor/containers/KapacitorRulePage.tsx +++ b/ui/src/kapacitor/containers/KapacitorRulePage.tsx @@ -1,6 +1,6 @@ import React, {Component} from 'react' import _ from 'lodash' -import {InjectedRouter} from 'react-router' +import {InjectedRouter, Link} from 'react-router' import {connect} from 'react-redux' import PageSpinner from 'src/shared/components/PageSpinner' @@ -36,6 +36,7 @@ import { KapacitorQueryConfigActions, KapacitorRuleActions, } from 'src/types/actions' +import {Page} from 'src/reusable_ui' interface Params { ruleID: string @@ -111,6 +112,24 @@ class KapacitorRulePage extends Component { const rule = this.rule const query = rule && queryConfigs[rule.queryID] + if (rule && rule['template-id'] && kapacitor) { + return ( + + +
+ This rule was created from a template. It cannot be edited in + chronograf, see its{' '} + + TICKScript + + . +
+
+
+ ) + } if (!query) { return } diff --git a/ui/src/kapacitor/containers/TickscriptPage.tsx b/ui/src/kapacitor/containers/TickscriptPage.tsx index 87071ada6..92eed7901 100644 --- a/ui/src/kapacitor/containers/TickscriptPage.tsx +++ b/ui/src/kapacitor/containers/TickscriptPage.tsx @@ -140,11 +140,29 @@ export class TickscriptPage extends PureComponent { if (this.isEditing) { await kapacitorActions.getRule(kapacitor, ruleID) - const {id, name, tickscript, status, dbrps, type} = this.props.rules.find( - r => r.id === ruleID - ) + const { + 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) diff --git a/ui/src/style/pages/tickscript-editor.scss b/ui/src/style/pages/tickscript-editor.scss index c5e5576ae..da2134447 100644 --- a/ui/src/style/pages/tickscript-editor.scss +++ b/ui/src/style/pages/tickscript-editor.scss @@ -44,7 +44,7 @@ $tickscript-controls-height: 60px; > * {margin-left: 8px;} } .tickscript-console { - align-items: flex-start; + display: block; height: $tickscript-controls-height * 2.25; border-top: 2px solid $g3-castle; background-color: $g0-obsidian; diff --git a/ui/src/types/kapacitor.ts b/ui/src/types/kapacitor.ts index 15edf0f58..76c449b0d 100644 --- a/ui/src/types/kapacitor.ts +++ b/ui/src/types/kapacitor.ts @@ -55,6 +55,8 @@ export interface AlertRule { modified: string queryID?: string 'last-enabled'?: string + 'template-id'?: string + vars?: Record } export interface Task { @@ -64,6 +66,8 @@ export interface Task { tickscript: string dbrps: DBRP[] type: string + templateID?: string + vars?: Record } export type TaskStatusType = 'active' | 'inactive'