Merge branch 'master' into table/field-options
commit
9cbfb58fde
|
@ -11,6 +11,7 @@
|
||||||
1. [#3215](https://github.com/influxdata/chronograf/pull/3215): Fix Template Variables Control Bar to top of dashboard page
|
1. [#3215](https://github.com/influxdata/chronograf/pull/3215): Fix Template Variables Control Bar to top of dashboard page
|
||||||
1. [#3214](https://github.com/influxdata/chronograf/pull/3214): Remove extra click when creating dashboard cell
|
1. [#3214](https://github.com/influxdata/chronograf/pull/3214): Remove extra click when creating dashboard cell
|
||||||
1. [#3256](https://github.com/influxdata/chronograf/pull/3256): Reduce font sizes in dashboards for increased space efficiency
|
1. [#3256](https://github.com/influxdata/chronograf/pull/3256): Reduce font sizes in dashboards for increased space efficiency
|
||||||
|
1. [#3245](https://github.com/influxdata/chronograf/pull/3245): Display 'no results' on cells without results
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import {
|
|
||||||
isUserAuthorized,
|
|
||||||
ADMIN_ROLE,
|
|
||||||
SUPERADMIN_ROLE,
|
|
||||||
} from 'src/auth/Authorized'
|
|
||||||
|
|
||||||
import {Tab, Tabs, TabPanel, TabPanels, TabList} from 'shared/components/Tabs'
|
|
||||||
import OrganizationsPage from 'src/admin/containers/chronograf/OrganizationsPage'
|
|
||||||
import UsersPage from 'src/admin/containers/chronograf/UsersPage'
|
|
||||||
import ProvidersPage from 'src/admin/containers/ProvidersPage'
|
|
||||||
import AllUsersPage from 'src/admin/containers/chronograf/AllUsersPage'
|
|
||||||
|
|
||||||
const ORGANIZATIONS_TAB_NAME = 'All Orgs'
|
|
||||||
const PROVIDERS_TAB_NAME = 'Org Mappings'
|
|
||||||
const CURRENT_ORG_USERS_TAB_NAME = 'Current Org'
|
|
||||||
const ALL_USERS_TAB_NAME = 'All Users'
|
|
||||||
|
|
||||||
const AdminTabs = ({
|
|
||||||
me: {currentOrganization: meCurrentOrganization, role: meRole, id: meID},
|
|
||||||
}) => {
|
|
||||||
const tabs = [
|
|
||||||
{
|
|
||||||
requiredRole: ADMIN_ROLE,
|
|
||||||
type: CURRENT_ORG_USERS_TAB_NAME,
|
|
||||||
component: (
|
|
||||||
<UsersPage meID={meID} meCurrentOrganization={meCurrentOrganization} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
requiredRole: SUPERADMIN_ROLE,
|
|
||||||
type: ALL_USERS_TAB_NAME,
|
|
||||||
component: <AllUsersPage meID={meID} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
requiredRole: SUPERADMIN_ROLE,
|
|
||||||
type: ORGANIZATIONS_TAB_NAME,
|
|
||||||
component: (
|
|
||||||
<OrganizationsPage meCurrentOrganization={meCurrentOrganization} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
requiredRole: SUPERADMIN_ROLE,
|
|
||||||
type: PROVIDERS_TAB_NAME,
|
|
||||||
component: <ProvidersPage />,
|
|
||||||
},
|
|
||||||
].filter(t => isUserAuthorized(meRole, t.requiredRole))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tabs className="row">
|
|
||||||
<TabList customClass="col-md-2 admin-tabs">
|
|
||||||
{tabs.map((t, i) => <Tab key={tabs[i].type}>{tabs[i].type}</Tab>)}
|
|
||||||
</TabList>
|
|
||||||
<TabPanels customClass="col-md-10 admin-tabs--content">
|
|
||||||
{tabs.map((t, i) => (
|
|
||||||
<TabPanel key={tabs[i].type}>{t.component}</TabPanel>
|
|
||||||
))}
|
|
||||||
</TabPanels>
|
|
||||||
</Tabs>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const {shape, string} = PropTypes
|
|
||||||
|
|
||||||
AdminTabs.propTypes = {
|
|
||||||
me: shape({
|
|
||||||
id: string.isRequired,
|
|
||||||
role: string.isRequired,
|
|
||||||
currentOrganization: shape({
|
|
||||||
name: string.isRequired,
|
|
||||||
id: string.isRequired,
|
|
||||||
}),
|
|
||||||
}).isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AdminTabs
|
|
|
@ -2,10 +2,52 @@ import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
|
|
||||||
import AdminTabs from 'src/admin/components/chronograf/AdminTabs'
|
import SubSections from 'src/shared/components/SubSections'
|
||||||
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
||||||
|
|
||||||
const AdminChronografPage = ({me}) => (
|
import UsersPage from 'src/admin/containers/chronograf/UsersPage'
|
||||||
|
import AllUsersPage from 'src/admin/containers/chronograf/AllUsersPage'
|
||||||
|
import OrganizationsPage from 'src/admin/containers/chronograf/OrganizationsPage'
|
||||||
|
import ProvidersPage from 'src/admin/containers/ProvidersPage'
|
||||||
|
|
||||||
|
import {
|
||||||
|
isUserAuthorized,
|
||||||
|
ADMIN_ROLE,
|
||||||
|
SUPERADMIN_ROLE,
|
||||||
|
} from 'src/auth/Authorized'
|
||||||
|
|
||||||
|
const sections = me => [
|
||||||
|
{
|
||||||
|
url: 'current-organization',
|
||||||
|
name: 'Current Org',
|
||||||
|
enabled: isUserAuthorized(me.role, ADMIN_ROLE),
|
||||||
|
component: (
|
||||||
|
<UsersPage meID={me.id} meCurrentOrganization={me.currentOrganization} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'all-users',
|
||||||
|
name: 'All Users',
|
||||||
|
enabled: isUserAuthorized(me.role, SUPERADMIN_ROLE),
|
||||||
|
component: <AllUsersPage meID={me.id} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'all-organizations',
|
||||||
|
name: 'All Orgs',
|
||||||
|
enabled: isUserAuthorized(me.role, SUPERADMIN_ROLE),
|
||||||
|
component: (
|
||||||
|
<OrganizationsPage meCurrentOrganization={me.currentOrganization} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'organization-mappings',
|
||||||
|
name: 'Org Mappings',
|
||||||
|
enabled: isUserAuthorized(me.role, SUPERADMIN_ROLE),
|
||||||
|
component: <ProvidersPage />,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const AdminChronografPage = ({me, source, params: {tab}}) => (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<div className="page-header">
|
<div className="page-header">
|
||||||
<div className="page-header__container">
|
<div className="page-header__container">
|
||||||
|
@ -16,9 +58,12 @@ const AdminChronografPage = ({me}) => (
|
||||||
</div>
|
</div>
|
||||||
<FancyScrollbar className="page-contents">
|
<FancyScrollbar className="page-contents">
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
<div className="row">
|
<SubSections
|
||||||
<AdminTabs me={me} />
|
sections={sections(me)}
|
||||||
</div>
|
activeSection={tab}
|
||||||
|
parentUrl="admin-chronograf"
|
||||||
|
sourceID={source.id}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</FancyScrollbar>
|
</FancyScrollbar>
|
||||||
</div>
|
</div>
|
||||||
|
@ -35,6 +80,15 @@ AdminChronografPage.propTypes = {
|
||||||
id: string.isRequired,
|
id: string.isRequired,
|
||||||
}),
|
}),
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
params: shape({
|
||||||
|
tab: string,
|
||||||
|
}).isRequired,
|
||||||
|
source: shape({
|
||||||
|
id: string.isRequired,
|
||||||
|
links: shape({
|
||||||
|
users: string.isRequired,
|
||||||
|
}),
|
||||||
|
}).isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = ({auth: {me}}) => ({
|
const mapStateToProps = ({auth: {me}}) => ({
|
||||||
|
|
|
@ -159,7 +159,7 @@ export const orderTableColumns = (data, fieldOptions) => {
|
||||||
})
|
})
|
||||||
const filteredFieldSortOrder = filter(fieldsSortOrder, f => f !== -1)
|
const filteredFieldSortOrder = filter(fieldsSortOrder, f => f !== -1)
|
||||||
const orderedData = map(data, row => {
|
const orderedData = map(data, row => {
|
||||||
return row.map((v, j, arr) => arr[filteredFieldSortOrder[j]] || v)
|
return row.map((__, j, arr) => arr[filteredFieldSortOrder[j]])
|
||||||
})
|
})
|
||||||
return orderedData[0].length ? orderedData : [[]]
|
return orderedData[0].length ? orderedData : [[]]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React, {PureComponent} from 'react'
|
import React, {PureComponent} from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
import {bindActionCreators} from 'redux'
|
import {bindActionCreators} from 'redux'
|
||||||
import {withRouter, InjectedRouter} from 'react-router'
|
import {withRouter, InjectedRouter} from 'react-router'
|
||||||
|
@ -52,15 +51,6 @@ interface State {
|
||||||
|
|
||||||
@ErrorHandling
|
@ErrorHandling
|
||||||
export class DataExplorer extends PureComponent<Props, State> {
|
export class DataExplorer extends PureComponent<Props, State> {
|
||||||
public static childContextTypes = {
|
|
||||||
source: PropTypes.shape({
|
|
||||||
links: PropTypes.shape({
|
|
||||||
proxy: PropTypes.string.isRequired,
|
|
||||||
self: PropTypes.string.isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
|
|
@ -21,25 +21,33 @@ class BodyBuilder extends PureComponent<Props> {
|
||||||
return b.declarations.map(d => {
|
return b.declarations.map(d => {
|
||||||
if (d.funcs) {
|
if (d.funcs) {
|
||||||
return (
|
return (
|
||||||
|
<div key={b.id}>
|
||||||
|
<div className="func-node--name">{d.name} =</div>
|
||||||
<ExpressionNode
|
<ExpressionNode
|
||||||
id={d.id}
|
key={b.id}
|
||||||
key={d.id}
|
bodyID={b.id}
|
||||||
|
declarationID={d.id}
|
||||||
funcNames={this.funcNames}
|
funcNames={this.funcNames}
|
||||||
funcs={d.funcs}
|
funcs={d.funcs}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div key={b.id}>{b.source}</div>
|
return (
|
||||||
|
<div className="func-node--name" key={b.id}>
|
||||||
|
{b.source}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExpressionNode
|
<ExpressionNode
|
||||||
id={b.id}
|
|
||||||
key={b.id}
|
key={b.id}
|
||||||
funcNames={this.funcNames}
|
bodyID={b.id}
|
||||||
funcs={b.funcs}
|
funcs={b.funcs}
|
||||||
|
funcNames={this.funcNames}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,36 +8,37 @@ import {Func} from 'src/types/ifql'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
funcNames: any[]
|
funcNames: any[]
|
||||||
id: string
|
bodyID: string
|
||||||
funcs: Func[]
|
funcs: Func[]
|
||||||
|
declarationID?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// an Expression is a group of one or more functions
|
// an Expression is a group of one or more functions
|
||||||
class ExpressionNode extends PureComponent<Props> {
|
class ExpressionNode extends PureComponent<Props> {
|
||||||
public render() {
|
public render() {
|
||||||
const {id, funcNames, funcs} = this.props
|
const {declarationID, bodyID, funcNames, funcs} = this.props
|
||||||
return (
|
return (
|
||||||
<IFQLContext.Consumer>
|
<IFQLContext.Consumer>
|
||||||
{({onDeleteFuncNode, onAddNode, onChangeArg, onGenerateScript}) => {
|
{({onDeleteFuncNode, onAddNode, onChangeArg, onGenerateScript}) => {
|
||||||
return (
|
return (
|
||||||
<div className="func-nodes-container">
|
<div className="func-nodes-container">
|
||||||
<h4>
|
|
||||||
<FuncSelector
|
|
||||||
expressionID={id}
|
|
||||||
funcs={funcNames}
|
|
||||||
onAddNode={onAddNode}
|
|
||||||
/>
|
|
||||||
</h4>
|
|
||||||
{funcs.map(func => (
|
{funcs.map(func => (
|
||||||
<FuncNode
|
<FuncNode
|
||||||
key={func.id}
|
key={func.id}
|
||||||
func={func}
|
func={func}
|
||||||
expressionID={func.id}
|
bodyID={bodyID}
|
||||||
onChangeArg={onChangeArg}
|
onChangeArg={onChangeArg}
|
||||||
onDelete={onDeleteFuncNode}
|
onDelete={onDeleteFuncNode}
|
||||||
|
declarationID={declarationID}
|
||||||
onGenerateScript={onGenerateScript}
|
onGenerateScript={onGenerateScript}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
<FuncSelector
|
||||||
|
bodyID={bodyID}
|
||||||
|
funcs={funcNames}
|
||||||
|
onAddNode={onAddNode}
|
||||||
|
declarationID={declarationID}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -9,7 +9,8 @@ interface Props {
|
||||||
funcID: string
|
funcID: string
|
||||||
argKey: string
|
argKey: string
|
||||||
value: string
|
value: string
|
||||||
expressionID: string
|
bodyID: string
|
||||||
|
declarationID: string
|
||||||
onChangeArg: OnChangeArg
|
onChangeArg: OnChangeArg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,12 +57,13 @@ class From extends PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleChooseDatabase = (item: DropdownItem): void => {
|
private handleChooseDatabase = (item: DropdownItem): void => {
|
||||||
const {argKey, funcID, onChangeArg, expressionID} = this.props
|
const {argKey, funcID, onChangeArg, bodyID, declarationID} = this.props
|
||||||
onChangeArg({
|
onChangeArg({
|
||||||
funcID,
|
funcID,
|
||||||
key: argKey,
|
key: argKey,
|
||||||
value: item.text,
|
value: item.text,
|
||||||
expressionID,
|
bodyID,
|
||||||
|
declarationID,
|
||||||
generate: true,
|
generate: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,8 @@ interface Props {
|
||||||
argKey: string
|
argKey: string
|
||||||
value: string | boolean
|
value: string | boolean
|
||||||
type: string
|
type: string
|
||||||
expressionID: string
|
bodyID: string
|
||||||
|
declarationID: string
|
||||||
onChangeArg: OnChangeArg
|
onChangeArg: OnChangeArg
|
||||||
onGenerateScript: () => void
|
onGenerateScript: () => void
|
||||||
}
|
}
|
||||||
|
@ -26,10 +27,11 @@ class FuncArg extends PureComponent<Props> {
|
||||||
argKey,
|
argKey,
|
||||||
value,
|
value,
|
||||||
type,
|
type,
|
||||||
funcName,
|
bodyID,
|
||||||
funcID,
|
funcID,
|
||||||
|
funcName,
|
||||||
onChangeArg,
|
onChangeArg,
|
||||||
expressionID,
|
declarationID,
|
||||||
onGenerateScript,
|
onGenerateScript,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
|
@ -39,7 +41,8 @@ class FuncArg extends PureComponent<Props> {
|
||||||
argKey={argKey}
|
argKey={argKey}
|
||||||
funcID={funcID}
|
funcID={funcID}
|
||||||
value={this.value}
|
value={this.value}
|
||||||
expressionID={expressionID}
|
bodyID={bodyID}
|
||||||
|
declarationID={declarationID}
|
||||||
onChangeArg={onChangeArg}
|
onChangeArg={onChangeArg}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -60,8 +63,9 @@ class FuncArg extends PureComponent<Props> {
|
||||||
value={this.value}
|
value={this.value}
|
||||||
argKey={argKey}
|
argKey={argKey}
|
||||||
funcID={funcID}
|
funcID={funcID}
|
||||||
expressionID={expressionID}
|
bodyID={bodyID}
|
||||||
onChangeArg={onChangeArg}
|
onChangeArg={onChangeArg}
|
||||||
|
declarationID={declarationID}
|
||||||
onGenerateScript={onGenerateScript}
|
onGenerateScript={onGenerateScript}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -72,9 +76,10 @@ class FuncArg extends PureComponent<Props> {
|
||||||
<FuncArgBool
|
<FuncArgBool
|
||||||
value={this.boolValue}
|
value={this.boolValue}
|
||||||
argKey={argKey}
|
argKey={argKey}
|
||||||
|
bodyID={bodyID}
|
||||||
funcID={funcID}
|
funcID={funcID}
|
||||||
onChangeArg={onChangeArg}
|
onChangeArg={onChangeArg}
|
||||||
expressionID={expressionID}
|
declarationID={declarationID}
|
||||||
onGenerateScript={onGenerateScript}
|
onGenerateScript={onGenerateScript}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,7 +7,8 @@ interface Props {
|
||||||
argKey: string
|
argKey: string
|
||||||
value: boolean
|
value: boolean
|
||||||
funcID: string
|
funcID: string
|
||||||
expressionID: string
|
bodyID: string
|
||||||
|
declarationID: string
|
||||||
onChangeArg: OnChangeArg
|
onChangeArg: OnChangeArg
|
||||||
onGenerateScript: () => void
|
onGenerateScript: () => void
|
||||||
}
|
}
|
||||||
|
@ -23,8 +24,15 @@ class FuncArgBool extends PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleToggle = (value: boolean): void => {
|
private handleToggle = (value: boolean): void => {
|
||||||
const {argKey, funcID, expressionID, onChangeArg} = this.props
|
const {argKey, funcID, bodyID, onChangeArg, declarationID} = this.props
|
||||||
onChangeArg({funcID, key: argKey, value, generate: true, expressionID})
|
onChangeArg({
|
||||||
|
key: argKey,
|
||||||
|
value,
|
||||||
|
funcID,
|
||||||
|
bodyID,
|
||||||
|
declarationID,
|
||||||
|
generate: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@ interface Props {
|
||||||
argKey: string
|
argKey: string
|
||||||
value: string
|
value: string
|
||||||
type: string
|
type: string
|
||||||
expressionID: string
|
bodyID: string
|
||||||
|
declarationID: string
|
||||||
onChangeArg: OnChangeArg
|
onChangeArg: OnChangeArg
|
||||||
onGenerateScript: () => void
|
onGenerateScript: () => void
|
||||||
}
|
}
|
||||||
|
@ -44,13 +45,14 @@ class FuncArgInput extends PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
private handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const {funcID, argKey, expressionID} = this.props
|
const {funcID, argKey, bodyID, declarationID} = this.props
|
||||||
|
|
||||||
this.props.onChangeArg({
|
this.props.onChangeArg({
|
||||||
funcID,
|
funcID,
|
||||||
key: argKey,
|
key: argKey,
|
||||||
value: e.target.value,
|
value: e.target.value,
|
||||||
expressionID,
|
declarationID,
|
||||||
|
bodyID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,22 @@ import {Func} from 'src/types/ifql'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
func: Func
|
func: Func
|
||||||
expressionID: string
|
bodyID: string
|
||||||
onChangeArg: OnChangeArg
|
onChangeArg: OnChangeArg
|
||||||
|
declarationID: string
|
||||||
onGenerateScript: () => void
|
onGenerateScript: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ErrorHandling
|
@ErrorHandling
|
||||||
export default class FuncArgs extends PureComponent<Props> {
|
export default class FuncArgs extends PureComponent<Props> {
|
||||||
public render() {
|
public render() {
|
||||||
const {expressionID, func, onChangeArg, onGenerateScript} = this.props
|
const {
|
||||||
|
func,
|
||||||
|
bodyID,
|
||||||
|
onChangeArg,
|
||||||
|
declarationID,
|
||||||
|
onGenerateScript,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="func-args">
|
<div className="func-args">
|
||||||
|
@ -25,10 +32,11 @@ export default class FuncArgs extends PureComponent<Props> {
|
||||||
type={type}
|
type={type}
|
||||||
argKey={key}
|
argKey={key}
|
||||||
value={value}
|
value={value}
|
||||||
|
bodyID={bodyID}
|
||||||
funcID={func.id}
|
funcID={func.id}
|
||||||
funcName={func.name}
|
funcName={func.name}
|
||||||
onChangeArg={onChangeArg}
|
onChangeArg={onChangeArg}
|
||||||
expressionID={expressionID}
|
declarationID={declarationID}
|
||||||
onGenerateScript={onGenerateScript}
|
onGenerateScript={onGenerateScript}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import React, {PureComponent, MouseEvent} from 'react'
|
import React, {PureComponent, MouseEvent} from 'react'
|
||||||
import FuncArgs from 'src/ifql/components/FuncArgs'
|
import FuncArgs from 'src/ifql/components/FuncArgs'
|
||||||
import {OnChangeArg, Func} from 'src/types/ifql'
|
import {OnDeleteFuncNode, OnChangeArg, Func} from 'src/types/ifql'
|
||||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
func: Func
|
func: Func
|
||||||
expressionID: string
|
bodyID: string
|
||||||
onDelete: (funcID: string, expressionID: string) => void
|
declarationID?: string
|
||||||
|
onDelete: OnDeleteFuncNode
|
||||||
onChangeArg: OnChangeArg
|
onChangeArg: OnChangeArg
|
||||||
onGenerateScript: () => void
|
onGenerateScript: () => void
|
||||||
}
|
}
|
||||||
|
@ -17,6 +18,10 @@ interface State {
|
||||||
|
|
||||||
@ErrorHandling
|
@ErrorHandling
|
||||||
export default class FuncNode extends PureComponent<Props, State> {
|
export default class FuncNode extends PureComponent<Props, State> {
|
||||||
|
public static defaultProps: Partial<Props> = {
|
||||||
|
declarationID: '',
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
|
@ -25,7 +30,13 @@ export default class FuncNode extends PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {expressionID, func, onChangeArg, onGenerateScript} = this.props
|
const {
|
||||||
|
func,
|
||||||
|
bodyID,
|
||||||
|
onChangeArg,
|
||||||
|
declarationID,
|
||||||
|
onGenerateScript,
|
||||||
|
} = this.props
|
||||||
const {isOpen} = this.state
|
const {isOpen} = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -36,8 +47,9 @@ export default class FuncNode extends PureComponent<Props, State> {
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<FuncArgs
|
<FuncArgs
|
||||||
func={func}
|
func={func}
|
||||||
|
bodyID={bodyID}
|
||||||
onChangeArg={onChangeArg}
|
onChangeArg={onChangeArg}
|
||||||
expressionID={expressionID}
|
declarationID={declarationID}
|
||||||
onGenerateScript={onGenerateScript}
|
onGenerateScript={onGenerateScript}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -49,7 +61,9 @@ export default class FuncNode extends PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleDelete = (): void => {
|
private handleDelete = (): void => {
|
||||||
this.props.onDelete(this.props.func.id, this.props.expressionID)
|
const {func, bodyID, declarationID} = this.props
|
||||||
|
|
||||||
|
this.props.onDelete({funcID: func.id, bodyID, declarationID})
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleClick = (e: MouseEvent<HTMLElement>): void => {
|
private handleClick = (e: MouseEvent<HTMLElement>): void => {
|
||||||
|
|
|
@ -14,7 +14,8 @@ interface State {
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
funcs: string[]
|
funcs: string[]
|
||||||
expressionID: string
|
bodyID: string
|
||||||
|
declarationID: string
|
||||||
onAddNode: OnAddNode
|
onAddNode: OnAddNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,8 +66,9 @@ export class FuncSelector extends PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleAddNode = (name: string) => {
|
private handleAddNode = (name: string) => {
|
||||||
|
const {bodyID, declarationID} = this.props
|
||||||
this.handleCloseList()
|
this.handleCloseList()
|
||||||
this.props.onAddNode(name, this.props.expressionID)
|
this.props.onAddNode(name, bodyID, declarationID)
|
||||||
}
|
}
|
||||||
|
|
||||||
private get availableFuncs() {
|
private get availableFuncs() {
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import React, {PureComponent} from 'react'
|
import React, {PureComponent} from 'react'
|
||||||
|
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
import TimeMachine from 'src/ifql/components/TimeMachine'
|
import TimeMachine from 'src/ifql/components/TimeMachine'
|
||||||
import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts'
|
import KeyboardShortcuts from 'src/shared/components/KeyboardShortcuts'
|
||||||
import {Suggestion, FlatBody} from 'src/types/ifql'
|
import {Suggestion, FlatBody} from 'src/types/ifql'
|
||||||
import {InputArg, Handlers} from 'src/types/ifql'
|
import {InputArg, Handlers, DeleteFuncNodeArgs, Func} from 'src/types/ifql'
|
||||||
|
|
||||||
import {bodyNodes} from 'src/ifql/helpers'
|
import {bodyNodes} from 'src/ifql/helpers'
|
||||||
import {getSuggestions, getAST} from 'src/ifql/apis'
|
import {getSuggestions, getAST} from 'src/ifql/apis'
|
||||||
|
@ -44,7 +45,7 @@ export class IFQLPage extends PureComponent<Props, State> {
|
||||||
ast: null,
|
ast: null,
|
||||||
suggestions: [],
|
suggestions: [],
|
||||||
script:
|
script:
|
||||||
'foo = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\nfrom(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\n',
|
'baz = "baz"\n\nfoo = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\nbar = from(db: "telegraf")\n\t|> filter() \n\t|> range(start: -15m)\n\n',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +109,7 @@ export class IFQLPage extends PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleGenerateScript = (): void => {
|
private handleGenerateScript = (): void => {
|
||||||
this.getASTResponse(this.expressionsToScript)
|
this.getASTResponse(this.bodyToScript)
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleChangeArg = ({
|
private handleChangeArg = ({
|
||||||
|
@ -116,14 +117,52 @@ export class IFQLPage extends PureComponent<Props, State> {
|
||||||
value,
|
value,
|
||||||
generate,
|
generate,
|
||||||
funcID,
|
funcID,
|
||||||
expressionID,
|
declarationID = '',
|
||||||
|
bodyID,
|
||||||
}: InputArg): void => {
|
}: InputArg): void => {
|
||||||
const body = this.state.body.map(expression => {
|
const body = this.state.body.map(b => {
|
||||||
if (expression.id !== expressionID) {
|
if (b.id !== bodyID) {
|
||||||
return expression
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
const funcs = expression.funcs.map(f => {
|
if (declarationID) {
|
||||||
|
const declarations = b.declarations.map(d => {
|
||||||
|
if (d.id !== declarationID) {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
const functions = this.editFuncArgs({
|
||||||
|
funcs: d.funcs,
|
||||||
|
funcID,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {...d, funcs: functions}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {...b, declarations}
|
||||||
|
}
|
||||||
|
|
||||||
|
const funcs = this.editFuncArgs({
|
||||||
|
funcs: b.funcs,
|
||||||
|
funcID,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {...b, funcs}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setState({body}, () => {
|
||||||
|
if (generate) {
|
||||||
|
this.handleGenerateScript()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private editFuncArgs = ({funcs, funcID, key, value}): Func[] => {
|
||||||
|
return funcs.map(f => {
|
||||||
if (f.id !== funcID) {
|
if (f.id !== funcID) {
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
@ -138,20 +177,26 @@ export class IFQLPage extends PureComponent<Props, State> {
|
||||||
|
|
||||||
return {...f, args}
|
return {...f, args}
|
||||||
})
|
})
|
||||||
|
|
||||||
return {...expression, funcs}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.setState({body}, () => {
|
|
||||||
if (generate) {
|
|
||||||
this.handleGenerateScript()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private get expressionsToScript(): string {
|
private get bodyToScript(): string {
|
||||||
return this.state.body.reduce((acc, expression) => {
|
return this.state.body.reduce((acc, b) => {
|
||||||
return `${acc + this.funcsToScript(expression.funcs)}\n\n`
|
if (b.declarations.length) {
|
||||||
|
const declaration = _.get(b, 'declarations.0', false)
|
||||||
|
if (!declaration) {
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!declaration.funcs) {
|
||||||
|
return `${acc}${b.source}\n\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${acc}${declaration.name} = ${this.funcsToScript(
|
||||||
|
declaration.funcs
|
||||||
|
)}\n\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${acc}${this.funcsToScript(b.funcs)}\n\n`
|
||||||
}, '')
|
}, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,49 +228,102 @@ export class IFQLPage extends PureComponent<Props, State> {
|
||||||
this.setState({script})
|
this.setState({script})
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleAddNode = (name: string, expressionID: string): void => {
|
private handleAddNode = (
|
||||||
const script = this.state.body.reduce((acc, expression) => {
|
name: string,
|
||||||
if (expression.id === expressionID) {
|
bodyID: string,
|
||||||
const {funcs} = expression
|
declarationID: string
|
||||||
return `${acc}${this.funcsToScript(funcs)}\n\t|> ${name}()\n\n`
|
): void => {
|
||||||
|
const script = this.state.body.reduce((acc, body) => {
|
||||||
|
const {id, source, funcs} = body
|
||||||
|
|
||||||
|
if (id === bodyID) {
|
||||||
|
const declaration = body.declarations.find(d => d.id === declarationID)
|
||||||
|
if (declaration) {
|
||||||
|
return `${acc}${declaration.name} = ${this.appendFunc(
|
||||||
|
declaration.funcs,
|
||||||
|
name
|
||||||
|
)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc + expression.source
|
return `${acc}${this.appendFunc(funcs, name)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${acc}${this.formatSource(source)}`
|
||||||
}, '')
|
}, '')
|
||||||
|
|
||||||
this.getASTResponse(script)
|
this.getASTResponse(script)
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleDeleteFuncNode = (
|
private appendFunc = (funcs, name): string => {
|
||||||
funcID: string,
|
return `${this.funcsToScript(funcs)}\n\t|> ${name}()\n\n`
|
||||||
expressionID: string
|
|
||||||
): void => {
|
|
||||||
// TODO: export this and test functionality
|
|
||||||
const script = this.state.body
|
|
||||||
.map((expression, expressionIndex) => {
|
|
||||||
if (expression.id !== expressionID) {
|
|
||||||
return expression.source
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const funcs = expression.funcs.filter(f => f.id !== funcID)
|
private handleDeleteFuncNode = (ids: DeleteFuncNodeArgs): void => {
|
||||||
const source = funcs.reduce((acc, f, i) => {
|
const {funcID, declarationID = '', bodyID} = ids
|
||||||
|
|
||||||
|
const script = this.state.body
|
||||||
|
.map((body, bodyIndex) => {
|
||||||
|
if (body.id !== bodyID) {
|
||||||
|
return this.formatSource(body.source)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLast = bodyIndex === this.state.body.length - 1
|
||||||
|
|
||||||
|
if (declarationID) {
|
||||||
|
const declaration = body.declarations.find(
|
||||||
|
d => d.id === declarationID
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!declaration) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const functions = declaration.funcs.filter(f => f.id !== funcID)
|
||||||
|
const s = this.funcsToSource(functions)
|
||||||
|
return `${declaration.name} = ${this.formatLastSource(s, isLast)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const funcs = body.funcs.filter(f => f.id !== funcID)
|
||||||
|
const source = this.funcsToSource(funcs)
|
||||||
|
return this.formatLastSource(source, isLast)
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
|
||||||
|
this.getASTResponse(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatSource = (source: string): string => {
|
||||||
|
// currently a bug in the AST which does not add newlines to literal variable assignment bodies
|
||||||
|
if (!source.match(/\n\n/)) {
|
||||||
|
return `${source}\n\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${source}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// formats the last line of a body string to include two new lines
|
||||||
|
private formatLastSource = (source: string, isLast: boolean): string => {
|
||||||
|
if (isLast) {
|
||||||
|
return `${source}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently a bug in the AST which does not add newlines to literal variable assignment bodies
|
||||||
|
if (!source.match(/\n\n/)) {
|
||||||
|
return `${source}\n\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${source}\n\n`
|
||||||
|
}
|
||||||
|
|
||||||
|
// funcsToSource takes a list of funtion nodes and returns an ifql script
|
||||||
|
private funcsToSource = (funcs): string => {
|
||||||
|
return funcs.reduce((acc, f, i) => {
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
return `${f.source}`
|
return `${f.source}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${acc}\n\t${f.source}`
|
return `${acc}\n\t${f.source}`
|
||||||
}, '')
|
}, '')
|
||||||
|
|
||||||
const isLast = expressionIndex === this.state.body.length - 1
|
|
||||||
if (isLast) {
|
|
||||||
return `${source}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${source}\n\n`
|
|
||||||
})
|
|
||||||
.join('')
|
|
||||||
|
|
||||||
this.getASTResponse(script)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getASTResponse = async (script: string) => {
|
private getASTResponse = async (script: string) => {
|
||||||
|
|
|
@ -146,7 +146,10 @@ class Root extends PureComponent<{}, State> {
|
||||||
path="kapacitors/:id/edit:hash"
|
path="kapacitors/:id/edit:hash"
|
||||||
component={KapacitorPage}
|
component={KapacitorPage}
|
||||||
/>
|
/>
|
||||||
<Route path="admin-chronograf" component={AdminChronografPage} />
|
<Route
|
||||||
|
path="admin-chronograf/:tab"
|
||||||
|
component={AdminChronografPage}
|
||||||
|
/>
|
||||||
<Route path="admin-influxdb/:tab" component={AdminInfluxDBPage} />
|
<Route path="admin-influxdb/:tab" component={AdminInfluxDBPage} />
|
||||||
<Route path="manage-sources" component={ManageSources} />
|
<Route path="manage-sources" component={ManageSources} />
|
||||||
<Route path="manage-sources/new" component={SourcePage} />
|
<Route path="manage-sources/new" component={SourcePage} />
|
||||||
|
|
|
@ -170,7 +170,7 @@ class AlertTabs extends Component {
|
||||||
const showDeprecation = pagerDutyV1Enabled
|
const showDeprecation = pagerDutyV1Enabled
|
||||||
const pagerDutyDeprecationMessage = (
|
const pagerDutyDeprecationMessage = (
|
||||||
<div>
|
<div>
|
||||||
PagerDuty v2 is being{' '}
|
PagerDuty v1 is being{' '}
|
||||||
{
|
{
|
||||||
<a
|
<a
|
||||||
href="https://v2.developer.pagerduty.com/docs/v1-rest-api-decommissioning-faq"
|
href="https://v2.developer.pagerduty.com/docs/v1-rest-api-decommissioning-faq"
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
import _ from 'lodash'
|
||||||
|
import {fetchTimeSeriesAsync} from 'src/shared/actions/timeSeries'
|
||||||
|
import {removeUnselectedTemplateValues} from 'src/dashboards/constants'
|
||||||
|
|
||||||
|
import {intervalValuesPoints} from 'src/shared/constants'
|
||||||
|
|
||||||
|
interface TemplateQuery {
|
||||||
|
db: string
|
||||||
|
rp: string
|
||||||
|
influxql: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TemplateValue {
|
||||||
|
type: string
|
||||||
|
value: string
|
||||||
|
selected: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Template {
|
||||||
|
type: string
|
||||||
|
tempVar: string
|
||||||
|
query: TemplateQuery
|
||||||
|
values: TemplateValue[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Query {
|
||||||
|
host: string | string[]
|
||||||
|
text: string
|
||||||
|
database: string
|
||||||
|
db: string
|
||||||
|
rp: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchTimeSeries = async (
|
||||||
|
queries: Query[],
|
||||||
|
resolution: number,
|
||||||
|
templates: Template[],
|
||||||
|
editQueryStatus: () => void
|
||||||
|
) => {
|
||||||
|
const timeSeriesPromises = queries.map(query => {
|
||||||
|
const {host, database, rp} = query
|
||||||
|
// the key `database` was used upstream in HostPage.js, and since as of this writing
|
||||||
|
// the codebase has not been fully converted to TypeScript, it's not clear where else
|
||||||
|
// it may be used, but this slight modification is intended to allow for the use of
|
||||||
|
// `database` while moving over to `db` for consistency over time
|
||||||
|
const db = _.get(query, 'db', database)
|
||||||
|
|
||||||
|
const templatesWithIntervalVals = templates.map(temp => {
|
||||||
|
if (temp.tempVar === ':interval:') {
|
||||||
|
if (resolution) {
|
||||||
|
const values = temp.values.map(v => ({
|
||||||
|
...v,
|
||||||
|
value: `${_.toInteger(Number(resolution) / 3)}`,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return {...temp, values}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {...temp, values: intervalValuesPoints}
|
||||||
|
}
|
||||||
|
return temp
|
||||||
|
})
|
||||||
|
|
||||||
|
const tempVars = removeUnselectedTemplateValues(templatesWithIntervalVals)
|
||||||
|
|
||||||
|
const source = host
|
||||||
|
return fetchTimeSeriesAsync(
|
||||||
|
{source, db, rp, query, tempVars, resolution},
|
||||||
|
editQueryStatus
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.all(timeSeriesPromises)
|
||||||
|
}
|
|
@ -1,292 +0,0 @@
|
||||||
import React, {Component} from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import _ from 'lodash'
|
|
||||||
|
|
||||||
import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries'
|
|
||||||
import {removeUnselectedTemplateValues} from 'src/dashboards/constants'
|
|
||||||
import {intervalValuesPoints} from 'src/shared/constants'
|
|
||||||
import {getQueryConfig} from 'shared/apis'
|
|
||||||
|
|
||||||
const AutoRefresh = ComposedComponent => {
|
|
||||||
class wrapper extends Component {
|
|
||||||
constructor() {
|
|
||||||
super()
|
|
||||||
this.state = {
|
|
||||||
lastQuerySuccessful: true,
|
|
||||||
timeSeries: [],
|
|
||||||
resolution: null,
|
|
||||||
queryASTs: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async componentDidMount() {
|
|
||||||
const {queries, templates, autoRefresh, type} = this.props
|
|
||||||
this.executeQueries(queries, templates)
|
|
||||||
if (type === 'table') {
|
|
||||||
const queryASTs = await this.getQueryASTs(queries, templates)
|
|
||||||
this.setState({queryASTs})
|
|
||||||
}
|
|
||||||
if (autoRefresh) {
|
|
||||||
this.intervalID = setInterval(
|
|
||||||
() => this.executeQueries(queries, templates),
|
|
||||||
autoRefresh
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getQueryASTs = async (queries, templates) => {
|
|
||||||
return await Promise.all(
|
|
||||||
queries.map(async q => {
|
|
||||||
const host = _.isArray(q.host) ? q.host[0] : q.host
|
|
||||||
const url = host.replace('proxy', 'queries')
|
|
||||||
const text = q.text
|
|
||||||
const {data} = await getQueryConfig(url, [{query: text}], templates)
|
|
||||||
return data.queries[0].queryAST
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async componentWillReceiveProps(nextProps) {
|
|
||||||
const inViewDidUpdate = this.props.inView !== nextProps.inView
|
|
||||||
|
|
||||||
const queriesDidUpdate = this.queryDifference(
|
|
||||||
this.props.queries,
|
|
||||||
nextProps.queries
|
|
||||||
).length
|
|
||||||
|
|
||||||
const tempVarsDidUpdate = !_.isEqual(
|
|
||||||
this.props.templates,
|
|
||||||
nextProps.templates
|
|
||||||
)
|
|
||||||
|
|
||||||
const shouldRefetch =
|
|
||||||
queriesDidUpdate || tempVarsDidUpdate || inViewDidUpdate
|
|
||||||
|
|
||||||
if (shouldRefetch) {
|
|
||||||
if (this.props.type === 'table') {
|
|
||||||
const queryASTs = await this.getQueryASTs(
|
|
||||||
nextProps.queries,
|
|
||||||
nextProps.templates
|
|
||||||
)
|
|
||||||
this.setState({queryASTs})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.executeQueries(
|
|
||||||
nextProps.queries,
|
|
||||||
nextProps.templates,
|
|
||||||
nextProps.inView
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.autoRefresh !== nextProps.autoRefresh || shouldRefetch) {
|
|
||||||
clearInterval(this.intervalID)
|
|
||||||
|
|
||||||
if (nextProps.autoRefresh) {
|
|
||||||
this.intervalID = setInterval(
|
|
||||||
() =>
|
|
||||||
this.executeQueries(
|
|
||||||
nextProps.queries,
|
|
||||||
nextProps.templates,
|
|
||||||
nextProps.inView
|
|
||||||
),
|
|
||||||
nextProps.autoRefresh
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
queryDifference = (left, right) => {
|
|
||||||
const leftStrs = left.map(q => `${q.host}${q.text}`)
|
|
||||||
const rightStrs = right.map(q => `${q.host}${q.text}`)
|
|
||||||
return _.difference(
|
|
||||||
_.union(leftStrs, rightStrs),
|
|
||||||
_.intersection(leftStrs, rightStrs)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
executeQueries = async (
|
|
||||||
queries,
|
|
||||||
templates = [],
|
|
||||||
inView = this.props.inView
|
|
||||||
) => {
|
|
||||||
const {editQueryStatus, grabDataForDownload} = this.props
|
|
||||||
const {resolution} = this.state
|
|
||||||
if (!inView) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!queries.length) {
|
|
||||||
this.setState({timeSeries: []})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({isFetching: true})
|
|
||||||
|
|
||||||
const timeSeriesPromises = queries.map(query => {
|
|
||||||
const {host, database, rp} = query
|
|
||||||
// the key `database` was used upstream in HostPage.js, and since as of this writing
|
|
||||||
// the codebase has not been fully converted to TypeScript, it's not clear where else
|
|
||||||
// it may be used, but this slight modification is intended to allow for the use of
|
|
||||||
// `database` while moving over to `db` for consistency over time
|
|
||||||
const db = _.get(query, 'db', database)
|
|
||||||
|
|
||||||
const templatesWithIntervalVals = templates.map(temp => {
|
|
||||||
if (temp.tempVar === ':interval:') {
|
|
||||||
if (resolution) {
|
|
||||||
// resize event
|
|
||||||
return {
|
|
||||||
...temp,
|
|
||||||
values: temp.values.map(v => ({
|
|
||||||
...v,
|
|
||||||
value: `${_.toInteger(Number(resolution) / 3)}`,
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...temp,
|
|
||||||
values: intervalValuesPoints,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return temp
|
|
||||||
})
|
|
||||||
|
|
||||||
const tempVars = removeUnselectedTemplateValues(
|
|
||||||
templatesWithIntervalVals
|
|
||||||
)
|
|
||||||
return fetchTimeSeriesAsync(
|
|
||||||
{
|
|
||||||
source: host,
|
|
||||||
db,
|
|
||||||
rp,
|
|
||||||
query,
|
|
||||||
tempVars,
|
|
||||||
resolution,
|
|
||||||
},
|
|
||||||
editQueryStatus
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
const timeSeries = await Promise.all(timeSeriesPromises)
|
|
||||||
const newSeries = timeSeries.map(response => ({response}))
|
|
||||||
const lastQuerySuccessful = this._resultsForQuery(newSeries)
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
timeSeries: newSeries,
|
|
||||||
lastQuerySuccessful,
|
|
||||||
isFetching: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (grabDataForDownload) {
|
|
||||||
grabDataForDownload(timeSeries)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
clearInterval(this.intervalID)
|
|
||||||
this.intervalID = false
|
|
||||||
}
|
|
||||||
|
|
||||||
setResolution = resolution => {
|
|
||||||
this.setState({resolution})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {timeSeries, queryASTs} = this.state
|
|
||||||
if (this.state.isFetching && this.state.lastQuerySuccessful) {
|
|
||||||
return (
|
|
||||||
<ComposedComponent
|
|
||||||
{...this.props}
|
|
||||||
data={timeSeries}
|
|
||||||
setResolution={this.setResolution}
|
|
||||||
isFetchingInitially={false}
|
|
||||||
isRefreshing={true}
|
|
||||||
queryASTs={queryASTs}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ComposedComponent
|
|
||||||
{...this.props}
|
|
||||||
data={timeSeries}
|
|
||||||
setResolution={this.setResolution}
|
|
||||||
queryASTs={queryASTs}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
_resultsForQuery = data =>
|
|
||||||
data.length
|
|
||||||
? data.every(({response}) =>
|
|
||||||
_.get(response, 'results', []).every(
|
|
||||||
result =>
|
|
||||||
Object.keys(result).filter(k => k !== 'statement_id').length !==
|
|
||||||
0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper.defaultProps = {
|
|
||||||
inView: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
|
||||||
array,
|
|
||||||
arrayOf,
|
|
||||||
bool,
|
|
||||||
element,
|
|
||||||
func,
|
|
||||||
number,
|
|
||||||
oneOfType,
|
|
||||||
shape,
|
|
||||||
string,
|
|
||||||
} = PropTypes
|
|
||||||
|
|
||||||
wrapper.propTypes = {
|
|
||||||
type: string.isRequired,
|
|
||||||
children: element,
|
|
||||||
autoRefresh: number.isRequired,
|
|
||||||
inView: bool,
|
|
||||||
templates: arrayOf(
|
|
||||||
shape({
|
|
||||||
type: string.isRequired,
|
|
||||||
tempVar: string.isRequired,
|
|
||||||
query: shape({
|
|
||||||
db: string,
|
|
||||||
rp: string,
|
|
||||||
influxql: string,
|
|
||||||
}),
|
|
||||||
values: arrayOf(
|
|
||||||
shape({
|
|
||||||
type: string.isRequired,
|
|
||||||
value: string.isRequired,
|
|
||||||
selected: bool,
|
|
||||||
})
|
|
||||||
).isRequired,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
queries: arrayOf(
|
|
||||||
shape({
|
|
||||||
host: oneOfType([string, arrayOf(string)]),
|
|
||||||
text: string,
|
|
||||||
}).isRequired
|
|
||||||
).isRequired,
|
|
||||||
axes: shape({
|
|
||||||
bounds: shape({
|
|
||||||
y: array,
|
|
||||||
y2: array,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
editQueryStatus: func,
|
|
||||||
grabDataForDownload: func,
|
|
||||||
}
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AutoRefresh
|
|
|
@ -0,0 +1,288 @@
|
||||||
|
import React, {Component, ComponentClass} from 'react'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
import {getQueryConfig} from 'src/shared/apis'
|
||||||
|
import {fetchTimeSeries} from 'src/shared/apis/query'
|
||||||
|
import {DEFAULT_TIME_SERIES} from 'src/shared/constants/series'
|
||||||
|
import {TimeSeriesServerResponse, TimeSeriesResponse} from 'src/types/series'
|
||||||
|
|
||||||
|
interface Axes {
|
||||||
|
bounds: {
|
||||||
|
y: number[]
|
||||||
|
y2: number[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Query {
|
||||||
|
host: string | string[]
|
||||||
|
text: string
|
||||||
|
database: string
|
||||||
|
db: string
|
||||||
|
rp: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TemplateQuery {
|
||||||
|
db: string
|
||||||
|
rp: string
|
||||||
|
influxql: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TemplateValue {
|
||||||
|
type: string
|
||||||
|
value: string
|
||||||
|
selected: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Template {
|
||||||
|
type: string
|
||||||
|
tempVar: string
|
||||||
|
query: TemplateQuery
|
||||||
|
values: TemplateValue[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
type: string
|
||||||
|
autoRefresh: number
|
||||||
|
inView: boolean
|
||||||
|
templates: Template[]
|
||||||
|
queries: Query[]
|
||||||
|
axes: Axes
|
||||||
|
editQueryStatus: () => void
|
||||||
|
grabDataForDownload: (timeSeries: TimeSeriesServerResponse[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QueryAST {
|
||||||
|
groupBy?: {
|
||||||
|
tags: string[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
isFetching: boolean
|
||||||
|
isLastQuerySuccessful: boolean
|
||||||
|
timeSeries: TimeSeriesServerResponse[]
|
||||||
|
resolution: number | null
|
||||||
|
queryASTs?: QueryAST[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OriginalProps {
|
||||||
|
data: TimeSeriesServerResponse[]
|
||||||
|
setResolution: (resolution: number) => void
|
||||||
|
isFetchingInitially?: boolean
|
||||||
|
isRefreshing?: boolean
|
||||||
|
queryASTs?: QueryAST[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const AutoRefresh = (
|
||||||
|
ComposedComponent: ComponentClass<OriginalProps & Props>
|
||||||
|
) => {
|
||||||
|
class Wrapper extends Component<Props, State> {
|
||||||
|
public static defaultProps = {
|
||||||
|
inView: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
private intervalID: NodeJS.Timer | null
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
isFetching: false,
|
||||||
|
isLastQuerySuccessful: true,
|
||||||
|
timeSeries: DEFAULT_TIME_SERIES,
|
||||||
|
resolution: null,
|
||||||
|
queryASTs: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async componentDidMount() {
|
||||||
|
if (this.isTable) {
|
||||||
|
const queryASTs = await this.getQueryASTs()
|
||||||
|
this.setState({queryASTs})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startNewPolling()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async componentDidUpdate(prevProps: Props) {
|
||||||
|
if (!this.isPropsDifferent(prevProps)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isTable) {
|
||||||
|
const queryASTs = await this.getQueryASTs()
|
||||||
|
this.setState({queryASTs})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startNewPolling()
|
||||||
|
}
|
||||||
|
|
||||||
|
public executeQueries = async () => {
|
||||||
|
const {editQueryStatus, grabDataForDownload, inView, queries} = this.props
|
||||||
|
const {resolution} = this.state
|
||||||
|
|
||||||
|
if (!inView) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!queries.length) {
|
||||||
|
this.setState({timeSeries: DEFAULT_TIME_SERIES})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({isFetching: true})
|
||||||
|
const templates: Template[] = _.get(this.props, 'templates', [])
|
||||||
|
|
||||||
|
try {
|
||||||
|
const timeSeries = await fetchTimeSeries(
|
||||||
|
queries,
|
||||||
|
resolution,
|
||||||
|
templates,
|
||||||
|
editQueryStatus
|
||||||
|
)
|
||||||
|
const newSeries = timeSeries.map((response: TimeSeriesResponse) => ({
|
||||||
|
response,
|
||||||
|
}))
|
||||||
|
const isLastQuerySuccessful = this.hasResultsForQuery(newSeries)
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
timeSeries: newSeries,
|
||||||
|
isLastQuerySuccessful,
|
||||||
|
isFetching: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (grabDataForDownload) {
|
||||||
|
grabDataForDownload(newSeries)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentWillUnmount() {
|
||||||
|
this.clearInterval()
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const {
|
||||||
|
timeSeries,
|
||||||
|
queryASTs,
|
||||||
|
isFetching,
|
||||||
|
isLastQuerySuccessful,
|
||||||
|
} = this.state
|
||||||
|
|
||||||
|
const hasValues = _.some(timeSeries, s => {
|
||||||
|
const results = _.get(s, 'response.results', [])
|
||||||
|
const v = _.some(results, r => r.series)
|
||||||
|
return v
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!hasValues) {
|
||||||
|
return (
|
||||||
|
<div className="graph-empty">
|
||||||
|
<p>No Results</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFetching && isLastQuerySuccessful) {
|
||||||
|
return (
|
||||||
|
<ComposedComponent
|
||||||
|
{...this.props}
|
||||||
|
data={timeSeries}
|
||||||
|
setResolution={this.setResolution}
|
||||||
|
isFetchingInitially={false}
|
||||||
|
isRefreshing={true}
|
||||||
|
queryASTs={queryASTs}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ComposedComponent
|
||||||
|
{...this.props}
|
||||||
|
data={timeSeries}
|
||||||
|
setResolution={this.setResolution}
|
||||||
|
queryASTs={queryASTs}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private setResolution = resolution => {
|
||||||
|
if (resolution !== this.state.resolution) {
|
||||||
|
this.setState({resolution})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearInterval() {
|
||||||
|
if (!this.intervalID) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
clearInterval(this.intervalID)
|
||||||
|
this.intervalID = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private isPropsDifferent(nextProps: Props) {
|
||||||
|
return (
|
||||||
|
this.props.inView !== nextProps.inView ||
|
||||||
|
!!this.queryDifference(this.props.queries, nextProps.queries).length ||
|
||||||
|
!_.isEqual(this.props.templates, nextProps.templates) ||
|
||||||
|
this.props.autoRefresh !== nextProps.autoRefresh
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private startNewPolling() {
|
||||||
|
this.clearInterval()
|
||||||
|
|
||||||
|
const {autoRefresh} = this.props
|
||||||
|
|
||||||
|
this.executeQueries()
|
||||||
|
|
||||||
|
if (autoRefresh) {
|
||||||
|
this.intervalID = setInterval(this.executeQueries, autoRefresh)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private queryDifference = (left, right) => {
|
||||||
|
const mapper = q => `${q.host}${q.text}`
|
||||||
|
const leftStrs = left.map(mapper)
|
||||||
|
const rightStrs = right.map(mapper)
|
||||||
|
return _.difference(
|
||||||
|
_.union(leftStrs, rightStrs),
|
||||||
|
_.intersection(leftStrs, rightStrs)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get isTable(): boolean {
|
||||||
|
return this.props.type === 'table'
|
||||||
|
}
|
||||||
|
|
||||||
|
private getQueryASTs = async (): Promise<QueryAST[]> => {
|
||||||
|
const {queries, templates} = this.props
|
||||||
|
|
||||||
|
return await Promise.all(
|
||||||
|
queries.map(async q => {
|
||||||
|
const host = _.isArray(q.host) ? q.host[0] : q.host
|
||||||
|
const url = host.replace('proxy', 'queries')
|
||||||
|
const text = q.text
|
||||||
|
const {data} = await getQueryConfig(url, [{query: text}], templates)
|
||||||
|
return data.queries[0].queryAST
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private hasResultsForQuery = (data): boolean => {
|
||||||
|
if (!data.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
data.every(({resp}) =>
|
||||||
|
_.get(resp, 'results', []).every(r => Object.keys(r).length > 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AutoRefresh
|
|
@ -41,7 +41,7 @@ class AutoRefreshDropdown extends Component {
|
||||||
paused: +milliseconds === 0,
|
paused: +milliseconds === 0,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className={classnames('dropdown dropdown-160', {open: isOpen})}>
|
<div className={classnames('dropdown dropdown-120', {open: isOpen})}>
|
||||||
<div
|
<div
|
||||||
className="btn btn-sm btn-default dropdown-toggle"
|
className="btn btn-sm btn-default dropdown-toggle"
|
||||||
onClick={this.toggleMenu}
|
onClick={this.toggleMenu}
|
||||||
|
@ -56,7 +56,7 @@ class AutoRefreshDropdown extends Component {
|
||||||
<span className="caret" />
|
<span className="caret" />
|
||||||
</div>
|
</div>
|
||||||
<ul className="dropdown-menu">
|
<ul className="dropdown-menu">
|
||||||
<li className="dropdown-header">AutoRefresh Interval</li>
|
<li className="dropdown-header">AutoRefresh</li>
|
||||||
{autoRefreshItems.map(item => (
|
{autoRefreshItems.map(item => (
|
||||||
<li className="dropdown-item" key={item.menuOption}>
|
<li className="dropdown-item" key={item.menuOption}>
|
||||||
<a href="#" onClick={this.handleSelection(item.milliseconds)}>
|
<a href="#" onClick={this.handleSelection(item.milliseconds)}>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import _ from 'lodash'
|
||||||
import React, {PureComponent} from 'react'
|
import React, {PureComponent} from 'react'
|
||||||
import Dygraph from 'dygraphs'
|
import Dygraph from 'dygraphs'
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
|
@ -35,7 +36,7 @@ class Crosshair extends PureComponent<Props> {
|
||||||
private get isVisible() {
|
private get isVisible() {
|
||||||
const {hoverTime} = this.props
|
const {hoverTime} = this.props
|
||||||
|
|
||||||
return hoverTime !== 0
|
return hoverTime !== 0 && _.isFinite(hoverTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
private get crosshairLeft(): number {
|
private get crosshairLeft(): number {
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import React, {PureComponent} from 'react'
|
||||||
|
|
||||||
|
class InvalidData extends PureComponent<{}> {
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<div className="graph-empty">
|
||||||
|
<p>
|
||||||
|
The data returned from the query can't be visualized with this graph
|
||||||
|
type.<br />Try updating the query or selecting a different graph type.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InvalidData
|
|
@ -1,3 +1,4 @@
|
||||||
|
import _ from 'lodash'
|
||||||
import React, {Component} from 'react'
|
import React, {Component} from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import Dygraph from 'shared/components/Dygraph'
|
import Dygraph from 'shared/components/Dygraph'
|
||||||
|
@ -6,17 +7,34 @@ import SingleStat from 'src/shared/components/SingleStat'
|
||||||
import {timeSeriesToDygraph} from 'utils/timeSeriesTransformers'
|
import {timeSeriesToDygraph} from 'utils/timeSeriesTransformers'
|
||||||
|
|
||||||
import {colorsStringSchema} from 'shared/schemas'
|
import {colorsStringSchema} from 'shared/schemas'
|
||||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
import {ErrorHandlingWith} from 'src/shared/decorators/errors'
|
||||||
|
import InvalidData from 'src/shared/components/InvalidData'
|
||||||
|
|
||||||
@ErrorHandling
|
const validateTimeSeries = timeseries => {
|
||||||
|
return _.every(timeseries, r =>
|
||||||
|
_.every(
|
||||||
|
r,
|
||||||
|
(v, i) => (i === 0 && Date.parse(v)) || _.isNumber(v) || _.isNull(v)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ErrorHandlingWith(InvalidData)
|
||||||
class LineGraph extends Component {
|
class LineGraph extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
this.isValidData = true
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
const {data, isInDataExplorer} = this.props
|
const {data, isInDataExplorer} = this.props
|
||||||
|
this.parseTimeSeries(data, isInDataExplorer)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseTimeSeries(data, isInDataExplorer) {
|
||||||
this._timeSeries = timeSeriesToDygraph(data, isInDataExplorer)
|
this._timeSeries = timeSeriesToDygraph(data, isInDataExplorer)
|
||||||
|
this.isValidData = validateTimeSeries(
|
||||||
|
_.get(this._timeSeries, 'timeSeries', [])
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUpdate(nextProps) {
|
componentWillUpdate(nextProps) {
|
||||||
|
@ -25,14 +43,15 @@ class LineGraph extends Component {
|
||||||
data !== nextProps.data ||
|
data !== nextProps.data ||
|
||||||
activeQueryIndex !== nextProps.activeQueryIndex
|
activeQueryIndex !== nextProps.activeQueryIndex
|
||||||
) {
|
) {
|
||||||
this._timeSeries = timeSeriesToDygraph(
|
this.parseTimeSeries(nextProps.data, nextProps.isInDataExplorer)
|
||||||
nextProps.data,
|
|
||||||
nextProps.isInDataExplorer
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
if (!this.isValidData) {
|
||||||
|
return <InvalidData />
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
axes,
|
axes,
|
||||||
|
|
|
@ -65,6 +65,7 @@ const RefreshingGraph = ({
|
||||||
templates={templates}
|
templates={templates}
|
||||||
autoRefresh={autoRefresh}
|
autoRefresh={autoRefresh}
|
||||||
cellHeight={cellHeight}
|
cellHeight={cellHeight}
|
||||||
|
editQueryStatus={editQueryStatus}
|
||||||
prefix={prefix}
|
prefix={prefix}
|
||||||
suffix={suffix}
|
suffix={suffix}
|
||||||
inView={inView}
|
inView={inView}
|
||||||
|
@ -83,6 +84,7 @@ const RefreshingGraph = ({
|
||||||
autoRefresh={autoRefresh}
|
autoRefresh={autoRefresh}
|
||||||
cellHeight={cellHeight}
|
cellHeight={cellHeight}
|
||||||
resizerTopHeight={resizerTopHeight}
|
resizerTopHeight={resizerTopHeight}
|
||||||
|
editQueryStatus={editQueryStatus}
|
||||||
resizeCoords={resizeCoords}
|
resizeCoords={resizeCoords}
|
||||||
cellID={cellID}
|
cellID={cellID}
|
||||||
prefix={prefix}
|
prefix={prefix}
|
||||||
|
@ -110,6 +112,7 @@ const RefreshingGraph = ({
|
||||||
fieldOptions={fieldOptions}
|
fieldOptions={fieldOptions}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
decimalPlaces={decimalPlaces}
|
decimalPlaces={decimalPlaces}
|
||||||
|
editQueryStatus={editQueryStatus}
|
||||||
resizerTopHeight={resizerTopHeight}
|
resizerTopHeight={resizerTopHeight}
|
||||||
handleSetHoverTime={handleSetHoverTime}
|
handleSetHoverTime={handleSetHoverTime}
|
||||||
isInCEO={isInCEO}
|
isInCEO={isInCEO}
|
||||||
|
|
|
@ -191,14 +191,14 @@ class TableGraph extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClickFieldName = fieldName => () => {
|
handleClickFieldName = clickedFieldName => () => {
|
||||||
|
const {tableOptions, fieldOptions, timeFormat, decimalPlaces} = this.props
|
||||||
const {data, sort} = this.state
|
const {data, sort} = this.state
|
||||||
const {tableOptions, timeFormat, fieldOptions, decimalPlaces} = this.props
|
|
||||||
|
|
||||||
if (fieldName === sort.field) {
|
if (clickedFieldName === sort.field) {
|
||||||
sort.direction = sort.direction === ASCENDING ? DESCENDING : ASCENDING
|
sort.direction = sort.direction === ASCENDING ? DESCENDING : ASCENDING
|
||||||
} else {
|
} else {
|
||||||
sort.field = fieldName
|
sort.field = clickedFieldName
|
||||||
sort.direction = DEFAULT_SORT_DIRECTION
|
sort.direction = DEFAULT_SORT_DIRECTION
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,12 +430,12 @@ class TableGraph extends Component {
|
||||||
enableFixedRowScroll={true}
|
enableFixedRowScroll={true}
|
||||||
scrollToRow={scrollToRow}
|
scrollToRow={scrollToRow}
|
||||||
scrollToColumn={scrollToColumn}
|
scrollToColumn={scrollToColumn}
|
||||||
|
sort={sort}
|
||||||
cellRenderer={this.cellRenderer}
|
cellRenderer={this.cellRenderer}
|
||||||
hoveredColumnIndex={hoveredColumnIndex}
|
hoveredColumnIndex={hoveredColumnIndex}
|
||||||
hoveredRowIndex={hoveredRowIndex}
|
hoveredRowIndex={hoveredRowIndex}
|
||||||
hoverTime={hoverTime}
|
hoverTime={hoverTime}
|
||||||
colors={colors}
|
colors={colors}
|
||||||
sort={sort}
|
|
||||||
fieldOptions={fieldOptions}
|
fieldOptions={fieldOptions}
|
||||||
tableOptions={tableOptions}
|
tableOptions={tableOptions}
|
||||||
timeFormat={timeFormat}
|
timeFormat={timeFormat}
|
||||||
|
|
|
@ -85,7 +85,7 @@ class TimeRangeDropdown extends Component {
|
||||||
<div className="time-range-dropdown">
|
<div className="time-range-dropdown">
|
||||||
<div
|
<div
|
||||||
className={classnames('dropdown', {
|
className={classnames('dropdown', {
|
||||||
'dropdown-160': isRelativeTimeRange,
|
'dropdown-120': isRelativeTimeRange,
|
||||||
'dropdown-210': isNow,
|
'dropdown-210': isNow,
|
||||||
'dropdown-290': !isRelativeTimeRange && !isNow,
|
'dropdown-290': !isRelativeTimeRange && !isNow,
|
||||||
open: isOpen,
|
open: isOpen,
|
||||||
|
@ -109,7 +109,7 @@ class TimeRangeDropdown extends Component {
|
||||||
>
|
>
|
||||||
{preventCustomTimeRange ? null : (
|
{preventCustomTimeRange ? null : (
|
||||||
<div>
|
<div>
|
||||||
<li className="dropdown-header">Absolute Time Ranges</li>
|
<li className="dropdown-header">Absolute Time</li>
|
||||||
<li
|
<li
|
||||||
className={
|
className={
|
||||||
isCustomTimeRangeOpen
|
isCustomTimeRangeOpen
|
||||||
|
@ -118,13 +118,13 @@ class TimeRangeDropdown extends Component {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<a href="#" onClick={this.showCustomTimeRange}>
|
<a href="#" onClick={this.showCustomTimeRange}>
|
||||||
Custom Date Picker
|
Date Picker
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<li className="dropdown-header">
|
<li className="dropdown-header">
|
||||||
{preventCustomTimeRange ? '' : 'Relative '}Time Ranges
|
{preventCustomTimeRange ? '' : 'Relative '}Time
|
||||||
</li>
|
</li>
|
||||||
{timeRanges.map(item => {
|
{timeRanges.map(item => {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
export const DEFAULT_TIME_SERIES = [
|
||||||
|
{
|
||||||
|
response: {
|
||||||
|
results: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
|
@ -2,28 +2,28 @@ const autoRefreshItems = [
|
||||||
{milliseconds: 0, inputValue: 'Paused', menuOption: 'Paused'},
|
{milliseconds: 0, inputValue: 'Paused', menuOption: 'Paused'},
|
||||||
{
|
{
|
||||||
milliseconds: 5000,
|
milliseconds: 5000,
|
||||||
inputValue: 'Every 5 seconds',
|
inputValue: 'Every 5s',
|
||||||
menuOption: 'Every 5 seconds',
|
menuOption: 'Every 5s',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
milliseconds: 10000,
|
milliseconds: 10000,
|
||||||
inputValue: 'Every 10 seconds',
|
inputValue: 'Every 10s',
|
||||||
menuOption: 'Every 10 seconds',
|
menuOption: 'Every 10s',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
milliseconds: 15000,
|
milliseconds: 15000,
|
||||||
inputValue: 'Every 15 seconds',
|
inputValue: 'Every 15s',
|
||||||
menuOption: 'Every 15 seconds',
|
menuOption: 'Every 15s',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
milliseconds: 30000,
|
milliseconds: 30000,
|
||||||
inputValue: 'Every 30 seconds',
|
inputValue: 'Every 30s',
|
||||||
menuOption: 'Every 30 seconds',
|
menuOption: 'Every 30s',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
milliseconds: 60000,
|
milliseconds: 60000,
|
||||||
inputValue: 'Every 60 seconds',
|
inputValue: 'Every 60s',
|
||||||
menuOption: 'Every 60 seconds',
|
menuOption: 'Every 60s',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -2,73 +2,73 @@ export const timeRanges = [
|
||||||
{
|
{
|
||||||
defaultGroupBy: '10s',
|
defaultGroupBy: '10s',
|
||||||
seconds: 300,
|
seconds: 300,
|
||||||
inputValue: 'Past 5 minutes',
|
inputValue: 'Past 5m',
|
||||||
lower: 'now() - 5m',
|
lower: 'now() - 5m',
|
||||||
upper: null,
|
upper: null,
|
||||||
menuOption: 'Past 5 minutes',
|
menuOption: 'Past 5m',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
defaultGroupBy: '1m',
|
defaultGroupBy: '1m',
|
||||||
seconds: 900,
|
seconds: 900,
|
||||||
inputValue: 'Past 15 minutes',
|
inputValue: 'Past 15m',
|
||||||
lower: 'now() - 15m',
|
lower: 'now() - 15m',
|
||||||
upper: null,
|
upper: null,
|
||||||
menuOption: 'Past 15 minutes',
|
menuOption: 'Past 15m',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
defaultGroupBy: '1m',
|
defaultGroupBy: '1m',
|
||||||
seconds: 3600,
|
seconds: 3600,
|
||||||
inputValue: 'Past hour',
|
inputValue: 'Past 1h',
|
||||||
lower: 'now() - 1h',
|
lower: 'now() - 1h',
|
||||||
upper: null,
|
upper: null,
|
||||||
menuOption: 'Past hour',
|
menuOption: 'Past 1h',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
defaultGroupBy: '1m',
|
defaultGroupBy: '1m',
|
||||||
seconds: 21600,
|
seconds: 21600,
|
||||||
inputValue: 'Past 6 hours',
|
inputValue: 'Past 6h',
|
||||||
lower: 'now() - 6h',
|
lower: 'now() - 6h',
|
||||||
upper: null,
|
upper: null,
|
||||||
menuOption: 'Past 6 hours',
|
menuOption: 'Past 6h',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
defaultGroupBy: '5m',
|
defaultGroupBy: '5m',
|
||||||
seconds: 43200,
|
seconds: 43200,
|
||||||
inputValue: 'Past 12 hours',
|
inputValue: 'Past 12h',
|
||||||
lower: 'now() - 12h',
|
lower: 'now() - 12h',
|
||||||
upper: null,
|
upper: null,
|
||||||
menuOption: 'Past 12 hours',
|
menuOption: 'Past 12h',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
defaultGroupBy: '10m',
|
defaultGroupBy: '10m',
|
||||||
seconds: 86400,
|
seconds: 86400,
|
||||||
inputValue: 'Past 24 hours',
|
inputValue: 'Past 24h',
|
||||||
lower: 'now() - 24h',
|
lower: 'now() - 24h',
|
||||||
upper: null,
|
upper: null,
|
||||||
menuOption: 'Past 24 hours',
|
menuOption: 'Past 24h',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
defaultGroupBy: '30m',
|
defaultGroupBy: '30m',
|
||||||
seconds: 172800,
|
seconds: 172800,
|
||||||
inputValue: 'Past 2 days',
|
inputValue: 'Past 2d',
|
||||||
lower: 'now() - 2d',
|
lower: 'now() - 2d',
|
||||||
upper: null,
|
upper: null,
|
||||||
menuOption: 'Past 2 days',
|
menuOption: 'Past 2d',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
defaultGroupBy: '1h',
|
defaultGroupBy: '1h',
|
||||||
seconds: 604800,
|
seconds: 604800,
|
||||||
inputValue: 'Past 7 days',
|
inputValue: 'Past 7d',
|
||||||
lower: 'now() - 7d',
|
lower: 'now() - 7d',
|
||||||
upper: null,
|
upper: null,
|
||||||
menuOption: 'Past 7 days',
|
menuOption: 'Past 7d',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
defaultGroupBy: '6h',
|
defaultGroupBy: '6h',
|
||||||
seconds: 2592000,
|
seconds: 2592000,
|
||||||
inputValue: 'Past 30 days',
|
inputValue: 'Past 30d',
|
||||||
lower: 'now() - 30d',
|
lower: 'now() - 30d',
|
||||||
upper: null,
|
upper: null,
|
||||||
menuOption: 'Past 30 days',
|
menuOption: 'Past 30d',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -122,14 +122,16 @@ class SideNav extends PureComponent<Props> {
|
||||||
<NavBlock
|
<NavBlock
|
||||||
highlightWhen={['admin-chronograf', 'admin-influxdb']}
|
highlightWhen={['admin-chronograf', 'admin-influxdb']}
|
||||||
icon="crown2"
|
icon="crown2"
|
||||||
link={`${sourcePrefix}/admin-chronograf`}
|
link={`${sourcePrefix}/admin-chronograf/current-organization`}
|
||||||
location={location}
|
location={location}
|
||||||
>
|
>
|
||||||
<NavHeader
|
<NavHeader
|
||||||
link={`${sourcePrefix}/admin-chronograf`}
|
link={`${sourcePrefix}/admin-chronograf/current-organization`}
|
||||||
title="Admin"
|
title="Admin"
|
||||||
/>
|
/>
|
||||||
<NavListItem link={`${sourcePrefix}/admin-chronograf`}>
|
<NavListItem
|
||||||
|
link={`${sourcePrefix}/admin-chronograf/current-organization`}
|
||||||
|
>
|
||||||
Chronograf
|
Chronograf
|
||||||
</NavListItem>
|
</NavListItem>
|
||||||
<NavListItem link={`${sourcePrefix}/admin-influxdb/databases`}>
|
<NavListItem link={`${sourcePrefix}/admin-influxdb/databases`}>
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
width: auto;
|
width: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
color: $ix-text-default;
|
color: $ix-text-default;
|
||||||
text-transform: uppercase;
|
|
||||||
margin-bottom: $ix-marg-a;
|
margin-bottom: $ix-marg-a;
|
||||||
font-family: $ix-text-font;
|
font-family: $ix-text-font;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
// function definitions
|
// function definitions
|
||||||
export type OnDeleteFuncNode = (funcID: string, expressionID: string) => void
|
export type OnDeleteFuncNode = (ids: DeleteFuncNodeArgs) => void
|
||||||
export type OnChangeArg = (inputArg: InputArg) => void
|
export type OnChangeArg = (inputArg: InputArg) => void
|
||||||
export type OnAddNode = (expressionID: string, funcName: string) => void
|
export type OnAddNode = (
|
||||||
|
bodyID: string,
|
||||||
|
funcName: string,
|
||||||
|
declarationID: string
|
||||||
|
) => void
|
||||||
export type OnGenerateScript = (script: string) => void
|
export type OnGenerateScript = (script: string) => void
|
||||||
export type OnChangeScript = (script: string) => void
|
export type OnChangeScript = (script: string) => void
|
||||||
export type OnSubmitScript = () => void
|
export type OnSubmitScript = () => void
|
||||||
|
@ -15,9 +19,16 @@ export interface Handlers {
|
||||||
onGenerateScript: OnGenerateScript
|
onGenerateScript: OnGenerateScript
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeleteFuncNodeArgs {
|
||||||
|
funcID: string
|
||||||
|
bodyID: string
|
||||||
|
declarationID?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface InputArg {
|
export interface InputArg {
|
||||||
funcID: string
|
funcID: string
|
||||||
expressionID: string
|
bodyID: string
|
||||||
|
declarationID?: string
|
||||||
key: string
|
key: string
|
||||||
value: string | boolean
|
value: string | boolean
|
||||||
generate?: boolean
|
generate?: boolean
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
export type TimeSeriesValue = string | number | Date | null
|
||||||
|
|
||||||
|
export interface Series {
|
||||||
|
name: string
|
||||||
|
columns: string[]
|
||||||
|
values: TimeSeriesValue[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Result {
|
||||||
|
series: Series[]
|
||||||
|
statement_id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimeSeriesResponse {
|
||||||
|
results: Result[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TimeSeriesServerResponse {
|
||||||
|
response: TimeSeriesResponse
|
||||||
|
}
|
|
@ -2,8 +2,11 @@ import _ from 'lodash'
|
||||||
import {shiftDate} from 'shared/query/helpers'
|
import {shiftDate} from 'shared/query/helpers'
|
||||||
import {map, reduce, forEach, concat, clone} from 'fast.js'
|
import {map, reduce, forEach, concat, clone} from 'fast.js'
|
||||||
|
|
||||||
const groupByMap = (responses, responseIndex, groupByColumns) => {
|
const groupByMap = (results, responseIndex, groupByColumns) => {
|
||||||
const firstColumns = _.get(responses, [0, 'series', 0, 'columns'])
|
if (_.isEmpty(results)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const firstColumns = _.get(results, [0, 'series', 0, 'columns'])
|
||||||
const accum = [
|
const accum = [
|
||||||
{
|
{
|
||||||
responseIndex,
|
responseIndex,
|
||||||
|
@ -15,14 +18,14 @@ const groupByMap = (responses, responseIndex, groupByColumns) => {
|
||||||
...firstColumns.slice(1),
|
...firstColumns.slice(1),
|
||||||
],
|
],
|
||||||
groupByColumns,
|
groupByColumns,
|
||||||
name: _.get(responses, [0, 'series', 0, 'name']),
|
name: _.get(results, [0, 'series', 0, 'name'], ''),
|
||||||
values: [],
|
values: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const seriesArray = _.get(responses, [0, 'series'])
|
const seriesArray = _.get(results, [0, 'series'])
|
||||||
seriesArray.forEach(s => {
|
seriesArray.forEach(s => {
|
||||||
const prevValues = accum[0].series[0].values
|
const prevValues = accum[0].series[0].values
|
||||||
const tagsToAdd = groupByColumns.map(gb => s.tags[gb])
|
const tagsToAdd = groupByColumns.map(gb => s.tags[gb])
|
||||||
|
@ -35,13 +38,14 @@ const groupByMap = (responses, responseIndex, groupByColumns) => {
|
||||||
const constructResults = (raw, groupBys) => {
|
const constructResults = (raw, groupBys) => {
|
||||||
return _.flatten(
|
return _.flatten(
|
||||||
map(raw, (response, index) => {
|
map(raw, (response, index) => {
|
||||||
const responses = _.get(response, 'response.results', [])
|
const results = _.get(response, 'response.results', [])
|
||||||
|
|
||||||
|
const successfulResults = _.filter(results, r => _.isNil(r.error))
|
||||||
|
|
||||||
if (groupBys[index]) {
|
if (groupBys[index]) {
|
||||||
return groupByMap(responses, index, groupBys[index])
|
return groupByMap(successfulResults, index, groupBys[index])
|
||||||
}
|
}
|
||||||
|
return map(successfulResults, r => ({...r, responseIndex: index}))
|
||||||
return map(responses, r => ({...r, responseIndex: index}))
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -81,22 +85,24 @@ const constructCells = serieses => {
|
||||||
name: measurement,
|
name: measurement,
|
||||||
columns,
|
columns,
|
||||||
groupByColumns,
|
groupByColumns,
|
||||||
values,
|
values = [],
|
||||||
seriesIndex,
|
seriesIndex,
|
||||||
responseIndex,
|
responseIndex,
|
||||||
tags = {},
|
tags = {},
|
||||||
},
|
},
|
||||||
ind
|
ind
|
||||||
) => {
|
) => {
|
||||||
const rows = map(values || [], vals => ({
|
const rows = map(values, vals => ({vals}))
|
||||||
vals,
|
|
||||||
}))
|
const tagSet = map(Object.keys(tags), tag => `[${tag}=${tags[tag]}]`)
|
||||||
|
.sort()
|
||||||
|
.join('')
|
||||||
|
|
||||||
const unsortedLabels = map(columns.slice(1), (field, i) => ({
|
const unsortedLabels = map(columns.slice(1), (field, i) => ({
|
||||||
label:
|
label:
|
||||||
groupByColumns && i <= groupByColumns.length - 1
|
groupByColumns && i <= groupByColumns.length - 1
|
||||||
? `${field}`
|
? `${field}`
|
||||||
: `${measurement}.${field}`,
|
: `${measurement}.${field}${tagSet}`,
|
||||||
responseIndex,
|
responseIndex,
|
||||||
seriesIndex,
|
seriesIndex,
|
||||||
}))
|
}))
|
||||||
|
@ -221,8 +227,8 @@ export const groupByTimeSeriesTransform = (raw, groupBys) => {
|
||||||
if (!groupBys) {
|
if (!groupBys) {
|
||||||
groupBys = Array(raw.length).fill(false)
|
groupBys = Array(raw.length).fill(false)
|
||||||
}
|
}
|
||||||
const results = constructResults(raw, groupBys)
|
|
||||||
|
|
||||||
|
const results = constructResults(raw, groupBys)
|
||||||
const serieses = constructSerieses(results)
|
const serieses = constructSerieses(results)
|
||||||
|
|
||||||
const {cells, sortedLabels, seriesLabels} = constructCells(serieses)
|
const {cells, sortedLabels, seriesLabels} = constructCells(serieses)
|
||||||
|
|
|
@ -9,7 +9,8 @@ const setup = () => {
|
||||||
funcID: '1',
|
funcID: '1',
|
||||||
argKey: 'db',
|
argKey: 'db',
|
||||||
value: 'db1',
|
value: 'db1',
|
||||||
expressionID: '2',
|
bodyID: '2',
|
||||||
|
declarationID: '1',
|
||||||
onChangeArg: () => {},
|
onChangeArg: () => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,9 @@ import FuncArg from 'src/ifql/components/FuncArg'
|
||||||
const setup = () => {
|
const setup = () => {
|
||||||
const props = {
|
const props = {
|
||||||
funcID: '',
|
funcID: '',
|
||||||
expressionID: '',
|
bodyID: '',
|
||||||
funcName: '',
|
funcName: '',
|
||||||
|
declarationID: '',
|
||||||
argKey: '',
|
argKey: '',
|
||||||
value: '',
|
value: '',
|
||||||
type: '',
|
type: '',
|
||||||
|
|
|
@ -8,7 +8,8 @@ import FuncList from 'src/ifql/components/FuncList'
|
||||||
const setup = (override = {}) => {
|
const setup = (override = {}) => {
|
||||||
const props = {
|
const props = {
|
||||||
funcs: ['count', 'range'],
|
funcs: ['count', 'range'],
|
||||||
expressionID: '1',
|
bodyID: '1',
|
||||||
|
declarationID: '2',
|
||||||
onAddNode: () => {},
|
onAddNode: () => {},
|
||||||
...override,
|
...override,
|
||||||
}
|
}
|
||||||
|
@ -133,7 +134,7 @@ describe('IFQL.Components.FuncsButton', () => {
|
||||||
const onAddNode = jest.fn()
|
const onAddNode = jest.fn()
|
||||||
const {wrapper, props} = setup({onAddNode})
|
const {wrapper, props} = setup({onAddNode})
|
||||||
const [, func2] = props.funcs
|
const [, func2] = props.funcs
|
||||||
const {expressionID} = props
|
const {bodyID, declarationID} = props
|
||||||
|
|
||||||
const dropdownButton = wrapper.find('button')
|
const dropdownButton = wrapper.find('button')
|
||||||
dropdownButton.simulate('click')
|
dropdownButton.simulate('click')
|
||||||
|
@ -148,7 +149,7 @@ describe('IFQL.Components.FuncsButton', () => {
|
||||||
input.simulate('keyDown', {key: 'ArrowDown'})
|
input.simulate('keyDown', {key: 'ArrowDown'})
|
||||||
input.simulate('keyDown', {key: 'Enter'})
|
input.simulate('keyDown', {key: 'Enter'})
|
||||||
|
|
||||||
expect(onAddNode).toHaveBeenCalledWith(func2, expressionID)
|
expect(onAddNode).toHaveBeenCalledWith(func2, bodyID, declarationID)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
import AutoRefresh, {
|
||||||
|
Props,
|
||||||
|
OriginalProps,
|
||||||
|
} from 'src/shared/components/AutoRefresh'
|
||||||
|
import React, {Component} from 'react'
|
||||||
|
import {shallow} from 'enzyme'
|
||||||
|
|
||||||
|
type ComponentProps = Props & OriginalProps
|
||||||
|
|
||||||
|
class MyComponent extends Component<ComponentProps> {
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return <p>Here</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const axes = {
|
||||||
|
bounds: {
|
||||||
|
y: [1],
|
||||||
|
y2: [2],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
type: 'table',
|
||||||
|
autoRefresh: 1,
|
||||||
|
inView: true,
|
||||||
|
templates: [],
|
||||||
|
queries: [],
|
||||||
|
axes,
|
||||||
|
editQueryStatus: () => {},
|
||||||
|
grabDataForDownload: () => {},
|
||||||
|
data: [],
|
||||||
|
setResolution: () => {},
|
||||||
|
isFetchingInitially: false,
|
||||||
|
isRefreshing: false,
|
||||||
|
queryASTs: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
const setup = (overrides: Partial<ComponentProps> = {}) => {
|
||||||
|
const ARComponent = AutoRefresh(MyComponent)
|
||||||
|
|
||||||
|
const props = {...defaultProps, ...overrides}
|
||||||
|
|
||||||
|
return shallow(<ARComponent {...props} />)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Shared.Components.AutoRefresh', () => {
|
||||||
|
describe('render', () => {
|
||||||
|
describe('when there are no results', () => {
|
||||||
|
it('renders the no results component', () => {
|
||||||
|
const wrapped = setup()
|
||||||
|
expect(wrapped.find('.graph-empty').exists()).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when there are results', () => {
|
||||||
|
it('renderes the wrapped component', () => {
|
||||||
|
const wrapped = setup()
|
||||||
|
const timeSeries = [
|
||||||
|
{
|
||||||
|
response: {
|
||||||
|
results: [{series: [1]}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
wrapped.update()
|
||||||
|
wrapped.setState({timeSeries})
|
||||||
|
process.nextTick(() => {
|
||||||
|
expect(wrapped.find(MyComponent).exists()).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in New Issue