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
54a3f5fd
Unverified
Commit
54a3f5fd
authored
Mar 04, 2020
by
David
Committed by
GitHub
Mar 04, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Loki: use series API for stream facetting (#21332)
parent
2cf538a4
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
248 additions
and
99 deletions
+248
-99
public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap
+34
-2
public/app/plugins/datasource/loki/components/useLokiSyntaxAndLabels.test.ts
+2
-2
public/app/plugins/datasource/loki/datasource.ts
+3
-5
public/app/plugins/datasource/loki/language_provider.test.ts
+37
-19
public/app/plugins/datasource/loki/language_provider.ts
+146
-56
public/app/plugins/datasource/loki/mocks.ts
+26
-15
No files found.
public/app/plugins/datasource/loki/components/__snapshots__/LokiExploreQueryEditor.test.tsx.snap
View file @
54a3f5fd
...
@@ -56,11 +56,43 @@ exports[`LokiExploreQueryEditor should render component 1`] = `
...
@@ -56,11 +56,43 @@ exports[`LokiExploreQueryEditor should render component 1`] = `
"languageProvider": LokiLanguageProvider {
"languageProvider": LokiLanguageProvider {
"cleanText": [Function],
"cleanText": [Function],
"datasource": [Circular],
"datasource": [Circular],
"fetchSeriesLabels": [Function],
"getBeginningCompletionItems": [Function],
"getBeginningCompletionItems": [Function],
"getTermCompletionItems": [Function],
"getTermCompletionItems": [Function],
"labelKeys": Object {},
"labelKeys": Array [],
"labelValues": Object {},
"labelsCache": LRUCache {
Symbol(max): 10,
Symbol(lengthCalculator): [Function],
Symbol(allowStale): false,
Symbol(maxAge): 0,
Symbol(dispose): undefined,
Symbol(noDisposeOnSet): false,
Symbol(updateAgeOnGet): false,
Symbol(cache): Map {},
Symbol(lruList): Yallist {
"head": null,
"length": 0,
"tail": null,
},
Symbol(length): 0,
},
"request": [Function],
"request": [Function],
"seriesCache": LRUCache {
Symbol(max): 10,
Symbol(lengthCalculator): [Function],
Symbol(allowStale): false,
Symbol(maxAge): 0,
Symbol(dispose): undefined,
Symbol(noDisposeOnSet): false,
Symbol(updateAgeOnGet): false,
Symbol(cache): Map {},
Symbol(lruList): Yallist {
"head": null,
"length": 0,
"tail": null,
},
Symbol(length): 0,
},
"start": [Function],
"start": [Function],
},
},
"metadataRequest": [Function],
"metadataRequest": [Function],
...
...
public/app/plugins/datasource/loki/components/useLokiSyntaxAndLabels.test.ts
View file @
54a3f5fd
...
@@ -59,9 +59,9 @@ describe('useLokiSyntax hook', () => {
...
@@ -59,9 +59,9 @@ describe('useLokiSyntax hook', () => {
await
waitForNextUpdate
();
await
waitForNextUpdate
();
expect
(
result
.
current
.
logLabelOptions
).
toEqual
(
logLabelOptionsMock2
);
expect
(
result
.
current
.
logLabelOptions
).
toEqual
(
logLabelOptionsMock2
);
languageProvider
.
fetchLabelValues
=
(
key
:
string
)
=>
{
languageProvider
.
fetchLabelValues
=
(
key
:
string
,
absoluteRange
:
AbsoluteTimeRange
)
=>
{
languageProvider
.
logLabelOptions
=
logLabelOptionsMock3
;
languageProvider
.
logLabelOptions
=
logLabelOptionsMock3
;
return
Promise
.
resolve
();
return
Promise
.
resolve
(
[]
);
};
};
act
(()
=>
result
.
current
.
setActiveOption
([
activeOptionMock
]));
act
(()
=>
result
.
current
.
setActiveOption
([
activeOptionMock
]));
...
...
public/app/plugins/datasource/loki/datasource.ts
View file @
54a3f5fd
...
@@ -390,9 +390,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
...
@@ -390,9 +390,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
async
metadataRequest
(
url
:
string
,
params
?:
Record
<
string
,
string
>
)
{
async
metadataRequest
(
url
:
string
,
params
?:
Record
<
string
,
string
>
)
{
const
res
=
await
this
.
_request
(
url
,
params
,
{
silent
:
true
}).
toPromise
();
const
res
=
await
this
.
_request
(
url
,
params
,
{
silent
:
true
}).
toPromise
();
return
{
return
res
.
data
.
data
||
res
.
data
.
values
||
[];
data
:
{
data
:
res
.
data
.
data
||
res
.
data
.
values
||
[]
},
};
}
}
async
metricFindQuery
(
query
:
string
)
{
async
metricFindQuery
(
query
:
string
)
{
...
@@ -423,7 +421,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
...
@@ -423,7 +421,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
async
labelNamesQuery
()
{
async
labelNamesQuery
()
{
const
url
=
(
await
this
.
getVersion
())
===
'v0'
?
`
${
LEGACY_LOKI_ENDPOINT
}
/label`
:
`
${
LOKI_ENDPOINT
}
/label`
;
const
url
=
(
await
this
.
getVersion
())
===
'v0'
?
`
${
LEGACY_LOKI_ENDPOINT
}
/label`
:
`
${
LOKI_ENDPOINT
}
/label`
;
const
result
=
await
this
.
metadataRequest
(
url
);
const
result
=
await
this
.
metadataRequest
(
url
);
return
result
.
data
.
data
.
map
((
value
:
string
)
=>
({
text
:
value
}));
return
result
.
map
((
value
:
string
)
=>
({
text
:
value
}));
}
}
async
labelValuesQuery
(
label
:
string
)
{
async
labelValuesQuery
(
label
:
string
)
{
...
@@ -432,7 +430,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
...
@@ -432,7 +430,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
?
`
${
LEGACY_LOKI_ENDPOINT
}
/label/
${
label
}
/values`
?
`
${
LEGACY_LOKI_ENDPOINT
}
/label/
${
label
}
/values`
:
`
${
LOKI_ENDPOINT
}
/label/
${
label
}
/values`
;
:
`
${
LOKI_ENDPOINT
}
/label/
${
label
}
/values`
;
const
result
=
await
this
.
metadataRequest
(
url
);
const
result
=
await
this
.
metadataRequest
(
url
);
return
result
.
data
.
data
.
map
((
value
:
string
)
=>
({
text
:
value
}));
return
result
.
map
((
value
:
string
)
=>
({
text
:
value
}));
}
}
interpolateQueryExpr
(
value
:
any
,
variable
:
any
)
{
interpolateQueryExpr
(
value
:
any
,
variable
:
any
)
{
...
...
public/app/plugins/datasource/loki/language_provider.test.ts
View file @
54a3f5fd
import
Plain
from
'slate-plain-serializer'
;
import
Plain
from
'slate-plain-serializer'
;
import
{
Editor
as
SlateEditor
}
from
'slate'
;
import
LanguageProvider
,
{
LABEL_REFRESH_INTERVAL
,
LokiHistoryItem
,
rangeToParams
}
from
'./language_provider'
;
import
LanguageProvider
,
{
LABEL_REFRESH_INTERVAL
,
LokiHistoryItem
,
rangeToParams
}
from
'./language_provider'
;
import
{
AbsoluteTimeRange
}
from
'@grafana/data'
;
import
{
AbsoluteTimeRange
}
from
'@grafana/data'
;
...
@@ -85,34 +84,53 @@ describe('Language completion provider', () => {
...
@@ -85,34 +84,53 @@ describe('Language completion provider', () => {
});
});
});
});
describe
(
'label suggestions'
,
()
=>
{
describe
(
'label key suggestions'
,
()
=>
{
it
(
'returns default label suggestions on label context'
,
async
()
=>
{
it
(
'returns all label suggestions on empty selector'
,
async
()
=>
{
const
instance
=
new
LanguageProvider
(
datasource
);
const
datasource
=
makeMockLokiDatasource
({
label1
:
[],
label2
:
[]
});
const
value
=
Plain
.
deserialize
(
'{}'
);
const
provider
=
await
getLanguageProvider
(
datasource
);
const
ed
=
new
SlateEditor
({
value
});
const
input
=
createTypeaheadInput
(
'{}'
,
''
,
''
,
1
);
const
valueWithSelection
=
ed
.
moveForward
(
1
).
value
;
const
result
=
await
provider
.
provideCompletionItems
(
input
,
{
absoluteRange
:
rangeMock
});
const
result
=
await
instance
.
provideCompletionItems
(
{
text
:
''
,
prefix
:
''
,
wrapperClasses
:
[
'context-labels'
],
value
:
valueWithSelection
,
},
{
absoluteRange
:
rangeMock
}
);
expect
(
result
.
context
).
toBe
(
'context-labels'
);
expect
(
result
.
context
).
toBe
(
'context-labels'
);
expect
(
result
.
suggestions
).
toEqual
([{
items
:
[{
label
:
'
job'
},
{
label
:
'namespace
'
}],
label
:
'Labels'
}]);
expect
(
result
.
suggestions
).
toEqual
([{
items
:
[{
label
:
'
label1'
},
{
label
:
'label2
'
}],
label
:
'Labels'
}]);
});
});
it
(
'returns
label suggestions from Loki
'
,
async
()
=>
{
it
(
'returns
all label suggestions on selector when starting to type
'
,
async
()
=>
{
const
datasource
=
makeMockLokiDatasource
({
label1
:
[],
label2
:
[]
});
const
datasource
=
makeMockLokiDatasource
({
label1
:
[],
label2
:
[]
});
const
provider
=
await
getLanguageProvider
(
datasource
);
const
provider
=
await
getLanguageProvider
(
datasource
);
const
input
=
createTypeaheadInput
(
'{
}'
,
''
);
const
input
=
createTypeaheadInput
(
'{
l}'
,
''
,
''
,
2
);
const
result
=
await
provider
.
provideCompletionItems
(
input
,
{
absoluteRange
:
rangeMock
});
const
result
=
await
provider
.
provideCompletionItems
(
input
,
{
absoluteRange
:
rangeMock
});
expect
(
result
.
context
).
toBe
(
'context-labels'
);
expect
(
result
.
context
).
toBe
(
'context-labels'
);
expect
(
result
.
suggestions
).
toEqual
([{
items
:
[{
label
:
'label1'
},
{
label
:
'label2'
}],
label
:
'Labels'
}]);
expect
(
result
.
suggestions
).
toEqual
([{
items
:
[{
label
:
'label1'
},
{
label
:
'label2'
}],
label
:
'Labels'
}]);
});
});
});
describe
(
'label suggestions facetted'
,
()
=>
{
it
(
'returns facetted label suggestions based on selector'
,
async
()
=>
{
const
datasource
=
makeMockLokiDatasource
(
{
label1
:
[],
label2
:
[]
},
{
'{foo="bar"}'
:
[{
label1
:
'label_val1'
}]
}
);
const
provider
=
await
getLanguageProvider
(
datasource
);
const
input
=
createTypeaheadInput
(
'{foo="bar",}'
,
''
,
''
,
11
);
const
result
=
await
provider
.
provideCompletionItems
(
input
,
{
absoluteRange
:
rangeMock
});
expect
(
result
.
context
).
toBe
(
'context-labels'
);
expect
(
result
.
suggestions
).
toEqual
([{
items
:
[{
label
:
'label1'
}],
label
:
'Labels'
}]);
});
it
(
'returns facetted label suggestions for multipule selectors'
,
async
()
=>
{
const
datasource
=
makeMockLokiDatasource
(
{
label1
:
[],
label2
:
[]
},
{
'{baz="42",foo="bar"}'
:
[{
label2
:
'label_val2'
}]
}
);
const
provider
=
await
getLanguageProvider
(
datasource
);
const
input
=
createTypeaheadInput
(
'{baz="42",foo="bar",}'
,
''
,
''
,
20
);
const
result
=
await
provider
.
provideCompletionItems
(
input
,
{
absoluteRange
:
rangeMock
});
expect
(
result
.
context
).
toBe
(
'context-labels'
);
expect
(
result
.
suggestions
).
toEqual
([{
items
:
[{
label
:
'label2'
}],
label
:
'Labels'
}]);
});
});
describe
(
'label suggestions'
,
()
=>
{
it
(
'returns label values suggestions from Loki'
,
async
()
=>
{
it
(
'returns label values suggestions from Loki'
,
async
()
=>
{
const
datasource
=
makeMockLokiDatasource
({
label1
:
[
'label1_val1'
,
'label1_val2'
],
label2
:
[]
});
const
datasource
=
makeMockLokiDatasource
({
label1
:
[
'label1_val1'
,
'label1_val2'
],
label2
:
[]
});
const
provider
=
await
getLanguageProvider
(
datasource
);
const
provider
=
await
getLanguageProvider
(
datasource
);
...
...
public/app/plugins/datasource/loki/language_provider.ts
View file @
54a3f5fd
// Libraries
// Libraries
import
_
from
'lodash'
;
import
_
from
'lodash'
;
import
LRU
from
'lru-cache'
;
// Services & Utils
// Services & Utils
import
{
parseSelector
,
labelRegexp
,
selectorRegexp
}
from
'app/plugins/datasource/prometheus/language_utils'
;
import
{
parseSelector
,
labelRegexp
,
selectorRegexp
,
processLabels
,
}
from
'app/plugins/datasource/prometheus/language_utils'
;
import
syntax
,
{
FUNCTIONS
}
from
'./syntax'
;
import
syntax
,
{
FUNCTIONS
}
from
'./syntax'
;
// Types
// Types
...
@@ -12,7 +18,7 @@ import { PromQuery } from '../prometheus/types';
...
@@ -12,7 +18,7 @@ import { PromQuery } from '../prometheus/types';
import
{
RATE_RANGES
}
from
'../prometheus/promql'
;
import
{
RATE_RANGES
}
from
'../prometheus/promql'
;
import
LokiDatasource
from
'./datasource'
;
import
LokiDatasource
from
'./datasource'
;
import
{
CompletionItem
,
TypeaheadInput
,
TypeaheadOutput
}
from
'@grafana/ui'
;
import
{
CompletionItem
,
TypeaheadInput
,
TypeaheadOutput
,
CompletionItemGroup
}
from
'@grafana/ui'
;
import
{
Grammar
}
from
'prismjs'
;
import
{
Grammar
}
from
'prismjs'
;
const
DEFAULT_KEYS
=
[
'job'
,
'namespace'
];
const
DEFAULT_KEYS
=
[
'job'
,
'namespace'
];
...
@@ -50,20 +56,27 @@ export function addHistoryMetadata(item: CompletionItem, history: LokiHistoryIte
...
@@ -50,20 +56,27 @@ export function addHistoryMetadata(item: CompletionItem, history: LokiHistoryIte
}
}
export
default
class
LokiLanguageProvider
extends
LanguageProvider
{
export
default
class
LokiLanguageProvider
extends
LanguageProvider
{
labelKeys
?:
{
[
index
:
string
]:
string
[]
};
// metric -> [labelKey,...]
labelKeys
?:
string
[];
labelValues
?:
{
[
index
:
string
]:
{
[
index
:
string
]:
string
[]
}
};
// metric -> labelKey -> [labelValue,...]
logLabelOptions
:
any
[];
logLabelOptions
:
any
[];
logLabelFetchTs
?:
number
;
logLabelFetchTs
?:
number
;
started
:
boolean
;
started
:
boolean
;
initialRange
:
AbsoluteTimeRange
;
initialRange
:
AbsoluteTimeRange
;
datasource
:
LokiDatasource
;
datasource
:
LokiDatasource
;
lookupsDisabled
:
boolean
;
// Dynamically set to true for big/slow instances
/**
* Cache for labels of series. This is bit simplistic in the sense that it just counts responses each as a 1 and does
* not account for different size of a response. If that is needed a `length` function can be added in the options.
* 10 as a max size is totally arbitrary right now.
*/
private
seriesCache
=
new
LRU
<
string
,
Record
<
string
,
string
[]
>>
(
10
);
private
labelsCache
=
new
LRU
<
string
,
string
[]
>
(
10
);
constructor
(
datasource
:
LokiDatasource
,
initialValues
?:
any
)
{
constructor
(
datasource
:
LokiDatasource
,
initialValues
?:
any
)
{
super
();
super
();
this
.
datasource
=
datasource
;
this
.
datasource
=
datasource
;
this
.
labelKeys
=
{};
this
.
labelKeys
=
[];
this
.
labelValues
=
{};
Object
.
assign
(
this
,
initialValues
);
Object
.
assign
(
this
,
initialValues
);
}
}
...
@@ -75,8 +88,14 @@ export default class LokiLanguageProvider extends LanguageProvider {
...
@@ -75,8 +88,14 @@ export default class LokiLanguageProvider extends LanguageProvider {
return
syntax
;
return
syntax
;
}
}
request
=
(
url
:
string
,
params
?:
any
):
Promise
<
{
data
:
{
data
:
string
[]
}
}
>
=>
{
request
=
async
(
url
:
string
,
params
?:
any
):
Promise
<
any
>
=>
{
return
this
.
datasource
.
metadataRequest
(
url
,
params
);
try
{
return
await
this
.
datasource
.
metadataRequest
(
url
,
params
);
}
catch
(
error
)
{
console
.
error
(
error
);
}
return
undefined
;
};
};
/**
/**
...
@@ -95,12 +114,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
...
@@ -95,12 +114,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
};
};
getLabelKeys
():
string
[]
{
getLabelKeys
():
string
[]
{
return
this
.
labelKeys
[
EMPTY_SELECTOR
];
return
this
.
labelKeys
;
}
async
getLabelValues
(
key
:
string
):
Promise
<
string
[]
>
{
await
this
.
fetchLabelValues
(
key
,
this
.
initialRange
);
return
this
.
labelValues
[
EMPTY_SELECTOR
][
key
];
}
}
/**
/**
...
@@ -219,42 +233,66 @@ export default class LokiLanguageProvider extends LanguageProvider {
...
@@ -219,42 +233,66 @@ export default class LokiLanguageProvider extends LanguageProvider {
{
text
,
wrapperClasses
,
labelKey
,
value
}:
TypeaheadInput
,
{
text
,
wrapperClasses
,
labelKey
,
value
}:
TypeaheadInput
,
{
absoluteRange
}:
any
{
absoluteRange
}:
any
):
Promise
<
TypeaheadOutput
>
{
):
Promise
<
TypeaheadOutput
>
{
let
context
:
string
;
let
context
=
'context-labels'
;
const
suggestions
=
[];
const
suggestions
:
CompletionItemGroup
[]
=
[];
const
line
=
value
.
anchorBlock
.
getText
();
const
line
=
value
.
anchorBlock
.
getText
();
const
cursorOffset
:
number
=
value
.
selection
.
anchor
.
offset
;
const
cursorOffset
=
value
.
selection
.
anchor
.
offset
;
const
isValueStart
=
text
.
match
(
/^
(
=|=~|!=|!~
)
/
);
//
Use EMPTY_SELECTOR until series API is implemented for facetting
//
Get normalized selector
const
selector
=
EMPTY_SELECTOR
;
let
selector
;
let
parsedSelector
;
let
parsedSelector
;
try
{
try
{
parsedSelector
=
parseSelector
(
line
,
cursorOffset
);
parsedSelector
=
parseSelector
(
line
,
cursorOffset
);
}
catch
{}
selector
=
parsedSelector
.
selector
;
}
catch
{
selector
=
EMPTY_SELECTOR
;
}
if
(
!
isValueStart
&&
selector
===
EMPTY_SELECTOR
)
{
// start task gets all labels
await
this
.
start
();
const
allLabels
=
this
.
getLabelKeys
();
return
{
context
,
suggestions
:
[{
label
:
`Labels`
,
items
:
allLabels
.
map
(
wrapLabel
)
}]
};
}
const
existingKeys
=
parsedSelector
?
parsedSelector
.
labelKeys
:
[];
const
existingKeys
=
parsedSelector
?
parsedSelector
.
labelKeys
:
[];
if
((
text
&&
text
.
match
(
/^!
?
=~
?
/
))
||
wrapperClasses
.
includes
(
'attr-value'
))
{
let
labelValues
;
// Label values
// Query labels for selector
if
(
labelKey
&&
this
.
labelValues
[
selector
])
{
if
(
selector
)
{
let
labelValues
=
this
.
labelValues
[
selector
][
labelKey
];
if
(
selector
===
EMPTY_SELECTOR
&&
labelKey
)
{
const
labelValuesForKey
=
await
this
.
getLabelValues
(
labelKey
);
labelValues
=
{
[
labelKey
]:
labelValuesForKey
};
}
else
{
labelValues
=
await
this
.
getSeriesLabels
(
selector
,
absoluteRange
);
}
}
if
(
!
labelValues
)
{
if
(
!
labelValues
)
{
await
this
.
fetchLabelValues
(
labelKey
,
absoluteRange
);
console
.
warn
(
`Server did not return any values for selector =
${
selector
}
`
);
labelValues
=
this
.
labelValues
[
selector
][
labelKey
]
;
return
{
context
,
suggestions
}
;
}
}
if
((
text
&&
isValueStart
)
||
wrapperClasses
.
includes
(
'attr-value'
))
{
// Label values
if
(
labelKey
&&
labelValues
[
labelKey
])
{
context
=
'context-label-values'
;
context
=
'context-label-values'
;
suggestions
.
push
({
suggestions
.
push
({
label
:
`Label values for "
${
labelKey
}
"`
,
label
:
`Label values for "
${
labelKey
}
"`
,
items
:
labelValues
.
map
(
wrapLabel
),
items
:
labelValues
[
labelKey
]
.
map
(
wrapLabel
),
});
});
}
}
}
else
{
}
else
{
// Label keys
// Label keys
const
labelKeys
=
this
.
labelKeys
[
selector
]
||
DEFAULT_KEYS
;
const
labelKeys
=
labelValues
?
Object
.
keys
(
labelValues
)
:
DEFAULT_KEYS
;
if
(
labelKeys
)
{
if
(
labelKeys
)
{
const
possibleKeys
=
_
.
difference
(
labelKeys
,
existingKeys
);
const
possibleKeys
=
_
.
difference
(
labelKeys
,
existingKeys
);
if
(
possibleKeys
.
length
)
{
if
(
possibleKeys
.
length
)
{
context
=
'context-labels'
;
const
newItems
=
possibleKeys
.
map
(
key
=>
({
label
:
key
}));
suggestions
.
push
({
label
:
`Labels`
,
items
:
possibleKeys
.
map
(
wrapLabel
)
});
const
newSuggestion
:
CompletionItemGroup
=
{
label
:
`Labels`
,
items
:
newItems
};
suggestions
.
push
(
newSuggestion
);
}
}
}
}
}
}
...
@@ -302,7 +340,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
...
@@ -302,7 +340,7 @@ export default class LokiLanguageProvider extends LanguageProvider {
// Keep only labels that exist on origin and target datasource
// Keep only labels that exist on origin and target datasource
await
this
.
start
();
// fetches all existing label keys
await
this
.
start
();
// fetches all existing label keys
const
existingKeys
=
this
.
labelKeys
[
EMPTY_SELECTOR
]
;
const
existingKeys
=
this
.
labelKeys
;
let
labelsToKeep
:
{
[
key
:
string
]:
{
value
:
any
;
operator
:
any
}
}
=
{};
let
labelsToKeep
:
{
[
key
:
string
]:
{
value
:
any
;
operator
:
any
}
}
=
{};
if
(
existingKeys
&&
existingKeys
.
length
)
{
if
(
existingKeys
&&
existingKeys
.
length
)
{
// Check for common labels
// Check for common labels
...
@@ -325,22 +363,31 @@ export default class LokiLanguageProvider extends LanguageProvider {
...
@@ -325,22 +363,31 @@ export default class LokiLanguageProvider extends LanguageProvider {
return
[
'{'
,
cleanSelector
,
'}'
].
join
(
''
);
return
[
'{'
,
cleanSelector
,
'}'
].
join
(
''
);
}
}
async
getSeriesLabels
(
selector
:
string
,
absoluteRange
:
AbsoluteTimeRange
)
{
if
(
this
.
lookupsDisabled
)
{
return
undefined
;
}
try
{
return
await
this
.
fetchSeriesLabels
(
selector
,
absoluteRange
);
}
catch
(
error
)
{
// TODO: better error handling
console
.
error
(
error
);
return
undefined
;
}
}
/**
* Fetches all label keys
* @param absoluteRange Fetches
*/
async
fetchLogLabels
(
absoluteRange
:
AbsoluteTimeRange
):
Promise
<
any
>
{
async
fetchLogLabels
(
absoluteRange
:
AbsoluteTimeRange
):
Promise
<
any
>
{
const
url
=
'/api/prom/label'
;
const
url
=
'/api/prom/label'
;
try
{
try
{
this
.
logLabelFetchTs
=
Date
.
now
();
this
.
logLabelFetchTs
=
Date
.
now
();
const
rangeParams
=
absoluteRange
?
rangeToParams
(
absoluteRange
)
:
{};
const
rangeParams
=
absoluteRange
?
rangeToParams
(
absoluteRange
)
:
{};
const
res
=
await
this
.
request
(
url
,
rangeParams
);
const
res
=
await
this
.
request
(
url
,
rangeParams
);
const
labelKeys
=
res
.
data
.
data
.
slice
().
sort
();
this
.
labelKeys
=
res
.
slice
().
sort
();
this
.
logLabelOptions
=
this
.
labelKeys
.
map
((
key
:
string
)
=>
({
label
:
key
,
value
:
key
,
isLeaf
:
false
}));
this
.
labelKeys
=
{
...
this
.
labelKeys
,
[
EMPTY_SELECTOR
]:
labelKeys
,
};
this
.
labelValues
=
{
[
EMPTY_SELECTOR
]:
{},
};
this
.
logLabelOptions
=
labelKeys
.
map
((
key
:
string
)
=>
({
label
:
key
,
value
:
key
,
isLeaf
:
false
}));
}
catch
(
e
)
{
}
catch
(
e
)
{
console
.
error
(
e
);
console
.
error
(
e
);
}
}
...
@@ -353,12 +400,64 @@ export default class LokiLanguageProvider extends LanguageProvider {
...
@@ -353,12 +400,64 @@ export default class LokiLanguageProvider extends LanguageProvider {
}
}
}
}
async
fetchLabelValues
(
key
:
string
,
absoluteRange
:
AbsoluteTimeRange
)
{
/**
* Fetch labels for a selector. This is cached by it's args but also by the global timeRange currently selected as
* they can change over requested time.
* @param name
*/
fetchSeriesLabels
=
async
(
match
:
string
,
absoluteRange
:
AbsoluteTimeRange
):
Promise
<
Record
<
string
,
string
[]
>>
=>
{
const
rangeParams
:
{
start
?:
number
;
end
?:
number
}
=
absoluteRange
?
rangeToParams
(
absoluteRange
)
:
{};
const
url
=
'/loki/api/v1/series'
;
const
{
start
,
end
}
=
rangeParams
;
const
cacheKey
=
this
.
generateCacheKey
(
url
,
start
,
end
,
match
);
const
params
=
{
match
,
start
,
end
};
let
value
=
this
.
seriesCache
.
get
(
cacheKey
);
if
(
!
value
)
{
// Clear value when requesting new one. Empty object being truthy also makes sure we don't request twice.
this
.
seriesCache
.
set
(
cacheKey
,
{});
const
data
=
await
this
.
request
(
url
,
params
);
const
{
values
}
=
processLabels
(
data
);
value
=
values
;
this
.
seriesCache
.
set
(
cacheKey
,
value
);
}
return
value
;
};
// Cache key is a bit different here. We round up to a minute the intervals.
// The rounding may seem strange but makes relative intervals like now-1h less prone to need separate request every
// millisecond while still actually getting all the keys for the correct interval. This still can create problems
// when user does not the newest values for a minute if already cached.
generateCacheKey
(
url
:
string
,
start
:
number
,
end
:
number
,
param
:
string
):
string
{
return
[
url
,
this
.
roundTime
(
start
),
this
.
roundTime
(
end
),
param
].
join
();
}
// Round nanos epoch to nearest 5 minute interval
roundTime
(
nanos
:
number
):
number
{
return
nanos
?
Math
.
floor
(
nanos
/
NS_IN_MS
/
1000
/
60
/
5
)
:
0
;
}
async
getLabelValues
(
key
:
string
):
Promise
<
string
[]
>
{
return
await
this
.
fetchLabelValues
(
key
,
this
.
initialRange
);
}
async
fetchLabelValues
(
key
:
string
,
absoluteRange
:
AbsoluteTimeRange
):
Promise
<
string
[]
>
{
const
url
=
`/api/prom/label/
${
key
}
/values`
;
const
url
=
`/api/prom/label/
${
key
}
/values`
;
let
values
:
string
[]
=
[];
const
rangeParams
:
{
start
?:
number
;
end
?:
number
}
=
absoluteRange
?
rangeToParams
(
absoluteRange
)
:
{};
const
{
start
,
end
}
=
rangeParams
;
const
cacheKey
=
this
.
generateCacheKey
(
url
,
start
,
end
,
key
);
const
params
=
{
start
,
end
};
let
value
=
this
.
labelsCache
.
get
(
cacheKey
);
if
(
!
value
)
{
try
{
try
{
const
rangeParams
=
absoluteRange
?
rangeToParams
(
absoluteRange
)
:
{};
// Clear value when requesting new one. Empty object being truthy also makes sure we don't request twice.
const
res
=
await
this
.
request
(
url
,
rangeParams
);
this
.
labelsCache
.
set
(
cacheKey
,
[]);
const
values
=
res
.
data
.
data
.
slice
().
sort
();
const
res
=
await
this
.
request
(
url
,
params
);
values
=
res
.
slice
().
sort
();
value
=
values
;
this
.
labelsCache
.
set
(
cacheKey
,
value
);
// Add to label options
// Add to label options
this
.
logLabelOptions
=
this
.
logLabelOptions
.
map
(
keyOption
=>
{
this
.
logLabelOptions
=
this
.
logLabelOptions
.
map
(
keyOption
=>
{
...
@@ -370,19 +469,10 @@ export default class LokiLanguageProvider extends LanguageProvider {
...
@@ -370,19 +469,10 @@ export default class LokiLanguageProvider extends LanguageProvider {
}
}
return
keyOption
;
return
keyOption
;
});
});
// Add to key map
const
exisingValues
=
this
.
labelValues
[
EMPTY_SELECTOR
];
const
nextValues
=
{
...
exisingValues
,
[
key
]:
values
,
};
this
.
labelValues
=
{
...
this
.
labelValues
,
[
EMPTY_SELECTOR
]:
nextValues
,
};
}
catch
(
e
)
{
}
catch
(
e
)
{
console
.
error
(
e
);
console
.
error
(
e
);
}
}
}
}
return
value
;
}
}
}
public/app/plugins/datasource/loki/mocks.ts
View file @
54a3f5fd
...
@@ -3,34 +3,45 @@ import { DataSourceSettings } from '@grafana/data';
...
@@ -3,34 +3,45 @@ import { DataSourceSettings } from '@grafana/data';
import
{
LokiOptions
}
from
'./types'
;
import
{
LokiOptions
}
from
'./types'
;
import
{
createDatasourceSettings
}
from
'../../../features/datasources/mocks'
;
import
{
createDatasourceSettings
}
from
'../../../features/datasources/mocks'
;
export
function
makeMockLokiDatasource
(
labelsAndValues
:
{
[
label
:
string
]:
string
[]
}):
LokiDatasource
{
interface
Labels
{
[
label
:
string
]:
string
[];
}
interface
Series
{
[
label
:
string
]:
string
;
}
interface
SeriesForSelector
{
[
selector
:
string
]:
Series
[];
}
export
function
makeMockLokiDatasource
(
labelsAndValues
:
Labels
,
series
?:
SeriesForSelector
):
LokiDatasource
{
const
legacyLokiLabelsAndValuesEndpointRegex
=
/^
\/
api
\/
prom
\/
label
\/(\w
*
)\/
values/
;
const
legacyLokiLabelsAndValuesEndpointRegex
=
/^
\/
api
\/
prom
\/
label
\/(\w
*
)\/
values/
;
const
lokiLabelsAndValuesEndpointRegex
=
/^
\/
loki
\/
api
\/
v1
\/
label
\/(\w
*
)\/
values/
;
const
lokiLabelsAndValuesEndpointRegex
=
/^
\/
loki
\/
api
\/
v1
\/
label
\/(\w
*
)\/
values/
;
const
lokiSeriesEndpointRegex
=
/^
\/
loki
\/
api
\/
v1
\/
series/
;
const
legacyLokiLabelsEndpoint
=
`
${
LEGACY_LOKI_ENDPOINT
}
/label`
;
const
legacyLokiLabelsEndpoint
=
`
${
LEGACY_LOKI_ENDPOINT
}
/label`
;
const
lokiLabelsEndpoint
=
`
${
LOKI_ENDPOINT
}
/label`
;
const
lokiLabelsEndpoint
=
`
${
LOKI_ENDPOINT
}
/label`
;
const
labels
=
Object
.
keys
(
labelsAndValues
);
const
labels
=
Object
.
keys
(
labelsAndValues
);
return
{
return
{
metadataRequest
:
(
url
:
string
)
=>
{
metadataRequest
:
(
url
:
string
,
params
?:
{
[
key
:
string
]:
string
})
=>
{
let
responseData
;
if
(
url
===
legacyLokiLabelsEndpoint
||
url
===
lokiLabelsEndpoint
)
{
if
(
url
===
legacyLokiLabelsEndpoint
||
url
===
lokiLabelsEndpoint
)
{
re
sponseData
=
labels
;
re
turn
labels
;
}
else
{
}
else
{
const
match
=
url
.
match
(
legacyLokiLabelsAndValuesEndpointRegex
)
||
url
.
match
(
lokiLabelsAndValuesEndpointRegex
);
const
legacyLabelsMatch
=
url
.
match
(
legacyLokiLabelsAndValuesEndpointRegex
);
if
(
match
)
{
const
labelsMatch
=
url
.
match
(
lokiLabelsAndValuesEndpointRegex
);
responseData
=
labelsAndValues
[
match
[
1
]];
const
seriesMatch
=
url
.
match
(
lokiSeriesEndpointRegex
);
}
if
(
legacyLabelsMatch
)
{
}
return
labelsAndValues
[
legacyLabelsMatch
[
1
]]
||
[];
if
(
responseData
)
{
}
else
if
(
labelsMatch
)
{
return
{
return
labelsAndValues
[
labelsMatch
[
1
]]
||
[];
data
:
{
}
else
if
(
seriesMatch
)
{
data
:
responseData
,
return
series
[
params
.
match
]
||
[];
},
};
}
else
{
}
else
{
throw
new
Error
(
`Unexpected url error,
${
url
}
`
);
throw
new
Error
(
`Unexpected url error,
${
url
}
`
);
}
}
}
},
},
}
as
any
;
}
as
any
;
}
}
...
...
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