Persist ifql script

Co-authored-by: Andrew Watkins <andrew.watkinz@gmail.com>
Co-authored-by: Chris Henn <chris.henn@influxdata.com>
ifql/save-service
Andrew Watkins 2018-05-22 14:50:07 -07:00
parent b778fd9756
commit 4326855844
11 changed files with 117 additions and 22 deletions

View File

@ -0,0 +1,17 @@
export type Action = ActionUpdateScript
export interface ActionUpdateScript {
type: 'UPDATE_SCRIPT'
payload: {
script: string
}
}
export type UpdateScript = (script: string) => ActionUpdateScript
export const updateScript = (script: string): ActionUpdateScript => {
return {
type: 'UPDATE_SCRIPT',
payload: {script},
}
}

View File

@ -1,4 +1,6 @@
import AJAX from 'src/utils/ajax' import AJAX from 'src/utils/ajax'
import {Service} from 'src/types'
import {updateService} from 'src/shared/apis'
export const getSuggestions = async (url: string) => { export const getSuggestions = async (url: string) => {
try { try {
@ -34,17 +36,24 @@ export const getAST = async (request: ASTRequest) => {
} }
} }
export const getTimeSeries = async (script: string) => { export const getTimeSeries = async (service: Service, script: string) => {
const and = encodeURIComponent('&')
const mark = encodeURIComponent('?')
const garbage = script.replace(/\s/g, '') // server cannot handle whitespace
try { try {
const data = await AJAX({ const data = await AJAX({
method: 'POST', method: 'POST',
url: `http://localhost:8093/query?q=${script}`, url: `${
service.links.proxy
}?path=/v1/query${mark}orgName=defaulorgname${and}q=${garbage}`,
headers: {'Content-Type': 'text/plain'},
}) })
return data return data
} catch (error) { } catch (error) {
console.error('Problem fetching data', error) console.error('Problem fetching data', error)
throw error throw error.data.message
} }
} }
@ -82,3 +91,19 @@ export const getTagValues = async () => {
throw error throw error
} }
} }
export const updateScript = async (service: Service, script: string) => {
const updates = {...service, metadata: {script}}
try {
const response = await updateService(updates)
return response
} catch (error) {
if (error.data) {
console.error('Could not update script', error.data)
throw error.data
}
throw error
}
}

View File

@ -58,3 +58,6 @@ export const EXCLUDED_KEYS = [
'Meta', 'Meta',
' ', ' ',
] ]
export const DEFAULT_SCRIPT =
'fil = (r) => r._measurement == "cpu"\ntele = from(db: "telegraf") \n\t\t|> filter(fn: fil)\n |> range(start: -1m)\n |> sum()\n\n'

View File

@ -10,11 +10,14 @@ import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts'
import {notify as notifyAction} from 'src/shared/actions/notifications' import {notify as notifyAction} from 'src/shared/actions/notifications'
import {analyzeSuccess} from 'src/shared/copy/notifications' import {analyzeSuccess} from 'src/shared/copy/notifications'
import {
updateScript as updateScriptAction,
UpdateScript,
} from 'src/ifql/actions'
import {bodyNodes} from 'src/ifql/helpers' import {bodyNodes} from 'src/ifql/helpers'
import {getSuggestions, getAST, getTimeSeries} from 'src/ifql/apis' import {getSuggestions, getAST, getTimeSeries} from 'src/ifql/apis'
import {builder, argTypes} from 'src/ifql/constants' import {funcNames, builder, argTypes} from 'src/ifql/constants'
import {funcNames} from 'src/ifql/constants'
import {Source, Service, Notification} from 'src/types' import {Source, Service, Notification} from 'src/types'
import { import {
@ -37,6 +40,11 @@ interface Props {
services: Service[] services: Service[]
sources: Source[] sources: Source[]
notify: (message: Notification) => void notify: (message: Notification) => void
script: string
updateScript: UpdateScript
params: {
sourceID: string
}
} }
interface Body extends FlatBody { interface Body extends FlatBody {
@ -46,7 +54,6 @@ interface Body extends FlatBody {
interface State { interface State {
body: Body[] body: Body[]
ast: object ast: object
script: string
data: string data: string
suggestions: Suggestion[] suggestions: Suggestion[]
status: Status status: Status
@ -63,7 +70,6 @@ export class IFQLPage extends PureComponent<Props, State> {
ast: null, ast: null,
data: 'Hit "Get Data!" or Ctrl + Enter to run your script', data: 'Hit "Get Data!" or Ctrl + Enter to run your script',
suggestions: [], suggestions: [],
script: `fil = (r) => r._measurement == \"cpu\"\ntele = from(db: \"telegraf\") \n\t\t|> filter(fn: fil)\n |> range(start: -1m)\n |> sum()\n\n`,
status: { status: {
type: 'none', type: 'none',
text: '', text: '',
@ -72,7 +78,7 @@ export class IFQLPage extends PureComponent<Props, State> {
} }
public async componentDidMount() { public async componentDidMount() {
const {links} = this.props const {links, script} = this.props
try { try {
const suggestions = await getSuggestions(links.suggestions) const suggestions = await getSuggestions(links.suggestions)
@ -81,11 +87,12 @@ export class IFQLPage extends PureComponent<Props, State> {
console.error('Could not get function suggestions: ', error) console.error('Could not get function suggestions: ', error)
} }
this.getASTResponse(this.state.script) this.getASTResponse(script)
} }
public render() { public render() {
const {suggestions, script, data, body, status} = this.state const {suggestions, data, body, status} = this.state
const {script} = this.props
return ( return (
<CheckServices> <CheckServices>
@ -140,7 +147,7 @@ export class IFQLPage extends PureComponent<Props, State> {
} }
private handleSubmitScript = () => { private handleSubmitScript = () => {
this.getASTResponse(this.state.script) this.getASTResponse(this.props.script)
} }
private handleGenerateScript = (): void => { private handleGenerateScript = (): void => {
@ -260,21 +267,21 @@ export class IFQLPage extends PureComponent<Props, State> {
} }
private handleAppendFrom = (): void => { private handleAppendFrom = (): void => {
const {script} = this.state const {script} = this.props
const newScript = `${script.trim()}\n\n${builder.NEW_FROM}\n\n` const newScript = `${script.trim()}\n\n${builder.NEW_FROM}\n\n`
this.getASTResponse(newScript) this.getASTResponse(newScript)
} }
private handleAppendJoin = (): void => { private handleAppendJoin = (): void => {
const {script} = this.state const {script} = this.props
const newScript = `${script.trim()}\n\n${builder.NEW_JOIN}\n\n` const newScript = `${script.trim()}\n\n${builder.NEW_JOIN}\n\n`
this.getASTResponse(newScript) this.getASTResponse(newScript)
} }
private handleChangeScript = (script: string): void => { private handleChangeScript = (script: string): void => {
this.setState({script}) this.props.updateScript(script)
} }
private handleAddNode = ( private handleAddNode = (
@ -376,10 +383,10 @@ export class IFQLPage extends PureComponent<Props, State> {
} }
private handleAnalyze = async () => { private handleAnalyze = async () => {
const {links, notify} = this.props const {links, notify, script} = this.props
try { try {
const ast = await getAST({url: links.ast, body: this.state.script}) const ast = await getAST({url: links.ast, body: script})
const body = bodyNodes(ast, this.state.suggestions) const body = bodyNodes(ast, this.state.suggestions)
const status = {type: 'success', text: ''} const status = {type: 'success', text: ''}
notify(analyzeSuccess) notify(analyzeSuccess)
@ -411,7 +418,8 @@ export class IFQLPage extends PureComponent<Props, State> {
}) })
const body = bodyNodes(ast, suggestions) const body = bodyNodes(ast, suggestions)
const status = {type: 'success', text: ''} const status = {type: 'success', text: ''}
this.setState({ast, script, body, status}) this.setState({ast, body, status})
this.props.updateScript(script)
} catch (error) { } catch (error) {
this.setState({status: this.parseError(error)}) this.setState({status: this.parseError(error)})
return console.error('Could not parse AST', error) return console.error('Could not parse AST', error)
@ -419,14 +427,14 @@ export class IFQLPage extends PureComponent<Props, State> {
} }
private getTimeSeries = async () => { private getTimeSeries = async () => {
const {script} = this.state const {script} = this.props
this.setState({data: 'fetching data...'}) this.setState({data: 'fetching data...'})
try { try {
const {data} = await getTimeSeries(script) const {data} = await getTimeSeries(this.service, script)
this.setState({data}) this.setState({data})
} catch (error) { } catch (error) {
this.setState({data: 'Error fetching data'}) this.setState({data: error})
console.error('Could not get timeSeries', error) console.error('Could not get timeSeries', error)
} }
@ -440,12 +448,13 @@ export class IFQLPage extends PureComponent<Props, State> {
} }
} }
const mapStateToProps = ({links, services, sources}) => { const mapStateToProps = ({links, services, sources, script}) => {
return {links: links.ifql, services, sources} return {links: links.ifql, services, sources, script}
} }
const mapDispatchToProps = { const mapDispatchToProps = {
notify: notifyAction, notify: notifyAction,
updateScript: updateScriptAction,
} }
export default connect(mapStateToProps, mapDispatchToProps)(IFQLPage) export default connect(mapStateToProps, mapDispatchToProps)(IFQLPage)

View File

@ -0,0 +1,17 @@
import {Action} from 'src/ifql/actions'
import {editor} from 'src/ifql/constants'
const scriptReducer = (
state: string = editor.DEFAULT_SCRIPT,
action: Action
): string => {
switch (action.type) {
case 'UPDATE_SCRIPT': {
return action.payload.script
}
}
return state
}
export default scriptReducer

View File

@ -56,6 +56,7 @@ export const saveToLocalStorage = ({
timeRange, timeRange,
dataExplorer, dataExplorer,
dashTimeV1: {ranges}, dashTimeV1: {ranges},
script,
}: LocalStorage): void => { }: LocalStorage): void => {
try { try {
const appPersisted = {app: {persisted}} const appPersisted = {app: {persisted}}
@ -70,6 +71,7 @@ export const saveToLocalStorage = ({
dashTimeV1, dashTimeV1,
dataExplorer, dataExplorer,
dataExplorerQueryConfigs, dataExplorerQueryConfigs,
script,
}) })
) )
} catch (err) { } catch (err) {

View File

@ -17,6 +17,7 @@ import overlayTechnology from 'src/shared/reducers/overlayTechnology'
import dashTimeV1 from 'src/dashboards/reducers/dashTimeV1' import dashTimeV1 from 'src/dashboards/reducers/dashTimeV1'
import persistStateEnhancer from './persistStateEnhancer' import persistStateEnhancer from './persistStateEnhancer'
import servicesReducer from 'src/shared/reducers/services' import servicesReducer from 'src/shared/reducers/services'
import scriptReducer from 'src/ifql/reducers/script'
const rootReducer = combineReducers({ const rootReducer = combineReducers({
...statusReducers, ...statusReducers,
@ -30,6 +31,7 @@ const rootReducer = combineReducers({
dashTimeV1, dashTimeV1,
routing: routerReducer, routing: routerReducer,
services: servicesReducer, services: servicesReducer,
script: scriptReducer,
}) })
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose

View File

@ -7,6 +7,7 @@ export interface LocalStorage {
dataExplorer: DataExplorer dataExplorer: DataExplorer
dataExplorerQueryConfigs: DataExplorerQueryConfigs dataExplorerQueryConfigs: DataExplorerQueryConfigs
timeRange: TimeRange timeRange: TimeRange
script: string
} }
export type VERSION = string export type VERSION = string

View File

@ -10,6 +10,7 @@ export interface NewService {
export interface Service { export interface Service {
id?: string id?: string
sourceID: string
url: string url: string
name: string name: string
type: string type: string
@ -17,6 +18,9 @@ export interface Service {
password?: string password?: string
active: boolean active: boolean
insecureSkipVerify: boolean insecureSkipVerify: boolean
metadata: {
[x: string]: any
}
links: { links: {
source: string source: string
self: string self: string

View File

@ -3,6 +3,7 @@ import {shallow} from 'enzyme'
import {IFQLPage} from 'src/ifql/containers/IFQLPage' import {IFQLPage} from 'src/ifql/containers/IFQLPage'
import TimeMachine from 'src/ifql/components/TimeMachine' import TimeMachine from 'src/ifql/components/TimeMachine'
import {ActionUpdateScript} from 'src/ifql/actions'
jest.mock('src/ifql/apis', () => require('mocks/ifql/apis')) jest.mock('src/ifql/apis', () => require('mocks/ifql/apis'))
@ -15,7 +16,19 @@ const setup = () => {
}, },
services: [], services: [],
sources: [], sources: [],
script: '',
notify: () => {}, notify: () => {},
params: {
sourceID: '',
},
updateScript: (script: string) => {
return {
type: 'UPDATE_SCRIPT',
payload: {
script,
},
} as ActionUpdateScript
},
} }
const wrapper = shallow(<IFQLPage {...props} />) const wrapper = shallow(<IFQLPage {...props} />)

View File

@ -96,6 +96,7 @@ export const kapacitor = {
export const service = { export const service = {
id: '1', id: '1',
sourceID: '1',
url: 'localhost:8082', url: 'localhost:8082',
type: 'ifql', type: 'ifql',
name: 'IFQL', name: 'IFQL',
@ -108,6 +109,7 @@ export const service = {
proxy: '/chronograf/v1/sources/1/services/2/proxy', proxy: '/chronograf/v1/sources/1/services/2/proxy',
self: '/chronograf/v1/sources/1/services/2', self: '/chronograf/v1/sources/1/services/2',
}, },
metadata: {},
} }
export const kapacitorRules = [ export const kapacitorRules = [