Merge branch 'master' into bugfix/groupbys-in-alert-rule
commit
e044f5b76a
|
@ -11,6 +11,7 @@
|
|||
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. [#3256](https://github.com/influxdata/chronograf/pull/3256): Reduce font sizes in dashboards for increased space efficiency
|
||||
1. [#3320](https://github.com/influxdata/chronograf/pull/3320): Add overlay animation to Template Variables Manager
|
||||
1. [#3245](https://github.com/influxdata/chronograf/pull/3245): Display 'no results' on cells without results
|
||||
|
||||
### Bug Fixes
|
||||
|
@ -20,6 +21,7 @@
|
|||
1. [#3281](https://github.com/influxdata/chronograf/pull/3281): Fix base path for kapacitor logs
|
||||
1. [#3284](https://github.com/influxdata/chronograf/pull/3284): Fix logout when using basepath & simplify basepath usage (deprecates `PREFIX_ROUTES`)
|
||||
1. [#3349](https://github.com/influxdata/chronograf/pull/3349): Fix graphs in alert rule builder for queries that include groupby
|
||||
1. [#3345](https://github.com/influxdata/chronograf/pull/3345): Fix auto not showing in the group by dropdown and explorer getting disconnected
|
||||
|
||||
## v1.4.4.1 [2018-04-16]
|
||||
|
||||
|
|
|
@ -63,3 +63,49 @@ export const updateKapacitorBody = {
|
|||
proxy: '/chronograf/v1/sources/47/kapacitors/1/proxy',
|
||||
},
|
||||
}
|
||||
|
||||
export const queryConfig = {
|
||||
queries: [
|
||||
{
|
||||
id: '60842c85-8bc7-4180-a844-b974e47a98cd',
|
||||
query:
|
||||
'SELECT mean(:fields:), mean("usage_user") AS "mean_usage_user" FROM "telegraf"."autogen"."cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:) FILL(null)',
|
||||
queryConfig: {
|
||||
id: '60842c85-8bc7-4180-a844-b974e47a98cd',
|
||||
database: 'telegraf',
|
||||
measurement: 'cpu',
|
||||
retentionPolicy: 'autogen',
|
||||
fields: [
|
||||
{
|
||||
value: 'mean',
|
||||
type: 'func',
|
||||
alias: '',
|
||||
args: [{value: 'usage_idle', type: 'field', alias: ''}],
|
||||
},
|
||||
{
|
||||
value: 'mean',
|
||||
type: 'func',
|
||||
alias: 'mean_usage_user',
|
||||
args: [{value: 'usage_user', type: 'field', alias: ''}],
|
||||
},
|
||||
],
|
||||
tags: {},
|
||||
groupBy: {time: 'auto', tags: []},
|
||||
areTagsAccepted: false,
|
||||
fill: 'null',
|
||||
rawText:
|
||||
'SELECT mean(:fields:), mean("usage_user") AS "mean_usage_user" FROM "telegraf"."autogen"."cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:) FILL(null)',
|
||||
range: null,
|
||||
shifts: [],
|
||||
},
|
||||
queryTemplated:
|
||||
'SELECT mean("usage_idle"), mean("usage_user") AS "mean_usage_user" FROM "telegraf"."autogen"."cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:) FILL(null)',
|
||||
tempVars: [
|
||||
{
|
||||
tempVar: ':fields:',
|
||||
values: [{value: 'usage_idle', type: 'fieldKey', selected: true}],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import {kapacitor} from 'mocks/dummy'
|
||||
import {kapacitor, queryConfig} from 'mocks/dummy'
|
||||
|
||||
export const getKapacitor = jest.fn(() => Promise.resolve(kapacitor))
|
||||
export const getActiveKapacitor = jest.fn(() => Promise.resolve(kapacitor))
|
||||
export const createKapacitor = jest.fn(() => Promise.resolve({data: kapacitor}))
|
||||
export const updateKapacitor = jest.fn(() => Promise.resolve({data: kapacitor}))
|
||||
export const pingKapacitor = jest.fn(() => Promise.resolve())
|
||||
export const getQueryConfigAndStatus = jest.fn(() =>
|
||||
Promise.resolve({data: queryConfig})
|
||||
)
|
||||
|
|
|
@ -3,9 +3,11 @@ import PropTypes from 'prop-types'
|
|||
|
||||
import SideNav from 'src/side_nav'
|
||||
import Notifications from 'shared/components/Notifications'
|
||||
import Overlay from 'shared/components/OverlayTechnology'
|
||||
|
||||
const App = ({children}) => (
|
||||
<div className="chronograf-root">
|
||||
<Overlay />
|
||||
<Notifications />
|
||||
<SideNav />
|
||||
{children}
|
||||
|
|
|
@ -51,6 +51,8 @@ interface Props {
|
|||
errorThrown: () => void
|
||||
}
|
||||
|
||||
export const SourceContext = React.createContext()
|
||||
|
||||
// Acts as a 'router middleware'. The main `App` component is responsible for
|
||||
// getting the list of data sources, but not every page requires them to function.
|
||||
// Routes that do require data sources can be nested under this component.
|
||||
|
@ -205,8 +207,10 @@ export class CheckSources extends Component<Props, State> {
|
|||
// TODO: guard against invalid resource access
|
||||
|
||||
return (
|
||||
this.props.children &&
|
||||
React.cloneElement(this.props.children, {...this.props, source})
|
||||
<SourceContext.Provider value={source}>
|
||||
{this.props.children &&
|
||||
React.cloneElement(this.props.children, {...this.props, source})}
|
||||
</SourceContext.Provider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ interface Props {
|
|||
}
|
||||
|
||||
const CEOBottom: SFC<Props> = ({children}) => (
|
||||
<div className="overlay-technology--editor">{children}</div>
|
||||
<div className="ceo--editor">{children}</div>
|
||||
)
|
||||
|
||||
export default CEOBottom
|
||||
|
|
|
@ -3,6 +3,11 @@ import React, {Component} from 'react'
|
|||
import _ from 'lodash'
|
||||
import uuid from 'uuid'
|
||||
|
||||
import {
|
||||
CellEditorOverlayActions,
|
||||
CellEditorOverlayActionsFunc,
|
||||
} from 'src/types/dashboard'
|
||||
|
||||
import ResizeContainer from 'src/shared/components/ResizeContainer'
|
||||
import QueryMaker from 'src/dashboards/components/QueryMaker'
|
||||
import Visualization from 'src/dashboards/components/Visualization'
|
||||
|
@ -14,7 +19,7 @@ import * as queryModifiers from 'src/utils/queryTransitions'
|
|||
|
||||
import defaultQueryConfig from 'src/utils/defaultQueryConfig'
|
||||
import {buildQuery} from 'src/utils/influxql'
|
||||
import {getQueryConfig} from 'src/shared/apis'
|
||||
import {getQueryConfigAndStatus} from 'src/shared/apis'
|
||||
import {IS_STATIC_LEGEND} from 'src/shared/constants'
|
||||
import {ColorString, ColorNumber} from 'src/types/colors'
|
||||
import {nextSource} from 'src/dashboards/utils/sources'
|
||||
|
@ -25,11 +30,21 @@ import {
|
|||
} from 'src/dashboards/constants'
|
||||
import {OVERLAY_TECHNOLOGY} from 'src/shared/constants/classNames'
|
||||
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants'
|
||||
import {AUTO_GROUP_BY} from 'src/shared/constants'
|
||||
import {
|
||||
AUTO_GROUP_BY,
|
||||
PREDEFINED_TEMP_VARS,
|
||||
TEMP_VAR_DASHBOARD_TIME,
|
||||
} from 'src/shared/constants'
|
||||
import {getCellTypeColors} from 'src/dashboards/constants/cellEditor'
|
||||
import {TimeRange, Source, Query} from 'src/types'
|
||||
import {Status} from 'src/types/query'
|
||||
import {Cell, CellQuery, Legend} from 'src/types/dashboard'
|
||||
import {
|
||||
TimeRange,
|
||||
Source,
|
||||
QueryConfig,
|
||||
Cell,
|
||||
CellQuery,
|
||||
Legend,
|
||||
Status,
|
||||
} from 'src/types'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
const staticLegend: Legend = {
|
||||
|
@ -65,15 +80,15 @@ interface Props {
|
|||
}
|
||||
|
||||
interface State {
|
||||
queriesWorkingDraft: Query[]
|
||||
queriesWorkingDraft: QueryConfig[]
|
||||
activeQueryIndex: number
|
||||
isDisplayOptionsTabActive: boolean
|
||||
isStaticLegend: boolean
|
||||
}
|
||||
|
||||
const createWorkingDraft = (source: string, query: CellQuery): Query => {
|
||||
const createWorkingDraft = (source: string, query: CellQuery): QueryConfig => {
|
||||
const {queryConfig} = query
|
||||
const draft: Query = {
|
||||
const draft: QueryConfig = {
|
||||
...queryConfig,
|
||||
id: uuid.v4(),
|
||||
source,
|
||||
|
@ -84,8 +99,8 @@ const createWorkingDraft = (source: string, query: CellQuery): Query => {
|
|||
|
||||
const createWorkingDrafts = (
|
||||
source: string,
|
||||
queries: CellQuery[] = []
|
||||
): Query[] =>
|
||||
queries: CellQuery[]
|
||||
): QueryConfig[] =>
|
||||
_.cloneDeep(
|
||||
queries.map((query: CellQuery) => createWorkingDraft(source, query))
|
||||
)
|
||||
|
@ -142,7 +157,9 @@ class CellEditorOverlay extends Component<Props, State> {
|
|||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.overlayRef.focus()
|
||||
if (this.overlayRef) {
|
||||
this.overlayRef.focus()
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
@ -228,7 +245,10 @@ class CellEditorOverlay extends Component<Props, State> {
|
|||
this.overlayRef = r
|
||||
}
|
||||
|
||||
private queryStateReducer = queryModifier => (queryID, ...payload) => {
|
||||
private queryStateReducer = (queryModifier): CellEditorOverlayActionsFunc => (
|
||||
queryID: string,
|
||||
...payload: any[]
|
||||
) => {
|
||||
const {queriesWorkingDraft} = this.state
|
||||
const query = queriesWorkingDraft.find(q => q.id === queryID)
|
||||
|
||||
|
@ -270,7 +290,7 @@ class CellEditorOverlay extends Component<Props, State> {
|
|||
const {cell, thresholdsListColors, gaugeColors, lineColors} = this.props
|
||||
|
||||
const queries = queriesWorkingDraft.map(q => {
|
||||
const timeRange = q.range || {upper: null, lower: ':dashboardTime:'}
|
||||
const timeRange = q.range || {upper: null, lower: TEMP_VAR_DASHBOARD_TIME}
|
||||
|
||||
return {
|
||||
queryConfig: q,
|
||||
|
@ -317,20 +337,91 @@ class CellEditorOverlay extends Component<Props, State> {
|
|||
|
||||
private getActiveQuery = () => {
|
||||
const {queriesWorkingDraft, activeQueryIndex} = this.state
|
||||
const activeQuery = _.get(
|
||||
queriesWorkingDraft,
|
||||
activeQueryIndex,
|
||||
queriesWorkingDraft[0]
|
||||
)
|
||||
|
||||
return _.get(queriesWorkingDraft, activeQueryIndex, queriesWorkingDraft[0])
|
||||
const queryText = _.get(activeQuery, 'rawText', '')
|
||||
const userDefinedTempVarsInQuery = this.findUserDefinedTempVarsInQuery(
|
||||
queryText,
|
||||
this.props.templates
|
||||
)
|
||||
|
||||
if (!!userDefinedTempVarsInQuery.length) {
|
||||
activeQuery.isQuerySupportedByExplorer = false
|
||||
}
|
||||
|
||||
return activeQuery
|
||||
}
|
||||
|
||||
private handleEditRawText = async (url, id, text) => {
|
||||
const templates = removeUnselectedTemplateValues(this.props.templates)
|
||||
|
||||
// use this as the handler passed into fetchTimeSeries to update a query status
|
||||
try {
|
||||
const {data} = await getQueryConfig(url, [{query: text, id}], templates)
|
||||
const config = data.queries.find(q => q.id === id)
|
||||
const nextQueries = this.state.queriesWorkingDraft.map(
|
||||
q => (q.id === id ? {...config.queryConfig, source: q.source} : q)
|
||||
private findUserDefinedTempVarsInQuery = (
|
||||
query: string,
|
||||
templates: Template[]
|
||||
): Template[] => {
|
||||
return templates.filter((temp: Template) => {
|
||||
if (!query) {
|
||||
return false
|
||||
}
|
||||
const isPredefinedTempVar: boolean = !!PREDEFINED_TEMP_VARS.find(
|
||||
t => t === temp.tempVar
|
||||
)
|
||||
if (!isPredefinedTempVar) {
|
||||
return query.includes(temp.tempVar)
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// The schema explorer is not built to handle user defined template variables
|
||||
// in the query in a clear manner. If they are being used, we indicate that in
|
||||
// the query config in order to disable the fields column down stream because
|
||||
// at this point the query string is disconnected from the schema explorer.
|
||||
private handleEditRawText = async (
|
||||
url: string,
|
||||
id: string,
|
||||
text: string
|
||||
): Promise<void> => {
|
||||
const userDefinedTempVarsInQuery = this.findUserDefinedTempVarsInQuery(
|
||||
text,
|
||||
this.props.templates
|
||||
)
|
||||
|
||||
const isUsingUserDefinedTempVars: boolean = !!userDefinedTempVarsInQuery.length
|
||||
|
||||
try {
|
||||
const selectedTempVars: Template[] = isUsingUserDefinedTempVars
|
||||
? removeUnselectedTemplateValues(userDefinedTempVarsInQuery)
|
||||
: []
|
||||
|
||||
const {data} = await getQueryConfigAndStatus(
|
||||
url,
|
||||
[{query: text, id}],
|
||||
selectedTempVars
|
||||
)
|
||||
|
||||
const config = data.queries.find(q => q.id === id)
|
||||
const nextQueries: QueryConfig[] = this.state.queriesWorkingDraft.map(
|
||||
(q: QueryConfig) => {
|
||||
if (q.id === id) {
|
||||
const isQuerySupportedByExplorer = !isUsingUserDefinedTempVars
|
||||
|
||||
if (isUsingUserDefinedTempVars) {
|
||||
return {...q, rawText: text, isQuerySupportedByExplorer}
|
||||
}
|
||||
|
||||
return {
|
||||
...config.queryConfig,
|
||||
source: q.source,
|
||||
isQuerySupportedByExplorer,
|
||||
}
|
||||
}
|
||||
|
||||
return q
|
||||
}
|
||||
)
|
||||
|
||||
this.setState({queriesWorkingDraft: nextQueries})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
@ -389,17 +480,32 @@ class CellEditorOverlay extends Component<Props, State> {
|
|||
const {queriesWorkingDraft} = this.state
|
||||
|
||||
return queriesWorkingDraft.every(
|
||||
(query: Query) =>
|
||||
(query: QueryConfig) =>
|
||||
(!!query.measurement && !!query.database && !!query.fields.length) ||
|
||||
!!query.rawText
|
||||
)
|
||||
}
|
||||
|
||||
private get queryActions() {
|
||||
return {
|
||||
editRawTextAsync: this.handleEditRawText,
|
||||
..._.mapValues(queryModifiers, this.queryStateReducer),
|
||||
private get queryActions(): CellEditorOverlayActions {
|
||||
const original = {
|
||||
editRawTextAsync: () => Promise.resolve(),
|
||||
...queryModifiers,
|
||||
}
|
||||
const mapped = _.reduce<CellEditorOverlayActions, CellEditorOverlayActions>(
|
||||
original,
|
||||
(acc, v, k) => {
|
||||
acc[k] = this.queryStateReducer(v)
|
||||
return acc
|
||||
},
|
||||
original
|
||||
)
|
||||
|
||||
const result = {
|
||||
...mapped,
|
||||
editRawTextAsync: this.handleEditRawText,
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private get sourceLink(): string {
|
||||
|
|
|
@ -42,16 +42,14 @@ class MeasurementDropdown extends Component {
|
|||
}
|
||||
|
||||
_getMeasurements = async () => {
|
||||
const {
|
||||
source: {
|
||||
links: {proxy},
|
||||
},
|
||||
} = this.context
|
||||
const {
|
||||
measurement,
|
||||
database,
|
||||
onSelectMeasurement,
|
||||
onErrorThrown,
|
||||
source: {
|
||||
links: {proxy},
|
||||
},
|
||||
} = this.props
|
||||
|
||||
try {
|
||||
|
@ -72,7 +70,12 @@ class MeasurementDropdown extends Component {
|
|||
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
MeasurementDropdown.contextTypes = {
|
||||
MeasurementDropdown.propTypes = {
|
||||
database: string.isRequired,
|
||||
measurement: string,
|
||||
onSelectMeasurement: func.isRequired,
|
||||
onStartEdit: func.isRequired,
|
||||
onErrorThrown: func.isRequired,
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string.isRequired,
|
||||
|
@ -80,12 +83,4 @@ MeasurementDropdown.contextTypes = {
|
|||
}).isRequired,
|
||||
}
|
||||
|
||||
MeasurementDropdown.propTypes = {
|
||||
database: string.isRequired,
|
||||
measurement: string,
|
||||
onSelectMeasurement: func.isRequired,
|
||||
onStartEdit: func.isRequired,
|
||||
onErrorThrown: func.isRequired,
|
||||
}
|
||||
|
||||
export default MeasurementDropdown
|
||||
|
|
|
@ -1,20 +1,44 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, {SFC} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import {QueryConfig, Source, SourceLinks, TimeRange} from 'src/types'
|
||||
import {CellEditorOverlayActions} from 'src/types/dashboard'
|
||||
|
||||
import EmptyQuery from 'src/shared/components/EmptyQuery'
|
||||
import QueryTabList from 'src/shared/components/QueryTabList'
|
||||
import QueryTextArea from 'src/dashboards/components/QueryTextArea'
|
||||
import SchemaExplorer from 'src/shared/components/SchemaExplorer'
|
||||
import {buildQuery} from 'utils/influxql'
|
||||
import {TYPE_QUERY_CONFIG} from 'src/dashboards/constants'
|
||||
import {buildQuery} from 'src/utils/influxql'
|
||||
import {TYPE_QUERY_CONFIG, TEMPLATE_RANGE} from 'src/dashboards/constants'
|
||||
|
||||
const TEMPLATE_RANGE = {upper: null, lower: ':dashboardTime:'}
|
||||
const rawTextBinder = (links, id, action) => text =>
|
||||
action(links.queries, id, text)
|
||||
const buildText = q =>
|
||||
const rawTextBinder = (
|
||||
links: SourceLinks,
|
||||
id: string,
|
||||
action: (linksQueries: string, id: string, text: string) => void
|
||||
) => (text: string) => action(links.queries, id, text)
|
||||
|
||||
const buildText = (q: QueryConfig): string =>
|
||||
q.rawText || buildQuery(TYPE_QUERY_CONFIG, q.range || TEMPLATE_RANGE, q) || ''
|
||||
|
||||
const QueryMaker = ({
|
||||
interface Template {
|
||||
tempVar: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
source: Source
|
||||
queries: QueryConfig[]
|
||||
timeRange: TimeRange
|
||||
actions: CellEditorOverlayActions
|
||||
setActiveQueryIndex: (index: number) => void
|
||||
onDeleteQuery: (index: number) => void
|
||||
activeQueryIndex: number
|
||||
activeQuery: QueryConfig
|
||||
onAddQuery: () => void
|
||||
templates: Template[]
|
||||
initialGroupByTime: string
|
||||
}
|
||||
|
||||
const QueryMaker: SFC<Props> = ({
|
||||
source,
|
||||
actions,
|
||||
queries,
|
||||
|
@ -52,8 +76,12 @@ const QueryMaker = ({
|
|||
source={source}
|
||||
actions={actions}
|
||||
query={activeQuery}
|
||||
onAddQuery={onAddQuery}
|
||||
initialGroupByTime={initialGroupByTime}
|
||||
isQuerySupportedByExplorer={_.get(
|
||||
activeQuery,
|
||||
'isQuerySupportedByExplorer',
|
||||
true
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
@ -62,43 +90,4 @@ const QueryMaker = ({
|
|||
</div>
|
||||
)
|
||||
|
||||
const {arrayOf, func, number, shape, string} = PropTypes
|
||||
|
||||
QueryMaker.propTypes = {
|
||||
source: shape({
|
||||
links: shape({
|
||||
queries: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
queries: arrayOf(shape({})).isRequired,
|
||||
timeRange: shape({
|
||||
upper: string,
|
||||
lower: string,
|
||||
}).isRequired,
|
||||
actions: shape({
|
||||
chooseNamespace: func.isRequired,
|
||||
chooseMeasurement: func.isRequired,
|
||||
chooseTag: func.isRequired,
|
||||
groupByTag: func.isRequired,
|
||||
toggleField: func.isRequired,
|
||||
groupByTime: func.isRequired,
|
||||
toggleTagAcceptance: func.isRequired,
|
||||
fill: func,
|
||||
applyFuncsToField: func.isRequired,
|
||||
editRawTextAsync: func.isRequired,
|
||||
addInitialField: func.isRequired,
|
||||
}).isRequired,
|
||||
setActiveQueryIndex: func.isRequired,
|
||||
onDeleteQuery: func.isRequired,
|
||||
activeQueryIndex: number,
|
||||
activeQuery: shape({}),
|
||||
onAddQuery: func.isRequired,
|
||||
templates: arrayOf(
|
||||
shape({
|
||||
tempVar: string.isRequired,
|
||||
})
|
||||
).isRequired,
|
||||
initialGroupByTime: string.isRequired,
|
||||
}
|
||||
|
||||
export default QueryMaker
|
|
@ -42,7 +42,7 @@ const TemplateVariableManager = ({
|
|||
>
|
||||
Save Changes
|
||||
</button>
|
||||
<span className="page-header__dismiss" onClick={onClose(isEdited)} />
|
||||
<span className="page-header__dismiss" onClick={onClose} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="template-variable-manager--body">
|
||||
|
@ -175,16 +175,29 @@ class TemplateVariableManagerWrapper extends Component {
|
|||
)
|
||||
}
|
||||
|
||||
handleDismissManager = () => {
|
||||
const {onDismissOverlay} = this.props
|
||||
const {isEdited} = this.state
|
||||
|
||||
if (
|
||||
!isEdited ||
|
||||
(isEdited && confirm('Do you want to close without saving?')) // eslint-disable-line no-alert
|
||||
) {
|
||||
onDismissOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {rows, isEdited} = this.state
|
||||
return (
|
||||
<TemplateVariableManager
|
||||
{...this.props}
|
||||
templates={rows}
|
||||
isEdited={isEdited}
|
||||
onClose={this.handleDismissManager}
|
||||
onRunQuerySuccess={this.onRunQuerySuccess}
|
||||
onSaveTemplatesSuccess={this.onSaveTemplatesSuccess}
|
||||
onAddVariable={this.onAddVariable}
|
||||
templates={rows}
|
||||
isEdited={isEdited}
|
||||
onDelete={this.onDeleteTemplateVariable}
|
||||
tempVarAlreadyExists={this.tempVarAlreadyExists}
|
||||
/>
|
||||
|
@ -204,13 +217,7 @@ TemplateVariableManager.propTypes = {
|
|||
}
|
||||
|
||||
TemplateVariableManagerWrapper.propTypes = {
|
||||
onClose: func.isRequired,
|
||||
onEditTemplateVariables: func.isRequired,
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string,
|
||||
}),
|
||||
}).isRequired,
|
||||
templates: arrayOf(
|
||||
shape({
|
||||
type: string.isRequired,
|
||||
|
@ -229,6 +236,7 @@ TemplateVariableManagerWrapper.propTypes = {
|
|||
})
|
||||
),
|
||||
onRunQueryFailure: func.isRequired,
|
||||
onDismissOverlay: func,
|
||||
}
|
||||
|
||||
export default TemplateVariableManagerWrapper
|
||||
|
|
|
@ -46,6 +46,7 @@ const TemplateVariableRow = ({
|
|||
onSubmit,
|
||||
onErrorThrown,
|
||||
onDeleteTempVar,
|
||||
source,
|
||||
}) => (
|
||||
<form
|
||||
className={classnames('template-variable-manager--table-row', {
|
||||
|
@ -78,6 +79,7 @@ const TemplateVariableRow = ({
|
|||
</div>
|
||||
<div className="tvm--col-3">
|
||||
<TemplateQueryBuilder
|
||||
source={source}
|
||||
onSelectDatabase={onSelectDatabase}
|
||||
selectedType={selectedType}
|
||||
selectedDatabase={selectedDatabase}
|
||||
|
|
|
@ -14,6 +14,7 @@ const TemplateQueryBuilder = ({
|
|||
onSelectTagKey,
|
||||
onStartEdit,
|
||||
onErrorThrown,
|
||||
source,
|
||||
}) => {
|
||||
switch (selectedType) {
|
||||
case 'csv':
|
||||
|
@ -25,6 +26,7 @@ const TemplateQueryBuilder = ({
|
|||
<div className="tvm-query-builder">
|
||||
<span className="tvm-query-builder--text">SHOW MEASUREMENTS ON</span>
|
||||
<DatabaseDropdown
|
||||
source={source}
|
||||
onSelectDatabase={onSelectDatabase}
|
||||
database={selectedDatabase}
|
||||
onStartEdit={onStartEdit}
|
||||
|
@ -40,6 +42,7 @@ const TemplateQueryBuilder = ({
|
|||
SHOW {selectedType === 'fieldKeys' ? 'FIELD' : 'TAG'} KEYS ON
|
||||
</span>
|
||||
<DatabaseDropdown
|
||||
source={source}
|
||||
onSelectDatabase={onSelectDatabase}
|
||||
database={selectedDatabase}
|
||||
onStartEdit={onStartEdit}
|
||||
|
@ -48,6 +51,7 @@ const TemplateQueryBuilder = ({
|
|||
<span className="tvm-query-builder--text">FROM</span>
|
||||
{selectedDatabase ? (
|
||||
<MeasurementDropdown
|
||||
source={source}
|
||||
database={selectedDatabase}
|
||||
measurement={selectedMeasurement}
|
||||
onSelectMeasurement={onSelectMeasurement}
|
||||
|
@ -64,6 +68,7 @@ const TemplateQueryBuilder = ({
|
|||
<div className="tvm-query-builder">
|
||||
<span className="tvm-query-builder--text">SHOW TAG VALUES ON</span>
|
||||
<DatabaseDropdown
|
||||
source={source}
|
||||
onSelectDatabase={onSelectDatabase}
|
||||
database={selectedDatabase}
|
||||
onStartEdit={onStartEdit}
|
||||
|
@ -72,6 +77,7 @@ const TemplateQueryBuilder = ({
|
|||
<span className="tvm-query-builder--text">FROM</span>
|
||||
{selectedDatabase ? (
|
||||
<MeasurementDropdown
|
||||
source={source}
|
||||
database={selectedDatabase}
|
||||
measurement={selectedMeasurement}
|
||||
onSelectMeasurement={onSelectMeasurement}
|
||||
|
@ -105,7 +111,7 @@ const TemplateQueryBuilder = ({
|
|||
}
|
||||
}
|
||||
|
||||
const {func, string} = PropTypes
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
TemplateQueryBuilder.propTypes = {
|
||||
selectedType: string.isRequired,
|
||||
|
@ -117,6 +123,11 @@ TemplateQueryBuilder.propTypes = {
|
|||
selectedDatabase: string,
|
||||
selectedTagKey: string,
|
||||
onErrorThrown: func.isRequired,
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
}
|
||||
|
||||
export default TemplateQueryBuilder
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
DEFAULT_FIX_FIRST_COLUMN,
|
||||
} from 'src/shared/constants/tableGraph'
|
||||
import {CELL_TYPE_LINE} from 'src/dashboards/graphics/graph'
|
||||
import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants'
|
||||
|
||||
export const UNTITLED_CELL_LINE = 'Untitled Line Graph'
|
||||
export const UNTITLED_CELL_STACKED = 'Untitled Stacked Gracph'
|
||||
|
@ -151,3 +152,4 @@ export const TYPE_QUERY_CONFIG = 'queryConfig'
|
|||
export const TYPE_SHIFTED = 'shifted queryConfig'
|
||||
export const TYPE_IFQL = 'ifql'
|
||||
export const DASHBOARD_NAME_MAX_LENGTH = 50
|
||||
export const TEMPLATE_RANGE = {upper: null, lower: TEMP_VAR_DASHBOARD_TIME}
|
||||
|
|
|
@ -8,7 +8,6 @@ import _ from 'lodash'
|
|||
|
||||
import {isUserAuthorized, EDITOR_ROLE} from 'src/auth/Authorized'
|
||||
|
||||
import OverlayTechnologies from 'shared/components/OverlayTechnologies'
|
||||
import CellEditorOverlay from 'src/dashboards/components/CellEditorOverlay'
|
||||
import DashboardHeader from 'src/dashboards/components/DashboardHeader'
|
||||
import Dashboard from 'src/dashboards/components/Dashboard'
|
||||
|
@ -28,6 +27,7 @@ import {
|
|||
showCellEditorOverlay,
|
||||
hideCellEditorOverlay,
|
||||
} from 'src/dashboards/actions/cellEditorOverlay'
|
||||
import {showOverlay} from 'src/shared/actions/overlayTechnology'
|
||||
|
||||
import {dismissEditingAnnotation} from 'src/shared/actions/annotations'
|
||||
|
||||
|
@ -36,10 +36,16 @@ import {
|
|||
templateControlBarVisibilityToggled as templateControlBarVisibilityToggledAction,
|
||||
} from 'shared/actions/app'
|
||||
import {presentationButtonDispatcher} from 'shared/dispatchers'
|
||||
import {interval, DASHBOARD_LAYOUT_ROW_HEIGHT} from 'shared/constants'
|
||||
import {
|
||||
interval,
|
||||
DASHBOARD_LAYOUT_ROW_HEIGHT,
|
||||
TEMP_VAR_DASHBOARD_TIME,
|
||||
TEMP_VAR_UPPER_DASHBOARD_TIME,
|
||||
} from 'shared/constants'
|
||||
import {notifyDashboardNotFound} from 'shared/copy/notifications'
|
||||
import {colorsStringSchema, colorsNumberSchema} from 'shared/schemas'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {OverlayContext} from 'src/shared/components/OverlayTechnology'
|
||||
|
||||
const FORMAT_INFLUXQL = 'influxql'
|
||||
const defaultTimeRange = {
|
||||
|
@ -57,7 +63,6 @@ class DashboardPage extends Component {
|
|||
this.state = {
|
||||
isEditMode: false,
|
||||
selectedCell: null,
|
||||
isTemplating: false,
|
||||
zoomedTimeRange: {zoomedLower: null, zoomedUpper: null},
|
||||
scrollTop: 0,
|
||||
windowHeight: window.innerHeight,
|
||||
|
@ -149,16 +154,28 @@ class DashboardPage extends Component {
|
|||
}
|
||||
|
||||
handleOpenTemplateManager = () => {
|
||||
this.setState({isTemplating: true})
|
||||
}
|
||||
|
||||
handleCloseTemplateManager = isEdited => () => {
|
||||
if (
|
||||
!isEdited ||
|
||||
(isEdited && confirm('Do you want to close without saving?')) // eslint-disable-line no-alert
|
||||
) {
|
||||
this.setState({isTemplating: false})
|
||||
const {handleShowOverlay, dashboard, source} = this.props
|
||||
const options = {
|
||||
dismissOnClickOutside: false,
|
||||
dismissOnEscape: false,
|
||||
}
|
||||
|
||||
handleShowOverlay(
|
||||
<OverlayContext.Consumer>
|
||||
{({onDismissOverlay}) => {
|
||||
return (
|
||||
<TemplateVariableManager
|
||||
source={source}
|
||||
templates={dashboard.templates}
|
||||
onDismissOverlay={onDismissOverlay}
|
||||
onRunQueryFailure={this.handleRunQueryFailure}
|
||||
onEditTemplateVariables={this.handleEditTemplateVariables}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</OverlayContext.Consumer>,
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
handleSaveEditedCell = newCell => {
|
||||
|
@ -321,7 +338,7 @@ class DashboardPage extends Component {
|
|||
|
||||
const dashboardTime = {
|
||||
id: 'dashtime',
|
||||
tempVar: ':dashboardTime:',
|
||||
tempVar: TEMP_VAR_DASHBOARD_TIME,
|
||||
type: lowerType,
|
||||
values: [
|
||||
{
|
||||
|
@ -334,7 +351,7 @@ class DashboardPage extends Component {
|
|||
|
||||
const upperDashboardTime = {
|
||||
id: 'upperdashtime',
|
||||
tempVar: ':upperDashboardTime:',
|
||||
tempVar: TEMP_VAR_UPPER_DASHBOARD_TIME,
|
||||
type: upperType,
|
||||
values: [
|
||||
{
|
||||
|
@ -357,7 +374,7 @@ class DashboardPage extends Component {
|
|||
templatesIncludingDashTime = []
|
||||
}
|
||||
|
||||
const {isEditMode, isTemplating} = this.state
|
||||
const {isEditMode} = this.state
|
||||
|
||||
const names = dashboards.map(d => ({
|
||||
name: d.name,
|
||||
|
@ -365,17 +382,6 @@ class DashboardPage extends Component {
|
|||
}))
|
||||
return (
|
||||
<div className="page dashboard-page">
|
||||
{isTemplating ? (
|
||||
<OverlayTechnologies>
|
||||
<TemplateVariableManager
|
||||
source={source}
|
||||
templates={dashboard.templates}
|
||||
onClose={this.handleCloseTemplateManager}
|
||||
onRunQueryFailure={this.handleRunQueryFailure}
|
||||
onEditTemplateVariables={this.handleEditTemplateVariables}
|
||||
/>
|
||||
</OverlayTechnologies>
|
||||
) : null}
|
||||
{selectedCell ? (
|
||||
<CellEditorOverlay
|
||||
source={source}
|
||||
|
@ -534,6 +540,7 @@ DashboardPage.propTypes = {
|
|||
thresholdsListColors: colorsNumberSchema.isRequired,
|
||||
gaugeColors: colorsNumberSchema.isRequired,
|
||||
lineColors: colorsStringSchema.isRequired,
|
||||
handleShowOverlay: func.isRequired,
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, {params: {dashboardID}}) => {
|
||||
|
@ -613,6 +620,7 @@ const mapDispatchToProps = dispatch => ({
|
|||
dismissEditingAnnotation,
|
||||
dispatch
|
||||
),
|
||||
handleShowOverlay: bindActionCreators(showOverlay, dispatch),
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import {Query} from 'src/types'
|
||||
import {QueryConfig} from 'src/types'
|
||||
|
||||
export const nextSource = (prevQuery: Query, nextQuery: Query): string => {
|
||||
export const nextSource = (
|
||||
prevQuery: QueryConfig,
|
||||
nextQuery: QueryConfig
|
||||
): string => {
|
||||
if (nextQuery.source) {
|
||||
return nextQuery.source
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import uuid from 'uuid'
|
||||
|
||||
import {getQueryConfig} from 'shared/apis'
|
||||
import {getQueryConfigAndStatus} from 'shared/apis'
|
||||
|
||||
import {errorThrown} from 'shared/actions/errors'
|
||||
|
||||
|
@ -158,7 +158,7 @@ export const timeShift = (queryID, shift) => ({
|
|||
// Async actions
|
||||
export const editRawTextAsync = (url, id, text) => async dispatch => {
|
||||
try {
|
||||
const {data} = await getQueryConfig(url, [{query: text, id}])
|
||||
const {data} = await getQueryConfigAndStatus(url, [{query: text, id}])
|
||||
const config = data.queries.find(q => q.id === id)
|
||||
dispatch(updateQueryConfig(config.queryConfig))
|
||||
} catch (error) {
|
||||
|
|
|
@ -1,14 +1,45 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, {PureComponent, MouseEvent} from 'react'
|
||||
import classnames from 'classnames'
|
||||
import _ from 'lodash'
|
||||
|
||||
import FunctionSelector from 'shared/components/FunctionSelector'
|
||||
import {firstFieldName} from 'shared/reducers/helpers/fields'
|
||||
import FunctionSelector from 'src/shared/components/FunctionSelector'
|
||||
import {firstFieldName} from 'src/shared/reducers/helpers/fields'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Field {
|
||||
type: string
|
||||
value: string
|
||||
}
|
||||
|
||||
interface FuncArg {
|
||||
value: string
|
||||
type: string
|
||||
}
|
||||
|
||||
interface FieldFunc extends Field {
|
||||
args: FuncArg[]
|
||||
}
|
||||
|
||||
interface ApplyFuncsToFieldArgs {
|
||||
field: Field
|
||||
funcs: FuncArg[]
|
||||
}
|
||||
interface Props {
|
||||
fieldFuncs: FieldFunc[]
|
||||
isSelected: boolean
|
||||
onToggleField: (field: Field) => void
|
||||
onApplyFuncsToField: (args: ApplyFuncsToFieldArgs) => void
|
||||
isKapacitorRule: boolean
|
||||
funcs: string[]
|
||||
isDisabled: boolean
|
||||
}
|
||||
|
||||
interface State {
|
||||
isOpen: boolean
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class FieldListItem extends Component {
|
||||
class FieldListItem extends PureComponent<Props, State> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
|
@ -16,55 +47,10 @@ class FieldListItem extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
toggleFunctionsMenu = e => {
|
||||
if (e) {
|
||||
e.stopPropagation()
|
||||
}
|
||||
this.setState({isOpen: !this.state.isOpen})
|
||||
}
|
||||
|
||||
close = () => {
|
||||
this.setState({isOpen: false})
|
||||
}
|
||||
|
||||
handleToggleField = () => {
|
||||
const {onToggleField} = this.props
|
||||
const value = this._getFieldName()
|
||||
|
||||
onToggleField({value, type: 'field'})
|
||||
this.close()
|
||||
}
|
||||
|
||||
handleApplyFunctions = selectedFuncs => {
|
||||
const {onApplyFuncsToField} = this.props
|
||||
const fieldName = this._getFieldName()
|
||||
const field = {value: fieldName, type: 'field'}
|
||||
|
||||
onApplyFuncsToField({
|
||||
field,
|
||||
funcs: selectedFuncs.map(this._makeFunc),
|
||||
})
|
||||
this.close()
|
||||
}
|
||||
|
||||
_makeFunc = value => ({
|
||||
value,
|
||||
type: 'func',
|
||||
})
|
||||
|
||||
_getFieldName = () => {
|
||||
const {fieldFuncs} = this.props
|
||||
const fieldFunc = _.head(fieldFuncs)
|
||||
|
||||
return _.get(fieldFunc, 'type') === 'field'
|
||||
? _.get(fieldFunc, 'value')
|
||||
: firstFieldName(_.get(fieldFunc, 'args'))
|
||||
}
|
||||
|
||||
render() {
|
||||
const {isKapacitorRule, isSelected, funcs} = this.props
|
||||
public render() {
|
||||
const {isKapacitorRule, isSelected, funcs, isDisabled} = this.props
|
||||
const {isOpen} = this.state
|
||||
const fieldName = this._getFieldName()
|
||||
const fieldName = this.getFieldName()
|
||||
|
||||
let fieldFuncsLabel
|
||||
const num = funcs.length
|
||||
|
@ -84,6 +70,7 @@ class FieldListItem extends Component {
|
|||
<div
|
||||
className={classnames('query-builder--list-item', {
|
||||
active: isSelected,
|
||||
disabled: isDisabled,
|
||||
})}
|
||||
onClick={this.handleToggleField}
|
||||
data-test={`query-builder-list-item-field-${fieldName}`}
|
||||
|
@ -98,6 +85,7 @@ class FieldListItem extends Component {
|
|||
active: isOpen,
|
||||
'btn-default': !num,
|
||||
'btn-primary': num,
|
||||
disabled: isDisabled,
|
||||
})}
|
||||
onClick={this.toggleFunctionsMenu}
|
||||
data-test={`query-builder-list-item-function-${fieldName}`}
|
||||
|
@ -116,28 +104,54 @@ class FieldListItem extends Component {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {string, shape, func, arrayOf, bool} = PropTypes
|
||||
private toggleFunctionsMenu = (e: MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation()
|
||||
const {isDisabled} = this.props
|
||||
if (isDisabled) {
|
||||
return
|
||||
}
|
||||
|
||||
FieldListItem.propTypes = {
|
||||
fieldFuncs: arrayOf(
|
||||
shape({
|
||||
type: string.isRequired,
|
||||
value: string.isRequired,
|
||||
alias: string,
|
||||
args: arrayOf(
|
||||
shape({
|
||||
type: string.isRequired,
|
||||
value: string.isRequired,
|
||||
})
|
||||
),
|
||||
this.setState({isOpen: !this.state.isOpen})
|
||||
}
|
||||
|
||||
private close = (): void => {
|
||||
this.setState({isOpen: false})
|
||||
}
|
||||
|
||||
private handleToggleField = (): void => {
|
||||
const {onToggleField} = this.props
|
||||
const value = this.getFieldName()
|
||||
|
||||
onToggleField({value, type: 'field'})
|
||||
this.close()
|
||||
}
|
||||
|
||||
private handleApplyFunctions = (selectedFuncs: string[]) => {
|
||||
const {onApplyFuncsToField} = this.props
|
||||
const fieldName = this.getFieldName()
|
||||
const field: Field = {value: fieldName, type: 'field'}
|
||||
|
||||
onApplyFuncsToField({
|
||||
field,
|
||||
funcs: selectedFuncs.map(val => this.makeFuncArg(val)),
|
||||
})
|
||||
).isRequired,
|
||||
isSelected: bool.isRequired,
|
||||
onToggleField: func.isRequired,
|
||||
onApplyFuncsToField: func.isRequired,
|
||||
isKapacitorRule: bool.isRequired,
|
||||
funcs: arrayOf(string.isRequired).isRequired,
|
||||
this.close()
|
||||
}
|
||||
|
||||
private makeFuncArg = (value: string): FuncArg => ({
|
||||
value,
|
||||
type: 'func',
|
||||
})
|
||||
|
||||
private getFieldName = (): string => {
|
||||
const {fieldFuncs} = this.props
|
||||
const fieldFunc = _.head(fieldFuncs)
|
||||
|
||||
return _.get(fieldFunc, 'type') === 'field'
|
||||
? _.get(fieldFunc, 'value')
|
||||
: firstFieldName(_.get(fieldFunc, 'args'))
|
||||
}
|
||||
}
|
||||
|
||||
export default FieldListItem
|
|
@ -1,24 +1,40 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, {SFC} from 'react'
|
||||
import {withRouter} from 'react-router'
|
||||
import {Location} from 'history'
|
||||
|
||||
import groupByTimeOptions from 'src/data_explorer/data/groupByTimes'
|
||||
|
||||
import Dropdown from 'shared/components/Dropdown'
|
||||
import Dropdown from 'src/shared/components/Dropdown'
|
||||
|
||||
import {AUTO_GROUP_BY} from 'shared/constants'
|
||||
import {AUTO_GROUP_BY} from 'src/shared/constants'
|
||||
import {GroupBy} from 'src/types'
|
||||
|
||||
const isInRuleBuilder = pathname => pathname.includes('alert-rules')
|
||||
interface GroupByTimeOption {
|
||||
defaultTimeBound: string
|
||||
seconds: number
|
||||
menuOption: string
|
||||
}
|
||||
|
||||
const getOptions = pathname =>
|
||||
interface Props {
|
||||
location?: Location
|
||||
selected: string
|
||||
onChooseGroupByTime: (groupBy: GroupBy) => void
|
||||
isDisabled: boolean
|
||||
}
|
||||
|
||||
const isInRuleBuilder = (pathname: string): boolean =>
|
||||
pathname.includes('alert-rules')
|
||||
|
||||
const getOptions = (pathname: string): GroupByTimeOption[] =>
|
||||
isInRuleBuilder(pathname)
|
||||
? groupByTimeOptions.filter(({menuOption}) => menuOption !== AUTO_GROUP_BY)
|
||||
: groupByTimeOptions
|
||||
|
||||
const GroupByTimeDropdown = ({
|
||||
const GroupByTimeDropdown: SFC<Props> = ({
|
||||
selected,
|
||||
onChooseGroupByTime,
|
||||
location: {pathname},
|
||||
isDisabled,
|
||||
}) => (
|
||||
<div className="group-by-time">
|
||||
<label className="group-by-time--label">Group by:</label>
|
||||
|
@ -32,18 +48,9 @@ const GroupByTimeDropdown = ({
|
|||
}))}
|
||||
onChoose={onChooseGroupByTime}
|
||||
selected={selected || 'Time'}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
const {func, string, shape} = PropTypes
|
||||
|
||||
GroupByTimeDropdown.propTypes = {
|
||||
location: shape({
|
||||
pathname: string.isRequired,
|
||||
}).isRequired,
|
||||
selected: string,
|
||||
onChooseGroupByTime: func.isRequired,
|
||||
}
|
||||
|
||||
export default withRouter(GroupByTimeDropdown)
|
|
@ -2,7 +2,7 @@ import React, {PureComponent} from 'react'
|
|||
|
||||
import QueryEditor from './QueryEditor'
|
||||
import SchemaExplorer from 'src/shared/components/SchemaExplorer'
|
||||
import {Source, Query} from 'src/types'
|
||||
import {Source, QueryConfig} from 'src/types'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
const rawTextBinder = (links, id, action) => text =>
|
||||
|
@ -12,7 +12,7 @@ interface Props {
|
|||
source: Source
|
||||
rawText: string
|
||||
actions: any
|
||||
activeQuery: Query
|
||||
activeQuery: QueryConfig
|
||||
initialGroupByTime: string
|
||||
}
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@ class WriteDataForm extends Component {
|
|||
handleFileInputRef = el => (this.fileInput = el)
|
||||
|
||||
render() {
|
||||
const {onClose, errorThrown} = this.props
|
||||
const {onClose, errorThrown, source} = this.props
|
||||
const {dragClass} = this.state
|
||||
|
||||
return (
|
||||
|
@ -148,6 +148,7 @@ class WriteDataForm extends Component {
|
|||
<div className="write-data-form">
|
||||
<WriteDataHeader
|
||||
{...this.state}
|
||||
source={source}
|
||||
handleSelectDatabase={this.handleSelectDatabase}
|
||||
errorThrown={errorThrown}
|
||||
toggleWriteView={this.toggleWriteView}
|
||||
|
|
|
@ -9,11 +9,13 @@ const WriteDataHeader = ({
|
|||
toggleWriteView,
|
||||
isManual,
|
||||
onClose,
|
||||
source,
|
||||
}) => (
|
||||
<div className="write-data-form--header">
|
||||
<div className="page-header__left">
|
||||
<h1 className="page-header__title">Write Data To</h1>
|
||||
<DatabaseDropdown
|
||||
source={source}
|
||||
onSelectDatabase={handleSelectDatabase}
|
||||
database={selectedDatabase}
|
||||
onErrorThrown={errorThrown}
|
||||
|
@ -40,7 +42,7 @@ const WriteDataHeader = ({
|
|||
</div>
|
||||
)
|
||||
|
||||
const {func, string, bool} = PropTypes
|
||||
const {func, shape, string, bool} = PropTypes
|
||||
|
||||
WriteDataHeader.propTypes = {
|
||||
handleSelectDatabase: func.isRequired,
|
||||
|
@ -49,6 +51,11 @@ WriteDataHeader.propTypes = {
|
|||
errorThrown: func.isRequired,
|
||||
onClose: func.isRequired,
|
||||
isManual: bool,
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
}
|
||||
|
||||
export default WriteDataHeader
|
||||
|
|
|
@ -26,12 +26,12 @@ import {writeLineProtocolAsync} from 'src/data_explorer/actions/view/write'
|
|||
import {buildRawText} from 'src/utils/influxql'
|
||||
import defaultQueryConfig from 'src/utils/defaultQueryConfig'
|
||||
|
||||
import {Source, Query, TimeRange} from 'src/types'
|
||||
import {Source, QueryConfig, TimeRange} from 'src/types'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
source: Source
|
||||
queryConfigs: Query[]
|
||||
queryConfigs: QueryConfig[]
|
||||
queryConfigActions: any // TODO: actually type these
|
||||
autoRefresh: number
|
||||
handleChooseAutoRefresh: () => void
|
||||
|
@ -169,7 +169,7 @@ export class DataExplorer extends PureComponent<Props, State> {
|
|||
return _.get(this.props.queryConfigs, ['0', 'database'], null)
|
||||
}
|
||||
|
||||
private get activeQuery(): Query {
|
||||
private get activeQuery(): QueryConfig {
|
||||
const {queryConfigs} = this.props
|
||||
|
||||
if (queryConfigs.length === 0) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import {TEMP_VAR_INTERVAL} from 'src/shared/constants'
|
||||
|
||||
const groupByTimes = [
|
||||
{defaultTimeBound: ':interval:', seconds: 604800, menuOption: 'auto'},
|
||||
{defaultTimeBound: TEMP_VAR_INTERVAL, seconds: 604800, menuOption: 'auto'},
|
||||
{defaultTimeBound: 'now() - 5m', seconds: 10, menuOption: '10s'},
|
||||
{defaultTimeBound: 'now() - 15m', seconds: 60, menuOption: '1m'},
|
||||
{defaultTimeBound: 'now() - 1h', seconds: 300, menuOption: '5m'},
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import {ReactElement} from 'react'
|
||||
|
||||
type OverlayNodeType = ReactElement<any>
|
||||
|
||||
interface Options {
|
||||
dismissOnClickOutside?: boolean
|
||||
dismissOnEscape?: boolean
|
||||
transitionTime?: number
|
||||
}
|
||||
|
||||
export const showOverlay = (
|
||||
OverlayNode: OverlayNodeType,
|
||||
options: Options
|
||||
) => ({
|
||||
type: 'SHOW_OVERLAY',
|
||||
payload: {OverlayNode, options},
|
||||
})
|
||||
|
||||
export const dismissOverlay = () => ({
|
||||
type: 'DISMISS_OVERLAY',
|
||||
})
|
|
@ -231,7 +231,7 @@ export function kapacitorProxy(kapacitor, method, path, body) {
|
|||
})
|
||||
}
|
||||
|
||||
export const getQueryConfig = (url, queries, tempVars) =>
|
||||
export const getQueryConfigAndStatus = (url, queries, tempVars) =>
|
||||
AJAX({
|
||||
url,
|
||||
method: 'POST',
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, {Component, ComponentClass} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import {getQueryConfig} from 'src/shared/apis'
|
||||
import {getQueryConfigAndStatus} 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'
|
||||
|
@ -265,7 +265,11 @@ const AutoRefresh = (
|
|||
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)
|
||||
const {data} = await getQueryConfigAndStatus(
|
||||
url,
|
||||
[{query: text}],
|
||||
templates
|
||||
)
|
||||
return data.queries[0].queryAST
|
||||
})
|
||||
)
|
||||
|
|
|
@ -2,8 +2,10 @@ import React from 'react'
|
|||
import PropTypes from 'prop-types'
|
||||
import _ from 'lodash'
|
||||
|
||||
import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants'
|
||||
|
||||
const CustomTimeIndicator = ({queries}) => {
|
||||
const q = queries.find(({query}) => !query.includes(':dashboardTime:'))
|
||||
const q = queries.find(({query}) => !query.includes(TEMP_VAR_DASHBOARD_TIME))
|
||||
const customLower = _.get(q, ['queryConfig', 'range', 'lower'], null)
|
||||
const customUpper = _.get(q, ['queryConfig', 'range', 'upper'], null)
|
||||
|
||||
|
|
|
@ -39,8 +39,7 @@ class DatabaseDropdown extends Component {
|
|||
}
|
||||
|
||||
_getDatabases = async () => {
|
||||
const {source} = this.context
|
||||
const {database, onSelectDatabase, onErrorThrown} = this.props
|
||||
const {source, database, onSelectDatabase, onErrorThrown} = this.props
|
||||
const proxy = source.links.proxy
|
||||
try {
|
||||
const {data} = await showDatabases(proxy)
|
||||
|
@ -65,7 +64,11 @@ class DatabaseDropdown extends Component {
|
|||
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
DatabaseDropdown.contextTypes = {
|
||||
DatabaseDropdown.propTypes = {
|
||||
database: string,
|
||||
onSelectDatabase: func.isRequired,
|
||||
onStartEdit: func,
|
||||
onErrorThrown: func.isRequired,
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string.isRequired,
|
||||
|
@ -73,11 +76,4 @@ DatabaseDropdown.contextTypes = {
|
|||
}).isRequired,
|
||||
}
|
||||
|
||||
DatabaseDropdown.propTypes = {
|
||||
database: string,
|
||||
onSelectDatabase: func.isRequired,
|
||||
onStartEdit: func,
|
||||
onErrorThrown: func.isRequired,
|
||||
}
|
||||
|
||||
export default DatabaseDropdown
|
||||
|
|
|
@ -3,7 +3,7 @@ import React, {PureComponent} from 'react'
|
|||
|
||||
import _ from 'lodash'
|
||||
|
||||
import {Query, Source} from 'src/types'
|
||||
import {QueryConfig, Source} from 'src/types'
|
||||
import {Namespace} from 'src/types/query'
|
||||
|
||||
import {showDatabases, showRetentionPolicies} from 'src/shared/apis/metaQuery'
|
||||
|
@ -15,7 +15,7 @@ import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
|||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface DatabaseListProps {
|
||||
query: Query
|
||||
query: QueryConfig
|
||||
querySource: Source
|
||||
onChooseNamespace: (namespace: Namespace) => void
|
||||
source: Source
|
||||
|
@ -102,7 +102,7 @@ class DatabaseList extends PureComponent<DatabaseListProps, DatabaseListState> {
|
|||
return () => this.props.onChooseNamespace(namespace)
|
||||
}
|
||||
|
||||
public isActive(query: Query, {database, retentionPolicy}: Namespace) {
|
||||
public isActive(query: QueryConfig, {database, retentionPolicy}: Namespace) {
|
||||
return (
|
||||
database === query.database && retentionPolicy === query.retentionPolicy
|
||||
)
|
||||
|
|
|
@ -1,23 +1,83 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import QueryOptions from 'shared/components/QueryOptions'
|
||||
import FieldListItem from 'src/data_explorer/components/FieldListItem'
|
||||
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
||||
import {QueryConfig, GroupBy, Source, TimeShift} from 'src/types'
|
||||
|
||||
import {showFieldKeys} from 'shared/apis/metaQuery'
|
||||
import showFieldKeysParser from 'shared/parsing/showFieldKeys'
|
||||
import QueryOptions from 'src/shared/components/QueryOptions'
|
||||
import FieldListItem from 'src/data_explorer/components/FieldListItem'
|
||||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||
|
||||
import {showFieldKeys} from 'src/shared/apis/metaQuery'
|
||||
import showFieldKeysParser from 'src/shared/parsing/showFieldKeys'
|
||||
import {
|
||||
functionNames,
|
||||
numFunctions,
|
||||
getFieldsWithName,
|
||||
getFuncsByFieldName,
|
||||
} from 'shared/reducers/helpers/fields'
|
||||
} from 'src/shared/reducers/helpers/fields'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface GroupByOption extends GroupBy {
|
||||
menuOption: string
|
||||
}
|
||||
|
||||
interface TimeShiftOption extends TimeShift {
|
||||
text: string
|
||||
}
|
||||
interface Links {
|
||||
proxy: string
|
||||
}
|
||||
|
||||
interface Field {
|
||||
type: string
|
||||
value: string
|
||||
}
|
||||
|
||||
interface FieldFunc extends Field {
|
||||
args: FuncArg[]
|
||||
}
|
||||
interface FuncArg {
|
||||
value: string
|
||||
type: string
|
||||
}
|
||||
|
||||
interface ApplyFuncsToFieldArgs {
|
||||
field: Field
|
||||
funcs: FuncArg[]
|
||||
}
|
||||
|
||||
interface Props {
|
||||
query: QueryConfig
|
||||
onTimeShift: (shift: TimeShiftOption) => void
|
||||
onToggleField: (field: Field) => void
|
||||
onGroupByTime: (groupByOption: string) => void
|
||||
onFill: (fill: string) => void
|
||||
applyFuncsToField: (field: ApplyFuncsToFieldArgs, groupBy: GroupBy) => void
|
||||
isKapacitorRule: boolean
|
||||
querySource: {
|
||||
links: Links
|
||||
}
|
||||
removeFuncs: (fields: Field[]) => void
|
||||
addInitialField: (field: Field, groupBy: GroupBy) => void
|
||||
initialGroupByTime: string | null
|
||||
isQuerySupportedByExplorer: boolean
|
||||
}
|
||||
|
||||
interface State {
|
||||
fields: Field[]
|
||||
}
|
||||
|
||||
interface Context {
|
||||
source: Source
|
||||
}
|
||||
@ErrorHandling
|
||||
class FieldList extends Component {
|
||||
class FieldList extends PureComponent<Props, State> {
|
||||
public static context: Context
|
||||
public static defaultProps: Partial<Props> = {
|
||||
isKapacitorRule: false,
|
||||
initialGroupByTime: null,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
|
@ -25,16 +85,16 @@ class FieldList extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
public componentDidMount() {
|
||||
const {database, measurement} = this.props.query
|
||||
if (!database || !measurement) {
|
||||
return
|
||||
}
|
||||
|
||||
this._getFields()
|
||||
this.getFields()
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
public componentDidUpdate(prevProps) {
|
||||
const {querySource, query} = this.props
|
||||
const {database, measurement, retentionPolicy} = query
|
||||
const {
|
||||
|
@ -55,26 +115,100 @@ class FieldList extends Component {
|
|||
return
|
||||
}
|
||||
|
||||
this._getFields()
|
||||
this.getFields()
|
||||
}
|
||||
|
||||
handleGroupByTime = groupBy => {
|
||||
public render() {
|
||||
const {
|
||||
query: {database, measurement, fields = [], groupBy, fill, shifts},
|
||||
isQuerySupportedByExplorer,
|
||||
isKapacitorRule,
|
||||
} = this.props
|
||||
|
||||
const hasAggregates = numFunctions(fields) > 0
|
||||
const noDBorMeas = !database || !measurement
|
||||
|
||||
return (
|
||||
<div className="query-builder--column">
|
||||
<div className="query-builder--heading">
|
||||
<span>Fields</span>
|
||||
{hasAggregates ? (
|
||||
<QueryOptions
|
||||
fill={fill}
|
||||
shift={_.first(shifts)}
|
||||
groupBy={groupBy}
|
||||
onFill={this.handleFill}
|
||||
isKapacitorRule={isKapacitorRule}
|
||||
onTimeShift={this.handleTimeShift}
|
||||
onGroupByTime={this.handleGroupByTime}
|
||||
isDisabled={!isQuerySupportedByExplorer}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{noDBorMeas ? (
|
||||
<div className="query-builder--list-empty">
|
||||
<span>
|
||||
No <strong>Measurement</strong> selected
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="query-builder--list">
|
||||
<FancyScrollbar>
|
||||
{this.state.fields.map((fieldFunc, i) => {
|
||||
const selectedFields = getFieldsWithName(
|
||||
fieldFunc.value,
|
||||
fields
|
||||
)
|
||||
|
||||
const funcs: FieldFunc[] = getFuncsByFieldName(
|
||||
fieldFunc.value,
|
||||
fields
|
||||
)
|
||||
const fieldFuncs = selectedFields.length
|
||||
? selectedFields
|
||||
: [fieldFunc]
|
||||
|
||||
return (
|
||||
<FieldListItem
|
||||
key={i}
|
||||
onToggleField={this.handleToggleField}
|
||||
onApplyFuncsToField={this.handleApplyFuncs}
|
||||
isSelected={!!selectedFields.length}
|
||||
fieldFuncs={fieldFuncs}
|
||||
funcs={functionNames(funcs)}
|
||||
isKapacitorRule={isKapacitorRule}
|
||||
isDisabled={!isQuerySupportedByExplorer}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</FancyScrollbar>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private handleGroupByTime = (groupBy: GroupByOption): void => {
|
||||
this.props.onGroupByTime(groupBy.menuOption)
|
||||
}
|
||||
|
||||
handleFill = fill => {
|
||||
private handleFill = (fill: string): void => {
|
||||
this.props.onFill(fill)
|
||||
}
|
||||
|
||||
handleToggleField = field => {
|
||||
private handleToggleField = (field: Field) => {
|
||||
const {
|
||||
query,
|
||||
onToggleField,
|
||||
addInitialField,
|
||||
initialGroupByTime: time,
|
||||
isKapacitorRule,
|
||||
isQuerySupportedByExplorer,
|
||||
} = this.props
|
||||
const {fields, groupBy} = query
|
||||
if (!isQuerySupportedByExplorer) {
|
||||
return
|
||||
}
|
||||
const initialGroupBy = {...groupBy, time}
|
||||
|
||||
if (!_.size(fields)) {
|
||||
|
@ -86,7 +220,7 @@ class FieldList extends Component {
|
|||
onToggleField(field)
|
||||
}
|
||||
|
||||
handleApplyFuncs = fieldFunc => {
|
||||
private handleApplyFuncs = (fieldFunc: ApplyFuncsToFieldArgs): void => {
|
||||
const {
|
||||
query,
|
||||
removeFuncs,
|
||||
|
@ -109,11 +243,11 @@ class FieldList extends Component {
|
|||
applyFuncsToField(fieldFunc, groupBy)
|
||||
}
|
||||
|
||||
handleTimeShift = shift => {
|
||||
private handleTimeShift = (shift: TimeShiftOption): void => {
|
||||
this.props.onTimeShift(shift)
|
||||
}
|
||||
|
||||
_getFields = () => {
|
||||
private getFields = (): void => {
|
||||
const {database, measurement, retentionPolicy} = this.props.query
|
||||
const {source} = this.context
|
||||
const {querySource} = this.props
|
||||
|
@ -137,114 +271,6 @@ class FieldList extends Component {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
query: {database, measurement, fields = [], groupBy, fill, shifts},
|
||||
isKapacitorRule,
|
||||
} = this.props
|
||||
|
||||
const hasAggregates = numFunctions(fields) > 0
|
||||
const noDBorMeas = !database || !measurement
|
||||
|
||||
return (
|
||||
<div className="query-builder--column">
|
||||
<div className="query-builder--heading">
|
||||
<span>Fields</span>
|
||||
{hasAggregates ? (
|
||||
<QueryOptions
|
||||
fill={fill}
|
||||
shift={_.first(shifts)}
|
||||
groupBy={groupBy}
|
||||
onFill={this.handleFill}
|
||||
isKapacitorRule={isKapacitorRule}
|
||||
onTimeShift={this.handleTimeShift}
|
||||
onGroupByTime={this.handleGroupByTime}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{noDBorMeas ? (
|
||||
<div className="query-builder--list-empty">
|
||||
<span>
|
||||
No <strong>Measurement</strong> selected
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="query-builder--list">
|
||||
<FancyScrollbar>
|
||||
{this.state.fields.map((fieldFunc, i) => {
|
||||
const selectedFields = getFieldsWithName(
|
||||
fieldFunc.value,
|
||||
fields
|
||||
)
|
||||
|
||||
const funcs = getFuncsByFieldName(fieldFunc.value, fields)
|
||||
const fieldFuncs = selectedFields.length
|
||||
? selectedFields
|
||||
: [fieldFunc]
|
||||
|
||||
return (
|
||||
<FieldListItem
|
||||
key={i}
|
||||
onToggleField={this.handleToggleField}
|
||||
onApplyFuncsToField={this.handleApplyFuncs}
|
||||
isSelected={!!selectedFields.length}
|
||||
fieldFuncs={fieldFuncs}
|
||||
funcs={functionNames(funcs)}
|
||||
isKapacitorRule={isKapacitorRule}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</FancyScrollbar>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
FieldList.defaultProps = {
|
||||
isKapacitorRule: false,
|
||||
initialGroupByTime: null,
|
||||
}
|
||||
|
||||
FieldList.contextTypes = {
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
}
|
||||
|
||||
FieldList.propTypes = {
|
||||
query: shape({
|
||||
database: string,
|
||||
retentionPolicy: string,
|
||||
measurement: string,
|
||||
shifts: arrayOf(
|
||||
shape({
|
||||
label: string,
|
||||
unit: string,
|
||||
quantity: string,
|
||||
})
|
||||
),
|
||||
}).isRequired,
|
||||
onTimeShift: func,
|
||||
onToggleField: func.isRequired,
|
||||
onGroupByTime: func.isRequired,
|
||||
onFill: func,
|
||||
applyFuncsToField: func.isRequired,
|
||||
isKapacitorRule: bool,
|
||||
querySource: shape({
|
||||
links: shape({
|
||||
proxy: string.isRequired,
|
||||
}).isRequired,
|
||||
}),
|
||||
removeFuncs: func.isRequired,
|
||||
addInitialField: func,
|
||||
initialGroupByTime: string,
|
||||
}
|
||||
|
||||
export default FieldList
|
|
@ -1,18 +1,48 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Dropdown from 'shared/components/Dropdown'
|
||||
import React, {
|
||||
PureComponent,
|
||||
FocusEvent,
|
||||
ChangeEvent,
|
||||
KeyboardEvent,
|
||||
} from 'react'
|
||||
import Dropdown from 'src/shared/components/Dropdown'
|
||||
|
||||
import {NULL_STRING, NUMBER} from 'shared/constants/queryFillOptions'
|
||||
import {NULL_STRING, NUMBER} from 'src/shared/constants/queryFillOptions'
|
||||
|
||||
import queryFills from 'shared/data/queryFills'
|
||||
import queryFills from 'src/shared/data/queryFills'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
onChooseFill: (text: string) => void
|
||||
value: string
|
||||
size?: string
|
||||
theme?: string
|
||||
isDisabled?: boolean
|
||||
}
|
||||
|
||||
interface Item {
|
||||
type: string
|
||||
text: string
|
||||
}
|
||||
interface State {
|
||||
selected: Item
|
||||
currentNumberValue: string
|
||||
resetNumberValue: string
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class FillQuery extends Component {
|
||||
class FillQuery extends PureComponent<Props, State> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
size: 'sm',
|
||||
theme: 'blue',
|
||||
value: NULL_STRING,
|
||||
}
|
||||
|
||||
private numberInput: HTMLElement
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
const isNumberValue = !isNaN(Number(props.value))
|
||||
const isNumberValue: boolean = !isNaN(Number(props.value))
|
||||
|
||||
this.state = isNumberValue
|
||||
? {
|
||||
|
@ -27,66 +57,8 @@ class FillQuery extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleDropdown = item => {
|
||||
if (item.text === NUMBER) {
|
||||
this.setState({selected: item}, () => {
|
||||
this.numberInput.focus()
|
||||
})
|
||||
} else {
|
||||
this.setState({selected: item}, () => {
|
||||
this.props.onChooseFill(item.text)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleInputBlur = e => {
|
||||
const nextNumberValue = e.target.value
|
||||
? e.target.value
|
||||
: this.state.resetNumberValue || '0'
|
||||
|
||||
this.setState({
|
||||
currentNumberValue: nextNumberValue,
|
||||
resetNumberValue: nextNumberValue,
|
||||
})
|
||||
|
||||
this.props.onChooseFill(nextNumberValue)
|
||||
}
|
||||
|
||||
handleInputChange = e => {
|
||||
const currentNumberValue = e.target.value
|
||||
|
||||
this.setState({currentNumberValue})
|
||||
}
|
||||
|
||||
handleKeyDown = e => {
|
||||
if (e.key === 'Enter') {
|
||||
this.numberInput.blur()
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyUp = e => {
|
||||
if (e.key === 'Escape') {
|
||||
this.setState({currentNumberValue: this.state.resetNumberValue}, () => {
|
||||
this.numberInput.blur()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
getColor = theme => {
|
||||
switch (theme) {
|
||||
case 'BLUE':
|
||||
return 'plutonium'
|
||||
case 'GREEN':
|
||||
return 'malachite'
|
||||
case 'PURPLE':
|
||||
return 'astronaut'
|
||||
default:
|
||||
return 'plutonium'
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {size, theme} = this.props
|
||||
public render() {
|
||||
const {size, theme, isDisabled} = this.props
|
||||
const {selected, currentNumberValue} = this.state
|
||||
|
||||
return (
|
||||
|
@ -114,26 +86,70 @@ class FillQuery extends Component {
|
|||
buttonColor="btn-info"
|
||||
menuClass={`dropdown-${this.getColor(theme)}`}
|
||||
onChoose={this.handleDropdown}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
<label className="fill-query--label">Fill:</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {func, string} = PropTypes
|
||||
private handleDropdown = (item: Item): void => {
|
||||
if (item.text === NUMBER) {
|
||||
this.setState({selected: item}, () => {
|
||||
this.numberInput.focus()
|
||||
})
|
||||
} else {
|
||||
this.setState({selected: item}, () => {
|
||||
this.props.onChooseFill(item.text)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
FillQuery.defaultProps = {
|
||||
size: 'sm',
|
||||
theme: 'blue',
|
||||
value: NULL_STRING,
|
||||
}
|
||||
private handleInputBlur = (e: FocusEvent<HTMLInputElement>): void => {
|
||||
const nextNumberValue = e.target.value
|
||||
? e.target.value
|
||||
: this.state.resetNumberValue || '0'
|
||||
|
||||
FillQuery.propTypes = {
|
||||
onChooseFill: func.isRequired,
|
||||
value: string,
|
||||
size: string,
|
||||
theme: string,
|
||||
this.setState({
|
||||
currentNumberValue: nextNumberValue,
|
||||
resetNumberValue: nextNumberValue,
|
||||
})
|
||||
|
||||
this.props.onChooseFill(nextNumberValue)
|
||||
}
|
||||
|
||||
private handleInputChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||
const currentNumberValue = e.target.value
|
||||
|
||||
this.setState({currentNumberValue})
|
||||
}
|
||||
|
||||
private handleKeyDown = (e: KeyboardEvent<HTMLInputElement>): void => {
|
||||
if (e.key === 'Enter') {
|
||||
this.numberInput.blur()
|
||||
}
|
||||
}
|
||||
|
||||
private handleKeyUp = (e: KeyboardEvent<HTMLInputElement>): void => {
|
||||
if (e.key === 'Escape') {
|
||||
this.setState({currentNumberValue: this.state.resetNumberValue}, () => {
|
||||
this.numberInput.blur()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private getColor = (theme: string): string => {
|
||||
switch (theme) {
|
||||
case 'BLUE':
|
||||
return 'plutonium'
|
||||
case 'GREEN':
|
||||
return 'malachite'
|
||||
case 'PURPLE':
|
||||
return 'astronaut'
|
||||
default:
|
||||
return 'plutonium'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FillQuery
|
|
@ -6,7 +6,7 @@ import _ from 'lodash'
|
|||
import {showMeasurements} from 'src/shared/apis/metaQuery'
|
||||
import showMeasurementsParser from 'src/shared/parsing/showMeasurements'
|
||||
|
||||
import {Query, Source} from 'src/types'
|
||||
import {QueryConfig, Source} from 'src/types'
|
||||
|
||||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||
import MeasurementListFilter from 'src/shared/components/MeasurementListFilter'
|
||||
|
@ -14,11 +14,12 @@ import MeasurementListItem from 'src/shared/components/MeasurementListItem'
|
|||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
query: Query
|
||||
query: QueryConfig
|
||||
querySource: Source
|
||||
onChooseTag: () => void
|
||||
onGroupByTag: () => void
|
||||
onToggleTagAcceptance: () => void
|
||||
isQuerySupportedByExplorer: boolean
|
||||
onChooseMeasurement: (measurement: string) => void
|
||||
}
|
||||
|
||||
|
@ -117,7 +118,13 @@ class MeasurementList extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const {query, querySource, onChooseTag, onGroupByTag} = this.props
|
||||
const {
|
||||
query,
|
||||
querySource,
|
||||
onChooseTag,
|
||||
onGroupByTag,
|
||||
isQuerySupportedByExplorer,
|
||||
} = this.props
|
||||
const {database, areTagsAccepted} = query
|
||||
const {filtered} = this.state
|
||||
|
||||
|
@ -147,6 +154,7 @@ class MeasurementList extends PureComponent<Props, State> {
|
|||
areTagsAccepted={areTagsAccepted}
|
||||
onAcceptReject={this.handleAcceptReject}
|
||||
isActive={measurement === query.measurement}
|
||||
isQuerySupportedByExplorer={isQuerySupportedByExplorer}
|
||||
numTagsActive={Object.keys(query.tags).length}
|
||||
onChooseMeasurement={this.handleChoosemeasurement}
|
||||
/>
|
||||
|
|
|
@ -38,6 +38,7 @@ interface Props {
|
|||
onChooseTag: () => void
|
||||
onGroupByTag: () => void
|
||||
onAcceptReject: () => void
|
||||
isQuerySupportedByExplorer: boolean
|
||||
onChooseMeasurement: (measurement: string) => () => void
|
||||
}
|
||||
|
||||
|
@ -62,6 +63,7 @@ class MeasurementListItem extends PureComponent<Props, State> {
|
|||
onGroupByTag,
|
||||
numTagsActive,
|
||||
areTagsAccepted,
|
||||
isQuerySupportedByExplorer,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
|
@ -80,6 +82,7 @@ class MeasurementListItem extends PureComponent<Props, State> {
|
|||
<div
|
||||
className={classnames('flip-toggle', {
|
||||
flipped: areTagsAccepted,
|
||||
disabled: !isQuerySupportedByExplorer,
|
||||
})}
|
||||
onClick={this.handleAcceptReject}
|
||||
>
|
||||
|
@ -96,6 +99,7 @@ class MeasurementListItem extends PureComponent<Props, State> {
|
|||
querySource={querySource}
|
||||
onChooseTag={onChooseTag}
|
||||
onGroupByTag={onGroupByTag}
|
||||
isQuerySupportedByExplorer={isQuerySupportedByExplorer}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -105,6 +109,11 @@ class MeasurementListItem extends PureComponent<Props, State> {
|
|||
private handleAcceptReject = (e: MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation()
|
||||
|
||||
const {isQuerySupportedByExplorer} = this.props
|
||||
if (!isQuerySupportedByExplorer) {
|
||||
return
|
||||
}
|
||||
|
||||
const {onAcceptReject} = this.props
|
||||
onAcceptReject()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
import React, {PureComponent, ComponentClass} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {bindActionCreators} from 'redux'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
import {dismissOverlay} from 'src/shared/actions/overlayTechnology'
|
||||
|
||||
interface Props {
|
||||
OverlayNode?: ComponentClass<any>
|
||||
dismissOnClickOutside?: boolean
|
||||
dismissOnEscape?: boolean
|
||||
transitionTime?: number
|
||||
handleDismissOverlay: () => void
|
||||
}
|
||||
|
||||
interface State {
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
export const OverlayContext = React.createContext()
|
||||
|
||||
@ErrorHandling
|
||||
class Overlay extends PureComponent<Props, State> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
dismissOnClickOutside: false,
|
||||
dismissOnEscape: false,
|
||||
transitionTime: 300,
|
||||
}
|
||||
|
||||
private animationTimer: number
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
visible: false,
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps) {
|
||||
if (prevProps.OverlayNode === null && this.props.OverlayNode) {
|
||||
return this.setState({visible: true})
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {OverlayNode} = this.props
|
||||
|
||||
return (
|
||||
<OverlayContext.Provider
|
||||
value={{
|
||||
onDismissOverlay: this.handleAnimateDismiss,
|
||||
}}
|
||||
>
|
||||
<div className={this.overlayClass}>
|
||||
<div className="overlay--dialog">{OverlayNode}</div>
|
||||
<div className="overlay--mask" onClick={this.handleClickOutside} />
|
||||
</div>
|
||||
</OverlayContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
private get overlayClass(): string {
|
||||
const {visible} = this.state
|
||||
return `overlay-tech ${visible ? 'show' : ''}`
|
||||
}
|
||||
|
||||
public handleClickOutside = () => {
|
||||
const {handleDismissOverlay, dismissOnClickOutside} = this.props
|
||||
|
||||
if (dismissOnClickOutside) {
|
||||
handleDismissOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
public handleAnimateDismiss = () => {
|
||||
const {transitionTime} = this.props
|
||||
this.setState({visible: false})
|
||||
this.animationTimer = window.setTimeout(this.handleDismiss, transitionTime)
|
||||
}
|
||||
|
||||
public handleDismiss = () => {
|
||||
const {handleDismissOverlay} = this.props
|
||||
handleDismissOverlay()
|
||||
clearTimeout(this.animationTimer)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({
|
||||
overlayTechnology: {
|
||||
OverlayNode,
|
||||
options: {dismissOnClickOutside, dismissOnEscape, transitionTime},
|
||||
},
|
||||
}) => ({
|
||||
OverlayNode,
|
||||
dismissOnClickOutside,
|
||||
dismissOnEscape,
|
||||
transitionTime,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleDismissOverlay: bindActionCreators(dismissOverlay, dispatch),
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Overlay)
|
|
@ -1,10 +1,23 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, {SFC} from 'react'
|
||||
|
||||
import {GroupBy, TimeShift} from 'src/types'
|
||||
|
||||
import GroupByTimeDropdown from 'src/data_explorer/components/GroupByTimeDropdown'
|
||||
import TimeShiftDropdown from 'src/shared/components/TimeShiftDropdown'
|
||||
import FillQuery from 'shared/components/FillQuery'
|
||||
import FillQuery from 'src/shared/components/FillQuery'
|
||||
|
||||
const QueryOptions = ({
|
||||
interface Props {
|
||||
fill: string
|
||||
onFill: (fill: string) => void
|
||||
groupBy: GroupBy
|
||||
shift: TimeShift
|
||||
onGroupByTime: (groupBy: GroupBy) => void
|
||||
isKapacitorRule: boolean
|
||||
onTimeShift: (shift: TimeShift) => void
|
||||
isDisabled: boolean
|
||||
}
|
||||
|
||||
const QueryOptions: SFC<Props> = ({
|
||||
fill,
|
||||
shift,
|
||||
onFill,
|
||||
|
@ -12,36 +25,25 @@ const QueryOptions = ({
|
|||
onTimeShift,
|
||||
onGroupByTime,
|
||||
isKapacitorRule,
|
||||
isDisabled,
|
||||
}) => (
|
||||
<div className="query-builder--groupby-fill-container">
|
||||
<GroupByTimeDropdown
|
||||
selected={groupBy.time}
|
||||
onChooseGroupByTime={onGroupByTime}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
{isKapacitorRule ? null : (
|
||||
<TimeShiftDropdown
|
||||
selected={shift && shift.label}
|
||||
onChooseTimeShift={onTimeShift}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
)}
|
||||
{isKapacitorRule ? null : <FillQuery value={fill} onChooseFill={onFill} />}
|
||||
{isKapacitorRule ? null : (
|
||||
<FillQuery value={fill} onChooseFill={onFill} isDisabled={isDisabled} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
const {bool, func, shape, string} = PropTypes
|
||||
|
||||
QueryOptions.propTypes = {
|
||||
fill: string,
|
||||
onFill: func.isRequired,
|
||||
groupBy: shape({
|
||||
time: string,
|
||||
}).isRequired,
|
||||
shift: shape({
|
||||
label: string,
|
||||
}),
|
||||
onGroupByTime: func.isRequired,
|
||||
isKapacitorRule: bool.isRequired,
|
||||
onTimeShift: func.isRequired,
|
||||
}
|
||||
|
||||
export default QueryOptions
|
|
@ -9,7 +9,6 @@ const actionBinder = (id, action) => (...args) => action(id, ...args)
|
|||
|
||||
const SchemaExplorer = ({
|
||||
query,
|
||||
query: {id},
|
||||
source,
|
||||
initialGroupByTime,
|
||||
actions: {
|
||||
|
@ -26,39 +25,50 @@ const SchemaExplorer = ({
|
|||
applyFuncsToField,
|
||||
toggleTagAcceptance,
|
||||
},
|
||||
}) => (
|
||||
<div className="query-builder">
|
||||
<DatabaseList
|
||||
query={query}
|
||||
querySource={source}
|
||||
onChooseNamespace={actionBinder(id, chooseNamespace)}
|
||||
/>
|
||||
<MeasurementList
|
||||
source={source}
|
||||
query={query}
|
||||
querySource={source}
|
||||
onChooseTag={actionBinder(id, chooseTag)}
|
||||
onGroupByTag={actionBinder(id, groupByTag)}
|
||||
onChooseMeasurement={actionBinder(id, chooseMeasurement)}
|
||||
onToggleTagAcceptance={actionBinder(id, toggleTagAcceptance)}
|
||||
/>
|
||||
<FieldList
|
||||
source={source}
|
||||
query={query}
|
||||
querySource={source}
|
||||
onFill={actionBinder(id, fill)}
|
||||
initialGroupByTime={initialGroupByTime}
|
||||
onTimeShift={actionBinder(id, timeShift)}
|
||||
removeFuncs={actionBinder(id, removeFuncs)}
|
||||
onToggleField={actionBinder(id, toggleField)}
|
||||
onGroupByTime={actionBinder(id, groupByTime)}
|
||||
addInitialField={actionBinder(id, addInitialField)}
|
||||
applyFuncsToField={actionBinder(id, applyFuncsToField)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
isQuerySupportedByExplorer = true,
|
||||
}) => {
|
||||
const {id} = query
|
||||
|
||||
const {func, shape, string} = PropTypes
|
||||
return (
|
||||
<div className="query-builder">
|
||||
<DatabaseList
|
||||
query={query}
|
||||
querySource={source}
|
||||
onChooseNamespace={actionBinder(id, chooseNamespace)}
|
||||
/>
|
||||
<MeasurementList
|
||||
source={source}
|
||||
query={query}
|
||||
querySource={source}
|
||||
onChooseTag={actionBinder(id, chooseTag)}
|
||||
onGroupByTag={actionBinder(id, groupByTag)}
|
||||
onChooseMeasurement={actionBinder(id, chooseMeasurement)}
|
||||
onToggleTagAcceptance={actionBinder(id, toggleTagAcceptance)}
|
||||
isQuerySupportedByExplorer={isQuerySupportedByExplorer}
|
||||
/>
|
||||
<FieldList
|
||||
source={source}
|
||||
query={query}
|
||||
querySource={source}
|
||||
onFill={actionBinder(id, fill)}
|
||||
initialGroupByTime={initialGroupByTime}
|
||||
onTimeShift={actionBinder(id, timeShift)}
|
||||
removeFuncs={actionBinder(id, removeFuncs)}
|
||||
onToggleField={actionBinder(id, toggleField)}
|
||||
onGroupByTime={actionBinder(id, groupByTime)}
|
||||
addInitialField={actionBinder(id, addInitialField)}
|
||||
applyFuncsToField={actionBinder(id, applyFuncsToField)}
|
||||
isQuerySupportedByExplorer={isQuerySupportedByExplorer}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const {bool, func, shape, string} = PropTypes
|
||||
|
||||
SchemaExplorer.defaultProps = {
|
||||
isQuerySupportedByExplorer: true,
|
||||
}
|
||||
|
||||
SchemaExplorer.propTypes = {
|
||||
query: shape({
|
||||
|
@ -80,6 +90,7 @@ SchemaExplorer.propTypes = {
|
|||
}).isRequired,
|
||||
source: shape({}),
|
||||
initialGroupByTime: string.isRequired,
|
||||
isQuerySupportedByExplorer: bool,
|
||||
}
|
||||
|
||||
export default SchemaExplorer
|
||||
|
|
|
@ -40,6 +40,7 @@ interface Props {
|
|||
querySource: Source
|
||||
onChooseTag: () => void
|
||||
onGroupByTag: () => void
|
||||
isQuerySupportedByExplorer: boolean
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -129,7 +130,12 @@ class TagList extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const {query, onChooseTag, onGroupByTag} = this.props
|
||||
const {
|
||||
query,
|
||||
onChooseTag,
|
||||
onGroupByTag,
|
||||
isQuerySupportedByExplorer,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className="query-builder--sub-list">
|
||||
|
@ -142,6 +148,7 @@ class TagList extends PureComponent<Props, State> {
|
|||
onGroupByTag={onGroupByTag}
|
||||
selectedTagValues={query.tags[tagKey] || []}
|
||||
isUsingGroupBy={query.groupBy.tags.indexOf(tagKey) > -1}
|
||||
isQuerySupportedByExplorer={isQuerySupportedByExplorer}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -14,6 +14,7 @@ interface Props {
|
|||
selectedTagValues: string[]
|
||||
isUsingGroupBy?: boolean
|
||||
onChooseTag: (tag: Tag) => void
|
||||
isQuerySupportedByExplorer: boolean
|
||||
onGroupByTag: (tagKey: string) => void
|
||||
}
|
||||
|
||||
|
@ -36,10 +37,16 @@ class TagListItem extends PureComponent<Props, State> {
|
|||
this.handleGroupBy = this.handleGroupBy.bind(this)
|
||||
this.handleClickKey = this.handleClickKey.bind(this)
|
||||
this.handleFilterText = this.handleFilterText.bind(this)
|
||||
this.handleInputClick = this.handleInputClick.bind(this)
|
||||
}
|
||||
|
||||
public handleChoose(tagValue: string, e: MouseEvent<HTMLElement>) {
|
||||
e.stopPropagation()
|
||||
|
||||
const {isQuerySupportedByExplorer} = this.props
|
||||
if (!isQuerySupportedByExplorer) {
|
||||
return
|
||||
}
|
||||
this.props.onChooseTag({key: this.props.tagKey, value: tagValue})
|
||||
}
|
||||
|
||||
|
@ -67,7 +74,11 @@ class TagListItem extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public handleGroupBy(e) {
|
||||
const {isQuerySupportedByExplorer} = this.props
|
||||
e.stopPropagation()
|
||||
if (!isQuerySupportedByExplorer) {
|
||||
return
|
||||
}
|
||||
this.props.onGroupByTag(this.props.tagKey)
|
||||
}
|
||||
|
||||
|
@ -76,7 +87,11 @@ class TagListItem extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public renderTagValues() {
|
||||
const {tagValues, selectedTagValues} = this.props
|
||||
const {
|
||||
tagValues,
|
||||
selectedTagValues,
|
||||
isQuerySupportedByExplorer,
|
||||
} = this.props
|
||||
if (!tagValues || !tagValues.length) {
|
||||
return <div>no tag values</div>
|
||||
}
|
||||
|
@ -103,6 +118,7 @@ class TagListItem extends PureComponent<Props, State> {
|
|||
{filtered.map(v => {
|
||||
const cx = classnames('query-builder--list-item', {
|
||||
active: selectedTagValues.indexOf(v) > -1,
|
||||
disabled: !isQuerySupportedByExplorer,
|
||||
})
|
||||
return (
|
||||
<div
|
||||
|
@ -123,7 +139,12 @@ class TagListItem extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const {tagKey, tagValues, isUsingGroupBy} = this.props
|
||||
const {
|
||||
tagKey,
|
||||
tagValues,
|
||||
isUsingGroupBy,
|
||||
isQuerySupportedByExplorer,
|
||||
} = this.props
|
||||
const {isOpen} = this.state
|
||||
const tagItemLabel = `${tagKey} — ${tagValues.length}`
|
||||
|
||||
|
@ -142,6 +163,7 @@ class TagListItem extends PureComponent<Props, State> {
|
|||
className={classnames('btn btn-xs group-by-tag', {
|
||||
'btn-default': !isUsingGroupBy,
|
||||
'btn-primary': isUsingGroupBy,
|
||||
disabled: !isQuerySupportedByExplorer,
|
||||
})}
|
||||
onClick={this.handleGroupBy}
|
||||
>
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Dropdown from 'shared/components/Dropdown'
|
||||
import {TIME_SHIFTS} from 'shared/constants/timeShift'
|
||||
|
||||
const TimeShiftDropdown = ({selected, onChooseTimeShift}) => (
|
||||
<div className="group-by-time">
|
||||
<label className="group-by-time--label">Compare:</label>
|
||||
<Dropdown
|
||||
className="group-by-time--dropdown"
|
||||
buttonColor="btn-info"
|
||||
items={TIME_SHIFTS}
|
||||
onChoose={onChooseTimeShift}
|
||||
selected={selected || 'none'}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
const {func, string} = PropTypes
|
||||
|
||||
TimeShiftDropdown.propTypes = {
|
||||
selected: string,
|
||||
onChooseTimeShift: func.isRequired,
|
||||
}
|
||||
|
||||
export default TimeShiftDropdown
|
|
@ -0,0 +1,31 @@
|
|||
import React, {SFC} from 'react'
|
||||
|
||||
import Dropdown from 'src/shared/components/Dropdown'
|
||||
import {TIME_SHIFTS} from 'src/shared/constants/timeShift'
|
||||
import {TimeShift} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
selected: string
|
||||
onChooseTimeShift: (timeShift: TimeShift) => void
|
||||
isDisabled: boolean
|
||||
}
|
||||
|
||||
const TimeShiftDropdown: SFC<Props> = ({
|
||||
selected,
|
||||
onChooseTimeShift,
|
||||
isDisabled,
|
||||
}) => (
|
||||
<div className="group-by-time">
|
||||
<label className="group-by-time--label">Compare:</label>
|
||||
<Dropdown
|
||||
className="group-by-time--dropdown"
|
||||
buttonColor="btn-info"
|
||||
items={TIME_SHIFTS}
|
||||
onChoose={onChooseTimeShift}
|
||||
selected={selected || 'none'}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default TimeShiftDropdown
|
|
@ -1 +1 @@
|
|||
export const OVERLAY_TECHNOLOGY = 'overlay-technology'
|
||||
export const OVERLAY_TECHNOLOGY = 'ceo'
|
||||
|
|
|
@ -59,7 +59,7 @@ export const generateThresholdsListHexs = ({
|
|||
}
|
||||
|
||||
if (!lastValue) {
|
||||
return {...defaultColoring, textColor: baseColor}
|
||||
return {...defaultColoring, textColor: baseColor.hex}
|
||||
}
|
||||
|
||||
// If the single stat is above a line graph never have a background color
|
||||
|
|
|
@ -410,6 +410,13 @@ export const VIS_VIEWS = [GRAPH, TABLE]
|
|||
|
||||
// InfluxQL Macros
|
||||
export const TEMP_VAR_INTERVAL = ':interval:'
|
||||
export const TEMP_VAR_DASHBOARD_TIME = ':dashboardTime:'
|
||||
export const TEMP_VAR_UPPER_DASHBOARD_TIME = ':upperDashboardTime:'
|
||||
export const PREDEFINED_TEMP_VARS = [
|
||||
TEMP_VAR_INTERVAL,
|
||||
TEMP_VAR_DASHBOARD_TIME,
|
||||
TEMP_VAR_UPPER_DASHBOARD_TIME,
|
||||
]
|
||||
export const INITIAL_GROUP_BY_TIME = '10s'
|
||||
export const AUTO_GROUP_BY = 'auto'
|
||||
|
||||
|
@ -443,7 +450,7 @@ export const intervalValuesPoints = [
|
|||
export const interval = {
|
||||
id: 'interval',
|
||||
type: 'autoGroupBy',
|
||||
tempVar: ':interval:',
|
||||
tempVar: TEMP_VAR_INTERVAL,
|
||||
label: 'automatically determine the best group by time',
|
||||
values: intervalValuesPoints,
|
||||
}
|
||||
|
|
|
@ -438,6 +438,13 @@ export const notifyCellDeleted = name => ({
|
|||
message: `Deleted "${name}" from dashboard.`,
|
||||
})
|
||||
|
||||
export const notifyBuilderDisabled = () => ({
|
||||
type: 'info',
|
||||
icon: 'graphline',
|
||||
duration: 7500,
|
||||
message: `Your query contains a user-defined Template Variable. The Schema Explorer cannot render the query and is disabled.`,
|
||||
})
|
||||
|
||||
// Rule Builder Notifications
|
||||
// ----------------------------------------------------------------------------
|
||||
export const notifyAlertRuleCreated = () => ({
|
||||
|
|
|
@ -42,6 +42,12 @@ export const firstFieldName = fields => _.head(fieldNamesDeep(fields))
|
|||
export const hasField = (fieldName, fields) =>
|
||||
fieldNamesDeep(fields).some(f => f === fieldName)
|
||||
|
||||
/**
|
||||
* getAllFields and funcs with fieldName
|
||||
* @param {string} fieldName
|
||||
* @param {FieldFunc[]} fields
|
||||
* @returns {FieldFunc[]}
|
||||
*/
|
||||
// getAllFields and funcs with fieldName
|
||||
export const getFieldsWithName = (fieldName, fields) =>
|
||||
getFieldsDeep(fields).filter(f => f.value === fieldName)
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
const initialState = {
|
||||
options: {
|
||||
dismissOnClickOutside: false,
|
||||
dismissOnEscape: false,
|
||||
transitionTime: 300,
|
||||
},
|
||||
OverlayNode: null,
|
||||
}
|
||||
|
||||
export default function overlayTechnology(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case 'SHOW_OVERLAY': {
|
||||
const {OverlayNode, options} = action.payload
|
||||
|
||||
return {...state, OverlayNode, options}
|
||||
}
|
||||
|
||||
case 'DISMISS_OVERLAY': {
|
||||
const {options} = initialState
|
||||
return {
|
||||
...state,
|
||||
OverlayNode: null,
|
||||
options,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
|
@ -8,6 +8,10 @@ import LayoutRenderer from 'shared/components/LayoutRenderer'
|
|||
|
||||
import {fixtureStatusPageCells} from 'src/status/fixtures'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {
|
||||
TEMP_VAR_DASHBOARD_TIME,
|
||||
TEMP_VAR_UPPER_DASHBOARD_TIME,
|
||||
} from 'src/shared/constants'
|
||||
|
||||
@ErrorHandling
|
||||
class StatusPage extends Component {
|
||||
|
@ -25,7 +29,7 @@ class StatusPage extends Component {
|
|||
|
||||
const dashboardTime = {
|
||||
id: 'dashtime',
|
||||
tempVar: ':dashboardTime:',
|
||||
tempVar: TEMP_VAR_DASHBOARD_TIME,
|
||||
type: 'constant',
|
||||
values: [
|
||||
{
|
||||
|
@ -38,7 +42,7 @@ class StatusPage extends Component {
|
|||
|
||||
const upperDashboardTime = {
|
||||
id: 'upperdashtime',
|
||||
tempVar: ':upperDashboardTime:',
|
||||
tempVar: TEMP_VAR_UPPER_DASHBOARD_TIME,
|
||||
type: 'constant',
|
||||
values: [
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes'
|
||||
import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants'
|
||||
|
||||
export const fixtureStatusPageCells = [
|
||||
{
|
||||
|
@ -13,8 +14,7 @@ export const fixtureStatusPageCells = [
|
|||
colors: DEFAULT_LINE_COLORS,
|
||||
queries: [
|
||||
{
|
||||
query:
|
||||
'SELECT count("value") AS "count_value" FROM "chronograf"."autogen"."alerts" WHERE time > :dashboardTime: GROUP BY time(1d)',
|
||||
query: `SELECT count("value") AS "count_value" FROM "chronograf"."autogen"."alerts" WHERE time > ${TEMP_VAR_DASHBOARD_TIME} GROUP BY time(1d)`,
|
||||
label: 'Events',
|
||||
queryConfig: {
|
||||
database: 'chronograf',
|
||||
|
|
|
@ -13,6 +13,7 @@ import adminReducers from 'src/admin/reducers'
|
|||
import kapacitorReducers from 'src/kapacitor/reducers'
|
||||
import dashboardUI from 'src/dashboards/reducers/ui'
|
||||
import cellEditorOverlay from 'src/dashboards/reducers/cellEditorOverlay'
|
||||
import overlayTechnology from 'src/shared/reducers/overlayTechnology'
|
||||
import dashTimeV1 from 'src/dashboards/reducers/dashTimeV1'
|
||||
import persistStateEnhancer from './persistStateEnhancer'
|
||||
|
||||
|
@ -24,6 +25,7 @@ const rootReducer = combineReducers({
|
|||
...adminReducers,
|
||||
dashboardUI,
|
||||
cellEditorOverlay,
|
||||
overlayTechnology,
|
||||
dashTimeV1,
|
||||
routing: routerReducer,
|
||||
})
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
@import 'layout/page-header';
|
||||
@import 'layout/page-subsections';
|
||||
@import 'layout/sidebar';
|
||||
@import 'layout/overlay';
|
||||
@import 'layout/overlay-technology';
|
||||
|
||||
// Components
|
||||
@import 'components/annotations';
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/*
|
||||
Flip Toggle
|
||||
-------------------------------------------------------------
|
||||
Toggles between 2 options using a 3D transition
|
||||
Aesthetic and space conservative
|
||||
Flip Toggle
|
||||
------------------------------------------------------------------------------
|
||||
Toggles between 2 options using a 3D transition
|
||||
Aesthetic and space conservative
|
||||
*/
|
||||
|
||||
$flip-toggle-text: $g11-sidewalk;
|
||||
|
@ -62,4 +62,20 @@ $flip-toggle-size: 28px;
|
|||
/* Flip Animation happens on class toggle */
|
||||
.flip-toggle.flipped .flip-toggle--container {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
/*
|
||||
Disabled State
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
.flip-toggle.disabled,
|
||||
.flip-toggle.disabled:hover {
|
||||
.flip-toggle--front,
|
||||
.flip-toggle--back {
|
||||
cursor: not-allowed;
|
||||
background-color: $g3-castle;
|
||||
color: $g9-mountain;
|
||||
border-color: $g5-pepper;
|
||||
}
|
||||
}
|
|
@ -98,6 +98,20 @@
|
|||
white-space: nowrap;
|
||||
margin-right: 8px;
|
||||
}
|
||||
/* Disabled State */
|
||||
&.disabled {
|
||||
font-style: italic;
|
||||
color: $query-builder--list-item-text-disabled;
|
||||
|
||||
&:hover {
|
||||
cursor: default;
|
||||
background-color: $query-builder--list-item-bg;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $query-builder--list-item-bg-active;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Filter Element */
|
||||
.query-builder--filter {
|
||||
|
@ -156,6 +170,10 @@
|
|||
border-radius: 50%;
|
||||
transition: transform 0.25s ease, opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.disabled &:after {
|
||||
background-color: $g5-pepper;
|
||||
}
|
||||
}
|
||||
.query-builder--list-item.active .query-builder--checkbox:after {
|
||||
opacity: 1;
|
||||
|
|
|
@ -63,6 +63,7 @@ $query-builder--list-item-bg-hover: $g4-onyx;
|
|||
$query-builder--list-item-text-hover: $g15-platinum;
|
||||
$query-builder--list-item-bg-active: $g4-onyx;
|
||||
$query-builder--list-item-text-active: $g18-cloud;
|
||||
$query-builder--list-item-text-disabled: $g9-mountain;
|
||||
|
||||
$query-builder--sub-list-gutter: 24px;
|
||||
$query-builder--sub-list-bg: $query-builder--list-item-bg-active;
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
Overlay Technology Styles
|
||||
----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
%overlay-styles {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.overlay--mask {
|
||||
@extend %overlay-styles;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s ease;
|
||||
@include gradient-diag-down($c-pool,$c-comet);
|
||||
}
|
||||
|
||||
.overlay--dialog {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
transform: translateY(72px);
|
||||
opacity: 0;
|
||||
transition: transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.overlay-tech {
|
||||
@extend %overlay-styles;
|
||||
visibility: hidden;
|
||||
transition: all 0.25s ease;
|
||||
z-index: 9999;;
|
||||
|
||||
&.show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
// Open State
|
||||
.overlay-tech.show {
|
||||
.overlay--mask {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.overlay--dialog {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
Manages Overlays
|
||||
----------------------------------------------
|
||||
*/
|
||||
|
||||
.page, .sidebar {
|
||||
position: relative;
|
||||
}
|
||||
.page {
|
||||
z-index: 2;
|
||||
}
|
||||
.sidebar {
|
||||
z-index: 1;
|
||||
|
||||
// Ensures that sidebar menus appear above the rest of the app on hover
|
||||
&:hover {z-index: 2;}
|
||||
&:hover + .page {z-index: 1;}
|
||||
}
|
||||
|
||||
// Make Overlay Technology full screen
|
||||
.overlay-technology {
|
||||
left: -($sidebar--width) !important;
|
||||
|
||||
// Hacky way to ensure proper appearance of file upload modal
|
||||
// Needed to have a this div nested inside of itself for the
|
||||
// Drag & drop feature to work correctly
|
||||
.overlay-technology {
|
||||
left: 0 !important;
|
||||
&:before {display: none;}
|
||||
}
|
||||
}
|
|
@ -341,4 +341,18 @@ span.icon.sidebar--icon.sidebar--icon__superadmin {
|
|||
transform: translate(-50%,-50%) rotate(45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensures that sidebar menus appear above the rest of the app on hover
|
||||
.page, .sidebar {
|
||||
position: relative;
|
||||
}
|
||||
.page {
|
||||
z-index: 2;
|
||||
}
|
||||
.sidebar {
|
||||
z-index: 1;
|
||||
|
||||
&:hover {z-index: 2;}
|
||||
&:hover + .page {z-index: 1;}
|
||||
}
|
|
@ -1,13 +1,27 @@
|
|||
/*
|
||||
Styles for Overlay Technology (aka Cell Edit Mode)
|
||||
------------------------------------------------------
|
||||
Styles for Cell Editor Overlay
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
$overlay-controls-height: 60px;
|
||||
$overlay-controls-bg: $g2-kevlar;
|
||||
$overlay-z: 100;
|
||||
|
||||
.overlay-technology {
|
||||
|
||||
// Make Overlay Technology full screen
|
||||
.ceo {
|
||||
left: -($sidebar--width) !important;
|
||||
|
||||
// Hacky way to ensure proper appearance of file upload modal
|
||||
// Needed to have a this div nested inside of itself for the
|
||||
// Drag & drop feature to work correctly
|
||||
.ceo {
|
||||
left: 0 !important;
|
||||
&:before {display: none;}
|
||||
}
|
||||
}
|
||||
|
||||
.ceo {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
@ -79,13 +93,13 @@ $overlay-z: 100;
|
|||
@include no-user-select;
|
||||
}
|
||||
}
|
||||
.overlay-technology--editor {
|
||||
.ceo--editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
height: 100%;
|
||||
}
|
||||
.overlay-technology--editor .query-maker--empty {
|
||||
.ceo--editor .query-maker--empty {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.overlay-controls .confirm-or-cancel {
|
||||
|
@ -93,18 +107,18 @@ $overlay-z: 100;
|
|||
}
|
||||
|
||||
/* Graph editing in Dashboards is a little smaller so the dash can be seen in the background */
|
||||
.overlay-technology .graph {
|
||||
.ceo .graph {
|
||||
margin: 0 15%;
|
||||
}
|
||||
.overlay-technology .query-maker {
|
||||
.ceo .query-maker {
|
||||
flex: 1 0 0%;
|
||||
padding: 0 18px;
|
||||
margin: 0;
|
||||
background-color: $g2-kevlar;
|
||||
}
|
||||
.overlay-technology .query-maker--tabs {
|
||||
.ceo .query-maker--tabs {
|
||||
margin-top: 0;
|
||||
}
|
||||
.overlay-technology .query-maker--tab-contents {
|
||||
.ceo .query-maker--tab-contents {
|
||||
margin-bottom: 8px;
|
||||
}
|
|
@ -486,10 +486,10 @@ $dash-graph-options-arrow: 8px;
|
|||
@import '../components/template-control-bar';
|
||||
|
||||
/*
|
||||
Overylay Technology (Cell Edit Mode)
|
||||
Cell Editor Overlay
|
||||
------------------------------------------------------
|
||||
*/
|
||||
@import 'overlay-technology';
|
||||
@import 'cell-editor-overlay';
|
||||
|
||||
/*
|
||||
Template Variables Manager
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {Query} from 'src/types'
|
||||
import {QueryConfig} from 'src/types'
|
||||
import {ColorString} from 'src/types/colors'
|
||||
|
||||
interface Axis {
|
||||
bounds: [string, string]
|
||||
label: string
|
||||
|
@ -9,18 +10,18 @@ interface Axis {
|
|||
scale: string
|
||||
}
|
||||
|
||||
interface Axes {
|
||||
export interface Axes {
|
||||
x: Axis
|
||||
y: Axis
|
||||
}
|
||||
|
||||
interface FieldName {
|
||||
export interface FieldName {
|
||||
internalName: string
|
||||
displayName: string
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
interface TableOptions {
|
||||
export interface TableOptions {
|
||||
verticalTimeAxis: boolean
|
||||
sortBy: FieldName
|
||||
wrapping: string
|
||||
|
@ -33,7 +34,7 @@ interface CellLinks {
|
|||
|
||||
export interface CellQuery {
|
||||
query: string
|
||||
queryConfig: Query
|
||||
queryConfig: QueryConfig
|
||||
}
|
||||
|
||||
export interface Legend {
|
||||
|
@ -41,7 +42,7 @@ export interface Legend {
|
|||
orientation?: string
|
||||
}
|
||||
|
||||
interface DecimalPlaces {
|
||||
export interface DecimalPlaces {
|
||||
isEnforced: boolean
|
||||
digits: number
|
||||
}
|
||||
|
@ -75,3 +76,21 @@ export interface Template {
|
|||
tempVar: string
|
||||
values: TemplateValue[]
|
||||
}
|
||||
|
||||
export type CellEditorOverlayActionsFunc = (id: string, ...args: any[]) => any
|
||||
|
||||
export interface CellEditorOverlayActions {
|
||||
chooseNamespace: (id: string) => void
|
||||
chooseMeasurement: (id: string) => void
|
||||
applyFuncsToField: (id: string) => void
|
||||
chooseTag: (id: string) => void
|
||||
groupByTag: (id: string) => void
|
||||
toggleField: (id: string) => void
|
||||
groupByTime: (id: string) => void
|
||||
toggleTagAcceptance: (id: string) => void
|
||||
fill: (id: string) => void
|
||||
editRawTextAsync: (url: string, id: string, text: string) => Promise<void>
|
||||
addInitialField: (id: string) => void
|
||||
removeFuncs: (id: string) => void
|
||||
timeShift: (id: string) => void
|
||||
}
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
import {AuthLinks, Organization, Role, User, Me} from './auth'
|
||||
import {Query, QueryConfig, TimeRange} from './query'
|
||||
import {Template, Cell, CellQuery, Legend} from './dashboard'
|
||||
import {
|
||||
GroupBy,
|
||||
QueryConfig,
|
||||
Status,
|
||||
TimeRange,
|
||||
TimeShift,
|
||||
Field,
|
||||
} from './query'
|
||||
import {AlertRule, Kapacitor, Task} from './kapacitor'
|
||||
import {Source} from './sources'
|
||||
import {Source, SourceLinks} from './sources'
|
||||
import {DropdownAction, DropdownItem} from './shared'
|
||||
import {Notification} from 'src/kapacitor/components/AlertOutputs'
|
||||
|
||||
export {
|
||||
Me,
|
||||
|
@ -10,13 +19,22 @@ export {
|
|||
Role,
|
||||
User,
|
||||
Organization,
|
||||
Template,
|
||||
Cell,
|
||||
CellQuery,
|
||||
Legend,
|
||||
Status,
|
||||
QueryConfig,
|
||||
TimeShift,
|
||||
Field,
|
||||
GroupBy,
|
||||
AlertRule,
|
||||
Kapacitor,
|
||||
Query,
|
||||
QueryConfig,
|
||||
Source,
|
||||
SourceLinks,
|
||||
DropdownAction,
|
||||
DropdownItem,
|
||||
TimeRange,
|
||||
Task,
|
||||
Notification,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export interface Query {
|
||||
id: QueryID
|
||||
export interface QueryConfig {
|
||||
id?: string
|
||||
database: string
|
||||
measurement: string
|
||||
retentionPolicy: string
|
||||
|
@ -7,12 +7,13 @@ export interface Query {
|
|||
tags: Tags
|
||||
groupBy: GroupBy
|
||||
areTagsAccepted: boolean
|
||||
rawText: string | null
|
||||
rawText: string
|
||||
range?: DurationRange | null
|
||||
source?: string
|
||||
fill?: string
|
||||
status?: Status
|
||||
shifts: TimeShift[]
|
||||
isQuerySupportedByExplorer?: boolean // doesn't come from server -- is set in CellEditorOverlay
|
||||
}
|
||||
|
||||
export interface Field {
|
||||
|
@ -22,8 +23,6 @@ export interface Field {
|
|||
args?: Args[]
|
||||
}
|
||||
|
||||
export type QueryID = string
|
||||
|
||||
export interface Args {
|
||||
value: string
|
||||
type: string
|
||||
|
@ -69,18 +68,3 @@ export interface TimeShift {
|
|||
unit: string
|
||||
quantity: string
|
||||
}
|
||||
|
||||
export interface QueryConfig {
|
||||
id?: string
|
||||
database: string
|
||||
measurement: string
|
||||
retentionPolicy: string
|
||||
fields: Field[]
|
||||
tags: Tags
|
||||
groupBy: GroupBy
|
||||
areTagsAccepted: boolean
|
||||
fill?: string
|
||||
rawText: string
|
||||
range: DurationRange
|
||||
shifts: TimeShift[]
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import {buildQuery} from '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'
|
||||
|
||||
const buildCannedDashboardQuery = (query, {lower, upper}, host) => {
|
||||
|
@ -48,8 +52,8 @@ export const buildQueriesForLayouts = (cell, source, timeRange, host) => {
|
|||
queryConfig: {database, measurement, fields, shifts, rawText, range},
|
||||
} = query
|
||||
const tR = range || {
|
||||
upper: ':upperDashboardTime:',
|
||||
lower: ':dashboardTime:',
|
||||
upper: TEMP_VAR_UPPER_DASHBOARD_TIME,
|
||||
lower: TEMP_VAR_DASHBOARD_TIME,
|
||||
}
|
||||
|
||||
queryText =
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
import React from 'react'
|
||||
import {shallow} from 'enzyme'
|
||||
|
||||
import CellEditorOverlay from 'src/dashboards/components/CellEditorOverlay'
|
||||
import QueryMaker from 'src/dashboards/components/QueryMaker'
|
||||
import {
|
||||
source,
|
||||
cell,
|
||||
timeRange,
|
||||
userDefinedTemplateVariables,
|
||||
predefinedTemplateVariables,
|
||||
thresholdsListColors,
|
||||
gaugeColors,
|
||||
lineColors,
|
||||
query,
|
||||
} from 'test/fixtures'
|
||||
|
||||
jest.mock('src/shared/apis', () => require('mocks/shared/apis'))
|
||||
|
||||
const setup = (override = {}) => {
|
||||
const props = {
|
||||
source,
|
||||
sources: [source],
|
||||
cell,
|
||||
timeRange,
|
||||
autoRefresh: 0,
|
||||
dashboardID: '9',
|
||||
queryStatus: {
|
||||
queryID: null,
|
||||
status: null,
|
||||
},
|
||||
templates: [
|
||||
...userDefinedTemplateVariables,
|
||||
...predefinedTemplateVariables,
|
||||
],
|
||||
thresholdsListType: 'text',
|
||||
thresholdsListColors,
|
||||
gaugeColors,
|
||||
lineColors,
|
||||
editQueryStatus: () => {},
|
||||
onCancel: () => {},
|
||||
onSave: () => {},
|
||||
notify: () => {},
|
||||
...override,
|
||||
}
|
||||
|
||||
const wrapper = shallow(<CellEditorOverlay {...props} />)
|
||||
|
||||
return {props, wrapper}
|
||||
}
|
||||
|
||||
describe('Dashboards.Components.CellEditorOverlay', () => {
|
||||
describe('rendering', () => {
|
||||
describe('if a predefined template variable is used in the query', () => {
|
||||
it('should render the query maker with isQuerySupportedByExplorer as false', () => {
|
||||
const queryText =
|
||||
'SELECT mean(:fields:), mean("usage_user") AS "mean_usage_user" FROM "telegraf"."autogen"."cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:) FILL(null)'
|
||||
const {queryConfig} = query
|
||||
const updatedQueryConfig = {...queryConfig, rawText: queryText}
|
||||
const updatedQueries = [
|
||||
{...query, query: queryText, queryConfig: updatedQueryConfig},
|
||||
]
|
||||
const updatedCell = {...cell, queries: updatedQueries}
|
||||
const {wrapper} = setup({cell: updatedCell})
|
||||
|
||||
const queryMaker = wrapper.find(QueryMaker)
|
||||
const activeQuery = queryMaker.prop('activeQuery')
|
||||
|
||||
expect(activeQuery.isQuerySupportedByExplorer).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,330 @@
|
|||
import {
|
||||
Source,
|
||||
CellQuery,
|
||||
SourceLinks,
|
||||
Cell,
|
||||
TimeRange,
|
||||
Template,
|
||||
} from 'src/types'
|
||||
import {Axes, TableOptions, FieldName, DecimalPlaces} from 'src/types/dashboard'
|
||||
import {ColorString, ColorNumber} from 'src/types/colors'
|
||||
|
||||
export const sourceLinks: SourceLinks = {
|
||||
self: '/chronograf/v1/sources/4',
|
||||
kapacitors: '/chronograf/v1/sources/4/kapacitors',
|
||||
proxy: '/chronograf/v1/sources/4/proxy',
|
||||
queries: '/chronograf/v1/sources/4/queries',
|
||||
write: '/chronograf/v1/sources/4/write',
|
||||
permissions: '/chronograf/v1/sources/4/permissions',
|
||||
users: '/chronograf/v1/sources/4/users',
|
||||
databases: '/chronograf/v1/sources/4/dbs',
|
||||
annotations: '/chronograf/v1/sources/4/annotations',
|
||||
health: '/chronograf/v1/sources/4/health',
|
||||
}
|
||||
|
||||
export const source: Source = {
|
||||
id: '4',
|
||||
name: 'Influx 1',
|
||||
type: 'influx',
|
||||
url: 'http://localhost:8086',
|
||||
default: false,
|
||||
telegraf: 'telegraf',
|
||||
organization: 'default',
|
||||
role: 'viewer',
|
||||
defaultRP: '',
|
||||
links: sourceLinks,
|
||||
insecureSkipVerify: false,
|
||||
}
|
||||
|
||||
export const query: CellQuery = {
|
||||
query:
|
||||
'SELECT mean("usage_idle") AS "mean_usage_idle", mean("usage_user") AS "mean_usage_user" FROM "telegraf"."autogen"."cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:) FILL(null)',
|
||||
queryConfig: {
|
||||
database: 'telegraf',
|
||||
measurement: 'cpu',
|
||||
retentionPolicy: 'autogen',
|
||||
fields: [
|
||||
{
|
||||
value: 'mean',
|
||||
type: 'func',
|
||||
alias: 'mean_usage_idle',
|
||||
args: [
|
||||
{
|
||||
value: 'usage_idle',
|
||||
type: 'field',
|
||||
alias: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: 'mean',
|
||||
type: 'func',
|
||||
alias: 'mean_usage_user',
|
||||
args: [
|
||||
{
|
||||
value: 'usage_user',
|
||||
type: 'field',
|
||||
alias: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
tags: {},
|
||||
groupBy: {
|
||||
time: 'auto',
|
||||
tags: [],
|
||||
},
|
||||
areTagsAccepted: false,
|
||||
fill: 'null',
|
||||
rawText: null,
|
||||
range: null,
|
||||
shifts: null,
|
||||
},
|
||||
}
|
||||
|
||||
export const axes: Axes = {
|
||||
x: {
|
||||
bounds: ['', ''],
|
||||
label: '',
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
base: '10',
|
||||
scale: 'linear',
|
||||
},
|
||||
y: {
|
||||
bounds: ['', ''],
|
||||
label: '',
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
base: '10',
|
||||
scale: 'linear',
|
||||
},
|
||||
}
|
||||
|
||||
export const fieldOptions: FieldName[] = [
|
||||
{
|
||||
internalName: 'time',
|
||||
displayName: '',
|
||||
visible: true,
|
||||
},
|
||||
]
|
||||
|
||||
export const tableOptions: TableOptions = {
|
||||
verticalTimeAxis: true,
|
||||
sortBy: {
|
||||
internalName: 'time',
|
||||
displayName: '',
|
||||
visible: true,
|
||||
},
|
||||
wrapping: 'truncate',
|
||||
fixFirstColumn: true,
|
||||
}
|
||||
export const lineColors: ColorString[] = [
|
||||
{
|
||||
id: '574fb0a3-0a26-44d7-8d71-d4981756acb1',
|
||||
type: 'scale',
|
||||
hex: '#31C0F6',
|
||||
name: 'Nineteen Eighty Four',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
id: '3b9750f9-d41d-4100-8ee6-bd2785237f35',
|
||||
type: 'scale',
|
||||
hex: '#A500A5',
|
||||
name: 'Nineteen Eighty Four',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
id: '8d39064f-8124-4967-ae22-ffe14e425781',
|
||||
type: 'scale',
|
||||
hex: '#FF7E27',
|
||||
name: 'Nineteen Eighty Four',
|
||||
value: '0',
|
||||
},
|
||||
]
|
||||
|
||||
export const decimalPlaces: DecimalPlaces = {
|
||||
isEnforced: true,
|
||||
digits: 4,
|
||||
}
|
||||
|
||||
export const cell: Cell = {
|
||||
id: '67435af2-17bf-4caa-a5fc-0dd1ffb40dab',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 8,
|
||||
h: 4,
|
||||
name: 'Untitled Line Graph',
|
||||
queries: [query],
|
||||
axes: axes,
|
||||
type: 'line',
|
||||
colors: lineColors,
|
||||
legend: {},
|
||||
tableOptions: tableOptions,
|
||||
fieldOptions: fieldOptions,
|
||||
timeFormat: 'MM/DD/YYYY HH:mm:ss',
|
||||
decimalPlaces: decimalPlaces,
|
||||
links: {
|
||||
self:
|
||||
'/chronograf/v1/dashboards/9/cells/67435af2-17bf-4caa-a5fc-0dd1ffb40dab',
|
||||
},
|
||||
}
|
||||
|
||||
export const fullTimeRange = {
|
||||
dashboardID: 9,
|
||||
defaultGroupBy: '10s',
|
||||
seconds: 300,
|
||||
inputValue: 'Past 5 minutes',
|
||||
lower: 'now() - 5m',
|
||||
upper: null,
|
||||
menuOption: 'Past 5 minutes',
|
||||
format: 'influxql',
|
||||
}
|
||||
|
||||
export const timeRange: TimeRange = {
|
||||
lower: 'now() - 5m',
|
||||
upper: null,
|
||||
}
|
||||
|
||||
export const userDefinedTemplateVariables: Template[] = [
|
||||
{
|
||||
tempVar: ':fields:',
|
||||
values: [
|
||||
{
|
||||
selected: false,
|
||||
value: 'usage_guest',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
value: 'usage_guest_nice',
|
||||
},
|
||||
{
|
||||
selected: true,
|
||||
value: 'usage_idle',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
value: 'usage_iowait',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
value: 'usage_irq',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
value: 'usage_nice',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
value: 'usage_softirq',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
value: 'usage_steal',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
value: 'usage_system',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
value: 'usage_user',
|
||||
},
|
||||
],
|
||||
id: '2b8dca84-879c-4555-a7cf-97f2951f8643',
|
||||
},
|
||||
{
|
||||
tempVar: ':measurements:',
|
||||
values: [
|
||||
{
|
||||
selected: true,
|
||||
value: 'cpu',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
value: 'disk',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
value: 'diskio',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
value: 'mem',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
value: 'processes',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
value: 'swap',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
value: 'system',
|
||||
},
|
||||
],
|
||||
id: '18855209-12db-4619-9834-1d7eb643ae6e',
|
||||
},
|
||||
]
|
||||
|
||||
export const predefinedTemplateVariables: Template[] = [
|
||||
{
|
||||
id: 'dashtime',
|
||||
tempVar: ':dashboardTime:',
|
||||
values: [
|
||||
{
|
||||
value: 'now() - 5m',
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'upperdashtime',
|
||||
tempVar: ':upperDashboardTime:',
|
||||
values: [
|
||||
{
|
||||
value: 'now()',
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'interval',
|
||||
tempVar: ':interval:',
|
||||
values: [
|
||||
{
|
||||
value: '333',
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export const thresholdsListColors: ColorNumber[] = [
|
||||
{
|
||||
type: 'text',
|
||||
hex: '#00C9FF',
|
||||
id: 'base',
|
||||
name: 'laser',
|
||||
value: -1000000000000000000,
|
||||
},
|
||||
]
|
||||
|
||||
export const gaugeColors: ColorNumber[] = [
|
||||
{
|
||||
type: 'min',
|
||||
hex: '#00C9FF',
|
||||
id: '0',
|
||||
name: 'laser',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
type: 'max',
|
||||
hex: '#9394FF',
|
||||
id: '1',
|
||||
name: 'comet',
|
||||
value: 100,
|
||||
},
|
||||
]
|
|
@ -13,6 +13,7 @@ const setup = (override = {}) => {
|
|||
onToggleTagAcceptance: () => {},
|
||||
query,
|
||||
querySource: source,
|
||||
isQuerySupportedByExplorer: true,
|
||||
...override,
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ const setup = (overrides = {}) => {
|
|||
measurement: 'test',
|
||||
numTagsActive: 3,
|
||||
areTagsAccepted: true,
|
||||
isQuerySupportedByExplorer: true,
|
||||
onChooseTag: () => {},
|
||||
onGroupByTag: () => {},
|
||||
onAcceptReject: () => {},
|
||||
|
|
|
@ -13,6 +13,7 @@ const setup = (override = {}) => {
|
|||
onGroupByTag: () => {},
|
||||
query,
|
||||
querySource: source,
|
||||
isQuerySupportedByExplorer: true,
|
||||
...override,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue