Merge branch 'master' into table/reorder-columns

pull/10616/head
ebb-tide 2018-04-09 16:22:15 -07:00
commit 7aa09418f1
12 changed files with 107 additions and 336 deletions

View File

@ -49,7 +49,6 @@ class CellEditorOverlay extends Component {
activeQueryIndex: 0,
isDisplayOptionsTabActive: false,
staticLegend: IS_STATIC_LEGEND(legend),
dataLabels: [],
}
}
@ -253,10 +252,6 @@ class CellEditorOverlay extends Component {
this.overlayRef.focus()
}
setDataLabels = dataLabels => {
this.setState({dataLabels})
}
render() {
const {
onCancel,
@ -271,7 +266,6 @@ class CellEditorOverlay extends Component {
isDisplayOptionsTabActive,
queriesWorkingDraft,
staticLegend,
dataLabels,
} = this.state
const queryActions = {
@ -304,7 +298,6 @@ class CellEditorOverlay extends Component {
queryConfigs={queriesWorkingDraft}
editQueryStatus={editQueryStatus}
staticLegend={staticLegend}
setDataLabels={this.setDataLabels}
/>
<CEOBottom>
<OverlayControls
@ -324,7 +317,6 @@ class CellEditorOverlay extends Component {
onToggleStaticLegend={this.handleToggleStaticLegend}
staticLegend={staticLegend}
onResetFocus={this.handleResetFocus}
dataLabels={dataLabels}
/>
) : (
<QueryMaker

View File

@ -42,7 +42,7 @@ class DisplayOptions extends Component {
staticLegend,
onToggleStaticLegend,
onResetFocus,
dataLabels,
queryConfigs,
} = this.props
switch (type) {
case 'gauge':
@ -51,7 +51,10 @@ class DisplayOptions extends Component {
return <SingleStatOptions onResetFocus={onResetFocus} />
case 'table':
return (
<TableOptions onResetFocus={onResetFocus} dataLabels={dataLabels} />
<TableOptions
onResetFocus={onResetFocus}
queryConfigs={queryConfigs}
/>
)
default:
return (
@ -90,7 +93,6 @@ DisplayOptions.propTypes = {
onToggleStaticLegend: func.isRequired,
staticLegend: bool,
onResetFocus: func.isRequired,
dataLabels: arrayOf(string),
}
const mapStateToProps = ({cellEditorOverlay: {cell, cell: {axes}}}) => ({

View File

@ -23,16 +23,6 @@ class GraphOptionsCustomizableField extends PureComponent<Props, {}> {
this.handleToggleVisible = this.handleToggleVisible.bind(this)
}
public handleFieldRename(rename: string) {
const {onFieldUpdate, internalName, visible} = this.props
onFieldUpdate({internalName, displayName: rename, visible})
}
public handleToggleVisible() {
const {onFieldUpdate, internalName, displayName, visible} = this.props
onFieldUpdate({internalName, displayName, visible: !visible})
}
public render() {
const {internalName, displayName, visible, opacity} = this.props
@ -68,6 +58,16 @@ class GraphOptionsCustomizableField extends PureComponent<Props, {}> {
</div>
)
}
private handleFieldRename(rename: string) {
const {onFieldUpdate, internalName, visible} = this.props
onFieldUpdate({internalName, displayName: rename, visible})
}
private handleToggleVisible() {
const {onFieldUpdate, internalName, displayName, visible} = this.props
onFieldUpdate({internalName, displayName, visible: !visible})
}
}
export default GraphOptionsCustomizableField

View File

@ -16,6 +16,7 @@ import ThresholdsListTypeToggle from 'src/shared/components/ThresholdsListTypeTo
import {updateTableOptions} from 'src/dashboards/actions/cellEditorOverlay'
import {TIME_FIELD_DEFAULT} from 'src/shared/constants/tableGraph'
import {QueryConfig} from 'src/types/query'
interface Option {
text: string
@ -38,10 +39,10 @@ interface Options {
}
interface Props {
queryConfigs: QueryConfig[]
handleUpdateTableOptions: (options: Options) => void
tableOptions: Options
onResetFocus: () => void
dataLabels: string[]
}
export class TableOptions extends PureComponent<Props, {}> {
@ -50,90 +51,6 @@ export class TableOptions extends PureComponent<Props, {}> {
this.moveField = this.moveField.bind(this)
}
get fieldNames() {
const {tableOptions: {fieldNames}} = this.props
return fieldNames || []
}
get timeField() {
return (
this.fieldNames.find(f => f.internalName === 'time') || TIME_FIELD_DEFAULT
)
}
get computedFieldNames() {
const {dataLabels} = this.props
return _.isEmpty(dataLabels)
? [this.timeField]
: _.concat(
this.fieldNames,
dataLabels
.filter(
label => !this.fieldNames.find(f => f.internalName === label)
)
.map(label => ({
internalName: label,
displayName: '',
visible: true,
}))
)
}
public handleChooseSortBy = (option: Option) => {
const {tableOptions, handleUpdateTableOptions} = this.props
const sortBy = {
displayName: option.text === option.key ? '' : option.text,
internalName: option.key,
visible: true,
}
handleUpdateTableOptions({...tableOptions, sortBy})
}
public handleTimeFormatChange = timeFormat => {
const {tableOptions, handleUpdateTableOptions} = this.props
handleUpdateTableOptions({...tableOptions, timeFormat})
}
public handleToggleVerticalTimeAxis = verticalTimeAxis => () => {
const {tableOptions, handleUpdateTableOptions} = this.props
handleUpdateTableOptions({...tableOptions, verticalTimeAxis})
}
public handleToggleFixFirstColumn = () => {
const {handleUpdateTableOptions, tableOptions} = this.props
const fixFirstColumn = !tableOptions.fixFirstColumn
handleUpdateTableOptions({...tableOptions, fixFirstColumn})
}
public handleSingleFieldUpdate = field => {
const {handleUpdateTableOptions, tableOptions, dataLabels} = this.props
const {sortBy, fieldNames} = tableOptions
const fields =
fieldNames.length >= dataLabels.length
? fieldNames
: this.computedFieldNames
const updatedFields = fields.map(
f => (f.internalName === field.internalName ? field : f)
)
_.sortBy(updatedFields, f => {
f.order
})
const updatedSortBy =
sortBy.internalName === field.internalName
? {...sortBy, displayName: field.displayName}
: sortBy
handleUpdateTableOptions({
...tableOptions,
fieldNames: updatedFields,
sortBy: updatedSortBy,
})
}
public componentWillMount() {
const {handleUpdateTableOptions, tableOptions} = this.props
handleUpdateTableOptions({
@ -143,14 +60,13 @@ export class TableOptions extends PureComponent<Props, {}> {
}
public shouldComponentUpdate(nextProps) {
const {tableOptions, dataLabels} = this.props
const {tableOptions} = this.props
const tableOptionsDifferent = !_.isEqual(
tableOptions,
nextProps.tableOptions
)
const dataLabelsDifferent = !_.isEqual(dataLabels, nextProps.dataLabels)
return tableOptionsDifferent || dataLabelsDifferent
return tableOptionsDifferent
}
public moveField(dragIndex, hoverIndex) {
@ -185,13 +101,11 @@ export class TableOptions extends PureComponent<Props, {}> {
tableOptions,
} = this.props
const tableSortByOptions = this.computedFieldNames.map(field => ({
const tableSortByOptions = fieldNames.map(field => ({
key: field.internalName,
text: field.displayName || field.internalName,
}))
const fields = fieldNames.length > 1 ? fieldNames : this.computedFieldNames
return (
<FancyScrollbar
className="display-options--cell y-axis-controls"
@ -219,8 +133,8 @@ export class TableOptions extends PureComponent<Props, {}> {
/>
</div>
<GraphOptionsCustomizeFields
fields={fields}
onFieldUpdate={this.handleSingleFieldUpdate}
fields={fieldNames}
onFieldUpdate={this.handleFieldUpdate}
moveField={this.moveField}
/>
<ThresholdsList showListHeading={true} onResetFocus={onResetFocus} />
@ -231,6 +145,79 @@ export class TableOptions extends PureComponent<Props, {}> {
</FancyScrollbar>
)
}
private get fieldNames() {
const {tableOptions: {fieldNames}} = this.props
return fieldNames || []
}
private get timeField() {
return (
this.fieldNames.find(f => f.internalName === 'time') || TIME_FIELD_DEFAULT
)
}
private get computedFieldNames() {
const {queryConfigs} = this.props
const queryFields = _.flatten(
queryConfigs.map(({measurement, fields}) => {
return fields.map(({alias}) => {
const internalName = `${measurement}.${alias}`
const existing = this.fieldNames.find(
c => c.internalName === internalName
)
return existing || {internalName, displayName: '', visible: true}
})
})
)
return [this.timeField, ...queryFields]
}
private handleChooseSortBy = (option: Option) => {
const {tableOptions, handleUpdateTableOptions} = this.props
const sortBy = {
displayName: option.text === option.key ? '' : option.text,
internalName: option.key,
visible: true,
}
handleUpdateTableOptions({...tableOptions, sortBy})
}
private handleTimeFormatChange = timeFormat => {
const {tableOptions, handleUpdateTableOptions} = this.props
handleUpdateTableOptions({...tableOptions, timeFormat})
}
private handleToggleVerticalTimeAxis = verticalTimeAxis => () => {
const {tableOptions, handleUpdateTableOptions} = this.props
handleUpdateTableOptions({...tableOptions, verticalTimeAxis})
}
private handleToggleFixFirstColumn = () => {
const {handleUpdateTableOptions, tableOptions} = this.props
const fixFirstColumn = !tableOptions.fixFirstColumn
handleUpdateTableOptions({...tableOptions, fixFirstColumn})
}
private handleFieldUpdate = field => {
const {handleUpdateTableOptions, tableOptions} = this.props
const {sortBy, fieldNames} = tableOptions
const updatedFields = fieldNames.map(
f => (f.internalName === field.internalName ? field : f)
)
const updatedSortBy =
sortBy.internalName === field.internalName
? {...sortBy, displayName: field.displayName}
: sortBy
handleUpdateTableOptions({
...tableOptions,
fieldNames: updatedFields,
sortBy: updatedSortBy,
})
}
}
const mapStateToProps = ({cellEditorOverlay: {cell: {tableOptions}}}) => ({

View File

@ -24,7 +24,6 @@ const DashVisualization = (
staticLegend,
thresholdsListColors,
tableOptions,
setDataLabels,
},
{source: {links: {proxy}}}
) => {
@ -50,7 +49,6 @@ const DashVisualization = (
editQueryStatus={editQueryStatus}
resizerTopHeight={resizerTopHeight}
staticLegend={staticLegend}
setDataLabels={setDataLabels}
/>
</div>
</div>
@ -80,7 +78,6 @@ DashVisualization.propTypes = {
gaugeColors: colorsNumberSchema,
lineColors: colorsStringSchema,
staticLegend: bool,
setDataLabels: func,
}
DashVisualization.contextTypes = {

View File

@ -236,6 +236,7 @@ class Dygraph extends Component {
const newTime = this.eventToTimestamp(e)
this.props.onSetHoverTime(newTime)
}
this.setState({isHoveringThisGraph: true})
}
@ -243,23 +244,13 @@ class Dygraph extends Component {
if (this.props.onSetHoverTime) {
this.props.onSetHoverTime(NULL_HOVER_TIME)
}
this.setState({isHoveringThisGraph: false})
}
handleHideLegend = e => {
const {top, bottom, left, right} = this.graphRef.getBoundingClientRect()
const mouseY = e.clientY
const mouseX = e.clientX
const mouseInGraphY = mouseY <= bottom && mouseY >= top
const mouseInGraphX = mouseX <= right && mouseX >= left
const isMouseHoveringGraph = mouseInGraphY && mouseInGraphX
if (!isMouseHoveringGraph) {
handleHideLegend = () => {
this.setState({isHidden: true})
}
}
getLineColors = () => {
return [...(this.props.overrideLineColors || LINE_COLORS)]

View File

@ -25,7 +25,6 @@ const RefreshingGraph = ({
cellID,
queries,
tableOptions,
setDataLabels,
templates,
timeRange,
cellHeight,
@ -101,7 +100,6 @@ const RefreshingGraph = ({
hoverTime={hoverTime}
onSetHoverTime={onSetHoverTime}
inView={inView}
setDataLabels={setDataLabels}
/>
)
}
@ -160,7 +158,6 @@ RefreshingGraph.propTypes = {
cellID: string,
inView: bool,
tableOptions: shape({}),
setDataLabels: func,
}
RefreshingGraph.defaultProps = {

View File

@ -40,7 +40,6 @@ class TableGraph extends Component {
data: [[]],
processedData: [[]],
sortedTimeVals: [],
labels: [],
hoveredColumnIndex: NULL_ARRAY_INDEX,
hoveredRowIndex: NULL_ARRAY_INDEX,
sortField,
@ -51,7 +50,7 @@ class TableGraph extends Component {
}
componentWillReceiveProps(nextProps) {
const {labels, data} = timeSeriesToTableGraph(nextProps.data)
const {data} = timeSeriesToTableGraph(nextProps.data)
if (_.isEmpty(data[0])) {
return
}
@ -64,13 +63,8 @@ class TableGraph extends Component {
verticalTimeAxis,
timeFormat,
},
setDataLabels,
} = nextProps
if (setDataLabels) {
setDataLabels(labels)
}
let direction, sortFieldName
if (
_.get(this.props, ['tableOptions', 'sortBy', 'internalName'], '') ===
@ -99,7 +93,6 @@ class TableGraph extends Component {
this.setState({
data,
labels,
processedData,
sortedTimeVals,
sortField: sortFieldName,
@ -194,7 +187,7 @@ class TableGraph extends Component {
})
}
calculateColumnWidth = __ => column => {
calculateColumnWidth = columnSizerWidth => column => {
const {index} = column
const {tableOptions: {fixFirstColumn}} = this.props
const {processedData, columnWidths, totalColumnWidths} = this.state
@ -205,8 +198,12 @@ class TableGraph extends Component {
const tableWidth = _.get(this, ['gridContainer', 'clientWidth'], 0)
if (tableWidth > totalColumnWidths) {
if (columnCount === 1) {
return columnSizerWidth
}
const difference = tableWidth - totalColumnWidths
const distributeOver = fixFirstColumn ? columnCount - 1 : columnCount
const distributeOver =
fixFirstColumn && columnCount > 1 ? columnCount - 1 : columnCount
const increment = difference / distributeOver
adjustedColumnSizerWidth =
fixFirstColumn && index === 0
@ -417,7 +414,6 @@ TableGraph.propTypes = {
hoverTime: string,
onSetHoverTime: func,
colors: colorsStringSchema,
setDataLabels: func,
}
export default TableGraph

View File

@ -180,7 +180,6 @@ export const timeSeriesToTableGraph = raw => {
const tableData = map(sortedTimeSeries, ({time, values}) => [time, ...values])
const data = tableData.length ? [labels, ...tableData] : [[]]
return {
labels,
data,
}
}

View File

@ -72,47 +72,4 @@ describe('Dashboards.Components.GraphOptionsCustomizableField', () => {
})
})
})
describe('instance methods', () => {
describe('#handleSingleFieldUpdate', () => {
it('calls onFieldUpdate once with internalName, new name, and visible', () => {
const onFieldUpdate = jest.fn()
const internalName = 'test'
const {instance, props: {visible}} = setup({
internalName,
onFieldUpdate,
})
const rename = 'TEST'
instance.handleFieldRename(rename)
expect(onFieldUpdate).toHaveBeenCalledTimes(1)
expect(onFieldUpdate).toHaveBeenCalledWith({
displayName: rename,
internalName,
visible,
})
})
})
describe('#handleToggleVisible', () => {
it('calls onFieldUpdate once with !visible, internalName, and displayName', () => {
const onFieldUpdate = jest.fn()
const visible = true
const {instance, props: {internalName, displayName}} = setup({
onFieldUpdate,
visible,
})
instance.handleToggleVisible()
expect(onFieldUpdate).toHaveBeenCalledTimes(1)
expect(onFieldUpdate).toHaveBeenCalledWith({
displayName,
internalName,
visible: !visible,
})
})
})
})
})

View File

@ -9,12 +9,11 @@ import {TableOptions} from 'src/dashboards/components/TableOptions'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import ThresholdsList from 'src/shared/components/ThresholdsList'
import ThresholdsListTypeToggle from 'src/shared/components/ThresholdsListTypeToggle'
import {TIME_FIELD_DEFAULT} from 'src/shared/constants/tableGraph'
const defaultProps = {
dataLabels: [],
handleUpdateTableOptions: () => {},
onResetFocus: () => {},
queryConfigs: [],
tableOptions: {
columnNames: [],
fieldNames: [],
@ -39,100 +38,6 @@ const setup = (override = {}) => {
}
describe('Dashboards.Components.TableOptions', () => {
describe('getters', () => {
describe('fieldNames', () => {
describe('if fieldNames are passed in tableOptions as props', () => {
it('returns fieldNames', () => {
const fieldNames = [
{internalName: 'time', displayName: 'TIME', visible: true},
{internalName: 'foo', displayName: 'BAR', visible: false},
]
const {instance} = setup({tableOptions: {fieldNames}})
expect(instance.fieldNames).toBe(fieldNames)
})
})
describe('if fieldNames are not passed in tableOptions as props', () => {
it('returns empty array', () => {
const {instance} = setup()
expect(instance.fieldNames).toEqual([])
})
})
})
describe('timeField', () => {
describe('if time field in fieldNames', () => {
it('returns time field', () => {
const timeField = {
internalName: 'time',
displayName: 'TIME',
visible: true,
}
const fieldNames = [
timeField,
{internalName: 'foo', displayName: 'BAR', visible: false},
]
const {instance} = setup({tableOptions: {fieldNames}})
expect(instance.timeField).toBe(timeField)
})
})
describe('if time field not in fieldNames', () => {
it('returns default time field', () => {
const fieldNames = [
{internalName: 'foo', displayName: 'BAR', visible: false},
]
const {instance} = setup({tableOptions: {fieldNames}})
expect(instance.timeField).toBe(TIME_FIELD_DEFAULT)
})
})
})
describe('computedFieldNames', () => {
describe('if dataLabels are not passed in', () => {
it('returns an array of the time column', () => {
const {instance} = setup()
expect(instance.computedFieldNames).toEqual([TIME_FIELD_DEFAULT])
})
})
describe('if dataLabels are passed in', () => {
describe('if dataLabel has a matching fieldName', () => {
it('returns array with the matching fieldName', () => {
const fieldNames = [
{internalName: 'foo', displayName: 'bar', visible: true},
]
const dataLabels = ['foo']
const {instance} = setup({tableOptions: {fieldNames}, dataLabels})
expect(instance.computedFieldNames).toEqual(fieldNames)
})
})
describe('if dataLabel does not have a matching fieldName', () => {
it('returns array with a new fieldName created for it', () => {
const fieldNames = [
{internalName: 'time', displayName: 'bar', visible: true},
]
const unmatchedLabel = 'foo'
const dataLabels = ['time', unmatchedLabel]
const {instance} = setup({tableOptions: {fieldNames}, dataLabels})
expect(instance.computedFieldNames).toEqual([
...fieldNames,
{internalName: unmatchedLabel, displayName: '', visible: true},
])
})
})
})
})
})
describe('rendering', () => {
it('should render all components', () => {
const {wrapper} = setup()

View File

@ -351,58 +351,6 @@ describe('timeSeriesToTableGraph', () => {
expect(actual.data).toEqual(expected)
})
it('returns labels starting with time and then alphabetized', () => {
const influxResponse = [
{
response: {
results: [
{
series: [
{
name: 'mb',
columns: ['time', 'f1'],
values: [[1000, 1], [2000, 2]],
},
],
},
{
series: [
{
name: 'ma',
columns: ['time', 'f1'],
values: [[1000, 1], [2000, 2]],
},
],
},
{
series: [
{
name: 'mc',
columns: ['time', 'f2'],
values: [[2000, 3], [4000, 4]],
},
],
},
{
series: [
{
name: 'mc',
columns: ['time', 'f1'],
values: [[2000, 3], [4000, 4]],
},
],
},
],
},
},
]
const actual = timeSeriesToTableGraph(influxResponse)
const expected = ['time', 'ma.f1', 'mb.f1', 'mc.f1', 'mc.f2']
expect(actual.labels).toEqual(expected)
})
it('parses raw data into a table-readable format with the first row being labels', () => {
const influxResponse = [
{