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
170a0df1
Unverified
Commit
170a0df1
authored
Apr 23, 2020
by
Andrej Ocenas
Committed by
GitHub
Apr 23, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Logs: Derived fields link design update (#23695)
parent
376765b3
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
156 additions
and
64 deletions
+156
-64
packages/grafana-ui/src/components/Logs/LogDetails.tsx
+13
-1
packages/grafana-ui/src/components/Logs/LogDetailsRow.tsx
+37
-19
packages/grafana-ui/src/components/Tags/Tag.tsx
+14
-7
packages/grafana-ui/src/utils/tags.ts
+6
-3
public/app/features/explore/utils/links.test.ts
+13
-3
public/app/features/explore/utils/links.ts
+26
-0
public/app/plugins/datasource/loki/result_transformer.test.ts
+10
-3
public/app/plugins/datasource/loki/result_transformer.ts
+37
-28
No files found.
packages/grafana-ui/src/components/Logs/LogDetails.tsx
View file @
170a0df1
...
@@ -121,7 +121,19 @@ class UnThemedLogDetails extends PureComponent<Props> {
...
@@ -121,7 +121,19 @@ class UnThemedLogDetails extends PureComponent<Props> {
}
}
return
acc
;
return
acc
;
},
{}
as
{
[
key
:
string
]:
FieldDef
});
},
{}
as
{
[
key
:
string
]:
FieldDef
});
return
Object
.
values
(
fieldsMap
);
const
allFields
=
Object
.
values
(
fieldsMap
);
allFields
.
sort
((
fieldA
,
fieldB
)
=>
{
if
(
fieldA
.
links
?.
length
&&
!
fieldB
.
links
?.
length
)
{
return
-
1
;
}
if
(
!
fieldA
.
links
?.
length
&&
fieldB
.
links
?.
length
)
{
return
1
;
}
return
fieldA
.
key
>
fieldB
.
key
?
1
:
fieldA
.
key
<
fieldB
.
key
?
-
1
:
0
;
});
return
allFields
;
});
});
getStatsForParsedField
=
(
key
:
string
)
=>
{
getStatsForParsedField
=
(
key
:
string
)
=>
{
...
...
packages/grafana-ui/src/components/Logs/LogDetailsRow.tsx
View file @
170a0df1
...
@@ -9,8 +9,8 @@ import { stylesFactory } from '../../themes/stylesFactory';
...
@@ -9,8 +9,8 @@ import { stylesFactory } from '../../themes/stylesFactory';
//Components
//Components
import
{
LogLabelStats
}
from
'./LogLabelStats'
;
import
{
LogLabelStats
}
from
'./LogLabelStats'
;
import
{
LinkButton
}
from
'../Button/Button'
;
import
{
IconButton
}
from
'../IconButton/IconButton'
;
import
{
IconButton
}
from
'../IconButton/IconButton'
;
import
{
Tag
}
from
'..'
;
export
interface
Props
extends
Themeable
{
export
interface
Props
extends
Themeable
{
parsedValue
:
string
;
parsedValue
:
string
;
...
@@ -116,27 +116,10 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
...
@@ -116,27 +116,10 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
{
links
&&
{
links
&&
links
.
map
(
link
=>
{
links
.
map
(
link
=>
{
return
(
return
(
<
span
key=
{
link
.
href
}
>
<>
<>
<
LinkButton
<
FieldLink
link=
{
link
}
/>
variant=
"link"
size=
{
'sm'
}
icon=
{
link
.
onClick
?
'list-ul'
:
'external-link-alt'
}
href=
{
link
.
href
}
target=
{
'_blank'
}
onClick=
{
link
.
onClick
&&
((
event
:
any
)
=>
{
if
(
!
(
event
.
ctrlKey
||
event
.
metaKey
||
event
.
shiftKey
)
&&
link
.
onClick
)
{
event
.
preventDefault
();
link
.
onClick
(
event
);
}
})
}
/>
</>
</>
</
span
>
);
);
})
}
})
}
{
showFieldsStats
&&
(
{
showFieldsStats
&&
(
...
@@ -154,5 +137,40 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
...
@@ -154,5 +137,40 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
}
}
}
}
const
getLinkStyles
=
stylesFactory
(()
=>
{
return
{
tag
:
css
`
margin-left: 6px;
font-size: 11px;
padding: 2px 6px;
`
,
};
});
type
FieldLinkProps
=
{
link
:
LinkModel
<
Field
>
;
};
function
FieldLink
({
link
}:
FieldLinkProps
)
{
const
styles
=
getLinkStyles
();
return
(
<
a
href=
{
link
.
href
}
target=
{
'_blank'
}
onClick=
{
link
.
onClick
?
event
=>
{
if
(
!
(
event
.
ctrlKey
||
event
.
metaKey
||
event
.
shiftKey
)
&&
link
.
onClick
)
{
event
.
preventDefault
();
link
.
onClick
(
event
);
}
}
:
undefined
}
>
<
Tag
name=
{
link
.
title
}
className=
{
styles
.
tag
}
colorIndex=
{
6
}
/>
</
a
>
);
}
export
const
LogDetailsRow
=
withTheme
(
UnThemedLogDetailsRow
);
export
const
LogDetailsRow
=
withTheme
(
UnThemedLogDetailsRow
);
LogDetailsRow
.
displayName
=
'LogDetailsRow'
;
LogDetailsRow
.
displayName
=
'LogDetailsRow'
;
packages/grafana-ui/src/components/Tags/Tag.tsx
View file @
170a0df1
...
@@ -2,19 +2,21 @@ import React, { forwardRef, HTMLAttributes } from 'react';
...
@@ -2,19 +2,21 @@ import React, { forwardRef, HTMLAttributes } from 'react';
import
{
cx
,
css
}
from
'emotion'
;
import
{
cx
,
css
}
from
'emotion'
;
import
{
GrafanaTheme
}
from
'@grafana/data'
;
import
{
GrafanaTheme
}
from
'@grafana/data'
;
import
{
useTheme
}
from
'../../themes'
;
import
{
useTheme
}
from
'../../themes'
;
import
{
getTagColorsFromName
}
from
'../../utils'
;
import
{
getTagColor
,
getTagColor
sFromName
}
from
'../../utils'
;
export
type
OnTagClick
=
(
name
:
string
,
event
:
React
.
MouseEvent
<
HTMLElement
>
)
=>
any
;
export
type
OnTagClick
=
(
name
:
string
,
event
:
React
.
MouseEvent
<
HTMLElement
>
)
=>
any
;
export
interface
Props
extends
Omit
<
HTMLAttributes
<
HTMLElement
>
,
'onClick'
>
{
export
interface
Props
extends
Omit
<
HTMLAttributes
<
HTMLElement
>
,
'onClick'
>
{
/** Name of the tag to display */
/** Name of the tag to display */
name
:
string
;
name
:
string
;
/** Use constant color from TAG_COLORS. Using index instead of color directly so we can match other styling. */
colorIndex
?:
number
;
onClick
?:
OnTagClick
;
onClick
?:
OnTagClick
;
}
}
export
const
Tag
=
forwardRef
<
HTMLElement
,
Props
>
(({
name
,
onClick
,
className
,
...
rest
},
ref
)
=>
{
export
const
Tag
=
forwardRef
<
HTMLElement
,
Props
>
(({
name
,
onClick
,
className
,
colorIndex
,
...
rest
},
ref
)
=>
{
const
theme
=
useTheme
();
const
theme
=
useTheme
();
const
styles
=
getTagStyles
(
theme
,
name
);
const
styles
=
getTagStyles
(
theme
,
name
,
colorIndex
);
const
onTagClick
=
(
event
:
React
.
MouseEvent
<
HTMLElement
>
)
=>
{
const
onTagClick
=
(
event
:
React
.
MouseEvent
<
HTMLElement
>
)
=>
{
if
(
onClick
)
{
if
(
onClick
)
{
...
@@ -29,20 +31,25 @@ export const Tag = forwardRef<HTMLElement, Props>(({ name, onClick, className, .
...
@@ -29,20 +31,25 @@ export const Tag = forwardRef<HTMLElement, Props>(({ name, onClick, className, .
);
);
});
});
const
getTagStyles
=
(
theme
:
GrafanaTheme
,
name
:
string
)
=>
{
const
getTagStyles
=
(
theme
:
GrafanaTheme
,
name
:
string
,
colorIndex
?:
number
)
=>
{
const
{
borderColor
,
color
}
=
getTagColorsFromName
(
name
);
let
colors
;
if
(
colorIndex
===
undefined
)
{
colors
=
getTagColorsFromName
(
name
);
}
else
{
colors
=
getTagColor
(
colorIndex
);
}
return
{
return
{
wrapper
:
css
`
wrapper
:
css
`
font-weight:
${
theme
.
typography
.
weight
.
semibold
}
;
font-weight:
${
theme
.
typography
.
weight
.
semibold
}
;
font-size:
${
theme
.
typography
.
size
.
sm
}
;
font-size:
${
theme
.
typography
.
size
.
sm
}
;
line-height:
${
theme
.
typography
.
lineHeight
.
xs
}
;
line-height:
${
theme
.
typography
.
lineHeight
.
xs
}
;
vertical-align: baseline;
vertical-align: baseline;
background-color:
${
color
}
;
background-color:
${
color
s
.
color
}
;
color:
${
theme
.
palette
.
white
}
;
color:
${
theme
.
palette
.
white
}
;
white-space: nowrap;
white-space: nowrap;
text-shadow: none;
text-shadow: none;
padding: 3px 6px;
padding: 3px 6px;
border: 1px solid
${
borderColor
}
;
border: 1px solid
${
colors
.
borderColor
}
;
border-radius:
${
theme
.
border
.
radius
.
md
}
;
border-radius:
${
theme
.
border
.
radius
.
md
}
;
:hover {
:hover {
...
...
packages/grafana-ui/src/utils/tags.ts
View file @
170a0df1
...
@@ -68,9 +68,12 @@ const TAG_BORDER_COLORS = [
...
@@ -68,9 +68,12 @@ const TAG_BORDER_COLORS = [
*/
*/
export
function
getTagColorsFromName
(
name
=
''
):
{
color
:
string
;
borderColor
:
string
}
{
export
function
getTagColorsFromName
(
name
=
''
):
{
color
:
string
;
borderColor
:
string
}
{
const
hash
=
djb2
(
name
.
toLowerCase
());
const
hash
=
djb2
(
name
.
toLowerCase
());
const
color
=
TAG_COLORS
[
Math
.
abs
(
hash
%
TAG_COLORS
.
length
)];
const
index
=
Math
.
abs
(
hash
%
TAG_COLORS
.
length
);
const
borderColor
=
TAG_BORDER_COLORS
[
Math
.
abs
(
hash
%
TAG_BORDER_COLORS
.
length
)];
return
getTagColor
(
index
);
return
{
color
,
borderColor
};
}
export
function
getTagColor
(
index
:
number
):
{
color
:
string
;
borderColor
:
string
}
{
return
{
color
:
TAG_COLORS
[
index
],
borderColor
:
TAG_BORDER_COLORS
[
index
]
};
}
}
function
djb2
(
str
:
string
)
{
function
djb2
(
str
:
string
)
{
...
...
public/app/features/explore/utils/links.test.ts
View file @
170a0df1
...
@@ -25,22 +25,32 @@ describe('getFieldLinksForExplore', () => {
...
@@ -25,22 +25,32 @@ describe('getFieldLinksForExplore', () => {
expect
(
links
[
0
].
title
).
toBe
(
'external'
);
expect
(
links
[
0
].
title
).
toBe
(
'external'
);
});
});
it
(
'returns generates title for external link'
,
()
=>
{
const
{
field
,
range
}
=
setup
({
title
:
''
,
url
:
'http://regionalhost'
,
});
const
links
=
getFieldLinksForExplore
(
field
,
0
,
jest
.
fn
(),
range
);
expect
(
links
[
0
].
href
).
toBe
(
'http://regionalhost'
);
expect
(
links
[
0
].
title
).
toBe
(
'regionalhost'
);
});
it
(
'returns correct link model for internal link'
,
()
=>
{
it
(
'returns correct link model for internal link'
,
()
=>
{
const
{
field
,
range
}
=
setup
({
const
{
field
,
range
}
=
setup
({
title
:
'
test
'
,
title
:
''
,
url
:
'query_1'
,
url
:
'query_1'
,
meta
:
{
meta
:
{
datasourceUid
:
'uid_1'
,
datasourceUid
:
'uid_1'
,
},
},
});
});
const
splitfn
=
jest
.
fn
();
const
splitfn
=
jest
.
fn
();
const
links
=
getFieldLinksForExplore
(
field
,
0
,
splitfn
,
range
);
const
links
=
getFieldLinksForExplore
(
field
,
0
,
splitfn
,
range
);
expect
(
links
[
0
].
href
).
toBe
(
expect
(
links
[
0
].
href
).
toBe
(
'/explore?left={"range":{"from":"now-1h","to":"now"},"datasource":"test_ds","queries":[{"query":"query_1"}],"mode":"Metrics","ui":{"showingGraph":true,"showingTable":true,"showingLogs":true}}'
'/explore?left={"range":{"from":"now-1h","to":"now"},"datasource":"test_ds","queries":[{"query":"query_1"}],"mode":"Metrics","ui":{"showingGraph":true,"showingTable":true,"showingLogs":true}}'
);
);
expect
(
links
[
0
].
title
).
toBe
(
'test'
);
expect
(
links
[
0
].
title
).
toBe
(
'test
_ds
'
);
links
[
0
].
onClick
({});
links
[
0
].
onClick
({});
expect
(
splitfn
).
toBeCalledWith
({
datasourceUid
:
'uid_1'
,
query
:
'query_1'
});
expect
(
splitfn
).
toBeCalledWith
({
datasourceUid
:
'uid_1'
,
query
:
'query_1'
});
});
});
...
...
public/app/features/explore/utils/links.ts
View file @
170a0df1
...
@@ -22,6 +22,10 @@ export function getFieldLinksForExplore(
...
@@ -22,6 +22,10 @@ export function getFieldLinksForExplore(
if
(
d
.
link
.
meta
?.
datasourceUid
)
{
if
(
d
.
link
.
meta
?.
datasourceUid
)
{
return
{
return
{
...
d
.
linkModel
,
...
d
.
linkModel
,
title
:
d
.
linkModel
.
title
||
getDataSourceSrv
().
getDataSourceSettingsByUid
(
d
.
link
.
meta
.
datasourceUid
)?.
name
||
'Unknown datasource'
,
onClick
:
()
=>
{
onClick
:
()
=>
{
splitOpenFn
({
splitOpenFn
({
datasourceUid
:
d
.
link
.
meta
.
datasourceUid
,
datasourceUid
:
d
.
link
.
meta
.
datasourceUid
,
...
@@ -37,6 +41,28 @@ export function getFieldLinksForExplore(
...
@@ -37,6 +41,28 @@ export function getFieldLinksForExplore(
href
:
generateInternalHref
(
d
.
link
.
meta
.
datasourceUid
,
d
.
linkModel
.
href
,
range
),
href
:
generateInternalHref
(
d
.
link
.
meta
.
datasourceUid
,
d
.
linkModel
.
href
,
range
),
};
};
}
}
if
(
!
d
.
linkModel
.
title
)
{
let
href
=
d
.
linkModel
.
href
;
// The URL constructor needs the url to have protocol
if
(
href
.
indexOf
(
'://'
)
<
0
)
{
// Doesn't really matter what protocol we use.
href
=
`http://
${
href
}
`
;
}
let
title
;
try
{
const
parsedUrl
=
new
URL
(
href
);
title
=
parsedUrl
.
hostname
;
}
catch
(
_e
)
{
// Should be good enough fallback, user probably did not input valid url.
title
=
href
;
}
return
{
...
d
.
linkModel
,
title
,
};
}
return
d
.
linkModel
;
return
d
.
linkModel
;
});
});
}
}
...
...
public/app/plugins/datasource/loki/result_transformer.test.ts
View file @
170a0df1
...
@@ -104,7 +104,7 @@ describe('loki result transformer', () => {
...
@@ -104,7 +104,7 @@ describe('loki result transformer', () => {
});
});
describe
(
'enhanceDataFrame'
,
()
=>
{
describe
(
'enhanceDataFrame'
,
()
=>
{
it
(
''
,
()
=>
{
it
(
'
adds links to fields
'
,
()
=>
{
const
df
=
new
MutableDataFrame
({
fields
:
[{
name
:
'line'
,
values
:
[
'nothing'
,
'trace1=1234'
,
'trace2=foo'
]
}]
});
const
df
=
new
MutableDataFrame
({
fields
:
[{
name
:
'line'
,
values
:
[
'nothing'
,
'trace1=1234'
,
'trace2=foo'
]
}]
});
enhanceDataFrame
(
df
,
{
enhanceDataFrame
(
df
,
{
derivedFields
:
[
derivedFields
:
[
...
@@ -123,8 +123,15 @@ describe('enhanceDataFrame', () => {
...
@@ -123,8 +123,15 @@ describe('enhanceDataFrame', () => {
expect
(
df
.
fields
.
length
).
toBe
(
3
);
expect
(
df
.
fields
.
length
).
toBe
(
3
);
const
fc
=
new
FieldCache
(
df
);
const
fc
=
new
FieldCache
(
df
);
expect
(
fc
.
getFieldByName
(
'trace1'
).
values
.
toArray
()).
toEqual
([
null
,
'1234'
,
null
]);
expect
(
fc
.
getFieldByName
(
'trace1'
).
values
.
toArray
()).
toEqual
([
null
,
'1234'
,
null
]);
expect
(
fc
.
getFieldByName
(
'trace1'
).
config
.
links
[
0
]).
toEqual
({
url
:
'http://localhost/${__value.raw}'
,
title
:
''
});
expect
(
fc
.
getFieldByName
(
'trace1'
).
config
.
links
[
0
]).
toEqual
({
url
:
'http://localhost/${__value.raw}'
,
title
:
''
,
});
expect
(
fc
.
getFieldByName
(
'trace2'
).
values
.
toArray
()).
toEqual
([
null
,
null
,
'foo'
]);
expect
(
fc
.
getFieldByName
(
'trace2'
).
values
.
toArray
()).
toEqual
([
null
,
null
,
'foo'
]);
expect
(
fc
.
getFieldByName
(
'trace2'
).
config
.
links
[
0
]).
toEqual
({
title
:
''
,
meta
:
{
datasourceUid
:
'uid'
}
});
expect
(
fc
.
getFieldByName
(
'trace2'
).
config
.
links
[
0
]).
toEqual
({
title
:
''
,
meta
:
{
datasourceUid
:
'uid'
},
});
});
});
});
});
public/app/plugins/datasource/loki/result_transformer.ts
View file @
170a0df1
...
@@ -12,6 +12,8 @@ import {
...
@@ -12,6 +12,8 @@ import {
findUniqueLabels
,
findUniqueLabels
,
FieldConfig
,
FieldConfig
,
DataFrameView
,
DataFrameView
,
DataLink
,
Field
,
}
from
'@grafana/data'
;
}
from
'@grafana/data'
;
import
templateSrv
from
'app/features/templating/template_srv'
;
import
templateSrv
from
'app/features/templating/template_srv'
;
...
@@ -28,6 +30,7 @@ import {
...
@@ -28,6 +30,7 @@ import {
LokiTailResponse
,
LokiTailResponse
,
LokiQuery
,
LokiQuery
,
LokiOptions
,
LokiOptions
,
DerivedFieldConfig
,
}
from
'./types'
;
}
from
'./types'
;
/**
/**
...
@@ -289,44 +292,50 @@ export const enhanceDataFrame = (dataFrame: DataFrame, config: LokiOptions | nul
...
@@ -289,44 +292,50 @@ export const enhanceDataFrame = (dataFrame: DataFrame, config: LokiOptions | nul
if
(
!
derivedFields
.
length
)
{
if
(
!
derivedFields
.
length
)
{
return
;
return
;
}
}
const
newFields
=
derivedFields
.
map
(
fieldFromDerivedFieldConfig
);
const
fields
=
derivedFields
.
reduce
((
acc
,
field
)
=>
{
const
newFieldsMap
=
_
.
keyBy
(
newFields
,
'name'
);
const
config
:
FieldConfig
=
{};
if
(
field
.
url
||
field
.
datasourceUid
)
{
config
.
links
=
[
{
url
:
field
.
url
,
title
:
''
,
meta
:
field
.
datasourceUid
?
{
datasourceUid
:
field
.
datasourceUid
,
}
:
undefined
,
},
];
}
const
dataFrameField
=
{
name
:
field
.
name
,
type
:
FieldType
.
string
,
config
,
values
:
new
ArrayVector
<
string
>
([]),
};
acc
[
field
.
name
]
=
dataFrameField
;
return
acc
;
},
{}
as
Record
<
string
,
any
>
);
const
view
=
new
DataFrameView
(
dataFrame
);
const
view
=
new
DataFrameView
(
dataFrame
);
view
.
forEach
((
row
:
{
line
:
string
})
=>
{
view
.
forEach
((
row
:
{
line
:
string
})
=>
{
for
(
const
field
of
derivedFields
)
{
for
(
const
field
of
derivedFields
)
{
const
logMatch
=
row
.
line
.
match
(
field
.
matcherRegex
);
const
logMatch
=
row
.
line
.
match
(
field
.
matcherRegex
);
fields
[
field
.
name
].
values
.
add
(
logMatch
&&
logMatch
[
1
]);
newFieldsMap
[
field
.
name
].
values
.
add
(
logMatch
&&
logMatch
[
1
]);
}
}
});
});
dataFrame
.
fields
=
[...
dataFrame
.
fields
,
...
Object
.
values
(
fields
)
];
dataFrame
.
fields
=
[...
dataFrame
.
fields
,
...
newFields
];
};
};
/**
* Transform derivedField config into dataframe field with config that contains link.
*/
function
fieldFromDerivedFieldConfig
(
derivedFieldConfig
:
DerivedFieldConfig
):
Field
<
any
,
ArrayVector
>
{
const
config
:
FieldConfig
=
{};
if
(
derivedFieldConfig
.
url
||
derivedFieldConfig
.
datasourceUid
)
{
const
link
:
Partial
<
DataLink
>
=
{
// We do not know what title to give here so we count on presentation layer to create a title from metadata.
title
:
''
,
url
:
derivedFieldConfig
.
url
,
};
// Having field.datasourceUid means it is an internal link.
if
(
derivedFieldConfig
.
datasourceUid
)
{
link
.
meta
=
{
datasourceUid
:
derivedFieldConfig
.
datasourceUid
,
};
}
config
.
links
=
[
link
as
DataLink
];
}
return
{
name
:
derivedFieldConfig
.
name
,
type
:
FieldType
.
string
,
config
,
// We are adding values later on
values
:
new
ArrayVector
<
string
>
([]),
};
}
export
function
rangeQueryResponseToTimeSeries
(
export
function
rangeQueryResponseToTimeSeries
(
response
:
LokiResponse
,
response
:
LokiResponse
,
query
:
LokiRangeQueryRequest
,
query
:
LokiRangeQueryRequest
,
...
...
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