Merge pull request #5836 from influxdata/feat/178/tickscript_rename
feat: allow to rename TICKscriptpull/5839/head
commit
9b15a496c1
|
@ -3,6 +3,7 @@
|
|||
### Features
|
||||
|
||||
1. [#5831](https://github.com/influxdata/chronograf/pull/5831): Add download button on query management page.
|
||||
1. [#5836](https://github.com/influxdata/chronograf/pull/5836): Allow to rename TICKscript.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/influxdata/chronograf"
|
||||
"github.com/influxdata/chronograf/id"
|
||||
|
@ -69,7 +70,7 @@ type Task struct {
|
|||
TICKScript chronograf.TICKScript // TICKScript is the running script
|
||||
}
|
||||
|
||||
var reTaskName = regexp.MustCompile(`[\r\n]*var[ \t]+name[ \t]+=[ \t]+'([^']+)'`)
|
||||
var reTaskName = regexp.MustCompile(`[\r\n]*var[ \t]+name[ \t]+=[ \t]+'([^\n]+)'[ \r\t]*\n`)
|
||||
|
||||
// NewTask creates a task from a kapacitor client task
|
||||
func NewTask(task *client.Task) *Task {
|
||||
|
@ -90,7 +91,7 @@ func NewTask(task *client.Task) *Task {
|
|||
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]
|
||||
rule.Name = strings.ReplaceAll(strings.ReplaceAll(matches[1], "\\'", "'"), "\\\\", "\\")
|
||||
} else {
|
||||
rule.Name = task.ID
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ interface Props {
|
|||
consoleMessage: string
|
||||
onChangeType: (type: string) => void
|
||||
onChangeID: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
onChangeName: (name: string) => void
|
||||
isNewTickscript: boolean
|
||||
unsavedChanges: boolean
|
||||
}
|
||||
|
@ -41,6 +42,7 @@ class Tickscript extends PureComponent<Props> {
|
|||
onChangeScript,
|
||||
onChangeType,
|
||||
onChangeID,
|
||||
onChangeName,
|
||||
unsavedChanges,
|
||||
isNewTickscript,
|
||||
areLogsVisible,
|
||||
|
@ -66,6 +68,7 @@ class Tickscript extends PureComponent<Props> {
|
|||
onSelectDbrps={onSelectDbrps}
|
||||
onChangeType={onChangeType}
|
||||
onChangeID={onChangeID}
|
||||
onChangeName={onChangeName}
|
||||
task={task}
|
||||
/>
|
||||
<TickscriptEditor
|
||||
|
|
|
@ -2,12 +2,11 @@ import React, {Component, ChangeEvent} from 'react'
|
|||
|
||||
import TickscriptType from 'src/kapacitor/components/TickscriptType'
|
||||
import MultiSelectDBDropdown from 'src/shared/components/MultiSelectDBDropdown'
|
||||
import TickscriptID, {
|
||||
TickscriptStaticID,
|
||||
} from 'src/kapacitor/components/TickscriptID'
|
||||
import TickscriptID from 'src/kapacitor/components/TickscriptID'
|
||||
|
||||
import {Task} from 'src/types'
|
||||
import {DBRP} from 'src/types/kapacitor'
|
||||
import TickscriptNameEditor from './TickscriptNameEditor'
|
||||
|
||||
interface DBRPDropdownItem extends DBRP {
|
||||
name: string
|
||||
|
@ -18,6 +17,7 @@ interface Props {
|
|||
onSelectDbrps: (dbrps: DBRP[]) => void
|
||||
onChangeType: (type: string) => void
|
||||
onChangeID: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
onChangeName: (name: string) => void
|
||||
task: Task
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ class TickscriptEditorControls extends Component<Props> {
|
|||
return (
|
||||
<div className="tickscript-controls">
|
||||
{this.tickscriptID}
|
||||
{!task.name || task.templateID ? undefined : (
|
||||
{!task.id || task.templateID ? undefined : (
|
||||
<div className="tickscript-controls--right">
|
||||
<TickscriptType type={task.type} onChangeType={onChangeType} />
|
||||
<MultiSelectDBDropdown
|
||||
|
@ -42,13 +42,13 @@ class TickscriptEditorControls extends Component<Props> {
|
|||
}
|
||||
|
||||
private get tickscriptID() {
|
||||
const {isNewTickscript, onChangeID, task} = this.props
|
||||
const {isNewTickscript, onChangeID, onChangeName, task} = this.props
|
||||
|
||||
if (isNewTickscript) {
|
||||
return <TickscriptID onChangeID={onChangeID} id={task.id} />
|
||||
}
|
||||
|
||||
return <TickscriptStaticID id={this.taskID} />
|
||||
return <TickscriptNameEditor name={this.taskID} onRename={onChangeName} />
|
||||
}
|
||||
|
||||
private get taskID() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {Component, FunctionComponent, ChangeEvent} from 'react'
|
||||
import React, {Component, ChangeEvent} from 'react'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface TickscriptIDProps {
|
||||
|
@ -29,11 +29,4 @@ class TickscriptID extends Component<TickscriptIDProps> {
|
|||
}
|
||||
}
|
||||
|
||||
interface TickscriptStaticIDProps {
|
||||
id: string
|
||||
}
|
||||
export const TickscriptStaticID: FunctionComponent<TickscriptStaticIDProps> = ({
|
||||
id,
|
||||
}) => <h1 className="tickscript-controls--name">{id}</h1>
|
||||
|
||||
export default TickscriptID
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
// Libraries
|
||||
import React, {
|
||||
KeyboardEvent,
|
||||
MutableRefObject,
|
||||
FocusEvent,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
interface Props {
|
||||
onRename: (name: string) => void
|
||||
name: string
|
||||
}
|
||||
|
||||
const TickscriptNameEditor = (props: Props) => {
|
||||
const [isEditing, setEditing] = useState(false)
|
||||
const inputRef: MutableRefObject<HTMLInputElement> = useRef(null)
|
||||
|
||||
const {name} = props
|
||||
|
||||
if (isEditing) {
|
||||
const handleInputBlur = (e: FocusEvent<HTMLInputElement>): void => {
|
||||
const {onRename} = props
|
||||
const newName = e.target.value
|
||||
if (newName !== name) {
|
||||
onRename(newName)
|
||||
}
|
||||
setEditing(false)
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>): void => {
|
||||
if (e.key === 'Enter') {
|
||||
inputRef.current.blur()
|
||||
}
|
||||
if (e.key === 'Escape') {
|
||||
inputRef.current.value = name
|
||||
inputRef.current.blur()
|
||||
}
|
||||
}
|
||||
|
||||
const handleFocus = (e: FocusEvent<HTMLInputElement>): void =>
|
||||
e.target.select()
|
||||
|
||||
return (
|
||||
<div className="rename-dashboard">
|
||||
<input
|
||||
type="text"
|
||||
className="rename-dashboard--input form-control input-sm"
|
||||
defaultValue={name}
|
||||
autoComplete="off"
|
||||
autoFocus={true}
|
||||
spellCheck={false}
|
||||
onBlur={handleInputBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
onFocus={handleFocus}
|
||||
placeholder="Name this TICKscript"
|
||||
ref={inputRef}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<h1 className="tickscript-controls--name" onClick={() => setEditing(true)}>
|
||||
{name}
|
||||
<span className="icon pencil" />
|
||||
</h1>
|
||||
)
|
||||
}
|
||||
export default TickscriptNameEditor
|
|
@ -25,6 +25,7 @@ import {
|
|||
notifyKapacitorNotFound,
|
||||
} from 'src/shared/copy/notifications'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import changeTaskName from '../utils/changeTaskName'
|
||||
|
||||
interface TaskResponse {
|
||||
id: number
|
||||
|
@ -200,6 +201,7 @@ export class TickscriptPage extends PureComponent<Props, State> {
|
|||
areLogsEnabled={areLogsEnabled}
|
||||
consoleMessage={consoleMessage}
|
||||
onChangeID={this.handleChangeID}
|
||||
onChangeName={this.handleChangeName}
|
||||
onChangeType={this.handleChangeType}
|
||||
isNewTickscript={!this.isEditing}
|
||||
onSelectDbrps={this.handleSelectDbrps}
|
||||
|
@ -302,6 +304,17 @@ export class TickscriptPage extends PureComponent<Props, State> {
|
|||
})
|
||||
}
|
||||
|
||||
private handleChangeName = (name: string): void => {
|
||||
this.setState(state => ({
|
||||
task: {
|
||||
...state.task,
|
||||
tickscript: changeTaskName(state.task.tickscript, name),
|
||||
name,
|
||||
},
|
||||
unsavedChanges: true,
|
||||
}))
|
||||
}
|
||||
|
||||
private handleToggleLogsVisibility = (areLogsVisible: boolean): void => {
|
||||
this.setState({areLogsVisible})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
const reNameDeclaration = new RegExp(
|
||||
// eslint-disable-next-line no-control-regex
|
||||
"(?:^|\n)var[ \t]+name[ \t]*=[ \t]*'[^\n]*'[ \r\t]*\n"
|
||||
)
|
||||
|
||||
function escapeName(s: string) {
|
||||
return s.replace(/['\\]/gi, (str: string): string => '\\' + str)
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes or creates a name variable in the supplied tickscript.
|
||||
* @param tickscript tickscript
|
||||
* @param newName new name
|
||||
* @returns modified tickscript with a new name
|
||||
*/
|
||||
export default function changeTaskName(
|
||||
tickscript: string,
|
||||
newName: string
|
||||
): string {
|
||||
const match = tickscript.match(reNameDeclaration)
|
||||
if (!match) {
|
||||
return `var name = '${escapeName(newName)}'\n${tickscript}`
|
||||
}
|
||||
let retVal = match.index ? `${tickscript.substring(0, match.index)}\n` : ''
|
||||
retVal += `var name = '${escapeName(newName)}'\n${tickscript.substring(
|
||||
match.index + match[0].length
|
||||
)}`
|
||||
return retVal
|
||||
}
|
|
@ -35,6 +35,24 @@ $tickscript-controls-height: 60px;
|
|||
font-size: 17px;
|
||||
font-weight: 400;
|
||||
color: $g13-mist;
|
||||
|
||||
.icon {
|
||||
padding-left: 6px;
|
||||
position: absolute;
|
||||
font-size: 15px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
&:hover {
|
||||
cursor: text;
|
||||
color: $g20-white;
|
||||
background-color: $g2-kevlar;
|
||||
border-color: $g2-kevlar;
|
||||
}
|
||||
|
||||
&:hover .icon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.tickscript-controls--right {
|
||||
display: flex;
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import changeTaskName from 'src/kapacitor/utils/changeTaskName'
|
||||
|
||||
describe('kapacitor.utils.changeTaskName', () => {
|
||||
;[
|
||||
{
|
||||
id: 'inserts into empty tickscript',
|
||||
existing: '',
|
||||
name: 'my name',
|
||||
result: "var name = 'my name'\n",
|
||||
},
|
||||
{
|
||||
id: 'inserts into tickscript without var',
|
||||
existing: 'var whatever = TRUE\n',
|
||||
name: 'my name',
|
||||
result: "var name = 'my name'\nvar whatever = TRUE\n",
|
||||
},
|
||||
{
|
||||
id: 'inserts escaped name into tickscript without var',
|
||||
existing: 'var whatever = TRUE\n',
|
||||
name: "my\\'name",
|
||||
result: "var name = 'my\\\\\\'name'\nvar whatever = TRUE\n",
|
||||
},
|
||||
{
|
||||
id: 'replaces leading variable definition',
|
||||
existing: "var name='otherName'\r\nWHATEVERHEREIN",
|
||||
name: 'my name',
|
||||
result: "var name = 'my name'\nWHATEVERHEREIN",
|
||||
},
|
||||
{
|
||||
id: 'replaces inline variable definition',
|
||||
existing: "WHATEVERBEFORE\r\nvar name='otherName'\nWHATEVERAFTER",
|
||||
name: 'my name',
|
||||
result: "WHATEVERBEFORE\r\nvar name = 'my name'\nWHATEVERAFTER",
|
||||
},
|
||||
{
|
||||
id: 'replaces escaped variable definition',
|
||||
existing:
|
||||
"WHATEVERBEFORE\nvar \tname \t= \t'otherName'\t \nWHATEVERAFTER",
|
||||
name: "my\\'name",
|
||||
result: "WHATEVERBEFORE\nvar name = 'my\\\\\\'name'\nWHATEVERAFTER",
|
||||
},
|
||||
].forEach(test => {
|
||||
it(test.id, () => {
|
||||
expect(changeTaskName(test.existing, test.name)).toBe(test.result)
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue