commit
d2af17d1e5
|
@ -82,7 +82,10 @@ const createWorkingDraft = (source: string, query: CellQuery): Query => {
|
|||
return draft
|
||||
}
|
||||
|
||||
const createWorkingDrafts = (source: string, queries: CellQuery[]): Query[] =>
|
||||
const createWorkingDrafts = (
|
||||
source: string,
|
||||
queries: CellQuery[] = []
|
||||
): Query[] =>
|
||||
_.cloneDeep(
|
||||
queries.map((query: CellQuery) => createWorkingDraft(source, query))
|
||||
)
|
||||
|
@ -179,6 +182,7 @@ class CellEditorOverlay extends Component<Props, State> {
|
|||
queryConfigs={queriesWorkingDraft}
|
||||
editQueryStatus={editQueryStatus}
|
||||
staticLegend={isStaticLegend}
|
||||
isInCEO={true}
|
||||
/>
|
||||
<CEOBottom>
|
||||
<OverlayControls
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {SFC} from 'react'
|
||||
import React, {PureComponent} from 'react'
|
||||
import {DragDropContext} from 'react-dnd'
|
||||
import HTML5Backend from 'react-dnd-html5-backend'
|
||||
|
||||
|
@ -10,35 +10,36 @@ interface RenamableField {
|
|||
visible: boolean
|
||||
}
|
||||
|
||||
interface GraphOptionsCustomizeFieldsProps {
|
||||
interface Props {
|
||||
fields: RenamableField[]
|
||||
onFieldUpdate: (field: RenamableField) => void
|
||||
moveField: (dragIndex: number, hoverIndex: number) => void
|
||||
}
|
||||
const GraphOptionsCustomizeFields: SFC<GraphOptionsCustomizeFieldsProps> = ({
|
||||
fields,
|
||||
onFieldUpdate,
|
||||
moveField,
|
||||
}) => {
|
||||
return (
|
||||
<div className="graph-options-group">
|
||||
<label className="form-label">Customize Fields</label>
|
||||
<div>
|
||||
{fields.map((field, i) => (
|
||||
<GraphOptionsCustomizableField
|
||||
key={field.internalName}
|
||||
index={i}
|
||||
id={field.internalName}
|
||||
internalName={field.internalName}
|
||||
displayName={field.displayName}
|
||||
visible={field.visible}
|
||||
onFieldUpdate={onFieldUpdate}
|
||||
moveField={moveField}
|
||||
/>
|
||||
))}
|
||||
|
||||
class GraphOptionsCustomizeFields extends PureComponent<Props> {
|
||||
public render() {
|
||||
const {fields, onFieldUpdate, moveField} = this.props
|
||||
|
||||
return (
|
||||
<div className="graph-options-group">
|
||||
<label className="form-label">Customize Fields</label>
|
||||
<div>
|
||||
{fields.map((field, i) => (
|
||||
<GraphOptionsCustomizableField
|
||||
key={field.internalName}
|
||||
index={i}
|
||||
id={field.internalName}
|
||||
internalName={field.internalName}
|
||||
displayName={field.displayName}
|
||||
visible={field.visible}
|
||||
onFieldUpdate={onFieldUpdate}
|
||||
moveField={moveField}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default DragDropContext(HTML5Backend)(GraphOptionsCustomizeFields)
|
||||
|
|
|
@ -4,7 +4,7 @@ import QuestionMarkTooltip from 'src/shared/components/QuestionMarkTooltip'
|
|||
import {
|
||||
FORMAT_OPTIONS,
|
||||
TIME_FORMAT_CUSTOM,
|
||||
TIME_FORMAT_DEFAULT,
|
||||
DEFAULT_TIME_FORMAT,
|
||||
TIME_FORMAT_TOOLTIP_LINK,
|
||||
} from 'src/shared/constants/tableGraph'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
@ -29,7 +29,7 @@ class GraphOptionsTimeFormat extends PureComponent<Props, State> {
|
|||
super(props)
|
||||
this.state = {
|
||||
customFormat: false,
|
||||
format: this.props.timeFormat || TIME_FORMAT_DEFAULT,
|
||||
format: this.props.timeFormat || DEFAULT_TIME_FORMAT,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -52,24 +52,6 @@ export class TableOptions extends Component<Props, {}> {
|
|||
this.moveField = this.moveField.bind(this)
|
||||
}
|
||||
|
||||
public componentWillMount() {
|
||||
const {handleUpdateTableOptions, tableOptions} = this.props
|
||||
handleUpdateTableOptions({
|
||||
...tableOptions,
|
||||
fieldNames: this.computedFieldNames,
|
||||
})
|
||||
}
|
||||
|
||||
public shouldComponentUpdate(nextProps) {
|
||||
const {tableOptions} = this.props
|
||||
const tableOptionsDifferent = !_.isEqual(
|
||||
tableOptions,
|
||||
nextProps.tableOptions
|
||||
)
|
||||
|
||||
return tableOptionsDifferent
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
tableOptions: {timeFormat, fieldNames, verticalTimeAxis, fixFirstColumn},
|
||||
|
@ -122,28 +104,14 @@ export class TableOptions extends Component<Props, {}> {
|
|||
)
|
||||
}
|
||||
|
||||
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 moveField(dragIndex, hoverIndex) {
|
||||
const {handleUpdateTableOptions, tableOptions} = this.props
|
||||
const {fieldNames} = tableOptions
|
||||
const fields = fieldNames.length > 1 ? fieldNames : this.computedFieldNames
|
||||
|
||||
const dragField = fields[dragIndex]
|
||||
const dragField = fieldNames[dragIndex]
|
||||
const removedFields = _.concat(
|
||||
_.slice(fields, 0, dragIndex),
|
||||
_.slice(fields, dragIndex + 1)
|
||||
_.slice(fieldNames, 0, dragIndex),
|
||||
_.slice(fieldNames, dragIndex + 1)
|
||||
)
|
||||
const addedFields = _.concat(
|
||||
_.slice(removedFields, 0, hoverIndex),
|
||||
|
@ -156,23 +124,6 @@ export class TableOptions extends Component<Props, {}> {
|
|||
})
|
||||
}
|
||||
|
||||
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 = {
|
||||
|
|
|
@ -24,6 +24,7 @@ const DashVisualization = (
|
|||
staticLegend,
|
||||
thresholdsListColors,
|
||||
tableOptions,
|
||||
isInCEO,
|
||||
},
|
||||
{
|
||||
source: {
|
||||
|
@ -53,6 +54,7 @@ const DashVisualization = (
|
|||
editQueryStatus={editQueryStatus}
|
||||
resizerTopHeight={resizerTopHeight}
|
||||
staticLegend={staticLegend}
|
||||
isInCEO={isInCEO}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -82,6 +84,7 @@ DashVisualization.propTypes = {
|
|||
gaugeColors: colorsNumberSchema,
|
||||
lineColors: colorsStringSchema,
|
||||
staticLegend: bool,
|
||||
isInCEO: bool,
|
||||
}
|
||||
|
||||
DashVisualization.contextTypes = {
|
||||
|
|
|
@ -363,7 +363,6 @@ class DashboardPage extends Component {
|
|||
name: d.name,
|
||||
link: `/sources/${sourceID}/dashboards/${d.id}`,
|
||||
}))
|
||||
|
||||
return (
|
||||
<div className="page dashboard-page">
|
||||
{isTemplating ? (
|
||||
|
@ -566,6 +565,7 @@ const mapStateToProps = (state, {params: {dashboardID}}) => {
|
|||
const dashboard = dashboards.find(
|
||||
d => d.id === idNormalizer(TYPE_ID, dashboardID)
|
||||
)
|
||||
|
||||
const selectedCell = cell
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import calculateSize from 'calculate-size'
|
||||
import _ from 'lodash'
|
||||
import {reduce} from 'fast.js'
|
||||
import {map, reduce, filter} from 'fast.js'
|
||||
|
||||
import {
|
||||
CELL_HORIZONTAL_PADDING,
|
||||
TIME_FIELD_DEFAULT,
|
||||
TIME_FORMAT_DEFAULT,
|
||||
DEFAULT_TIME_FORMAT,
|
||||
} from 'src/shared/constants/tableGraph'
|
||||
|
||||
const calculateTimeColumnWidth = timeFormat => {
|
||||
|
@ -77,6 +77,28 @@ const updateMaxWidths = (
|
|||
)
|
||||
}
|
||||
|
||||
export const computeFieldNames = (existingFieldNames, sortedLabels) => {
|
||||
const timeField =
|
||||
existingFieldNames.find(f => f.internalName === 'time') ||
|
||||
TIME_FIELD_DEFAULT
|
||||
let astNames = [timeField]
|
||||
|
||||
sortedLabels.forEach(({label}) => {
|
||||
const field = {internalName: label, displayName: '', visible: true}
|
||||
astNames = [...astNames, field]
|
||||
})
|
||||
|
||||
const intersection = existingFieldNames.filter(f => {
|
||||
return astNames.find(a => a.internalName === f.internalName)
|
||||
})
|
||||
|
||||
const newFields = astNames.filter(a => {
|
||||
return !existingFieldNames.find(f => f.internalName === a.internalName)
|
||||
})
|
||||
|
||||
return [...intersection, ...newFields]
|
||||
}
|
||||
|
||||
export const calculateColumnWidths = (
|
||||
data,
|
||||
fieldNames,
|
||||
|
@ -84,7 +106,7 @@ export const calculateColumnWidths = (
|
|||
verticalTimeAxis
|
||||
) => {
|
||||
const timeFormatWidth = calculateTimeColumnWidth(
|
||||
timeFormat === '' ? TIME_FORMAT_DEFAULT : timeFormat
|
||||
timeFormat === '' ? DEFAULT_TIME_FORMAT : timeFormat
|
||||
)
|
||||
return reduce(
|
||||
data,
|
||||
|
@ -102,3 +124,50 @@ export const calculateColumnWidths = (
|
|||
{widths: {}, totalWidths: 0}
|
||||
)
|
||||
}
|
||||
|
||||
export const filterTableColumns = (data, fieldNames) => {
|
||||
const visibility = {}
|
||||
const filteredData = map(data, (row, i) => {
|
||||
return filter(row, (col, j) => {
|
||||
if (i === 0) {
|
||||
const foundField = fieldNames.find(field => field.internalName === col)
|
||||
visibility[j] = foundField ? foundField.visible : true
|
||||
}
|
||||
return visibility[j]
|
||||
})
|
||||
})
|
||||
return filteredData[0].length ? filteredData : [[]]
|
||||
}
|
||||
|
||||
export const orderTableColumns = (data, fieldNames) => {
|
||||
const fieldsSortOrder = fieldNames.map(fieldName => {
|
||||
return _.findIndex(data[0], dataLabel => {
|
||||
return dataLabel === fieldName.internalName
|
||||
})
|
||||
})
|
||||
const filteredFieldSortOrder = filter(fieldsSortOrder, f => f !== -1)
|
||||
const orderedData = map(data, row => {
|
||||
return row.map((v, j, arr) => arr[filteredFieldSortOrder[j]] || v)
|
||||
})
|
||||
return orderedData[0].length ? orderedData : [[]]
|
||||
}
|
||||
|
||||
export const transformTableData = (data, sort, fieldNames, tableOptions) => {
|
||||
const {verticalTimeAxis, timeFormat} = tableOptions
|
||||
const sortIndex = _.indexOf(data[0], sort.field)
|
||||
const sortedData = [
|
||||
data[0],
|
||||
..._.orderBy(_.drop(data, 1), sortIndex, [sort.direction]),
|
||||
]
|
||||
const sortedTimeVals = map(sortedData, r => r[0])
|
||||
const filteredData = filterTableColumns(sortedData, fieldNames)
|
||||
const orderedData = orderTableColumns(filteredData, fieldNames)
|
||||
const transformedData = verticalTimeAxis ? orderedData : _.unzip(orderedData)
|
||||
const {widths: columnWidths, totalWidths} = calculateColumnWidths(
|
||||
transformedData,
|
||||
fieldNames,
|
||||
timeFormat,
|
||||
verticalTimeAxis
|
||||
)
|
||||
return {transformedData, sortedTimeVals, columnWidths, totalWidths}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import _ from 'lodash'
|
|||
import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries'
|
||||
import {removeUnselectedTemplateValues} from 'src/dashboards/constants'
|
||||
import {intervalValuesPoints} from 'src/shared/constants'
|
||||
import {getQueryConfig} from 'shared/apis'
|
||||
|
||||
const AutoRefresh = ComposedComponent => {
|
||||
class wrapper extends Component {
|
||||
|
@ -14,12 +15,17 @@ const AutoRefresh = ComposedComponent => {
|
|||
lastQuerySuccessful: true,
|
||||
timeSeries: [],
|
||||
resolution: null,
|
||||
queryASTs: [],
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const {queries, templates, autoRefresh} = this.props
|
||||
async componentDidMount() {
|
||||
const {queries, templates, autoRefresh, type} = this.props
|
||||
this.executeQueries(queries, templates)
|
||||
if (type === 'table') {
|
||||
const queryASTs = await this.getQueryASTs(queries, templates)
|
||||
this.setState({queryASTs})
|
||||
}
|
||||
if (autoRefresh) {
|
||||
this.intervalID = setInterval(
|
||||
() => this.executeQueries(queries, templates),
|
||||
|
@ -28,7 +34,19 @@ const AutoRefresh = ComposedComponent => {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
getQueryASTs = async (queries, templates) => {
|
||||
return await Promise.all(
|
||||
queries.map(async q => {
|
||||
const host = _.isArray(q.host) ? q.host[0] : q.host
|
||||
const url = host.replace('proxy', 'queries')
|
||||
const text = q.text
|
||||
const {data} = await getQueryConfig(url, [{query: text}], templates)
|
||||
return data.queries[0].queryAST
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async componentWillReceiveProps(nextProps) {
|
||||
const inViewDidUpdate = this.props.inView !== nextProps.inView
|
||||
|
||||
const queriesDidUpdate = this.queryDifference(
|
||||
|
@ -45,6 +63,14 @@ const AutoRefresh = ComposedComponent => {
|
|||
queriesDidUpdate || tempVarsDidUpdate || inViewDidUpdate
|
||||
|
||||
if (shouldRefetch) {
|
||||
if (this.props.type === 'table') {
|
||||
const queryASTs = await this.getQueryASTs(
|
||||
nextProps.queries,
|
||||
nextProps.templates
|
||||
)
|
||||
this.setState({queryASTs})
|
||||
}
|
||||
|
||||
this.executeQueries(
|
||||
nextProps.queries,
|
||||
nextProps.templates,
|
||||
|
@ -169,8 +195,7 @@ const AutoRefresh = ComposedComponent => {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {timeSeries} = this.state
|
||||
|
||||
const {timeSeries, queryASTs} = this.state
|
||||
if (this.state.isFetching && this.state.lastQuerySuccessful) {
|
||||
return (
|
||||
<ComposedComponent
|
||||
|
@ -179,6 +204,7 @@ const AutoRefresh = ComposedComponent => {
|
|||
setResolution={this.setResolution}
|
||||
isFetchingInitially={false}
|
||||
isRefreshing={true}
|
||||
queryASTs={queryASTs}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -188,6 +214,7 @@ const AutoRefresh = ComposedComponent => {
|
|||
{...this.props}
|
||||
data={timeSeries}
|
||||
setResolution={this.setResolution}
|
||||
queryASTs={queryASTs}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -221,6 +248,7 @@ const AutoRefresh = ComposedComponent => {
|
|||
} = PropTypes
|
||||
|
||||
wrapper.propTypes = {
|
||||
type: string.isRequired,
|
||||
children: element,
|
||||
autoRefresh: number.isRequired,
|
||||
inView: bool,
|
||||
|
|
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
|
|||
import Dygraph from 'shared/components/Dygraph'
|
||||
|
||||
import SingleStat from 'src/shared/components/SingleStat'
|
||||
import timeSeriesToDygraph from 'utils/timeSeriesTransformers'
|
||||
import {timeSeriesToDygraph} from 'utils/timeSeriesTransformers'
|
||||
|
||||
import {colorsStringSchema} from 'shared/schemas'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
|
|
@ -40,6 +40,7 @@ const RefreshingGraph = ({
|
|||
editQueryStatus,
|
||||
handleSetHoverTime,
|
||||
grabDataForDownload,
|
||||
isInCEO,
|
||||
}) => {
|
||||
const prefix = (axes && axes.y.prefix) || ''
|
||||
const suffix = (axes && axes.y.suffix) || ''
|
||||
|
@ -55,6 +56,7 @@ const RefreshingGraph = ({
|
|||
if (type === 'single-stat') {
|
||||
return (
|
||||
<RefreshingSingleStat
|
||||
type={type}
|
||||
colors={colors}
|
||||
key={manualRefresh}
|
||||
queries={[queries[0]]}
|
||||
|
@ -71,6 +73,7 @@ const RefreshingGraph = ({
|
|||
if (type === 'gauge') {
|
||||
return (
|
||||
<RefreshingGaugeChart
|
||||
type={type}
|
||||
colors={colors}
|
||||
key={manualRefresh}
|
||||
queries={[queries[0]]}
|
||||
|
@ -90,6 +93,7 @@ const RefreshingGraph = ({
|
|||
if (type === 'table') {
|
||||
return (
|
||||
<RefreshingTableGraph
|
||||
type={type}
|
||||
cellID={cellID}
|
||||
colors={colors}
|
||||
inView={inView}
|
||||
|
@ -103,6 +107,7 @@ const RefreshingGraph = ({
|
|||
tableOptions={tableOptions}
|
||||
resizerTopHeight={resizerTopHeight}
|
||||
handleSetHoverTime={handleSetHoverTime}
|
||||
isInCEO={isInCEO}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -114,6 +119,7 @@ const RefreshingGraph = ({
|
|||
|
||||
return (
|
||||
<RefreshingLineGraph
|
||||
type={type}
|
||||
axes={axes}
|
||||
cellID={cellID}
|
||||
colors={colors}
|
||||
|
@ -161,6 +167,7 @@ RefreshingGraph.propTypes = {
|
|||
tableOptions: shape({}),
|
||||
hoverTime: string.isRequired,
|
||||
handleSetHoverTime: func.isRequired,
|
||||
isInCEO: bool,
|
||||
}
|
||||
|
||||
RefreshingGraph.defaultProps = {
|
||||
|
|
|
@ -2,26 +2,30 @@ import React, {Component} from 'react'
|
|||
import PropTypes from 'prop-types'
|
||||
import _ from 'lodash'
|
||||
import classnames from 'classnames'
|
||||
import {connect} from 'react-redux'
|
||||
|
||||
import {MultiGrid, ColumnSizer} from 'react-virtualized'
|
||||
import {bindActionCreators} from 'redux'
|
||||
import moment from 'moment'
|
||||
import {reduce} from 'fast.js'
|
||||
|
||||
const {arrayOf, bool, shape, string, func} = PropTypes
|
||||
|
||||
import {timeSeriesToTableGraph} from 'src/utils/timeSeriesTransformers'
|
||||
import {
|
||||
timeSeriesToTableGraph,
|
||||
processTableData,
|
||||
} from 'src/utils/timeSeriesTransformers'
|
||||
computeFieldNames,
|
||||
transformTableData,
|
||||
} from 'src/dashboards/utils/tableGraph'
|
||||
import {updateTableOptions} from 'src/dashboards/actions/cellEditorOverlay'
|
||||
|
||||
import {
|
||||
NULL_ARRAY_INDEX,
|
||||
NULL_HOVER_TIME,
|
||||
TIME_FORMAT_DEFAULT,
|
||||
DEFAULT_TIME_FORMAT,
|
||||
TIME_FIELD_DEFAULT,
|
||||
ASCENDING,
|
||||
DESCENDING,
|
||||
DEFAULT_SORT,
|
||||
DEFAULT_SORT_DIRECTION,
|
||||
FIX_FIRST_COLUMN_DEFAULT,
|
||||
VERTICAL_TIME_AXIS_DEFAULT,
|
||||
} from 'src/shared/constants/tableGraph'
|
||||
|
@ -40,70 +44,87 @@ class TableGraph extends Component {
|
|||
['tableOptions', 'sortBy', 'internalName'],
|
||||
TIME_FIELD_DEFAULT.internalName
|
||||
)
|
||||
|
||||
this.state = {
|
||||
data: [[]],
|
||||
processedData: [[]],
|
||||
transformedData: [[]],
|
||||
sortedTimeVals: [],
|
||||
sortedLabels: [],
|
||||
hoveredColumnIndex: NULL_ARRAY_INDEX,
|
||||
hoveredRowIndex: NULL_ARRAY_INDEX,
|
||||
sortField,
|
||||
sortDirection: DEFAULT_SORT,
|
||||
sort: {field: sortField, direction: DEFAULT_SORT_DIRECTION},
|
||||
columnWidths: {},
|
||||
totalColumnWidths: 0,
|
||||
}
|
||||
}
|
||||
|
||||
handleUpdateTableOptions = (fieldNames, tableOptions) => {
|
||||
const {isInCEO} = this.props
|
||||
if (!isInCEO) {
|
||||
return
|
||||
}
|
||||
this.props.handleUpdateTableOptions({...tableOptions, fieldNames})
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const {data} = timeSeriesToTableGraph(nextProps.data)
|
||||
const updatedProps = _.keys(nextProps).filter(
|
||||
k => !_.isEqual(this.props[k], nextProps[k])
|
||||
)
|
||||
const {tableOptions} = nextProps
|
||||
|
||||
let result = {}
|
||||
|
||||
if (_.includes(updatedProps, 'data')) {
|
||||
result = timeSeriesToTableGraph(nextProps.data, nextProps.queryASTs)
|
||||
}
|
||||
|
||||
const data = _.get(result, 'data', this.state.data)
|
||||
const sortedLabels = _.get(result, 'sortedLabels', this.state.sortedLabels)
|
||||
const fieldNames = computeFieldNames(tableOptions.fieldNames, sortedLabels)
|
||||
|
||||
if (_.includes(updatedProps, 'data')) {
|
||||
this.handleUpdateTableOptions(fieldNames, tableOptions)
|
||||
}
|
||||
|
||||
if (_.isEmpty(data[0])) {
|
||||
return
|
||||
}
|
||||
|
||||
const {sortField, sortDirection} = this.state
|
||||
const {
|
||||
tableOptions: {
|
||||
sortBy: {internalName},
|
||||
fieldNames,
|
||||
verticalTimeAxis,
|
||||
timeFormat,
|
||||
},
|
||||
} = nextProps
|
||||
|
||||
let direction, sortFieldName
|
||||
const {sort} = this.state
|
||||
const internalName = _.get(
|
||||
nextProps,
|
||||
['tableOptions', 'sortBy', 'internalName'],
|
||||
''
|
||||
)
|
||||
if (
|
||||
_.get(this.props, ['tableOptions', 'sortBy', 'internalName'], '') ===
|
||||
!_.get(this.props, ['tableOptions', 'sortBy', 'internalName'], '') ===
|
||||
internalName
|
||||
) {
|
||||
direction = sortDirection
|
||||
sortFieldName = sortField
|
||||
} else {
|
||||
direction = DEFAULT_SORT
|
||||
sortFieldName = internalName
|
||||
sort.direction = DEFAULT_SORT_DIRECTION
|
||||
sort.field = internalName
|
||||
}
|
||||
|
||||
const {
|
||||
processedData,
|
||||
sortedTimeVals,
|
||||
columnWidths,
|
||||
totalWidths,
|
||||
} = processTableData(
|
||||
data,
|
||||
sortFieldName,
|
||||
direction,
|
||||
verticalTimeAxis,
|
||||
fieldNames,
|
||||
timeFormat
|
||||
)
|
||||
if (
|
||||
_.includes(updatedProps, 'data') ||
|
||||
_.includes(updatedProps, 'tableOptions')
|
||||
) {
|
||||
const {
|
||||
transformedData,
|
||||
sortedTimeVals,
|
||||
columnWidths,
|
||||
totalWidths,
|
||||
} = transformTableData(data, sort, fieldNames, tableOptions)
|
||||
|
||||
this.setState({
|
||||
data,
|
||||
processedData,
|
||||
sortedTimeVals,
|
||||
sortField: sortFieldName,
|
||||
sortDirection: direction,
|
||||
columnWidths,
|
||||
totalColumnWidths: totalWidths,
|
||||
})
|
||||
this.setState({
|
||||
data,
|
||||
sortedLabels,
|
||||
transformedData,
|
||||
sortedTimeVals,
|
||||
sort,
|
||||
columnWidths,
|
||||
totalColumnWidths: totalWidths,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
calcScrollToColRow = () => {
|
||||
|
@ -167,6 +188,7 @@ class TableGraph extends Component {
|
|||
|
||||
handleClickFieldName = fieldName => () => {
|
||||
const {tableOptions} = this.props
|
||||
const {timeFormat} = tableOptions
|
||||
const {data, sortField, sortDirection} = this.state
|
||||
const verticalTimeAxis = _.get(tableOptions, 'verticalTimeAxis', true)
|
||||
const fieldNames = _.get(tableOptions, 'fieldNames', [TIME_FIELD_DEFAULT])
|
||||
|
@ -175,19 +197,20 @@ class TableGraph extends Component {
|
|||
if (fieldName === sortField) {
|
||||
direction = sortDirection === ASCENDING ? DESCENDING : ASCENDING
|
||||
} else {
|
||||
direction = DEFAULT_SORT
|
||||
direction = DEFAULT_SORT_DIRECTION
|
||||
}
|
||||
|
||||
const {processedData, sortedTimeVals} = processTableData(
|
||||
const {transformedData, sortedTimeVals} = transformTableData(
|
||||
data,
|
||||
fieldName,
|
||||
direction,
|
||||
verticalTimeAxis,
|
||||
fieldNames
|
||||
fieldNames,
|
||||
timeFormat
|
||||
)
|
||||
|
||||
this.setState({
|
||||
processedData,
|
||||
transformedData,
|
||||
sortedTimeVals,
|
||||
sortField: fieldName,
|
||||
sortDirection: direction,
|
||||
|
@ -199,9 +222,9 @@ class TableGraph extends Component {
|
|||
const {
|
||||
tableOptions: {fixFirstColumn},
|
||||
} = this.props
|
||||
const {processedData, columnWidths, totalColumnWidths} = this.state
|
||||
const columnCount = _.get(processedData, ['0', 'length'], 0)
|
||||
const columnLabel = processedData[0][index]
|
||||
const {transformedData, columnWidths, totalColumnWidths} = this.state
|
||||
const columnCount = _.get(transformedData, ['0', 'length'], 0)
|
||||
const columnLabel = transformedData[0][index]
|
||||
|
||||
let adjustedColumnSizerWidth = columnWidths[columnLabel]
|
||||
|
||||
|
@ -227,20 +250,20 @@ class TableGraph extends Component {
|
|||
const {
|
||||
hoveredColumnIndex,
|
||||
hoveredRowIndex,
|
||||
processedData,
|
||||
transformedData,
|
||||
sortField,
|
||||
sortDirection,
|
||||
} = this.state
|
||||
const {tableOptions, colors} = parent.props
|
||||
|
||||
const {
|
||||
timeFormat = TIME_FORMAT_DEFAULT,
|
||||
timeFormat = DEFAULT_TIME_FORMAT,
|
||||
verticalTimeAxis = VERTICAL_TIME_AXIS_DEFAULT,
|
||||
fixFirstColumn = FIX_FIRST_COLUMN_DEFAULT,
|
||||
fieldNames = [TIME_FIELD_DEFAULT],
|
||||
} = tableOptions
|
||||
|
||||
const cellData = processedData[rowIndex][columnIndex]
|
||||
const cellData = transformedData[rowIndex][columnIndex]
|
||||
|
||||
const timeFieldIndex = fieldNames.findIndex(
|
||||
field => field.internalName === TIME_FIELD_DEFAULT.internalName
|
||||
|
@ -302,7 +325,7 @@ class TableGraph extends Component {
|
|||
|
||||
const cellContents = isTimeData
|
||||
? `${moment(cellData).format(
|
||||
timeFormat === '' ? TIME_FORMAT_DEFAULT : timeFormat
|
||||
timeFormat === '' ? DEFAULT_TIME_FORMAT : timeFormat
|
||||
)}`
|
||||
: fieldName || `${cellData}`
|
||||
|
||||
|
@ -332,12 +355,12 @@ class TableGraph extends Component {
|
|||
timeColumnWidth,
|
||||
sortField,
|
||||
sortDirection,
|
||||
processedData,
|
||||
transformedData,
|
||||
} = this.state
|
||||
const {hoverTime, tableOptions, colors} = this.props
|
||||
const {fixFirstColumn = FIX_FIRST_COLUMN_DEFAULT} = tableOptions
|
||||
const columnCount = _.get(processedData, ['0', 'length'], 0)
|
||||
const rowCount = columnCount === 0 ? 0 : processedData.length
|
||||
const columnCount = _.get(transformedData, ['0', 'length'], 0)
|
||||
const rowCount = columnCount === 0 ? 0 : transformedData.length
|
||||
|
||||
const COLUMN_MIN_WIDTH = 100
|
||||
const COLUMN_MAX_WIDTH = 1000
|
||||
|
@ -348,7 +371,6 @@ class TableGraph extends Component {
|
|||
const tableWidth = _.get(this, ['gridContainer', 'clientWidth'], 0)
|
||||
const tableHeight = _.get(this, ['gridContainer', 'clientHeight'], 0)
|
||||
const {scrollToColumn, scrollToRow} = this.calcScrollToColRow()
|
||||
|
||||
return (
|
||||
<div
|
||||
className="table-graph-container"
|
||||
|
@ -417,8 +439,15 @@ TableGraph.propTypes = {
|
|||
fixFirstColumn: bool,
|
||||
}),
|
||||
hoverTime: string,
|
||||
handleUpdateTableOptions: func,
|
||||
handleSetHoverTime: func,
|
||||
colors: colorsStringSchema,
|
||||
queryASTs: arrayOf(shape()),
|
||||
isInCEO: bool,
|
||||
}
|
||||
|
||||
export default TableGraph
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleUpdateTableOptions: bindActionCreators(updateTableOptions, dispatch),
|
||||
})
|
||||
|
||||
export default connect(null, mapDispatchToProps)(TableGraph)
|
||||
|
|
|
@ -13,18 +13,18 @@ export const TIME_FIELD_DEFAULT = {
|
|||
|
||||
export const ASCENDING = 'asc'
|
||||
export const DESCENDING = 'desc'
|
||||
export const DEFAULT_SORT = ASCENDING
|
||||
export const DEFAULT_SORT_DIRECTION = ASCENDING
|
||||
|
||||
export const FIX_FIRST_COLUMN_DEFAULT = true
|
||||
export const VERTICAL_TIME_AXIS_DEFAULT = true
|
||||
|
||||
export const CELL_HORIZONTAL_PADDING = 30
|
||||
|
||||
export const TIME_FORMAT_DEFAULT = 'MM/DD/YYYY HH:mm:ss'
|
||||
export const DEFAULT_TIME_FORMAT = 'MM/DD/YYYY HH:mm:ss'
|
||||
export const TIME_FORMAT_CUSTOM = 'Custom'
|
||||
|
||||
export const FORMAT_OPTIONS = [
|
||||
{text: TIME_FORMAT_DEFAULT},
|
||||
{text: DEFAULT_TIME_FORMAT},
|
||||
{text: 'MM/DD/YYYY HH:mm:ss.SSS'},
|
||||
{text: 'YYYY-MM-DD HH:mm:ss'},
|
||||
{text: 'HH:mm:ss'},
|
||||
|
@ -36,7 +36,7 @@ export const FORMAT_OPTIONS = [
|
|||
|
||||
export const DEFAULT_TABLE_OPTIONS = {
|
||||
verticalTimeAxis: VERTICAL_TIME_AXIS_DEFAULT,
|
||||
timeFormat: TIME_FORMAT_DEFAULT,
|
||||
timeFormat: DEFAULT_TIME_FORMAT,
|
||||
sortBy: TIME_FIELD_DEFAULT,
|
||||
wrapping: 'truncate',
|
||||
fieldNames: [TIME_FIELD_DEFAULT],
|
||||
|
|
|
@ -0,0 +1,242 @@
|
|||
import _ from 'lodash'
|
||||
import {shiftDate} from 'shared/query/helpers'
|
||||
import {map, reduce, forEach, concat, clone} from 'fast.js'
|
||||
|
||||
const groupByMap = (responses, responseIndex, groupByColumns) => {
|
||||
const firstColumns = _.get(responses, [0, 'series', 0, 'columns'])
|
||||
const accum = [
|
||||
{
|
||||
responseIndex,
|
||||
series: [
|
||||
{
|
||||
columns: [
|
||||
firstColumns[0],
|
||||
...groupByColumns,
|
||||
...firstColumns.slice(1),
|
||||
],
|
||||
groupByColumns,
|
||||
name: _.get(responses, [0, 'series', 0, 'name']),
|
||||
values: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const seriesArray = _.get(responses, [0, 'series'])
|
||||
seriesArray.forEach(s => {
|
||||
const prevValues = accum[0].series[0].values
|
||||
const tagsToAdd = groupByColumns.map(gb => s.tags[gb])
|
||||
const newValues = s.values.map(v => [v[0], ...tagsToAdd, ...v.slice(1)])
|
||||
accum[0].series[0].values = [...prevValues, ...newValues]
|
||||
})
|
||||
return accum
|
||||
}
|
||||
|
||||
const constructResults = (raw, groupBys) => {
|
||||
return _.flatten(
|
||||
map(raw, (response, index) => {
|
||||
const responses = _.get(response, 'response.results', [])
|
||||
|
||||
if (groupBys[index]) {
|
||||
return groupByMap(responses, index, groupBys[index])
|
||||
}
|
||||
|
||||
return map(responses, r => ({...r, responseIndex: index}))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const constructSerieses = results => {
|
||||
return reduce(
|
||||
results,
|
||||
(acc, {series = [], responseIndex}) => {
|
||||
return [
|
||||
...acc,
|
||||
...map(series, (item, index) => ({
|
||||
...item,
|
||||
responseIndex,
|
||||
seriesIndex: index,
|
||||
})),
|
||||
]
|
||||
},
|
||||
[]
|
||||
)
|
||||
}
|
||||
|
||||
const constructCells = serieses => {
|
||||
let cellIndex = 0
|
||||
let labels = []
|
||||
const seriesLabels = []
|
||||
const cells = {
|
||||
label: [],
|
||||
value: [],
|
||||
time: [],
|
||||
seriesIndex: [],
|
||||
responseIndex: [],
|
||||
}
|
||||
forEach(
|
||||
serieses,
|
||||
(
|
||||
{
|
||||
name: measurement,
|
||||
columns,
|
||||
groupByColumns,
|
||||
values,
|
||||
seriesIndex,
|
||||
responseIndex,
|
||||
tags = {},
|
||||
},
|
||||
ind
|
||||
) => {
|
||||
const rows = map(values || [], vals => ({
|
||||
vals,
|
||||
}))
|
||||
|
||||
const unsortedLabels = map(columns.slice(1), (field, i) => ({
|
||||
label:
|
||||
groupByColumns && i <= groupByColumns.length - 1
|
||||
? `${field}`
|
||||
: `${measurement}.${field}`,
|
||||
responseIndex,
|
||||
seriesIndex,
|
||||
}))
|
||||
seriesLabels[ind] = unsortedLabels
|
||||
labels = concat(labels, unsortedLabels)
|
||||
|
||||
forEach(rows, ({vals}) => {
|
||||
const [time, ...rowValues] = vals
|
||||
forEach(rowValues, (value, i) => {
|
||||
cells.label[cellIndex] = unsortedLabels[i].label
|
||||
cells.value[cellIndex] = value
|
||||
cells.time[cellIndex] = time
|
||||
cells.seriesIndex[cellIndex] = seriesIndex
|
||||
cells.responseIndex[cellIndex] = responseIndex
|
||||
cellIndex++ // eslint-disable-line no-plusplus
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
const sortedLabels = _.sortBy(labels, 'label')
|
||||
return {cells, sortedLabels, seriesLabels}
|
||||
}
|
||||
|
||||
const insertGroupByValues = (
|
||||
serieses,
|
||||
groupBys,
|
||||
seriesLabels,
|
||||
labelsToValueIndex,
|
||||
sortedLabels
|
||||
) => {
|
||||
const dashArray = Array(sortedLabels.length).fill('-')
|
||||
const timeSeries = []
|
||||
let existingRowIndex
|
||||
forEach(serieses, (s, sind) => {
|
||||
if (groupBys[s.responseIndex]) {
|
||||
forEach(s.values, vs => {
|
||||
timeSeries.push({time: vs[0], values: clone(dashArray)})
|
||||
existingRowIndex = timeSeries.length - 1
|
||||
forEach(vs.slice(1), (v, i) => {
|
||||
const label = seriesLabels[sind][i].label
|
||||
timeSeries[existingRowIndex].values[
|
||||
labelsToValueIndex[label + s.responseIndex + s.seriesIndex]
|
||||
] = v
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return timeSeries
|
||||
}
|
||||
|
||||
const constructTimeSeries = (
|
||||
serieses,
|
||||
cells,
|
||||
sortedLabels,
|
||||
groupBys,
|
||||
seriesLabels
|
||||
) => {
|
||||
const nullArray = Array(sortedLabels.length).fill(null)
|
||||
|
||||
const labelsToValueIndex = reduce(
|
||||
sortedLabels,
|
||||
(acc, {label, responseIndex, seriesIndex}, i) => {
|
||||
// adding series index prevents overwriting of two distinct labels that have the same field and measurements
|
||||
acc[label + responseIndex + seriesIndex] = i
|
||||
return acc
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
||||
const tsMemo = {}
|
||||
|
||||
const timeSeries = insertGroupByValues(
|
||||
serieses,
|
||||
groupBys,
|
||||
seriesLabels,
|
||||
labelsToValueIndex,
|
||||
sortedLabels
|
||||
)
|
||||
|
||||
let existingRowIndex
|
||||
|
||||
for (let i = 0; i < _.get(cells, ['value', 'length'], 0); i++) {
|
||||
let time
|
||||
time = cells.time[i]
|
||||
const value = cells.value[i]
|
||||
const label = cells.label[i]
|
||||
const seriesIndex = cells.seriesIndex[i]
|
||||
const responseIndex = cells.responseIndex[i]
|
||||
|
||||
if (groupBys[cells.responseIndex[i]]) {
|
||||
// we've already inserted GroupByValues
|
||||
continue
|
||||
}
|
||||
|
||||
if (label.includes('_shifted__')) {
|
||||
const [, quantity, duration] = label.split('__')
|
||||
time = +shiftDate(time, quantity, duration).format('x')
|
||||
}
|
||||
|
||||
existingRowIndex = tsMemo[time]
|
||||
|
||||
if (existingRowIndex === undefined) {
|
||||
timeSeries.push({
|
||||
time,
|
||||
values: clone(nullArray),
|
||||
})
|
||||
|
||||
existingRowIndex = timeSeries.length - 1
|
||||
tsMemo[time] = existingRowIndex
|
||||
}
|
||||
|
||||
timeSeries[existingRowIndex].values[
|
||||
labelsToValueIndex[label + responseIndex + seriesIndex]
|
||||
] = value
|
||||
}
|
||||
|
||||
return _.sortBy(timeSeries, 'time')
|
||||
}
|
||||
|
||||
export const groupByTimeSeriesTransform = (raw, groupBys) => {
|
||||
if (!groupBys) {
|
||||
groupBys = Array(raw.length).fill(false)
|
||||
}
|
||||
const results = constructResults(raw, groupBys)
|
||||
|
||||
const serieses = constructSerieses(results)
|
||||
|
||||
const {cells, sortedLabels, seriesLabels} = constructCells(serieses)
|
||||
|
||||
const sortedTimeSeries = constructTimeSeries(
|
||||
serieses,
|
||||
cells,
|
||||
sortedLabels,
|
||||
groupBys,
|
||||
seriesLabels
|
||||
)
|
||||
|
||||
return {
|
||||
sortedLabels,
|
||||
sortedTimeSeries,
|
||||
}
|
||||
}
|
|
@ -1,153 +1,9 @@
|
|||
import _ from 'lodash'
|
||||
import {shiftDate} from 'shared/query/helpers'
|
||||
import {map, reduce, filter, forEach, concat, clone} from 'fast.js'
|
||||
import {calculateColumnWidths} from 'src/dashboards/utils/tableGraph'
|
||||
|
||||
/**
|
||||
* Accepts an array of raw influxdb responses and returns a format
|
||||
* that Dygraph understands.
|
||||
**/
|
||||
|
||||
const DEFAULT_SIZE = 0
|
||||
const cells = {
|
||||
label: new Array(DEFAULT_SIZE),
|
||||
value: new Array(DEFAULT_SIZE),
|
||||
time: new Array(DEFAULT_SIZE),
|
||||
seriesIndex: new Array(DEFAULT_SIZE),
|
||||
responseIndex: new Array(DEFAULT_SIZE),
|
||||
}
|
||||
|
||||
const timeSeriesTransform = (raw = []) => {
|
||||
// collect results from each influx response
|
||||
const results = reduce(
|
||||
raw,
|
||||
(acc, rawResponse, responseIndex) => {
|
||||
const responses = _.get(rawResponse, 'response.results', [])
|
||||
const indexedResponses = map(responses, response => ({
|
||||
...response,
|
||||
responseIndex,
|
||||
}))
|
||||
return [...acc, ...indexedResponses]
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
// collect each series
|
||||
const serieses = reduce(
|
||||
results,
|
||||
(acc, {series = [], responseIndex}, index) => {
|
||||
return [...acc, ...map(series, item => ({...item, responseIndex, index}))]
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const size = reduce(
|
||||
serieses,
|
||||
(acc, {columns, values}) => {
|
||||
if (columns.length && (values && values.length)) {
|
||||
return acc + (columns.length - 1) * values.length
|
||||
}
|
||||
return acc
|
||||
},
|
||||
0
|
||||
)
|
||||
|
||||
// convert series into cells with rows and columns
|
||||
let cellIndex = 0
|
||||
let labels = []
|
||||
|
||||
forEach(
|
||||
serieses,
|
||||
({
|
||||
name: measurement,
|
||||
columns,
|
||||
values,
|
||||
index: seriesIndex,
|
||||
responseIndex,
|
||||
tags = {},
|
||||
}) => {
|
||||
const rows = map(values || [], vals => ({
|
||||
vals,
|
||||
}))
|
||||
|
||||
// tagSet is each tag key and value for a series
|
||||
const tagSet = map(Object.keys(tags), tag => `[${tag}=${tags[tag]}]`)
|
||||
.sort()
|
||||
.join('')
|
||||
const unsortedLabels = map(columns.slice(1), field => ({
|
||||
label: `${measurement}.${field}${tagSet}`,
|
||||
responseIndex,
|
||||
seriesIndex,
|
||||
}))
|
||||
labels = concat(labels, unsortedLabels)
|
||||
|
||||
forEach(rows, ({vals}) => {
|
||||
const [time, ...rowValues] = vals
|
||||
|
||||
forEach(rowValues, (value, i) => {
|
||||
cells.label[cellIndex] = unsortedLabels[i].label
|
||||
cells.value[cellIndex] = value
|
||||
cells.time[cellIndex] = time
|
||||
cells.seriesIndex[cellIndex] = seriesIndex
|
||||
cells.responseIndex[cellIndex] = responseIndex
|
||||
cellIndex++ // eslint-disable-line no-plusplus
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const sortedLabels = _.sortBy(labels, 'label')
|
||||
const tsMemo = {}
|
||||
const nullArray = Array(sortedLabels.length).fill(null)
|
||||
|
||||
const labelsToValueIndex = reduce(
|
||||
sortedLabels,
|
||||
(acc, {label, seriesIndex}, i) => {
|
||||
// adding series index prevents overwriting of two distinct labels that have the same field and measurements
|
||||
acc[label + seriesIndex] = i
|
||||
return acc
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
||||
const timeSeries = []
|
||||
for (let i = 0; i < size; i++) {
|
||||
let time = cells.time[i]
|
||||
const value = cells.value[i]
|
||||
const label = cells.label[i]
|
||||
const seriesIndex = cells.seriesIndex[i]
|
||||
|
||||
if (label.includes('_shifted__')) {
|
||||
const [, quantity, duration] = label.split('__')
|
||||
time = +shiftDate(time, quantity, duration).format('x')
|
||||
}
|
||||
|
||||
let existingRowIndex = tsMemo[time]
|
||||
|
||||
if (existingRowIndex === undefined) {
|
||||
timeSeries.push({
|
||||
time,
|
||||
values: clone(nullArray),
|
||||
})
|
||||
|
||||
existingRowIndex = timeSeries.length - 1
|
||||
tsMemo[time] = existingRowIndex
|
||||
}
|
||||
|
||||
timeSeries[existingRowIndex].values[
|
||||
labelsToValueIndex[label + seriesIndex]
|
||||
] = value
|
||||
}
|
||||
const sortedTimeSeries = _.sortBy(timeSeries, 'time')
|
||||
|
||||
return {
|
||||
sortedLabels,
|
||||
sortedTimeSeries,
|
||||
}
|
||||
}
|
||||
import {map, reduce} from 'fast.js'
|
||||
import {groupByTimeSeriesTransform} from 'src/utils/groupBy.js'
|
||||
|
||||
export const timeSeriesToDygraph = (raw = [], isInDataExplorer) => {
|
||||
const {sortedLabels, sortedTimeSeries} = timeSeriesTransform(raw)
|
||||
const {sortedLabels, sortedTimeSeries} = groupByTimeSeriesTransform(raw)
|
||||
|
||||
const dygraphSeries = reduce(
|
||||
sortedLabels,
|
||||
|
@ -172,8 +28,17 @@ export const timeSeriesToDygraph = (raw = [], isInDataExplorer) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const timeSeriesToTableGraph = raw => {
|
||||
const {sortedLabels, sortedTimeSeries} = timeSeriesTransform(raw)
|
||||
const computeGroupBys = queryASTs => {
|
||||
return queryASTs.map(queryAST => {
|
||||
return _.get(queryAST, ['groupBy', 'tags'], false)
|
||||
})
|
||||
}
|
||||
|
||||
export const timeSeriesToTableGraph = (raw, queryASTs) => {
|
||||
const {sortedLabels, sortedTimeSeries} = groupByTimeSeriesTransform(
|
||||
raw,
|
||||
computeGroupBys(queryASTs)
|
||||
)
|
||||
|
||||
const labels = ['time', ...map(sortedLabels, ({label}) => label)]
|
||||
|
||||
|
@ -181,60 +46,6 @@ export const timeSeriesToTableGraph = raw => {
|
|||
const data = tableData.length ? [labels, ...tableData] : [[]]
|
||||
return {
|
||||
data,
|
||||
sortedLabels,
|
||||
}
|
||||
}
|
||||
|
||||
export const filterTableColumns = (data, fieldNames) => {
|
||||
const visibility = {}
|
||||
const filteredData = map(data, (row, i) => {
|
||||
return filter(row, (col, j) => {
|
||||
if (i === 0) {
|
||||
const foundField = fieldNames.find(field => field.internalName === col)
|
||||
visibility[j] = foundField ? foundField.visible : true
|
||||
}
|
||||
return visibility[j]
|
||||
})
|
||||
})
|
||||
return filteredData[0].length ? filteredData : [[]]
|
||||
}
|
||||
|
||||
export const orderTableColumns = (data, fieldNames) => {
|
||||
const fieldsSortOrder = fieldNames.map(fieldName => {
|
||||
return _.findIndex(data[0], dataLabel => {
|
||||
return dataLabel === fieldName.internalName
|
||||
})
|
||||
})
|
||||
const filteredFieldSortOrder = filter(fieldsSortOrder, f => f !== -1)
|
||||
const orderedData = map(data, row => {
|
||||
return row.map((v, j, arr) => arr[filteredFieldSortOrder[j]])
|
||||
})
|
||||
return orderedData[0].length ? orderedData : [[]]
|
||||
}
|
||||
|
||||
export const processTableData = (
|
||||
data,
|
||||
sortFieldName,
|
||||
direction,
|
||||
verticalTimeAxis,
|
||||
fieldNames,
|
||||
timeFormat
|
||||
) => {
|
||||
const sortIndex = _.indexOf(data[0], sortFieldName)
|
||||
const sortedData = [
|
||||
data[0],
|
||||
..._.orderBy(_.drop(data, 1), sortIndex, [direction]),
|
||||
]
|
||||
const sortedTimeVals = map(sortedData, r => r[0])
|
||||
const filteredData = filterTableColumns(sortedData, fieldNames)
|
||||
const orderedData = orderTableColumns(filteredData, fieldNames)
|
||||
const processedData = verticalTimeAxis ? orderedData : _.unzip(orderedData)
|
||||
const {widths: columnWidths, totalWidths} = calculateColumnWidths(
|
||||
processedData,
|
||||
fieldNames,
|
||||
timeFormat,
|
||||
verticalTimeAxis
|
||||
)
|
||||
return {processedData, sortedTimeVals, columnWidths, totalWidths}
|
||||
}
|
||||
|
||||
export default timeSeriesToDygraph
|
||||
|
|
|
@ -22,6 +22,7 @@ const defaultProps = {
|
|||
timeFormat: '',
|
||||
verticalTimeAxis: true,
|
||||
},
|
||||
queryASTs: [],
|
||||
}
|
||||
|
||||
const setup = (override = {}) => {
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
import timeSeriesToDygraph, {
|
||||
import {
|
||||
timeSeriesToDygraph,
|
||||
timeSeriesToTableGraph,
|
||||
filterTableColumns,
|
||||
processTableData,
|
||||
} from 'src/utils/timeSeriesTransformers'
|
||||
import {DEFAULT_SORT} from 'src/shared/constants/tableGraph'
|
||||
|
||||
import {
|
||||
filterTableColumns,
|
||||
transformTableData,
|
||||
} from 'src/dashboards/utils/tableGraph'
|
||||
|
||||
import {
|
||||
DEFAULT_SORT_DIRECTION,
|
||||
DEFAULT_TIME_FORMAT,
|
||||
} from 'src/shared/constants/tableGraph'
|
||||
|
||||
describe('timeSeriesToDygraph', () => {
|
||||
it('parses a raw InfluxDB response into a dygraph friendly data format', () => {
|
||||
|
@ -178,7 +186,6 @@ describe('timeSeriesToDygraph', () => {
|
|||
},
|
||||
},
|
||||
]
|
||||
|
||||
const actual = timeSeriesToDygraph(influxResponse)
|
||||
|
||||
const expected = {
|
||||
|
@ -341,13 +348,45 @@ describe('timeSeriesToTableGraph', () => {
|
|||
},
|
||||
]
|
||||
|
||||
const actual = timeSeriesToTableGraph(influxResponse)
|
||||
const qASTs = [
|
||||
{
|
||||
groupBy: {
|
||||
time: {
|
||||
interval: '2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
groupBy: {
|
||||
time: {
|
||||
interval: '2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
groupBy: {
|
||||
time: {
|
||||
interval: '2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
groupBy: {
|
||||
time: {
|
||||
interval: '2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const actual = timeSeriesToTableGraph(influxResponse, qASTs)
|
||||
const expected = [
|
||||
['time', 'ma.f1', 'mb.f1', 'mc.f1', 'mc.f2'],
|
||||
[1000, 1, 1, null, null],
|
||||
[2000, 2, 2, 3, 3],
|
||||
[4000, null, null, 4, 4],
|
||||
]
|
||||
|
||||
expect(actual.data).toEqual(expected)
|
||||
})
|
||||
|
||||
|
@ -397,7 +436,38 @@ describe('timeSeriesToTableGraph', () => {
|
|||
},
|
||||
]
|
||||
|
||||
const actual = timeSeriesToTableGraph(influxResponse)
|
||||
const qASTs = [
|
||||
{
|
||||
groupBy: {
|
||||
time: {
|
||||
interval: '2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
groupBy: {
|
||||
time: {
|
||||
interval: '2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
groupBy: {
|
||||
time: {
|
||||
interval: '2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
groupBy: {
|
||||
time: {
|
||||
interval: '2s',
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const actual = timeSeriesToTableGraph(influxResponse, qASTs)
|
||||
const expected = ['time', 'ma.f1', 'mb.f1', 'mc.f1', 'mc.f2']
|
||||
|
||||
expect(actual.data[0]).toEqual(expected)
|
||||
|
@ -406,7 +476,9 @@ describe('timeSeriesToTableGraph', () => {
|
|||
it('returns an array of an empty array if there is an empty response', () => {
|
||||
const influxResponse = []
|
||||
|
||||
const actual = timeSeriesToTableGraph(influxResponse)
|
||||
const qASTs = []
|
||||
|
||||
const actual = timeSeriesToTableGraph(influxResponse, qASTs)
|
||||
const expected = [[]]
|
||||
|
||||
expect(actual.data).toEqual(expected)
|
||||
|
@ -453,7 +525,7 @@ describe('filterTableColumns', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('processTableData', () => {
|
||||
describe('transformTableData', () => {
|
||||
it('sorts the data based on the provided sortFieldName', () => {
|
||||
const data = [
|
||||
['time', 'f1', 'f2'],
|
||||
|
@ -461,9 +533,11 @@ describe('processTableData', () => {
|
|||
[2000, 1000, 3000],
|
||||
[3000, 2000, 1000],
|
||||
]
|
||||
const sortFieldName = 'f1'
|
||||
const direction = DEFAULT_SORT
|
||||
const verticalTimeAxis = true
|
||||
const sort = {field: 'f1', direction: DEFAULT_SORT_DIRECTION}
|
||||
const tableOptions = {
|
||||
verticalTimeAxis: true,
|
||||
timeFormat: DEFAULT_TIME_FORMAT,
|
||||
}
|
||||
|
||||
const fieldNames = [
|
||||
{internalName: 'time', displayName: 'Time', visible: true},
|
||||
|
@ -471,13 +545,7 @@ describe('processTableData', () => {
|
|||
{internalName: 'f2', displayName: 'F2', visible: true},
|
||||
]
|
||||
|
||||
const actual = processTableData(
|
||||
data,
|
||||
sortFieldName,
|
||||
direction,
|
||||
verticalTimeAxis,
|
||||
fieldNames
|
||||
)
|
||||
const actual = transformTableData(data, sort, fieldNames, tableOptions)
|
||||
const expected = [
|
||||
['time', 'f1', 'f2'],
|
||||
[2000, 1000, 3000],
|
||||
|
@ -485,7 +553,7 @@ describe('processTableData', () => {
|
|||
[1000, 3000, 2000],
|
||||
]
|
||||
|
||||
expect(actual.processedData).toEqual(expected)
|
||||
expect(actual.transformedData).toEqual(expected)
|
||||
})
|
||||
|
||||
it('filters out columns that should not be visible', () => {
|
||||
|
@ -495,9 +563,13 @@ describe('processTableData', () => {
|
|||
[2000, 1000, 3000],
|
||||
[3000, 2000, 1000],
|
||||
]
|
||||
const sortFieldName = 'time'
|
||||
const direction = DEFAULT_SORT
|
||||
const verticalTimeAxis = true
|
||||
|
||||
const sort = {field: 'time', direction: DEFAULT_SORT_DIRECTION}
|
||||
|
||||
const tableOptions = {
|
||||
verticalTimeAxis: true,
|
||||
timeFormat: DEFAULT_TIME_FORMAT,
|
||||
}
|
||||
|
||||
const fieldNames = [
|
||||
{internalName: 'time', displayName: 'Time', visible: true},
|
||||
|
@ -505,16 +577,11 @@ describe('processTableData', () => {
|
|||
{internalName: 'f2', displayName: 'F2', visible: true},
|
||||
]
|
||||
|
||||
const actual = processTableData(
|
||||
data,
|
||||
sortFieldName,
|
||||
direction,
|
||||
verticalTimeAxis,
|
||||
fieldNames
|
||||
)
|
||||
const actual = transformTableData(data, sort, fieldNames, tableOptions)
|
||||
|
||||
const expected = [['time', 'f2'], [1000, 2000], [2000, 3000], [3000, 1000]]
|
||||
|
||||
expect(actual.processedData).toEqual(expected)
|
||||
expect(actual.transformedData).toEqual(expected)
|
||||
})
|
||||
|
||||
it('filters out invisible columns after sorting', () => {
|
||||
|
@ -524,9 +591,13 @@ describe('processTableData', () => {
|
|||
[2000, 1000, 3000],
|
||||
[3000, 2000, 1000],
|
||||
]
|
||||
const sortFieldName = 'f1'
|
||||
const direction = DEFAULT_SORT
|
||||
const verticalTimeAxis = true
|
||||
|
||||
const sort = {field: 'f1', direction: DEFAULT_SORT_DIRECTION}
|
||||
|
||||
const tableOptions = {
|
||||
verticalTimeAxis: true,
|
||||
timeFormat: DEFAULT_TIME_FORMAT,
|
||||
}
|
||||
|
||||
const fieldNames = [
|
||||
{internalName: 'time', displayName: 'Time', visible: true},
|
||||
|
@ -534,16 +605,11 @@ describe('processTableData', () => {
|
|||
{internalName: 'f2', displayName: 'F2', visible: true},
|
||||
]
|
||||
|
||||
const actual = processTableData(
|
||||
data,
|
||||
sortFieldName,
|
||||
direction,
|
||||
verticalTimeAxis,
|
||||
fieldNames
|
||||
)
|
||||
const actual = transformTableData(data, sort, fieldNames, tableOptions)
|
||||
|
||||
const expected = [['time', 'f2'], [2000, 3000], [3000, 1000], [1000, 2000]]
|
||||
|
||||
expect(actual.processedData).toEqual(expected)
|
||||
expect(actual.transformedData).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -555,9 +621,13 @@ describe('if verticalTimeAxis is false', () => {
|
|||
[2000, 1000, 3000],
|
||||
[3000, 2000, 1000],
|
||||
]
|
||||
const sortFieldName = 'time'
|
||||
const direction = DEFAULT_SORT
|
||||
const verticalTimeAxis = false
|
||||
|
||||
const sort = {field: 'time', direction: DEFAULT_SORT_DIRECTION}
|
||||
|
||||
const tableOptions = {
|
||||
verticalTimeAxis: false,
|
||||
timeFormat: DEFAULT_TIME_FORMAT,
|
||||
}
|
||||
|
||||
const fieldNames = [
|
||||
{internalName: 'time', displayName: 'Time', visible: true},
|
||||
|
@ -565,20 +635,15 @@ describe('if verticalTimeAxis is false', () => {
|
|||
{internalName: 'f2', displayName: 'F2', visible: true},
|
||||
]
|
||||
|
||||
const actual = processTableData(
|
||||
data,
|
||||
sortFieldName,
|
||||
direction,
|
||||
verticalTimeAxis,
|
||||
fieldNames
|
||||
)
|
||||
const actual = transformTableData(data, sort, fieldNames, tableOptions)
|
||||
|
||||
const expected = [
|
||||
['time', 1000, 2000, 3000],
|
||||
['f1', 3000, 1000, 2000],
|
||||
['f2', 2000, 3000, 1000],
|
||||
]
|
||||
|
||||
expect(actual.processedData).toEqual(expected)
|
||||
expect(actual.transformedData).toEqual(expected)
|
||||
})
|
||||
|
||||
it('transforms data after filtering out invisible columns', () => {
|
||||
|
@ -588,9 +653,13 @@ describe('if verticalTimeAxis is false', () => {
|
|||
[2000, 1000, 3000],
|
||||
[3000, 2000, 1000],
|
||||
]
|
||||
const sortFieldName = 'f1'
|
||||
const direction = DEFAULT_SORT
|
||||
const verticalTimeAxis = false
|
||||
|
||||
const sort = {field: 'f1', direction: DEFAULT_SORT_DIRECTION}
|
||||
|
||||
const tableOptions = {
|
||||
verticalTimeAxis: false,
|
||||
timeFormat: DEFAULT_TIME_FORMAT,
|
||||
}
|
||||
|
||||
const fieldNames = [
|
||||
{internalName: 'time', displayName: 'Time', visible: true},
|
||||
|
@ -598,15 +667,10 @@ describe('if verticalTimeAxis is false', () => {
|
|||
{internalName: 'f2', displayName: 'F2', visible: true},
|
||||
]
|
||||
|
||||
const actual = processTableData(
|
||||
data,
|
||||
sortFieldName,
|
||||
direction,
|
||||
verticalTimeAxis,
|
||||
fieldNames
|
||||
)
|
||||
const actual = transformTableData(data, sort, fieldNames, tableOptions)
|
||||
|
||||
const expected = [['time', 2000, 3000, 1000], ['f2', 3000, 1000, 2000]]
|
||||
|
||||
expect(actual.processedData).toEqual(expected)
|
||||
expect(actual.transformedData).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue