diff --git a/ui/mocks/kapacitor/apis/index.ts b/ui/mocks/kapacitor/apis/index.ts new file mode 100644 index 000000000..74aa998c7 --- /dev/null +++ b/ui/mocks/kapacitor/apis/index.ts @@ -0,0 +1,2 @@ +export const pingKapacitorVersion = jest.fn(() => Promise.resolve('2.0')) +export const getLogStreamByRuleID = jest.fn(() => Promise.resolve()) diff --git a/ui/mocks/shared/apis/index.ts b/ui/mocks/shared/apis/index.ts index d70eaa530..a3c6e233c 100644 --- a/ui/mocks/shared/apis/index.ts +++ b/ui/mocks/shared/apis/index.ts @@ -1,6 +1,7 @@ import {kapacitor} from 'mocks/dummy' export const getKapacitor = jest.fn(() => Promise.resolve(kapacitor)) +export const getActiveKapacitor = jest.fn(() => Promise.resolve(kapacitor)) export const createKapacitor = jest.fn(() => Promise.resolve({data: kapacitor})) export const updateKapacitor = jest.fn(() => Promise.resolve({data: kapacitor})) export const pingKapacitor = jest.fn(() => Promise.resolve()) diff --git a/ui/package.json b/ui/package.json index 46b603580..7680f053c 100644 --- a/ui/package.json +++ b/ui/package.json @@ -41,6 +41,7 @@ "@types/prop-types": "^15.5.2", "@types/react": "^16.0.38", "@types/react-router": "3", + "@types/text-encoding": "^0.0.32", "autoprefixer": "^6.3.1", "babel-core": "^6.5.1", "babel-eslint": "6.1.2", diff --git a/ui/src/kapacitor/containers/TickscriptPage.js b/ui/src/kapacitor/containers/TickscriptPage.tsx similarity index 75% rename from ui/src/kapacitor/containers/TickscriptPage.js rename to ui/src/kapacitor/containers/TickscriptPage.tsx index 3b645e838..bf447f9a1 100644 --- a/ui/src/kapacitor/containers/TickscriptPage.js +++ b/ui/src/kapacitor/containers/TickscriptPage.tsx @@ -1,28 +1,88 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' +import React, {PureComponent} from 'react' import {connect} from 'react-redux' import {bindActionCreators} from 'redux' import uuid from 'uuid' import Tickscript from 'src/kapacitor/components/Tickscript' import * as kapactiorActionCreators from 'src/kapacitor/actions/view' -import * as errorActionCreators from 'shared/actions/errors' +import * as errorActionCreators from 'src/shared/actions/errors' import {getActiveKapacitor} from 'src/shared/apis' import {getLogStreamByRuleID, pingKapacitorVersion} from 'src/kapacitor/apis' -import {notify as notifyAction} from 'shared/actions/notifications' +import {notify as notifyAction} from 'src/shared/actions/notifications' + +import {Source, Kapacitor, Task, AlertRule} from 'src/types' import { notifyTickscriptLoggingUnavailable, notifyTickscriptLoggingError, notifyKapacitorNotFound, -} from 'shared/copy/notifications' +} from 'src/shared/copy/notifications' -class TickscriptPage extends Component { +interface ErrorActions { + errorThrown: (notify: string | object) => void +} + +interface Router { + push: (path: string) => void +} + +interface KapacitorActions { + updateTask: ( + kapacitor: Kapacitor, + task: Task, + ruleID: string, + router: Router, + sourceID: string + ) => void + createTask: ( + kapacitor: Kapacitor, + task: Task, + router: Router, + sourceID: string + ) => void + getRule: (kapacitor: Kapacitor, ruleID: string) => void +} + +interface Params { + ruleID: string +} + +interface Props { + source: Source + errorActions: ErrorActions + kapacitorActions: KapacitorActions + router: Router + params: Params + rules: AlertRule[] + notify: any +} + +interface State { + kapacitor: Kapacitor + task: Task + consoleMessage: string + isEditingID: boolean + logs: object[] + areLogsVisible: boolean + areLogsEnabled: boolean + failStr: string + unsavedChanges: boolean +} + +export class TickscriptPage extends PureComponent { constructor(props) { super(props) - this.state = { - kapacitor: {}, + kapacitor: { + id: '', + url: '', + name: '', + active: false, + insecureSkipVerify: false, + links: { + self: '', + }, + }, task: { id: '', name: '', @@ -34,13 +94,143 @@ class TickscriptPage extends Component { consoleMessage: '', isEditingID: true, logs: [], - areLogsEnabled: false, failStr: '', + areLogsVisible: false, + areLogsEnabled: false, unsavedChanges: false, } } - fetchChunkedLogs = async (kapacitor, ruleID) => { + public async componentDidMount() { + const { + source, + errorActions, + kapacitorActions, + params: {ruleID}, + } = this.props + + const kapacitor = await getActiveKapacitor(source) + if (!kapacitor) { + errorActions.errorThrown(notifyKapacitorNotFound()) + } + + if (this._isEditing()) { + await kapacitorActions.getRule(kapacitor, ruleID) + const {id, name, tickscript, dbrps, type} = this.props.rules.find( + r => r.id === ruleID + ) + + this.setState({task: {tickscript, dbrps, type, status, name, id}}) + } + + this.fetchChunkedLogs(kapacitor, ruleID) + + this.setState({kapacitor}) + } + + public componentWillUnmount() { + this.setState({ + areLogsEnabled: false, + }) + } + + public render() { + const { + task, + logs, + areLogsVisible, + areLogsEnabled, + unsavedChanges, + consoleMessage, + } = this.state + + return ( + + ) + } + + private handleSave = async () => { + const {kapacitor, task} = this.state + const { + source: {id: sourceID}, + router, + kapacitorActions: {createTask, updateTask}, + params: {ruleID}, + } = this.props + + let response + + try { + if (this._isEditing()) { + response = await updateTask(kapacitor, task, ruleID, router, sourceID) + } else { + response = await createTask(kapacitor, task, router, sourceID) + router.push(`/sources/${sourceID}/tickscript/${response.id}`) + } + if (response.code) { + this.setState({unsavedChanges: true, consoleMessage: response.message}) + } else { + this.setState({unsavedChanges: false, consoleMessage: ''}) + } + } catch (error) { + console.error(error) + throw error + } + } + + private handleExit = () => { + const {source: {id: sourceID}, router} = this.props + + return router.push(`/sources/${sourceID}/alert-rules`) + } + + private handleChangeScript = tickscript => { + this.setState({ + task: {...this.state.task, tickscript}, + unsavedChanges: true, + }) + } + + private handleSelectDbrps = dbrps => { + this.setState({task: {...this.state.task, dbrps}, unsavedChanges: true}) + } + + private handleChangeType = type => () => { + this.setState({task: {...this.state.task, type}, unsavedChanges: true}) + } + + private handleChangeID = e => { + this.setState({ + task: {...this.state.task, id: e.target.value}, + unsavedChanges: true, + }) + } + + private handleToggleLogsVisibility = () => { + this.setState({areLogsVisible: !this.state.areLogsVisible}) + } + + private _isEditing() { + const {params} = this.props + return params.ruleID && params.ruleID !== 'new' + } + + private fetchChunkedLogs = async (kapacitor, ruleID) => { const {notify} = this.props try { @@ -110,7 +300,7 @@ class TickscriptPage extends Component { failStr, }) } catch (err) { - console.warn(err, failStr) + console.warn(err, failStr) // tslint:disable-line this.setState({ logs: [...logs, ...this.state.logs], failStr, @@ -119,165 +309,10 @@ class TickscriptPage extends Component { } } catch (error) { console.error(error) - notify(notifyTickscriptLoggingError()(error)) + notify(notifyTickscriptLoggingError(error)) throw error } } - - async componentDidMount() { - const { - source, - errorActions, - kapacitorActions, - params: {ruleID}, - } = this.props - - const kapacitor = await getActiveKapacitor(source) - if (!kapacitor) { - errorActions.errorThrown(notifyKapacitorNotFound()) - } - - if (this._isEditing()) { - await kapacitorActions.getRule(kapacitor, ruleID) - const {id, name, tickscript, dbrps, type} = this.props.rules.find( - r => r.id === ruleID - ) - - this.setState({task: {tickscript, dbrps, type, status, name, id}}) - } - - this.fetchChunkedLogs(kapacitor, ruleID) - - this.setState({kapacitor}) - } - - componentWillUnmount() { - this.setState({ - areLogsEnabled: false, - }) - } - - handleSave = async () => { - const {kapacitor, task} = this.state - const { - source: {id: sourceID}, - router, - kapacitorActions: {createTask, updateTask}, - params: {ruleID}, - } = this.props - - let response - - try { - if (this._isEditing()) { - response = await updateTask(kapacitor, task, ruleID, router, sourceID) - } else { - response = await createTask(kapacitor, task, router, sourceID) - router.push(`/sources/${sourceID}/tickscript/${response.id}`) - } - if (response.code) { - this.setState({unsavedChanges: true, consoleMessage: response.message}) - } else { - this.setState({unsavedChanges: false, consoleMessage: ''}) - } - } catch (error) { - console.error(error) - throw error - } - } - - handleExit = () => { - const {source: {id: sourceID}, router} = this.props - - return router.push(`/sources/${sourceID}/alert-rules`) - } - - handleChangeScript = tickscript => { - this.setState({ - task: {...this.state.task, tickscript}, - unsavedChanges: true, - }) - } - - handleSelectDbrps = dbrps => { - this.setState({task: {...this.state.task, dbrps}, unsavedChanges: true}) - } - - handleChangeType = type => () => { - this.setState({task: {...this.state.task, type}, unsavedChanges: true}) - } - - handleChangeID = e => { - this.setState({ - task: {...this.state.task, id: e.target.value}, - unsavedChanges: true, - }) - } - - handleToggleLogsVisibility = () => { - this.setState({areLogsVisible: !this.state.areLogsVisible}) - } - - render() { - const {source} = this.props - const { - task, - logs, - areLogsVisible, - areLogsEnabled, - unsavedChanges, - consoleMessage, - } = this.state - - return ( - - ) - } - - _isEditing() { - const {params} = this.props - return params.ruleID && params.ruleID !== 'new' - } -} - -const {arrayOf, func, shape, string} = PropTypes - -TickscriptPage.propTypes = { - source: shape({ - name: string, - }), - errorActions: shape({ - errorThrown: func.isRequired, - }).isRequired, - kapacitorActions: shape({ - updateTask: func.isRequired, - createTask: func.isRequired, - getRule: func.isRequired, - }), - router: shape({ - push: func.isRequired, - }).isRequired, - params: shape({ - ruleID: string, - }).isRequired, - rules: arrayOf(shape()), - notify: func.isRequired, } const mapStateToProps = state => { diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index f2f08d515..43c1d24d1 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -1,6 +1,6 @@ import {AuthLinks, Organization, Role, User, Me} from './auth' -import {AlertRule, Kapacitor} from './kapacitor' import {Query, QueryConfig, TimeRange} from './query' +import {AlertRule, Kapacitor, Task} from './kapacitor' import {Source} from './sources' import {DropdownAction, DropdownItem} from './shared' @@ -18,4 +18,5 @@ export { DropdownAction, DropdownItem, TimeRange, + Task, } diff --git a/ui/src/types/kapacitor.ts b/ui/src/types/kapacitor.ts index 44023b9f6..04d7e75ce 100644 --- a/ui/src/types/kapacitor.ts +++ b/ui/src/types/kapacitor.ts @@ -34,6 +34,15 @@ export interface AlertRule { 'last-enabled'?: string } +export interface Task { + id: string + name: string + status: string + tickscript: string + dbrps: DBRP[] + type: string +} + type TICKScript = string // AlertNodes defines all possible kapacitor interactions with an alert. diff --git a/ui/test/kapacitor/containers/TickscriptPage.test.tsx b/ui/test/kapacitor/containers/TickscriptPage.test.tsx new file mode 100644 index 000000000..a210cc6eb --- /dev/null +++ b/ui/test/kapacitor/containers/TickscriptPage.test.tsx @@ -0,0 +1,43 @@ +import React from 'react' +import {shallow} from 'enzyme' +import {TickscriptPage} from 'src/kapacitor/containers/TickscriptPage' +import {source} from 'test/resources' + +jest.mock('src/shared/apis', () => require('mocks/shared/apis')) +jest.mock('src/kapacitor/apis', () => require('mocks/kapacitor/apis')) + +const setup = () => { + const props = { + source, + errorActions: { + errorThrown: () => {}, + }, + kapacitorActions: { + updateTask: () => {}, + createTask: () => {}, + getRule: () => {}, + }, + router: { + push: () => {}, + }, + params: { + ruleID: '', + }, + rules: [], + notify: () => {}, + } + const wrapper = shallow() + + return { + wrapper, + } +} + +describe('Kapacitor.Containers.TickscriptPage', () => { + describe('rendering', () => { + it('renders without errors', () => { + const {wrapper} = setup() + expect(wrapper.exists()).toBe(true) + }) + }) +}) diff --git a/ui/tsconfig.json b/ui/tsconfig.json index c585ab2e3..f0950c95f 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -8,7 +8,8 @@ "react", "prop-types", "jest", - "react-router" + "react-router", + "text-encoding" ], "target": "es6", "module": "es2015", diff --git a/ui/yarn.lock b/ui/yarn.lock index dfbdf9154..17b0ed4bd 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -72,6 +72,10 @@ version "16.0.40" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.0.40.tgz#caabc2296886f40b67f6fc80f0f3464476461df9" +"@types/text-encoding@^0.0.32": + version "0.0.32" + resolved "https://registry.yarnpkg.com/@types/text-encoding/-/text-encoding-0.0.32.tgz#52289b320a406850b14f08f48b475ca021218048" + abab@^1.0.3, abab@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"