Merge remote-tracking branch 'origin/master' into fun/dashboard_page
commit
f43f504bc1
|
@ -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]
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -10,6 +10,4 @@
|
|||
<div id='react-root' data-basepath=""></div>
|
||||
</body>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -72,6 +72,7 @@ class TemplateControlDropdown extends PureComponent<Props, State> {
|
|||
<span
|
||||
className="icon cog-thick"
|
||||
onClick={this.handleShowSettings}
|
||||
data-test="edit"
|
||||
/>
|
||||
</label>
|
||||
</Authorized>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -45,6 +45,7 @@ export interface DeleteFuncNodeArgs {
|
|||
funcID: string
|
||||
bodyID: string
|
||||
declarationID?: string
|
||||
yieldNodeID?: string
|
||||
}
|
||||
|
||||
export interface InputArg {
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
})
|
|
@ -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', () => {
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
})
|
||||
})
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
Loading…
Reference in New Issue