feat(ui): add name to dashboard query (#1794)
parent
d64dbbc034
commit
27adc0ff91
|
@ -407,6 +407,9 @@ components:
|
|||
description: Optional URI for data source for this query
|
||||
queryConfig:
|
||||
$ref: '#/components/schemas/QueryConfig'
|
||||
name:
|
||||
type: string
|
||||
description: An optional word or phrase that refers to the query
|
||||
Axis:
|
||||
type: object
|
||||
description: A description of a particular axis for a visualization
|
||||
|
|
|
@ -4132,6 +4132,9 @@ components:
|
|||
description: Optional URI for data source for this query
|
||||
queryConfig:
|
||||
$ref: '#/components/schemas/QueryConfig'
|
||||
name:
|
||||
type: string
|
||||
description: An optional word or phrase that refers to the query
|
||||
Axis:
|
||||
type: object
|
||||
description: A description of a particular axis for a visualization
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React, {SFC, ReactChildren} from 'react'
|
||||
|
||||
import RightClickLayer from 'src/clockface/components/right_click_menu/RightClickLayer'
|
||||
import Nav from 'src/pageLayout'
|
||||
import Notifications from 'src/shared/components/notifications/Notifications'
|
||||
|
||||
|
@ -10,6 +11,7 @@ interface Props {
|
|||
const App: SFC<Props> = ({children}) => (
|
||||
<div className="chronograf-root">
|
||||
<Notifications />
|
||||
<RightClickLayer />
|
||||
<Nav />
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
@ -668,6 +668,12 @@ export interface DashboardQuery {
|
|||
* @memberof DashboardQuery
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* An optional word or phrase that refers to the query
|
||||
* @type {string}
|
||||
* @memberof DashboardQuery
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import React, {SFC} from 'react'
|
||||
|
||||
interface WrapperProps<T> {
|
||||
type: JSX.Element['type']
|
||||
children: T[]
|
||||
}
|
||||
|
||||
interface Options {
|
||||
count?: number
|
||||
strict?: boolean
|
||||
}
|
||||
|
||||
type WrapSelection<T> = SFC<WrapperProps<T> & Options>
|
||||
|
||||
const Select: WrapSelection<JSX.Element> = ({
|
||||
type,
|
||||
children,
|
||||
strict = false,
|
||||
count = Infinity,
|
||||
}): JSX.Element => (
|
||||
<>
|
||||
{React.Children.map(children, (child: JSX.Element) => {
|
||||
if (child.type === type && count-- > 0) {
|
||||
return child
|
||||
} else if (strict && child.type !== type) {
|
||||
throw new Error(`Expected ${type} but received ${child.type}`)
|
||||
}
|
||||
})}
|
||||
</>
|
||||
)
|
||||
|
||||
export default Select
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
Right Click Menu Styles
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
@import 'src/style/modules';
|
||||
|
||||
.right-click--layer {
|
||||
position: relative;
|
||||
z-index: $z--right-click-layer;
|
||||
}
|
||||
|
||||
.right-click--menu {
|
||||
border-radius: $radius;
|
||||
@include gradient-h($g0-obsidian, $g2-kevlar);
|
||||
}
|
||||
|
||||
.right-click--menu .fancy-scroll--track-h {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.right-click--list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.right-click--menu-item {
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
line-height: 13px;
|
||||
font-weight: 600;
|
||||
transition: color 0.25s ease, background-color 0.25s ease;
|
||||
color: $g13-mist;
|
||||
padding: 5px 11px;
|
||||
|
||||
&:hover {
|
||||
color: $g18-cloud;
|
||||
background-color: $g5-pepper;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.disabled,
|
||||
&.disabled:hover {
|
||||
background-color: transparent;
|
||||
cursor: default !important;
|
||||
color: $g9-mountain;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: $radius;
|
||||
border-top-right-radius: $radius;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom-left-radius: $radius;
|
||||
border-bottom-right-radius: $radius;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
// Libraries
|
||||
import React, {SFC, Component} from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
// Components
|
||||
import RightClickMenu from 'src/clockface/components/right_click_menu/RightClickMenu'
|
||||
import RightClickMenuItem from 'src/clockface/components/right_click_menu/RightClickMenuItem'
|
||||
import {ClickOutside} from 'src/shared/components/ClickOutside'
|
||||
import Select from 'src/clockface/components/Select'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
children: JSX.Element[]
|
||||
}
|
||||
|
||||
interface ChildProps {
|
||||
children: JSX.Element
|
||||
}
|
||||
interface State {
|
||||
expanded: boolean
|
||||
mouseX: number
|
||||
mouseY: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles bubbling onContextMenu events from elements
|
||||
* wrapped in Trigger and displays the MenuContainer
|
||||
* contents. Portals the RightClickMenu into the
|
||||
* RightClickLayer.
|
||||
*
|
||||
* @example Using RightClickMenu and RightClickMenuItem
|
||||
*
|
||||
* <RightClick>
|
||||
* <RightClick.Trigger>
|
||||
* <button>Right click me</button>
|
||||
* </RightClick.Trigger>
|
||||
* <RightClick.MenuContainer>
|
||||
* <RightClick.Menu>
|
||||
* <RightClick.MenuItem onClick={this.handleClickA}>
|
||||
* Test Item A
|
||||
* </RightClick.MenuItem>
|
||||
* <RightClick.MenuItem onClick={this.handleClickB}>
|
||||
* Test Item B
|
||||
* </RightClick.MenuItem>
|
||||
* <RightClick.MenuItem
|
||||
* onClick={this.handleClickC}
|
||||
* disabled={true}
|
||||
* >
|
||||
* Test Item B
|
||||
* </RightClick.MenuItem>
|
||||
* </RightClick.Menu>
|
||||
* </RightClick.MenuContainer>
|
||||
* </RightClick>
|
||||
*
|
||||
* @example Using a custom menu
|
||||
*
|
||||
* <RightClick>
|
||||
* <RightClick.Trigger>
|
||||
* <button>Right click me</button>
|
||||
* </RightClick.Trigger>
|
||||
* <RightClick.MenuContainer>
|
||||
* <ul className="custom-menu">
|
||||
* <li onClick={this.handleClickA}>Test A</li>
|
||||
* <li onClick={this.handleClickB}>Test B</li>
|
||||
* </ul>
|
||||
* </RightClick.MenuContainer>
|
||||
* </RightClick>
|
||||
*/
|
||||
|
||||
class RightClick extends Component<Props, State> {
|
||||
public static Menu = RightClickMenu
|
||||
public static MenuItem = RightClickMenuItem
|
||||
public static MenuContainer: SFC<ChildProps> = ({children}) => children
|
||||
public static Trigger: SFC<ChildProps> = ({children}) => children
|
||||
|
||||
private static Only: typeof Select = props => <Select count={1} {...props} />
|
||||
|
||||
public state: State = {
|
||||
expanded: false,
|
||||
mouseX: null,
|
||||
mouseY: null,
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
document.addEventListener('contextmenu', this.handleRightClick, true)
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
document.removeEventListener('contextmenu', this.handleRightClick, true)
|
||||
}
|
||||
|
||||
public render() {
|
||||
this.validateChildren()
|
||||
|
||||
return (
|
||||
<>
|
||||
<RightClick.Only type={RightClick.Trigger}>
|
||||
{this.props.children}
|
||||
</RightClick.Only>
|
||||
<div className="right-click--wrapper">{this.menu}</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
private handleRightClick = (e: MouseEvent): void => {
|
||||
const domNode = ReactDOM.findDOMNode(this)
|
||||
|
||||
if (!domNode || domNode.contains(e.target)) {
|
||||
e.preventDefault()
|
||||
|
||||
const {pageX: mouseX, pageY: mouseY} = e
|
||||
|
||||
this.setState({
|
||||
expanded: true,
|
||||
mouseX,
|
||||
mouseY,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private handleCollapseMenu = (): void => {
|
||||
this.setState({expanded: false})
|
||||
}
|
||||
|
||||
private get menu() {
|
||||
return ReactDOM.createPortal(
|
||||
this.menuElement,
|
||||
document.getElementById('right-click--layer')
|
||||
)
|
||||
}
|
||||
|
||||
private get menuElement(): JSX.Element {
|
||||
const {expanded, mouseX, mouseY} = this.state
|
||||
|
||||
if (!expanded) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'fixed',
|
||||
left: mouseX,
|
||||
top: mouseY,
|
||||
}}
|
||||
onClick={this.handleCollapseMenu}
|
||||
>
|
||||
<ClickOutside onClickOutside={this.handleCollapseMenu}>
|
||||
<RightClick.Only type={RightClick.MenuContainer}>
|
||||
{this.props.children}
|
||||
</RightClick.Only>
|
||||
</ClickOutside>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private validateChildren = (): void => {
|
||||
const {children} = this.props
|
||||
|
||||
if (React.Children.count(children) > 2) {
|
||||
throw new Error('<RightClick> has more than 2 children')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default RightClick
|
|
@ -0,0 +1,11 @@
|
|||
// Libraries
|
||||
import React, {SFC} from 'react'
|
||||
|
||||
// Styles
|
||||
import './RightClick.scss'
|
||||
|
||||
const RightClickLayer: SFC = () => (
|
||||
<div id="right-click--layer" className="right-click--layer" />
|
||||
)
|
||||
|
||||
export default RightClickLayer
|
|
@ -0,0 +1,27 @@
|
|||
// Libraries
|
||||
import React, {Component, MouseEvent} from 'react'
|
||||
|
||||
// Components
|
||||
import FancyScrollbar from 'src/shared/components/fancy_scrollbar/FancyScrollbar'
|
||||
|
||||
interface Props {
|
||||
children: JSX.Element[] | JSX.Element
|
||||
}
|
||||
|
||||
class RightClickMenu extends Component<Props> {
|
||||
public render() {
|
||||
return (
|
||||
<div className="right-click--menu" onContextMenu={this.handleRightClick}>
|
||||
<FancyScrollbar autoHeight={true} maxHeight={250}>
|
||||
<ul className="right-click--list">{this.props.children}</ul>
|
||||
</FancyScrollbar>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private handleRightClick = (e: MouseEvent<HTMLDivElement>) => {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
export default RightClickMenu
|
|
@ -0,0 +1,40 @@
|
|||
// Libraries
|
||||
import React, {Component, MouseEvent} from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
interface Props {
|
||||
disabled?: boolean
|
||||
onClick: (e?: MouseEvent<HTMLLIElement>) => void
|
||||
children: JSX.Element[] | JSX.Element | string
|
||||
}
|
||||
|
||||
class RightClickMenuItem extends Component<Props> {
|
||||
public render() {
|
||||
const {children} = this.props
|
||||
|
||||
return (
|
||||
<li className={this.className} onClick={this.handleClick}>
|
||||
{children}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
private handleClick = (e: MouseEvent<HTMLLIElement>) => {
|
||||
const {onClick, disabled} = this.props
|
||||
|
||||
if (disabled) {
|
||||
e.stopPropagation()
|
||||
return
|
||||
}
|
||||
|
||||
onClick(e)
|
||||
}
|
||||
|
||||
private get className(): string {
|
||||
const {disabled} = this.props
|
||||
|
||||
return classnames('right-click--menu-item', {disabled})
|
||||
}
|
||||
}
|
||||
|
||||
export default RightClickMenuItem
|
|
@ -37,6 +37,7 @@ export type Action =
|
|||
| EditActiveQueryAsInfluxQLAction
|
||||
| EditActiveQueryWithBuilderAction
|
||||
| BuildQueryAction
|
||||
| UpdateActiveQueryNameAction
|
||||
|
||||
interface SetActiveTimeMachineAction {
|
||||
type: 'SET_ACTIVE_TIME_MACHINE'
|
||||
|
@ -349,3 +350,15 @@ export const buildQuery = (builderConfig: BuilderConfig): BuildQueryAction => ({
|
|||
type: 'BUILD_QUERY',
|
||||
payload: {builderConfig},
|
||||
})
|
||||
|
||||
interface UpdateActiveQueryNameAction {
|
||||
type: 'UPDATE_ACTIVE_QUERY_NAME'
|
||||
payload: {queryName: string}
|
||||
}
|
||||
|
||||
export const updateActiveQueryName = (
|
||||
queryName: string
|
||||
): UpdateActiveQueryNameAction => ({
|
||||
type: 'UPDATE_ACTIVE_QUERY_NAME',
|
||||
payload: {queryName},
|
||||
})
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Libraries
|
||||
import React, {SFC} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import {range} from 'lodash'
|
||||
|
||||
// Components
|
||||
import TimeMachineFluxEditor from 'src/shared/components/TimeMachineFluxEditor'
|
||||
|
@ -41,7 +40,7 @@ import {RemoteDataState} from 'src/types'
|
|||
|
||||
interface StateProps {
|
||||
activeQuery: DashboardQuery
|
||||
queryCount: number
|
||||
draftQueries: DashboardQuery[]
|
||||
}
|
||||
|
||||
interface DispatchProps {
|
||||
|
@ -55,7 +54,7 @@ interface OwnProps {
|
|||
type Props = StateProps & DispatchProps & OwnProps
|
||||
|
||||
const TimeMachineQueries: SFC<Props> = props => {
|
||||
const {activeQuery, queryStatus, queryCount, onAddQuery} = props
|
||||
const {activeQuery, queryStatus, draftQueries, onAddQuery} = props
|
||||
|
||||
let queryEditor
|
||||
|
||||
|
@ -71,8 +70,12 @@ const TimeMachineQueries: SFC<Props> = props => {
|
|||
<div className="time-machine-queries">
|
||||
<div className="time-machine-queries--controls">
|
||||
<div className="time-machine-queries--tabs">
|
||||
{range(queryCount).map(i => (
|
||||
<TimeMachineQueryTab key={i} queryIndex={i} />
|
||||
{draftQueries.map((query, queryIndex) => (
|
||||
<TimeMachineQueryTab
|
||||
key={queryIndex}
|
||||
queryIndex={queryIndex}
|
||||
query={query}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
customClass="time-machine-queries--new"
|
||||
|
@ -95,10 +98,9 @@ const TimeMachineQueries: SFC<Props> = props => {
|
|||
|
||||
const mstp = (state: AppState) => {
|
||||
const {draftQueries, activeQueryIndex} = getActiveTimeMachine(state)
|
||||
const queryCount = draftQueries.length
|
||||
const activeQuery = getActiveQuery(state)
|
||||
|
||||
return {activeQuery, activeQueryIndex, queryCount}
|
||||
return {activeQuery, activeQueryIndex, draftQueries}
|
||||
}
|
||||
|
||||
const mdtp = {
|
||||
|
|
|
@ -2,10 +2,15 @@
|
|||
import React, {PureComponent, MouseEvent} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
|
||||
// Components
|
||||
import TimeMachineQueryTabName from 'src/shared/components/TimeMachineQueryTabName'
|
||||
import RightClick from 'src/clockface/components/right_click_menu/RightClick'
|
||||
|
||||
// Actions
|
||||
import {
|
||||
setActiveQueryIndex,
|
||||
removeQuery,
|
||||
updateActiveQueryName,
|
||||
} from 'src/shared/actions/v2/timeMachines'
|
||||
|
||||
// Utils
|
||||
|
@ -15,7 +20,7 @@ import {getActiveTimeMachine} from 'src/shared/selectors/timeMachines'
|
|||
import 'src/shared/components/TimeMachineQueryTab.scss'
|
||||
|
||||
// Types
|
||||
import {AppState} from 'src/types/v2'
|
||||
import {AppState, DashboardQuery} from 'src/types/v2'
|
||||
|
||||
interface StateProps {
|
||||
activeQueryIndex: number
|
||||
|
@ -24,42 +29,107 @@ interface StateProps {
|
|||
interface DispatchProps {
|
||||
onSetActiveQueryIndex: typeof setActiveQueryIndex
|
||||
onRemoveQuery: typeof removeQuery
|
||||
onUpdateActiveQueryName: typeof updateActiveQueryName
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
queryIndex: number
|
||||
query: DashboardQuery
|
||||
}
|
||||
|
||||
type Props = StateProps & DispatchProps & OwnProps
|
||||
|
||||
class TimeMachineQueryTab extends PureComponent<Props> {
|
||||
interface State {
|
||||
isEditingName: boolean
|
||||
}
|
||||
class TimeMachineQueryTab extends PureComponent<Props, State> {
|
||||
public static getDerivedStateFromProps(props: Props): Partial<State> {
|
||||
if (props.queryIndex !== props.activeQueryIndex) {
|
||||
return {isEditingName: false}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
public state: State = {isEditingName: false}
|
||||
|
||||
public render() {
|
||||
const {queryIndex, activeQueryIndex} = this.props
|
||||
const {queryIndex, activeQueryIndex, query} = this.props
|
||||
const isActive = queryIndex === activeQueryIndex
|
||||
const activeClass = queryIndex === activeQueryIndex ? 'active' : ''
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`time-machine-query-tab ${activeClass}`}
|
||||
onClick={this.handleSetActive}
|
||||
>
|
||||
Query {queryIndex + 1}
|
||||
<div
|
||||
className="time-machine-query-tab--close"
|
||||
onClick={this.handleRemove}
|
||||
>
|
||||
<span className="icon remove" />
|
||||
</div>
|
||||
</div>
|
||||
<RightClick>
|
||||
<RightClick.Trigger>
|
||||
<div
|
||||
className={`time-machine-query-tab ${activeClass}`}
|
||||
onClick={this.handleSetActive}
|
||||
>
|
||||
<TimeMachineQueryTabName
|
||||
isActive={isActive}
|
||||
name={query.name}
|
||||
queryIndex={queryIndex}
|
||||
isEditing={this.state.isEditingName}
|
||||
onUpdate={this.handleUpdateName}
|
||||
onEdit={this.handleEditName}
|
||||
onCancelEdit={this.handleCancelEditName}
|
||||
/>
|
||||
{this.removeButton}
|
||||
</div>
|
||||
</RightClick.Trigger>
|
||||
<RightClick.MenuContainer>
|
||||
<RightClick.Menu>
|
||||
<RightClick.MenuItem onClick={this.handleEditActiveQueryName}>
|
||||
Edit
|
||||
</RightClick.MenuItem>
|
||||
<RightClick.MenuItem onClick={this.handleRemove}>
|
||||
Remove
|
||||
</RightClick.MenuItem>
|
||||
</RightClick.Menu>
|
||||
</RightClick.MenuContainer>
|
||||
</RightClick>
|
||||
)
|
||||
}
|
||||
|
||||
private handleEditActiveQueryName = () => {
|
||||
this.handleSetActive()
|
||||
this.handleEditName()
|
||||
}
|
||||
|
||||
private handleUpdateName = (queryName: string) => {
|
||||
this.props.onUpdateActiveQueryName(queryName)
|
||||
}
|
||||
|
||||
private handleSetActive = (): void => {
|
||||
const {queryIndex, onSetActiveQueryIndex} = this.props
|
||||
|
||||
onSetActiveQueryIndex(queryIndex)
|
||||
}
|
||||
|
||||
private handleRemove = (e: MouseEvent<HTMLDivElement>): void => {
|
||||
private handleCancelEditName = () => {
|
||||
this.setState({isEditingName: false})
|
||||
}
|
||||
|
||||
private handleEditName = (): void => {
|
||||
this.setState({isEditingName: true})
|
||||
}
|
||||
|
||||
private get removeButton(): JSX.Element {
|
||||
if (this.state.isEditingName) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="time-machine-query-tab--close"
|
||||
onClick={this.handleRemove}
|
||||
>
|
||||
<span className="icon remove" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private handleRemove = (e: MouseEvent): void => {
|
||||
const {queryIndex, onRemoveQuery} = this.props
|
||||
|
||||
e.stopPropagation()
|
||||
|
@ -69,12 +139,14 @@ class TimeMachineQueryTab extends PureComponent<Props> {
|
|||
|
||||
const mstp = (state: AppState) => {
|
||||
const {activeQueryIndex} = getActiveTimeMachine(state)
|
||||
|
||||
return {activeQueryIndex}
|
||||
}
|
||||
|
||||
const mdtp = {
|
||||
onSetActiveQueryIndex: setActiveQueryIndex,
|
||||
onRemoveQuery: removeQuery,
|
||||
onUpdateActiveQueryName: updateActiveQueryName,
|
||||
}
|
||||
|
||||
export default connect<StateProps, DispatchProps, OwnProps>(
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
// Libraries
|
||||
import React, {PureComponent, KeyboardEvent, ChangeEvent} from 'react'
|
||||
|
||||
// Components
|
||||
import {Input, InputType} from 'src/clockface'
|
||||
|
||||
// Styles
|
||||
import 'src/shared/components/TimeMachineQueryTab.scss'
|
||||
|
||||
interface Props {
|
||||
isActive: boolean
|
||||
isEditing: boolean
|
||||
queryIndex: number
|
||||
name?: string
|
||||
onEdit: () => void
|
||||
onCancelEdit: () => void
|
||||
onUpdate: (newName: string) => void
|
||||
}
|
||||
|
||||
interface State {
|
||||
newName?: string
|
||||
}
|
||||
|
||||
class TimeMachineQueryTabName extends PureComponent<Props, State> {
|
||||
public state: State = {newName: null}
|
||||
|
||||
public render() {
|
||||
const {queryIndex, name, isEditing, onCancelEdit} = this.props
|
||||
const queryName = !!name ? name : `Query ${queryIndex + 1}`
|
||||
|
||||
if (isEditing) {
|
||||
return (
|
||||
<Input
|
||||
type={InputType.Text}
|
||||
placeholder="Name your query"
|
||||
value={this.state.newName || ''}
|
||||
onChange={this.handleChange}
|
||||
onBlur={onCancelEdit}
|
||||
onKeyUp={this.handleEnterKey}
|
||||
autoFocus={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
className="time-machine-query-tab-name"
|
||||
onDoubleClick={this.handleDoubleClick}
|
||||
>
|
||||
{queryName}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
private handleDoubleClick = () => {
|
||||
if (this.props.isActive) {
|
||||
this.props.onEdit()
|
||||
|
||||
this.setState({newName: this.props.name || ''})
|
||||
}
|
||||
}
|
||||
|
||||
private handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({newName: e.target.value})
|
||||
}
|
||||
|
||||
private handleEnterKey = (e: KeyboardEvent<HTMLInputElement>) => {
|
||||
switch (e.key) {
|
||||
case 'Enter':
|
||||
return this.handleUpdate()
|
||||
case 'Escape':
|
||||
return this.props.onCancelEdit()
|
||||
}
|
||||
}
|
||||
|
||||
private handleUpdate() {
|
||||
const {onUpdate, onCancelEdit} = this.props
|
||||
const {newName} = this.state
|
||||
|
||||
if (newName !== null) {
|
||||
onUpdate(newName)
|
||||
onCancelEdit()
|
||||
}
|
||||
|
||||
this.setState({newName: null})
|
||||
}
|
||||
}
|
||||
|
||||
export default TimeMachineQueryTabName
|
|
@ -18,6 +18,7 @@ import {
|
|||
editActiveQueryAsInfluxQL,
|
||||
addQuery,
|
||||
removeQuery,
|
||||
updateActiveQueryName,
|
||||
} from 'src/shared/actions/v2/timeMachines'
|
||||
|
||||
// Utils
|
||||
|
@ -597,4 +598,58 @@ describe('timeMachineReducer', () => {
|
|||
expect(nextState.activeQueryIndex).toEqual(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('UPDATE_ACTIVE_QUERY_NAME', () => {
|
||||
test('sets the name for the activeQueryIndex', () => {
|
||||
const state = initialStateHelper()
|
||||
state.activeQueryIndex = 1
|
||||
|
||||
const builderConfig = {
|
||||
buckets: [],
|
||||
measurements: [],
|
||||
fields: [],
|
||||
functions: [],
|
||||
}
|
||||
|
||||
state.draftQueries = [
|
||||
{
|
||||
text: 'foo',
|
||||
type: InfluxLanguage.Flux,
|
||||
sourceID: '',
|
||||
editMode: QueryEditMode.Advanced,
|
||||
builderConfig,
|
||||
},
|
||||
{
|
||||
text: 'bar',
|
||||
type: InfluxLanguage.Flux,
|
||||
sourceID: '',
|
||||
editMode: QueryEditMode.Builder,
|
||||
builderConfig,
|
||||
},
|
||||
]
|
||||
|
||||
const nextState = timeMachineReducer(
|
||||
state,
|
||||
updateActiveQueryName('test query')
|
||||
)
|
||||
|
||||
expect(nextState.draftQueries).toEqual([
|
||||
{
|
||||
text: 'foo',
|
||||
type: InfluxLanguage.Flux,
|
||||
sourceID: '',
|
||||
editMode: QueryEditMode.Advanced,
|
||||
builderConfig,
|
||||
},
|
||||
{
|
||||
text: 'bar',
|
||||
type: InfluxLanguage.Flux,
|
||||
sourceID: '',
|
||||
editMode: QueryEditMode.Builder,
|
||||
name: 'test query',
|
||||
builderConfig,
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -16,17 +16,17 @@ import {TimeRange} from 'src/types/v2'
|
|||
import {
|
||||
View,
|
||||
ViewType,
|
||||
NewView,
|
||||
QueryViewProperties,
|
||||
DashboardQuery,
|
||||
InfluxLanguage,
|
||||
QueryEditMode,
|
||||
QueryView,
|
||||
QueryViewProperties,
|
||||
} from 'src/types/v2/dashboards'
|
||||
import {Action} from 'src/shared/actions/v2/timeMachines'
|
||||
import {TimeMachineTab} from 'src/types/v2/timeMachine'
|
||||
|
||||
export interface TimeMachineState {
|
||||
view: View<QueryViewProperties> | NewView<QueryViewProperties>
|
||||
view: QueryView
|
||||
timeRange: TimeRange
|
||||
draftQueries: DashboardQuery[]
|
||||
isViewingRawData: boolean
|
||||
|
@ -404,6 +404,18 @@ export const timeMachineReducer = (
|
|||
builderConfig,
|
||||
}
|
||||
|
||||
return {...state, draftQueries}
|
||||
}
|
||||
case 'UPDATE_ACTIVE_QUERY_NAME': {
|
||||
const {activeQueryIndex} = state
|
||||
const {queryName} = action.payload
|
||||
const draftQueries = [...state.draftQueries]
|
||||
|
||||
draftQueries[activeQueryIndex] = {
|
||||
...draftQueries[activeQueryIndex],
|
||||
name: queryName,
|
||||
}
|
||||
|
||||
return {...state, draftQueries}
|
||||
}
|
||||
}
|
||||
|
@ -437,7 +449,7 @@ const setYAxis = (state: TimeMachineState, update: {[key: string]: any}) => {
|
|||
}
|
||||
|
||||
const convertView = (
|
||||
view: View<QueryViewProperties> | NewView<QueryViewProperties>,
|
||||
view: QueryView,
|
||||
outType: ViewType
|
||||
): View<QueryViewProperties> => {
|
||||
const newView: any = createView(outType)
|
||||
|
|
|
@ -17,6 +17,7 @@ $sidebar--width: 50px; //delete this later
|
|||
/* Z Variables */
|
||||
|
||||
$z--notifications: 9999;
|
||||
$z--right-click-layer: 9995;
|
||||
$z--overlays: 9990;
|
||||
$z--drag-n-drop: 5000;
|
||||
$z--dygraph-legend: 4000;
|
||||
|
|
|
@ -59,6 +59,7 @@ export interface DashboardQuery {
|
|||
editMode: QueryEditMode
|
||||
builderConfig: BuilderConfig
|
||||
sourceID: string // Which source to use when running the query; may be empty, which means “use the dynamic source”
|
||||
name?: string
|
||||
}
|
||||
|
||||
export interface URLQuery {
|
||||
|
@ -100,19 +101,23 @@ export interface ViewLinks {
|
|||
|
||||
export type DygraphViewProperties = XYView | LinePlusSingleStatView
|
||||
|
||||
export type QueryViewProperties =
|
||||
export type ViewProperties =
|
||||
| XYView
|
||||
| LinePlusSingleStatView
|
||||
| SingleStatView
|
||||
| TableView
|
||||
| GaugeView
|
||||
|
||||
export type ViewProperties =
|
||||
| QueryViewProperties
|
||||
| MarkdownView
|
||||
| EmptyView
|
||||
| LogViewerView
|
||||
|
||||
export type QueryViewProperties = Extract<
|
||||
ViewProperties,
|
||||
{queries: DashboardQuery[]}
|
||||
>
|
||||
|
||||
export type QueryView = View<QueryViewProperties> | NewView<QueryViewProperties>
|
||||
|
||||
export interface EmptyView {
|
||||
type: ViewShape.Empty
|
||||
shape: ViewShape.Empty
|
||||
|
|
1
view.go
1
view.go
|
@ -411,6 +411,7 @@ type DashboardQuery struct {
|
|||
Type string `json:"type"`
|
||||
SourceID string `json:"sourceID"`
|
||||
EditMode string `json:"editMode"` // Either "builder" or "advanced"
|
||||
Name string `json:"name"` // Term or phrase that refers to the query
|
||||
BuilderConfig BuilderConfig `json:"builderConfig"`
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue