commit
d9653fd3f0
|
@ -212,7 +212,6 @@
|
|||
'react/jsx-boolean-value': [2, 'always'],
|
||||
'react/jsx-curly-spacing': [2, 'never'],
|
||||
'react/jsx-equals-spacing': [2, 'never'],
|
||||
'react/jsx-indent-props': [2, 2],
|
||||
'react/jsx-key': 2,
|
||||
'react/jsx-no-duplicate-props': 2,
|
||||
'react/jsx-no-undef': 2,
|
||||
|
|
|
@ -3,26 +3,17 @@ import React, {PropTypes} from 'react'
|
|||
import Dimensions from 'react-dimensions'
|
||||
import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import Dropdown from 'shared/components/Dropdown'
|
||||
import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries'
|
||||
|
||||
import {Table, Column, Cell} from 'fixed-data-table'
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
func,
|
||||
number,
|
||||
oneOfType,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
|
||||
const emptyCells = {
|
||||
columns: [],
|
||||
values: [],
|
||||
}
|
||||
const {arrayOf, bool, func, number, oneOfType, shape, string} = PropTypes
|
||||
|
||||
const defaultTableHeight = 1000
|
||||
const emptySeries = {columns: [], values: []}
|
||||
|
||||
const CustomCell = React.createClass({
|
||||
propTypes: {
|
||||
|
@ -57,8 +48,9 @@ const ChronoTable = React.createClass({
|
|||
|
||||
getInitialState() {
|
||||
return {
|
||||
cellData: emptyCells,
|
||||
series: [emptySeries],
|
||||
columnWidths: {},
|
||||
activeSeriesIndex: 0,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -80,7 +72,6 @@ const ChronoTable = React.createClass({
|
|||
this.fetchCellData(nextProps.query)
|
||||
},
|
||||
|
||||
|
||||
async fetchCellData(query) {
|
||||
if (!query || !query.text) {
|
||||
return
|
||||
|
@ -92,87 +83,147 @@ const ChronoTable = React.createClass({
|
|||
const {results} = await fetchTimeSeriesAsync({source: query.host, query})
|
||||
this.setState({isLoading: false})
|
||||
|
||||
if (!results) {
|
||||
return this.setState({cellData: emptyCells})
|
||||
let series = _.get(results, ['0', 'series'], [])
|
||||
|
||||
if (!series.length) {
|
||||
return this.setState({series: []})
|
||||
}
|
||||
|
||||
const cellData = _.get(results, ['0', 'series', '0'], false)
|
||||
|
||||
if (!cellData) {
|
||||
return this.setState({cellData: emptyCells})
|
||||
}
|
||||
|
||||
this.setState({cellData})
|
||||
series = series.map(s => (s.values ? s : {...s, values: []}))
|
||||
this.setState({series})
|
||||
} catch (error) {
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
cellData: emptyCells,
|
||||
series: [],
|
||||
})
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
handleColumnResize(newColumnWidth, columnKey) {
|
||||
this.setState(({columnWidths}) => ({
|
||||
columnWidths: Object.assign({}, columnWidths, {
|
||||
[columnKey]: newColumnWidth,
|
||||
}),
|
||||
}))
|
||||
const columnWidths = {
|
||||
...this.state.columnWidths,
|
||||
[columnKey]: newColumnWidth,
|
||||
}
|
||||
|
||||
this.setState({
|
||||
columnWidths,
|
||||
})
|
||||
},
|
||||
|
||||
handleClickTab(activeSeriesIndex) {
|
||||
this.setState({activeSeriesIndex})
|
||||
},
|
||||
|
||||
handleClickDropdown(item) {
|
||||
this.setState({activeSeriesIndex: item.index})
|
||||
},
|
||||
|
||||
// Table data as a list of array.
|
||||
render() {
|
||||
const {containerWidth, height, query} = this.props
|
||||
const {cellData, columnWidths, isLoading} = this.state
|
||||
const {columns, values} = cellData
|
||||
const {series, columnWidths, isLoading, activeSeriesIndex} = this.state
|
||||
const {columns, values} = _.get(
|
||||
series,
|
||||
[`${activeSeriesIndex}`],
|
||||
emptySeries
|
||||
)
|
||||
|
||||
const maximumTabsCount = 11
|
||||
// adjust height to proper value by subtracting the heights of the UI around it
|
||||
// tab height, graph-container vertical padding, graph-heading height, multitable-header height
|
||||
const stylePixelOffset = 136
|
||||
|
||||
const rowHeight = 34
|
||||
const defaultColumnWidth = 200
|
||||
const width = columns.length > 1 ? defaultColumnWidth : containerWidth
|
||||
const headerHeight = 30
|
||||
const minWidth = 70
|
||||
const styleAdjustedHeight = height - stylePixelOffset
|
||||
const width = columns && columns.length > 1
|
||||
? defaultColumnWidth
|
||||
: containerWidth
|
||||
|
||||
if (!query) {
|
||||
return <div className="generic-empty-state">Please add a query below</div>
|
||||
}
|
||||
|
||||
if (!isLoading && !values.length) {
|
||||
return <div className="generic-empty-state">Your query returned no data</div>
|
||||
if (isLoading) {
|
||||
return <div className="generic-empty-state">Loading...</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<Table
|
||||
onColumnResizeEndCallback={this.handleColumnResize}
|
||||
isColumnResizing={false}
|
||||
rowHeight={rowHeight}
|
||||
rowsCount={values.length}
|
||||
width={containerWidth}
|
||||
ownerHeight={styleAdjustedHeight}
|
||||
height={styleAdjustedHeight}
|
||||
headerHeight={headerHeight}>
|
||||
{columns.map((columnName, colIndex) => {
|
||||
return (
|
||||
<Column
|
||||
isResizable={true}
|
||||
key={columnName}
|
||||
columnKey={columnName}
|
||||
header={<Cell>{columnName}</Cell>}
|
||||
cell={({rowIndex}) => {
|
||||
return <CustomCell columnName={columnName} data={values[rowIndex][colIndex]} />
|
||||
}}
|
||||
width={columnWidths[columnName] || width}
|
||||
minWidth={minWidth}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Table>
|
||||
<div style={{width: '100%', height: '100%', position: 'relative'}}>
|
||||
{series.length < maximumTabsCount
|
||||
? <div className="table--tabs">
|
||||
{series.map(({name}, i) => (
|
||||
<TabItem
|
||||
isActive={i === activeSeriesIndex}
|
||||
key={i}
|
||||
name={name}
|
||||
index={i}
|
||||
onClickTab={this.handleClickTab}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
: <Dropdown
|
||||
className="dropdown-160 table--tabs-dropdown"
|
||||
items={series.map((s, index) => ({...s, text: s.name, index}))}
|
||||
onChoose={this.handleClickDropdown}
|
||||
selected={series[activeSeriesIndex].name}
|
||||
buttonSize="btn-xs"
|
||||
/>}
|
||||
<div className="table--tabs-content">
|
||||
{(columns && !columns.length) || (values && !values.length)
|
||||
? <div className="generic-empty-state">
|
||||
This series is empty
|
||||
</div>
|
||||
: <Table
|
||||
onColumnResizeEndCallback={this.handleColumnResize}
|
||||
isColumnResizing={false}
|
||||
rowHeight={rowHeight}
|
||||
rowsCount={values.length}
|
||||
width={containerWidth}
|
||||
ownerHeight={styleAdjustedHeight}
|
||||
height={styleAdjustedHeight}
|
||||
headerHeight={headerHeight}
|
||||
>
|
||||
{columns.map((columnName, colIndex) => {
|
||||
return (
|
||||
<Column
|
||||
isResizable={true}
|
||||
key={columnName}
|
||||
columnKey={columnName}
|
||||
header={<Cell>{columnName}</Cell>}
|
||||
cell={({rowIndex}) => (
|
||||
<CustomCell
|
||||
columnName={columnName}
|
||||
data={values[rowIndex][colIndex]}
|
||||
/>
|
||||
)}
|
||||
width={columnWidths[columnName] || width}
|
||||
minWidth={minWidth}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Table>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const TabItem = ({name, index, onClickTab, isActive}) => (
|
||||
<div
|
||||
className={classNames('table--tab', {active: isActive})}
|
||||
onClick={() => onClickTab(index)}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
)
|
||||
|
||||
TabItem.propTypes = {
|
||||
name: string,
|
||||
onClickTab: func.isRequired,
|
||||
index: number.isRequired,
|
||||
isActive: bool,
|
||||
}
|
||||
|
||||
export default Dimensions({elementResize: true})(ChronoTable)
|
||||
|
|
|
@ -11,22 +11,60 @@ export const INFLUXQL_FUNCTIONS = [
|
|||
'stddev',
|
||||
]
|
||||
|
||||
const SEPARATOR = 'SEPARATOR'
|
||||
|
||||
export const QUERY_TEMPLATES = [
|
||||
{text: 'Show Databases', query: 'SHOW DATABASES'},
|
||||
{text: 'Create Database', query: 'CREATE DATABASE "db_name"'},
|
||||
{text: 'Drop Database', query: 'DROP DATABASE "db_name"'},
|
||||
{text: `${SEPARATOR}`},
|
||||
{text: 'Show Measurements', query: 'SHOW MEASUREMENTS ON "db_name"'},
|
||||
{text: 'Show Tag Keys', query: 'SHOW TAG KEYS ON "db_name" FROM "measurement_name"'},
|
||||
{text: 'Show Tag Values', query: 'SHOW TAG VALUES ON "db_name" FROM "measurement_name" WITH KEY = "tag_key"'},
|
||||
{text: 'Show Retention Policies', query: 'SHOW RETENTION POLICIES on "db_name"'},
|
||||
{text: 'Create Retention Policy', query: 'CREATE RETENTION POLICY "rp_name" ON "db_name" DURATION 30d REPLICATION 1 DEFAULT'},
|
||||
{text: 'Drop Retention Policy', query: 'DROP RETENTION POLICY "rp_name" ON "db_name"'},
|
||||
{text: 'Create Continuous Query', query: 'CREATE CONTINUOUS QUERY "cq_name" ON "db_name" BEGIN SELECT min("field") INTO "target_measurement" FROM "current_measurement" GROUP BY time(30m) END'},
|
||||
{text: 'Drop Continuous Query', query: 'DROP CONTINUOUS QUERY "cq_name" ON "db_name"'},
|
||||
{
|
||||
text: 'Show Tag Keys',
|
||||
query: 'SHOW TAG KEYS ON "db_name" FROM "measurement_name"',
|
||||
},
|
||||
{
|
||||
text: 'Show Tag Values',
|
||||
query: 'SHOW TAG VALUES ON "db_name" FROM "measurement_name" WITH KEY = "tag_key"',
|
||||
},
|
||||
{text: `${SEPARATOR}`},
|
||||
{
|
||||
text: 'Show Retention Policies',
|
||||
query: 'SHOW RETENTION POLICIES on "db_name"',
|
||||
},
|
||||
{
|
||||
text: 'Create Retention Policy',
|
||||
query: 'CREATE RETENTION POLICY "rp_name" ON "db_name" DURATION 30d REPLICATION 1 DEFAULT',
|
||||
},
|
||||
{
|
||||
text: 'Drop Retention Policy',
|
||||
query: 'DROP RETENTION POLICY "rp_name" ON "db_name"',
|
||||
},
|
||||
{text: `${SEPARATOR}`},
|
||||
{
|
||||
text: 'Show Continuos Queries',
|
||||
query: 'SHOW CONTINUOUS QUERIES',
|
||||
},
|
||||
{
|
||||
text: 'Create Continuous Query',
|
||||
query: 'CREATE CONTINUOUS QUERY "cq_name" ON "db_name" BEGIN SELECT min("field") INTO "target_measurement" FROM "current_measurement" GROUP BY time(30m) END',
|
||||
},
|
||||
{
|
||||
text: 'Drop Continuous Query',
|
||||
query: 'DROP CONTINUOUS QUERY "cq_name" ON "db_name"',
|
||||
},
|
||||
{text: `${SEPARATOR}`},
|
||||
{text: 'Show Users', query: 'SHOW USERS'},
|
||||
{text: 'Create User', query: 'CREATE USER "username" WITH PASSWORD \'password\''},
|
||||
{text: 'Create Admin User', query: 'CREATE USER "username" WITH PASSWORD \'password\' WITH ALL PRIVILEGES'},
|
||||
{
|
||||
text: 'Create User',
|
||||
query: 'CREATE USER "username" WITH PASSWORD \'password\'',
|
||||
},
|
||||
{
|
||||
text: 'Create Admin User',
|
||||
query: 'CREATE USER "username" WITH PASSWORD \'password\' WITH ALL PRIVILEGES',
|
||||
},
|
||||
{text: 'Drop User', query: 'DROP USER "username"'},
|
||||
{text: `${SEPARATOR}`},
|
||||
{text: 'Show Stats', query: 'SHOW STATS'},
|
||||
{text: 'Show Diagnostics', query: 'SHOW DIAGNOSTICS'},
|
||||
]
|
||||
|
|
|
@ -46,70 +46,93 @@ class Dropdown extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {items, selected, className, iconName, actions, addNew, buttonSize, buttonColor, menuWidth} = this.props
|
||||
const {
|
||||
items,
|
||||
selected,
|
||||
className,
|
||||
iconName,
|
||||
actions,
|
||||
addNew,
|
||||
buttonSize,
|
||||
buttonColor,
|
||||
menuWidth,
|
||||
} = this.props
|
||||
const {isOpen} = this.state
|
||||
|
||||
return (
|
||||
<div onClick={this.toggleMenu} className={classnames(`dropdown ${className}`, {open: isOpen})}>
|
||||
<div
|
||||
onClick={this.toggleMenu}
|
||||
className={classnames(`dropdown ${className}`, {open: isOpen})}
|
||||
>
|
||||
<div className={`btn dropdown-toggle ${buttonSize} ${buttonColor}`}>
|
||||
{iconName ? <span className={classnames('icon', {[iconName]: true})}></span> : null}
|
||||
{iconName
|
||||
? <span className={classnames('icon', {[iconName]: true})} />
|
||||
: null}
|
||||
<span className="dropdown-selected">{selected}</span>
|
||||
<span className="caret" />
|
||||
</div>
|
||||
{isOpen ?
|
||||
<ul className="dropdown-menu" style={{width: menuWidth}}>
|
||||
{items.map((item, i) => {
|
||||
return (
|
||||
<li className="dropdown-item" key={i}>
|
||||
<a href="#" onClick={() => this.handleSelection(item)}>
|
||||
{item.text}
|
||||
</a>
|
||||
{actions.length > 0 ?
|
||||
<div className="dropdown-item__actions">
|
||||
{actions.map((action) => {
|
||||
return (
|
||||
<button key={action.text} className="dropdown-item__action" onClick={(e) => this.handleAction(e, action, item)}>
|
||||
<span title={action.text} className={`icon ${action.icon}`}></span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
: null}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
{
|
||||
addNew ?
|
||||
<li>
|
||||
<Link to={addNew.url}>
|
||||
{addNew.text}
|
||||
</Link>
|
||||
</li> :
|
||||
null
|
||||
}
|
||||
</ul>
|
||||
{isOpen
|
||||
? <ul className="dropdown-menu" style={{width: menuWidth}}>
|
||||
{items.map((item, i) => {
|
||||
if (item.text === 'SEPARATOR') {
|
||||
return <li key={i} role="separator" className="divider" />
|
||||
}
|
||||
return (
|
||||
<li className="dropdown-item" key={i}>
|
||||
<a href="#" onClick={() => this.handleSelection(item)}>
|
||||
{item.text}
|
||||
</a>
|
||||
{actions.length > 0
|
||||
? <div className="dropdown-item__actions">
|
||||
{actions.map(action => {
|
||||
return (
|
||||
<button
|
||||
key={action.text}
|
||||
className="dropdown-item__action"
|
||||
onClick={e =>
|
||||
this.handleAction(e, action, item)}
|
||||
>
|
||||
<span
|
||||
title={action.text}
|
||||
className={`icon ${action.icon}`}
|
||||
/>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
: null}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
{addNew
|
||||
? <li>
|
||||
<Link to={addNew.url}>
|
||||
{addNew.text}
|
||||
</Link>
|
||||
</li>
|
||||
: null}
|
||||
</ul>
|
||||
: null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
shape,
|
||||
string,
|
||||
func,
|
||||
} = PropTypes
|
||||
const {arrayOf, shape, string, func} = PropTypes
|
||||
|
||||
Dropdown.propTypes = {
|
||||
actions: arrayOf(shape({
|
||||
icon: string.isRequired,
|
||||
text: string.isRequired,
|
||||
handler: func.isRequired,
|
||||
})),
|
||||
items: arrayOf(shape({
|
||||
text: string.isRequired,
|
||||
})).isRequired,
|
||||
actions: arrayOf(
|
||||
shape({
|
||||
icon: string.isRequired,
|
||||
text: string.isRequired,
|
||||
handler: func.isRequired,
|
||||
})
|
||||
),
|
||||
items: arrayOf(
|
||||
shape({
|
||||
text: string.isRequired,
|
||||
})
|
||||
).isRequired,
|
||||
onChoose: func.isRequired,
|
||||
addNew: shape({
|
||||
url: string.isRequired,
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
border-color 0.25s ease;
|
||||
border: 2px solid $query-editor--bg;
|
||||
background-color: $query-editor--field-bg;
|
||||
|
||||
|
||||
}
|
||||
.query-editor--field {
|
||||
font-size: 12px;
|
||||
|
@ -103,4 +103,7 @@
|
|||
min-width: $query-editor--templates-menu-width;
|
||||
max-width: $query-editor--templates-menu-width;
|
||||
}
|
||||
}
|
||||
.divider {
|
||||
background: linear-gradient(to right, #00C9FF 0%, #22ADF6 100%);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -131,6 +131,58 @@ table .monotype {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Table Tabs
|
||||
----------------------------------------------
|
||||
*/
|
||||
$table-tab-height: 30px;
|
||||
$table-tab-scrollbar-height: 6px;
|
||||
|
||||
.table--tabs {
|
||||
display: flex;
|
||||
height: $table-tab-height;
|
||||
align-items: center;
|
||||
}
|
||||
.table--tab {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
@include no-user-select();
|
||||
height: $table-tab-height;
|
||||
border-radius: $radius-small $radius-small 0 0;
|
||||
line-height: $table-tab-height;
|
||||
padding: 0 6px;
|
||||
background-color: $g4-onyx;
|
||||
color: $g11-sidewalk;
|
||||
margin-right: 2px;
|
||||
transition:
|
||||
color 0.25s ease,
|
||||
background-color 0.25s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: $g5-pepper;
|
||||
color: $g15-platinum;
|
||||
cursor: pointer;
|
||||
}
|
||||
&.active {
|
||||
background-color: $g6-smoke;
|
||||
color: $g18-cloud;
|
||||
}
|
||||
}
|
||||
.table--tabs-dropdown {
|
||||
display: inline-block;
|
||||
}
|
||||
.table--tabs-content {
|
||||
width: 100%;
|
||||
height: calc(100% - #{$table-tab-height});
|
||||
position: absolute;
|
||||
top: $table-tab-height;
|
||||
}
|
||||
|
||||
.table--tabs + .table--tabs-content > .generic-empty-state {
|
||||
background-color: $g6-smoke !important;
|
||||
border-radius: 0 $radius-small $radius-small $radius-small;
|
||||
}
|
||||
|
||||
/*
|
||||
Responsive Tables
|
||||
----------------------------------------------
|
||||
|
|
|
@ -16,67 +16,102 @@ const cells = {
|
|||
}
|
||||
|
||||
// activeQueryIndex is an optional argument that indicated which query's series we want highlighted.
|
||||
export default function timeSeriesToDygraph(raw = [], activeQueryIndex, isInDataExplorer) {
|
||||
export default function timeSeriesToDygraph(
|
||||
raw = [],
|
||||
activeQueryIndex,
|
||||
isInDataExplorer
|
||||
) {
|
||||
// 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]
|
||||
}, [])
|
||||
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 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.length) {
|
||||
return acc + (columns.length - 1) * values.length
|
||||
}
|
||||
return acc
|
||||
}, 0)
|
||||
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}`,
|
||||
forEach(
|
||||
serieses,
|
||||
({
|
||||
name: measurement,
|
||||
columns,
|
||||
values,
|
||||
index: seriesIndex,
|
||||
responseIndex,
|
||||
seriesIndex,
|
||||
}))
|
||||
labels = concat(labels, unsortedLabels)
|
||||
tags = {},
|
||||
}) => {
|
||||
const rows = map(values || [], vals => ({
|
||||
vals,
|
||||
}))
|
||||
|
||||
forEach(rows, ({vals}) => {
|
||||
const [time, ...rowValues] = 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(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
|
||||
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 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++) {
|
||||
|
@ -97,23 +132,32 @@ export default function timeSeriesToDygraph(raw = [], activeQueryIndex, isInData
|
|||
tsMemo[time] = existingRowIndex
|
||||
}
|
||||
|
||||
timeSeries[existingRowIndex].values[labelsToValueIndex[label + seriesIndex]] = value
|
||||
timeSeries[existingRowIndex].values[
|
||||
labelsToValueIndex[label + seriesIndex]
|
||||
] = value
|
||||
}
|
||||
const sortedTimeSeries = _.sortBy(timeSeries, 'time')
|
||||
|
||||
const dygraphSeries = reduce(sortedLabels, (acc, {label, responseIndex}) => {
|
||||
if (!isInDataExplorer) {
|
||||
acc[label] = {
|
||||
axis: responseIndex === 0 ? 'y' : 'y2',
|
||||
const dygraphSeries = reduce(
|
||||
sortedLabels,
|
||||
(acc, {label, responseIndex}) => {
|
||||
if (!isInDataExplorer) {
|
||||
acc[label] = {
|
||||
axis: responseIndex === 0 ? 'y' : 'y2',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
return acc
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
||||
return {
|
||||
labels: ['time', ...map(sortedLabels, ({label}) => label)],
|
||||
timeSeries: map(sortedTimeSeries, ({time, values}) => ([new Date(time), ...values])),
|
||||
timeSeries: map(sortedTimeSeries, ({time, values}) => [
|
||||
new Date(time),
|
||||
...values,
|
||||
]),
|
||||
dygraphSeries,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue