Merge remote-tracking branch 'origin/master' into fun/dashboard_page

pull/3739/head
Jared Scheib 2018-06-22 17:53:44 -07:00
commit f43f504bc1
22 changed files with 243 additions and 74 deletions

View File

@ -23,6 +23,7 @@
1. [#3697](https://github.com/influxdata/chronograf/pull/3697): Fix allowing hyphens in basepath
1. [#3698](https://github.com/influxdata/chronograf/pull/3698): Fix error in cell when tempVar returns no values
1. [#3733](https://github.com/influxdata/chronograf/pull/3733): Change arrows in table columns so that ascending sort points up and descending points down
1. [#3751](https://github.com/influxdata/chronograf/pull/3751): Fix crosshairs moving passed the edges of graphs
## v1.5.0.0 [2018-05-15-RC]

View File

@ -52,7 +52,7 @@ const isValidRole = role => {
}
@ErrorHandling
class AdminInfluxDBPage extends Component {
export class DisconnectedAdminInfluxDBPage extends Component {
constructor(props) {
super(props)
}
@ -223,7 +223,7 @@ class AdminInfluxDBPage extends Component {
return (
<div className="page">
<PageHeader title="InfluxDB Admin" sourceIndicator={true} />
<PageHeader titleText="InfluxDB Admin" sourceIndicator={true} />
<FancyScrollbar className="page-contents">
{users ? (
<div className="container-fluid">
@ -245,7 +245,7 @@ class AdminInfluxDBPage extends Component {
const {arrayOf, func, shape, string} = PropTypes
AdminInfluxDBPage.propTypes = {
DisconnectedAdminInfluxDBPage.propTypes = {
source: shape({
id: string.isRequired,
links: shape({
@ -317,4 +317,6 @@ const mapDispatchToProps = dispatch => ({
notify: bindActionCreators(notifyAction, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(AdminInfluxDBPage)
export default connect(mapStateToProps, mapDispatchToProps)(
DisconnectedAdminInfluxDBPage
)

View File

@ -50,7 +50,7 @@ const sections = me => [
const AdminChronografPage = ({me, source, params: {tab}}) => (
<div className="page">
<PageHeader title="Chronograf Admin" />
<PageHeader titleText="Chronograf Admin" />
<FancyScrollbar className="page-contents">
<div className="container-fluid">
<SubSections

View File

@ -75,7 +75,6 @@ class ExpressionNode extends PureComponent<Props, State> {
if (func.name === 'filter') {
isAfterFilter = true
}
const isYieldable = isAfterFilter && isAfterRange
const funcNode = (
@ -111,12 +110,18 @@ class ExpressionNode extends PureComponent<Props, State> {
isYieldable
)
let yieldFunc = func
if (this.isYieldNodeIndex(i + 1)) {
yieldFunc = funcs[i + 1]
}
return (
<Fragment key={`${i}-notInScript`}>
{funcNode}
<YieldFuncNode
index={i}
func={func}
func={yieldFunc}
data={data}
script={script}
bodyID={bodyID}

View File

@ -104,7 +104,7 @@ export default class FilterTagListItem extends PureComponent<Props, State> {
{this.renderEqualitySwitcher()}
</div>
{this.state.isOpen && (
<>
<div className="flux-schema--children">
<div
className="flux-schema--header"
onClick={this.handleInputClick}
@ -130,23 +130,21 @@ export default class FilterTagListItem extends PureComponent<Props, State> {
</div>
{this.isLoading && <LoaderSkeleton />}
{!this.isLoading && (
<>
<FilterTagValueList
db={db}
service={service}
values={tagValues}
selectedValues={selectedValues}
tagKey={tagKey}
onChangeValue={this.props.onChangeValue}
filter={filter}
onLoadMoreValues={this.handleLoadMoreValues}
isLoadingMoreValues={loadingMore === RemoteDataState.Loading}
shouldShowMoreValues={limit < count}
loadMoreCount={this.loadMoreCount}
/>
</>
<FilterTagValueList
db={db}
service={service}
values={tagValues}
selectedValues={selectedValues}
tagKey={tagKey}
onChangeValue={this.props.onChangeValue}
filter={filter}
onLoadMoreValues={this.handleLoadMoreValues}
isLoadingMoreValues={loadingMore === RemoteDataState.Loading}
shouldShowMoreValues={limit < count}
loadMoreCount={this.loadMoreCount}
/>
)}
</>
</div>
)}
</div>
)

View File

@ -2,6 +2,7 @@ import React, {PureComponent, MouseEvent} from 'react'
import classnames from 'classnames'
import _ from 'lodash'
import {getDeep} from 'src/utils/wrappers'
import BodyDelete from 'src/flux/components/BodyDelete'
import FuncArgs from 'src/flux/components/FuncArgs'
import FuncArgsPreview from 'src/flux/components/FuncArgsPreview'
@ -13,6 +14,7 @@ import {
Func,
} from 'src/types/flux'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {Service} from 'src/types'
interface Props {
@ -166,9 +168,31 @@ export default class FuncNode extends PureComponent<Props, State> {
}
private handleDelete = (): void => {
const {func, bodyID, declarationID} = this.props
const {
func,
funcs,
index,
bodyID,
declarationID,
isYielding,
isYieldedInScript,
onToggleYieldWithLast,
} = this.props
this.props.onDelete({funcID: func.id, bodyID, declarationID})
let yieldFuncNodeID: string
if (isYieldedInScript) {
yieldFuncNodeID = getDeep<string>(funcs, `${index + 1}.id`, '')
} else if (isYielding) {
onToggleYieldWithLast(index)
}
this.props.onDelete({
funcID: func.id,
yieldNodeID: yieldFuncNodeID,
bodyID,
declarationID,
})
}
private handleToggleEdit = (e: MouseEvent<HTMLElement>): void => {

View File

@ -37,6 +37,12 @@ class YieldFuncNode extends PureComponent<Props, State> {
this.getData()
}
public componentDidUpdate(prevProps: Props) {
if (prevProps.script !== this.props.script) {
this.getData()
}
}
public render() {
const {func} = this.props
const {data} = this.state

View File

@ -522,7 +522,7 @@ export class FluxPage extends PureComponent<Props, State> {
}
private handleDeleteFuncNode = (ids: DeleteFuncNodeArgs): void => {
const {funcID, declarationID = '', bodyID} = ids
const {funcID, declarationID = '', bodyID, yieldNodeID = ''} = ids
const script = this.state.body
.map((body, bodyIndex) => {
@ -541,12 +541,17 @@ export class FluxPage extends PureComponent<Props, State> {
return
}
const functions = declaration.funcs.filter(f => f.id !== funcID)
const functions = declaration.funcs.filter(
f => f.id !== funcID && f.id !== yieldNodeID
)
const s = this.funcsToSource(functions)
return `${declaration.name} = ${this.formatLastSource(s, isLast)}`
}
const funcs = body.funcs.filter(f => f.id !== funcID)
const funcs = body.funcs.filter(
f => f.id !== funcID && f.id !== yieldNodeID
)
const source = this.funcsToSource(funcs)
return this.formatLastSource(source, isLast)
})

View File

@ -115,7 +115,7 @@ export class HostsPage extends Component {
return (
<div className="page hosts-list-page">
<PageHeader
title="Host List"
titleText="Host List"
optionsComponents={this.optionsComponents}
sourceIndicator={true}
/>
@ -137,7 +137,7 @@ export class HostsPage extends Component {
)
}
optionsComponents = () => {
get optionsComponents() {
const {autoRefresh, onChooseAutoRefresh, onManualRefresh} = this.props
return (

View File

@ -10,6 +10,4 @@
<div id='react-root' data-basepath=""></div>
</body>
</body>
</html>
</html>

View File

@ -34,9 +34,17 @@ class Crosshair extends PureComponent<Props> {
}
private get isVisible() {
const {hoverTime} = this.props
const {dygraph, hoverTime} = this.props
const timeRanges = dygraph.xAxisRange()
return hoverTime !== 0 && _.isFinite(hoverTime)
const minTimeRange = _.get(timeRanges, '0', 0)
const isBeforeMinTimeRange = hoverTime < minTimeRange
const maxTimeRange = _.get(timeRanges, '1', Infinity)
const isPastMaxTimeRange = hoverTime > maxTimeRange
const isValidHoverTime = !isBeforeMinTimeRange && !isPastMaxTimeRange
return isValidHoverTime && hoverTime !== 0 && _.isFinite(hoverTime)
}
private get crosshairLeft(): number {

View File

@ -48,7 +48,7 @@ class PageHeader extends Component<Props> {
const {titleText, titleComponents} = this.props
if (!titleText && !titleComponents) {
console.error(
throw new Error(
'PageHeader requires either titleText or titleComponents prop'
)
}

View File

@ -13,6 +13,13 @@ $padding: 20px 30px;
justify-content: space-between;
}
.edit-temp-var--header > h1 {
color: #eeeff2;
letter-spacing: 0;
font-size: 19px;
font-weight: 400;
}
.edit-temp-var--header-controls > button {
display: inline-block;
margin: 0 5px;

View File

@ -77,6 +77,7 @@ class TemplateControlBar extends Component<Props, State> {
<Authorized requiredRole={EDITOR_ROLE}>
<button
className="btn btn-primary btn-sm template-control--manage"
data-test="add-template-variable"
onClick={this.handleAddVariable}
>
<span className="icon plus" />

View File

@ -72,6 +72,7 @@ class TemplateControlDropdown extends PureComponent<Props, State> {
<span
className="icon cog-thick"
onClick={this.handleShowSettings}
data-test="edit"
/>
</label>
</Authorized>

View File

@ -102,9 +102,7 @@ class TemplateVariableEditor extends PureComponent<Props, State> {
return (
<div className="edit-temp-var">
<div className="edit-temp-var--header">
<h1 className="page-header__title">
{isNew ? 'Create' : 'Edit'} Template Variable
</h1>
<h1>{isNew ? 'Create' : 'Edit'} Template Variable</h1>
<div className="edit-temp-var--header-controls">
<button
className="btn btn-default"

View File

@ -45,6 +45,7 @@ export interface DeleteFuncNodeArgs {
funcID: string
bodyID: string
declarationID?: string
yieldNodeID?: string
}
export interface InputArg {

View File

@ -0,0 +1,33 @@
import React from 'react'
import {shallow} from 'enzyme'
import {DisconnectedAdminInfluxDBPage} from 'src/admin/containers/AdminInfluxDBPage'
import PageHeader from 'src/shared/components/PageHeader'
import Title from 'src/shared/components/PageHeaderTitle'
import {source} from 'test/resources'
describe('AdminInfluxDBPage', () => {
it('should render the approriate header text', () => {
const props = {
source,
loadUsers: () => {},
loadRoles: () => {},
loadPermissions: () => {},
notify: () => {},
params: {tab: ''},
}
const wrapper = shallow(<DisconnectedAdminInfluxDBPage {...props} />)
const pageTitle = wrapper
.find(PageHeader)
.dive()
.find(Title)
.dive()
.find('h1')
.first()
.text()
expect(pageTitle).toBe('InfluxDB Admin')
})
})

View File

@ -4,6 +4,7 @@ import {shallow} from 'enzyme'
import {HostsPage} from 'src/hosts/containers/HostsPage'
import HostsTable from 'src/hosts/components/HostsTable'
import PageHeader from 'src/shared/components/PageHeader'
import Title from 'src/shared/components/PageHeaderTitle'
import {source} from 'test/resources'
@ -32,11 +33,20 @@ describe('Hosts.Containers.HostsPage', () => {
describe('rendering', () => {
it('renders all children components', () => {
const {wrapper} = setup()
const pageHeader = wrapper.find(PageHeader)
const hostsTable = wrapper.find(HostsTable)
expect(pageHeader.exists()).toBe(true)
expect(hostsTable.exists()).toBe(true)
const pageTitle = wrapper
.find(PageHeader)
.dive()
.find(Title)
.dive()
.find('h1')
.first()
.text()
expect(pageTitle).toBe('Host List')
})
describe('hosts', () => {

View File

@ -0,0 +1,12 @@
import React from 'react'
import {shallow} from 'enzyme'
import PageHeader from 'src/shared/components/PageHeader'
describe('PageHeader', () => {
it('should throw an error if neither titleText nor titleComponents is supplied', () => {
expect(() => shallow(<PageHeader />)).toThrow(
'PageHeader requires either titleText or titleComponents prop'
)
})
})

View File

@ -3,13 +3,26 @@ import {shallow} from 'enzyme'
import TemplateControlBar from 'src/tempVars/components/TemplateControlBar'
import TemplateControlDropdown from 'src/tempVars/components/TemplateControlDropdown'
import {TemplateType, TemplateValueType} from 'src/types'
import TemplateVariableEditor from 'src/tempVars/components/TemplateVariableEditor'
import SimpleOverlayTechnology from 'src/shared/components/SimpleOverlayTechnology'
import {source} from 'test/resources'
import {TemplateType, TemplateValueType} from 'src/types'
const defaultProps = {
isOpen: true,
templates: [
{
templates: [],
meRole: 'EDITOR',
isUsingAuth: true,
onSelectTemplate: () => {},
onSaveTemplates: () => {},
onCreateTemplateVariable: () => {},
source,
}
describe('TemplateControlBar', () => {
it('renders component with variables', () => {
const template = {
id: '000',
tempVar: ':alpha:',
label: '',
@ -26,42 +39,39 @@ const defaultProps = {
selected: false,
},
],
},
],
meRole: 'EDITOR',
isUsingAuth: true,
onOpenTemplateManager: () => {},
onSelectTemplate: () => {},
onSaveTemplates: () => {},
onCreateTemplateVariable: () => {},
source,
}
}
const props = {...defaultProps, templates: [template]}
const wrapper = shallow(<TemplateControlBar {...props} />)
const setup = (override = {}) => {
const props = {...defaultProps, ...override}
const wrapper = shallow(<TemplateControlBar {...props} />)
const dropdown = wrapper.find(TemplateControlDropdown)
expect(dropdown.exists()).toBe(true)
})
return {wrapper, props}
}
it('renders component without variables', () => {
const props = {...defaultProps}
const wrapper = shallow(<TemplateControlBar {...props} />)
describe('Dashboard.TemplateControlBar', () => {
describe('rendering', () => {
it('renders component with variables', () => {
const {wrapper} = setup()
const emptyState = wrapper.find({'data-test': 'empty-state'})
const dropdown = wrapper.find(TemplateControlDropdown)
expect(dropdown.exists()).toBe(true)
})
const dropdown = wrapper.find(TemplateControlDropdown)
it('renders component without variables', () => {
const {wrapper} = setup({...defaultProps, templates: []})
expect(dropdown.exists()).toBe(false)
expect(emptyState.exists()).toBe(true)
})
const emptyState = wrapper.find({'data-test': 'empty-state'})
it('renders an TemplateVariableEditor overlay when adding a template variable', () => {
const props = {...defaultProps}
const wrapper = shallow(<TemplateControlBar {...props} />)
const dropdown = wrapper.find(TemplateControlDropdown)
expect(wrapper.find(SimpleOverlayTechnology)).toHaveLength(0)
expect(dropdown.exists()).toBe(false)
expect(emptyState.exists()).toBe(true)
})
wrapper.find('[data-test="add-template-variable"]').simulate('click')
const elements = wrapper
.find(SimpleOverlayTechnology)
.dive()
.find(TemplateVariableEditor)
expect(elements).toHaveLength(1)
})
})

View File

@ -0,0 +1,49 @@
import React from 'react'
import {shallow} from 'enzyme'
import SimpleOverlayTechnology from 'src/shared/components/SimpleOverlayTechnology'
import TemplateVariableEditor from 'src/tempVars/components/TemplateVariableEditor'
import TemplateControlDropdown from 'src/tempVars/components/TemplateControlDropdown'
import {source} from 'test/resources'
import {TemplateType, TemplateValueType} from 'src/types'
const defaultProps = {
template: {
id: '0',
tempVar: ':my-var:',
label: '',
type: TemplateType.Databases,
values: [
{
value: 'db0',
type: TemplateValueType.Database,
selected: true,
},
],
},
meRole: 'EDITOR',
isUsingAuth: true,
source,
onSelectTemplate: () => Promise.resolve(),
onCreateTemplate: () => Promise.resolve(),
onUpdateTemplate: () => Promise.resolve(),
onDeleteTemplate: () => Promise.resolve(),
}
describe('TemplateControlDropdown', () => {
it('should show a TemplateVariableEditor overlay when the settings icon is clicked', () => {
const wrapper = shallow(<TemplateControlDropdown {...defaultProps} />)
expect(wrapper.find(SimpleOverlayTechnology)).toHaveLength(0)
wrapper.find("[data-test='edit']").simulate('click')
const elements = wrapper
.find(SimpleOverlayTechnology)
.dive()
.find(TemplateVariableEditor)
expect(elements).toHaveLength(1)
})
})