Scripted Dashboard in Grafana with opentsdb as the source - opentsdb

I want to create a scripted dashboard that takes one OpenTSDB metric as the datasource. On the Grafana website, I couldn't find any example. I hope I can add some line like:
metric = 'my.metric.name'
into the JavaScript code, and than I can access the dashboard on the fly.
var rows = 1;
var seriesName = 'argName';
if(!_.isUndefined(ARGS.rows)) {
rows = parseInt(ARGS.rows, 10);
}
if(!_.isUndefined(ARGS.name)) {
seriesName = ARGS.name;
}
for (var i = 0; i < rows; i++) {
dashboard.rows.push({
title: 'Scripted Graph ' + i,
height: '300px',
panels: [
{
title: 'Events',
type: 'graph',
span: 12,
fill: 1,
linewidth: 2,
targets: [
{
'target': "randomWalk('" + seriesName + "')"
},
{
'target': "randomWalk('random walk2')"
}
],
}
]
});
}
return dashboard;

Sorry to answer my own question. But I just figured it out and hopefully post here will benefit somebody.
The script is here. Access the dashboard on the fly with:
http://grafana_ip:3000/dashboard/script/donkey.js?name=tsdbmetricname
/* global _ */
/*
* Complex scripted dashboard
* This script generates a dashboard object that Grafana can load. It also takes a number of user
* supplied URL parameters (in the ARGS variable)
*
* Return a dashboard object, or a function
*
* For async scripts, return a function, this function must take a single callback function as argument,
* call this callback function with the dashboard object (look at scripted_async.js for an example)
*/
// accessible variables in this scope
var window, document, ARGS, $, jQuery, moment, kbn;
// Setup some variables
var dashboard;
// All url parameters are available via the ARGS object
var ARGS;
// Intialize a skeleton with nothing but a rows array and service object
dashboard = {
rows : [],
};
// Set a title
dashboard.title = 'From Shrek';
// Set default time
// time can be overriden in the url using from/to parameters, but this is
// handled automatically in grafana core during dashboard initialization
dashboard.time = {
from: "now-6h",
to: "now"
};
var rows = 1;
var metricName = 'argName';
//if(!_.isUndefined(ARGS.rows)) {
// rows = parseInt(ARGS.rows, 10);
//}
if(!_.isUndefined(ARGS.name)) {
metricName = ARGS.name;
}
for (var i = 0; i < rows; i++) {
dashboard.rows.push({
title: metricName,
height: '300px',
panels: [
{
title: metricName,
type: 'graph',
span: 12,
fill: 1,
linewidth: 2,
targets: [
{
"aggregator": "avg",
"downsampleAggregator": "avg",
"errors": {},
"metric":ARGS.name,
//"metric": "search-engine.relevance.latency.mean",
"tags": {
"host": "*"
}
}
],
tooltip: {
shared: true
}
}
]
});
}
return dashboard;

Related

Kibana Server-Side Plugin in 7.16.3, Response.Body is always empty and fails validation step

The plugin I'm working on is for Kibana 7.16.3.
The server side code currently looks like the following:
import { schema } from '#kbn/config-schema';
import { logger } from 'elastic-apm-node';
import { IRouter } from '../../../../src/core/server';
import { ComplexityAndChurnFactory } from "../resources/cxchquery";
import { validateBody, linearmap } from "../resources/utility";
let elasticSearchHost = ""
export function defineHosts(host: string) {
elasticSearchHost = host
}
export function defineRoutes(router: IRouter) {
router.get(
{
path: '/api/complexity_and_churn/agg',
validate: {
params: schema.object({}),
body: schema.object({
Size: schema.number({}),
Index: schema.string({}),
StartDate: schema.string({}),
EndDate: schema.string({}),
FileTypeFilters: schema.arrayOf(schema.string({}), {})
}, { })
},
},
async (context, request, response) => {
console.log(`Recv Req: ${JSON.stringify(request.body)}`);
let reqBody = request.body;
validateBody(reqBody);
let query = ComplexityAndChurnFactory(reqBody.Index, reqBody.StartDate, reqBody.EndDate, reqBody.FileTypeFilters, 10000);
let resultSize = reqBody.Size;
let minScore = 0;
let maxScore = 50;
// If the user needs to scan over 10 million files after date range and filtering, there is likely a bigger problem.
const MAX_QUERIES = 1000;
let topXScores: Array<Object> = []
/**Strategy for getting top scores in one pass of the dataset
* Composite aggreggation returns subset of data => update global min/max complexity/churn based on this data.
* Based on global min/max complexity/churn, calculate the score of the composite aggregation subset.
* Based on global min/max complexity/churn, update the score of the previously saved top scores.
* Join the current aggregation subset and previously saved top scores into one dataset.
* Remove all but the top x scores.
* Repeat with previous composite aggregation after key until data is exhausted.
*/
let minComplexity = Number.POSITIVE_INFINITY;
let maxComplexity = Number.NEGATIVE_INFINITY;
let minChurn = Number.POSITIVE_INFINITY;
let maxChurn = Number.NEGATIVE_INFINITY;
let i = 0;
for (i=0; i<MAX_QUERIES; i++)
{
let resp = await context.core.elasticsearch.client.asCurrentUser.search(
query
);
logger.info(`query responded with: ${resp}`);
// Check for completion
let buckets = resp.body.aggregations.buckets.buckets;
if (buckets.length == 0 || !query?.after_key) {
break;
}
// Set up next query if buckets were returned.
query.after_key = resp.body.aggregations.buckets.after_key;
minComplexity = buckets.reduce((p: Object, v: Object)=>p.complexity.value < v.complexity.value? p.complexity.value : v.complexity.value, minComplexity);
maxComplexity = buckets.reduce((p: Object, v: Object)=>p.complexity.value > v.complexity.value? p.complexity.value : v.complexity.value, maxComplexity);
minChurn = buckets.reduce((p: Object, v: Object)=>p.churn.value < v.churn.value? p.churn.value : v.churn.value, minChurn);
maxChurn = buckets.reduce((p: Object, v: Object)=>p.churn.value > v.churn.value? p.churn.value : v.churn.value, maxChurn);
// Recalculate scores for topXScores based on updated min and max complexity and churn.
topXScores.forEach(element => {
let complexityScore = linearmap(element.complexity.value, minComplexity, maxComplexity, minScore, maxScore);
let churnScore = linearmap(element.churn.value, minChurn, maxChurn, minScore, maxScore);
element.score = complexityScore + churnScore;
});
// For new data, calculate score and add to topXScores array.
buckets.forEach(element => {
let complexityScore = linearmap(element.complexity.value, minComplexity, maxComplexity, minScore, maxScore);
let churnScore = linearmap(element.churn.value, minChurn, maxChurn, minScore, maxScore);
element.score = complexityScore + churnScore;
topXScores.push(element);
});
// Sort the topXScores by score.
topXScores = topXScores.sort((a, b) => a.score - b.score);
// Remove all but the top x scores from the array.
let numberBucketsToRemove = Math.max(topXScores.length - resultSize, 0);
topXScores.splice(0, numberBucketsToRemove);
}
if (i == MAX_QUERIES) {
throw new Error(`[ERROR] Exceeded maximum allowed queries (${MAX_QUERIES}) for composite aggregations please reach out to an administrator to get this amount changed or limit your query's date range and filters.`)
}
return response.ok({
body: {
buckets: topXScores
}
});
}
);
}
When I make a request to the endpoint like in the following:
curl --request GET 'http://localhost:5601/api/complexity_and_churn/agg' --header 'kbn-xsrf: anything' --header 'content-type: application/json; charset=utf-8' --header 'Authorization: Basic <Auth>' -d '{
"Size": 100,
"Index": "mainindexfour",
"StartDate": "2010/10/10",
"EndDate": "2022/10/10",
"FileTypeFilters": ["xml"]
}'
I get the response:
{
"statusCode": 400,
"error": "Bad Request",
"message": "[request body.Size]: expected value of type [number] but got [undefined]"
}
If I remove the validation on the body and print out JSON.stringify(request.body), I see that it is an empty object, regardless of what data I send. If I try to use params or query, they also end up being undefined.
Is my server side code or the request I'm sending incorrect?

Mapbox GL and .net core webApi

I am moving from Leaflet to Mapbox GL and have some data issues. My webApi is proven but I cannot smoothly integrate them.
The approach I gave up on, based upon their examples and my own research, looks like:
map = new mapboxgl.Map({
container: 'mapdiv',
style: 'mapbox://styles/mapbox/streets-v10'
, center: start
, zoom: $scope.zoom
, transformRequest: (url, resourceType) => {
if (resourceType === 'Source' && url.startsWith(CONFIG.API_URL)) {
return {
headers: {
'Authorization': 'Bearer ' + localStorageService.get("authorizationData")
, 'Access-Control-Allow-Origin': CONFIG.APP_URL
, 'Access-Control-Allow-Credentials': 'true'
}
}
}
}
});
This is passing my OAuth2 token (or at least I think it should be) and the Cross site scripting part CORS.
Accompanying the above with:
map.addSource(layerName, { type: 'geojson', url: getLayerURL($scope.remLayers[i]) });
map.getSource(layerName).setData(getLayerURL($scope.remLayers[i]));
Having also tried to no avail:
map.addSource(layerName, { "type": 'geojson', "data": { "type": "FeatureCollection", "features": [] }});
map.getSource(layerName).setData(getLayerURL($scope.remLayers[i]));
Although there are no errors Fiddler does not show any requests being made to my layer webApi. All the others show but Mapbox does not appear to raising them.
The Url looks like:
http://localhost:49198/api/layer/?bbox=36.686654090881355,34.72821077223763,36.74072742462159,34.73664000652042&dtype=l&id=cf0e1df7-9510-4d03-9319-d4a1a7d6646d&sessionId=9a7d7daf-76fc-4dd8-af4f-b55d341e60e4
Because this was not working I attempted to make it more manual using my existing $http calls which partially works.
map = new mapboxgl.Map({
container: 'mapdiv',
style: 'mapbox://styles/mapbox/streets-v10'
, center: start
, zoom: $scope.zoom
, transformRequest: (url, resourceType) => {
if (resourceType === 'Source' && url.startsWith(CONFIG.API_URL)) {
return {
headers: {
'Authorization': 'Bearer ' + localStorageService.get("authorizationData")
}
}
}
}
});
map.addSource(layerName,
{
"type": 'geojson',
"data": { "type": "FeatureCollection", "features": [] }
});
The tricky part is to know when to run the data retrieval call. The only place I could find was on the maps data event which now looks like:
map.on('data', function (e) {
if (e.dataType === 'source' && e.isSourceLoaded === false && e.tile === undefined) {
// See if the datasource is known
for (var i = 0; i < $scope.remLayers.length; i++) {
if (e.sourceId === $scope.remLayers[i].name) {
askForData(i)
}
}
}
});
function askForData(i) {
var data = getBBoxString(map);
var mapZoomLevel = map.getZoom();
if (checkZoom(mapZoomLevel, $scope.remLayers[i].minZoom, $scope.remLayers[i].maxZoom)) {
mapWebSvr.getData({
bbox: data, dtype: 0, id: $scope.remLayers[i].id, buffer: $scope.remLayers[i].isBuffer, sessionId
},
function (data, indexValue, indexType) {
showNewData(data, indexValue, indexType);
},
function () {
// Not done yet.
},
i,
0
);
}
}
function showNewData(ajxresponse, index, indexType) {
map.getSource($scope.remLayers[index].name).setData(ajxresponse);
map.getSource($scope.remLayers[index].name).isSourceLoaded = true;
}
This is all working with one exception. It keeps firing time and time again. Some of these calls return a lot of data for a web call so its not a solution at the moment.
Its like its never satisfied with the data even though its showing it on the map!
There is a parameter on the data event, isSourceLoaded but it does not get set to true.
I have searched for an example, have tried setting isSourceLoaded in a number of places (as with the code above) but to no avail.
Does anyone have a method accomplishing this basic data retrieval function successfully or can point out the error(s) in my code? Or even point me to a working example...
I have spent too long on this now and could do with some help.
After a bit of a run around I have a solution.
A Mapbox email pointed to populating the data in the load event - which I am now doing.
This was not however the solution I was looking for as the data needs refreshing when the map moves, zooms etc - further look ups are required.
Following a bit more a examination a solution was found.
Using the code blow on the render event will request the information when the bounding box is changed.
var renderStaticBounds = getBoundsString(map.getBounds());
map.on('render', function (e) {
if (renderStaticBounds != getBoundsString(map.getBounds())) {
renderStaticBounds = getBoundsString(map.getBounds());
for (var i = 0; i < $scope.remLayers.length; i++) {
askForData(i);
}
}
});
function getBoundsString(mapBounds) {
var left = mapBounds._sw.lng;
var bottom = mapBounds._sw.lat;
var right = mapBounds._ne.lng;
var top = mapBounds._ne.lat;
return left + ',' + bottom + ',' + right + ',' + top;
}
This hopefully will save someone some development time.

Visjs timeline edit content item template

I'd like to be able to edit the content attribute of visjs timeline items in the timeline itself. However, when I use an input as part of a template, it doesn't appear to receive any mouse events; I can't click in it and type anything, and clicking buttons doesn't work, either. Buttons appear to get the mouseover event, though:
function test(item) {
alert('clicked');
}
var options = {
minHeight: '100%',
editable: true,
moveable: false,
selectable: false,
orientation: 'top',
min: new Date('2015-01-01'),
max: new Date('2015-12-31'),
zoomMin: 1000 * 4 * 60 * 24 * 7,
margin: {
item: 10,
axis: 5
},
template: function(item) {
return '<div onClick="test"><input value="click in the middle"></input><button onClick="test">test</button></div>'
}
};
/* create timeline */
timeline.on('click', function (properties) {
var target = properties.event.target;
if(properties.item) properties.event.target.focus();
});
https://codepen.io/barticula/pen/EpWJKd
Edit: Code above CodePen example have been updated to use the click event to focus on the input, but all other normal mouse behavior is missing. Keyboard events appear to function normally.
To get a reaction with a click on a timeline element, you can use the library's own events (see events on doc and this exemple on website).
On your example, you could do something like this among other possible solutions in pure javascript including...
// Configuration for the Timeline
var options = {
minHeight: '100%',
editable: true,
moveable: false,
selectable: false,
orientation: 'top',
min: new Date('2015-01-01'),
max: new Date('2015-12-31'),
zoomMin: 1000 * 4 * 60 * 24 * 7,
margin: {
item: 10,
axis: 5
},
template: function(item) {
return '<div id="test-div"><input placeholder="hey" type="text" id="inputTest" ><button id="test-button">test</button></div>'
}
};
// Create a Timeline
var timeline = new vis.Timeline(container, null, options);
timeline.setGroups(groups);
timeline.setItems(items);
timeline.on('click', function (properties) {
var target = properties.event.target;
if(properties.item) alert('click on' + target.id);
});
UPDATED
It is difficult to know exactly what you want to do because there are several possible solutions anyway.
Eventually, I propose another snippet below and a codepen updated.... but will it meet your need, not sure ?
2nd UPDATE (for another work track, see comments)
// Configuration for the Timeline
var options = {
minHeight: '100%',
editable: true,
moveable: false,
selectable: false,
orientation: 'top',
margin: {
item: 10,
axis: 5
},
template: function(item) {
return '<div><input placeholder="edit me..." type="text"></input><button>send value</button></div>'
}
};
// Create a Timeline
var timeline = new vis.Timeline(container, null, options);
timeline.setGroups(groups);
timeline.setItems(items);
timeline.on('click', function(properties) {
var target = properties.event.target;
var item = items.get(properties.item);
console.log(properties.event);
// if (properties.item && target.tagName === "DIV") focusMethod(target);
if (properties.item && target.tagName === "INPUT") target.focus();
if (properties.item && target.tagName === "BUTTON") getInputValue(item, target);
});
focusMethod = function getFocus(target) {
// target.insertAfter("BUTTON");
target.firstChild.focus();
}
getInputValue = function getValue(item, target) {
target.focus();
var inputValue = (target.parentNode.firstChild.value) ? target.parentNode.firstChild.value : "no value entered ";
alert("Input value : " + inputValue + " => send by: " + item.content)
}

ExtJS : move a record in a grid

I have a simple grid on ExtJS and would like the user to be able to move the record from its original position.
When the user double clicks on a record, a small window containing a combobox appears, he can choose a value on the combobox and then click the save button to apply the change.
However, it doesn't work, I've searched many solutions for this on different forums and none seems to work. Either nothing happens, or an undefined row is added at the end of the grid. Here is the base code I use :
onEditRank: function(view, cell, cellIndex, record, row, rowIndex, e)
{
var reditor = Ext.create('CMS.view.Views.RankEditor', {id: 'reditorView'});
var form = reditor.down('form');
var oldPos = this.getFlatrq().getView().indexOf(record);
var grStore = this.getGridRnkStoreStore();
var i;
var data = [];
for(i = 1; i <= CMS.global.Variables.limit + 1; i++)
{
data.push(i);
}
var combo = Ext.create
(
'Ext.form.field.ComboBox',
{
fieldLabel: 'Rank',
itemId: 'cmbRank',
store: data
}
);
var saveRnk = Ext.create
(
'Ext.button.Button',
{
text: 'Save',
handler: function()
{
}
}
);
form.add(combo);
form.add(saveRnk);
reditor.show();
}
Now here are the different handlers I have tried for my save button :
handler: function()
{
grStore.remove(record);
grStore.insert(record, combo.getValue() - 1);
this.up('form').up('window').close();
}
handler: function()
{
grStore.removeAt(oldPos);
grStore.insert(record, combo.getValue() - 1);
this.up('form').up('window').close();
}
handler: function()
{
var rec = grStore.getAt(oldPos).copy();
grStore.removeAt(oldPos);
grStore.insert(rec, combo.getValue() - 1);
this.up('form').up('window').close();
}
Those 3 handlers inserted undefined rows at the end of my grid. I displayed the values of oldPos and combo.getValue() and they are correct, I also displayed the record variable before and after the remove because I thought it might be destroyed but it wasn't. I have also tried to add a move function to store and call it :
'CMS.store.GridRnkStore',
{
extend: 'Ext.data.Store',
model: 'CMS.model.GridRnkModel',
autoLoad: false,
filterOnLoad: true,
autoSync: true,
move: function(from, to)
{
console.log(from + " " + to);
var r = this.getAt(from);
this.data.removeAt(from);
this.data.insert(to, r);
this.fireEvent("move", this, from, to);
},
}
);
But it didn't work either, it did nothing actually (I put some console.log in the move function to see if it was called and it was, with the right parameters). I'm running out of ideas, any help would be appreciated.
Thank you.
try to set the private move parameter to true of store.remove():
remove: function(records, /* private */ isMove, silent)

Unique Dropdownlist is coming for the first page values in Jqgrid

Hello Every One!!!
I added codes for getting unique dropdownlist in the columns of jqgrid .
Dropdownlist is coming but it is coming for the first page of the jqgrid means that dropdownlist has the unique values of the first page of the jqgrid whereas i need all the unique values of the whole Jqgrid..
Below I am posting my codes...
grid = $("#gridId");
getUniqueNames = function (columnName) {
var texts = grid.jqGrid('getCol', columnName), uniqueTexts = [],
textsLength = texts.length, text, textsMap = {}, i;
for (i = 0; i < textsLength; i++) {
text = texts[i];
if (text !== undefined && textsMap[text] === undefined) {
// to test whether the texts is unique we place it in the map.
textsMap[text] = true;
uniqueTexts.push(text);
}
}
return uniqueTexts;
},
buildSearchSelect = function (uniqueNames) {
var values = ":All";
$.each(uniqueNames, function () {
values += ";" + this + ":" + this;
});
return values;
},
setSearchSelect = function (columnName) {
grid.jqGrid('setColProp', columnName,
{
stype: 'select',
searchoptions: {
value: buildSearchSelect(getUniqueNames(columnName)),
sopt: ['eq']
}
}
);
};
This function i have called like this...
setSearchSelect('extension');
grid.jqGrid('setColProp', 'Name',
{
searchoptions: {
sopt: ['cn'],
dataInit: function (elem) {
$(elem).autocomplete({
source: getUniqueNames('Name'),
delay: 0,
minLength: 0
});
}
}
});
setSearchSelect('username');
grid.jqGrid('setColProp', 'Name',
{
searchoptions: {
sopt: ['cn'],
dataInit: function (elem) {
$(elem).autocomplete({
source: getUniqueNames('Name'),
delay: 0,
minLength: 0
});
}
}
});
In between these two code snippets I am loading data into jqgrid locally using Ajax call.
Any help will be heartely appreciated..
Thanx in advance..
I belive getCol will return only currently loaded data from jqgrid (for defined column).
Because at first you load just first page, autocomplete has no way of knowing unique values of column for more than that!
You will have to either load all pages at once (small dataset) or
fill autocomplete from database.

Resources