Merge pull request #5805 from influxdata/5547/templated_task_readOnly

fix(kapacitor): make templated kapacitor task read-only
pull/5812/head
Pavel Závora 2021-09-04 05:46:15 +02:00 committed by GitHub
commit b0458a3a95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 163 additions and 53 deletions

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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"],

View File

@ -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))
}
})
})
}

View File

@ -70,11 +70,13 @@ class Tickscript extends PureComponent<Props> {
/>
<TickscriptEditor
script={task.tickscript}
readOnly={!!task.templateID}
onChangeScript={onChangeScript}
/>
<TickscriptEditorConsole
consoleMessage={consoleMessage}
unsavedChanges={unsavedChanges}
task={task}
/>
</div>
{this.logsTable}

View File

@ -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<Props> {
}
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',
}

View File

@ -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<Props> = ({
consoleMessage,
unsavedChanges,
task,
}) => {
let consoleOutput = 'TICKscript is valid'
let consoleClass = 'tickscript-console--valid'
@ -22,7 +25,24 @@ const TickscriptEditorConsole: FunctionComponent<Props> = ({
return (
<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>
)
}

View File

@ -27,14 +27,16 @@ class TickscriptEditorControls extends Component<Props> {
return (
<div className="tickscript-controls">
{this.tickscriptID}
<div className="tickscript-controls--right">
<TickscriptType type={task.type} onChangeType={onChangeType} />
<MultiSelectDBDropdown
selectedItems={this.addName(task.dbrps)}
onApply={onSelectDbrps}
rightAligned={true}
/>
</div>
{!task.name || task.templateID ? undefined : (
<div className="tickscript-controls--right">
<TickscriptType type={task.type} onChangeType={onChangeType} />
<MultiSelectDBDropdown
selectedItems={this.addName(task.dbrps)}
onApply={onSelectDbrps}
rightAligned={true}
/>
</div>
)}
</div>
)
}

View File

@ -39,12 +39,14 @@ class TickscriptHeader extends Component<Props> {
areLogsVisible={areLogsVisible}
onToggleLogsVisibility={onToggleLogsVisibility}
/>
<TickscriptSave
task={task}
onSave={onSave}
unsavedChanges={unsavedChanges}
isNewTickscript={isNewTickscript}
/>
{task.templateID ? undefined : (
<TickscriptSave
task={task}
onSave={onSave}
unsavedChanges={unsavedChanges}
isNewTickscript={isNewTickscript}
/>
)}
{this.saveButton}
</Page.Header.Right>
</Page.Header>

View File

@ -5,6 +5,7 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
export interface Task {
dbrps: DBRP[]
id: string
templateID?: string
}
interface SaveProps {

View File

@ -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<Props, State> {
const rule = this.rule
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) {
return <PageSpinner />
}

View File

@ -140,11 +140,29 @@ export class TickscriptPage extends PureComponent<Props, State> {
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)

View File

@ -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;

View File

@ -55,6 +55,8 @@ export interface AlertRule {
modified: string
queryID?: string
'last-enabled'?: string
'template-id'?: string
vars?: Record<string, {type: string; value: unknown}>
}
export interface Task {
@ -64,6 +66,8 @@ export interface Task {
tickscript: string
dbrps: DBRP[]
type: string
templateID?: string
vars?: Record<string, {type: string; value: unknown}>
}
export type TaskStatusType = 'active' | 'inactive'