Skip to content

Commit

Permalink
Enable selecting variable values in time machine
Browse files Browse the repository at this point in the history
  • Loading branch information
chnn committed Mar 18, 2019
1 parent b6ff068 commit 243f1ea
Show file tree
Hide file tree
Showing 19 changed files with 461 additions and 376 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
## v2.0.0-alpha.7 [unreleased]

### Features

1. [12663](https://github.com/influxdata/influxdb/pull/12663): Insert flux function near cursor in flux editor
1. [12678](https://github.com/influxdata/influxdb/pull/12678): Enable the use of variables in the Data Explorer and Cell Editor Overlay

### Bug Fixes
1. [12684](https://github.com/influxdata/influxdb/pull/12684): Fix mismatch in bucket row and header
Expand Down
136 changes: 49 additions & 87 deletions ui/src/clockface/components/dropdowns/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Libraries
import React, {Component, CSSProperties, MouseEvent} from 'react'
import classnames from 'classnames'
import {isUndefined, isNull} from 'lodash'

// Components
import {ClickOutside} from 'src/shared/components/ClickOutside'
Expand Down Expand Up @@ -80,9 +79,6 @@ class Dropdown extends Component<Props, State> {
const {widthPixels} = this.props
const width = widthPixels ? `${widthPixels}px` : '100%'

this.validateChildCount()
this.validateMode()

return (
<ClickOutside onClickOutside={this.collapseMenu}>
<div className={this.containerClassName} style={{width}}>
Expand Down Expand Up @@ -130,23 +126,29 @@ class Dropdown extends Component<Props, State> {
buttonColor,
buttonSize,
icon,
mode,
titleText,
buttonTestID,
} = this.props

const {expanded} = this.state
const children: JSX.Element[] = this.props.children

const selectedChild = children.find(child => child.props.id === selectedID)
const isLoading = status === ComponentStatus.Loading

let resolvedStatus = status
let dropdownLabel

if (isLoading) {
dropdownLabel = <WaitingText text="Loading" />
} else if (selectedChild) {
dropdownLabel = selectedChild.props.children
} else if (mode === DropdownMode.ActionList) {
dropdownLabel = titleText
} else {
dropdownLabel = titleText
resolvedStatus = ComponentStatus.Disabled
}

return (
Expand All @@ -156,7 +158,7 @@ class Dropdown extends Component<Props, State> {
size={buttonSize}
icon={icon}
onClick={this.toggleMenu}
status={status}
status={resolvedStatus}
title={titleText}
testID={buttonTestID}
>
Expand All @@ -174,55 +176,52 @@ class Dropdown extends Component<Props, State> {
children,
testID,
} = this.props

const {expanded} = this.state

if (expanded) {
return (
<div
className={`dropdown--menu-container dropdown--${menuColor}`}
style={this.menuStyle}
>
<FancyScrollbar
autoHide={false}
autoHeight={true}
maxHeight={maxMenuHeight}
>
<div
className="dropdown--menu"
data-testid={`dropdown--menu ${testID}`}
>
{menuHeader && menuHeader}
{React.Children.map(children, (child: JSX.Element) => {
if (this.childTypeIsValid(child)) {
if (child.type === DropdownItem) {
return (
<DropdownItem
{...child.props}
key={child.props.id}
selected={child.props.id === selectedID}
onClick={this.handleItemClick}
>
{child.props.children}
</DropdownItem>
)
}

return (
<DropdownDivider {...child.props} key={child.props.id} />
)
} else {
throw new Error(
'Expected children of type <Dropdown.Item /> or <Dropdown.Divider />'
)
}
})}
</div>
</FancyScrollbar>
</div>
)
if (!expanded) {
return null
}

return null
return (
<div
className={`dropdown--menu-container dropdown--${menuColor}`}
style={this.menuStyle}
>
<FancyScrollbar
autoHide={false}
autoHeight={true}
maxHeight={maxMenuHeight}
>
<div
className="dropdown--menu"
data-testid={`dropdown--menu ${testID}`}
>
{menuHeader && menuHeader}
{React.Children.map(children, (child: JSX.Element) => {
if (child.type === DropdownItem) {
return (
<DropdownItem
{...child.props}
key={child.props.id}
selected={child.props.id === selectedID}
onClick={this.handleItemClick}
>
{child.props.children}
</DropdownItem>
)
} else if (child.type === DropdownDivider) {
return <DropdownDivider {...child.props} key={child.props.id} />
} else {
throw new Error(
'Expected children of type <Dropdown.Item /> or <Dropdown.Divider />'
)
}
})}
</div>
</FancyScrollbar>
</div>
)
}

private get menuStyle(): CSSProperties {
Expand All @@ -245,48 +244,11 @@ class Dropdown extends Component<Props, State> {
}
}

private get shouldHaveChildren(): boolean {
const {status} = this.props

return (
status === ComponentStatus.Default || status === ComponentStatus.Valid
)
}

private handleItemClick = (value: any): void => {
const {onChange} = this.props
onChange(value)
this.collapseMenu()
}

private validateChildCount = (): void => {
const {children} = this.props

if (this.shouldHaveChildren && React.Children.count(children) === 0) {
throw new Error(
'Dropdowns require at least 1 child element. We recommend using Dropdown.Item and/or Dropdown.Divider.'
)
}
}

private validateMode = (): void => {
const {mode, selectedID, titleText} = this.props

if (mode === DropdownMode.ActionList && titleText === '') {
throw new Error('Dropdowns in ActionList mode require a titleText prop.')
}

if (
mode === DropdownMode.Radio &&
this.shouldHaveChildren &&
(isUndefined(selectedID) || isNull(selectedID))
) {
throw new Error('Dropdowns in Radio mode require a selectedID prop.')
}
}

private childTypeIsValid = (child: JSX.Element): boolean =>
child.type === DropdownItem || child.type === DropdownDivider
}

export default Dropdown
4 changes: 4 additions & 0 deletions ui/src/clockface/components/dropdowns/DropdownButton.scss
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
}
}

.dropdown--button.button-disabled {
font-style: italic;
}

.dropdown--button.button-xs {
@include buttonSizing($form-xs-padding, $form-xs-font);
}
Expand Down
20 changes: 0 additions & 20 deletions ui/src/clockface/components/dropdowns/test/Dropdown.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,24 +79,4 @@ describe('Dropdown', () => {
expect(actualProps).toEqual(expectedProps)
})
})

describe('when no children are present', () => {
const errorLog = console.error

beforeEach(() => {
console.error = jest.fn(() => {})
})

afterEach(() => {
console.error = errorLog
})

it('throws error', () => {
expect(() => {
wrapperSetup({children: null})
}).toThrow(
'Dropdowns require at least 1 child element. We recommend using Dropdown.Item and/or Dropdown.Divider.'
)
})
})
})
8 changes: 5 additions & 3 deletions ui/src/dataExplorer/components/DataExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {connect} from 'react-redux'

// Components
import TimeMachine from 'src/timeMachine/components/TimeMachine'
import GetVariables from 'src/shared/components/GetVariables'
import GetResources, {
ResourceTypes,
} from 'src/configuration/components/GetResources'

// Actions
import {setActiveTimeMachine} from 'src/timeMachine/actions'
Expand Down Expand Up @@ -33,9 +35,9 @@ class DataExplorer extends PureComponent<DispatchProps, {}> {
return (
<div className="data-explorer">
<HoverTimeProvider>
<GetVariables>
<GetResources resource={ResourceTypes.Variables}>
<TimeMachine />
</GetVariables>
</GetResources>
</HoverTimeProvider>
</div>
)
Expand Down
25 changes: 25 additions & 0 deletions ui/src/shared/components/BoxTooltip.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@import 'src/style/modules';

$box-tooltip--caret-size: 6px;

.box-tooltip {
position: fixed;
visibility: hidden;
z-index: $z--dygraph-legend;
border: $ix-border solid $c-pool;
background-color: $g1-raven;
border-radius: $radius;
padding: 10px;
font-size: 13px;
}

.box-tooltip.left .box-tooltip--caret {
position: absolute;
right: -$box-tooltip--caret-size;
transform: translate(0,-50%);
width: 0;
height: 0;
border-top: $box-tooltip--caret-size solid transparent;
border-bottom: $box-tooltip--caret-size solid transparent;
border-left: $box-tooltip--caret-size solid $c-pool;
}
72 changes: 72 additions & 0 deletions ui/src/shared/components/BoxTooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Libraries
import React, {useRef, useLayoutEffect, FunctionComponent} from 'react'
import {createPortal} from 'react-dom'

// Constants
import {TOOLTIP_PORTAL_ID} from 'src/shared/components/TooltipPortal'

// Styles
import 'src/shared/components/BoxTooltip.scss'

interface Props {
triggerRect: DOMRect
children: JSX.Element
}

const BoxTooltip: FunctionComponent<Props> = ({triggerRect, children}) => {
const ref = useRef<HTMLDivElement>(null)

// Position the tooltip after it has rendered, taking into account its size
useLayoutEffect(() => {
const el = ref.current

if (!el || !triggerRect) {
return
}

const rect = el.getBoundingClientRect()

// Always position the tooltip to the left of the trigger position
const left = triggerRect.left - rect.width

// Attempt to position the vertical midpoint of tooltip next to the
// vertical midpoint of the trigger rectangle
let top = triggerRect.top + triggerRect.height / 2 - rect.height / 2

// If the tooltip overflows the top of the screen, align the top of
// the tooltip with the top of the screen
if (top < 0) {
top = 0
}

// If the tooltip overflows the bottom of the screen, align the bottom of
// the tooltip with the bottom of the screen
if (top + rect.height > window.innerHeight) {
top = window.innerHeight - rect.height
}

el.setAttribute(
'style',
`visibility: visible; top: ${top}px; left: ${left}px;`
)

// Position the caret (little arrow on the side of the tooltip) so that it
// points to the vertical midpoint of the trigger rectangle
const caretTop = triggerRect.top + triggerRect.height / 2 - top

el.querySelector('.box-tooltip--caret').setAttribute(
'style',
`top: ${caretTop}px;`
)
})

return createPortal(
<div className="box-tooltip left" ref={ref}>
{children}
<div className="box-tooltip--caret" />
</div>,
document.querySelector(`#${TOOLTIP_PORTAL_ID}`)
)
}

export default BoxTooltip

0 comments on commit 243f1ea

Please sign in to comment.