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
76ba2db4
Unverified
Commit
76ba2db4
authored
Jan 15, 2020
by
Ryan McKinley
Committed by
GitHub
Jan 15, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
DataLinks: allow using values from other fields in the same row (#21478)
parent
3f957a37
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
351 additions
and
9 deletions
+351
-9
packages/grafana-data/src/field/fieldOverrides.ts
+7
-1
packages/grafana-ui/src/components/DataLinks/DataLinkSuggestions.tsx
+1
-0
public/app/features/panel/panellinks/fieldDisplayValuesProxy.test.ts
+66
-0
public/app/features/panel/panellinks/fieldDisplayValuesProxy.ts
+35
-0
public/app/features/panel/panellinks/linkSuppliers.test.ts
+139
-2
public/app/features/panel/panellinks/linkSuppliers.ts
+25
-3
public/app/features/panel/panellinks/link_srv.ts
+78
-3
No files found.
packages/grafana-data/src/field/fieldOverrides.ts
View file @
76ba2db4
...
...
@@ -11,6 +11,7 @@ import {
ThresholdsMode
,
FieldColorMode
,
ColorScheme
,
TimeZone
,
}
from
'../types'
;
import
{
fieldMatchers
,
ReducerID
,
reduceField
}
from
'../transformations'
;
import
{
FieldMatcher
}
from
'../types/transformations'
;
...
...
@@ -34,6 +35,7 @@ export interface ApplyFieldOverrideOptions {
fieldOptions
:
FieldConfigSource
;
replaceVariables
:
InterpolateFunction
;
theme
:
GrafanaTheme
;
timeZone
?:
TimeZone
;
autoMinMax
?:
boolean
;
}
...
...
@@ -164,7 +166,11 @@ export function applyFieldOverrides(options: ApplyFieldOverrideOptions): DataFra
type
,
};
// and set the display processor using it
f
.
display
=
getDisplayProcessor
({
field
:
f
,
theme
:
options
.
theme
});
f
.
display
=
getDisplayProcessor
({
field
:
f
,
theme
:
options
.
theme
,
timeZone
:
options
.
timeZone
,
});
return
f
;
});
...
...
packages/grafana-ui/src/components/DataLinks/DataLinkSuggestions.tsx
View file @
76ba2db4
...
...
@@ -11,6 +11,7 @@ import { stylesFactory } from '../../themes';
export
enum
VariableOrigin
{
Series
=
'series'
,
Field
=
'field'
,
Fields
=
'fields'
,
Value
=
'value'
,
BuiltIn
=
'built-in'
,
Template
=
'template'
,
...
...
public/app/features/panel/panellinks/fieldDisplayValuesProxy.test.ts
0 → 100644
View file @
76ba2db4
import
{
toDataFrame
,
applyFieldOverrides
,
GrafanaTheme
}
from
'@grafana/data'
;
import
{
getFieldDisplayValuesProxy
}
from
'./fieldDisplayValuesProxy'
;
describe
(
'getFieldDisplayValuesProxy'
,
()
=>
{
const
data
=
applyFieldOverrides
({
data
:
[
toDataFrame
({
fields
:
[
{
name
:
'Time'
,
values
:
[
1
,
2
,
3
]
},
{
name
:
'power'
,
values
:
[
100
,
200
,
300
],
config
:
{
title
:
'The Power'
,
},
},
{
name
:
'Last'
,
values
:
[
'a'
,
'b'
,
'c'
],
},
],
}),
],
fieldOptions
:
{
defaults
:
{},
overrides
:
[],
},
replaceVariables
:
(
val
:
string
)
=>
val
,
timeZone
:
'utc'
,
theme
:
{}
as
GrafanaTheme
,
autoMinMax
:
true
,
})[
0
];
it
(
'should define all display functions'
,
()
=>
{
// Field display should be set
for
(
const
field
of
data
.
fields
)
{
expect
(
field
.
display
).
toBeDefined
();
}
});
it
(
'should format the time values in UTC'
,
()
=>
{
// Test Proxies in general
const
p
=
getFieldDisplayValuesProxy
(
data
,
0
);
const
time
=
p
.
Time
;
expect
(
time
.
numeric
).
toEqual
(
1
);
expect
(
time
.
text
).
toEqual
(
'1970-01-01 00:00:00'
);
// Should get to the same values by name or index
const
time2
=
p
[
0
];
expect
(
time2
.
toString
()).
toEqual
(
time
.
toString
());
});
it
(
'Lookup by name, index, or title'
,
()
=>
{
const
p
=
getFieldDisplayValuesProxy
(
data
,
2
);
expect
(
p
.
power
.
numeric
).
toEqual
(
300
);
expect
(
p
[
'power'
].
numeric
).
toEqual
(
300
);
expect
(
p
[
'The Power'
].
numeric
).
toEqual
(
300
);
expect
(
p
[
1
].
numeric
).
toEqual
(
300
);
});
it
(
'should return undefined when missing'
,
()
=>
{
const
p
=
getFieldDisplayValuesProxy
(
data
,
0
);
expect
(
p
.
xyz
).
toBeUndefined
();
expect
(
p
[
100
]).
toBeUndefined
();
});
});
public/app/features/panel/panellinks/fieldDisplayValuesProxy.ts
0 → 100644
View file @
76ba2db4
import
{
DisplayValue
,
DataFrame
,
formattedValueToString
,
getDisplayProcessor
}
from
'@grafana/data'
;
import
{
config
}
from
'@grafana/runtime'
;
import
toNumber
from
'lodash/toNumber'
;
export
function
getFieldDisplayValuesProxy
(
frame
:
DataFrame
,
rowIndex
:
number
):
Record
<
string
,
DisplayValue
>
{
return
new
Proxy
({}
as
Record
<
string
,
DisplayValue
>
,
{
get
:
(
obj
:
any
,
key
:
string
)
=>
{
// 1. Match the name
let
field
=
frame
.
fields
.
find
(
f
=>
key
===
f
.
name
);
if
(
!
field
)
{
// 2. Match the array index
const
k
=
toNumber
(
key
);
field
=
frame
.
fields
[
k
];
}
if
(
!
field
)
{
// 3. Match the title
field
=
frame
.
fields
.
find
(
f
=>
key
===
f
.
config
.
title
);
}
if
(
!
field
)
{
return
undefined
;
}
if
(
!
field
.
display
)
{
// Lazy load the display processor
field
.
display
=
getDisplayProcessor
({
field
,
theme
:
config
.
theme
,
});
}
const
raw
=
field
.
values
.
get
(
rowIndex
);
const
disp
=
field
.
display
(
raw
);
disp
.
toString
=
()
=>
formattedValueToString
(
disp
);
return
disp
;
},
});
}
public/app/features/panel/panellinks/linkSuppliers.test.ts
View file @
76ba2db4
import
{
getLinksFromLogsField
}
from
'./linkSuppliers'
;
import
{
ArrayVector
,
dateTime
,
Field
,
FieldType
}
from
'@grafana/data'
;
import
{
getLinksFromLogsField
,
getFieldLinksSupplier
}
from
'./linkSuppliers'
;
import
{
ArrayVector
,
dateTime
,
Field
,
FieldType
,
toDataFrame
,
applyFieldOverrides
,
GrafanaTheme
,
FieldDisplay
,
DataFrameView
,
}
from
'@grafana/data'
;
import
{
getLinkSrv
,
LinkService
,
LinkSrv
,
setLinkSrv
}
from
'./link_srv'
;
import
{
TemplateSrv
}
from
'../../templating/template_srv'
;
import
{
TimeSrv
}
from
'../../dashboard/services/TimeSrv'
;
...
...
@@ -58,4 +68,131 @@ describe('getLinksFromLogsField', () => {
const
links
=
getLinksFromLogsField
(
field
,
2
);
expect
(
links
.
length
).
toBe
(
0
);
});
it
(
'links to items on the row'
,
()
=>
{
const
data
=
applyFieldOverrides
({
data
:
[
toDataFrame
({
name
:
'Hello Templates'
,
refId
:
'ZZZ'
,
fields
:
[
{
name
:
'Time'
,
values
:
[
1
,
2
,
3
]
},
{
name
:
'Power'
,
values
:
[
100.2000001
,
200
,
300
],
config
:
{
unit
:
'kW'
,
decimals
:
3
,
title
:
'TheTitle'
,
},
},
{
name
:
'Last'
,
values
:
[
'a'
,
'b'
,
'c'
],
config
:
{
links
:
[
{
title
:
'By Name'
,
url
:
'http://go/${__data.fields.Power}'
,
},
{
title
:
'By Index'
,
url
:
'http://go/${__data.fields[1]}'
,
},
{
title
:
'By Title'
,
url
:
'http://go/${__data.fields[TheTitle]}'
,
},
{
title
:
'Numeric Value'
,
url
:
'http://go/${__data.fields.Power.numeric}'
,
},
{
title
:
'Text (no suffix)'
,
url
:
'http://go/${__data.fields.Power.text}'
,
},
{
title
:
'Unknown Field'
,
url
:
'http://go/${__data.fields.XYZ}'
,
},
{
title
:
'Data Frame name'
,
url
:
'http://go/${__data.name}'
,
},
{
title
:
'Data Frame refId'
,
url
:
'http://go/${__data.refId}'
,
},
],
},
},
],
}),
],
fieldOptions
:
{
defaults
:
{},
overrides
:
[],
},
replaceVariables
:
(
val
:
string
)
=>
val
,
timeZone
:
'utc'
,
theme
:
{}
as
GrafanaTheme
,
autoMinMax
:
true
,
})[
0
];
const
rowIndex
=
0
;
const
colIndex
=
data
.
fields
.
length
-
1
;
const
field
=
data
.
fields
[
colIndex
];
const
fieldDisp
:
FieldDisplay
=
{
name
:
'hello'
,
field
:
field
.
config
,
view
:
new
DataFrameView
(
data
),
rowIndex
,
colIndex
,
display
:
field
.
display
!
(
field
.
values
.
get
(
rowIndex
)),
};
const
supplier
=
getFieldLinksSupplier
(
fieldDisp
);
const
links
=
supplier
.
getLinks
({}).
map
(
m
=>
{
return
{
title
:
m
.
title
,
href
:
m
.
href
,
};
});
expect
(
links
).
toMatchInlineSnapshot
(
`
Array [
Object {
"href": "http://go/100.200 kW",
"title": "By Name",
},
Object {
"href": "http://go/100.200 kW",
"title": "By Index",
},
Object {
"href": "http://go/100.200 kW",
"title": "By Title",
},
Object {
"href": "http://go/100.2000001",
"title": "Numeric Value",
},
Object {
"href": "http://go/100.200",
"title": "Text (no suffix)",
},
Object {
"href": "http://go/\${__data.fields.XYZ}",
"title": "Unknown Field",
},
Object {
"href": "http://go/Hello Templates",
"title": "Data Frame name",
},
Object {
"href": "http://go/ZZZ",
"title": "Data Frame refId",
},
]
`
);
});
});
public/app/features/panel/panellinks/linkSuppliers.ts
View file @
76ba2db4
...
...
@@ -8,8 +8,11 @@ import {
ScopedVar
,
Field
,
LinkModel
,
formattedValueToString
,
DisplayValue
,
}
from
'@grafana/data'
;
import
{
getLinkSrv
}
from
'./link_srv'
;
import
{
getFieldDisplayValuesProxy
}
from
'./fieldDisplayValuesProxy'
;
interface
SeriesVars
{
name
?:
string
;
...
...
@@ -29,10 +32,17 @@ interface ValueVars {
calc
?:
string
;
}
interface
DataViewVars
{
name
?:
string
;
refId
?:
string
;
fields
?:
Record
<
string
,
DisplayValue
>
;
}
interface
DataLinkScopedVars
extends
ScopedVars
{
__series
?:
ScopedVar
<
SeriesVars
>
;
__field
?:
ScopedVar
<
FieldVars
>
;
__value
?:
ScopedVar
<
ValueVars
>
;
__data
?:
ScopedVar
<
DataViewVars
>
;
}
/**
...
...
@@ -71,24 +81,36 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<Fi
};
}
if
(
value
.
rowIndex
)
{
if
(
!
isNaN
(
value
.
rowIndex
)
)
{
const
{
timeField
}
=
getTimeField
(
dataFrame
);
scopedVars
[
'__value'
]
=
{
value
:
{
raw
:
field
.
values
.
get
(
value
.
rowIndex
),
numeric
:
value
.
display
.
numeric
,
text
:
value
.
display
.
text
,
text
:
formattedValueToString
(
value
.
display
)
,
time
:
timeField
?
timeField
.
values
.
get
(
value
.
rowIndex
)
:
undefined
,
},
text
:
'Value'
,
};
// Expose other values on the row
if
(
value
.
view
)
{
scopedVars
[
'__data'
]
=
{
value
:
{
name
:
dataFrame
.
name
,
refId
:
dataFrame
.
refId
,
fields
:
getFieldDisplayValuesProxy
(
dataFrame
,
value
.
rowIndex
!
),
},
text
:
'Data'
,
};
}
}
else
{
// calculation
scopedVars
[
'__value'
]
=
{
value
:
{
raw
:
value
.
display
.
numeric
,
numeric
:
value
.
display
.
numeric
,
text
:
value
.
display
.
text
,
text
:
formattedValueToString
(
value
.
display
)
,
calc
:
value
.
name
,
},
text
:
'Value'
,
...
...
public/app/features/panel/panellinks/link_srv.ts
View file @
76ba2db4
...
...
@@ -6,7 +6,16 @@ import { appendQueryToUrl, toUrlParams } from 'app/core/utils/url';
import
{
sanitizeUrl
}
from
'app/core/utils/text'
;
import
{
getConfig
}
from
'app/core/config'
;
import
{
VariableSuggestion
,
VariableOrigin
,
DataLinkBuiltInVars
}
from
'@grafana/ui'
;
import
{
DataLink
,
KeyValue
,
deprecationWarning
,
LinkModel
,
DataFrame
,
ScopedVars
}
from
'@grafana/data'
;
import
{
DataLink
,
KeyValue
,
deprecationWarning
,
LinkModel
,
DataFrame
,
ScopedVars
,
FieldType
,
Field
,
}
from
'@grafana/data'
;
const
timeRangeVars
=
[
{
...
...
@@ -110,15 +119,81 @@ const getFieldVars = (dataFrames: DataFrame[]) => {
})),
];
};
const
getDataFrameVars
=
(
dataFrames
:
DataFrame
[])
=>
{
let
numeric
:
Field
=
undefined
;
let
title
:
Field
=
undefined
;
const
suggestions
:
VariableSuggestion
[]
=
[];
const
keys
:
KeyValue
<
true
>
=
{};
for
(
const
df
of
dataFrames
)
{
for
(
const
f
of
df
.
fields
)
{
if
(
keys
[
f
.
name
])
{
continue
;
}
suggestions
.
push
({
value
:
`__data.fields[
${
f
.
name
}
]`
,
label
:
`
${
f
.
name
}
`
,
documentation
:
`Formatted value for
${
f
.
name
}
on the same row`
,
origin
:
VariableOrigin
.
Fields
,
});
keys
[
f
.
name
]
=
true
;
if
(
!
numeric
&&
f
.
type
===
FieldType
.
number
)
{
numeric
=
f
;
}
if
(
!
title
&&
f
.
config
.
title
&&
f
.
config
.
title
!==
f
.
name
)
{
title
=
f
;
}
}
}
if
(
suggestions
.
length
)
{
suggestions
.
push
({
value
:
`__data.fields[0]`
,
label
:
`Select by index`
,
documentation
:
`Enter the field order`
,
origin
:
VariableOrigin
.
Fields
,
});
}
if
(
numeric
)
{
suggestions
.
push
({
value
:
`__data.fields[
${
numeric
.
name
}
].numeric`
,
label
:
`Show numeric value`
,
documentation
:
`the numeric field value`
,
origin
:
VariableOrigin
.
Fields
,
});
suggestions
.
push
({
value
:
`__data.fields[
${
numeric
.
name
}
].text`
,
label
:
`Show text value`
,
documentation
:
`the text value`
,
origin
:
VariableOrigin
.
Fields
,
});
}
if
(
title
)
{
suggestions
.
push
({
value
:
`__data.fields[
${
title
.
config
.
title
}
]`
,
label
:
`Select by title`
,
documentation
:
`Use the title to pick the field`
,
origin
:
VariableOrigin
.
Fields
,
});
}
return
suggestions
;
};
export
const
getDataLinksVariableSuggestions
=
(
dataFrames
:
DataFrame
[]):
VariableSuggestion
[]
=>
{
const
fieldVars
=
getFieldVars
(
dataFrames
);
const
valueTimeVar
=
{
value
:
`
${
DataLinkBuiltInVars
.
valueTime
}
`
,
label
:
'Time'
,
documentation
:
'Time value of the clicked datapoint (in ms epoch)'
,
origin
:
VariableOrigin
.
Value
,
};
return
[...
seriesVars
,
...
fieldVars
,
...
valueVars
,
valueTimeVar
,
...
getPanelLinksVariableSuggestions
()];
return
[
...
seriesVars
,
...
getFieldVars
(
dataFrames
),
...
valueVars
,
valueTimeVar
,
...
getDataFrameVars
(
dataFrames
),
...
getPanelLinksVariableSuggestions
(),
];
};
export
const
getCalculationValueDataLinksVariableSuggestions
=
(
dataFrames
:
DataFrame
[]):
VariableSuggestion
[]
=>
{
...
...
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