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
bf187044
Unverified
Commit
bf187044
authored
Jan 09, 2020
by
Ryan McKinley
Committed by
GitHub
Jan 09, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Inspector: support custom metadata display (#20854)
parent
0bf9e9bc
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
359 additions
and
17 deletions
+359
-17
packages/grafana-data/src/types/data.ts
+7
-0
packages/grafana-data/src/types/datasource.ts
+17
-0
public/app/features/dashboard/components/Inspector/PanelInspector.tsx
+158
-13
public/app/plugins/datasource/graphite/MetricTankMetaInspector.tsx
+58
-0
public/app/plugins/datasource/graphite/datasource.ts
+7
-4
public/app/plugins/datasource/graphite/meta.test.ts
+40
-0
public/app/plugins/datasource/graphite/meta.ts
+49
-0
public/app/plugins/datasource/graphite/module.ts
+2
-0
public/app/plugins/datasource/graphite/types.ts
+21
-0
No files found.
packages/grafana-data/src/types/data.ts
View file @
bf187044
...
...
@@ -19,6 +19,13 @@ export interface QueryResultMeta {
// Used in Explore to show limit applied to search result
limit
?:
number
;
// HACK: save the datassource name in the meta so we can load it from the response
// we should be able to find the datasource from the refId
datasource
?:
string
;
// DatasSource Specific Values
custom
?:
Record
<
string
,
any
>
;
}
export
interface
QueryResultBase
{
...
...
packages/grafana-data/src/types/datasource.ts
View file @
bf187044
...
...
@@ -91,6 +91,11 @@ export class DataSourcePlugin<
return
this
;
}
setMetadataInspector
(
MetadataInspector
:
ComponentType
<
MetadataInspectorProps
<
DSType
,
TQuery
,
TOptions
>>
)
{
this
.
components
.
MetadataInspector
=
MetadataInspector
;
return
this
;
}
setComponentsFromLegacyExports
(
pluginExports
:
any
)
{
this
.
angularConfigCtrl
=
pluginExports
.
ConfigCtrl
;
...
...
@@ -137,6 +142,7 @@ export interface DataSourcePluginComponents<
ExploreLogsQueryField
?:
ComponentType
<
ExploreQueryFieldProps
<
DSType
,
TQuery
,
TOptions
>>
;
ExploreStartPage
?:
ComponentType
<
ExploreStartPageProps
>
;
ConfigEditor
?:
ComponentType
<
DataSourcePluginOptionsEditorProps
<
TOptions
>>
;
MetadataInspector
?:
ComponentType
<
MetadataInspectorProps
<
DSType
,
TQuery
,
TOptions
>>
;
}
// Only exported for tests
...
...
@@ -331,6 +337,17 @@ export function updateDatasourcePluginResetKeyOption(props: DataSourcePluginOpti
props
.
onOptionsChange
(
config
);
}
export
interface
MetadataInspectorProps
<
DSType
extends
DataSourceApi
<
TQuery
,
TOptions
>
,
TQuery
extends
DataQuery
=
DataQuery
,
TOptions
extends
DataSourceJsonData
=
DataSourceJsonData
>
{
datasource
:
DSType
;
// All Data from this DataSource
data
:
DataFrame
[];
}
export
interface
QueryEditorProps
<
DSType
extends
DataSourceApi
<
TQuery
,
TOptions
>
,
TQuery
extends
DataQuery
=
DataQuery
,
...
...
public/app/features/dashboard/components/Inspector/PanelInspector.tsx
View file @
bf187044
// Libraries
import
React
,
{
PureComponent
}
from
'react'
;
import
{
DashboardModel
,
PanelModel
}
from
'app/features/dashboard/state'
;
import
{
Drawer
,
JSONFormatter
}
from
'@grafana/ui'
;
import
{
css
}
from
'emotion'
;
import
{
getLocationSrv
}
from
'@grafana/runtime'
;
import
{
JSONFormatter
,
Drawer
,
Select
,
Table
}
from
'@grafana/ui'
;
import
{
getLocationSrv
,
getDataSourceSrv
}
from
'@grafana/runtime'
;
import
{
DataFrame
,
DataSourceApi
,
SelectableValue
,
applyFieldOverrides
}
from
'@grafana/data'
;
import
{
config
}
from
'app/core/config'
;
interface
Props
{
dashboard
:
DashboardModel
;
panel
:
PanelModel
;
}
export
class
PanelInspector
extends
PureComponent
<
Props
>
{
enum
InspectTab
{
Data
=
'data'
,
Raw
=
'raw'
,
Issue
=
'issue'
,
Meta
=
'meta'
,
// When result metadata exists
}
interface
State
{
// The last raw response
last
?:
any
;
// Data frem the last response
data
:
DataFrame
[];
// The selected data frame
selected
:
number
;
// The Selected Tab
tab
:
InspectTab
;
// If the datasource supports custom metadata
metaDS
?:
DataSourceApi
;
}
export
class
PanelInspector
extends
PureComponent
<
Props
,
State
>
{
constructor
(
props
:
Props
)
{
super
(
props
);
this
.
state
=
{
data
:
[],
selected
:
0
,
tab
:
InspectTab
.
Data
,
};
}
async
componentDidMount
()
{
const
{
panel
}
=
this
.
props
;
if
(
!
panel
)
{
this
.
onDismiss
();
// Try to close the component
return
;
}
// TODO? should we get the result with an observable once?
const
lastResult
=
(
panel
.
getQueryRunner
()
as
any
).
lastResult
;
if
(
!
lastResult
)
{
this
.
onDismiss
();
// Usually opened from refresh?
return
;
}
// Find the first DataSource wanting to show custom metadata
let
metaDS
:
DataSourceApi
;
const
data
=
lastResult
?.
series
as
DataFrame
[];
if
(
data
)
{
for
(
const
frame
of
data
)
{
const
key
=
frame
.
meta
?.
datasource
;
if
(
key
)
{
const
ds
=
await
getDataSourceSrv
().
get
(
key
);
if
(
ds
&&
ds
.
components
.
MetadataInspector
)
{
metaDS
=
ds
;
break
;
}
}
}
}
// Set last result, but no metadata inspector
this
.
setState
({
last
:
lastResult
,
data
,
metaDS
,
});
}
onDismiss
=
()
=>
{
getLocationSrv
().
update
({
query
:
{
inspect
:
null
},
...
...
@@ -19,24 +90,98 @@ export class PanelInspector extends PureComponent<Props> {
});
};
onSelectTab
=
(
item
:
SelectableValue
<
InspectTab
>
)
=>
{
this
.
setState
({
tab
:
item
.
value
||
InspectTab
.
Data
});
};
onSelectedFrameChanged
=
(
item
:
SelectableValue
<
number
>
)
=>
{
this
.
setState
({
selected
:
item
.
value
||
0
});
};
renderMetadataInspector
()
{
const
{
metaDS
,
data
}
=
this
.
state
;
if
(
!
metaDS
||
!
metaDS
.
components
?.
MetadataInspector
)
{
return
<
div
>
No Metadata Inspector
</
div
>;
}
return
<
metaDS
.
components
.
MetadataInspector
datasource=
{
metaDS
}
data=
{
data
}
/>;
}
renderDataTab
()
{
const
{
data
,
selected
}
=
this
.
state
;
if
(
!
data
||
!
data
.
length
)
{
return
<
div
>
No Data
</
div
>;
}
const
choices
=
data
.
map
((
frame
,
index
)
=>
{
return
{
value
:
index
,
label
:
`
${
frame
.
name
}
(
${
index
}
)`
,
};
});
// Apply dummy styles
const
processed
=
applyFieldOverrides
({
data
,
theme
:
config
.
theme
,
fieldOptions
:
{
defaults
:
{},
overrides
:
[]
},
replaceVariables
:
(
value
:
string
)
=>
{
return
value
;
},
});
return
(
<
div
>
{
choices
.
length
>
1
&&
(
<
div
>
<
Select
options=
{
choices
}
value=
{
choices
.
find
(
t
=>
t
.
value
===
selected
)
}
onChange=
{
this
.
onSelectedFrameChanged
}
/>
</
div
>
)
}
<
div
style=
{
{
border
:
'1px solid #666'
}
}
>
<
Table
width=
{
330
}
height=
{
400
}
data=
{
processed
[
selected
]
}
/>
</
div
>
</
div
>
);
}
renderIssueTab
()
{
return
<
div
>
TODO: show issue form
</
div
>;
}
render
()
{
const
{
panel
}
=
this
.
props
;
const
{
last
,
tab
}
=
this
.
state
;
if
(
!
panel
)
{
this
.
onDismiss
();
// Try to close the component
return
null
;
}
const
bodyStyle
=
css
`
max-height: 70vh;
overflow-y: scroll;
`
;
// TODO? should we get the result with an observable once?
const
data
=
(
panel
.
getQueryRunner
()
as
any
).
lastResult
;
const
tabs
=
[
{
label
:
'Data'
,
value
:
InspectTab
.
Data
},
{
label
:
'Issue'
,
value
:
InspectTab
.
Issue
},
{
label
:
'Raw JSON'
,
value
:
InspectTab
.
Raw
},
];
if
(
this
.
state
.
metaDS
)
{
tabs
.
push
({
label
:
'Meta Data'
,
value
:
InspectTab
.
Meta
});
}
return
(
<
Drawer
title=
{
panel
.
title
}
onClose=
{
this
.
onDismiss
}
>
<
div
className=
{
bodyStyle
}
>
<
JSONFormatter
json=
{
data
}
open=
{
2
}
/>
<
Select
options=
{
tabs
}
value=
{
tabs
.
find
(
t
=>
t
.
value
===
tab
)
}
onChange=
{
this
.
onSelectTab
}
/>
{
tab
===
InspectTab
.
Data
&&
this
.
renderDataTab
()
}
{
tab
===
InspectTab
.
Meta
&&
this
.
renderMetadataInspector
()
}
{
tab
===
InspectTab
.
Issue
&&
this
.
renderIssueTab
()
}
{
tab
===
InspectTab
.
Raw
&&
(
<
div
>
<
JSONFormatter
json=
{
last
}
open=
{
2
}
/>
</
div
>
)
}
</
Drawer
>
);
}
...
...
public/app/plugins/datasource/graphite/MetricTankMetaInspector.tsx
0 → 100644
View file @
bf187044
import
React
,
{
PureComponent
}
from
'react'
;
import
{
MetadataInspectorProps
,
DataFrame
}
from
'@grafana/data'
;
import
{
GraphiteDatasource
}
from
'./datasource'
;
import
{
GraphiteQuery
,
GraphiteOptions
,
MetricTankMeta
,
MetricTankResultMeta
}
from
'./types'
;
import
{
parseSchemaRetentions
}
from
'./meta'
;
export
type
Props
=
MetadataInspectorProps
<
GraphiteDatasource
,
GraphiteQuery
,
GraphiteOptions
>
;
export
interface
State
{
index
:
number
;
}
export
class
MetricTankMetaInspector
extends
PureComponent
<
Props
,
State
>
{
state
=
{
index
:
0
};
renderInfo
=
(
info
:
MetricTankResultMeta
,
frame
:
DataFrame
)
=>
{
const
buckets
=
parseSchemaRetentions
(
info
[
'schema-retentions'
]);
return
(
<
div
>
<
h3
>
Info
</
h3
>
<
table
>
<
tbody
>
{
buckets
.
map
(
row
=>
(
<
tr
key=
{
row
.
interval
}
>
<
td
>
{
row
.
interval
}
</
td
>
<
td
>
{
row
.
retention
}
</
td
>
<
td
>
{
row
.
chunkspan
}
</
td
>
<
td
>
{
row
.
numchunks
}
</
td
>
<
td
>
{
row
.
ready
}
</
td
>
</
tr
>
))
}
</
tbody
>
</
table
>
<
pre
>
{
JSON
.
stringify
(
info
,
null
,
2
)
}
</
pre
>
</
div
>
);
};
render
()
{
const
{
data
}
=
this
.
props
;
if
(
!
data
||
!
data
.
length
)
{
return
<
div
>
No Metadata
</
div
>;
}
const
frame
=
data
[
this
.
state
.
index
];
const
meta
=
frame
.
meta
?.
custom
as
MetricTankMeta
;
if
(
!
meta
||
!
meta
.
info
)
{
return
<>
No Metadatata on DataFrame
</>;
}
return
(
<
div
>
<
h3
>
MetricTank Request
</
h3
>
<
pre
>
{
JSON
.
stringify
(
meta
.
request
,
null
,
2
)
}
</
pre
>
{
meta
.
info
.
map
(
info
=>
this
.
renderInfo
(
info
,
frame
))
}
</
div
>
);
}
}
public/app/plugins/datasource/graphite/datasource.ts
View file @
bf187044
...
...
@@ -103,7 +103,7 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
}
}
convertResponseToDataFrames
(
result
:
any
):
DataQueryResponse
{
convertResponseToDataFrames
=
(
result
:
any
):
DataQueryResponse
=>
{
const
data
:
DataFrame
[]
=
[];
if
(
!
result
||
!
result
.
data
)
{
return
{
data
};
...
...
@@ -124,14 +124,17 @@ export class GraphiteDatasource extends DataSourceApi<GraphiteQuery, GraphiteOpt
// Metrictank metadata
if
(
s
.
meta
)
{
frame
.
meta
=
{
metrictank
:
s
.
meta
,
// array of metadata
metrictankReq
:
result
.
data
.
meta
,
// info on the request
datasource
:
this
.
name
,
custom
:
{
request
:
result
.
data
.
meta
,
// info for the whole request
info
:
s
.
meta
,
// Array of metadata
},
};
}
data
.
push
(
frame
);
}
return
{
data
};
}
}
;
parseTags
(
tagString
:
string
)
{
let
tags
:
string
[]
=
[];
...
...
public/app/plugins/datasource/graphite/meta.test.ts
0 → 100644
View file @
bf187044
import
{
parseSchemaRetentions
}
from
'./meta'
;
describe
(
'metadata parsing'
,
()
=>
{
it
(
'should parse schema retentions'
,
()
=>
{
const
retentions
=
'1s:35d:20min:5:1542274085,1min:38d:2h:1:true,10min:120d:6h:1:true,2h:2y:6h:2'
;
const
info
=
parseSchemaRetentions
(
retentions
);
expect
(
info
).
toMatchInlineSnapshot
(
`
Array [
Object {
"chunkspan": "20min",
"interval": "1s",
"numchunks": 5,
"ready": 1542274085,
"retention": "35d",
},
Object {
"chunkspan": "2h",
"interval": "1min",
"numchunks": 1,
"ready": true,
"retention": "38d",
},
Object {
"chunkspan": "6h",
"interval": "10min",
"numchunks": 1,
"ready": true,
"retention": "120d",
},
Object {
"chunkspan": "6h",
"interval": "2h",
"numchunks": 2,
"ready": undefined,
"retention": "2y",
},
]
`
);
});
});
public/app/plugins/datasource/graphite/meta.ts
0 → 100644
View file @
bf187044
export
interface
MetricTankResultMeta
{
'schema-name'
:
string
;
'schema-retentions'
:
string
;
//"1s:35d:20min:5:1542274085,1min:38d:2h:1:true,10min:120d:6h:1:true,2h:2y:6h:2",
}
// https://github.com/grafana/metrictank/blob/master/scripts/config/storage-schemas.conf#L15-L46
export
interface
RetentionInfo
{
interval
:
string
;
retention
?:
string
;
chunkspan
?:
string
;
numchunks
?:
number
;
ready
?:
boolean
|
number
;
// whether, or as of what data timestamp, the archive is ready for querying.
}
function
toInteger
(
val
?:
string
):
number
|
undefined
{
if
(
val
)
{
return
parseInt
(
val
,
10
);
}
return
undefined
;
}
function
toBooleanOrTimestamp
(
val
?:
string
):
number
|
boolean
|
undefined
{
if
(
val
)
{
if
(
val
===
'true'
)
{
return
true
;
}
if
(
val
===
'false'
)
{
return
false
;
}
return
parseInt
(
val
,
10
);
}
return
undefined
;
}
export
function
parseSchemaRetentions
(
spec
:
string
):
RetentionInfo
[]
{
if
(
!
spec
)
{
return
[];
}
return
spec
.
split
(
','
).
map
(
str
=>
{
const
vals
=
str
.
split
(
':'
);
return
{
interval
:
vals
[
0
],
retention
:
vals
[
1
],
chunkspan
:
vals
[
2
],
numchunks
:
toInteger
(
vals
[
3
]),
ready
:
toBooleanOrTimestamp
(
vals
[
4
]),
};
});
}
public/app/plugins/datasource/graphite/module.ts
View file @
bf187044
...
...
@@ -2,6 +2,7 @@ import { GraphiteDatasource } from './datasource';
import
{
GraphiteQueryCtrl
}
from
'./query_ctrl'
;
import
{
DataSourcePlugin
}
from
'@grafana/data'
;
import
{
ConfigEditor
}
from
'./configuration/ConfigEditor'
;
import
{
MetricTankMetaInspector
}
from
'./MetricTankMetaInspector'
;
class
AnnotationsQueryCtrl
{
static
templateUrl
=
'partials/annotations.editor.html'
;
...
...
@@ -10,4 +11,5 @@ class AnnotationsQueryCtrl {
export
const
plugin
=
new
DataSourcePlugin
(
GraphiteDatasource
)
.
setQueryCtrl
(
GraphiteQueryCtrl
)
.
setConfigEditor
(
ConfigEditor
)
.
setMetadataInspector
(
MetricTankMetaInspector
)
.
setAnnotationQueryCtrl
(
AnnotationsQueryCtrl
);
public/app/plugins/datasource/graphite/types.ts
View file @
bf187044
...
...
@@ -13,3 +13,24 @@ export enum GraphiteType {
Default
=
'default'
,
Metrictank
=
'metrictank'
,
}
export
interface
MetricTankRequestMeta
{
[
key
:
string
]:
any
;
// TODO -- fill this with real values from metrictank
}
export
interface
MetricTankResultMeta
{
'schema-name'
:
string
;
'schema-retentions'
:
string
;
//"1s:35d:20min:5:1542274085,1min:38d:2h:1:true,10min:120d:6h:1:true,2h:2y:6h:2",
'archive-read'
:
number
;
'archive-interval'
:
number
;
'aggnum-norm'
:
number
;
'consolidate-normfetch'
:
string
;
//"MaximumConsolidator",
'aggnum-rc'
:
number
;
'consolidate-rc'
:
string
;
//"MaximumConsolidator",
count
:
number
;
}
export
interface
MetricTankMeta
{
request
:
MetricTankRequestMeta
;
info
:
MetricTankResultMeta
[];
}
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