commit
cd48790f0c
|
@ -41,6 +41,7 @@
|
|||
"@types/jest": "^22.1.4",
|
||||
"@types/lodash": "^4.14.104",
|
||||
"@types/node": "^9.4.6",
|
||||
"@types/papaparse": "^4.1.34",
|
||||
"@types/prop-types": "^15.5.2",
|
||||
"@types/react": "^16.0.38",
|
||||
"@types/react-dnd": "^2.0.36",
|
||||
|
@ -133,6 +134,7 @@
|
|||
"lodash": "^4.3.0",
|
||||
"moment": "^2.13.0",
|
||||
"nano-date": "^2.0.1",
|
||||
"papaparse": "^4.4.0",
|
||||
"prop-types": "^15.6.1",
|
||||
"query-string": "^5.0.0",
|
||||
"react": "^16.3.1",
|
||||
|
@ -157,4 +159,4 @@
|
|||
"rome": "^2.1.22",
|
||||
"uuid": "^3.2.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import _ from 'lodash'
|
||||
|
||||
import AJAX from 'src/utils/ajax'
|
||||
import {Service} from 'src/types'
|
||||
import {Service, ScriptResult} from 'src/types'
|
||||
import {updateService} from 'src/shared/apis'
|
||||
import {parseResults} from 'src/shared/parsing/ifql'
|
||||
|
||||
export const getSuggestions = async (url: string) => {
|
||||
try {
|
||||
|
@ -36,24 +39,28 @@ export const getAST = async (request: ASTRequest) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const getTimeSeries = async (service: Service, script: string) => {
|
||||
export const getTimeSeries = async (
|
||||
service: Service,
|
||||
script: string
|
||||
): Promise<ScriptResult[]> => {
|
||||
const and = encodeURIComponent('&')
|
||||
const mark = encodeURIComponent('?')
|
||||
const garbage = script.replace(/\s/g, '') // server cannot handle whitespace
|
||||
|
||||
try {
|
||||
const data = await AJAX({
|
||||
const {data} = await AJAX({
|
||||
method: 'POST',
|
||||
url: `${
|
||||
service.links.proxy
|
||||
}?path=/v1/query${mark}orgName=defaulorgname${and}q=${garbage}`,
|
||||
headers: {'Content-Type': 'text/plain'},
|
||||
})
|
||||
|
||||
return data
|
||||
return parseResults(data)
|
||||
} catch (error) {
|
||||
console.error('Problem fetching data', error)
|
||||
throw error.data.message
|
||||
|
||||
throw _.get(error, 'headers.x-influx-error', false) ||
|
||||
_.get(error, 'data.message', 'unknown error 🤷')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import React, {SFC} from 'react'
|
||||
|
||||
const NoResult: SFC = () => (
|
||||
<div className="graph-empty">
|
||||
<p>No Results</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default NoResult
|
|
@ -0,0 +1,58 @@
|
|||
import React, {PureComponent, CSSProperties} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import {ScriptResult} from 'src/types'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||
import TableSidebarItem from 'src/ifql/components/TableSidebarItem'
|
||||
import {vis} from 'src/ifql/constants'
|
||||
|
||||
interface Props {
|
||||
data: ScriptResult[]
|
||||
selectedResultID: string
|
||||
onSelectResult: (id: string) => void
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
export default class TableSidebar extends PureComponent<Props> {
|
||||
public render() {
|
||||
const {data, selectedResultID, onSelectResult} = this.props
|
||||
|
||||
return (
|
||||
<div className="time-machine--sidebar">
|
||||
{!this.isDataEmpty && (
|
||||
<div className="query-builder--heading" style={this.headingStyle}>
|
||||
Results
|
||||
</div>
|
||||
)}
|
||||
<FancyScrollbar>
|
||||
<div className="time-machine-vis--sidebar query-builder--list">
|
||||
{data.map(({name, id}) => {
|
||||
return (
|
||||
<TableSidebarItem
|
||||
id={id}
|
||||
key={id}
|
||||
name={name}
|
||||
onSelect={onSelectResult}
|
||||
isSelected={id === selectedResultID}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</FancyScrollbar>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
get headingStyle(): CSSProperties {
|
||||
return {
|
||||
height: `${vis.TABLE_ROW_HEIGHT + 2.5}px`,
|
||||
backgroundColor: '#31313d',
|
||||
borderBottom: '2px solid #383846', // $g5-pepper
|
||||
}
|
||||
}
|
||||
|
||||
get isDataEmpty(): boolean {
|
||||
return _.isEmpty(this.props.data)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
name: string
|
||||
id: string
|
||||
isSelected: boolean
|
||||
onSelect: (id: string) => void
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
export default class TableSidebarItem extends PureComponent<Props> {
|
||||
public render() {
|
||||
return (
|
||||
<div
|
||||
className={`query-builder--list-item ${this.active}`}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
{this.props.name}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get active(): string {
|
||||
if (this.props.isSelected) {
|
||||
return 'active'
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
private handleClick = (): void => {
|
||||
this.props.onSelect(this.props.id)
|
||||
}
|
||||
}
|
|
@ -9,16 +9,17 @@ import {
|
|||
OnChangeScript,
|
||||
OnSubmitScript,
|
||||
FlatBody,
|
||||
Status,
|
||||
ScriptStatus,
|
||||
ScriptResult,
|
||||
} from 'src/types/ifql'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {HANDLE_VERTICAL, HANDLE_HORIZONTAL} from 'src/shared/constants'
|
||||
|
||||
interface Props {
|
||||
data: string
|
||||
data: ScriptResult[]
|
||||
script: string
|
||||
body: Body[]
|
||||
status: Status
|
||||
status: ScriptStatus
|
||||
suggestions: Suggestion[]
|
||||
onChangeScript: OnChangeScript
|
||||
onSubmitScript: OnSubmitScript
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
import {Grid, GridCellProps, AutoSizer, ColumnSizer} from 'react-virtualized'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {ScriptResult} from 'src/types'
|
||||
import {vis} from 'src/ifql/constants'
|
||||
|
||||
@ErrorHandling
|
||||
export default class TimeMachineTable extends PureComponent<ScriptResult> {
|
||||
public render() {
|
||||
const {data} = this.props
|
||||
|
||||
return (
|
||||
<div style={{flex: '1 1 auto'}}>
|
||||
<AutoSizer>
|
||||
{({height, width}) => (
|
||||
<ColumnSizer
|
||||
width={width}
|
||||
columnMinWidth={vis.TIME_COLUMN_WIDTH}
|
||||
columnCount={this.columnCount}
|
||||
>
|
||||
{({adjustedWidth, getColumnWidth}) => (
|
||||
<Grid
|
||||
className="table-graph--scroll-window"
|
||||
cellRenderer={this.cellRenderer}
|
||||
columnCount={this.columnCount}
|
||||
columnWidth={getColumnWidth}
|
||||
height={height}
|
||||
rowCount={data.length}
|
||||
rowHeight={vis.TABLE_ROW_HEIGHT}
|
||||
width={adjustedWidth}
|
||||
/>
|
||||
)}
|
||||
</ColumnSizer>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get columnCount(): number {
|
||||
return _.get(this.props.data, '0', []).length
|
||||
}
|
||||
|
||||
private cellRenderer = ({
|
||||
columnIndex,
|
||||
key,
|
||||
rowIndex,
|
||||
style,
|
||||
}: GridCellProps): React.ReactNode => {
|
||||
const {data} = this.props
|
||||
const headerRowClass = !rowIndex ? 'table-graph-cell__fixed-row' : ''
|
||||
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
style={style}
|
||||
className={`table-graph-cell ${headerRowClass}`}
|
||||
>
|
||||
{data[rowIndex][columnIndex]}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,40 +1,79 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||
import React, {PureComponent, CSSProperties} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {ScriptResult} from 'src/types'
|
||||
import TableSidebar from 'src/ifql/components/TableSidebar'
|
||||
import TimeMachineTable from 'src/ifql/components/TimeMachineTable'
|
||||
import {HANDLE_PIXELS} from 'src/shared/constants'
|
||||
import NoResults from 'src/ifql/components/NoResults'
|
||||
|
||||
interface Props {
|
||||
data: string
|
||||
data: ScriptResult[]
|
||||
}
|
||||
|
||||
interface State {
|
||||
selectedResultID: string | null
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class TimeMachineVis extends PureComponent<Props> {
|
||||
class TimeMachineVis extends PureComponent<Props, State> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {selectedResultID: this.initialResultID}
|
||||
}
|
||||
|
||||
public componentDidUpdate(__, prevState) {
|
||||
if (prevState.selectedResultID === null) {
|
||||
this.setState({selectedResultID: this.initialResultID})
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className="time-machine-visualization">
|
||||
<div className="time-machine--graph">
|
||||
<FancyScrollbar>
|
||||
<div className="time-machine--graph-body">
|
||||
{this.data.map((d, i) => {
|
||||
return (
|
||||
<div key={i} className="data-row">
|
||||
{d}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</FancyScrollbar>
|
||||
<div className="time-machine-visualization" style={this.style}>
|
||||
{this.hasResults && (
|
||||
<TableSidebar
|
||||
data={this.props.data}
|
||||
selectedResultID={this.state.selectedResultID}
|
||||
onSelectResult={this.handleSelectResult}
|
||||
/>
|
||||
)}
|
||||
<div className="time-machine--vis">
|
||||
{this.shouldShowTable && (
|
||||
<TimeMachineTable {...this.selectedResult} />
|
||||
)}
|
||||
{!this.hasResults && <NoResults />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get data(): string[] {
|
||||
const {data} = this.props
|
||||
if (!data) {
|
||||
return ['Your query was syntactically correct but returned no data']
|
||||
}
|
||||
private get initialResultID(): string {
|
||||
return _.get(this.props.data, '0.id', null)
|
||||
}
|
||||
|
||||
return this.props.data.split('\n')
|
||||
private handleSelectResult = (selectedResultID: string): void => {
|
||||
this.setState({selectedResultID})
|
||||
}
|
||||
|
||||
private get style(): CSSProperties {
|
||||
return {
|
||||
padding: `${HANDLE_PIXELS}px`,
|
||||
}
|
||||
}
|
||||
|
||||
private get hasResults(): boolean {
|
||||
return !!this.props.data.length
|
||||
}
|
||||
|
||||
private get shouldShowTable(): boolean {
|
||||
return !!this.props.data && !!this.selectedResult
|
||||
}
|
||||
|
||||
private get selectedResult(): ScriptResult {
|
||||
return this.props.data.find(d => d.id === this.state.selectedResultID)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,5 +3,6 @@ import * as editor from 'src/ifql/constants/editor'
|
|||
import * as argTypes from 'src/ifql/constants/argumentTypes'
|
||||
import * as funcNames from 'src/ifql/constants/funcNames'
|
||||
import * as builder from 'src/ifql/constants/builder'
|
||||
import * as vis from 'src/ifql/constants/vis'
|
||||
|
||||
export {ast, funcNames, argTypes, editor, builder}
|
||||
export {ast, funcNames, argTypes, editor, builder, vis}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export const TABLE_ROW_HEIGHT = 30
|
||||
export const TIME_COLUMN_WIDTH = 170
|
|
@ -1,10 +1,17 @@
|
|||
import React, {PureComponent, ReactChildren} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {withRouter, WithRouterProps} from 'react-router'
|
||||
import {WithRouterProps} from 'react-router'
|
||||
|
||||
import {IFQLPage} from 'src/ifql'
|
||||
import IFQLOverlay from 'src/ifql/components/IFQLOverlay'
|
||||
import {OverlayContext} from 'src/shared/components/OverlayTechnology'
|
||||
import {Source, Service} from 'src/types'
|
||||
import {Source, Service, Notification} from 'src/types'
|
||||
import {Links} from 'src/types/ifql'
|
||||
import {notify as notifyAction} from 'src/shared/actions/notifications'
|
||||
import {
|
||||
updateScript as updateScriptAction,
|
||||
UpdateScript,
|
||||
} from 'src/ifql/actions'
|
||||
import * as a from 'src/shared/actions/overlayTechnology'
|
||||
import * as b from 'src/shared/actions/services'
|
||||
|
||||
|
@ -16,6 +23,10 @@ interface Props {
|
|||
children: ReactChildren
|
||||
showOverlay: a.ShowOverlay
|
||||
fetchServicesAsync: b.FetchServicesAsync
|
||||
notify: (message: Notification) => void
|
||||
updateScript: UpdateScript
|
||||
script: string
|
||||
links: Links
|
||||
}
|
||||
|
||||
export class CheckServices extends PureComponent<Props & WithRouterProps> {
|
||||
|
@ -36,7 +47,22 @@ export class CheckServices extends PureComponent<Props & WithRouterProps> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
return this.props.children
|
||||
const {services, sources, notify, updateScript, links, script} = this.props
|
||||
|
||||
if (!this.props.services.length) {
|
||||
return null // put loading spinner here
|
||||
}
|
||||
|
||||
return (
|
||||
<IFQLPage
|
||||
sources={sources}
|
||||
services={services}
|
||||
links={links}
|
||||
script={script}
|
||||
notify={notify}
|
||||
updateScript={updateScript}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private overlay() {
|
||||
|
@ -65,8 +91,17 @@ export class CheckServices extends PureComponent<Props & WithRouterProps> {
|
|||
const mdtp = {
|
||||
fetchServicesAsync: actions.fetchServicesAsync,
|
||||
showOverlay: actions.showOverlay,
|
||||
updateScript: updateScriptAction,
|
||||
notify: notifyAction,
|
||||
}
|
||||
|
||||
const mstp = ({sources, services}) => ({sources, services})
|
||||
const mstp = ({sources, services, links, script}) => {
|
||||
return {
|
||||
links: links.ifql,
|
||||
script,
|
||||
sources,
|
||||
services,
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mstp, mdtp)(withRouter(CheckServices))
|
||||
export default connect(mstp, mdtp)(CheckServices)
|
||||
|
|
|
@ -1,25 +1,22 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import _ from 'lodash'
|
||||
|
||||
import CheckServices from 'src/ifql/containers/CheckServices'
|
||||
import TimeMachine from 'src/ifql/components/TimeMachine'
|
||||
import IFQLHeader from 'src/ifql/components/IFQLHeader'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts'
|
||||
|
||||
import {notify as notifyAction} from 'src/shared/actions/notifications'
|
||||
import {analyzeSuccess} from 'src/shared/copy/notifications'
|
||||
import {
|
||||
updateScript as updateScriptAction,
|
||||
UpdateScript,
|
||||
} from 'src/ifql/actions'
|
||||
analyzeSuccess,
|
||||
ifqlTimeSeriesError,
|
||||
} from 'src/shared/copy/notifications'
|
||||
import {UpdateScript} from 'src/ifql/actions'
|
||||
|
||||
import {bodyNodes} from 'src/ifql/helpers'
|
||||
import {getSuggestions, getAST, getTimeSeries} from 'src/ifql/apis'
|
||||
import {funcNames, builder, argTypes} from 'src/ifql/constants'
|
||||
|
||||
import {Source, Service, Notification} from 'src/types'
|
||||
import {Source, Service, Notification, ScriptResult} from 'src/types'
|
||||
import {
|
||||
Suggestion,
|
||||
FlatBody,
|
||||
|
@ -28,6 +25,7 @@ import {
|
|||
Handlers,
|
||||
DeleteFuncNodeArgs,
|
||||
Func,
|
||||
ScriptStatus,
|
||||
} from 'src/types/ifql'
|
||||
|
||||
interface Status {
|
||||
|
@ -42,9 +40,6 @@ interface Props {
|
|||
notify: (message: Notification) => void
|
||||
script: string
|
||||
updateScript: UpdateScript
|
||||
params: {
|
||||
sourceID: string
|
||||
}
|
||||
}
|
||||
|
||||
interface Body extends FlatBody {
|
||||
|
@ -54,9 +49,9 @@ interface Body extends FlatBody {
|
|||
interface State {
|
||||
body: Body[]
|
||||
ast: object
|
||||
data: string
|
||||
data: ScriptResult[]
|
||||
status: ScriptStatus
|
||||
suggestions: Suggestion[]
|
||||
status: Status
|
||||
}
|
||||
|
||||
export const IFQLContext = React.createContext()
|
||||
|
@ -68,7 +63,7 @@ export class IFQLPage extends PureComponent<Props, State> {
|
|||
this.state = {
|
||||
body: [],
|
||||
ast: null,
|
||||
data: 'Hit "Get Data!" or Ctrl + Enter to run your script',
|
||||
data: [],
|
||||
suggestions: [],
|
||||
status: {
|
||||
type: 'none',
|
||||
|
@ -78,7 +73,7 @@ export class IFQLPage extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
const {links, script} = this.props
|
||||
const {links} = this.props
|
||||
|
||||
try {
|
||||
const suggestions = await getSuggestions(links.suggestions)
|
||||
|
@ -87,7 +82,7 @@ export class IFQLPage extends PureComponent<Props, State> {
|
|||
console.error('Could not get function suggestions: ', error)
|
||||
}
|
||||
|
||||
this.getASTResponse(script)
|
||||
this.getTimeSeries()
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
@ -95,27 +90,25 @@ export class IFQLPage extends PureComponent<Props, State> {
|
|||
const {script} = this.props
|
||||
|
||||
return (
|
||||
<CheckServices>
|
||||
<IFQLContext.Provider value={this.handlers}>
|
||||
<KeyboardShortcuts onControlEnter={this.getTimeSeries}>
|
||||
<div className="page hosts-list-page">
|
||||
{this.header}
|
||||
<TimeMachine
|
||||
data={data}
|
||||
body={body}
|
||||
script={script}
|
||||
status={status}
|
||||
suggestions={suggestions}
|
||||
onAnalyze={this.handleAnalyze}
|
||||
onAppendFrom={this.handleAppendFrom}
|
||||
onAppendJoin={this.handleAppendJoin}
|
||||
onChangeScript={this.handleChangeScript}
|
||||
onSubmitScript={this.handleSubmitScript}
|
||||
/>
|
||||
</div>
|
||||
</KeyboardShortcuts>
|
||||
</IFQLContext.Provider>
|
||||
</CheckServices>
|
||||
<IFQLContext.Provider value={this.handlers}>
|
||||
<KeyboardShortcuts onControlEnter={this.getTimeSeries}>
|
||||
<div className="page hosts-list-page">
|
||||
{this.header}
|
||||
<TimeMachine
|
||||
data={data}
|
||||
body={body}
|
||||
script={script}
|
||||
status={status}
|
||||
suggestions={suggestions}
|
||||
onAnalyze={this.handleAnalyze}
|
||||
onAppendFrom={this.handleAppendFrom}
|
||||
onAppendJoin={this.handleAppendJoin}
|
||||
onChangeScript={this.handleChangeScript}
|
||||
onSubmitScript={this.handleSubmitScript}
|
||||
/>
|
||||
</div>
|
||||
</KeyboardShortcuts>
|
||||
</IFQLContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -401,6 +394,10 @@ export class IFQLPage extends PureComponent<Props, State> {
|
|||
private getASTResponse = async (script: string) => {
|
||||
const {links} = this.props
|
||||
|
||||
if (!script) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const ast = await getAST({url: links.ast, body: script})
|
||||
const suggestions = this.state.suggestions.map(s => {
|
||||
|
@ -427,18 +424,28 @@ export class IFQLPage extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
private getTimeSeries = async () => {
|
||||
const {script} = this.props
|
||||
this.setState({data: 'fetching data...'})
|
||||
const {script, links, notify} = this.props
|
||||
|
||||
try {
|
||||
const {data} = await getTimeSeries(this.service, script)
|
||||
this.setState({data})
|
||||
} catch (error) {
|
||||
this.setState({data: error})
|
||||
console.error('Could not get timeSeries', error)
|
||||
if (!script) {
|
||||
return
|
||||
}
|
||||
|
||||
this.getASTResponse(script)
|
||||
try {
|
||||
await getAST({url: links.ast, body: script})
|
||||
} catch (error) {
|
||||
this.setState({status: this.parseError(error)})
|
||||
return console.error('Could not parse AST', error)
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await getTimeSeries(this.service, script)
|
||||
this.setState({data})
|
||||
} catch (error) {
|
||||
this.setState({data: []})
|
||||
|
||||
notify(ifqlTimeSeriesError(error))
|
||||
console.error('Could not get timeSeries', error)
|
||||
}
|
||||
}
|
||||
|
||||
private parseError = (error): Status => {
|
||||
|
@ -448,13 +455,4 @@ export class IFQLPage extends PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({links, services, sources, script}) => {
|
||||
return {links: links.ifql, services, sources, script}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
notify: notifyAction,
|
||||
updateScript: updateScriptAction,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(IFQLPage)
|
||||
export default IFQLPage
|
||||
|
|
|
@ -36,7 +36,7 @@ import {
|
|||
} from 'src/kapacitor'
|
||||
import {AdminChronografPage, AdminInfluxDBPage} from 'src/admin'
|
||||
import {SourcePage, ManageSources} from 'src/sources'
|
||||
import {IFQLPage} from 'src/ifql'
|
||||
import {CheckServices} from 'src/ifql'
|
||||
import NotFound from 'src/shared/components/NotFound'
|
||||
|
||||
import {getLinksAsync} from 'src/shared/actions/links'
|
||||
|
@ -158,7 +158,7 @@ class Root extends PureComponent<{}, State> {
|
|||
<Route path="manage-sources" component={ManageSources} />
|
||||
<Route path="manage-sources/new" component={SourcePage} />
|
||||
<Route path="manage-sources/:id/edit" component={SourcePage} />
|
||||
<Route path="delorean" component={IFQLPage} />
|
||||
<Route path="delorean" component={CheckServices} />
|
||||
</Route>
|
||||
</Route>
|
||||
<Route path="*" component={NotFound} />
|
||||
|
|
|
@ -640,3 +640,8 @@ export const ifqlUpdated = {
|
|||
...defaultSuccessNotification,
|
||||
message: 'Connection Updated. Rejoice!',
|
||||
}
|
||||
|
||||
export const ifqlTimeSeriesError = (message: string) => ({
|
||||
...defaultErrorNotification,
|
||||
message: `Could not get data: ${message}`,
|
||||
})
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import Papa from 'papaparse'
|
||||
import _ from 'lodash'
|
||||
import uuid from 'uuid'
|
||||
|
||||
import {ScriptResult} from 'src/types'
|
||||
|
||||
export const parseResults = (response: string): ScriptResult[] => {
|
||||
const trimmedReponse = response.trim()
|
||||
|
||||
if (_.isEmpty(trimmedReponse)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return trimmedReponse.split(/\n\s*\n/).map(parseResult)
|
||||
}
|
||||
|
||||
export const parseResult = (raw: string, index: number): ScriptResult => {
|
||||
const lines = raw.split('\n')
|
||||
const rawMetadata: string = lines
|
||||
.filter(line => line.startsWith('#'))
|
||||
.map(line => line.slice(1))
|
||||
.join('\n')
|
||||
const rawData: string = lines.filter(line => !line.startsWith('#')).join('\n')
|
||||
|
||||
const metadata = Papa.parse(rawMetadata).data
|
||||
const data = Papa.parse(rawData).data
|
||||
|
||||
const headerRow = _.get(data, '0', [])
|
||||
const measurementHeaderIndex = headerRow.findIndex(v => v === '_measurement')
|
||||
const name = _.get(data, `1.${measurementHeaderIndex}`, `Result ${index}`)
|
||||
|
||||
return {
|
||||
id: uuid.v4(),
|
||||
name,
|
||||
data,
|
||||
metadata,
|
||||
}
|
||||
}
|
|
@ -13,15 +13,25 @@
|
|||
@include gradient-v($g2-kevlar, $g0-obsidian);
|
||||
}
|
||||
|
||||
.time-machine--graph {
|
||||
width: calc(100% - 60px);
|
||||
height: calc(100% - 60px);
|
||||
.time-machine--sidebar,
|
||||
.time-machine--vis {
|
||||
background-color: $g3-castle;
|
||||
border-radius: $radius;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
flex-wrap: nowrap;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.time-machine--sidebar {
|
||||
flex-basis: 25%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.time-machine--vis {
|
||||
flex-basis: 75%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.time-machine--graph-header {
|
||||
|
@ -34,7 +44,6 @@
|
|||
|
||||
.time-machine--graph-header .nav.nav-tablist {
|
||||
width: 180px;
|
||||
|
||||
li {
|
||||
justify-content: center;
|
||||
flex: 1 0 0;
|
||||
|
|
|
@ -10,7 +10,7 @@ export type OnGenerateScript = (script: string) => void
|
|||
export type OnChangeScript = (script: string) => void
|
||||
export type OnSubmitScript = () => void
|
||||
|
||||
export interface Status {
|
||||
export interface ScriptStatus {
|
||||
type: string
|
||||
text: string
|
||||
}
|
||||
|
@ -110,3 +110,12 @@ export interface Links {
|
|||
suggestions: string
|
||||
ast: string
|
||||
}
|
||||
|
||||
// ScriptResult is the result of a request to IFQL
|
||||
// https://github.com/influxdata/platform/blob/master/query/docs/SPEC.md#response-format
|
||||
export interface ScriptResult {
|
||||
id: string
|
||||
name: string
|
||||
data: string[][]
|
||||
metadata: string[][]
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import {LayoutCell, LayoutQuery} from './layouts'
|
||||
import {Service, NewService} from './services'
|
||||
import {AuthLinks, Organization, Role, User, Me} from './auth'
|
||||
import {Template, Cell, CellQuery, Legend, Axes} from './dashboard'
|
||||
|
@ -21,6 +22,7 @@ import {AlertRule, Kapacitor, Task} from './kapacitor'
|
|||
import {Source, SourceLinks} from './sources'
|
||||
import {DropdownAction, DropdownItem} from './shared'
|
||||
import {Notification, NotificationFunc} from './notifications'
|
||||
import {ScriptResult, ScriptStatus} from './ifql'
|
||||
|
||||
export {
|
||||
Me,
|
||||
|
@ -58,4 +60,8 @@ export {
|
|||
Axes,
|
||||
Service,
|
||||
NewService,
|
||||
LayoutCell,
|
||||
LayoutQuery,
|
||||
ScriptResult,
|
||||
ScriptStatus,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
export interface LayoutCell {
|
||||
x: number
|
||||
y: number
|
||||
w: number
|
||||
h: number
|
||||
i: string
|
||||
name: string
|
||||
queries: LayoutQuery[]
|
||||
}
|
||||
|
||||
export interface LayoutQuery {
|
||||
query: string
|
||||
label: string
|
||||
wheres: string[]
|
||||
groupbys: string[]
|
||||
}
|
|
@ -1,18 +1,23 @@
|
|||
import {buildQuery} from 'utils/influxql'
|
||||
import {buildQuery} from 'src/utils/influxql'
|
||||
import {TYPE_SHIFTED, TYPE_QUERY_CONFIG} from 'src/dashboards/constants'
|
||||
import {
|
||||
TEMP_VAR_DASHBOARD_TIME,
|
||||
TEMP_VAR_UPPER_DASHBOARD_TIME,
|
||||
} from 'src/shared/constants'
|
||||
import {timeRanges} from 'shared/data/timeRanges'
|
||||
import {timeRanges} from 'src/shared/data/timeRanges'
|
||||
import {Source, LayoutQuery, TimeRange} from 'src/types'
|
||||
|
||||
const buildCannedDashboardQuery = (query, {lower, upper}, host) => {
|
||||
const buildCannedDashboardQuery = (
|
||||
query: LayoutQuery,
|
||||
{lower, upper}: TimeRange,
|
||||
host: string
|
||||
): string => {
|
||||
const {defaultGroupBy} = timeRanges.find(range => range.lower === lower) || {
|
||||
defaultGroupBy: '5m',
|
||||
}
|
||||
const {wheres, groupbys} = query
|
||||
|
||||
let text = query.text
|
||||
let text = query.query
|
||||
|
||||
if (upper) {
|
||||
text += ` where time > '${lower}' AND time < '${upper}'`
|
||||
|
@ -43,7 +48,12 @@ const buildCannedDashboardQuery = (query, {lower, upper}, host) => {
|
|||
return text
|
||||
}
|
||||
|
||||
export const buildQueriesForLayouts = (cell, source, timeRange, host) => {
|
||||
export const buildQueriesForLayouts = (
|
||||
cell,
|
||||
source: Source,
|
||||
timeRange: TimeRange,
|
||||
host: string
|
||||
) => {
|
||||
return cell.queries.map(query => {
|
||||
let queryText
|
||||
// Canned dashboards use an different a schema different from queryConfig.
|
|
@ -6,14 +6,14 @@ const setup = () => {
|
|||
const props = {
|
||||
script: '',
|
||||
body: [],
|
||||
data: '',
|
||||
status: {type: '', text: ''},
|
||||
data: [],
|
||||
suggestions: [],
|
||||
onSubmitScript: () => {},
|
||||
onChangeScript: () => {},
|
||||
onAnalyze: () => {},
|
||||
onAppendFrom: () => {},
|
||||
onAppendJoin: () => {},
|
||||
status: {type: '', text: ''},
|
||||
}
|
||||
|
||||
const wrapper = shallow(<TimeMachine {...props} />)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,53 @@
|
|||
import {parseResults} from 'src/shared/parsing/ifql'
|
||||
import {
|
||||
RESPONSE_NO_METADATA,
|
||||
RESPONSE_METADATA,
|
||||
RESPONSE_NO_MEASUREMENT,
|
||||
LARGE_RESPONSE,
|
||||
EXPECTED_METADATA,
|
||||
EXPECTED_COLUMNS,
|
||||
} from 'test/shared/parsing/constants'
|
||||
|
||||
describe('IFQL response parser', () => {
|
||||
it('parseResults into the right number of tables', () => {
|
||||
const result = parseResults(LARGE_RESPONSE)
|
||||
|
||||
expect(result).toHaveLength(47)
|
||||
})
|
||||
|
||||
describe('headers', () => {
|
||||
it('can parse headers when no metadata is present', () => {
|
||||
const actual = parseResults(RESPONSE_NO_METADATA)[0].data[0]
|
||||
|
||||
expect(actual).toEqual(EXPECTED_COLUMNS)
|
||||
})
|
||||
|
||||
it('can parse headers when metadata is present', () => {
|
||||
const actual = parseResults(RESPONSE_METADATA)[0].data[0]
|
||||
|
||||
expect(actual).toEqual(EXPECTED_COLUMNS)
|
||||
})
|
||||
|
||||
it('returns the approriate metadata', () => {
|
||||
const actual = parseResults(RESPONSE_METADATA)[0].metadata
|
||||
|
||||
expect(actual).toEqual(EXPECTED_METADATA)
|
||||
})
|
||||
})
|
||||
|
||||
describe('name', () => {
|
||||
it('uses the measurement as a name when present', () => {
|
||||
const actual = parseResults(RESPONSE_METADATA)[0].name
|
||||
const expected = 'cpu'
|
||||
|
||||
expect(actual).toBe(expected)
|
||||
})
|
||||
|
||||
it('uses the index as a name if a measurement column is not present', () => {
|
||||
const actual = parseResults(RESPONSE_NO_MEASUREMENT)[0].name
|
||||
const expected = 'Result 0'
|
||||
|
||||
expect(actual).toBe(expected)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -65,6 +65,10 @@
|
|||
version "9.6.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.6.tgz#439b91f9caf3983cad2eef1e11f6bedcbf9431d2"
|
||||
|
||||
"@types/papaparse@^4.1.34":
|
||||
version "4.1.34"
|
||||
resolved "https://registry.yarnpkg.com/@types/papaparse/-/papaparse-4.1.34.tgz#893628fbb70243313e46d1b962989c023184783b"
|
||||
|
||||
"@types/prop-types@*", "@types/prop-types@^15.5.2":
|
||||
version "15.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.2.tgz#3c6b8dceb2906cc87fe4358e809f9d20c8d59be1"
|
||||
|
@ -6297,6 +6301,10 @@ pako@~1.0.5:
|
|||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"
|
||||
|
||||
papaparse@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-4.4.0.tgz#6bcdbda80873e00cfb0bdcd7a4571c72a9a40168"
|
||||
|
||||
parallel-transform@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06"
|
||||
|
|
Loading…
Reference in New Issue