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
618d4f0a
Commit
618d4f0a
authored
Mar 27, 2015
by
Torkel Ödegaard
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Testing kariosdb datasource, hm.. needs a lot of work
parent
c01efad9
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
1204 additions
and
3 deletions
+1204
-3
pkg/api/frontendsettings.go
+3
-3
src/app/plugins/datasource/kairosdb/datasource.js
+420
-0
src/app/plugins/datasource/kairosdb/partials/config.html
+1
-0
src/app/plugins/datasource/kairosdb/partials/query.editor.html
+384
-0
src/app/plugins/datasource/kairosdb/plugin.json
+17
-0
src/app/plugins/datasource/kairosdb/queryCtrl.js
+379
-0
No files found.
pkg/api/frontendsettings.go
View file @
618d4f0a
package
api
import
(
"errors"
"fmt"
"strconv"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/middleware"
m
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
...
...
@@ -45,7 +44,8 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
meta
,
exists
:=
plugins
.
DataSources
[
ds
.
Type
]
if
!
exists
{
return
nil
,
errors
.
New
(
fmt
.
Sprintf
(
"Could not find plugin definition for data source: %v"
,
ds
.
Type
))
log
.
Error
(
3
,
"Could not find plugin definition for data source: %v"
,
ds
.
Type
)
continue
}
dsMap
[
"meta"
]
=
meta
...
...
src/app/plugins/datasource/kairosdb/datasource.js
0 → 100644
View file @
618d4f0a
define
([
'angular'
,
'lodash'
,
'kbn'
,
'./queryCtrl'
,
],
function
(
angular
,
_
,
kbn
)
{
'use strict'
;
var
module
=
angular
.
module
(
'grafana.services'
);
var
tagList
=
null
;
module
.
factory
(
'KairosDBDatasource'
,
function
(
$q
,
$http
)
{
function
KairosDBDatasource
(
datasource
)
{
this
.
type
=
datasource
.
type
;
this
.
editorSrc
=
'plugins/datasources/kairosdb/kairosdb.editor.html'
;
this
.
url
=
datasource
.
url
;
this
.
name
=
datasource
.
name
;
this
.
supportMetrics
=
true
;
this
.
grafanaDB
=
datasource
.
grafanaDB
;
}
// Called once per panel (graph)
KairosDBDatasource
.
prototype
.
query
=
function
(
options
)
{
var
start
=
options
.
range
.
from
;
var
end
=
options
.
range
.
to
;
var
queries
=
_
.
compact
(
_
.
map
(
options
.
targets
,
_
.
partial
(
convertTargetToQuery
,
options
)));
var
plotParams
=
_
.
compact
(
_
.
map
(
options
.
targets
,
function
(
target
){
var
alias
=
target
.
alias
;
if
(
typeof
target
.
alias
==
'undefined'
||
target
.
alias
==
""
)
alias
=
target
.
metric
;
return
!
target
.
hide
?
{
alias
:
alias
,
exouter
:
target
.
exOuter
}
:
null
;
}));
var
handleKairosDBQueryResponseAlias
=
_
.
partial
(
handleKairosDBQueryResponse
,
plotParams
);
// No valid targets, return the empty result to save a round trip.
if
(
_
.
isEmpty
(
queries
))
{
var
d
=
$q
.
defer
();
d
.
resolve
({
data
:
[]
});
return
d
.
promise
;
}
return
this
.
performTimeSeriesQuery
(
queries
,
start
,
end
).
then
(
handleKairosDBQueryResponseAlias
,
handleQueryError
);
};
///////////////////////////////////////////////////////////////////////
/// Query methods
///////////////////////////////////////////////////////////////////////
KairosDBDatasource
.
prototype
.
performTimeSeriesQuery
=
function
(
queries
,
start
,
end
)
{
var
reqBody
=
{
metrics
:
queries
};
reqBody
.
cache_time
=
0
;
convertToKairosTime
(
start
,
reqBody
,
'start'
);
convertToKairosTime
(
end
,
reqBody
,
'end'
);
var
options
=
{
method
:
'POST'
,
url
:
'/api/v1/datapoints/query'
,
data
:
reqBody
};
options
.
url
=
this
.
url
+
options
.
url
;
return
$http
(
options
);
};
/**
* Gets the list of metrics
* @returns {*|Promise}
*/
KairosDBDatasource
.
prototype
.
performMetricSuggestQuery
=
function
()
{
var
options
=
{
url
:
this
.
url
+
'/api/v1/metricnames'
,
method
:
'GET'
};
return
$http
(
options
).
then
(
function
(
results
)
{
if
(
!
results
.
data
)
{
return
[];
}
return
results
.
data
.
results
;
});
};
KairosDBDatasource
.
prototype
.
performTagSuggestQuery
=
function
(
metricname
,
range
,
type
,
keyValue
)
{
if
(
tagList
&&
(
metricname
===
tagList
.
metricName
)
&&
(
range
.
from
===
tagList
.
range
.
from
)
&&
(
range
.
to
===
tagList
.
range
.
to
))
{
return
getTagListFromResponse
(
tagList
.
results
,
type
,
keyValue
);
}
tagList
=
{
metricName
:
metricname
,
range
:
range
};
var
body
=
{
metrics
:
[{
name
:
metricname
}]
};
convertToKairosTime
(
range
.
from
,
body
,
'start'
);
convertToKairosTime
(
range
.
to
,
body
,
'end'
);
var
options
=
{
url
:
this
.
url
+
'/api/v1/datapoints/query/tags'
,
method
:
'POST'
,
data
:
body
};
return
$http
(
options
).
then
(
function
(
results
)
{
tagList
.
results
=
results
;
return
getTagListFromResponse
(
results
,
type
,
keyValue
);
});
};
/////////////////////////////////////////////////////////////////////////
/// Formatting methods
////////////////////////////////////////////////////////////////////////
function
getTagListFromResponse
(
results
,
type
,
keyValue
)
{
if
(
!
results
.
data
)
{
return
[];
}
if
(
type
===
"key"
)
{
return
_
.
keys
(
results
.
data
.
queries
[
0
].
results
[
0
].
tags
);
}
else
if
(
type
===
"value"
&&
_
.
has
(
results
.
data
.
queries
[
0
].
results
[
0
].
tags
,
keyValue
))
{
return
results
.
data
.
queries
[
0
].
results
[
0
].
tags
[
keyValue
];
}
return
[];
}
/**
* Requires a verion of KairosDB with every CORS defects fixed
* @param results
* @returns {*}
*/
function
handleQueryError
(
results
)
{
if
(
results
.
data
.
errors
&&
!
_
.
isEmpty
(
results
.
data
.
errors
))
{
var
errors
=
{
message
:
results
.
data
.
errors
[
0
]
};
return
$q
.
reject
(
errors
);
}
else
{
return
$q
.
reject
(
results
);
}
}
function
handleKairosDBQueryResponse
(
plotParams
,
results
)
{
var
output
=
[];
var
index
=
0
;
_
.
each
(
results
.
data
.
queries
,
function
(
series
)
{
var
sample_size
=
series
.
sample_size
;
console
.
log
(
"sample_size:"
+
sample_size
+
" samples"
);
_
.
each
(
series
.
results
,
function
(
result
)
{
//var target = result.name;
var
target
=
plotParams
[
index
].
alias
;
var
details
=
" ( "
;
_
.
each
(
result
.
group_by
,
function
(
element
)
{
if
(
element
.
name
===
"tag"
)
{
_
.
each
(
element
.
group
,
function
(
value
,
key
)
{
details
+=
key
+
"="
+
value
+
" "
;
});
}
else
if
(
element
.
name
===
"value"
)
{
details
+=
'value_group='
+
element
.
group
.
group_number
+
" "
;
}
else
if
(
element
.
name
===
"time"
)
{
details
+=
'time_group='
+
element
.
group
.
group_number
+
" "
;
}
});
details
+=
") "
;
if
(
details
!=
" ( ) "
)
target
+=
details
;
var
datapoints
=
[];
for
(
var
i
=
0
;
i
<
result
.
values
.
length
;
i
++
)
{
var
t
=
Math
.
floor
(
result
.
values
[
i
][
0
]);
var
v
=
result
.
values
[
i
][
1
];
datapoints
[
i
]
=
[
v
,
t
];
}
if
(
plotParams
[
index
].
exouter
)
datapoints
=
PeakFilter
(
datapoints
,
10
);
output
.
push
({
target
:
target
,
datapoints
:
datapoints
});
});
index
++
;
});
var
output2
=
{
data
:
_
.
flatten
(
output
)
};
return
output2
;
}
function
convertTargetToQuery
(
options
,
target
)
{
if
(
!
target
.
metric
||
target
.
hide
)
{
return
null
;
}
var
query
=
{
name
:
target
.
metric
};
query
.
aggregators
=
[];
if
(
target
.
downsampling
!==
'(NONE)'
)
{
query
.
aggregators
.
push
({
name
:
target
.
downsampling
,
align_sampling
:
true
,
align_start_time
:
true
,
sampling
:
KairosDBDatasource
.
prototype
.
convertToKairosInterval
(
target
.
sampling
||
options
.
interval
)
});
}
if
(
target
.
horizontalAggregators
)
{
_
.
each
(
target
.
horizontalAggregators
,
function
(
chosenAggregator
)
{
var
returnedAggregator
=
{
name
:
chosenAggregator
.
name
};
if
(
chosenAggregator
.
sampling_rate
)
{
returnedAggregator
.
sampling
=
KairosDBDatasource
.
prototype
.
convertToKairosInterval
(
chosenAggregator
.
sampling_rate
);
returnedAggregator
.
align_sampling
=
true
;
returnedAggregator
.
align_start_time
=
true
;
}
if
(
chosenAggregator
.
unit
)
{
returnedAggregator
.
unit
=
chosenAggregator
.
unit
+
's'
;
}
if
(
chosenAggregator
.
factor
&&
chosenAggregator
.
name
===
'div'
)
{
returnedAggregator
.
divisor
=
chosenAggregator
.
factor
;
}
else
if
(
chosenAggregator
.
factor
&&
chosenAggregator
.
name
===
'scale'
)
{
returnedAggregator
.
factor
=
chosenAggregator
.
factor
;
}
if
(
chosenAggregator
.
percentile
)
{
returnedAggregator
.
percentile
=
chosenAggregator
.
percentile
;
}
query
.
aggregators
.
push
(
returnedAggregator
);
});
}
if
(
_
.
isEmpty
(
query
.
aggregators
))
{
delete
query
.
aggregators
;
}
if
(
target
.
tags
)
{
query
.
tags
=
angular
.
copy
(
target
.
tags
);
}
if
(
target
.
groupByTags
||
target
.
nonTagGroupBys
)
{
query
.
group_by
=
[];
if
(
target
.
groupByTags
)
{
query
.
group_by
.
push
({
name
:
"tag"
,
tags
:
angular
.
copy
(
target
.
groupByTags
)});}
if
(
target
.
nonTagGroupBys
)
{
_
.
each
(
target
.
nonTagGroupBys
,
function
(
rawGroupBy
)
{
var
formattedGroupBy
=
angular
.
copy
(
rawGroupBy
);
if
(
formattedGroupBy
.
name
===
'time'
)
{
formattedGroupBy
.
range_size
=
KairosDBDatasource
.
prototype
.
convertToKairosInterval
(
formattedGroupBy
.
range_size
);
}
query
.
group_by
.
push
(
formattedGroupBy
);
});
}
}
return
query
;
}
///////////////////////////////////////////////////////////////////////
/// Time conversion functions specifics to KairosDB
//////////////////////////////////////////////////////////////////////
KairosDBDatasource
.
prototype
.
convertToKairosInterval
=
function
(
intervalString
)
{
var
interval_regex
=
/
(\d
+
(?:\.\d
+
)?)([
Mwdhmsy
])
/
;
var
interval_regex_ms
=
/
(\d
+
(?:\.\d
+
)?)(
ms
)
/
;
var
matches
=
intervalString
.
match
(
interval_regex_ms
);
if
(
!
matches
)
{
matches
=
intervalString
.
match
(
interval_regex
);
}
if
(
!
matches
)
{
throw
new
Error
(
'Invalid interval string, expecting a number followed by one of "y M w d h m s ms"'
);
}
var
value
=
matches
[
1
];
var
unit
=
matches
[
2
];
if
(
value
%
1
!==
0
)
{
if
(
unit
===
'ms'
)
{
throw
new
Error
(
'Invalid interval value, cannot be smaller than the millisecond'
);}
value
=
Math
.
round
(
kbn
.
intervals_in_seconds
[
unit
]
*
value
*
1000
);
unit
=
'ms'
;
}
switch
(
unit
)
{
case
'ms'
:
unit
=
'milliseconds'
;
break
;
case
's'
:
unit
=
'seconds'
;
break
;
case
'm'
:
unit
=
'minutes'
;
break
;
case
'h'
:
unit
=
'hours'
;
break
;
case
'd'
:
unit
=
'days'
;
break
;
case
'w'
:
unit
=
'weeks'
;
break
;
case
'M'
:
unit
=
'months'
;
break
;
case
'y'
:
unit
=
'years'
;
break
;
default
:
console
.
log
(
"Unknown interval "
,
intervalString
);
break
;
}
return
{
"value"
:
value
,
"unit"
:
unit
};
};
function
convertToKairosTime
(
date
,
response_obj
,
start_stop_name
)
{
var
name
;
if
(
_
.
isString
(
date
))
{
if
(
date
===
'now'
)
{
return
;
}
else
if
(
date
.
indexOf
(
'now-'
)
>=
0
)
{
name
=
start_stop_name
+
"_relative"
;
date
=
date
.
substring
(
4
);
var
re_date
=
/
(\d
+
)\s
*
(\D
+
)
/
;
var
result
=
re_date
.
exec
(
date
);
if
(
result
)
{
var
value
=
result
[
1
];
var
unit
=
result
[
2
];
switch
(
unit
)
{
case
'ms'
:
unit
=
'milliseconds'
;
break
;
case
's'
:
unit
=
'seconds'
;
break
;
case
'm'
:
unit
=
'minutes'
;
break
;
case
'h'
:
unit
=
'hours'
;
break
;
case
'd'
:
unit
=
'days'
;
break
;
case
'w'
:
unit
=
'weeks'
;
break
;
case
'M'
:
unit
=
'months'
;
break
;
case
'y'
:
unit
=
'years'
;
break
;
default
:
console
.
log
(
"Unknown date "
,
date
);
break
;
}
response_obj
[
name
]
=
{
"value"
:
value
,
"unit"
:
unit
};
return
;
}
console
.
log
(
"Unparseable date"
,
date
);
return
;
}
date
=
kbn
.
parseDate
(
date
);
}
if
(
_
.
isDate
(
date
))
{
name
=
start_stop_name
+
"_absolute"
;
response_obj
[
name
]
=
date
.
getTime
();
return
;
}
console
.
log
(
"Date is neither string nor date"
);
}
function
PeakFilter
(
dataIn
,
limit
)
{
var
datapoints
=
dataIn
;
var
arrLength
=
datapoints
.
length
;
if
(
arrLength
<=
3
)
return
datapoints
;
var
LastIndx
=
arrLength
-
1
;
// Check first point
var
prvDelta
=
Math
.
abs
((
datapoints
[
1
][
0
]
-
datapoints
[
0
][
0
])
/
datapoints
[
0
][
0
]);
var
nxtDelta
=
Math
.
abs
((
datapoints
[
1
][
0
]
-
datapoints
[
2
][
0
])
/
datapoints
[
2
][
0
]);
if
(
prvDelta
>=
limit
&&
nxtDelta
<
limit
)
datapoints
[
0
][
0
]
=
datapoints
[
1
][
0
];
// Check last point
prvDelta
=
Math
.
abs
((
datapoints
[
LastIndx
-
1
][
0
]
-
datapoints
[
LastIndx
-
2
][
0
])
/
datapoints
[
LastIndx
-
2
][
0
]);
nxtDelta
=
Math
.
abs
((
datapoints
[
LastIndx
-
1
][
0
]
-
datapoints
[
LastIndx
][
0
])
/
datapoints
[
LastIndx
][
0
]);
if
(
prvDelta
>=
limit
&&
nxtDelta
<
limit
)
datapoints
[
LastIndx
][
0
]
=
datapoints
[
LastIndx
-
1
][
0
];
for
(
var
i
=
1
;
i
<
arrLength
-
1
;
i
++
){
prvDelta
=
Math
.
abs
((
datapoints
[
i
][
0
]
-
datapoints
[
i
-
1
][
0
])
/
datapoints
[
i
-
1
][
0
]);
nxtDelta
=
Math
.
abs
((
datapoints
[
i
][
0
]
-
datapoints
[
i
+
1
][
0
])
/
datapoints
[
i
+
1
][
0
]);
if
(
prvDelta
>=
limit
&&
nxtDelta
>=
limit
)
datapoints
[
i
][
0
]
=
(
datapoints
[
i
-
1
][
0
]
+
datapoints
[
i
+
1
][
0
])
/
2
;
}
return
datapoints
;
}
////////////////////////////////////////////////////////////////////////
return
KairosDBDatasource
;
});
});
src/app/plugins/datasource/kairosdb/partials/config.html
0 → 100644
View file @
618d4f0a
<div
ng-include=
"httpConfigPartialSrc"
></div>
src/app/plugins/datasource/kairosdb/partials/query.editor.html
0 → 100644
View file @
618d4f0a
<div
class=
"editor-row"
>
<div
ng-repeat=
"target in panel.targets"
class=
"tight-form-container"
ng-class=
"{'tight-form-disabled': target.hide}"
ng-controller=
"KairosDBTargetCtrl"
ng-init=
"init()"
>
<div
class=
"tight-form"
>
<ul
class=
"tight-form-list pull-right"
>
<li>
<a
bs-tooltip=
"'Group by\'s are always executed before aggregations!'"
ng-click=
"alert('Group by\'s are always executed before aggregations!')"
>
<i
class=
"icon-info"
></i>
</a>
</li>
<li
class=
"dropdown"
>
<a
class=
"pointer dropdown-toggle"
data-toggle=
"dropdown"
tabindex=
"1"
>
<i
class=
"fa fa-cog"
></i>
</a>
<ul
class=
"dropdown-menu pull-right"
role=
"menu"
>
<li
role=
"menuitem"
>
<a
tabindex=
"1"
ng-click=
"duplicate()"
>
Duplicate
</a>
</li>
<li
role=
"menuitem"
>
<a
tabindex=
"1"
ng-click=
"moveMetricQuery($index, $index-1)"
>
Move up
</a>
</li>
<li
role=
"menuitem"
>
<a
tabindex=
"1"
ng-click=
"moveMetricQuery($index, $index+1)"
>
Move down
</a>
</li>
</ul>
</li>
<li>
<a
class=
"pointer"
tabindex=
"1"
ng-click=
"removeDataQuery(target)"
>
<i
class=
"fa fa-remove"
></i>
</a>
</li>
</ul>
<ul
class=
"tight-form-list"
>
<li
class=
"tight-form-item"
style=
"min-width: 15px; text-align: center"
>
{{targetLetters[$index]}}
</li>
<li>
<a
class=
"tight-form-item"
ng-click=
"target.hide = !target.hide; targetBlur();"
role=
"menuitem"
>
<i
class=
"fa fa-fw fa-eye"
></i>
</a>
</li>
<li>
<input
type=
"text"
class=
"input-medium tight-form-input"
ng-model=
"target.alias"
spellcheck=
'false'
placeholder=
"alias"
ng-blur=
"targetBlur()"
>
</li>
<li>
<select
style=
"width: 20em"
class=
"input-medium tight-form-input"
ng-change=
"targetBlur()"
ng-model=
"metric.value"
bs-tooltip=
"metricValue.length > 40 ? metricValue : ''"
ng-options=
"f for f in metric.list"
>
<option
value=
""
>
--select metric--
</option>
</select>
<a
bs-tooltip=
"target.errors.metric"
style=
"color: rgb(229, 189, 28)"
ng-show=
"target.errors.metric"
>
<i
class=
"icon-warning-sign"
></i>
</a>
</li>
<li
class=
"tight-form-item"
>
Peak filter
<input
class=
"input-medium"
type=
"checkbox"
ng-model=
"target.exOuter"
ng-change=
"targetBlur()"
>
</li>
</ul>
<div
class=
"clearfix"
></div>
</div>
<div
class=
"tight-form"
>
<ul
class=
"tight-form-list"
role=
"menu"
>
<li
class=
"tight-form-item"
style=
"min-width: 15px; text-align: center"
>
</li>
<li>
<i
class=
"fa fa-fw fa-eye invisible"
></i>
</li>
<li
class=
"tight-form-item"
>
Filter by Tag:
</li>
<li
ng-repeat=
"(key, value) in target.tags track by $index"
class=
"tight-form-item"
>
{{key}}
=
{{value}}
<a
ng-click=
"removeFilterTag(key)"
>
<i
class=
"icon-remove"
></i>
</a>
</li>
<li
class=
"tight-form-item"
ng-hide=
"addFilterTagMode"
>
<a
ng-click=
"addFilterTag()"
>
<i
class=
"icon-plus-sign"
></i>
</a>
</li>
<li
ng-show=
"addFilterTagMode"
>
<input
type=
"text"
class=
"input-small tight-form-input"
spellcheck=
'false'
bs-typeahead=
"suggestTagKeys"
ng-change=
"validateFilterTag()"
data-min-length=
0
data-items=
100
ng-model=
"target.currentTagKey"
placeholder=
"key"
>
</li>
<li
ng-show=
"addFilterTagMode"
>
<input
type=
"text"
class=
"input-small tight-form-input"
spellcheck=
'false'
bs-typeahead=
"suggestTagValues"
ng-change=
"validateFilterTag()"
data-min-length=
0
data-items=
100
ng-model=
"target.currentTagValue"
placeholder=
"value"
>
<a
bs-tooltip=
"target.errors.tags"
style=
"color: rgb(229, 189, 28)"
ng-show=
"target.errors.tags"
>
<i
class=
"icon-warning-sign"
></i>
</a>
<li
class=
"tight-form-item"
ng-show=
"addFilterTagMode"
>
<a
ng-click=
"addFilterTag()"
>
<i
ng-show=
"target.errors.tags"
class=
"icon-remove"
></i>
<i
ng-hide=
"target.errors.tags"
class=
"icon-plus-sign"
></i>
</a>
</li>
</li>
<!-- TAGS GROUP BYS -->
<li
class=
"tight-form-item"
>
Group by
</li>
<li
class=
"tight-form-item"
ng-show=
"target.groupByTags"
>
tags:
</li>
<li
ng-repeat=
"key in target.groupByTags track by $index"
class=
"tight-form-item"
>
{{key}}
<a
ng-click=
"removeGroupByTag($index)"
>
<i
class=
"icon-remove"
></i>
</a>
</li>
<li
class=
"tight-form-item"
ng-show=
"target.groupByTags && target.nonTagGroupBys"
>
and by:
</li>
<li
ng-repeat=
"groupByObject in target.nonTagGroupBys track by $index"
class=
"tight-form-item"
>
{{_.values(groupByObject)}}
<a
ng-click=
"removeNonTagGroupBy($index)"
>
<i
class=
"icon-remove"
></i>
</a>
</li>
<li
class=
"tight-form-item"
ng-hide=
"addGroupByMode"
>
<a
ng-click=
"addGroupBy()"
>
<i
class=
"fa fa-fw fa-plus"
></i>
</a>
</li>
<li
ng-show=
"addGroupByMode"
>
<select
class=
"input-small tight-form-input"
ng-change=
"changeGroupByInput()"
ng-model=
"target.currentGroupByType"
ng-options=
"f for f in ['tag','value','time']"
></select>
</li>
<li
ng-show=
"isTagGroupBy"
>
<input
type=
"text"
class=
"input-small tight-form-input"
spellcheck=
'false'
bs-typeahead=
"suggestTagKeys"
ng-change =
"validateGroupBy()"
data-min-length=
0
data-items=
100
ng-model=
"target.groupBy.tagKey"
placeholder=
"key"
>
<a
bs-tooltip=
"target.errors.groupBy.tagKey"
style=
"color: rgb(229, 189, 28)"
ng-show=
"target.errors.groupBy.tagKey"
>
<i
class=
"icon-warning-sign"
></i>
</a>
</li>
<li
ng-show=
"isValueGroupBy"
>
<input
type=
"text"
class=
"input-mini tight-form-input"
spellcheck=
'false'
ng-model=
"target.groupBy.valueRange"
placeholder=
"range"
bs-tooltip=
"'Range on which values are considered in the same group'"
ng-change =
"validateGroupBy()"
>
<a
bs-tooltip=
"target.errors.groupBy.valueRange"
style=
"color: rgb(229, 189, 28)"
ng-show=
"target.errors.groupBy.valueRange"
>
<i
class=
"icon-warning-sign"
></i>
</a>
</li>
<li
ng-show=
"isTimeGroupBy"
>
<input
type=
"text"
class=
"input-mini tight-form-input"
ng-model=
"target.groupBy.timeInterval"
ng-init=
"target.groupBy.timeInterval='1s'"
placeholder=
"interval"
bs-tooltip=
"'Duration of time groups'"
spellcheck=
'false'
ng-change=
"validateGroupBy()"
>
<a
bs-tooltip=
"target.errors.groupBy.timeInterval"
style=
"color: rgb(229, 189, 28)"
ng-show=
"target.errors.groupBy.timeInterval"
>
<i
class=
"icon-warning-sign"
></i>
</a>
</li>
<li
ng-show=
"isTimeGroupBy"
>
<input
type=
"text"
class=
"input-mini tight-form-input"
ng-model=
"target.groupBy.groupCount"
placeholder=
"Count"
bs-tooltip=
"'Number of time groups to be formed'"
spellcheck=
'false'
ng-change=
"validateGroupBy()"
>
<a
bs-tooltip=
"target.errors.groupBy.groupCount"
style=
"color: rgb(229, 189, 28)"
ng-show=
"target.errors.groupBy.groupCount"
>
<i
class=
"icon-warning-sign"
></i>
</a>
</li>
<li
class=
"tight-form-item"
ng-show=
"addGroupByMode"
>
<a
ng-click=
"addGroupBy()"
>
<i
ng-hide=
"isGroupByValid"
class=
"icon-remove"
></i>
<i
ng-show=
"isGroupByValid"
class=
"icon-plus-sign"
></i>
</a>
</li>
<!-- HORIZONTAL AGGREGATION -->
<li
class=
"tight-form-item"
>
Aggregation:
</li>
<li
ng-repeat=
"aggregatorObject in target.horizontalAggregators track by $index"
class=
"tight-form-item"
>
{{aggregatorObject.name}}
(
<span
ng-repeat=
"aggKey in _.keys(_.omit(aggregatorObject,'name'))"
bs-tooltip=
"aggKey"
>
{{$last?aggregatorObject[aggKey]:aggregatorObject[aggKey]+","}}
</span>
)
<a
ng-click=
"removeHorizontalAggregator($index)"
>
<i
class=
"fa fa-remove"
></i>
</a>
</li>
<li
class=
"tight-form-item"
ng-hide=
"addHorizontalAggregatorMode"
>
<a
ng-click=
"addHorizontalAggregator()"
>
<i
class=
"fa fa-plus"
></i>
</a>
</li>
<li
ng-show=
"addHorizontalAggregatorMode"
>
<select
class=
"input-medium tight-form-input"
ng-change=
"changeHorAggregationInput()"
ng-model=
"target.currentHorizontalAggregatorName"
ng-options=
"f for f in ['avg','dev','max','min','rate','sampler','count','sum','least_squares','percentile','scale','div']"
></select>
</li>
<!-- Different parameters -->
<li
ng-show=
"hasSamplingRate"
class=
"tight-form-item"
>
every
</li>
<li
ng-show=
"hasSamplingRate"
>
<input
type=
"text"
class=
"input-mini tight-form-input"
ng-model=
"target.horAggregator.samplingRate"
ng-init=
"target.horAggregator.samplingRate='1s'"
spellcheck=
'false'
ng-change=
"validateHorizontalAggregator()"
>
<a
bs-tooltip=
"target.errors.horAggregator.samplingRate"
style=
"color: rgb(229, 189, 28)"
ng-show=
"target.errors.horAggregator.samplingRate"
>
<i
class=
"icon-warning-sign"
></i>
</a>
</li>
<li
ng-show=
"hasUnit"
class=
"tight-form-item"
>
every
</li>
<li
ng-show=
"hasUnit"
>
<select
class=
"input-medium tight-form-input"
ng-model=
"target.horAggregator.unit"
ng-init=
"target.horAggregator.unit='millisecond'"
ng-options=
"f for f in ['millisecond','second','minute','hour','day','week','month','year']"
></select>
</li>
<li
ng-show=
"hasFactor"
class=
"tight-form-item"
>
by
</li>
<li
ng-show=
"hasFactor"
>
<input
type=
"text"
class=
"input-mini tight-form-input"
ng-model=
"target.horAggregator.factor"
ng-init=
"target.horAggregator.factor='1'"
spellcheck=
'false'
ng-change=
"validateHorizontalAggregator()"
>
<a
bs-tooltip=
"target.errors.horAggregator.factor"
style=
"color: rgb(229, 189, 28)"
ng-show=
"target.errors.horAggregator.factor"
>
<i
class=
"fa fa-warning"
></i>
</a>
</li>
<li
ng-show=
"hasPercentile"
class=
"tight-form-item"
>
percentile
</li>
<li
ng-show=
"hasPercentile"
>
<input
type=
"text"
class=
"input-mini tight-form-input"
ng-model=
"target.horAggregator.percentile"
ng-init=
"target.horAggregator.percentile='0.75'"
spellcheck=
'false'
ng-change=
"validateHorizontalAggregator()"
>
<a
bs-tooltip=
"target.errors.horAggregator.percentile"
style=
"color: rgb(229, 189, 28)"
ng-show=
"target.errors.horAggregator.percentile"
>
<i
class=
"fa fa-warning"
></i>
</a>
</li>
<li
class=
"tight-form-item"
ng-show=
"addHorizontalAggregatorMode"
>
<a
ng-click=
"addHorizontalAggregator()"
>
<i
ng-hide=
"isAggregatorValid"
class=
"fa fa-remove"
></i>
<i
ng-show=
"isAggregatorValid"
class=
"fa fa-plus"
></i>
</a>
</li>
</ul>
<div
class=
"clearfix"
></div>
</div>
</div>
</div>
<section
class=
"grafana-metric-options"
ng-controller=
"KairosDBTargetCtrl"
>
<div
class=
"tight-form"
>
<ul
class=
"tight-form-list"
>
<li
class=
"tight-form-item tight-form-item-icon"
>
<i
class=
"fa fa-wrench"
></i>
</li>
<li
class=
"tight-form-item"
>
Downsampling with
</li>
<li>
<select
class=
"input-medium tight-form-input"
ng-change=
"panelBlur()"
ng-model=
"panel.downsampling"
ng-options=
"f for f in ['(NONE)','avg', 'sum', 'min', 'max', 'dev']"
></select>
</li>
<!-- SAMPLING RATE -->
<li
ng-hide=
"panel.downsampling=='(NONE)'"
class=
"tight-form-item"
>
every
</li>
<li>
<input
type=
"text"
ng-hide=
"panel.downsampling=='(NONE)'"
class=
"input-mini tight-form-input"
ng-model=
"panel.sampling"
placeholder=
"{{interval}}"
bs-tooltip=
"'Leave blank for auto handling based on time range and panel width'"
spellcheck=
'false'
ng-blur=
"panelBlur()"
>
<a
bs-tooltip=
"target.errors.sampling"
style=
"color: rgb(229, 189, 28)"
ng-show=
"target.errors.sampling"
>
<i
class=
"icon-warning-sign"
></i>
</a>
</li>
</ul>
<div
class=
"clearfix"
></div>
</div>
</section>
src/app/plugins/datasource/kairosdb/plugin.json
0 → 100644
View file @
618d4f0a
{
"pluginType"
:
"datasource"
,
"name"
:
"KairosDB"
,
"type"
:
"kairosdb"
,
"serviceName"
:
"KairosDBDatasource"
,
"module"
:
"plugins/datasource/kairosdb/datasource"
,
"partials"
:
{
"config"
:
"app/plugins/datasource/kairosdb/partials/config.html"
,
"query"
:
"app/plugins/datasource/kairosdb/partials/query.editor.html"
},
"metrics"
:
true
,
"annotations"
:
false
}
src/app/plugins/datasource/kairosdb/queryCtrl.js
0 → 100644
View file @
618d4f0a
define
([
'angular'
,
'lodash'
],
function
(
angular
,
_
)
{
'use strict'
;
var
module
=
angular
.
module
(
'grafana.controllers'
);
var
metricList
=
null
;
var
targetLetters
=
[
'A'
,
'B'
,
'C'
,
'D'
,
'E'
,
'F'
,
'G'
,
'H'
,
'I'
,
'J'
,
'K'
,
'L'
,
'M'
,
'N'
,
'O'
];
module
.
controller
(
'KairosDBTargetCtrl'
,
function
(
$scope
)
{
$scope
.
init
=
function
()
{
$scope
.
metric
=
{
list
:
[
"Loading..."
],
value
:
"Loading..."
};
$scope
.
panel
.
stack
=
false
;
if
(
!
$scope
.
panel
.
downsampling
)
{
$scope
.
panel
.
downsampling
=
'avg'
;
}
if
(
!
$scope
.
target
.
downsampling
)
{
$scope
.
target
.
downsampling
=
$scope
.
panel
.
downsampling
;
$scope
.
target
.
sampling
=
$scope
.
panel
.
sampling
;
}
$scope
.
targetLetters
=
targetLetters
;
$scope
.
updateMetricList
();
$scope
.
target
.
errors
=
validateTarget
(
$scope
.
target
);
};
$scope
.
targetBlur
=
function
()
{
$scope
.
target
.
metric
=
$scope
.
metric
.
value
;
$scope
.
target
.
errors
=
validateTarget
(
$scope
.
target
);
if
(
!
_
.
isEqual
(
$scope
.
oldTarget
,
$scope
.
target
)
&&
_
.
isEmpty
(
$scope
.
target
.
errors
))
{
$scope
.
oldTarget
=
angular
.
copy
(
$scope
.
target
);
$scope
.
get_data
();
}
};
$scope
.
panelBlur
=
function
()
{
_
.
each
(
$scope
.
panel
.
targets
,
function
(
target
)
{
target
.
downsampling
=
$scope
.
panel
.
downsampling
;
target
.
sampling
=
$scope
.
panel
.
sampling
;
});
$scope
.
get_data
();
};
$scope
.
duplicate
=
function
()
{
var
clone
=
angular
.
copy
(
$scope
.
target
);
$scope
.
panel
.
targets
.
push
(
clone
);
};
$scope
.
moveMetricQuery
=
function
(
fromIndex
,
toIndex
)
{
_
.
move
(
$scope
.
panel
.
targets
,
fromIndex
,
toIndex
);
};
//////////////////////////////
// SUGGESTION QUERIES
//////////////////////////////
$scope
.
updateMetricList
=
function
()
{
$scope
.
metricListLoading
=
true
;
metricList
=
[];
$scope
.
datasource
.
performMetricSuggestQuery
().
then
(
function
(
series
)
{
metricList
=
series
;
$scope
.
metric
.
list
=
series
;
if
(
$scope
.
target
.
metric
)
$scope
.
metric
.
value
=
$scope
.
target
.
metric
;
else
$scope
.
metric
.
value
=
""
;
$scope
.
metricListLoading
=
false
;
return
metricList
;
});
};
$scope
.
suggestTagKeys
=
function
(
query
,
callback
)
{
$scope
.
updateTimeRange
();
callback
(
$scope
.
datasource
.
performTagSuggestQuery
(
$scope
.
target
.
metric
,
$scope
.
rangeUnparsed
,
'key'
,
''
));
};
$scope
.
suggestTagValues
=
function
(
query
,
callback
)
{
callback
(
$scope
.
datasource
.
performTagSuggestQuery
(
$scope
.
target
.
metric
,
$scope
.
rangeUnparsed
,
'value'
,
$scope
.
target
.
currentTagKey
));
};
//////////////////////////////
// FILTER by TAG
//////////////////////////////
$scope
.
addFilterTag
=
function
()
{
if
(
!
$scope
.
addFilterTagMode
)
{
$scope
.
addFilterTagMode
=
true
;
$scope
.
validateFilterTag
();
return
;
}
if
(
!
$scope
.
target
.
tags
)
{
$scope
.
target
.
tags
=
{};
}
$scope
.
validateFilterTag
();
if
(
!
$scope
.
target
.
errors
.
tags
)
{
if
(
!
_
.
has
(
$scope
.
target
.
tags
,
$scope
.
target
.
currentTagKey
))
{
$scope
.
target
.
tags
[
$scope
.
target
.
currentTagKey
]
=
[];
}
$scope
.
target
.
tags
[
$scope
.
target
.
currentTagKey
].
push
(
$scope
.
target
.
currentTagValue
);
$scope
.
target
.
currentTagKey
=
''
;
$scope
.
target
.
currentTagValue
=
''
;
$scope
.
targetBlur
();
}
$scope
.
addFilterTagMode
=
false
;
};
$scope
.
removeFilterTag
=
function
(
key
)
{
delete
$scope
.
target
.
tags
[
key
];
if
(
_
.
size
(
$scope
.
target
.
tags
)
===
0
)
{
$scope
.
target
.
tags
=
null
;
}
$scope
.
targetBlur
();
};
$scope
.
validateFilterTag
=
function
()
{
$scope
.
target
.
errors
.
tags
=
null
;
if
(
!
$scope
.
target
.
currentTagKey
||
!
$scope
.
target
.
currentTagValue
)
{
$scope
.
target
.
errors
.
tags
=
"You must specify a tag name and value."
;
}
};
//////////////////////////////
// GROUP BY
//////////////////////////////
$scope
.
addGroupBy
=
function
()
{
if
(
!
$scope
.
addGroupByMode
)
{
$scope
.
addGroupByMode
=
true
;
$scope
.
target
.
currentGroupByType
=
'tag'
;
$scope
.
isTagGroupBy
=
true
;
$scope
.
validateGroupBy
();
return
;
}
$scope
.
validateGroupBy
();
// nb: if error is found, means that user clicked on cross : cancels input
if
(
_
.
isEmpty
(
$scope
.
target
.
errors
.
groupBy
))
{
if
(
$scope
.
isTagGroupBy
)
{
if
(
!
$scope
.
target
.
groupByTags
)
{
$scope
.
target
.
groupByTags
=
[];
}
console
.
log
(
$scope
.
target
.
groupBy
.
tagKey
);
if
(
!
_
.
contains
(
$scope
.
target
.
groupByTags
,
$scope
.
target
.
groupBy
.
tagKey
))
{
$scope
.
target
.
groupByTags
.
push
(
$scope
.
target
.
groupBy
.
tagKey
);
$scope
.
targetBlur
();
}
$scope
.
target
.
groupBy
.
tagKey
=
''
;
}
else
{
if
(
!
$scope
.
target
.
nonTagGroupBys
)
{
$scope
.
target
.
nonTagGroupBys
=
[];
}
var
groupBy
=
{
name
:
$scope
.
target
.
currentGroupByType
};
if
(
$scope
.
isValueGroupBy
)
{
groupBy
.
range_size
=
$scope
.
target
.
groupBy
.
valueRange
;}
else
if
(
$scope
.
isTimeGroupBy
)
{
groupBy
.
range_size
=
$scope
.
target
.
groupBy
.
timeInterval
;
groupBy
.
group_count
=
$scope
.
target
.
groupBy
.
groupCount
;
}
$scope
.
target
.
nonTagGroupBys
.
push
(
groupBy
);
}
$scope
.
targetBlur
();
}
$scope
.
isTagGroupBy
=
false
;
$scope
.
isValueGroupBy
=
false
;
$scope
.
isTimeGroupBy
=
false
;
$scope
.
addGroupByMode
=
false
;
};
$scope
.
removeGroupByTag
=
function
(
index
)
{
$scope
.
target
.
groupByTags
.
splice
(
index
,
1
);
if
(
_
.
size
(
$scope
.
target
.
groupByTags
)
===
0
)
{
$scope
.
target
.
groupByTags
=
null
;
}
$scope
.
targetBlur
();
};
$scope
.
removeNonTagGroupBy
=
function
(
index
)
{
$scope
.
target
.
nonTagGroupBys
.
splice
(
index
,
1
);
if
(
_
.
size
(
$scope
.
target
.
nonTagGroupBys
)
===
0
)
{
$scope
.
target
.
nonTagGroupBys
=
null
;
}
$scope
.
targetBlur
();
};
$scope
.
changeGroupByInput
=
function
()
{
$scope
.
isTagGroupBy
=
$scope
.
target
.
currentGroupByType
===
'tag'
;
$scope
.
isValueGroupBy
=
$scope
.
target
.
currentGroupByType
===
'value'
;
$scope
.
isTimeGroupBy
=
$scope
.
target
.
currentGroupByType
===
'time'
;
$scope
.
validateGroupBy
();
};
$scope
.
validateGroupBy
=
function
()
{
delete
$scope
.
target
.
errors
.
groupBy
;
var
errors
=
{};
$scope
.
isGroupByValid
=
true
;
if
(
$scope
.
isTagGroupBy
)
{
if
(
!
$scope
.
target
.
groupBy
.
tagKey
)
{
$scope
.
isGroupByValid
=
false
;
errors
.
tagKey
=
'You must supply a tag name'
;
}
}
if
(
$scope
.
isValueGroupBy
)
{
if
(
!
$scope
.
target
.
groupBy
.
valueRange
||
!
isInt
(
$scope
.
target
.
groupBy
.
valueRange
))
{
errors
.
valueRange
=
"Range must be an integer"
;
$scope
.
isGroupByValid
=
false
;
}
}
if
(
$scope
.
isTimeGroupBy
)
{
try
{
$scope
.
datasource
.
convertToKairosInterval
(
$scope
.
target
.
groupBy
.
timeInterval
);
}
catch
(
err
)
{
errors
.
timeInterval
=
err
.
message
;
$scope
.
isGroupByValid
=
false
;
}
if
(
!
$scope
.
target
.
groupBy
.
groupCount
||
!
isInt
(
$scope
.
target
.
groupBy
.
groupCount
))
{
errors
.
groupCount
=
"Group count must be an integer"
;
$scope
.
isGroupByValid
=
false
;
}
}
if
(
!
_
.
isEmpty
(
errors
))
{
$scope
.
target
.
errors
.
groupBy
=
errors
;
}
};
function
isInt
(
n
)
{
return
parseInt
(
n
)
%
1
===
0
;
}
//////////////////////////////
// HORIZONTAL AGGREGATION
//////////////////////////////
$scope
.
addHorizontalAggregator
=
function
()
{
if
(
!
$scope
.
addHorizontalAggregatorMode
)
{
$scope
.
addHorizontalAggregatorMode
=
true
;
$scope
.
target
.
currentHorizontalAggregatorName
=
'avg'
;
$scope
.
hasSamplingRate
=
true
;
$scope
.
validateHorizontalAggregator
();
return
;
}
$scope
.
validateHorizontalAggregator
();
// nb: if error is found, means that user clicked on cross : cancels input
if
(
_
.
isEmpty
(
$scope
.
target
.
errors
.
horAggregator
))
{
if
(
!
$scope
.
target
.
horizontalAggregators
)
{
$scope
.
target
.
horizontalAggregators
=
[];
}
var
aggregator
=
{
name
:
$scope
.
target
.
currentHorizontalAggregatorName
};
if
(
$scope
.
hasSamplingRate
)
{
aggregator
.
sampling_rate
=
$scope
.
target
.
horAggregator
.
samplingRate
;}
if
(
$scope
.
hasUnit
)
{
aggregator
.
unit
=
$scope
.
target
.
horAggregator
.
unit
;}
if
(
$scope
.
hasFactor
)
{
aggregator
.
factor
=
$scope
.
target
.
horAggregator
.
factor
;}
if
(
$scope
.
hasPercentile
)
{
aggregator
.
percentile
=
$scope
.
target
.
horAggregator
.
percentile
;}
$scope
.
target
.
horizontalAggregators
.
push
(
aggregator
);
$scope
.
targetBlur
();
}
$scope
.
addHorizontalAggregatorMode
=
false
;
$scope
.
hasSamplingRate
=
false
;
$scope
.
hasUnit
=
false
;
$scope
.
hasFactor
=
false
;
$scope
.
hasPercentile
=
false
;
};
$scope
.
removeHorizontalAggregator
=
function
(
index
)
{
$scope
.
target
.
horizontalAggregators
.
splice
(
index
,
1
);
if
(
_
.
size
(
$scope
.
target
.
horizontalAggregators
)
===
0
)
{
$scope
.
target
.
horizontalAggregators
=
null
;
}
$scope
.
targetBlur
();
};
$scope
.
changeHorAggregationInput
=
function
()
{
$scope
.
hasSamplingRate
=
_
.
contains
([
'avg'
,
'dev'
,
'max'
,
'min'
,
'sum'
,
'least_squares'
,
'count'
,
'percentile'
],
$scope
.
target
.
currentHorizontalAggregatorName
);
$scope
.
hasUnit
=
_
.
contains
([
'sampler'
,
'rate'
],
$scope
.
target
.
currentHorizontalAggregatorName
);
$scope
.
hasFactor
=
_
.
contains
([
'div'
,
'scale'
],
$scope
.
target
.
currentHorizontalAggregatorName
);
$scope
.
hasPercentile
=
'percentile'
===
$scope
.
target
.
currentHorizontalAggregatorName
;
$scope
.
validateHorizontalAggregator
();
};
$scope
.
validateHorizontalAggregator
=
function
()
{
delete
$scope
.
target
.
errors
.
horAggregator
;
var
errors
=
{};
$scope
.
isAggregatorValid
=
true
;
if
(
$scope
.
hasSamplingRate
)
{
try
{
$scope
.
datasource
.
convertToKairosInterval
(
$scope
.
target
.
horAggregator
.
samplingRate
);
}
catch
(
err
)
{
errors
.
samplingRate
=
err
.
message
;
$scope
.
isAggregatorValid
=
false
;
}
}
if
(
$scope
.
hasFactor
)
{
if
(
!
$scope
.
target
.
horAggregator
.
factor
)
{
errors
.
factor
=
'You must supply a numeric value for this aggregator'
;
$scope
.
isAggregatorValid
=
false
;
}
else
if
(
parseInt
(
$scope
.
target
.
horAggregator
.
factor
)
===
0
&&
$scope
.
target
.
currentHorizontalAggregatorName
===
'div'
)
{
errors
.
factor
=
'Cannot divide by 0'
;
$scope
.
isAggregatorValid
=
false
;
}
}
if
(
$scope
.
hasPercentile
)
{
if
(
!
$scope
.
target
.
horAggregator
.
percentile
||
$scope
.
target
.
horAggregator
.
percentile
<=
0
||
$scope
.
target
.
horAggregator
.
percentile
>
1
)
{
errors
.
percentile
=
'Percentile must be between 0 and 1'
;
$scope
.
isAggregatorValid
=
false
;
}
}
if
(
!
_
.
isEmpty
(
errors
))
{
$scope
.
target
.
errors
.
horAggregator
=
errors
;
}
};
$scope
.
alert
=
function
(
message
)
{
alert
(
message
);
};
//////////////////////////////
// VALIDATION
//////////////////////////////
function
MetricListToObject
(
MetricList
)
{
var
result
=
{};
var
Metric
;
var
MetricArray
=
[];
var
MetricCnt
=
0
;
for
(
var
i
=
0
;
i
<
MetricList
.
length
;
i
++
)
{
Metric
=
MetricList
[
i
];
MetricArray
=
Metric
.
split
(
'.'
);
if
(
!
result
.
hasOwnProperty
(
MetricArray
[
0
]))
{
result
[
MetricArray
[
0
]]
=
{};
}
if
(
!
result
[
MetricArray
[
0
]].
hasOwnProperty
(
MetricArray
[
1
]))
{
result
[
MetricArray
[
0
]][
MetricArray
[
1
]]
=
[];
}
result
[
MetricArray
[
0
]][
MetricArray
[
1
]].
push
(
MetricArray
[
2
]);
}
return
result
;
}
function
validateTarget
(
target
)
{
var
errs
=
{};
if
(
!
target
.
metric
)
{
errs
.
metric
=
"You must supply a metric name."
;
}
try
{
if
(
target
.
sampling
)
{
$scope
.
datasource
.
convertToKairosInterval
(
target
.
sampling
);
}
}
catch
(
err
)
{
errs
.
sampling
=
err
.
message
;
}
return
errs
;
}
});
});
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