Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
268dca5b
Unverified
Commit
268dca5b
authored
Mar 22, 2019
by
Torkel Ödegaard
Committed by
GitHub
Mar 22, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #16112 from ryantxu/show-all-columns
show all numeric columns in singlestats/graph2
parents
ded73c97
8e6279cb
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
300 additions
and
59 deletions
+300
-59
packages/grafana-ui/src/utils/processTableData.test.ts
+43
-17
packages/grafana-ui/src/utils/processTableData.ts
+113
-17
public/app/features/dashboard/dashgrid/DataPanel.test.tsx
+60
-0
public/app/features/dashboard/dashgrid/DataPanel.tsx
+21
-1
public/app/features/dashboard/dashgrid/PanelChrome.tsx
+4
-2
public/app/plugins/panel/graph2/GraphPanel.tsx
+34
-14
public/app/plugins/panel/singlestat2/SingleStatPanel.tsx
+25
-8
No files found.
packages/grafana-ui/src/utils/processTableData.test.ts
View file @
268dca5b
import
{
parseCSV
,
toTableData
}
from
'./processTableData'
;
import
{
parseCSV
,
toTableData
,
guessColumnTypes
,
guessColumnTypeFromValue
}
from
'./processTableData'
;
import
{
ColumnType
}
from
'../types/data'
;
import
moment
from
'moment'
;
describe
(
'processTableData'
,
()
=>
{
describe
(
'basic processing'
,
()
=>
{
...
...
@@ -20,23 +22,23 @@ describe('processTableData', () => {
});
describe
(
'toTableData'
,
()
=>
{
it
(
'converts timeseries to table
skipping nulls
'
,
()
=>
{
it
(
'converts timeseries to table '
,
()
=>
{
const
input1
=
{
target
:
'Field Name'
,
datapoints
:
[[
100
,
1
],
[
200
,
2
]],
};
let
table
=
toTableData
(
input1
);
expect
(
table
.
columns
[
0
].
text
).
toBe
(
input1
.
target
);
expect
(
table
.
rows
).
toBe
(
input1
.
datapoints
);
// Should fill a default name if target is empty
const
input2
=
{
// without target
target
:
''
,
datapoints
:
[[
100
,
1
],
[
200
,
2
]],
};
const
data
=
toTableData
([
null
,
input1
,
input2
,
null
,
null
]);
expect
(
data
.
length
).
toBe
(
2
);
expect
(
data
[
0
].
columns
[
0
].
text
).
toBe
(
input1
.
target
);
expect
(
data
[
0
].
rows
).
toBe
(
input1
.
datapoints
);
// Default name
expect
(
data
[
1
].
columns
[
0
].
text
).
toEqual
(
'Value'
);
table
=
toTableData
(
input2
);
expect
(
table
.
columns
[
0
].
text
).
toEqual
(
'Value'
);
});
it
(
'keeps tableData unchanged'
,
()
=>
{
...
...
@@ -44,15 +46,39 @@ describe('toTableData', () => {
columns
:
[{
text
:
'A'
},
{
text
:
'B'
},
{
text
:
'C'
}],
rows
:
[[
100
,
'A'
,
1
],
[
200
,
'B'
,
2
],
[
300
,
'C'
,
3
]],
};
const
data
=
toTableData
([
null
,
input
,
null
,
null
]);
expect
(
data
.
length
).
toBe
(
1
);
expect
(
data
[
0
]).
toBe
(
input
);
const
table
=
toTableData
(
input
);
expect
(
table
).
toBe
(
input
);
});
it
(
'Guess Colum Types from value'
,
()
=>
{
expect
(
guessColumnTypeFromValue
(
1
)).
toBe
(
ColumnType
.
number
);
expect
(
guessColumnTypeFromValue
(
1.234
)).
toBe
(
ColumnType
.
number
);
expect
(
guessColumnTypeFromValue
(
3.125e7
)).
toBe
(
ColumnType
.
number
);
expect
(
guessColumnTypeFromValue
(
true
)).
toBe
(
ColumnType
.
boolean
);
expect
(
guessColumnTypeFromValue
(
false
)).
toBe
(
ColumnType
.
boolean
);
expect
(
guessColumnTypeFromValue
(
new
Date
())).
toBe
(
ColumnType
.
time
);
expect
(
guessColumnTypeFromValue
(
moment
())).
toBe
(
ColumnType
.
time
);
});
it
(
'supports null values OK'
,
()
=>
{
expect
(
toTableData
([
null
,
null
,
null
,
null
])).
toEqual
([]);
expect
(
toTableData
(
undefined
)).
toEqual
([]);
expect
(
toTableData
((
null
as
unknown
)
as
any
[])).
toEqual
([]);
expect
(
toTableData
([])).
toEqual
([]);
it
(
'Guess Colum Types from strings'
,
()
=>
{
expect
(
guessColumnTypeFromValue
(
'1'
)).
toBe
(
ColumnType
.
number
);
expect
(
guessColumnTypeFromValue
(
'1.234'
)).
toBe
(
ColumnType
.
number
);
expect
(
guessColumnTypeFromValue
(
'3.125e7'
)).
toBe
(
ColumnType
.
number
);
expect
(
guessColumnTypeFromValue
(
'True'
)).
toBe
(
ColumnType
.
boolean
);
expect
(
guessColumnTypeFromValue
(
'FALSE'
)).
toBe
(
ColumnType
.
boolean
);
expect
(
guessColumnTypeFromValue
(
'true'
)).
toBe
(
ColumnType
.
boolean
);
expect
(
guessColumnTypeFromValue
(
'xxxx'
)).
toBe
(
ColumnType
.
string
);
});
it
(
'Guess Colum Types from table'
,
()
=>
{
const
table
=
{
columns
:
[{
text
:
'A (number)'
},
{
text
:
'B (strings)'
},
{
text
:
'C (nulls)'
},
{
text
:
'Time'
}],
rows
:
[[
123
,
null
,
null
,
'2000'
],
[
null
,
'Hello'
,
null
,
'XXX'
]],
};
const
norm
=
guessColumnTypes
(
table
);
expect
(
norm
.
columns
[
0
].
type
).
toBe
(
ColumnType
.
number
);
expect
(
norm
.
columns
[
1
].
type
).
toBe
(
ColumnType
.
string
);
expect
(
norm
.
columns
[
2
].
type
).
toBeUndefined
();
expect
(
norm
.
columns
[
3
].
type
).
toBe
(
ColumnType
.
time
);
// based on name
});
});
packages/grafana-ui/src/utils/processTableData.ts
View file @
268dca5b
// Libraries
import
isNumber
from
'lodash/isNumber'
;
import
isString
from
'lodash/isString'
;
import
isBoolean
from
'lodash/isBoolean'
;
import
moment
from
'moment'
;
import
Papa
,
{
ParseError
,
ParseMeta
}
from
'papaparse'
;
// Types
...
...
@@ -147,26 +151,118 @@ function convertTimeSeriesToTableData(timeSeries: TimeSeries): TableData {
};
}
export
const
isTableData
=
(
data
:
any
):
data
is
TableData
=>
data
&&
data
.
hasOwnProperty
(
'columns'
);
export
const
getFirstTimeColumn
=
(
table
:
TableData
):
number
=>
{
const
{
columns
}
=
table
;
for
(
let
i
=
0
;
i
<
columns
.
length
;
i
++
)
{
if
(
columns
[
i
].
type
===
ColumnType
.
time
)
{
return
i
;
}
}
return
-
1
;
};
// PapaParse Dynamic Typing regex:
// https://github.com/mholt/PapaParse/blob/master/papaparse.js#L998
const
NUMBER
=
/^
\s
*-
?(\d
*
\.?\d
+|
\d
+
\.?\d
*
)(
e
[
-+
]?\d
+
)?\s
*$/i
;
export
const
toTableData
=
(
results
?:
any
[]):
TableData
[]
=>
{
if
(
!
results
)
{
return
[];
/**
* Given a value this will guess the best column type
*
* TODO: better Date/Time support! Look for standard date strings?
*/
export
function
guessColumnTypeFromValue
(
v
:
any
):
ColumnType
{
if
(
isNumber
(
v
))
{
return
ColumnType
.
number
;
}
return
results
.
filter
(
d
=>
!!
d
)
.
map
(
data
=>
{
if
(
data
.
hasOwnProperty
(
'columns'
))
{
return
data
as
TableData
;
}
if
(
data
.
hasOwnProperty
(
'datapoints'
))
{
return
convertTimeSeriesToTableData
(
data
);
}
// TODO, try to convert JSON to table?
console
.
warn
(
'Can not convert'
,
data
);
throw
new
Error
(
'Unsupported data format'
);
});
if
(
isString
(
v
))
{
if
(
NUMBER
.
test
(
v
))
{
return
ColumnType
.
number
;
}
if
(
v
===
'true'
||
v
===
'TRUE'
||
v
===
'True'
||
v
===
'false'
||
v
===
'FALSE'
||
v
===
'False'
)
{
return
ColumnType
.
boolean
;
}
return
ColumnType
.
string
;
}
if
(
isBoolean
(
v
))
{
return
ColumnType
.
boolean
;
}
if
(
v
instanceof
Date
||
v
instanceof
moment
)
{
return
ColumnType
.
time
;
}
return
ColumnType
.
other
;
}
/**
* Looks at the data to guess the column type. This ignores any existing setting
*/
function
guessColumnTypeFromTable
(
table
:
TableData
,
index
:
number
):
ColumnType
|
undefined
{
const
column
=
table
.
columns
[
index
];
// 1. Use the column name to guess
if
(
column
.
text
)
{
const
name
=
column
.
text
.
toLowerCase
();
if
(
name
===
'date'
||
name
===
'time'
)
{
return
ColumnType
.
time
;
}
}
// 2. Check the first non-null value
for
(
let
i
=
0
;
i
<
table
.
rows
.
length
;
i
++
)
{
const
v
=
table
.
rows
[
i
][
index
];
if
(
v
!==
null
)
{
return
guessColumnTypeFromValue
(
v
);
}
}
// Could not find anything
return
undefined
;
}
/**
* @returns a table Returns a copy of the table with the best guess for each column type
* If the table already has column types defined, they will be used
*/
export
const
guessColumnTypes
=
(
table
:
TableData
):
TableData
=>
{
for
(
let
i
=
0
;
i
<
table
.
columns
.
length
;
i
++
)
{
if
(
!
table
.
columns
[
i
].
type
)
{
// Somethign is missing a type return a modified copy
return
{
...
table
,
columns
:
table
.
columns
.
map
((
column
,
index
)
=>
{
if
(
column
.
type
)
{
return
column
;
}
// Replace it with a calculated version
return
{
...
column
,
type
:
guessColumnTypeFromTable
(
table
,
index
),
};
}),
};
}
}
// No changes necessary
return
table
;
};
export
const
isTableData
=
(
data
:
any
):
data
is
TableData
=>
data
&&
data
.
hasOwnProperty
(
'columns'
);
export
const
toTableData
=
(
data
:
any
):
TableData
=>
{
if
(
data
.
hasOwnProperty
(
'columns'
))
{
return
data
as
TableData
;
}
if
(
data
.
hasOwnProperty
(
'datapoints'
))
{
return
convertTimeSeriesToTableData
(
data
);
}
// TODO, try to convert JSON/Array to table?
console
.
warn
(
'Can not convert'
,
data
);
throw
new
Error
(
'Unsupported data format'
);
};
export
function
sortTableData
(
data
:
TableData
,
sortIndex
?:
number
,
reverse
=
false
):
TableData
{
...
...
public/app/features/dashboard/dashgrid/DataPanel.test.tsx
0 → 100644
View file @
268dca5b
// Library
import
React
from
'react'
;
import
{
DataPanel
,
getProcessedTableData
}
from
'./DataPanel'
;
describe
(
'DataPanel'
,
()
=>
{
let
dataPanel
:
DataPanel
;
beforeEach
(()
=>
{
dataPanel
=
new
DataPanel
({
queries
:
[],
panelId
:
1
,
widthPixels
:
100
,
refreshCounter
:
1
,
datasource
:
'xxx'
,
children
:
r
=>
{
return
<
div
>
hello
</
div
>;
},
onError
:
(
message
,
error
)
=>
{},
});
});
it
(
'starts with unloaded state'
,
()
=>
{
expect
(
dataPanel
.
state
.
isFirstLoad
).
toBe
(
true
);
});
it
(
'converts timeseries to table skipping nulls'
,
()
=>
{
const
input1
=
{
target
:
'Field Name'
,
datapoints
:
[[
100
,
1
],
[
200
,
2
]],
};
const
input2
=
{
// without target
target
:
''
,
datapoints
:
[[
100
,
1
],
[
200
,
2
]],
};
const
data
=
getProcessedTableData
([
null
,
input1
,
input2
,
null
,
null
]);
expect
(
data
.
length
).
toBe
(
2
);
expect
(
data
[
0
].
columns
[
0
].
text
).
toBe
(
input1
.
target
);
expect
(
data
[
0
].
rows
).
toBe
(
input1
.
datapoints
);
// Default name
expect
(
data
[
1
].
columns
[
0
].
text
).
toEqual
(
'Value'
);
// Every colun should have a name and a type
for
(
const
table
of
data
)
{
for
(
const
column
of
table
.
columns
)
{
expect
(
column
.
text
).
toBeDefined
();
expect
(
column
.
type
).
toBeDefined
();
}
}
});
it
(
'supports null values from query OK'
,
()
=>
{
expect
(
getProcessedTableData
([
null
,
null
,
null
,
null
])).
toEqual
([]);
expect
(
getProcessedTableData
(
undefined
)).
toEqual
([]);
expect
(
getProcessedTableData
((
null
as
unknown
)
as
any
[])).
toEqual
([]);
expect
(
getProcessedTableData
([])).
toEqual
([]);
});
});
public/app/features/dashboard/dashgrid/DataPanel.tsx
View file @
268dca5b
...
...
@@ -15,6 +15,7 @@ import {
TimeRange
,
ScopedVars
,
toTableData
,
guessColumnTypes
,
}
from
'@grafana/ui'
;
interface
RenderProps
{
...
...
@@ -46,6 +47,25 @@ export interface State {
data
?:
TableData
[];
}
/**
* All panels will be passed tables that have our best guess at colum type set
*
* This is also used by PanelChrome for snapshot support
*/
export
function
getProcessedTableData
(
results
?:
any
[]):
TableData
[]
{
if
(
!
results
)
{
return
[];
}
const
tables
:
TableData
[]
=
[];
for
(
const
r
of
results
)
{
if
(
r
)
{
tables
.
push
(
guessColumnTypes
(
toTableData
(
r
)));
}
}
return
tables
;
}
export
class
DataPanel
extends
Component
<
Props
,
State
>
{
static
defaultProps
=
{
isVisible
:
true
,
...
...
@@ -147,7 +167,7 @@ export class DataPanel extends Component<Props, State> {
this
.
setState
({
loading
:
LoadingState
.
Done
,
response
:
resp
,
data
:
to
TableData
(
resp
.
data
),
data
:
getProcessed
TableData
(
resp
.
data
),
isFirstLoad
:
false
,
});
}
catch
(
err
)
{
...
...
public/app/features/dashboard/dashgrid/PanelChrome.tsx
View file @
268dca5b
...
...
@@ -19,11 +19,13 @@ import config from 'app/core/config';
// Types
import
{
DashboardModel
,
PanelModel
}
from
'../state'
;
import
{
PanelPlugin
}
from
'app/types'
;
import
{
DataQueryResponse
,
TimeRange
,
LoadingState
,
TableData
,
DataQueryError
,
toTableData
}
from
'@grafana/ui'
;
import
{
DataQueryResponse
,
TimeRange
,
LoadingState
,
TableData
,
DataQueryError
}
from
'@grafana/ui'
;
import
{
ScopedVars
}
from
'@grafana/ui'
;
import
templateSrv
from
'app/features/templating/template_srv'
;
import
{
getProcessedTableData
}
from
'./DataPanel'
;
const
DEFAULT_PLUGIN_ERROR
=
'Error in plugin'
;
export
interface
Props
{
...
...
@@ -139,7 +141,7 @@ export class PanelChrome extends PureComponent<Props, State> {
}
get
getDataForPanel
()
{
return
this
.
hasPanelSnapshot
?
to
TableData
(
this
.
props
.
panel
.
snapshotData
)
:
null
;
return
this
.
hasPanelSnapshot
?
getProcessed
TableData
(
this
.
props
.
panel
.
snapshotData
)
:
null
;
}
renderPanelPlugin
(
loading
:
LoadingState
,
data
:
TableData
[],
width
:
number
,
height
:
number
):
JSX
.
Element
{
...
...
public/app/plugins/panel/graph2/GraphPanel.tsx
View file @
268dca5b
...
...
@@ -2,14 +2,16 @@
import
_
from
'lodash'
;
import
React
,
{
PureComponent
}
from
'react'
;
// Utils
import
{
processTimeSeries
}
from
'@grafana/ui/src/utils'
;
// Components
import
{
Graph
}
from
'@grafana/ui'
;
// Types
import
{
PanelProps
,
NullValueMode
,
TimeSeriesVMs
}
from
'@grafana/ui/src/types'
;
import
{
Graph
,
PanelProps
,
NullValueMode
,
colors
,
TimeSeriesVMs
,
ColumnType
,
getFirstTimeColumn
,
processTimeSeries
,
}
from
'@grafana/ui'
;
import
{
Options
}
from
'./types'
;
interface
Props
extends
PanelProps
<
Options
>
{}
...
...
@@ -19,12 +21,30 @@ export class GraphPanel extends PureComponent<Props> {
const
{
data
,
timeRange
,
width
,
height
}
=
this
.
props
;
const
{
showLines
,
showBars
,
showPoints
}
=
this
.
props
.
options
;
let
vmSeries
:
TimeSeriesVMs
;
if
(
data
)
{
vmSeries
=
processTimeSeries
({
data
,
nullValueMode
:
NullValueMode
.
Ignore
,
});
const
vmSeries
:
TimeSeriesVMs
=
[];
for
(
const
table
of
data
)
{
const
timeColumn
=
getFirstTimeColumn
(
table
);
if
(
timeColumn
<
0
)
{
continue
;
}
for
(
let
i
=
0
;
i
<
table
.
columns
.
length
;
i
++
)
{
const
column
=
table
.
columns
[
i
];
// Show all numeric columns
if
(
column
.
type
===
ColumnType
.
number
)
{
const
tsvm
=
processTimeSeries
({
data
:
[
table
],
xColumn
:
timeColumn
,
yColumn
:
i
,
nullValueMode
:
NullValueMode
.
Null
,
})[
0
];
const
colorIndex
=
vmSeries
.
length
%
colors
.
length
;
tsvm
.
color
=
colors
[
colorIndex
];
vmSeries
.
push
(
tsvm
);
}
}
}
return
(
...
...
public/app/plugins/panel/singlestat2/SingleStatPanel.tsx
View file @
268dca5b
...
...
@@ -4,7 +4,7 @@ import React, { PureComponent, CSSProperties } from 'react';
// Types
import
{
SingleStatOptions
,
SingleStatBaseOptions
}
from
'./types'
;
import
{
DisplayValue
,
PanelProps
,
processTimeSeries
,
NullValueMode
}
from
'@grafana/ui'
;
import
{
DisplayValue
,
PanelProps
,
processTimeSeries
,
NullValueMode
,
ColumnType
}
from
'@grafana/ui'
;
import
{
config
}
from
'app/core/config'
;
import
{
getDisplayProcessor
}
from
'@grafana/ui'
;
import
{
ProcessedValuesRepeater
}
from
'./ProcessedValuesRepeater'
;
...
...
@@ -24,13 +24,30 @@ export const getSingleStatValues = (props: PanelProps<SingleStatBaseOptions>): D
theme
:
config
.
theme
,
});
return
processTimeSeries
({
data
,
nullValueMode
:
NullValueMode
.
Null
,
}).
map
((
series
,
index
)
=>
{
const
value
=
stat
!==
'name'
?
series
.
stats
[
stat
]
:
series
.
label
;
return
processor
(
value
);
});
const
values
:
DisplayValue
[]
=
[];
for
(
const
table
of
data
)
{
for
(
let
i
=
0
;
i
<
table
.
columns
.
length
;
i
++
)
{
const
column
=
table
.
columns
[
i
];
// Show all columns that are not 'time'
if
(
column
.
type
===
ColumnType
.
number
)
{
const
series
=
processTimeSeries
({
data
:
[
table
],
xColumn
:
i
,
yColumn
:
i
,
nullValueMode
:
NullValueMode
.
Null
,
})[
0
];
const
value
=
stat
!==
'name'
?
series
.
stats
[
stat
]
:
series
.
label
;
values
.
push
(
processor
(
value
));
}
}
}
if
(
values
.
length
===
0
)
{
throw
{
message
:
'Could not find numeric data'
};
}
return
values
;
};
export
class
SingleStatPanel
extends
PureComponent
<
PanelProps
<
SingleStatOptions
>>
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment