commit
d9653fd3f0
|
@ -212,7 +212,6 @@
|
||||||
'react/jsx-boolean-value': [2, 'always'],
|
'react/jsx-boolean-value': [2, 'always'],
|
||||||
'react/jsx-curly-spacing': [2, 'never'],
|
'react/jsx-curly-spacing': [2, 'never'],
|
||||||
'react/jsx-equals-spacing': [2, 'never'],
|
'react/jsx-equals-spacing': [2, 'never'],
|
||||||
'react/jsx-indent-props': [2, 2],
|
|
||||||
'react/jsx-key': 2,
|
'react/jsx-key': 2,
|
||||||
'react/jsx-no-duplicate-props': 2,
|
'react/jsx-no-duplicate-props': 2,
|
||||||
'react/jsx-no-undef': 2,
|
'react/jsx-no-undef': 2,
|
||||||
|
|
|
@ -3,26 +3,17 @@ import React, {PropTypes} from 'react'
|
||||||
import Dimensions from 'react-dimensions'
|
import Dimensions from 'react-dimensions'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
import Dropdown from 'shared/components/Dropdown'
|
||||||
import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries'
|
import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries'
|
||||||
|
|
||||||
import {Table, Column, Cell} from 'fixed-data-table'
|
import {Table, Column, Cell} from 'fixed-data-table'
|
||||||
|
|
||||||
const {
|
const {arrayOf, bool, func, number, oneOfType, shape, string} = PropTypes
|
||||||
arrayOf,
|
|
||||||
func,
|
|
||||||
number,
|
|
||||||
oneOfType,
|
|
||||||
shape,
|
|
||||||
string,
|
|
||||||
} = PropTypes
|
|
||||||
|
|
||||||
const emptyCells = {
|
|
||||||
columns: [],
|
|
||||||
values: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultTableHeight = 1000
|
const defaultTableHeight = 1000
|
||||||
|
const emptySeries = {columns: [], values: []}
|
||||||
|
|
||||||
const CustomCell = React.createClass({
|
const CustomCell = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
@ -57,8 +48,9 @@ const ChronoTable = React.createClass({
|
||||||
|
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return {
|
return {
|
||||||
cellData: emptyCells,
|
series: [emptySeries],
|
||||||
columnWidths: {},
|
columnWidths: {},
|
||||||
|
activeSeriesIndex: 0,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -80,7 +72,6 @@ const ChronoTable = React.createClass({
|
||||||
this.fetchCellData(nextProps.query)
|
this.fetchCellData(nextProps.query)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
async fetchCellData(query) {
|
async fetchCellData(query) {
|
||||||
if (!query || !query.text) {
|
if (!query || !query.text) {
|
||||||
return
|
return
|
||||||
|
@ -92,87 +83,147 @@ const ChronoTable = React.createClass({
|
||||||
const {results} = await fetchTimeSeriesAsync({source: query.host, query})
|
const {results} = await fetchTimeSeriesAsync({source: query.host, query})
|
||||||
this.setState({isLoading: false})
|
this.setState({isLoading: false})
|
||||||
|
|
||||||
if (!results) {
|
let series = _.get(results, ['0', 'series'], [])
|
||||||
return this.setState({cellData: emptyCells})
|
|
||||||
|
if (!series.length) {
|
||||||
|
return this.setState({series: []})
|
||||||
}
|
}
|
||||||
|
|
||||||
const cellData = _.get(results, ['0', 'series', '0'], false)
|
series = series.map(s => (s.values ? s : {...s, values: []}))
|
||||||
|
this.setState({series})
|
||||||
if (!cellData) {
|
|
||||||
return this.setState({cellData: emptyCells})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({cellData})
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
cellData: emptyCells,
|
series: [],
|
||||||
})
|
})
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
handleColumnResize(newColumnWidth, columnKey) {
|
handleColumnResize(newColumnWidth, columnKey) {
|
||||||
this.setState(({columnWidths}) => ({
|
const columnWidths = {
|
||||||
columnWidths: Object.assign({}, columnWidths, {
|
...this.state.columnWidths,
|
||||||
[columnKey]: newColumnWidth,
|
[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() {
|
render() {
|
||||||
const {containerWidth, height, query} = this.props
|
const {containerWidth, height, query} = this.props
|
||||||
const {cellData, columnWidths, isLoading} = this.state
|
const {series, columnWidths, isLoading, activeSeriesIndex} = this.state
|
||||||
const {columns, values} = cellData
|
const {columns, values} = _.get(
|
||||||
|
series,
|
||||||
|
[`${activeSeriesIndex}`],
|
||||||
|
emptySeries
|
||||||
|
)
|
||||||
|
|
||||||
|
const maximumTabsCount = 11
|
||||||
// adjust height to proper value by subtracting the heights of the UI around it
|
// 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
|
// tab height, graph-container vertical padding, graph-heading height, multitable-header height
|
||||||
const stylePixelOffset = 136
|
const stylePixelOffset = 136
|
||||||
|
|
||||||
const rowHeight = 34
|
const rowHeight = 34
|
||||||
const defaultColumnWidth = 200
|
const defaultColumnWidth = 200
|
||||||
const width = columns.length > 1 ? defaultColumnWidth : containerWidth
|
|
||||||
const headerHeight = 30
|
const headerHeight = 30
|
||||||
const minWidth = 70
|
const minWidth = 70
|
||||||
const styleAdjustedHeight = height - stylePixelOffset
|
const styleAdjustedHeight = height - stylePixelOffset
|
||||||
|
const width = columns && columns.length > 1
|
||||||
|
? defaultColumnWidth
|
||||||
|
: containerWidth
|
||||||
|
|
||||||
if (!query) {
|
if (!query) {
|
||||||
return <div className="generic-empty-state">Please add a query below</div>
|
return <div className="generic-empty-state">Please add a query below</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isLoading && !values.length) {
|
if (isLoading) {
|
||||||
return <div className="generic-empty-state">Your query returned no data</div>
|
return <div className="generic-empty-state">Loading...</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<div style={{width: '100%', height: '100%', position: 'relative'}}>
|
||||||
onColumnResizeEndCallback={this.handleColumnResize}
|
{series.length < maximumTabsCount
|
||||||
isColumnResizing={false}
|
? <div className="table--tabs">
|
||||||
rowHeight={rowHeight}
|
{series.map(({name}, i) => (
|
||||||
rowsCount={values.length}
|
<TabItem
|
||||||
width={containerWidth}
|
isActive={i === activeSeriesIndex}
|
||||||
ownerHeight={styleAdjustedHeight}
|
key={i}
|
||||||
height={styleAdjustedHeight}
|
name={name}
|
||||||
headerHeight={headerHeight}>
|
index={i}
|
||||||
{columns.map((columnName, colIndex) => {
|
onClickTab={this.handleClickTab}
|
||||||
return (
|
/>
|
||||||
<Column
|
))}
|
||||||
isResizable={true}
|
</div>
|
||||||
key={columnName}
|
: <Dropdown
|
||||||
columnKey={columnName}
|
className="dropdown-160 table--tabs-dropdown"
|
||||||
header={<Cell>{columnName}</Cell>}
|
items={series.map((s, index) => ({...s, text: s.name, index}))}
|
||||||
cell={({rowIndex}) => {
|
onChoose={this.handleClickDropdown}
|
||||||
return <CustomCell columnName={columnName} data={values[rowIndex][colIndex]} />
|
selected={series[activeSeriesIndex].name}
|
||||||
}}
|
buttonSize="btn-xs"
|
||||||
width={columnWidths[columnName] || width}
|
/>}
|
||||||
minWidth={minWidth}
|
<div className="table--tabs-content">
|
||||||
/>
|
{(columns && !columns.length) || (values && !values.length)
|
||||||
)
|
? <div className="generic-empty-state">
|
||||||
})}
|
This series is empty
|
||||||
</Table>
|
</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)
|
export default Dimensions({elementResize: true})(ChronoTable)
|
||||||
|
|
|
@ -11,22 +11,60 @@ export const INFLUXQL_FUNCTIONS = [
|
||||||
'stddev',
|
'stddev',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const SEPARATOR = 'SEPARATOR'
|
||||||
|
|
||||||
export const QUERY_TEMPLATES = [
|
export const QUERY_TEMPLATES = [
|
||||||
{text: 'Show Databases', query: 'SHOW DATABASES'},
|
{text: 'Show Databases', query: 'SHOW DATABASES'},
|
||||||
{text: 'Create Database', query: 'CREATE DATABASE "db_name"'},
|
{text: 'Create Database', query: 'CREATE DATABASE "db_name"'},
|
||||||
{text: 'Drop Database', query: 'DROP DATABASE "db_name"'},
|
{text: 'Drop Database', query: 'DROP DATABASE "db_name"'},
|
||||||
|
{text: `${SEPARATOR}`},
|
||||||
{text: 'Show Measurements', query: 'SHOW MEASUREMENTS ON "db_name"'},
|
{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 Tag Keys',
|
||||||
{text: 'Show Retention Policies', query: 'SHOW RETENTION POLICIES on "db_name"'},
|
query: 'SHOW TAG KEYS ON "db_name" FROM "measurement_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: 'Show Tag Values',
|
||||||
{text: 'Drop Continuous Query', query: 'DROP CONTINUOUS QUERY "cq_name" ON "db_name"'},
|
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: '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: 'Drop User', query: 'DROP USER "username"'},
|
||||||
|
{text: `${SEPARATOR}`},
|
||||||
{text: 'Show Stats', query: 'SHOW STATS'},
|
{text: 'Show Stats', query: 'SHOW STATS'},
|
||||||
{text: 'Show Diagnostics', query: 'SHOW DIAGNOSTICS'},
|
{text: 'Show Diagnostics', query: 'SHOW DIAGNOSTICS'},
|
||||||
]
|
]
|
||||||
|
|
|
@ -46,70 +46,93 @@ class Dropdown extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
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
|
const {isOpen} = this.state
|
||||||
|
|
||||||
return (
|
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}`}>
|
<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="dropdown-selected">{selected}</span>
|
||||||
<span className="caret" />
|
<span className="caret" />
|
||||||
</div>
|
</div>
|
||||||
{isOpen ?
|
{isOpen
|
||||||
<ul className="dropdown-menu" style={{width: menuWidth}}>
|
? <ul className="dropdown-menu" style={{width: menuWidth}}>
|
||||||
{items.map((item, i) => {
|
{items.map((item, i) => {
|
||||||
return (
|
if (item.text === 'SEPARATOR') {
|
||||||
<li className="dropdown-item" key={i}>
|
return <li key={i} role="separator" className="divider" />
|
||||||
<a href="#" onClick={() => this.handleSelection(item)}>
|
}
|
||||||
{item.text}
|
return (
|
||||||
</a>
|
<li className="dropdown-item" key={i}>
|
||||||
{actions.length > 0 ?
|
<a href="#" onClick={() => this.handleSelection(item)}>
|
||||||
<div className="dropdown-item__actions">
|
{item.text}
|
||||||
{actions.map((action) => {
|
</a>
|
||||||
return (
|
{actions.length > 0
|
||||||
<button key={action.text} className="dropdown-item__action" onClick={(e) => this.handleAction(e, action, item)}>
|
? <div className="dropdown-item__actions">
|
||||||
<span title={action.text} className={`icon ${action.icon}`}></span>
|
{actions.map(action => {
|
||||||
</button>
|
return (
|
||||||
)
|
<button
|
||||||
})}
|
key={action.text}
|
||||||
</div>
|
className="dropdown-item__action"
|
||||||
: null}
|
onClick={e =>
|
||||||
</li>
|
this.handleAction(e, action, item)}
|
||||||
)
|
>
|
||||||
})}
|
<span
|
||||||
{
|
title={action.text}
|
||||||
addNew ?
|
className={`icon ${action.icon}`}
|
||||||
<li>
|
/>
|
||||||
<Link to={addNew.url}>
|
</button>
|
||||||
{addNew.text}
|
)
|
||||||
</Link>
|
})}
|
||||||
</li> :
|
</div>
|
||||||
null
|
: null}
|
||||||
}
|
</li>
|
||||||
</ul>
|
)
|
||||||
|
})}
|
||||||
|
{addNew
|
||||||
|
? <li>
|
||||||
|
<Link to={addNew.url}>
|
||||||
|
{addNew.text}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
: null}
|
||||||
|
</ul>
|
||||||
: null}
|
: null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {arrayOf, shape, string, func} = PropTypes
|
||||||
arrayOf,
|
|
||||||
shape,
|
|
||||||
string,
|
|
||||||
func,
|
|
||||||
} = PropTypes
|
|
||||||
|
|
||||||
Dropdown.propTypes = {
|
Dropdown.propTypes = {
|
||||||
actions: arrayOf(shape({
|
actions: arrayOf(
|
||||||
icon: string.isRequired,
|
shape({
|
||||||
text: string.isRequired,
|
icon: string.isRequired,
|
||||||
handler: func.isRequired,
|
text: string.isRequired,
|
||||||
})),
|
handler: func.isRequired,
|
||||||
items: arrayOf(shape({
|
})
|
||||||
text: string.isRequired,
|
),
|
||||||
})).isRequired,
|
items: arrayOf(
|
||||||
|
shape({
|
||||||
|
text: string.isRequired,
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
onChoose: func.isRequired,
|
onChoose: func.isRequired,
|
||||||
addNew: shape({
|
addNew: shape({
|
||||||
url: string.isRequired,
|
url: string.isRequired,
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
border-color 0.25s ease;
|
border-color 0.25s ease;
|
||||||
border: 2px solid $query-editor--bg;
|
border: 2px solid $query-editor--bg;
|
||||||
background-color: $query-editor--field-bg;
|
background-color: $query-editor--field-bg;
|
||||||
|
|
||||||
}
|
}
|
||||||
.query-editor--field {
|
.query-editor--field {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
@ -103,4 +103,7 @@
|
||||||
min-width: $query-editor--templates-menu-width;
|
min-width: $query-editor--templates-menu-width;
|
||||||
max-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
|
Responsive Tables
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
|
@ -16,67 +16,102 @@ const cells = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// activeQueryIndex is an optional argument that indicated which query's series we want highlighted.
|
// 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
|
// collect results from each influx response
|
||||||
const results = reduce(raw, (acc, rawResponse, responseIndex) => {
|
const results = reduce(
|
||||||
const responses = _.get(rawResponse, 'response.results', [])
|
raw,
|
||||||
const indexedResponses = map(responses, (response) => ({...response, responseIndex}))
|
(acc, rawResponse, responseIndex) => {
|
||||||
return [...acc, ...indexedResponses]
|
const responses = _.get(rawResponse, 'response.results', [])
|
||||||
}, [])
|
const indexedResponses = map(responses, response => ({
|
||||||
|
...response,
|
||||||
|
responseIndex,
|
||||||
|
}))
|
||||||
|
return [...acc, ...indexedResponses]
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
// collect each series
|
// collect each series
|
||||||
const serieses = reduce(results, (acc, {series = [], responseIndex}, index) => {
|
const serieses = reduce(
|
||||||
return [...acc, ...map(series, (item) => ({...item, responseIndex, index}))]
|
results,
|
||||||
}, [])
|
(acc, {series = [], responseIndex}, index) => {
|
||||||
|
return [...acc, ...map(series, item => ({...item, responseIndex, index}))]
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
const size = reduce(serieses, (acc, {columns, values}) => {
|
const size = reduce(
|
||||||
if (columns.length && values.length) {
|
serieses,
|
||||||
return acc + (columns.length - 1) * values.length
|
(acc, {columns, values}) => {
|
||||||
}
|
if (columns.length && (values && values.length)) {
|
||||||
return acc
|
return acc + (columns.length - 1) * values.length
|
||||||
}, 0)
|
}
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
// convert series into cells with rows and columns
|
// convert series into cells with rows and columns
|
||||||
let cellIndex = 0
|
let cellIndex = 0
|
||||||
let labels = []
|
let labels = []
|
||||||
|
|
||||||
forEach(serieses, ({name: measurement, columns, values, index: seriesIndex, responseIndex, tags = {}}) => {
|
forEach(
|
||||||
const rows = map(values, (vals) => ({
|
serieses,
|
||||||
vals,
|
({
|
||||||
}))
|
name: measurement,
|
||||||
|
columns,
|
||||||
// tagSet is each tag key and value for a series
|
values,
|
||||||
const tagSet = map(Object.keys(tags), (tag) => `[${tag}=${tags[tag]}]`).sort().join('')
|
index: seriesIndex,
|
||||||
const unsortedLabels = map(columns.slice(1), (field) => ({
|
|
||||||
label: `${measurement}.${field}${tagSet}`,
|
|
||||||
responseIndex,
|
responseIndex,
|
||||||
seriesIndex,
|
tags = {},
|
||||||
}))
|
}) => {
|
||||||
labels = concat(labels, unsortedLabels)
|
const rows = map(values || [], vals => ({
|
||||||
|
vals,
|
||||||
|
}))
|
||||||
|
|
||||||
forEach(rows, ({vals}) => {
|
// tagSet is each tag key and value for a series
|
||||||
const [time, ...rowValues] = vals
|
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) => {
|
forEach(rows, ({vals}) => {
|
||||||
cells.label[cellIndex] = unsortedLabels[i].label
|
const [time, ...rowValues] = vals
|
||||||
cells.value[cellIndex] = value
|
|
||||||
cells.time[cellIndex] = time
|
forEach(rowValues, (value, i) => {
|
||||||
cells.seriesIndex[cellIndex] = seriesIndex
|
cells.label[cellIndex] = unsortedLabels[i].label
|
||||||
cells.responseIndex[cellIndex] = responseIndex
|
cells.value[cellIndex] = value
|
||||||
cellIndex++ // eslint-disable-line no-plusplus
|
cells.time[cellIndex] = time
|
||||||
|
cells.seriesIndex[cellIndex] = seriesIndex
|
||||||
|
cells.responseIndex[cellIndex] = responseIndex
|
||||||
|
cellIndex++ // eslint-disable-line no-plusplus
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
const sortedLabels = _.sortBy(labels, 'label')
|
const sortedLabels = _.sortBy(labels, 'label')
|
||||||
const tsMemo = {}
|
const tsMemo = {}
|
||||||
const nullArray = Array(sortedLabels.length).fill(null)
|
const nullArray = Array(sortedLabels.length).fill(null)
|
||||||
|
|
||||||
const labelsToValueIndex = reduce(sortedLabels, (acc, {label, seriesIndex}, i) => {
|
const labelsToValueIndex = reduce(
|
||||||
// adding series index prevents overwriting of two distinct labels that have the same field and measurements
|
sortedLabels,
|
||||||
acc[label + seriesIndex] = i
|
(acc, {label, seriesIndex}, i) => {
|
||||||
return acc
|
// adding series index prevents overwriting of two distinct labels that have the same field and measurements
|
||||||
}, {})
|
acc[label + seriesIndex] = i
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
const timeSeries = []
|
const timeSeries = []
|
||||||
for (let i = 0; i < size; i++) {
|
for (let i = 0; i < size; i++) {
|
||||||
|
@ -97,23 +132,32 @@ export default function timeSeriesToDygraph(raw = [], activeQueryIndex, isInData
|
||||||
tsMemo[time] = existingRowIndex
|
tsMemo[time] = existingRowIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
timeSeries[existingRowIndex].values[labelsToValueIndex[label + seriesIndex]] = value
|
timeSeries[existingRowIndex].values[
|
||||||
|
labelsToValueIndex[label + seriesIndex]
|
||||||
|
] = value
|
||||||
}
|
}
|
||||||
const sortedTimeSeries = _.sortBy(timeSeries, 'time')
|
const sortedTimeSeries = _.sortBy(timeSeries, 'time')
|
||||||
|
|
||||||
const dygraphSeries = reduce(sortedLabels, (acc, {label, responseIndex}) => {
|
const dygraphSeries = reduce(
|
||||||
if (!isInDataExplorer) {
|
sortedLabels,
|
||||||
acc[label] = {
|
(acc, {label, responseIndex}) => {
|
||||||
axis: responseIndex === 0 ? 'y' : 'y2',
|
if (!isInDataExplorer) {
|
||||||
|
acc[label] = {
|
||||||
|
axis: responseIndex === 0 ? 'y' : 'y2',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
labels: ['time', ...map(sortedLabels, ({label}) => label)],
|
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,
|
dygraphSeries,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue