parent
3836444bea
commit
e6cd29da58
|
@ -61,6 +61,7 @@ import {
|
|||
Cell,
|
||||
Source,
|
||||
Template,
|
||||
TemplateType,
|
||||
URLQueryParams,
|
||||
} from 'src/types'
|
||||
import {CellType, DashboardName} from 'src/types/dashboard'
|
||||
|
@ -444,7 +445,7 @@ export const getChronografVersion = () => async (): Promise<string | void> => {
|
|||
const removeUnselectedTemplateValues = (dashboard: Dashboard): Template[] => {
|
||||
const templates = getDeep<Template[]>(dashboard, 'templates', []).map(
|
||||
template => {
|
||||
if (template.type === 'csv') {
|
||||
if (template.type === TemplateType.CSV) {
|
||||
return template
|
||||
}
|
||||
|
||||
|
|
|
@ -19,10 +19,8 @@ import {getQueryConfigAndStatus} from 'src/shared/apis'
|
|||
import {IS_STATIC_LEGEND} from 'src/shared/constants'
|
||||
import {nextSource} from 'src/dashboards/utils/sources'
|
||||
|
||||
import {
|
||||
removeUnselectedTemplateValues,
|
||||
TYPE_QUERY_CONFIG,
|
||||
} from 'src/dashboards/constants'
|
||||
import {TYPE_QUERY_CONFIG} from 'src/dashboards/constants'
|
||||
import {removeUnselectedTemplateValues} from 'src/tempVars/constants'
|
||||
import {OVERLAY_TECHNOLOGY} from 'src/shared/constants/classNames'
|
||||
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants'
|
||||
import {
|
||||
|
|
|
@ -6,7 +6,8 @@ import QueryTabList from 'src/shared/components/QueryTabList'
|
|||
import QueryTextArea from 'src/dashboards/components/QueryTextArea'
|
||||
import SchemaExplorer from 'src/shared/components/SchemaExplorer'
|
||||
import {buildQuery} from 'src/utils/influxql'
|
||||
import {TYPE_QUERY_CONFIG, TEMPLATE_RANGE} from 'src/dashboards/constants'
|
||||
import {TYPE_QUERY_CONFIG} from 'src/dashboards/constants'
|
||||
import {TEMPLATE_RANGE} from 'src/tempVars/constants'
|
||||
|
||||
import {QueryConfig, Source, SourceLinks, TimeRange} from 'src/types'
|
||||
import {CellEditorOverlayActions} from 'src/dashboards/components/CellEditorOverlay'
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
import React, {Component} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import classnames from 'classnames'
|
||||
import uuid from 'uuid'
|
||||
|
||||
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
|
||||
import TemplateControlDropdown from 'src/dashboards/components/TemplateControlDropdown'
|
||||
import {Template} from 'src/types/tempVars'
|
||||
|
||||
interface Props {
|
||||
meRole: string
|
||||
isUsingAuth: boolean
|
||||
templates: Template[]
|
||||
isOpen: boolean
|
||||
onOpenTemplateManager: () => void
|
||||
onSelectTemplate: (id: string) => void
|
||||
}
|
||||
|
||||
class TemplateControlBar extends Component<Props> {
|
||||
public shouldComponentUpdate(nextProps) {
|
||||
return !_.isEqual(this.props, nextProps)
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
isOpen,
|
||||
templates,
|
||||
onSelectTemplate,
|
||||
onOpenTemplateManager,
|
||||
meRole,
|
||||
isUsingAuth,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className={classnames('template-control-bar', {show: isOpen})}>
|
||||
<div className="template-control--container">
|
||||
<div className="template-control--controls">
|
||||
{templates && templates.length ? (
|
||||
templates.map(template => (
|
||||
<TemplateControlDropdown
|
||||
key={uuid.v4()}
|
||||
meRole={meRole}
|
||||
isUsingAuth={isUsingAuth}
|
||||
template={template}
|
||||
onSelectTemplate={onSelectTemplate}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="template-control--empty" data-test="empty-state">
|
||||
This dashboard does not have any{' '}
|
||||
<strong>Template Variables</strong>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Authorized requiredRole={EDITOR_ROLE}>
|
||||
<button
|
||||
className="btn btn-primary btn-sm template-control--manage"
|
||||
onClick={onOpenTemplateManager}
|
||||
>
|
||||
<span className="icon cog-thick" />
|
||||
Manage
|
||||
</button>
|
||||
</Authorized>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateControlBar
|
|
@ -1,53 +0,0 @@
|
|||
import React, {SFC} from 'react'
|
||||
|
||||
import Dropdown from 'src/shared/components/Dropdown'
|
||||
import {calculateDropdownWidth} from 'src/dashboards/constants/templateControlBar'
|
||||
import {isUserAuthorized, EDITOR_ROLE} from 'src/auth/Authorized'
|
||||
import {Template} from 'src/types/tempVars'
|
||||
|
||||
interface Props {
|
||||
template: Template
|
||||
meRole: string
|
||||
isUsingAuth: boolean
|
||||
onSelectTemplate: (id: string) => void
|
||||
}
|
||||
|
||||
// TODO: change Dropdown to a MultiSelectDropdown, `selected` to
|
||||
// the full array, and [item] to all `selected` values when we update
|
||||
// this component to support multiple values
|
||||
|
||||
const TemplateControlDropdown: SFC<Props> = ({
|
||||
template,
|
||||
onSelectTemplate,
|
||||
isUsingAuth,
|
||||
meRole,
|
||||
}) => {
|
||||
const dropdownItems = template.values.map(value => ({
|
||||
...value,
|
||||
text: value.value,
|
||||
}))
|
||||
|
||||
const dropdownStyle = template.values.length
|
||||
? {minWidth: calculateDropdownWidth(template.values)}
|
||||
: null
|
||||
|
||||
const selectedItem = dropdownItems.find(item => item.selected) ||
|
||||
dropdownItems[0] || {text: '(No values)'}
|
||||
|
||||
return (
|
||||
<div className="template-control--dropdown" style={dropdownStyle}>
|
||||
<Dropdown
|
||||
items={dropdownItems}
|
||||
buttonSize="btn-xs"
|
||||
menuClass="dropdown-astronaut"
|
||||
useAutoComplete={true}
|
||||
selected={selectedItem.text}
|
||||
disabled={isUsingAuth && !isUserAuthorized(meRole, EDITOR_ROLE)}
|
||||
onChoose={onSelectTemplate(template.id)}
|
||||
/>
|
||||
<label className="template-control--label">{template.tempVar}</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TemplateControlDropdown
|
|
@ -1,383 +0,0 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import {connect} from 'react-redux'
|
||||
import {bindActionCreators} from 'redux'
|
||||
|
||||
import uniq from 'lodash/uniq'
|
||||
|
||||
import OnClickOutside from 'react-onclickoutside'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import Dropdown from 'shared/components/Dropdown'
|
||||
import TemplateQueryBuilder from 'src/dashboards/components/template_variables/TemplateQueryBuilder'
|
||||
import TableInput from 'src/dashboards/components/template_variables/TableInput'
|
||||
import RowValues from 'src/dashboards/components/template_variables/RowValues'
|
||||
import ConfirmButton from 'src/shared/components/ConfirmButton'
|
||||
|
||||
import {getTempVarValuesBySourceQuery as getTempVarValuesBySourceQueryAJAX} from 'src/dashboards/apis'
|
||||
|
||||
import parsers from 'shared/parsing'
|
||||
|
||||
import {TEMPLATE_TYPES} from 'src/dashboards/constants'
|
||||
import generateTemplateVariableQuery from 'src/dashboards/utils/tempVars'
|
||||
|
||||
import {errorThrown as errorThrownAction} from 'shared/actions/errors'
|
||||
import {notify as notifyAction} from 'shared/actions/notifications'
|
||||
|
||||
import {notifyTempVarAlreadyExists} from 'shared/copy/notifications'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
const compact = values => uniq(values).filter(value => /\S/.test(value))
|
||||
|
||||
const TemplateVariableRow = ({
|
||||
template: {id, tempVar, values},
|
||||
isEditing,
|
||||
selectedType,
|
||||
selectedDatabase,
|
||||
selectedMeasurement,
|
||||
onSelectType,
|
||||
onSelectDatabase,
|
||||
onSelectMeasurement,
|
||||
selectedTagKey,
|
||||
onSelectTagKey,
|
||||
onStartEdit,
|
||||
onCancelEdit,
|
||||
autoFocusTarget,
|
||||
onSubmit,
|
||||
onErrorThrown,
|
||||
onDeleteTempVar,
|
||||
source,
|
||||
}) => (
|
||||
<form
|
||||
className={classnames('template-variable-manager--table-row', {
|
||||
editing: isEditing,
|
||||
})}
|
||||
onSubmit={onSubmit({
|
||||
selectedType,
|
||||
selectedDatabase,
|
||||
selectedMeasurement,
|
||||
selectedTagKey,
|
||||
})}
|
||||
>
|
||||
<div className="tvm--col-1">
|
||||
<TableInput
|
||||
name="tempVar"
|
||||
defaultValue={tempVar}
|
||||
isEditing={isEditing}
|
||||
onStartEdit={onStartEdit}
|
||||
autoFocusTarget={autoFocusTarget}
|
||||
/>
|
||||
</div>
|
||||
<div className="tvm--col-2">
|
||||
<Dropdown
|
||||
items={TEMPLATE_TYPES}
|
||||
onChoose={onSelectType}
|
||||
onClick={onStartEdit('tempVar')}
|
||||
selected={TEMPLATE_TYPES.find(t => t.type === selectedType).text}
|
||||
className="dropdown-140"
|
||||
/>
|
||||
</div>
|
||||
<div className="tvm--col-3">
|
||||
<TemplateQueryBuilder
|
||||
source={source}
|
||||
onSelectDatabase={onSelectDatabase}
|
||||
selectedType={selectedType}
|
||||
selectedDatabase={selectedDatabase}
|
||||
onSelectMeasurement={onSelectMeasurement}
|
||||
selectedMeasurement={selectedMeasurement}
|
||||
selectedTagKey={selectedTagKey}
|
||||
onSelectTagKey={onSelectTagKey}
|
||||
onStartEdit={onStartEdit('tempVar')}
|
||||
onErrorThrown={onErrorThrown}
|
||||
/>
|
||||
<RowValues
|
||||
selectedType={selectedType}
|
||||
values={values}
|
||||
isEditing={isEditing}
|
||||
onStartEdit={onStartEdit}
|
||||
autoFocusTarget={autoFocusTarget}
|
||||
/>
|
||||
</div>
|
||||
<div className={`tvm--col-4${isEditing ? ' editing' : ''}`}>
|
||||
{isEditing ? (
|
||||
<div className="tvm-actions">
|
||||
<button
|
||||
className="btn btn-sm btn-info btn-square"
|
||||
type="button"
|
||||
onClick={onCancelEdit}
|
||||
>
|
||||
<span className="icon remove" />
|
||||
</button>
|
||||
<button className="btn btn-sm btn-success btn-square" type="submit">
|
||||
<span className="icon checkmark" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="tvm-actions">
|
||||
<ConfirmButton
|
||||
type="btn-danger"
|
||||
confirmText="Delete template variable?"
|
||||
confirmAction={onDeleteTempVar(id)}
|
||||
icon="trash"
|
||||
square={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
|
||||
@ErrorHandling
|
||||
class RowWrapper extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
const {
|
||||
template: {type, query, isNew},
|
||||
} = this.props
|
||||
|
||||
this.state = {
|
||||
isEditing: !!isNew,
|
||||
isNew: !!isNew,
|
||||
hasBeenSavedToComponentStateOnce: !isNew,
|
||||
selectedType: type,
|
||||
selectedDatabase: query && query.db,
|
||||
selectedMeasurement: query && query.measurement,
|
||||
selectedTagKey: query && query.tagKey,
|
||||
autoFocusTarget: 'tempVar',
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit = ({
|
||||
selectedDatabase: database,
|
||||
selectedMeasurement: measurement,
|
||||
selectedTagKey: tagKey,
|
||||
selectedType: type,
|
||||
}) => async e => {
|
||||
e.preventDefault()
|
||||
|
||||
const {
|
||||
source,
|
||||
template,
|
||||
template: {id},
|
||||
onRunQuerySuccess,
|
||||
onRunQueryFailure,
|
||||
tempVarAlreadyExists,
|
||||
notify,
|
||||
} = this.props
|
||||
|
||||
const _tempVar = e.target.tempVar.value.replace(/\u003a/g, '')
|
||||
const tempVar = `\u003a${_tempVar}\u003a` // add ':'s
|
||||
|
||||
if (tempVarAlreadyExists(tempVar, id)) {
|
||||
return notify(notifyTempVarAlreadyExists(_tempVar))
|
||||
}
|
||||
|
||||
this.setState({
|
||||
isEditing: false,
|
||||
hasBeenSavedToComponentStateOnce: true,
|
||||
})
|
||||
|
||||
const {query, tempVars} = generateTemplateVariableQuery({
|
||||
type,
|
||||
tempVar,
|
||||
query: {
|
||||
database,
|
||||
// rp, TODO
|
||||
measurement,
|
||||
tagKey,
|
||||
},
|
||||
})
|
||||
|
||||
const queryConfig = {
|
||||
type,
|
||||
tempVars,
|
||||
query,
|
||||
database,
|
||||
// rp: TODO
|
||||
measurement,
|
||||
tagKey,
|
||||
}
|
||||
|
||||
try {
|
||||
let parsedData
|
||||
if (type === 'csv') {
|
||||
parsedData = e.target.values.value.split(',').map(value => value.trim())
|
||||
} else {
|
||||
parsedData = await this.getTempVarValuesBySourceQuery(
|
||||
source,
|
||||
queryConfig
|
||||
)
|
||||
}
|
||||
|
||||
onRunQuerySuccess(template, queryConfig, compact(parsedData), tempVar)
|
||||
} catch (error) {
|
||||
onRunQueryFailure(error)
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOutside() {
|
||||
this.handleCancelEdit()
|
||||
}
|
||||
|
||||
handleStartEdit = name => () => {
|
||||
this.setState({isEditing: true, autoFocusTarget: name})
|
||||
}
|
||||
|
||||
handleCancelEdit = () => {
|
||||
const {
|
||||
template: {
|
||||
type,
|
||||
query: {db, measurement, tagKey},
|
||||
id,
|
||||
},
|
||||
onDelete,
|
||||
} = this.props
|
||||
const {hasBeenSavedToComponentStateOnce} = this.state
|
||||
|
||||
if (!hasBeenSavedToComponentStateOnce) {
|
||||
return onDelete(id)
|
||||
}
|
||||
this.setState({
|
||||
selectedType: type,
|
||||
selectedDatabase: db,
|
||||
selectedMeasurement: measurement,
|
||||
selectedTagKey: tagKey,
|
||||
isEditing: false,
|
||||
})
|
||||
}
|
||||
|
||||
handleSelectType = item => {
|
||||
this.setState({
|
||||
selectedType: item.type,
|
||||
selectedDatabase: null,
|
||||
selectedMeasurement: null,
|
||||
selectedTagKey: null,
|
||||
})
|
||||
}
|
||||
|
||||
handleSelectDatabase = item => {
|
||||
this.setState({selectedDatabase: item.text})
|
||||
}
|
||||
|
||||
handleSelectMeasurement = item => {
|
||||
this.setState({selectedMeasurement: item.text})
|
||||
}
|
||||
|
||||
handleSelectTagKey = item => {
|
||||
this.setState({selectedTagKey: item.text})
|
||||
}
|
||||
|
||||
getTempVarValuesBySourceQuery = async (
|
||||
source,
|
||||
{query, database, rp, tempVars, type, measurement, tagKey}
|
||||
) => {
|
||||
try {
|
||||
const {data} = await getTempVarValuesBySourceQueryAJAX(source, {
|
||||
query,
|
||||
db: database,
|
||||
rp,
|
||||
tempVars,
|
||||
})
|
||||
const parsedData = parsers[type](data, tagKey || measurement) // tagKey covers tagKey and fieldKey
|
||||
if (parsedData.errors.length) {
|
||||
throw parsedData.errors
|
||||
}
|
||||
|
||||
return parsedData[type]
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
handleDelete = id => () => {
|
||||
this.props.onDelete(id)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
isEditing,
|
||||
selectedType,
|
||||
selectedDatabase,
|
||||
selectedMeasurement,
|
||||
selectedTagKey,
|
||||
autoFocusTarget,
|
||||
} = this.state
|
||||
|
||||
return (
|
||||
<TemplateVariableRow
|
||||
{...this.props}
|
||||
isEditing={isEditing}
|
||||
selectedType={selectedType}
|
||||
selectedDatabase={selectedDatabase}
|
||||
selectedMeasurement={selectedMeasurement}
|
||||
selectedTagKey={selectedTagKey}
|
||||
onSelectType={this.handleSelectType}
|
||||
onSelectDatabase={this.handleSelectDatabase}
|
||||
onSelectMeasurement={this.handleSelectMeasurement}
|
||||
onSelectTagKey={this.handleSelectTagKey}
|
||||
onStartEdit={this.handleStartEdit}
|
||||
onCancelEdit={this.handleCancelEdit}
|
||||
autoFocusTarget={autoFocusTarget}
|
||||
onSubmit={this.handleSubmit}
|
||||
onDeleteTempVar={this.handleDelete}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
RowWrapper.propTypes = {
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string,
|
||||
}),
|
||||
}).isRequired,
|
||||
template: shape({
|
||||
type: string.isRequired,
|
||||
tempVar: string.isRequired,
|
||||
query: shape({
|
||||
db: string,
|
||||
influxql: string,
|
||||
measurement: string,
|
||||
tagKey: string,
|
||||
}),
|
||||
values: arrayOf(
|
||||
shape({
|
||||
value: string.isRequired,
|
||||
type: string.isRequired,
|
||||
selected: bool.isRequired,
|
||||
})
|
||||
).isRequired,
|
||||
links: shape({
|
||||
self: string.isRequired,
|
||||
}),
|
||||
}),
|
||||
onRunQuerySuccess: func.isRequired,
|
||||
onRunQueryFailure: func.isRequired,
|
||||
onDelete: func.isRequired,
|
||||
tempVarAlreadyExists: func.isRequired,
|
||||
notify: func.isRequired,
|
||||
}
|
||||
|
||||
TemplateVariableRow.propTypes = {
|
||||
...RowWrapper.propTypes,
|
||||
selectedType: string.isRequired,
|
||||
selectedDatabase: string,
|
||||
selectedTagKey: string,
|
||||
onSelectType: func.isRequired,
|
||||
onSelectDatabase: func.isRequired,
|
||||
onSelectTagKey: func.isRequired,
|
||||
onStartEdit: func.isRequired,
|
||||
onCancelEdit: func.isRequired,
|
||||
onSubmit: func.isRequired,
|
||||
onErrorThrown: func.isRequired,
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onErrorThrown: bindActionCreators(errorThrownAction, dispatch),
|
||||
notify: bindActionCreators(notifyAction, dispatch),
|
||||
})
|
||||
|
||||
export default connect(null, mapDispatchToProps)(OnClickOutside(RowWrapper))
|
|
@ -1,44 +0,0 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import TableInput from 'src/dashboards/components/template_variables/TableInput'
|
||||
|
||||
const RowValues = ({
|
||||
selectedType,
|
||||
values = [],
|
||||
isEditing,
|
||||
onStartEdit,
|
||||
autoFocusTarget,
|
||||
}) => {
|
||||
const _values = values.map(v => v.value).join(', ')
|
||||
|
||||
if (selectedType === 'csv') {
|
||||
return (
|
||||
<TableInput
|
||||
name="values"
|
||||
defaultValue={_values}
|
||||
isEditing={isEditing}
|
||||
onStartEdit={onStartEdit}
|
||||
autoFocusTarget={autoFocusTarget}
|
||||
spellCheck={false}
|
||||
autoComplete="false"
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className={values.length ? 'tvm-values' : 'tvm-values-empty'}>
|
||||
{values.length ? _values : 'No values to display'}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
RowValues.propTypes = {
|
||||
selectedType: string.isRequired,
|
||||
values: arrayOf(shape()),
|
||||
isEditing: bool.isRequired,
|
||||
onStartEdit: func.isRequired,
|
||||
autoFocusTarget: string,
|
||||
}
|
||||
|
||||
export default RowValues
|
|
@ -1,80 +0,0 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import TemplateVariableRow from 'src/dashboards/components/template_variables/Row'
|
||||
|
||||
const TemplateVariableTable = ({
|
||||
source,
|
||||
templates,
|
||||
onRunQuerySuccess,
|
||||
onRunQueryFailure,
|
||||
onDelete,
|
||||
tempVarAlreadyExists,
|
||||
}) => (
|
||||
<div className="template-variable-manager--table">
|
||||
{templates.length ? (
|
||||
<div className="template-variable-manager--table-container">
|
||||
<div className="template-variable-manager--table-heading">
|
||||
<div className="tvm--col-1">Variable</div>
|
||||
<div className="tvm--col-2">Type</div>
|
||||
<div className="tvm--col-3">Definition / Values</div>
|
||||
<div className="tvm--col-4" />
|
||||
</div>
|
||||
<div className="template-variable-manager--table-rows">
|
||||
{templates.map(t => (
|
||||
<TemplateVariableRow
|
||||
key={t.id}
|
||||
source={source}
|
||||
template={t}
|
||||
onRunQuerySuccess={onRunQuerySuccess}
|
||||
onRunQueryFailure={onRunQueryFailure}
|
||||
onDelete={onDelete}
|
||||
tempVarAlreadyExists={tempVarAlreadyExists}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="generic-empty-state">
|
||||
<h4 style={{margin: '60px 0'}} className="no-user-select">
|
||||
You have no Template Variables, why not create one?
|
||||
</h4>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
TemplateVariableTable.propTypes = {
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string,
|
||||
}),
|
||||
}).isRequired,
|
||||
templates: arrayOf(
|
||||
shape({
|
||||
type: string.isRequired,
|
||||
tempVar: string.isRequired,
|
||||
query: shape({
|
||||
db: string,
|
||||
influxql: string,
|
||||
measurement: string,
|
||||
tagKey: string,
|
||||
}),
|
||||
values: arrayOf(
|
||||
shape({
|
||||
value: string.isRequired,
|
||||
type: string.isRequired,
|
||||
selected: bool.isRequired,
|
||||
})
|
||||
).isRequired,
|
||||
})
|
||||
),
|
||||
onRunQuerySuccess: func.isRequired,
|
||||
onRunQueryFailure: func.isRequired,
|
||||
onDelete: func.isRequired,
|
||||
tempVarAlreadyExists: func.isRequired,
|
||||
}
|
||||
|
||||
export default TemplateVariableTable
|
|
@ -1,43 +0,0 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const TableInput = ({
|
||||
name,
|
||||
defaultValue,
|
||||
isEditing,
|
||||
onStartEdit,
|
||||
autoFocusTarget,
|
||||
}) => {
|
||||
return isEditing ? (
|
||||
<div name={name} style={{width: '100%'}}>
|
||||
<input
|
||||
required={true}
|
||||
name={name}
|
||||
autoFocus={name === autoFocusTarget}
|
||||
className="form-control input-sm tvm-input-edit"
|
||||
type="text"
|
||||
defaultValue={
|
||||
name === 'tempVar'
|
||||
? defaultValue.replace(/\u003a/g, '') // remove ':'s
|
||||
: defaultValue
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{width: '100%'}} onClick={onStartEdit(name)}>
|
||||
<div className="tvm-input">{defaultValue}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const {bool, func, string} = PropTypes
|
||||
|
||||
TableInput.propTypes = {
|
||||
defaultValue: string,
|
||||
isEditing: bool.isRequired,
|
||||
onStartEdit: func.isRequired,
|
||||
name: string.isRequired,
|
||||
autoFocusTarget: string,
|
||||
}
|
||||
|
||||
export default TableInput
|
|
@ -1,134 +0,0 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import DatabaseDropdown from 'shared/components/DatabaseDropdown'
|
||||
import MeasurementDropdown from 'src/dashboards/components/MeasurementDropdown'
|
||||
import TagKeyDropdown from 'src/dashboards/components/TagKeyDropdown'
|
||||
|
||||
const TemplateQueryBuilder = ({
|
||||
selectedType,
|
||||
selectedDatabase,
|
||||
selectedMeasurement,
|
||||
selectedTagKey,
|
||||
onSelectDatabase,
|
||||
onSelectMeasurement,
|
||||
onSelectTagKey,
|
||||
onStartEdit,
|
||||
onErrorThrown,
|
||||
source,
|
||||
}) => {
|
||||
switch (selectedType) {
|
||||
case 'csv':
|
||||
return null
|
||||
case 'databases':
|
||||
return <div className="tvm-query-builder--text">SHOW DATABASES</div>
|
||||
case 'measurements':
|
||||
return (
|
||||
<div className="tvm-query-builder">
|
||||
<span className="tvm-query-builder--text">SHOW MEASUREMENTS ON</span>
|
||||
<DatabaseDropdown
|
||||
source={source}
|
||||
onSelectDatabase={onSelectDatabase}
|
||||
database={selectedDatabase}
|
||||
onStartEdit={onStartEdit}
|
||||
onErrorThrown={onErrorThrown}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
case 'fieldKeys':
|
||||
case 'tagKeys':
|
||||
return (
|
||||
<div className="tvm-query-builder">
|
||||
<span className="tvm-query-builder--text">
|
||||
SHOW {selectedType === 'fieldKeys' ? 'FIELD' : 'TAG'} KEYS ON
|
||||
</span>
|
||||
<DatabaseDropdown
|
||||
source={source}
|
||||
onSelectDatabase={onSelectDatabase}
|
||||
database={selectedDatabase}
|
||||
onStartEdit={onStartEdit}
|
||||
onErrorThrown={onErrorThrown}
|
||||
/>
|
||||
<span className="tvm-query-builder--text">FROM</span>
|
||||
{selectedDatabase ? (
|
||||
<MeasurementDropdown
|
||||
source={source}
|
||||
database={selectedDatabase}
|
||||
measurement={selectedMeasurement}
|
||||
onSelectMeasurement={onSelectMeasurement}
|
||||
onStartEdit={onStartEdit}
|
||||
onErrorThrown={onErrorThrown}
|
||||
/>
|
||||
) : (
|
||||
<div>No database selected</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
case 'tagValues':
|
||||
return (
|
||||
<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}
|
||||
onErrorThrown={onErrorThrown}
|
||||
/>
|
||||
<span className="tvm-query-builder--text">FROM</span>
|
||||
{selectedDatabase ? (
|
||||
<MeasurementDropdown
|
||||
source={source}
|
||||
database={selectedDatabase}
|
||||
measurement={selectedMeasurement}
|
||||
onSelectMeasurement={onSelectMeasurement}
|
||||
onStartEdit={onStartEdit}
|
||||
onErrorThrown={onErrorThrown}
|
||||
/>
|
||||
) : (
|
||||
'Pick a DB'
|
||||
)}
|
||||
<span className="tvm-query-builder--text">WITH KEY =</span>
|
||||
{selectedMeasurement ? (
|
||||
<TagKeyDropdown
|
||||
source={source}
|
||||
database={selectedDatabase}
|
||||
measurement={selectedMeasurement}
|
||||
tagKey={selectedTagKey}
|
||||
onSelectTagKey={onSelectTagKey}
|
||||
onStartEdit={onStartEdit}
|
||||
onErrorThrown={onErrorThrown}
|
||||
/>
|
||||
) : (
|
||||
'Pick a Tag Key'
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<div>
|
||||
<span className="tvm-query-builder--text">n/a</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
TemplateQueryBuilder.propTypes = {
|
||||
selectedType: string.isRequired,
|
||||
onSelectDatabase: func.isRequired,
|
||||
onSelectMeasurement: func.isRequired,
|
||||
onSelectTagKey: func.isRequired,
|
||||
onStartEdit: func.isRequired,
|
||||
selectedMeasurement: string,
|
||||
selectedDatabase: string,
|
||||
selectedTagKey: string,
|
||||
onErrorThrown: func.isRequired,
|
||||
source: shape({
|
||||
links: shape({
|
||||
proxy: string.isRequired,
|
||||
}).isRequired,
|
||||
}).isRequired,
|
||||
}
|
||||
|
||||
export default TemplateQueryBuilder
|
|
@ -1,242 +0,0 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import uuid from 'uuid'
|
||||
|
||||
import TemplateVariableTable from 'src/dashboards/components/template_variables/Table'
|
||||
|
||||
import {TEMPLATE_VARIABLE_TYPES} from 'src/dashboards/constants'
|
||||
|
||||
const TemplateVariableManager = ({
|
||||
source,
|
||||
onClose,
|
||||
onDelete,
|
||||
isEdited,
|
||||
templates,
|
||||
onAddVariable,
|
||||
onRunQuerySuccess,
|
||||
onRunQueryFailure,
|
||||
tempVarAlreadyExists,
|
||||
onSaveTemplatesSuccess,
|
||||
onEditTemplateVariables,
|
||||
}) => (
|
||||
<div className="template-variable-manager">
|
||||
<div className="template-variable-manager--header">
|
||||
<div className="page-header__left">
|
||||
<h1 className="page-header__title">Template Variables</h1>
|
||||
</div>
|
||||
<div className="page-header__right">
|
||||
<button
|
||||
className="btn btn-primary btn-sm"
|
||||
type="button"
|
||||
onClick={onAddVariable}
|
||||
>
|
||||
Add Variable
|
||||
</button>
|
||||
<button
|
||||
className={classnames('btn btn-success btn-sm', {
|
||||
disabled: !isEdited,
|
||||
})}
|
||||
type="button"
|
||||
onClick={onEditTemplateVariables(templates, onSaveTemplatesSuccess)}
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
<span className="page-header__dismiss" onClick={onClose} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="template-variable-manager--body">
|
||||
<TemplateVariableTable
|
||||
source={source}
|
||||
onDelete={onDelete}
|
||||
templates={templates}
|
||||
onRunQuerySuccess={onRunQuerySuccess}
|
||||
onRunQueryFailure={onRunQueryFailure}
|
||||
tempVarAlreadyExists={tempVarAlreadyExists}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
class TemplateVariableManagerWrapper extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
rows: this.props.templates,
|
||||
isEdited: false,
|
||||
}
|
||||
}
|
||||
|
||||
onAddVariable = () => {
|
||||
const {rows} = this.state
|
||||
|
||||
const newRow = {
|
||||
tempVar: '',
|
||||
values: [],
|
||||
id: uuid.v4(),
|
||||
type: 'csv',
|
||||
query: {
|
||||
influxql: '',
|
||||
db: '',
|
||||
// rp, TODO
|
||||
measurement: '',
|
||||
tagKey: '',
|
||||
},
|
||||
isNew: true,
|
||||
}
|
||||
|
||||
const newRows = [newRow, ...rows]
|
||||
|
||||
this.setState({rows: newRows})
|
||||
}
|
||||
|
||||
onRunQuerySuccess = (template, queryConfig, parsedData, tempVar) => {
|
||||
const {rows} = this.state
|
||||
const {id, links} = template
|
||||
const {
|
||||
type,
|
||||
query: influxql,
|
||||
database: db,
|
||||
measurement,
|
||||
tagKey,
|
||||
} = queryConfig
|
||||
|
||||
// Determine which is the selectedValue, if any
|
||||
const currentRow = rows.find(row => row.id === id)
|
||||
|
||||
let selectedValue
|
||||
if (currentRow && currentRow.values && currentRow.values.length) {
|
||||
const matchedValue = currentRow.values.find(val => val.selected)
|
||||
if (matchedValue) {
|
||||
selectedValue = matchedValue.value
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!selectedValue &&
|
||||
currentRow &&
|
||||
currentRow.values &&
|
||||
currentRow.values.length
|
||||
) {
|
||||
selectedValue = currentRow.values[0].value
|
||||
}
|
||||
|
||||
if (!selectedValue) {
|
||||
selectedValue = parsedData[0]
|
||||
}
|
||||
|
||||
const values = parsedData.map(value => ({
|
||||
value,
|
||||
type: TEMPLATE_VARIABLE_TYPES[type],
|
||||
selected: selectedValue === value,
|
||||
}))
|
||||
|
||||
const templateVariable = {
|
||||
tempVar,
|
||||
values,
|
||||
id,
|
||||
type,
|
||||
query: {
|
||||
influxql,
|
||||
db,
|
||||
// rp, TODO
|
||||
measurement,
|
||||
tagKey,
|
||||
},
|
||||
links,
|
||||
}
|
||||
|
||||
const newRows = rows.map(r => (r.id === template.id ? templateVariable : r))
|
||||
|
||||
this.setState({rows: newRows, isEdited: true})
|
||||
}
|
||||
|
||||
onSaveTemplatesSuccess = () => {
|
||||
const {rows} = this.state
|
||||
|
||||
const newRows = rows.map(row => ({...row, isNew: false}))
|
||||
|
||||
this.setState({rows: newRows, isEdited: false})
|
||||
}
|
||||
|
||||
onDeleteTemplateVariable = templateID => {
|
||||
const {rows} = this.state
|
||||
|
||||
const newRows = rows.filter(({id}) => id !== templateID)
|
||||
|
||||
this.setState({rows: newRows, isEdited: true})
|
||||
}
|
||||
|
||||
tempVarAlreadyExists = (testTempVar, testID) => {
|
||||
const {rows: tempVars} = this.state
|
||||
return tempVars.some(
|
||||
({tempVar, id}) => tempVar === testTempVar && id !== testID
|
||||
)
|
||||
}
|
||||
|
||||
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}
|
||||
onDelete={this.onDeleteTemplateVariable}
|
||||
tempVarAlreadyExists={this.tempVarAlreadyExists}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
TemplateVariableManager.propTypes = {
|
||||
...TemplateVariableManagerWrapper.propTypes,
|
||||
onRunQuerySuccess: func.isRequired,
|
||||
onSaveTemplatesSuccess: func.isRequired,
|
||||
onAddVariable: func.isRequired,
|
||||
isEdited: bool.isRequired,
|
||||
onDelete: func.isRequired,
|
||||
}
|
||||
|
||||
TemplateVariableManagerWrapper.propTypes = {
|
||||
onEditTemplateVariables: func.isRequired,
|
||||
templates: arrayOf(
|
||||
shape({
|
||||
type: string.isRequired,
|
||||
tempVar: string.isRequired,
|
||||
query: shape({
|
||||
db: string,
|
||||
influxql: string,
|
||||
}),
|
||||
values: arrayOf(
|
||||
shape({
|
||||
value: string.isRequired,
|
||||
type: string.isRequired,
|
||||
selected: bool.isRequired,
|
||||
})
|
||||
).isRequired,
|
||||
})
|
||||
),
|
||||
onRunQueryFailure: func.isRequired,
|
||||
onDismissOverlay: func,
|
||||
}
|
||||
|
||||
export default TemplateVariableManagerWrapper
|
|
@ -4,8 +4,6 @@ import {
|
|||
} from 'src/shared/constants/tableGraph'
|
||||
import {Cell, QueryConfig} from 'src/types'
|
||||
import {CellType, Dashboard, DecimalPlaces} from 'src/types/dashboard'
|
||||
import {TimeRange} from 'src/types/query'
|
||||
import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants'
|
||||
|
||||
export const UNTITLED_GRAPH: string = 'Untitled Graph'
|
||||
|
||||
|
@ -106,84 +104,7 @@ export const NEW_DASHBOARD: NewDefaultDashboard = {
|
|||
cells: [NEW_DEFAULT_DASHBOARD_CELL],
|
||||
}
|
||||
|
||||
export const TEMPLATE_TYPES = [
|
||||
{
|
||||
text: 'CSV',
|
||||
type: 'csv',
|
||||
},
|
||||
{
|
||||
text: 'Databases',
|
||||
type: 'databases',
|
||||
},
|
||||
{
|
||||
text: 'Measurements',
|
||||
type: 'measurements',
|
||||
},
|
||||
{
|
||||
text: 'Field Keys',
|
||||
type: 'fieldKeys',
|
||||
},
|
||||
{
|
||||
text: 'Tag Keys',
|
||||
type: 'tagKeys',
|
||||
},
|
||||
{
|
||||
text: 'Tag Values',
|
||||
type: 'tagValues',
|
||||
},
|
||||
]
|
||||
|
||||
export const TEMPLATE_VARIABLE_TYPES = {
|
||||
csv: 'csv',
|
||||
databases: 'database',
|
||||
measurements: 'measurement',
|
||||
fieldKeys: 'fieldKey',
|
||||
tagKeys: 'tagKey',
|
||||
tagValues: 'tagValue',
|
||||
}
|
||||
|
||||
interface TemplateVariableQueries {
|
||||
databases: string
|
||||
measurements: string
|
||||
fieldKeys: string
|
||||
tagKeys: string
|
||||
tagValues: string
|
||||
}
|
||||
|
||||
export const TEMPLATE_VARIABLE_QUERIES: TemplateVariableQueries = {
|
||||
databases: 'SHOW DATABASES',
|
||||
measurements: 'SHOW MEASUREMENTS ON :database:',
|
||||
fieldKeys: 'SHOW FIELD KEYS ON :database: FROM :measurement:',
|
||||
tagKeys: 'SHOW TAG KEYS ON :database: FROM :measurement:',
|
||||
tagValues:
|
||||
'SHOW TAG VALUES ON :database: FROM :measurement: WITH KEY=:tagKey:',
|
||||
}
|
||||
|
||||
export const MATCH_INCOMPLETE_TEMPLATES = /:[\w-]*/g
|
||||
|
||||
export const applyMasks = query => {
|
||||
const matchWholeTemplates = /:([\w-]*):/g
|
||||
const maskForWholeTemplates = '😸$1😸'
|
||||
return query.replace(matchWholeTemplates, maskForWholeTemplates)
|
||||
}
|
||||
export const insertTempVar = (query, tempVar) => {
|
||||
return query.replace(MATCH_INCOMPLETE_TEMPLATES, tempVar)
|
||||
}
|
||||
export const unMask = query => {
|
||||
return query.replace(/😸/g, ':')
|
||||
}
|
||||
export const removeUnselectedTemplateValues = templates => {
|
||||
return templates.map(template => {
|
||||
const selectedValues = template.values.filter(value => value.selected)
|
||||
return {...template, values: selectedValues}
|
||||
})
|
||||
}
|
||||
|
||||
export const TYPE_QUERY_CONFIG: string = 'queryConfig'
|
||||
export const TYPE_SHIFTED: string = 'shifted queryConfig'
|
||||
export const TYPE_FLUX: string = 'flux'
|
||||
export const DASHBOARD_NAME_MAX_LENGTH: number = 50
|
||||
export const TEMPLATE_RANGE: TimeRange = {
|
||||
upper: null,
|
||||
lower: TEMP_VAR_DASHBOARD_TIME,
|
||||
}
|
||||
|
|
|
@ -11,9 +11,8 @@ import {isUserAuthorized, EDITOR_ROLE} from 'src/auth/Authorized'
|
|||
import CellEditorOverlay from 'src/dashboards/components/CellEditorOverlay'
|
||||
import DashboardHeader from 'src/dashboards/components/DashboardHeader'
|
||||
import Dashboard from 'src/dashboards/components/Dashboard'
|
||||
import TemplateVariableManager from 'src/dashboards/components/template_variables/TemplateVariableManager'
|
||||
import ManualRefresh from 'src/shared/components/ManualRefresh'
|
||||
import TemplateControlBar from 'src/dashboards/components/TemplateControlBar'
|
||||
import TemplateControlBar from 'src/tempVars/components/TemplateControlBar'
|
||||
|
||||
import {errorThrown as errorThrownAction} from 'shared/actions/errors'
|
||||
import {notify as notifyAction} from 'shared/actions/notifications'
|
||||
|
@ -48,7 +47,6 @@ import {FORMAT_INFLUXQL, defaultTimeRange} from 'src/shared/data/timeRanges'
|
|||
|
||||
import {colorsStringSchema, colorsNumberSchema} from 'shared/schemas'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {OverlayContext} from 'src/shared/components/OverlayTechnology'
|
||||
|
||||
import {getDeep} from 'src/utils/wrappers'
|
||||
|
||||
|
@ -174,31 +172,6 @@ class DashboardPage extends Component {
|
|||
return topInView && bottomInView
|
||||
}
|
||||
|
||||
handleOpenTemplateManager = () => {
|
||||
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 => {
|
||||
const {
|
||||
dashboardActions,
|
||||
|
@ -311,10 +284,7 @@ class DashboardPage extends Component {
|
|||
dashboardActions.putDashboardByID(dashboardID)
|
||||
}
|
||||
|
||||
handleEditTemplateVariables = (
|
||||
templates,
|
||||
onSaveTemplatesSuccess
|
||||
) => async () => {
|
||||
handleSaveTemplateVariables = async templates => {
|
||||
const {location, dashboardActions, dashboard} = this.props
|
||||
|
||||
try {
|
||||
|
@ -322,7 +292,6 @@ class DashboardPage extends Component {
|
|||
...dashboard,
|
||||
templates,
|
||||
})
|
||||
onSaveTemplatesSuccess()
|
||||
const deletedTempVars = dashboard.templates.filter(
|
||||
({tempVar: oldTempVar}) =>
|
||||
!templates.find(({tempVar: newTempVar}) => oldTempVar === newTempVar)
|
||||
|
@ -481,9 +450,10 @@ class DashboardPage extends Component {
|
|||
templates={dashboard && dashboard.templates}
|
||||
meRole={meRole}
|
||||
isUsingAuth={isUsingAuth}
|
||||
onSaveTemplates={this.handleSaveTemplateVariables}
|
||||
onSelectTemplate={this.handleSelectTemplate}
|
||||
onOpenTemplateManager={this.handleOpenTemplateManager}
|
||||
isOpen={showTemplateControlBar}
|
||||
source={source}
|
||||
/>
|
||||
)}
|
||||
{dashboard ? (
|
||||
|
@ -504,7 +474,6 @@ class DashboardPage extends Component {
|
|||
onDeleteCell={this.handleDeleteDashboardCell}
|
||||
onCloneCell={this.handleCloneCell}
|
||||
showTemplateControlBar={showTemplateControlBar}
|
||||
onOpenTemplateManager={this.handleOpenTemplateManager}
|
||||
templatesIncludingDashTime={templatesIncludingDashTime}
|
||||
onSummonOverlayTechnologies={handleShowCellEditorOverlay}
|
||||
/>
|
||||
|
|
|
@ -16,7 +16,7 @@ export const initialState = {
|
|||
activeCellID: '',
|
||||
}
|
||||
|
||||
import {TEMPLATE_VARIABLE_TYPES} from 'src/dashboards/constants'
|
||||
import {TEMPLATE_VARIABLE_TYPES} from 'src/tempVars/constants'
|
||||
|
||||
const ui = (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import _ from 'lodash'
|
||||
|
||||
import {TEMPLATE_VARIABLE_QUERIES} from 'src/dashboards/constants'
|
||||
import {
|
||||
Dashboard,
|
||||
Template,
|
||||
|
@ -8,65 +7,7 @@ import {
|
|||
TemplateValue,
|
||||
URLQueryParams,
|
||||
} from 'src/types'
|
||||
import {TemplateUpdate} from 'src/types/tempVars'
|
||||
|
||||
interface PartialTemplateWithQuery {
|
||||
query: string
|
||||
tempVars: Array<Partial<Template>>
|
||||
}
|
||||
|
||||
const generateTemplateVariableQuery = ({
|
||||
type,
|
||||
query: {
|
||||
database,
|
||||
// rp, TODO
|
||||
measurement,
|
||||
tagKey,
|
||||
},
|
||||
}: Partial<Template>): PartialTemplateWithQuery => {
|
||||
const tempVars = []
|
||||
|
||||
if (database) {
|
||||
tempVars.push({
|
||||
tempVar: ':database:',
|
||||
values: [
|
||||
{
|
||||
type: 'database',
|
||||
value: database,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
if (measurement) {
|
||||
tempVars.push({
|
||||
tempVar: ':measurement:',
|
||||
values: [
|
||||
{
|
||||
type: 'measurement',
|
||||
value: measurement,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
if (tagKey) {
|
||||
tempVars.push({
|
||||
tempVar: ':tagKey:',
|
||||
values: [
|
||||
{
|
||||
type: 'tagKey',
|
||||
value: tagKey,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
const query: string = TEMPLATE_VARIABLE_QUERIES[type]
|
||||
|
||||
return {
|
||||
query,
|
||||
tempVars,
|
||||
}
|
||||
}
|
||||
import {TemplateUpdate} from 'src/types'
|
||||
|
||||
export const makeQueryForTemplate = ({
|
||||
influxql,
|
||||
|
@ -97,7 +38,7 @@ export const generateURLQueryParamsFromTempVars = (
|
|||
return urlQueryParams
|
||||
}
|
||||
|
||||
export const isValidTempVarOverride = (
|
||||
const isValidTempVarOverride = (
|
||||
values: TemplateValue[],
|
||||
overrideValue: string
|
||||
): boolean => !!values.find(({value}) => value === overrideValue)
|
||||
|
@ -199,5 +140,3 @@ export const findInvalidTempVarsInURLQuery = (
|
|||
|
||||
return urlQueryParamsTempVarsWithInvalidValues
|
||||
}
|
||||
|
||||
export default generateTemplateVariableQuery
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import _ from 'lodash'
|
||||
import {fetchTimeSeriesAsync} from 'src/shared/actions/timeSeries'
|
||||
import {removeUnselectedTemplateValues} from 'src/dashboards/constants'
|
||||
import {removeUnselectedTemplateValues} from 'src/tempVars/constants'
|
||||
|
||||
import {intervalValuesPoints} from 'src/shared/constants'
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import React, {SFC} from 'react'
|
||||
|
||||
import {RemoteDataState} from 'src/types'
|
||||
import LoadingSpinner from 'src/flux/components/LoadingSpinner'
|
||||
import Dropdown from 'src/shared/components/Dropdown'
|
||||
|
||||
interface Props {
|
||||
rds: RemoteDataState
|
||||
children: typeof Dropdown
|
||||
}
|
||||
|
||||
const DropdownLoadingPlaceholder: SFC<Props> = ({children, rds}) => {
|
||||
if (rds === RemoteDataState.Loading) {
|
||||
return (
|
||||
<div className="dropdown-placeholder">
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
export default DropdownLoadingPlaceholder
|
|
@ -0,0 +1,12 @@
|
|||
import React, {SFC} from 'react'
|
||||
|
||||
const SimpleOverlayTechnology: SFC = ({children}) => {
|
||||
return (
|
||||
<div className="overlay-tech show">
|
||||
<div className="overlay--dialog">{children}</div>
|
||||
<div className="overlay--mask" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SimpleOverlayTechnology
|
|
@ -1,5 +1,6 @@
|
|||
import _ from 'lodash'
|
||||
|
||||
import {TemplateValueType, TemplateType} from 'src/types'
|
||||
import {CellType} from 'src/types/dashboard'
|
||||
|
||||
export const NO_CELL = 'none'
|
||||
|
@ -440,12 +441,12 @@ export const DEFAULT_SOURCE = {
|
|||
|
||||
export const defaultIntervalValue = '333'
|
||||
export const intervalValuesPoints = [
|
||||
{value: defaultIntervalValue, type: 'points', selected: true},
|
||||
{value: defaultIntervalValue, type: TemplateValueType.Points, selected: true},
|
||||
]
|
||||
|
||||
export const interval = {
|
||||
id: 'interval',
|
||||
type: 'autoGroupBy',
|
||||
type: TemplateType.AutoGroupBy,
|
||||
tempVar: TEMP_VAR_INTERVAL,
|
||||
label: 'automatically determine the best group by time',
|
||||
values: intervalValuesPoints,
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
@import 'components/threesizer';
|
||||
@import 'components/threshold-controls';
|
||||
@import 'components/kapacitor-logs-table';
|
||||
@import 'components/dropdown-placeholder';
|
||||
|
||||
// Pages
|
||||
@import 'pages/config-endpoints';
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
.dropdown-placeholder {
|
||||
background-color: $g5-pepper;
|
||||
border-radius: 4px;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: $form-md-height;
|
||||
|
||||
.loading-spinner .spinner div {
|
||||
background-color: $g14-chromium;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
$padding: 20px 30px;
|
||||
|
||||
.edit-temp-var {
|
||||
margin: 0 auto;
|
||||
width: 650px;
|
||||
}
|
||||
|
||||
.edit-temp-var--header {
|
||||
background-color: $g0-obsidian;
|
||||
padding: $padding;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.edit-temp-var--header-controls > button {
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.edit-temp-var--body {
|
||||
@include gradient-v($g3-castle, $g1-raven);
|
||||
padding: $padding;
|
||||
|
||||
.delete {
|
||||
margin: 20px auto 10px auto;
|
||||
width: 90px;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-temp-var--body-row {
|
||||
display: flex;
|
||||
|
||||
.name {
|
||||
flex: 1 1 50%;
|
||||
padding-right: 10px;
|
||||
|
||||
input {
|
||||
color: $c-potassium;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.template-type {
|
||||
flex: 1 1 50%;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dropdown-toggle, .dropdown-placeholder {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.temp-builder {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.temp-builder--mq-controls {
|
||||
background: $g3-castle;
|
||||
border-radius: $radius-small;
|
||||
display: flex;
|
||||
padding: 10px 10px 0 10px;
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
> .temp-builder--mq-text, > .dropdown, .dropdown-placeholder {
|
||||
margin-right: 5px;
|
||||
flex-grow: 1;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.temp-builder--mq-text {
|
||||
@include no-user-select();
|
||||
background-color: $g5-pepper;
|
||||
border-radius: $radius-small;
|
||||
padding: 8px;
|
||||
white-space: nowrap;
|
||||
color: $c-pool;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-family: $code-font;
|
||||
}
|
||||
|
||||
.temp-builder .temp-builder-results {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
color: $g19-ghost;
|
||||
}
|
||||
|
||||
50% {
|
||||
color: $g12-forge;
|
||||
}
|
||||
|
||||
100% {
|
||||
color: $g19-ghost;
|
||||
}
|
||||
}
|
||||
|
||||
.temp-builder-results > p {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
color: $g19-ghost;
|
||||
margin: 15px 0;
|
||||
|
||||
&.error {
|
||||
color: $c-fire;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
color: $c-pineapple;
|
||||
}
|
||||
|
||||
&.loading {
|
||||
animation: pulse 1.5s ease infinite;
|
||||
}
|
||||
|
||||
> strong {
|
||||
color: $c-potassium;
|
||||
}
|
||||
}
|
||||
|
||||
.temp-builder-results--list {
|
||||
max-height: 250px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
background-color: $g3-castle;
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: $radius-small;
|
||||
margin: 0;
|
||||
color: $g19-ghost;
|
||||
font-weight: bold;
|
||||
list-style: none;
|
||||
}
|
||||
}
|
|
@ -62,7 +62,7 @@ button.btn.template-control--manage {
|
|||
font-weight: 500;
|
||||
@include no-user-select();
|
||||
}
|
||||
.template-control--dropdown {
|
||||
.template-control--controls > .template-control--dropdown {
|
||||
flex: 0 1 auto;
|
||||
min-width: $template-control-dropdown-min-width;
|
||||
max-width: $template-control-dropdown-max-width;
|
||||
|
@ -71,29 +71,32 @@ button.btn.template-control--manage {
|
|||
align-items: stretch;
|
||||
margin: $template-control--margin;
|
||||
|
||||
.dropdown {
|
||||
> .dropdown {
|
||||
order: 2;
|
||||
margin: 0;
|
||||
flex: 1 0 0%;
|
||||
|
||||
.dropdown-toggle {
|
||||
border-radius: 0 0 $radius-small $radius-small;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
font-family: $code-font;
|
||||
}
|
||||
|
||||
.dropdown-menu .fancy-scroll--view li.dropdown-item a {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
font-family: $code-font;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.dropdown-toggle {
|
||||
border-radius: 0 0 $radius-small $radius-small;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
font-family: $code-font;
|
||||
}
|
||||
.dropdown .dropdown-menu .fancy-scroll--view li.dropdown-item a {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
font-family: $code-font;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
}
|
||||
.template-control--label {
|
||||
@include no-user-select();
|
||||
order: 1;
|
||||
height: 18px;
|
||||
height: 22px;
|
||||
padding: 0 8px;
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
|
@ -102,4 +105,13 @@ button.btn.template-control--manage {
|
|||
line-height: 18px;
|
||||
border-radius: $radius-small $radius-small 0 0;
|
||||
background-color: $g4-onyx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
> .icon {
|
||||
padding-bottom: 1px;
|
||||
color: $g11-sidewalk;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -495,6 +495,12 @@ $dash-graph-options-arrow: 8px;
|
|||
*/
|
||||
|
||||
@import '../components/template-variables-manager';
|
||||
/*
|
||||
Edit Template Variable
|
||||
------------------------------------------------------
|
||||
*/
|
||||
|
||||
@import '../components/edit-template-variable';
|
||||
/*
|
||||
Write Data Form
|
||||
------------------------------------------------------
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import React, {PureComponent, ChangeEvent} from 'react'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import TemplatePreviewList from 'src/tempVars/components/TemplatePreviewList'
|
||||
|
||||
import {TemplateBuilderProps, TemplateValueType} from 'src/types'
|
||||
|
||||
interface State {
|
||||
templateValues: string[]
|
||||
templateValuesString: string
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class CSVTemplateBuilder extends PureComponent<TemplateBuilderProps, State> {
|
||||
public constructor(props) {
|
||||
super(props)
|
||||
|
||||
const templateValues = props.template.values.map(v => v.value)
|
||||
|
||||
this.state = {
|
||||
templateValues,
|
||||
templateValuesString: templateValues.join(', '),
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {templateValues, templateValuesString} = this.state
|
||||
const pluralizer = templateValues.length === 1 ? '' : 's'
|
||||
|
||||
return (
|
||||
<div className="temp-builder csv-temp-builder">
|
||||
<div className="form-group">
|
||||
<label>Comma Separated Values</label>
|
||||
<div className="temp-builder--mq-controls">
|
||||
<textarea
|
||||
className="form-control"
|
||||
value={templateValuesString}
|
||||
onChange={this.handleChange}
|
||||
onBlur={this.handleBlur}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="temp-builder-results">
|
||||
<p>
|
||||
CSV contains <strong>{templateValues.length}</strong> value{
|
||||
pluralizer
|
||||
}
|
||||
</p>
|
||||
{templateValues.length > 0 && (
|
||||
<TemplatePreviewList items={templateValues} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private handleChange = (e: ChangeEvent<HTMLTextAreaElement>): void => {
|
||||
this.setState({templateValuesString: e.target.value})
|
||||
}
|
||||
|
||||
private handleBlur = (): void => {
|
||||
const {template, onUpdateTemplate} = this.props
|
||||
const {templateValuesString} = this.state
|
||||
|
||||
let templateValues
|
||||
|
||||
if (templateValuesString.trim() === '') {
|
||||
templateValues = []
|
||||
} else {
|
||||
templateValues = templateValuesString.split(',').map(s => s.trim())
|
||||
}
|
||||
|
||||
this.setState({templateValues})
|
||||
|
||||
const nextValues = templateValues.map(value => {
|
||||
return {
|
||||
type: TemplateValueType.CSV,
|
||||
value,
|
||||
selected: false,
|
||||
}
|
||||
})
|
||||
|
||||
if (nextValues.length > 0) {
|
||||
nextValues[0].selected = true
|
||||
}
|
||||
|
||||
onUpdateTemplate({...template, values: nextValues})
|
||||
}
|
||||
}
|
||||
|
||||
export default CSVTemplateBuilder
|
|
@ -0,0 +1,94 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {showDatabases} from 'src/shared/apis/metaQuery'
|
||||
import parseShowDatabases from 'src/shared/parsing/showDatabases'
|
||||
import TemplateMetaQueryPreview from 'src/tempVars/components/TemplateMetaQueryPreview'
|
||||
|
||||
import {
|
||||
TemplateBuilderProps,
|
||||
RemoteDataState,
|
||||
TemplateValueType,
|
||||
} from 'src/types'
|
||||
|
||||
interface State {
|
||||
databases: string[]
|
||||
databasesStatus: RemoteDataState
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class DatabasesTemplateBuilder extends PureComponent<
|
||||
TemplateBuilderProps,
|
||||
State
|
||||
> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
databases: [],
|
||||
databasesStatus: RemoteDataState.Loading,
|
||||
}
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
this.loadDatabases()
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {databases, databasesStatus} = this.state
|
||||
|
||||
return (
|
||||
<div className="temp-builder databases-temp-builder">
|
||||
<div className="form-group">
|
||||
<label>Meta Query</label>
|
||||
<div className="temp-builder--mq-controls">
|
||||
<div className="temp-builder--mq-text">SHOW DATABASES</div>
|
||||
</div>
|
||||
</div>
|
||||
<TemplateMetaQueryPreview
|
||||
items={databases}
|
||||
loadingStatus={databasesStatus}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private async loadDatabases(): Promise<void> {
|
||||
const {template, source, onUpdateTemplate} = this.props
|
||||
|
||||
this.setState({databasesStatus: RemoteDataState.Loading})
|
||||
|
||||
try {
|
||||
const {data} = await showDatabases(source.links.proxy)
|
||||
const {databases} = parseShowDatabases(data)
|
||||
|
||||
this.setState({
|
||||
databases,
|
||||
databasesStatus: RemoteDataState.Done,
|
||||
})
|
||||
|
||||
const nextValues = databases.map(db => {
|
||||
return {
|
||||
type: TemplateValueType.Database,
|
||||
value: db,
|
||||
selected: false,
|
||||
}
|
||||
})
|
||||
|
||||
if (nextValues[0]) {
|
||||
nextValues[0].selected = true
|
||||
}
|
||||
|
||||
const nextTemplate = {
|
||||
...template,
|
||||
values: nextValues,
|
||||
}
|
||||
|
||||
onUpdateTemplate(nextTemplate)
|
||||
} catch {
|
||||
this.setState({databasesStatus: RemoteDataState.Error})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DatabasesTemplateBuilder
|
|
@ -0,0 +1,39 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import {TemplateBuilderProps, TemplateValueType} from 'src/types'
|
||||
import KeysTemplateBuilder from 'src/tempVars/components/KeysTemplateBuilder'
|
||||
import {proxy} from 'src/utils/queryUrlGenerator'
|
||||
import parseShowFieldKeys from 'src/shared/parsing/showFieldKeys'
|
||||
|
||||
const fetchKeys = async (source, db, measurement): Promise<string[]> => {
|
||||
const {data} = await proxy({
|
||||
source: source.links.proxy,
|
||||
db,
|
||||
query: `SHOW FIELD KEYS ON "${db}" FROM "${measurement}"`,
|
||||
})
|
||||
|
||||
const {fieldSets} = parseShowFieldKeys(data)
|
||||
const fieldKeys = _.get(Object.values(fieldSets), '0', [])
|
||||
|
||||
return fieldKeys
|
||||
}
|
||||
|
||||
class FieldKeysTemplateBuilder extends PureComponent<TemplateBuilderProps> {
|
||||
public render() {
|
||||
const {template, source, onUpdateTemplate} = this.props
|
||||
|
||||
return (
|
||||
<KeysTemplateBuilder
|
||||
queryPrefix={'SHOW FIELD KEYS ON'}
|
||||
templateValueType={TemplateValueType.FieldKey}
|
||||
fetchKeys={fetchKeys}
|
||||
template={template}
|
||||
source={source}
|
||||
onUpdateTemplate={onUpdateTemplate}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default FieldKeysTemplateBuilder
|
|
@ -0,0 +1,244 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
|
||||
import {getDeep} from 'src/utils/wrappers'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import Dropdown from 'src/shared/components/Dropdown'
|
||||
import {showDatabases, showMeasurements} from 'src/shared/apis/metaQuery'
|
||||
import parseShowDatabases from 'src/shared/parsing/showDatabases'
|
||||
import parseShowMeasurements from 'src/shared/parsing/showMeasurements'
|
||||
import TemplateMetaQueryPreview from 'src/tempVars/components/TemplateMetaQueryPreview'
|
||||
import DropdownLoadingPlaceholder from 'src/shared/components/DropdownLoadingPlaceholder'
|
||||
|
||||
import {
|
||||
TemplateBuilderProps,
|
||||
TemplateValueType,
|
||||
RemoteDataState,
|
||||
Source,
|
||||
} from 'src/types'
|
||||
|
||||
interface Props extends TemplateBuilderProps {
|
||||
queryPrefix: string
|
||||
templateValueType: TemplateValueType
|
||||
fetchKeys: (
|
||||
source: Source,
|
||||
db: string,
|
||||
measurement: string
|
||||
) => Promise<string[]>
|
||||
}
|
||||
|
||||
interface State {
|
||||
databases: string[]
|
||||
databasesStatus: RemoteDataState
|
||||
selectedDatabase: string
|
||||
measurements: string[]
|
||||
measurementsStatus: RemoteDataState
|
||||
selectedMeasurement: string
|
||||
keys: string[]
|
||||
keysStatus: RemoteDataState
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class KeysTemplateBuilder extends PureComponent<Props, State> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
const selectedDatabase = getDeep(props, 'template.query.db', '')
|
||||
const selectedMeasurement = getDeep(props, 'template.query.measurement', '')
|
||||
|
||||
this.state = {
|
||||
databases: [],
|
||||
databasesStatus: RemoteDataState.Loading,
|
||||
selectedDatabase,
|
||||
measurements: [],
|
||||
measurementsStatus: RemoteDataState.Loading,
|
||||
selectedMeasurement,
|
||||
keys: [],
|
||||
keysStatus: RemoteDataState.Loading,
|
||||
}
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
await this.loadDatabases()
|
||||
await this.loadMeasurements()
|
||||
await this.loadKeys()
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {queryPrefix} = this.props
|
||||
const {
|
||||
databases,
|
||||
databasesStatus,
|
||||
selectedDatabase,
|
||||
measurements,
|
||||
measurementsStatus,
|
||||
selectedMeasurement,
|
||||
keys,
|
||||
keysStatus,
|
||||
} = this.state
|
||||
|
||||
return (
|
||||
<div className="temp-builder measurements-temp-builder">
|
||||
<div className="form-group">
|
||||
<label>Meta Query</label>
|
||||
<div className="temp-builder--mq-controls">
|
||||
<div className="temp-builder--mq-text">{queryPrefix}</div>
|
||||
<DropdownLoadingPlaceholder rds={databasesStatus}>
|
||||
<Dropdown
|
||||
items={databases.map(text => ({text}))}
|
||||
onChoose={this.handleChooseDatabaseDropdown}
|
||||
selected={selectedDatabase}
|
||||
buttonSize=""
|
||||
/>
|
||||
</DropdownLoadingPlaceholder>
|
||||
<div className="temp-builder--mq-text">FROM</div>
|
||||
<DropdownLoadingPlaceholder rds={measurementsStatus}>
|
||||
<Dropdown
|
||||
items={measurements.map(text => ({text}))}
|
||||
onChoose={this.handleChooseMeasurementDropdown}
|
||||
selected={selectedMeasurement}
|
||||
buttonSize=""
|
||||
/>
|
||||
</DropdownLoadingPlaceholder>
|
||||
</div>
|
||||
</div>
|
||||
<TemplateMetaQueryPreview items={keys} loadingStatus={keysStatus} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private async loadDatabases(): Promise<void> {
|
||||
const {source} = this.props
|
||||
|
||||
this.setState({databasesStatus: RemoteDataState.Loading})
|
||||
|
||||
try {
|
||||
const {data} = await showDatabases(source.links.proxy)
|
||||
const {databases} = parseShowDatabases(data)
|
||||
const {selectedDatabase} = this.state
|
||||
|
||||
this.setState({
|
||||
databases,
|
||||
databasesStatus: RemoteDataState.Done,
|
||||
})
|
||||
|
||||
if (!selectedDatabase) {
|
||||
this.handleChooseDatabase(getDeep(databases, 0, ''))
|
||||
}
|
||||
} catch (error) {
|
||||
this.setState({databasesStatus: RemoteDataState.Error})
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
private async loadMeasurements(): Promise<void> {
|
||||
const {source} = this.props
|
||||
const {selectedDatabase, selectedMeasurement} = this.state
|
||||
|
||||
this.setState({measurementsStatus: RemoteDataState.Loading})
|
||||
|
||||
try {
|
||||
const {data} = await showMeasurements(
|
||||
source.links.proxy,
|
||||
selectedDatabase
|
||||
)
|
||||
const {measurementSets} = parseShowMeasurements(data)
|
||||
const measurements = getDeep(measurementSets, '0.measurements', [])
|
||||
|
||||
this.setState({
|
||||
measurements,
|
||||
measurementsStatus: RemoteDataState.Done,
|
||||
})
|
||||
|
||||
if (!selectedMeasurement) {
|
||||
this.handleChooseMeasurement(getDeep(measurements, 0, ''))
|
||||
}
|
||||
} catch (error) {
|
||||
this.setState({measurementsStatus: RemoteDataState.Error})
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
private async loadKeys(): Promise<void> {
|
||||
const {
|
||||
template,
|
||||
onUpdateTemplate,
|
||||
templateValueType,
|
||||
fetchKeys,
|
||||
source,
|
||||
} = this.props
|
||||
|
||||
const {selectedDatabase, selectedMeasurement} = this.state
|
||||
|
||||
this.setState({keysStatus: RemoteDataState.Loading})
|
||||
|
||||
try {
|
||||
const keys = await fetchKeys(
|
||||
source,
|
||||
selectedDatabase,
|
||||
selectedMeasurement
|
||||
)
|
||||
|
||||
this.setState({
|
||||
keys,
|
||||
keysStatus: RemoteDataState.Done,
|
||||
})
|
||||
|
||||
const nextValues = keys.map(value => {
|
||||
return {
|
||||
type: templateValueType,
|
||||
value,
|
||||
selected: false,
|
||||
}
|
||||
})
|
||||
|
||||
if (nextValues[0]) {
|
||||
nextValues[0].selected = true
|
||||
}
|
||||
|
||||
onUpdateTemplate({...template, values: nextValues})
|
||||
} catch (error) {
|
||||
this.setState({keysStatus: RemoteDataState.Error})
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
private handleChooseDatabaseDropdown = ({text}) => {
|
||||
this.handleChooseDatabase(text)
|
||||
}
|
||||
|
||||
private handleChooseDatabase = (db: string): void => {
|
||||
this.setState({selectedDatabase: db, selectedMeasurement: ''}, () =>
|
||||
this.loadMeasurements()
|
||||
)
|
||||
|
||||
const {template, onUpdateTemplate} = this.props
|
||||
|
||||
onUpdateTemplate({
|
||||
...template,
|
||||
query: {
|
||||
...template.query,
|
||||
db,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private handleChooseMeasurementDropdown = ({text}): void => {
|
||||
this.handleChooseMeasurement(text)
|
||||
}
|
||||
|
||||
private handleChooseMeasurement = (measurement: string): void => {
|
||||
this.setState({selectedMeasurement: measurement}, () => this.loadKeys())
|
||||
|
||||
const {template, onUpdateTemplate} = this.props
|
||||
|
||||
onUpdateTemplate({
|
||||
...template,
|
||||
query: {
|
||||
...template.query,
|
||||
measurement,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default KeysTemplateBuilder
|
|
@ -0,0 +1,165 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import Dropdown from 'src/shared/components/Dropdown'
|
||||
import {showDatabases, showMeasurements} from 'src/shared/apis/metaQuery'
|
||||
import parseShowDatabases from 'src/shared/parsing/showDatabases'
|
||||
import parseShowMeasurements from 'src/shared/parsing/showMeasurements'
|
||||
import TemplateMetaQueryPreview from 'src/tempVars/components/TemplateMetaQueryPreview'
|
||||
import DropdownLoadingPlaceholder from 'src/shared/components/DropdownLoadingPlaceholder'
|
||||
|
||||
import {
|
||||
TemplateBuilderProps,
|
||||
RemoteDataState,
|
||||
TemplateValueType,
|
||||
} from 'src/types'
|
||||
|
||||
interface State {
|
||||
databases: string[]
|
||||
databasesStatus: RemoteDataState
|
||||
selectedDatabase: string
|
||||
measurements: string[]
|
||||
measurementsStatus: RemoteDataState
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class MeasurementsTemplateBuilder extends PureComponent<
|
||||
TemplateBuilderProps,
|
||||
State
|
||||
> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
const selectedDatabase = _.get(props, 'template.query.db', '')
|
||||
|
||||
this.state = {
|
||||
databases: [],
|
||||
databasesStatus: RemoteDataState.Loading,
|
||||
selectedDatabase,
|
||||
measurements: [],
|
||||
measurementsStatus: RemoteDataState.Loading,
|
||||
}
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
await this.loadDatabases()
|
||||
await this.loadMeasurements()
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
databases,
|
||||
databasesStatus,
|
||||
selectedDatabase,
|
||||
measurements,
|
||||
measurementsStatus,
|
||||
} = this.state
|
||||
|
||||
return (
|
||||
<div className="temp-builder measurements-temp-builder">
|
||||
<div className="form-group">
|
||||
<label>Meta Query</label>
|
||||
<div className="temp-builder--mq-controls">
|
||||
<div className="temp-builder--mq-text">SHOW MEASUREMENTS ON</div>
|
||||
<DropdownLoadingPlaceholder rds={databasesStatus}>
|
||||
<Dropdown
|
||||
items={databases.map(text => ({text}))}
|
||||
onChoose={this.handleChooseDatabaseDropdown}
|
||||
selected={selectedDatabase}
|
||||
buttonSize=""
|
||||
/>
|
||||
</DropdownLoadingPlaceholder>
|
||||
</div>
|
||||
</div>
|
||||
<TemplateMetaQueryPreview
|
||||
items={measurements}
|
||||
loadingStatus={measurementsStatus}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private async loadDatabases(): Promise<void> {
|
||||
const {source} = this.props
|
||||
|
||||
this.setState({databasesStatus: RemoteDataState.Loading})
|
||||
|
||||
try {
|
||||
const {data} = await showDatabases(source.links.proxy)
|
||||
const {databases} = parseShowDatabases(data)
|
||||
const {selectedDatabase} = this.state
|
||||
|
||||
this.setState({
|
||||
databases,
|
||||
databasesStatus: RemoteDataState.Done,
|
||||
})
|
||||
|
||||
if (!selectedDatabase) {
|
||||
this.handleChooseDatabase(_.get(databases, 0, ''))
|
||||
}
|
||||
} catch (error) {
|
||||
this.setState({databasesStatus: RemoteDataState.Error})
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
private async loadMeasurements(): Promise<void> {
|
||||
const {template, source, onUpdateTemplate} = this.props
|
||||
const {selectedDatabase} = this.state
|
||||
|
||||
this.setState({measurementsStatus: RemoteDataState.Loading})
|
||||
|
||||
try {
|
||||
const {data} = await showMeasurements(
|
||||
source.links.proxy,
|
||||
selectedDatabase
|
||||
)
|
||||
const {measurementSets} = parseShowMeasurements(data)
|
||||
|
||||
const measurements = _.get(measurementSets, '0.measurements', [])
|
||||
|
||||
this.setState({
|
||||
measurements,
|
||||
measurementsStatus: RemoteDataState.Done,
|
||||
})
|
||||
|
||||
const nextValues = measurements.map(value => {
|
||||
return {
|
||||
type: TemplateValueType.Measurement,
|
||||
value,
|
||||
selected: false,
|
||||
}
|
||||
})
|
||||
|
||||
if (nextValues[0]) {
|
||||
nextValues[0].selected = true
|
||||
}
|
||||
|
||||
onUpdateTemplate({...template, values: nextValues})
|
||||
} catch (error) {
|
||||
this.setState({measurementsStatus: RemoteDataState.Error})
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
private handleChooseDatabaseDropdown = ({text}) => {
|
||||
this.handleChooseDatabase(text)
|
||||
}
|
||||
|
||||
private handleChooseDatabase = (db: string): void => {
|
||||
this.setState({selectedDatabase: db}, () => this.loadMeasurements())
|
||||
|
||||
const {template, onUpdateTemplate} = this.props
|
||||
|
||||
onUpdateTemplate({
|
||||
...template,
|
||||
query: {
|
||||
...template.query,
|
||||
db,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default MeasurementsTemplateBuilder
|
|
@ -0,0 +1,42 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
|
||||
import KeysTemplateBuilder from 'src/tempVars/components/KeysTemplateBuilder'
|
||||
import {proxy} from 'src/utils/queryUrlGenerator'
|
||||
import parseShowTagKeys from 'src/shared/parsing/showTagKeys'
|
||||
|
||||
import {TemplateBuilderProps, TemplateValueType} from 'src/types'
|
||||
|
||||
export const fetchTagKeys = async (
|
||||
source,
|
||||
db,
|
||||
measurement
|
||||
): Promise<string[]> => {
|
||||
const {data} = await proxy({
|
||||
source: source.links.proxy,
|
||||
db,
|
||||
query: `SHOW TAG KEYS ON "${db}" FROM "${measurement}"`,
|
||||
})
|
||||
|
||||
const {tagKeys} = parseShowTagKeys(data)
|
||||
|
||||
return tagKeys
|
||||
}
|
||||
|
||||
class TagKeysTemplateBuilder extends PureComponent<TemplateBuilderProps> {
|
||||
public render() {
|
||||
const {template, source, onUpdateTemplate} = this.props
|
||||
|
||||
return (
|
||||
<KeysTemplateBuilder
|
||||
queryPrefix={'SHOW TAG KEYS ON'}
|
||||
templateValueType={TemplateValueType.TagKey}
|
||||
fetchKeys={fetchTagKeys}
|
||||
template={template}
|
||||
source={source}
|
||||
onUpdateTemplate={onUpdateTemplate}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default TagKeysTemplateBuilder
|
|
@ -0,0 +1,308 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import Dropdown from 'src/shared/components/Dropdown'
|
||||
import {showDatabases, showMeasurements} from 'src/shared/apis/metaQuery'
|
||||
import {proxy} from 'src/utils/queryUrlGenerator'
|
||||
import parseShowDatabases from 'src/shared/parsing/showDatabases'
|
||||
import parseShowMeasurements from 'src/shared/parsing/showMeasurements'
|
||||
import parseShowTagValues from 'src/shared/parsing/showTagValues'
|
||||
import {fetchTagKeys} from 'src/tempVars/components/TagKeysTemplateBuilder'
|
||||
import TemplateMetaQueryPreview from 'src/tempVars/components/TemplateMetaQueryPreview'
|
||||
import DropdownLoadingPlaceholder from 'src/shared/components/DropdownLoadingPlaceholder'
|
||||
|
||||
import {
|
||||
TemplateBuilderProps,
|
||||
TemplateValueType,
|
||||
RemoteDataState,
|
||||
} from 'src/types'
|
||||
|
||||
interface State {
|
||||
databases: string[]
|
||||
databasesStatus: RemoteDataState
|
||||
selectedDatabase: string
|
||||
measurements: string[]
|
||||
measurementsStatus: RemoteDataState
|
||||
selectedMeasurement: string
|
||||
tagKeys: string[]
|
||||
tagKeysStatus: RemoteDataState
|
||||
selectedTagKey: string
|
||||
tagValues: string[]
|
||||
tagValuesStatus: RemoteDataState
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class KeysTemplateBuilder extends PureComponent<TemplateBuilderProps, State> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
const selectedDatabase = _.get(props, 'template.query.db', '')
|
||||
const selectedMeasurement = _.get(props, 'template.query.measurement', '')
|
||||
const selectedTagKey = _.get(props, 'template.query.tagKey', '')
|
||||
|
||||
this.state = {
|
||||
databases: [],
|
||||
databasesStatus: RemoteDataState.Loading,
|
||||
selectedDatabase,
|
||||
measurements: [],
|
||||
measurementsStatus: RemoteDataState.Loading,
|
||||
selectedMeasurement,
|
||||
tagKeys: [],
|
||||
tagKeysStatus: RemoteDataState.Loading,
|
||||
selectedTagKey,
|
||||
tagValues: [],
|
||||
tagValuesStatus: RemoteDataState.Loading,
|
||||
}
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
await this.loadDatabases()
|
||||
await this.loadMeasurements()
|
||||
await this.loadTagKeys()
|
||||
await this.loadTagValues()
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
databases,
|
||||
databasesStatus,
|
||||
selectedDatabase,
|
||||
measurements,
|
||||
measurementsStatus,
|
||||
selectedMeasurement,
|
||||
tagKeys,
|
||||
tagKeysStatus,
|
||||
selectedTagKey,
|
||||
tagValues,
|
||||
tagValuesStatus,
|
||||
} = this.state
|
||||
|
||||
return (
|
||||
<div className="temp-builder measurements-temp-builder">
|
||||
<div className="form-group">
|
||||
<label>Meta Query</label>
|
||||
<div className="temp-builder--mq-controls">
|
||||
<div className="temp-builder--mq-text">SHOW TAG VALUES ON</div>
|
||||
<DropdownLoadingPlaceholder rds={databasesStatus}>
|
||||
<Dropdown
|
||||
items={databases.map(text => ({text}))}
|
||||
onChoose={this.handleChooseDatabaseDropdown}
|
||||
selected={selectedDatabase}
|
||||
buttonSize=""
|
||||
/>
|
||||
</DropdownLoadingPlaceholder>
|
||||
</div>
|
||||
<div className="temp-builder--mq-controls">
|
||||
<div className="temp-builder--mq-text">FROM</div>
|
||||
<DropdownLoadingPlaceholder rds={measurementsStatus}>
|
||||
<Dropdown
|
||||
items={measurements.map(text => ({text}))}
|
||||
onChoose={this.handleChooseMeasurementDropdown}
|
||||
selected={selectedMeasurement}
|
||||
buttonSize=""
|
||||
/>
|
||||
</DropdownLoadingPlaceholder>
|
||||
<div className="temp-builder--mq-text">WITH KEY</div>
|
||||
<DropdownLoadingPlaceholder rds={tagKeysStatus}>
|
||||
<Dropdown
|
||||
items={tagKeys.map(text => ({text}))}
|
||||
onChoose={this.handleChooseTagKeyDropdown}
|
||||
selected={selectedTagKey}
|
||||
buttonSize=""
|
||||
/>
|
||||
</DropdownLoadingPlaceholder>
|
||||
</div>
|
||||
</div>
|
||||
<TemplateMetaQueryPreview
|
||||
items={tagValues}
|
||||
loadingStatus={tagValuesStatus}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private async loadDatabases(): Promise<void> {
|
||||
const {source} = this.props
|
||||
|
||||
this.setState({databasesStatus: RemoteDataState.Loading})
|
||||
|
||||
try {
|
||||
const {data} = await showDatabases(source.links.proxy)
|
||||
const {databases} = parseShowDatabases(data)
|
||||
const {selectedDatabase} = this.state
|
||||
|
||||
this.setState({
|
||||
databases,
|
||||
databasesStatus: RemoteDataState.Done,
|
||||
})
|
||||
|
||||
if (!selectedDatabase) {
|
||||
this.handleChooseDatabase(_.get(databases, 0, ''))
|
||||
}
|
||||
} catch (error) {
|
||||
this.setState({databasesStatus: RemoteDataState.Error})
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
private async loadMeasurements(): Promise<void> {
|
||||
const {source} = this.props
|
||||
const {selectedDatabase, selectedMeasurement} = this.state
|
||||
|
||||
this.setState({measurementsStatus: RemoteDataState.Loading})
|
||||
|
||||
try {
|
||||
const {data} = await showMeasurements(
|
||||
source.links.proxy,
|
||||
selectedDatabase
|
||||
)
|
||||
const {measurementSets} = parseShowMeasurements(data)
|
||||
const measurements = _.get(measurementSets, '0.measurements', [])
|
||||
|
||||
this.setState({
|
||||
measurements,
|
||||
measurementsStatus: RemoteDataState.Done,
|
||||
})
|
||||
|
||||
if (!selectedMeasurement) {
|
||||
this.handleChooseMeasurement(_.get(measurements, 0, ''))
|
||||
}
|
||||
} catch (error) {
|
||||
this.setState({measurementsStatus: RemoteDataState.Error})
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
private async loadTagKeys(): Promise<void> {
|
||||
const {source} = this.props
|
||||
const {selectedTagKey} = this.state
|
||||
|
||||
const {selectedDatabase, selectedMeasurement} = this.state
|
||||
|
||||
this.setState({tagKeysStatus: RemoteDataState.Loading})
|
||||
|
||||
try {
|
||||
const tagKeys = await fetchTagKeys(
|
||||
source,
|
||||
selectedDatabase,
|
||||
selectedMeasurement
|
||||
)
|
||||
|
||||
this.setState({
|
||||
tagKeys,
|
||||
tagKeysStatus: RemoteDataState.Done,
|
||||
})
|
||||
|
||||
if (!selectedTagKey) {
|
||||
this.handleChooseTagKey(_.get(tagKeys, 0, ''))
|
||||
}
|
||||
} catch (error) {
|
||||
this.setState({tagKeysStatus: RemoteDataState.Error})
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
private loadTagValues = async (): Promise<void> => {
|
||||
const {source, template, onUpdateTemplate} = this.props
|
||||
const {selectedDatabase, selectedMeasurement, selectedTagKey} = this.state
|
||||
|
||||
this.setState({tagValuesStatus: RemoteDataState.Loading})
|
||||
|
||||
try {
|
||||
const {data} = await proxy({
|
||||
source: source.links.proxy,
|
||||
db: selectedDatabase,
|
||||
query: `SHOW TAG VALUES ON "${selectedDatabase}" FROM "${selectedMeasurement}" WITH KEY = "${selectedTagKey}"`,
|
||||
})
|
||||
|
||||
const {tags} = parseShowTagValues(data)
|
||||
const tagValues = _.get(Object.values(tags), 0, [])
|
||||
|
||||
this.setState({
|
||||
tagValues,
|
||||
tagValuesStatus: RemoteDataState.Done,
|
||||
})
|
||||
|
||||
const nextValues = tagValues.map(value => {
|
||||
return {
|
||||
type: TemplateValueType.TagValue,
|
||||
value,
|
||||
selected: false,
|
||||
}
|
||||
})
|
||||
|
||||
if (nextValues[0]) {
|
||||
nextValues[0].selected = true
|
||||
}
|
||||
|
||||
onUpdateTemplate({...template, values: nextValues})
|
||||
} catch (error) {
|
||||
this.setState({tagValuesStatus: RemoteDataState.Error})
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
private handleChooseDatabaseDropdown = ({text}) => {
|
||||
this.handleChooseDatabase(text)
|
||||
}
|
||||
|
||||
private handleChooseDatabase = (db: string): void => {
|
||||
this.setState({selectedDatabase: db, selectedMeasurement: ''}, () =>
|
||||
this.loadMeasurements()
|
||||
)
|
||||
|
||||
const {template, onUpdateTemplate} = this.props
|
||||
|
||||
onUpdateTemplate({
|
||||
...template,
|
||||
query: {
|
||||
...template.query,
|
||||
db,
|
||||
tagKey: '',
|
||||
measurement: '',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private handleChooseMeasurementDropdown = ({text}): void => {
|
||||
this.handleChooseMeasurement(text)
|
||||
}
|
||||
|
||||
private handleChooseMeasurement = (measurement: string): void => {
|
||||
this.setState({selectedMeasurement: measurement, selectedTagKey: ''}, () =>
|
||||
this.loadTagKeys()
|
||||
)
|
||||
|
||||
const {template, onUpdateTemplate} = this.props
|
||||
|
||||
onUpdateTemplate({
|
||||
...template,
|
||||
query: {
|
||||
...template.query,
|
||||
measurement,
|
||||
tagKey: '',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private handleChooseTagKeyDropdown = ({text}): void => {
|
||||
this.handleChooseTagKey(text)
|
||||
}
|
||||
|
||||
private handleChooseTagKey = (tagKey: string): void => {
|
||||
this.setState({selectedTagKey: tagKey}, () => this.loadTagValues())
|
||||
|
||||
const {template, onUpdateTemplate} = this.props
|
||||
|
||||
onUpdateTemplate({
|
||||
...template,
|
||||
query: {
|
||||
...template.query,
|
||||
tagKey,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default KeysTemplateBuilder
|
|
@ -0,0 +1,129 @@
|
|||
import React, {Component} from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import TemplateControlDropdown from 'src/tempVars/components/TemplateControlDropdown'
|
||||
import SimpleOverlayTechnology from 'src/shared/components/SimpleOverlayTechnology'
|
||||
import TemplateVariableEditor from 'src/tempVars/components/TemplateVariableEditor'
|
||||
import Authorized, {EDITOR_ROLE} from 'src/auth/Authorized'
|
||||
|
||||
import {Template, Source} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
meRole: string
|
||||
isUsingAuth: boolean
|
||||
templates: Template[]
|
||||
isOpen: boolean
|
||||
source: Source
|
||||
onSelectTemplate: (id: string) => void
|
||||
onSaveTemplates: (templates: Template[]) => void
|
||||
onCreateTemplateVariable: () => void
|
||||
}
|
||||
|
||||
interface State {
|
||||
isAdding: boolean
|
||||
}
|
||||
|
||||
class TemplateControlBar extends Component<Props, State> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {isAdding: false}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
isOpen,
|
||||
templates,
|
||||
onSelectTemplate,
|
||||
meRole,
|
||||
isUsingAuth,
|
||||
source,
|
||||
} = this.props
|
||||
const {isAdding} = this.state
|
||||
|
||||
return (
|
||||
<div className={classnames('template-control-bar', {show: isOpen})}>
|
||||
<div className="template-control--container">
|
||||
<div className="template-control--controls">
|
||||
{templates && templates.length ? (
|
||||
templates.map(template => (
|
||||
<TemplateControlDropdown
|
||||
key={template.id}
|
||||
meRole={meRole}
|
||||
isUsingAuth={isUsingAuth}
|
||||
template={template}
|
||||
source={source}
|
||||
onSelectTemplate={onSelectTemplate}
|
||||
onCreateTemplate={this.handleCreateTemplate}
|
||||
onUpdateTemplate={this.handleUpdateTemplate}
|
||||
onDeleteTemplate={this.handleDeleteTemplate}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="template-control--empty" data-test="empty-state">
|
||||
This dashboard does not have any{' '}
|
||||
<strong>Template Variables</strong>
|
||||
</div>
|
||||
)}
|
||||
{isAdding && (
|
||||
<SimpleOverlayTechnology>
|
||||
<TemplateVariableEditor
|
||||
source={source}
|
||||
onCreate={this.handleCreateTemplate}
|
||||
onCancel={this.handleCancelAddVariable}
|
||||
/>
|
||||
</SimpleOverlayTechnology>
|
||||
)}
|
||||
</div>
|
||||
<Authorized requiredRole={EDITOR_ROLE}>
|
||||
<button
|
||||
className="btn btn-primary btn-sm template-control--manage"
|
||||
onClick={this.handleAddVariable}
|
||||
>
|
||||
<span className="icon plus" />
|
||||
Add Variable
|
||||
</button>
|
||||
</Authorized>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private handleAddVariable = (): void => {
|
||||
this.setState({isAdding: true})
|
||||
}
|
||||
|
||||
private handleCancelAddVariable = (): void => {
|
||||
this.setState({isAdding: false})
|
||||
}
|
||||
|
||||
private handleCreateTemplate = async (template: Template): Promise<void> => {
|
||||
const {templates, onSaveTemplates} = this.props
|
||||
|
||||
await onSaveTemplates([...templates, template])
|
||||
|
||||
this.setState({isAdding: false})
|
||||
}
|
||||
|
||||
private handleUpdateTemplate = async (template: Template): Promise<void> => {
|
||||
const {templates, onSaveTemplates} = this.props
|
||||
const newTemplates = templates.reduce((acc, t) => {
|
||||
if (t.id === template.id) {
|
||||
return [...acc, template]
|
||||
}
|
||||
|
||||
return [...acc, t]
|
||||
}, [])
|
||||
|
||||
await onSaveTemplates(newTemplates)
|
||||
}
|
||||
|
||||
private handleDeleteTemplate = async (template: Template): Promise<void> => {
|
||||
const {templates, onSaveTemplates} = this.props
|
||||
const newTemplates = templates.filter(t => t.id !== template.id)
|
||||
|
||||
await onSaveTemplates(newTemplates)
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateControlBar
|
|
@ -0,0 +1,117 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
|
||||
import Dropdown from 'src/shared/components/Dropdown'
|
||||
import SimpleOverlayTechnology from 'src/shared/components/SimpleOverlayTechnology'
|
||||
import TemplateVariableEditor from 'src/tempVars/components/TemplateVariableEditor'
|
||||
import {calculateDropdownWidth} from 'src/dashboards/constants/templateControlBar'
|
||||
import Authorized, {isUserAuthorized, EDITOR_ROLE} from 'src/auth/Authorized'
|
||||
|
||||
import {Template, Source} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
template: Template
|
||||
meRole: string
|
||||
isUsingAuth: boolean
|
||||
source: Source
|
||||
onSelectTemplate: (id: string) => void
|
||||
onCreateTemplate: (template: Template) => Promise<void>
|
||||
onUpdateTemplate: (template: Template) => Promise<void>
|
||||
onDeleteTemplate: (template: Template) => Promise<void>
|
||||
}
|
||||
|
||||
interface State {
|
||||
isEditing: boolean
|
||||
}
|
||||
|
||||
class TemplateControlDropdown extends PureComponent<Props, State> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
isEditing: false,
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
template,
|
||||
isUsingAuth,
|
||||
meRole,
|
||||
source,
|
||||
onSelectTemplate,
|
||||
onCreateTemplate,
|
||||
} = this.props
|
||||
const {isEditing} = this.state
|
||||
|
||||
const dropdownItems = template.values.map(value => ({
|
||||
...value,
|
||||
text: value.value,
|
||||
}))
|
||||
|
||||
const dropdownStyle = template.values.length
|
||||
? {minWidth: calculateDropdownWidth(template.values)}
|
||||
: null
|
||||
|
||||
const selectedItem = dropdownItems.find(item => item.selected) ||
|
||||
dropdownItems[0] || {text: '(No values)'}
|
||||
|
||||
return (
|
||||
<div className="template-control--dropdown" style={dropdownStyle}>
|
||||
<Dropdown
|
||||
items={dropdownItems}
|
||||
buttonSize="btn-xs"
|
||||
menuClass="dropdown-astronaut"
|
||||
useAutoComplete={true}
|
||||
selected={selectedItem.text}
|
||||
disabled={isUsingAuth && !isUserAuthorized(meRole, EDITOR_ROLE)}
|
||||
onChoose={onSelectTemplate(template.id)}
|
||||
/>
|
||||
<Authorized requiredRole={EDITOR_ROLE}>
|
||||
<label className="template-control--label">
|
||||
{template.tempVar}
|
||||
<span
|
||||
className="icon cog-thick"
|
||||
onClick={this.handleShowSettings}
|
||||
/>
|
||||
</label>
|
||||
</Authorized>
|
||||
{isEditing && (
|
||||
<SimpleOverlayTechnology>
|
||||
<TemplateVariableEditor
|
||||
template={template}
|
||||
source={source}
|
||||
onCreate={onCreateTemplate}
|
||||
onUpdate={this.handleUpdateTemplate}
|
||||
onDelete={this.handleDelete}
|
||||
onCancel={this.handleHideSettings}
|
||||
/>
|
||||
</SimpleOverlayTechnology>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private handleShowSettings = (): void => {
|
||||
this.setState({isEditing: true})
|
||||
}
|
||||
|
||||
private handleHideSettings = (): void => {
|
||||
this.setState({isEditing: false})
|
||||
}
|
||||
|
||||
private handleUpdateTemplate = async (template: Template): Promise<void> => {
|
||||
const {onUpdateTemplate} = this.props
|
||||
|
||||
await onUpdateTemplate(template)
|
||||
|
||||
this.setState({isEditing: false})
|
||||
}
|
||||
|
||||
private handleDelete = (): Promise<any> => {
|
||||
const {onDeleteTemplate, template} = this.props
|
||||
|
||||
return onDeleteTemplate(template)
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateControlDropdown
|
|
@ -0,0 +1,61 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import TemplatePreviewList from 'src/tempVars/components/TemplatePreviewList'
|
||||
|
||||
import {RemoteDataState} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
items: string[]
|
||||
loadingStatus: RemoteDataState
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class TemplateMetaQueryPreview extends PureComponent<Props> {
|
||||
public render() {
|
||||
const {items, loadingStatus} = this.props
|
||||
|
||||
if (loadingStatus === RemoteDataState.NotStarted) {
|
||||
return <div className="temp-builder-results" />
|
||||
}
|
||||
|
||||
if (loadingStatus === RemoteDataState.Loading) {
|
||||
return (
|
||||
<div className="temp-builder-results">
|
||||
<p className="loading">Loading meta query preview...</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (loadingStatus === RemoteDataState.Error) {
|
||||
return (
|
||||
<div className="temp-builder-results">
|
||||
<p className="error">Meta Query failed to execute</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (items.length === 0) {
|
||||
return (
|
||||
<div className="temp-builder-results">
|
||||
<p className="warning">
|
||||
Meta Query is syntactically correct but returned no results
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const pluralizer = items.length === 1 ? '' : 's'
|
||||
|
||||
return (
|
||||
<div className="temp-builder-results">
|
||||
<p>
|
||||
Meta Query returned <strong>{items.length}</strong> value{pluralizer}
|
||||
</p>
|
||||
{items.length > 0 && <TemplatePreviewList items={items} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplateMetaQueryPreview
|
|
@ -0,0 +1,52 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import uuid from 'uuid'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||
|
||||
const LI_HEIGHT = 35
|
||||
const LI_MARGIN_BOTTOM = 3
|
||||
const RESULTS_TO_DISPLAY = 10
|
||||
|
||||
interface Props {
|
||||
items: string[]
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class TemplatePreviewList extends PureComponent<Props> {
|
||||
public render() {
|
||||
const {items} = this.props
|
||||
|
||||
return (
|
||||
<ul
|
||||
className="temp-builder-results--list"
|
||||
style={{height: `${this.resultsListHeight}px`}}
|
||||
>
|
||||
<FancyScrollbar>
|
||||
{items.map(db => {
|
||||
return (
|
||||
<li
|
||||
key={uuid.v4()}
|
||||
style={{
|
||||
height: `${LI_HEIGHT}px`,
|
||||
marginBottom: `${LI_MARGIN_BOTTOM}px`,
|
||||
}}
|
||||
>
|
||||
{db}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</FancyScrollbar>
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
private get resultsListHeight() {
|
||||
const {items} = this.props
|
||||
const count = Math.min(items.length, RESULTS_TO_DISPLAY)
|
||||
|
||||
return count * (LI_HEIGHT + LI_MARGIN_BOTTOM)
|
||||
}
|
||||
}
|
||||
|
||||
export default TemplatePreviewList
|
|
@ -0,0 +1,288 @@
|
|||
import React, {
|
||||
PureComponent,
|
||||
ComponentClass,
|
||||
ChangeEvent,
|
||||
KeyboardEvent,
|
||||
} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import Dropdown from 'src/shared/components/Dropdown'
|
||||
import ConfirmButton from 'src/shared/components/ConfirmButton'
|
||||
import {getDeep} from 'src/utils/wrappers'
|
||||
import {notify as notifyActionCreator} from 'src/shared/actions/notifications'
|
||||
|
||||
import DatabasesTemplateBuilder from 'src/tempVars/components/DatabasesTemplateBuilder'
|
||||
import CSVTemplateBuilder from 'src/tempVars/components/CSVTemplateBuilder'
|
||||
import MeasurementsTemplateBuilder from 'src/tempVars/components/MeasurementsTemplateBuilder'
|
||||
import FieldKeysTemplateBuilder from 'src/tempVars/components/FieldKeysTemplateBuilder'
|
||||
import TagKeysTemplateBuilder from 'src/tempVars/components/TagKeysTemplateBuilder'
|
||||
import TagValuesTemplateBuilder from 'src/tempVars/components/TagValuesTemplateBuilder'
|
||||
|
||||
import {
|
||||
Template,
|
||||
TemplateType,
|
||||
TemplateBuilderProps,
|
||||
Source,
|
||||
RemoteDataState,
|
||||
Notification,
|
||||
} from 'src/types'
|
||||
import {
|
||||
TEMPLATE_TYPES_LIST,
|
||||
DEFAULT_TEMPLATES,
|
||||
RESERVED_TEMPLATE_NAMES,
|
||||
} from 'src/tempVars/constants'
|
||||
import {FIVE_SECONDS} from 'src/shared/constants/index'
|
||||
|
||||
interface Props {
|
||||
// We will assume we are creating a new template if none is passed in
|
||||
template?: Template
|
||||
source: Source
|
||||
onCancel: () => void
|
||||
onCreate?: (template: Template) => Promise<any>
|
||||
onUpdate?: (template: Template) => Promise<any>
|
||||
onDelete?: () => Promise<any>
|
||||
notify: (n: Notification) => void
|
||||
}
|
||||
|
||||
interface State {
|
||||
nextTemplate: Template
|
||||
isNew: boolean
|
||||
savingStatus: RemoteDataState
|
||||
deletingStatus: RemoteDataState
|
||||
}
|
||||
|
||||
const TEMPLATE_BUILDERS = {
|
||||
[TemplateType.Databases]: DatabasesTemplateBuilder,
|
||||
[TemplateType.CSV]: CSVTemplateBuilder,
|
||||
[TemplateType.Measurements]: MeasurementsTemplateBuilder,
|
||||
[TemplateType.FieldKeys]: FieldKeysTemplateBuilder,
|
||||
[TemplateType.TagKeys]: TagKeysTemplateBuilder,
|
||||
[TemplateType.TagValues]: TagValuesTemplateBuilder,
|
||||
}
|
||||
|
||||
const formatName = name => `:${name.replace(/:/g, '')}:`
|
||||
|
||||
const DEFAULT_TEMPLATE = DEFAULT_TEMPLATES[TemplateType.Databases]
|
||||
|
||||
@ErrorHandling
|
||||
class TemplateVariableEditor extends PureComponent<Props, State> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
const defaultState = {
|
||||
savingStatus: RemoteDataState.NotStarted,
|
||||
deletingStatus: RemoteDataState.NotStarted,
|
||||
}
|
||||
|
||||
const {template} = this.props
|
||||
|
||||
if (template) {
|
||||
this.state = {
|
||||
...defaultState,
|
||||
nextTemplate: {...template},
|
||||
isNew: false,
|
||||
}
|
||||
} else {
|
||||
this.state = {
|
||||
...defaultState,
|
||||
nextTemplate: DEFAULT_TEMPLATE(),
|
||||
isNew: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {source, onCancel} = this.props
|
||||
const {nextTemplate, isNew} = this.state
|
||||
const TemplateBuilder = this.templateBuilder
|
||||
|
||||
return (
|
||||
<div className="edit-temp-var">
|
||||
<div className="edit-temp-var--header">
|
||||
<h1 className="page-header__title">Edit Template Variable</h1>
|
||||
<div className="edit-temp-var--header-controls">
|
||||
<button
|
||||
className="btn btn-default"
|
||||
type="button"
|
||||
onClick={onCancel}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-success"
|
||||
type="button"
|
||||
onClick={this.handleSave}
|
||||
disabled={!this.canSave}
|
||||
>
|
||||
{this.isSaving ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="edit-temp-var--body">
|
||||
<div className="edit-temp-var--body-row">
|
||||
<div className="form-group name">
|
||||
<label>Name</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
value={nextTemplate.tempVar}
|
||||
onChange={this.handleChangeName}
|
||||
onKeyPress={this.handleNameKeyPress}
|
||||
onBlur={this.formatName}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group template-type">
|
||||
<label>Type</label>
|
||||
<Dropdown
|
||||
items={TEMPLATE_TYPES_LIST}
|
||||
onChoose={this.handleChooseType}
|
||||
selected={this.dropdownSelection}
|
||||
buttonSize=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="edit-temp-var--body-row">
|
||||
<TemplateBuilder
|
||||
template={nextTemplate}
|
||||
source={source}
|
||||
onUpdateTemplate={this.handleUpdateTemplate}
|
||||
/>
|
||||
</div>
|
||||
<ConfirmButton
|
||||
text={this.isDeleting ? 'Deleting...' : 'Delete'}
|
||||
confirmAction={this.handleDelete}
|
||||
type="btn-danger"
|
||||
size="btn-xs"
|
||||
customClass="delete"
|
||||
disabled={isNew || this.isDeleting}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get templateBuilder(): ComponentClass<TemplateBuilderProps> {
|
||||
const {
|
||||
nextTemplate: {type},
|
||||
} = this.state
|
||||
|
||||
const component = TEMPLATE_BUILDERS[type]
|
||||
|
||||
if (!component) {
|
||||
throw new Error(`Could not find template builder for type "${type}"`)
|
||||
}
|
||||
|
||||
return component
|
||||
}
|
||||
|
||||
private handleUpdateTemplate = (nextTemplate: Template): void => {
|
||||
this.setState({nextTemplate})
|
||||
}
|
||||
|
||||
private handleChooseType = ({type}) => {
|
||||
const {
|
||||
nextTemplate: {id},
|
||||
} = this.state
|
||||
|
||||
const nextNextTemplate = {...DEFAULT_TEMPLATES[type](), id}
|
||||
|
||||
this.setState({nextTemplate: nextNextTemplate})
|
||||
}
|
||||
|
||||
private handleChangeName = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||
const {value} = e.target
|
||||
const {nextTemplate} = this.state
|
||||
|
||||
this.setState({
|
||||
nextTemplate: {
|
||||
...nextTemplate,
|
||||
tempVar: value,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private formatName = (): void => {
|
||||
const {nextTemplate} = this.state
|
||||
const tempVar = formatName(nextTemplate.tempVar)
|
||||
|
||||
this.setState({nextTemplate: {...nextTemplate, tempVar}})
|
||||
}
|
||||
|
||||
private handleSave = async (): Promise<any> => {
|
||||
if (!this.canSave) {
|
||||
return
|
||||
}
|
||||
|
||||
const {onUpdate, onCreate, notify} = this.props
|
||||
const {nextTemplate, isNew} = this.state
|
||||
|
||||
nextTemplate.tempVar = formatName(nextTemplate.tempVar)
|
||||
|
||||
this.setState({savingStatus: RemoteDataState.Loading})
|
||||
|
||||
try {
|
||||
if (isNew) {
|
||||
await onCreate(nextTemplate)
|
||||
} else {
|
||||
await onUpdate(nextTemplate)
|
||||
}
|
||||
} catch (error) {
|
||||
notify({
|
||||
message: `Error saving template: ${error}`,
|
||||
type: 'error',
|
||||
icon: 'alert-triangle',
|
||||
duration: FIVE_SECONDS,
|
||||
})
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
private handleNameKeyPress = (e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.handleSave()
|
||||
}
|
||||
}
|
||||
|
||||
private get isSaving(): boolean {
|
||||
return this.state.savingStatus === RemoteDataState.Loading
|
||||
}
|
||||
|
||||
private get canSave(): boolean {
|
||||
const {
|
||||
nextTemplate: {tempVar},
|
||||
} = this.state
|
||||
|
||||
return (
|
||||
tempVar !== '' &&
|
||||
!RESERVED_TEMPLATE_NAMES.includes(formatName(tempVar)) &&
|
||||
!this.isSaving
|
||||
)
|
||||
}
|
||||
|
||||
private get dropdownSelection(): string {
|
||||
const {
|
||||
nextTemplate: {type},
|
||||
} = this.state
|
||||
|
||||
return getDeep(
|
||||
TEMPLATE_TYPES_LIST.filter(t => t.type === type),
|
||||
'0.text',
|
||||
''
|
||||
)
|
||||
}
|
||||
|
||||
private handleDelete = (): void => {
|
||||
const {onDelete} = this.props
|
||||
|
||||
this.setState({deletingStatus: RemoteDataState.Loading}, onDelete)
|
||||
}
|
||||
|
||||
private get isDeleting(): boolean {
|
||||
return this.state.deletingStatus === RemoteDataState.Loading
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {notify: notifyActionCreator}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(TemplateVariableEditor)
|
|
@ -0,0 +1,173 @@
|
|||
import uuid from 'uuid'
|
||||
|
||||
import {TimeRange} from 'src/types/query'
|
||||
import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants'
|
||||
import {Template, TemplateType, TemplateValueType} from 'src/types'
|
||||
|
||||
interface TemplateTypesListItem {
|
||||
text: string
|
||||
type: TemplateType
|
||||
}
|
||||
|
||||
export const TEMPLATE_TYPES_LIST: TemplateTypesListItem[] = [
|
||||
{
|
||||
text: 'Databases',
|
||||
type: TemplateType.Databases,
|
||||
},
|
||||
{
|
||||
text: 'Measurements',
|
||||
type: TemplateType.Measurements,
|
||||
},
|
||||
{
|
||||
text: 'Field Keys',
|
||||
type: TemplateType.FieldKeys,
|
||||
},
|
||||
{
|
||||
text: 'Tag Keys',
|
||||
type: TemplateType.TagKeys,
|
||||
},
|
||||
{
|
||||
text: 'Tag Values',
|
||||
type: TemplateType.TagValues,
|
||||
},
|
||||
{
|
||||
text: 'CSV',
|
||||
type: TemplateType.CSV,
|
||||
},
|
||||
]
|
||||
|
||||
export const TEMPLATE_VARIABLE_TYPES = {
|
||||
[TemplateType.CSV]: TemplateValueType.CSV,
|
||||
[TemplateType.Databases]: TemplateValueType.Database,
|
||||
[TemplateType.Measurements]: TemplateValueType.Measurement,
|
||||
[TemplateType.FieldKeys]: TemplateValueType.FieldKey,
|
||||
[TemplateType.TagKeys]: TemplateValueType.TagKey,
|
||||
[TemplateType.TagValues]: TemplateValueType.TagValue,
|
||||
}
|
||||
|
||||
export const TEMPLATE_VARIABLE_QUERIES = {
|
||||
[TemplateType.Databases]: 'SHOW DATABASES',
|
||||
[TemplateType.Measurements]: 'SHOW MEASUREMENTS ON :database:',
|
||||
[TemplateType.FieldKeys]: 'SHOW FIELD KEYS ON :database: FROM :measurement:',
|
||||
[TemplateType.TagKeys]: 'SHOW TAG KEYS ON :database: FROM :measurement:',
|
||||
[TemplateType.TagValues]:
|
||||
'SHOW TAG VALUES ON :database: FROM :measurement: WITH KEY=:tagKey:',
|
||||
}
|
||||
|
||||
interface DefaultTemplates {
|
||||
[templateType: string]: () => Template
|
||||
}
|
||||
|
||||
export const DEFAULT_TEMPLATES: DefaultTemplates = {
|
||||
[TemplateType.Databases]: () => {
|
||||
return {
|
||||
id: uuid.v4(),
|
||||
tempVar: ':my-databases:',
|
||||
values: [
|
||||
{
|
||||
value: '_internal',
|
||||
type: TemplateValueType.Database,
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
type: TemplateType.Databases,
|
||||
label: '',
|
||||
query: {
|
||||
influxql: TEMPLATE_VARIABLE_QUERIES[TemplateType.Databases],
|
||||
},
|
||||
}
|
||||
},
|
||||
[TemplateType.Measurements]: () => {
|
||||
return {
|
||||
id: uuid.v4(),
|
||||
tempVar: ':my-measurements:',
|
||||
values: [],
|
||||
type: TemplateType.Measurements,
|
||||
label: '',
|
||||
query: {
|
||||
influxql: TEMPLATE_VARIABLE_QUERIES[TemplateType.Measurements],
|
||||
db: '',
|
||||
},
|
||||
}
|
||||
},
|
||||
[TemplateType.CSV]: () => {
|
||||
return {
|
||||
id: uuid.v4(),
|
||||
tempVar: ':my-values:',
|
||||
values: [],
|
||||
type: TemplateType.CSV,
|
||||
label: '',
|
||||
}
|
||||
},
|
||||
[TemplateType.TagKeys]: () => {
|
||||
return {
|
||||
id: uuid.v4(),
|
||||
tempVar: ':my-tag-keys:',
|
||||
values: [],
|
||||
type: TemplateType.TagKeys,
|
||||
label: '',
|
||||
query: {
|
||||
influxql: TEMPLATE_VARIABLE_QUERIES[TemplateType.TagKeys],
|
||||
},
|
||||
}
|
||||
},
|
||||
[TemplateType.FieldKeys]: () => {
|
||||
return {
|
||||
id: uuid.v4(),
|
||||
tempVar: ':my-field-keys:',
|
||||
values: [],
|
||||
type: TemplateType.FieldKeys,
|
||||
label: '',
|
||||
query: {
|
||||
influxql: TEMPLATE_VARIABLE_QUERIES[TemplateType.FieldKeys],
|
||||
},
|
||||
}
|
||||
},
|
||||
[TemplateType.TagValues]: () => {
|
||||
return {
|
||||
id: uuid.v4(),
|
||||
tempVar: ':my-tag-values:',
|
||||
values: [],
|
||||
type: TemplateType.TagValues,
|
||||
label: '',
|
||||
query: {
|
||||
influxql: TEMPLATE_VARIABLE_QUERIES[TemplateType.TagValues],
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const RESERVED_TEMPLATE_NAMES = [
|
||||
':dashboardTime:',
|
||||
':upperDashboardTime:',
|
||||
':interval:',
|
||||
':lower:',
|
||||
':upper:',
|
||||
':zoomedLower:',
|
||||
':zoomedUpper:',
|
||||
]
|
||||
|
||||
export const MATCH_INCOMPLETE_TEMPLATES = /:[\w-]*/g
|
||||
|
||||
export const applyMasks = query => {
|
||||
const matchWholeTemplates = /:([\w-]*):/g
|
||||
const maskForWholeTemplates = '😸$1😸'
|
||||
return query.replace(matchWholeTemplates, maskForWholeTemplates)
|
||||
}
|
||||
export const insertTempVar = (query, tempVar) => {
|
||||
return query.replace(MATCH_INCOMPLETE_TEMPLATES, tempVar)
|
||||
}
|
||||
export const unMask = query => {
|
||||
return query.replace(/😸/g, ':')
|
||||
}
|
||||
export const removeUnselectedTemplateValues = templates => {
|
||||
return templates.map(template => {
|
||||
const selectedValues = template.values.filter(value => value.selected)
|
||||
return {...template, values: selectedValues}
|
||||
})
|
||||
}
|
||||
|
||||
export const TEMPLATE_RANGE: TimeRange = {
|
||||
upper: null,
|
||||
lower: TEMP_VAR_DASHBOARD_TIME,
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import {QueryConfig} from 'src/types'
|
||||
import {ColorString} from 'src/types/colors'
|
||||
import {Template} from 'src/types/tempVars'
|
||||
import {Template} from 'src/types'
|
||||
|
||||
export interface Axis {
|
||||
bounds: [string, string]
|
||||
|
|
|
@ -7,6 +7,10 @@ import {
|
|||
TemplateQuery,
|
||||
TemplateValue,
|
||||
URLQueryParams,
|
||||
TemplateType,
|
||||
TemplateValueType,
|
||||
TemplateUpdate,
|
||||
TemplateBuilderProps,
|
||||
} from './tempVars'
|
||||
import {
|
||||
GroupBy,
|
||||
|
@ -99,4 +103,8 @@ export {
|
|||
RemoteDataState,
|
||||
URLQueryParams,
|
||||
AnnotationInterface,
|
||||
TemplateType,
|
||||
TemplateValueType,
|
||||
TemplateUpdate,
|
||||
TemplateBuilderProps,
|
||||
}
|
||||
|
|
|
@ -1,25 +1,48 @@
|
|||
import {Source} from 'src/types'
|
||||
|
||||
export enum TemplateValueType {
|
||||
Database = 'database',
|
||||
TagKey = 'tagKey',
|
||||
FieldKey = 'fieldKey',
|
||||
Measurement = 'measurement',
|
||||
TagValue = 'tagValue',
|
||||
CSV = 'csv',
|
||||
Points = 'points',
|
||||
Constant = 'constant',
|
||||
}
|
||||
|
||||
export interface TemplateValue {
|
||||
value: string
|
||||
type: string
|
||||
type: TemplateValueType
|
||||
selected: boolean
|
||||
}
|
||||
|
||||
export interface TemplateQuery {
|
||||
command: string
|
||||
db: string
|
||||
database?: string
|
||||
db?: string
|
||||
rp?: string
|
||||
measurement: string
|
||||
tagKey: string
|
||||
fieldKey: string
|
||||
influxql: string
|
||||
measurement?: string
|
||||
tagKey?: string
|
||||
fieldKey?: string
|
||||
influxql?: string
|
||||
}
|
||||
|
||||
export enum TemplateType {
|
||||
AutoGroupBy = 'autoGroupBy',
|
||||
Constant = 'constant',
|
||||
FieldKeys = 'fieldKeys',
|
||||
Measurements = 'measurements',
|
||||
TagKeys = 'tagKeys',
|
||||
TagValues = 'tagValues',
|
||||
CSV = 'csv',
|
||||
Query = 'query',
|
||||
Databases = 'databases',
|
||||
}
|
||||
|
||||
export interface Template {
|
||||
id: string
|
||||
tempVar: string
|
||||
values: TemplateValue[]
|
||||
type: string
|
||||
type: TemplateType
|
||||
label: string
|
||||
query?: TemplateQuery
|
||||
}
|
||||
|
@ -32,3 +55,9 @@ export interface TemplateUpdate {
|
|||
export interface URLQueryParams {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export interface TemplateBuilderProps {
|
||||
template: Template
|
||||
source: Source
|
||||
onUpdateTemplate: (nextTemplate: Template) => void
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import _ from 'lodash'
|
|||
import reducer from 'src/dashboards/reducers/ui'
|
||||
import {template, dashboard, cell} from 'test/resources'
|
||||
import {initialState} from 'src/dashboards/reducers/ui'
|
||||
import {TemplateType, TemplateValueType} from 'src/types'
|
||||
|
||||
import {
|
||||
setTimeRange,
|
||||
|
@ -21,13 +22,13 @@ let state
|
|||
const t2 = {
|
||||
...template,
|
||||
id: '2',
|
||||
type: 'csv',
|
||||
type: TemplateType.CSV,
|
||||
label: 'test csv',
|
||||
tempVar: ':temperature:',
|
||||
values: [
|
||||
{value: '98.7', type: 'measurement', selected: false},
|
||||
{value: '99.1', type: 'measurement', selected: false},
|
||||
{value: '101.3', type: 'measurement', selected: true},
|
||||
{value: '98.7', type: TemplateValueType.Measurement, selected: false},
|
||||
{value: '99.1', type: TemplateValueType.Measurement, selected: false},
|
||||
{value: '101.3', type: TemplateValueType.Measurement, selected: true},
|
||||
],
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {applyMasks, insertTempVar, unMask} from 'src/dashboards/constants'
|
||||
import {applyMasks, insertTempVar, unMask} from 'src/tempVars/constants'
|
||||
|
||||
const masquerade = query => {
|
||||
const masked = applyMasks(query)
|
||||
|
|
|
@ -7,6 +7,8 @@ import {
|
|||
TimeRange,
|
||||
Template,
|
||||
QueryConfig,
|
||||
TemplateType,
|
||||
TemplateValueType,
|
||||
} from 'src/types'
|
||||
import {
|
||||
Axes,
|
||||
|
@ -201,57 +203,57 @@ export const timeRange: TimeRange = {
|
|||
export const userDefinedTemplateVariables: Template[] = [
|
||||
{
|
||||
tempVar: ':fields:',
|
||||
type: 'fieldKeys',
|
||||
type: TemplateType.FieldKeys,
|
||||
label: '',
|
||||
values: [
|
||||
{
|
||||
selected: false,
|
||||
type: 'fieldKey',
|
||||
type: TemplateValueType.FieldKey,
|
||||
value: 'usage_guest',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
type: 'fieldKey',
|
||||
type: TemplateValueType.FieldKey,
|
||||
value: 'usage_guest_nice',
|
||||
},
|
||||
{
|
||||
selected: true,
|
||||
type: 'fieldKey',
|
||||
type: TemplateValueType.FieldKey,
|
||||
value: 'usage_idle',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
type: 'fieldKey',
|
||||
type: TemplateValueType.FieldKey,
|
||||
value: 'usage_iowait',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
type: 'fieldKey',
|
||||
type: TemplateValueType.FieldKey,
|
||||
value: 'usage_irq',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
type: 'fieldKey',
|
||||
type: TemplateValueType.FieldKey,
|
||||
value: 'usage_nice',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
type: 'fieldKey',
|
||||
type: TemplateValueType.FieldKey,
|
||||
value: 'usage_softirq',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
type: 'fieldKey',
|
||||
type: TemplateValueType.FieldKey,
|
||||
value: 'usage_steal',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
type: 'fieldKey',
|
||||
type: TemplateValueType.FieldKey,
|
||||
value: 'usage_system',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
type: 'fieldKey',
|
||||
type: TemplateValueType.FieldKey,
|
||||
value: 'usage_user',
|
||||
},
|
||||
],
|
||||
|
@ -259,42 +261,42 @@ export const userDefinedTemplateVariables: Template[] = [
|
|||
},
|
||||
{
|
||||
tempVar: ':measurements:',
|
||||
type: 'measurements',
|
||||
type: TemplateType.Measurements,
|
||||
label: '',
|
||||
values: [
|
||||
{
|
||||
selected: true,
|
||||
type: 'measurement',
|
||||
type: TemplateValueType.Measurement,
|
||||
value: 'cpu',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
type: 'measurement',
|
||||
type: TemplateValueType.Measurement,
|
||||
value: 'disk',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
type: 'measurement',
|
||||
type: TemplateValueType.Measurement,
|
||||
value: 'diskio',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
type: 'measurement',
|
||||
type: TemplateValueType.Measurement,
|
||||
value: 'mem',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
type: 'measurement',
|
||||
type: TemplateValueType.Measurement,
|
||||
value: 'processes',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
type: 'measurement',
|
||||
type: TemplateValueType.Measurement,
|
||||
value: 'swap',
|
||||
},
|
||||
{
|
||||
selected: false,
|
||||
type: 'measurement',
|
||||
type: TemplateValueType.Measurement,
|
||||
value: 'system',
|
||||
},
|
||||
],
|
||||
|
@ -305,11 +307,11 @@ export const userDefinedTemplateVariables: Template[] = [
|
|||
const dashtimeTempVar: Template = {
|
||||
id: 'dashtime',
|
||||
tempVar: ':dashboardTime:',
|
||||
type: 'constant',
|
||||
type: TemplateType.Constant,
|
||||
values: [
|
||||
{
|
||||
value: 'now() - 5m',
|
||||
type: 'constant',
|
||||
type: TemplateValueType.Constant,
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
|
@ -318,11 +320,11 @@ const dashtimeTempVar: Template = {
|
|||
const upperdashtimeTempVar: Template = {
|
||||
id: 'upperdashtime',
|
||||
tempVar: ':upperDashboardTime:',
|
||||
type: 'constant',
|
||||
type: TemplateType.Constant,
|
||||
values: [
|
||||
{
|
||||
value: 'now()',
|
||||
type: 'constant',
|
||||
type: TemplateValueType.Constant,
|
||||
selected: true,
|
||||
},
|
||||
],
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {Source, Template, Dashboard, Cell, CellType} from 'src/types'
|
||||
import {SourceLinks} from 'src/types/sources'
|
||||
import {SourceLinks, TemplateType, TemplateValueType} from 'src/types'
|
||||
|
||||
export const role = {
|
||||
name: '',
|
||||
|
@ -590,12 +590,11 @@ export const hosts = {
|
|||
// Dashboards
|
||||
export const template: Template = {
|
||||
id: '1',
|
||||
type: 'tagKeys',
|
||||
type: TemplateType.TagKeys,
|
||||
label: 'test query',
|
||||
tempVar: ':region:',
|
||||
query: {
|
||||
db: 'db1',
|
||||
command: '',
|
||||
rp: 'rp1',
|
||||
tagKey: 'tk1',
|
||||
fieldKey: 'fk1',
|
||||
|
@ -603,9 +602,9 @@ export const template: Template = {
|
|||
influxql: 'SHOW TAGS WHERE CHRONOGIRAFFE = "friend"',
|
||||
},
|
||||
values: [
|
||||
{value: 'us-west', type: 'tagKey', selected: false},
|
||||
{value: 'us-east', type: 'tagKey', selected: true},
|
||||
{value: 'us-mount', type: 'tagKey', selected: false},
|
||||
{value: 'us-west', type: TemplateValueType.TagKey, selected: false},
|
||||
{value: 'us-east', type: TemplateValueType.TagKey, selected: true},
|
||||
{value: 'us-mount', type: TemplateValueType.TagKey, selected: false},
|
||||
],
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import React from 'react'
|
||||
import {shallow} from 'enzyme'
|
||||
|
||||
import TemplateControlBar from 'src/dashboards/components/TemplateControlBar'
|
||||
import TemplateControlDropdown from 'src/dashboards/components/TemplateControlDropdown'
|
||||
import TemplateControlBar from 'src/tempVars/components/TemplateControlBar'
|
||||
import TemplateControlDropdown from 'src/tempVars/components/TemplateControlDropdown'
|
||||
import {TemplateType, TemplateValueType} from 'src/types'
|
||||
import {source} from 'test/resources'
|
||||
|
||||
const defaultProps = {
|
||||
isOpen: true,
|
||||
|
@ -11,16 +13,16 @@ const defaultProps = {
|
|||
id: '000',
|
||||
tempVar: ':alpha:',
|
||||
label: '',
|
||||
type: 'constant',
|
||||
type: TemplateType.Constant,
|
||||
values: [
|
||||
{
|
||||
value: 'firstValue',
|
||||
type: 'constant',
|
||||
type: TemplateValueType.Constant,
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
value: 'secondValue',
|
||||
type: 'constant',
|
||||
type: TemplateValueType.Constant,
|
||||
selected: false,
|
||||
},
|
||||
],
|
||||
|
@ -30,6 +32,9 @@ const defaultProps = {
|
|||
isUsingAuth: true,
|
||||
onOpenTemplateManager: () => {},
|
||||
onSelectTemplate: () => {},
|
||||
onSaveTemplates: () => {},
|
||||
onCreateTemplateVariable: () => {},
|
||||
source,
|
||||
}
|
||||
|
||||
const setup = (override = {}) => {
|
Loading…
Reference in New Issue