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
0e3e874e
Unverified
Commit
0e3e874e
authored
Sep 10, 2019
by
Andrej Ocenas
Committed by
GitHub
Sep 10, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Annotations: Add annotations support to Loki (#18949)
parent
eccc6adf
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
239 additions
and
14 deletions
+239
-14
docs/sources/plugins/developing/datasources.md
+2
-1
packages/grafana-data/src/utils/dataFrameView.ts
+6
-0
packages/grafana-ui/src/types/datasource.ts
+22
-0
public/app/core/angular_wrappers.ts
+7
-0
public/app/features/annotations/annotations_srv.ts
+2
-1
public/app/plugins/datasource/loki/LokiAnnotationsQueryCtrl.tsx
+17
-0
public/app/plugins/datasource/loki/components/AnnotationsQueryEditor.tsx
+54
-0
public/app/plugins/datasource/loki/datasource.test.ts
+64
-9
public/app/plugins/datasource/loki/datasource.ts
+57
-2
public/app/plugins/datasource/loki/module.ts
+2
-0
public/app/plugins/datasource/loki/partials/annotations.editor.html
+5
-0
public/app/plugins/datasource/loki/plugin.json
+1
-1
No files found.
docs/sources/plugins/developing/datasources.md
View file @
0e3e874e
...
@@ -146,7 +146,8 @@ Request object passed to datasource.annotationQuery function:
...
@@ -146,7 +146,8 @@ Request object passed to datasource.annotationQuery function:
"datasource"
:
"generic datasource"
,
"datasource"
:
"generic datasource"
,
"enable"
:
true
,
"enable"
:
true
,
"name"
:
"annotation name"
"name"
:
"annotation name"
}
},
"dashboard"
:
DashboardModel
}
}
```
```
...
...
packages/grafana-data/src/utils/dataFrameView.ts
View file @
0e3e874e
...
@@ -68,4 +68,10 @@ export class DataFrameView<T = any> implements Vector<T> {
...
@@ -68,4 +68,10 @@ export class DataFrameView<T = any> implements Vector<T> {
toJSON
():
T
[]
{
toJSON
():
T
[]
{
return
this
.
toArray
();
return
this
.
toArray
();
}
}
forEachRow
(
iterator
:
(
row
:
T
)
=>
void
)
{
for
(
let
i
=
0
;
i
<
this
.
data
.
length
;
i
++
)
{
iterator
(
this
.
get
(
i
));
}
}
}
}
packages/grafana-ui/src/types/datasource.ts
View file @
0e3e874e
...
@@ -8,6 +8,7 @@ import {
...
@@ -8,6 +8,7 @@ import {
LogRowModel
,
LogRowModel
,
LoadingState
,
LoadingState
,
DataFrameDTO
,
DataFrameDTO
,
AnnotationEvent
,
}
from
'@grafana/data'
;
}
from
'@grafana/data'
;
import
{
PluginMeta
,
GrafanaPlugin
}
from
'./plugin'
;
import
{
PluginMeta
,
GrafanaPlugin
}
from
'./plugin'
;
import
{
PanelData
}
from
'./panel'
;
import
{
PanelData
}
from
'./panel'
;
...
@@ -276,6 +277,12 @@ export abstract class DataSourceApi<
...
@@ -276,6 +277,12 @@ export abstract class DataSourceApi<
* Used in explore
* Used in explore
*/
*/
languageProvider
?:
any
;
languageProvider
?:
any
;
/**
* Can be optionally implemented to allow datasource to be a source of annotations for dashboard. To be visible
* in the annotation editor `annotations` capability also needs to be enabled in plugin.json.
*/
annotationQuery
?(
options
:
AnnotationQueryRequest
<
TQuery
>
):
Promise
<
AnnotationEvent
[]
>
;
}
}
export
interface
QueryEditorProps
<
export
interface
QueryEditorProps
<
...
@@ -542,3 +549,18 @@ export interface DataSourceSelectItem {
...
@@ -542,3 +549,18 @@ export interface DataSourceSelectItem {
meta
:
DataSourcePluginMeta
;
meta
:
DataSourcePluginMeta
;
sort
:
string
;
sort
:
string
;
}
}
/**
* Options passed to the datasource.annotationQuery method. See docs/plugins/developing/datasource.md
*/
export
interface
AnnotationQueryRequest
<
MoreOptions
=
{}
>
{
range
:
TimeRange
;
rangeRaw
:
RawTimeRange
;
// Should be DataModel but cannot import that here from the main app. Needs to be moved to package first.
dashboard
:
any
;
annotation
:
{
datasource
:
string
;
enable
:
boolean
;
name
:
string
;
}
&
MoreOptions
;
}
public/app/core/angular_wrappers.ts
View file @
0e3e874e
...
@@ -12,6 +12,7 @@ import { FunctionEditor } from 'app/plugins/datasource/graphite/FunctionEditor';
...
@@ -12,6 +12,7 @@ import { FunctionEditor } from 'app/plugins/datasource/graphite/FunctionEditor';
import
{
SearchField
}
from
'./components/search/SearchField'
;
import
{
SearchField
}
from
'./components/search/SearchField'
;
import
{
GraphContextMenu
}
from
'app/plugins/panel/graph/GraphContextMenu'
;
import
{
GraphContextMenu
}
from
'app/plugins/panel/graph/GraphContextMenu'
;
import
ReactProfileWrapper
from
'app/features/profile/ReactProfileWrapper'
;
import
ReactProfileWrapper
from
'app/features/profile/ReactProfileWrapper'
;
import
{
LokiAnnotationsQueryEditor
}
from
'../plugins/datasource/loki/components/AnnotationsQueryEditor'
;
export
function
registerAngularDirectives
()
{
export
function
registerAngularDirectives
()
{
react2AngularDirective
(
'sidemenu'
,
SideMenu
,
[]);
react2AngularDirective
(
'sidemenu'
,
SideMenu
,
[]);
...
@@ -102,4 +103,10 @@ export function registerAngularDirectives() {
...
@@ -102,4 +103,10 @@ export function registerAngularDirectives() {
]);
]);
react2AngularDirective
(
'reactProfileWrapper'
,
ReactProfileWrapper
,
[]);
react2AngularDirective
(
'reactProfileWrapper'
,
ReactProfileWrapper
,
[]);
react2AngularDirective
(
'lokiAnnotationsQueryEditor'
,
LokiAnnotationsQueryEditor
,
[
'expr'
,
'onChange'
,
[
'datasource'
,
{
watchDepth
:
'reference'
}],
]);
}
}
public/app/features/annotations/annotations_srv.ts
View file @
0e3e874e
...
@@ -15,6 +15,7 @@ import { AnnotationEvent } from '@grafana/data';
...
@@ -15,6 +15,7 @@ import { AnnotationEvent } from '@grafana/data';
import
DatasourceSrv
from
'../plugins/datasource_srv'
;
import
DatasourceSrv
from
'../plugins/datasource_srv'
;
import
{
BackendSrv
}
from
'app/core/services/backend_srv'
;
import
{
BackendSrv
}
from
'app/core/services/backend_srv'
;
import
{
TimeSrv
}
from
'../dashboard/services/TimeSrv'
;
import
{
TimeSrv
}
from
'../dashboard/services/TimeSrv'
;
import
{
DataSourceApi
}
from
'@grafana/ui'
;
export
class
AnnotationsSrv
{
export
class
AnnotationsSrv
{
globalAnnotationsPromise
:
any
;
globalAnnotationsPromise
:
any
;
...
@@ -126,7 +127,7 @@ export class AnnotationsSrv {
...
@@ -126,7 +127,7 @@ export class AnnotationsSrv {
dsPromises
.
push
(
datasourcePromise
);
dsPromises
.
push
(
datasourcePromise
);
promises
.
push
(
promises
.
push
(
datasourcePromise
datasourcePromise
.
then
((
datasource
:
any
)
=>
{
.
then
((
datasource
:
DataSourceApi
)
=>
{
// issue query against data source
// issue query against data source
return
datasource
.
annotationQuery
({
return
datasource
.
annotationQuery
({
range
:
range
,
range
:
range
,
...
...
public/app/plugins/datasource/loki/LokiAnnotationsQueryCtrl.tsx
0 → 100644
View file @
0e3e874e
/**
* Just a simple wrapper for a react component that is actually implementing the query editor.
*/
export
class
LokiAnnotationsQueryCtrl
{
static
templateUrl
=
'partials/annotations.editor.html'
;
annotation
:
any
;
/** @ngInject */
constructor
()
{
this
.
annotation
.
target
=
this
.
annotation
.
target
||
{};
this
.
onQueryChange
=
this
.
onQueryChange
.
bind
(
this
);
}
onQueryChange
(
expr
:
string
)
{
this
.
annotation
.
expr
=
expr
;
}
}
public/app/plugins/datasource/loki/components/AnnotationsQueryEditor.tsx
0 → 100644
View file @
0e3e874e
// Libraries
import
React
,
{
memo
}
from
'react'
;
// Types
import
{
DataSourceApi
,
DataSourceJsonData
,
DataSourceStatus
}
from
'@grafana/ui'
;
import
{
LokiQuery
}
from
'../types'
;
import
{
useLokiSyntax
}
from
'./useLokiSyntax'
;
import
{
LokiQueryFieldForm
}
from
'./LokiQueryFieldForm'
;
interface
Props
{
expr
:
string
;
datasource
:
DataSourceApi
<
LokiQuery
,
DataSourceJsonData
>
;
onChange
:
(
expr
:
string
)
=>
void
;
}
export
const
LokiAnnotationsQueryEditor
=
memo
(
function
LokiAnnotationQueryEditor
(
props
:
Props
)
{
const
{
expr
,
datasource
,
onChange
}
=
props
;
// Timerange to get existing labels from. Hard coding like this seems to be good enough right now.
const
absolute
=
{
from
:
Date
.
now
()
-
10000
,
to
:
Date
.
now
(),
};
const
{
isSyntaxReady
,
setActiveOption
,
refreshLabels
,
...
syntaxProps
}
=
useLokiSyntax
(
datasource
.
languageProvider
,
DataSourceStatus
.
Connected
,
absolute
);
const
query
:
LokiQuery
=
{
refId
:
''
,
expr
,
};
return
(
<
div
className=
"gf-form-group"
>
<
LokiQueryFieldForm
datasource=
{
datasource
}
datasourceStatus=
{
DataSourceStatus
.
Connected
}
query=
{
query
}
onChange=
{
(
query
:
LokiQuery
)
=>
onChange
(
query
.
expr
)
}
onRunQuery=
{
()
=>
{}
}
history=
{
[]
}
panelData=
{
null
}
onLoadOptions=
{
setActiveOption
}
onLabelsRefresh=
{
refreshLabels
}
syntaxLoaded=
{
isSyntaxReady
}
absoluteRange=
{
absolute
}
{
...
syntaxProps
}
/>
</
div
>
);
});
public/app/plugins/datasource/loki/datasource.test.ts
View file @
0e3e874e
import
LokiDatasource
from
'./datasource'
;
import
LokiDatasource
from
'./datasource'
;
import
{
LokiQuery
}
from
'./types'
;
import
{
LokiQuery
}
from
'./types'
;
import
{
getQueryOptions
}
from
'test/helpers/getQueryOptions'
;
import
{
getQueryOptions
}
from
'test/helpers/getQueryOptions'
;
import
{
DataSourceApi
}
from
'@grafana/ui'
;
import
{
AnnotationQueryRequest
,
DataSourceApi
}
from
'@grafana/ui'
;
import
{
DataFrame
}
from
'@grafana/data'
;
import
{
DataFrame
,
dateTime
}
from
'@grafana/data'
;
import
{
BackendSrv
}
from
'app/core/services/backend_srv'
;
import
{
BackendSrv
}
from
'app/core/services/backend_srv'
;
import
{
TemplateSrv
}
from
'app/features/templating/template_srv'
;
import
{
TemplateSrv
}
from
'app/features/templating/template_srv'
;
...
@@ -22,15 +22,15 @@ describe('LokiDatasource', () => {
...
@@ -22,15 +22,15 @@ describe('LokiDatasource', () => {
},
},
};
};
describe
(
'when querying'
,
()
=>
{
const
backendSrvMock
=
{
datasourceRequest
:
jest
.
fn
()
};
const
backendSrvMock
=
{
datasourceRequest
:
jest
.
fn
()
};
const
backendSrv
=
(
backendSrvMock
as
unknown
)
as
BackendSrv
;
const
backendSrv
=
(
backendSrvMock
as
unknown
)
as
BackendSrv
;
const
templateSrvMock
=
({
const
templateSrvMock
=
({
getAdhocFilters
:
():
any
[]
=>
[],
getAdhocFilters
:
():
any
[]
=>
[],
replace
:
(
a
:
string
)
=>
a
,
replace
:
(
a
:
string
)
=>
a
,
}
as
unknown
)
as
TemplateSrv
;
}
as
unknown
)
as
TemplateSrv
;
describe
(
'when querying'
,
()
=>
{
const
testLimit
=
makeLimitTest
(
instanceSettings
,
backendSrvMock
,
backendSrv
,
templateSrvMock
,
testResp
);
const
testLimit
=
makeLimitTest
(
instanceSettings
,
backendSrvMock
,
backendSrv
,
templateSrvMock
,
testResp
);
test
(
'should use default max lines when no limit given'
,
()
=>
{
test
(
'should use default max lines when no limit given'
,
()
=>
{
...
@@ -171,6 +171,37 @@ describe('LokiDatasource', () => {
...
@@ -171,6 +171,37 @@ describe('LokiDatasource', () => {
});
});
});
});
});
});
describe
(
'annotationQuery'
,
()
=>
{
it
(
'should transform the loki data to annototion response'
,
async
()
=>
{
const
ds
=
new
LokiDatasource
(
instanceSettings
,
backendSrv
,
templateSrvMock
);
backendSrvMock
.
datasourceRequest
=
jest
.
fn
(()
=>
Promise
.
resolve
({
data
:
{
streams
:
[
{
entries
:
[{
ts
:
'2019-02-01T10:27:37.498180581Z'
,
line
:
'hello'
}],
labels
:
'{label="value"}'
,
},
{
entries
:
[{
ts
:
'2019-02-01T12:27:37.498180581Z'
,
line
:
'hello 2'
}],
labels
:
'{label2="value2"}'
,
},
],
},
})
);
const
query
=
makeAnnotationQueryRequest
();
const
res
=
await
ds
.
annotationQuery
(
query
);
expect
(
res
.
length
).
toBe
(
2
);
expect
(
res
[
0
].
text
).
toBe
(
'hello'
);
expect
(
res
[
0
].
tags
).
toEqual
([
'value'
]);
expect
(
res
[
1
].
text
).
toBe
(
'hello 2'
);
expect
(
res
[
1
].
tags
).
toEqual
([
'value2'
]);
});
});
});
});
type
LimitTestArgs
=
{
type
LimitTestArgs
=
{
...
@@ -208,3 +239,27 @@ function makeLimitTest(
...
@@ -208,3 +239,27 @@ function makeLimitTest(
expect
(
backendSrvMock
.
datasourceRequest
.
mock
.
calls
[
0
][
0
].
url
).
toContain
(
`limit=
${
expectedLimit
}
`
);
expect
(
backendSrvMock
.
datasourceRequest
.
mock
.
calls
[
0
][
0
].
url
).
toContain
(
`limit=
${
expectedLimit
}
`
);
};
};
}
}
function
makeAnnotationQueryRequest
():
AnnotationQueryRequest
<
LokiQuery
>
{
const
timeRange
=
{
from
:
dateTime
(),
to
:
dateTime
(),
};
return
{
annotation
:
{
expr
:
'{test=test}'
,
refId
:
''
,
datasource
:
'loki'
,
enable
:
true
,
name
:
'test-annotation'
,
},
dashboard
:
{
id
:
1
,
}
as
any
,
range
:
{
...
timeRange
,
raw
:
timeRange
,
},
rangeRaw
:
timeRange
,
};
}
public/app/plugins/datasource/loki/datasource.ts
View file @
0e3e874e
// Libraries
// Libraries
import
_
from
'lodash'
;
import
_
from
'lodash'
;
// Services & Utils
// Services & Utils
import
{
dateMath
,
DataFrame
,
LogRowModel
,
LoadingState
,
DateTime
}
from
'@grafana/data'
;
import
{
dateMath
,
DataFrame
,
LogRowModel
,
LoadingState
,
DateTime
,
AnnotationEvent
,
DataFrameView
,
}
from
'@grafana/data'
;
import
{
addLabelToSelector
}
from
'app/plugins/datasource/prometheus/add_label_to_query'
;
import
{
addLabelToSelector
}
from
'app/plugins/datasource/prometheus/add_label_to_query'
;
import
LanguageProvider
from
'./language_provider'
;
import
LanguageProvider
from
'./language_provider'
;
import
{
logStreamToDataFrame
}
from
'./result_transformer'
;
import
{
logStreamToDataFrame
}
from
'./result_transformer'
;
...
@@ -15,6 +23,7 @@ import {
...
@@ -15,6 +23,7 @@ import {
DataQueryRequest
,
DataQueryRequest
,
DataStreamObserver
,
DataStreamObserver
,
DataQueryResponse
,
DataQueryResponse
,
AnnotationQueryRequest
,
}
from
'@grafana/ui'
;
}
from
'@grafana/ui'
;
import
{
LokiQuery
,
LokiOptions
,
LokiLogsStream
,
LokiResponse
}
from
'./types'
;
import
{
LokiQuery
,
LokiOptions
,
LokiLogsStream
,
LokiResponse
}
from
'./types'
;
...
@@ -193,7 +202,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
...
@@ -193,7 +202,7 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
}
}
};
};
runQueries
=
async
(
options
:
DataQueryRequest
<
LokiQuery
>
)
=>
{
runQueries
=
async
(
options
:
DataQueryRequest
<
LokiQuery
>
)
:
Promise
<
{
data
:
DataFrame
[]
}
>
=>
{
const
queryTargets
=
options
.
targets
const
queryTargets
=
options
.
targets
.
filter
(
target
=>
target
.
expr
&&
!
target
.
hide
&&
!
target
.
live
)
.
filter
(
target
=>
target
.
expr
&&
!
target
.
hide
&&
!
target
.
live
)
.
map
(
target
=>
this
.
prepareQueryTarget
(
target
,
options
));
.
map
(
target
=>
this
.
prepareQueryTarget
(
target
,
options
));
...
@@ -368,6 +377,52 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
...
@@ -368,6 +377,52 @@ export class LokiDatasource extends DataSourceApi<LokiQuery, LokiOptions> {
return
{
status
:
'error'
,
message
:
message
};
return
{
status
:
'error'
,
message
:
message
};
});
});
}
}
async
annotationQuery
(
options
:
AnnotationQueryRequest
<
LokiQuery
>
):
Promise
<
AnnotationEvent
[]
>
{
if
(
!
options
.
annotation
.
expr
)
{
return
[];
}
const
query
=
queryRequestFromAnnotationOptions
(
options
);
const
{
data
}
=
await
this
.
runQueries
(
query
);
const
annotations
:
AnnotationEvent
[]
=
[];
for
(
const
frame
of
data
)
{
const
tags
=
Object
.
values
(
frame
.
labels
);
const
view
=
new
DataFrameView
<
{
ts
:
string
;
line
:
string
}
>
(
frame
);
view
.
forEachRow
(
row
=>
{
annotations
.
push
({
time
:
new
Date
(
row
.
ts
).
valueOf
(),
text
:
row
.
line
,
tags
,
});
});
}
return
annotations
;
}
}
function
queryRequestFromAnnotationOptions
(
options
:
AnnotationQueryRequest
<
LokiQuery
>
):
DataQueryRequest
<
LokiQuery
>
{
const
refId
=
`annotation-
${
options
.
annotation
.
name
}
`
;
const
target
:
LokiQuery
=
{
refId
,
expr
:
options
.
annotation
.
expr
};
return
{
requestId
:
refId
,
range
:
options
.
range
,
targets
:
[
target
],
dashboardId
:
options
.
dashboard
.
id
,
scopedVars
:
null
,
startTime
:
Date
.
now
(),
// This should mean the default defined on datasource is used.
maxDataPoints
:
0
,
// Dummy values, are required in type but not used here.
timezone
:
'utc'
,
panelId
:
0
,
interval
:
''
,
intervalMs
:
0
,
};
}
}
export
default
LokiDatasource
;
export
default
LokiDatasource
;
public/app/plugins/datasource/loki/module.ts
View file @
0e3e874e
...
@@ -3,6 +3,7 @@ import Datasource from './datasource';
...
@@ -3,6 +3,7 @@ import Datasource from './datasource';
import
LokiStartPage
from
'./components/LokiStartPage'
;
import
LokiStartPage
from
'./components/LokiStartPage'
;
import
LokiQueryField
from
'./components/LokiQueryField'
;
import
LokiQueryField
from
'./components/LokiQueryField'
;
import
LokiQueryEditor
from
'./components/LokiQueryEditor'
;
import
LokiQueryEditor
from
'./components/LokiQueryEditor'
;
import
{
LokiAnnotationsQueryCtrl
}
from
'./LokiAnnotationsQueryCtrl'
;
export
class
LokiConfigCtrl
{
export
class
LokiConfigCtrl
{
static
templateUrl
=
'partials/config.html'
;
static
templateUrl
=
'partials/config.html'
;
...
@@ -14,4 +15,5 @@ export {
...
@@ -14,4 +15,5 @@ export {
LokiConfigCtrl
as
ConfigCtrl
,
LokiConfigCtrl
as
ConfigCtrl
,
LokiQueryField
as
ExploreQueryField
,
LokiQueryField
as
ExploreQueryField
,
LokiStartPage
as
ExploreStartPage
,
LokiStartPage
as
ExploreStartPage
,
LokiAnnotationsQueryCtrl
as
AnnotationsQueryCtrl
,
};
};
public/app/plugins/datasource/loki/partials/annotations.editor.html
0 → 100644
View file @
0e3e874e
<loki-annotations-query-editor
expr=
"ctrl.annotation.expr"
on-change=
"ctrl.onQueryChange"
datasource=
"ctrl.datasource"
/>
public/app/plugins/datasource/loki/plugin.json
View file @
0e3e874e
...
@@ -6,7 +6,7 @@
...
@@ -6,7 +6,7 @@
"metrics"
:
true
,
"metrics"
:
true
,
"alerting"
:
false
,
"alerting"
:
false
,
"annotations"
:
fals
e
,
"annotations"
:
tru
e
,
"logs"
:
true
,
"logs"
:
true
,
"streaming"
:
true
,
"streaming"
:
true
,
...
...
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