Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
yanscalyr committed Sep 30, 2020
2 parents 259d582 + 470e867 commit d8b9a59
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 6 deletions.
6 changes: 3 additions & 3 deletions dist/module.js

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion dist/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"type": "datasource",
"metrics": true,
"annotations": false,
"annotations": true,
"category": "logging",
"name": "Scalyr",
"id": "scalyr-datasource",
Expand Down Expand Up @@ -35,6 +35,11 @@
"path": "powerQuery",
"method": "POST",
"url": "{{.JsonData.scalyrUrl}}/api/powerQuery"
},
{
"path": "query",
"method": "POST",
"url": "{{.JsonData.scalyrUrl}}/api/query"
}
]
}
64 changes: 64 additions & 0 deletions src/datasource.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ export class GenericDatasource {
return this.performTimeseriesQuery(options);
}

annotationQuery(options) {
const query = this.createLogsQueryForAnnotation(options);
return this.backendSrv.datasourceRequest(query)
.then( (response) => {
const data = response.data;
const timeField = options.annotation.timeField || "timestamp"
const timeEndField = options.annotation.timeEndField || null
const textField = options.annotation.textField || "message"
return GenericDatasource.transformAnnotationResults(data.matches, timeField, timeEndField, textField);
}
);
}

/**
* Grafana uses this function to test data source settings.
* This verifies API key using the facet query API.
Expand Down Expand Up @@ -194,6 +207,24 @@ export class GenericDatasource {
};
}

createLogsQueryForAnnotation(options) {
const queryText = this.templateSrv.replace(options.annotation.queryText, options.scopedVars, GenericDatasource.interpolateVariable);

return {
url: this.url + '/query',
method: 'POST',
headers: this.headers,
data: JSON.stringify({
token: this.apiKey,
queryType: "log",
filter: queryText,
startTime: options.range.from.valueOf(),
endTime: options.range.to.valueOf(),
maxCount: 5000
})
};
}

/**
* Get how many buckets to return based on the query time range
* @param {*} options
Expand Down Expand Up @@ -246,6 +277,39 @@ export class GenericDatasource {
return graphs;
}

/**
* Transform data returned by time series query into Grafana annotation format.
* @param results
* @param options
* @returns Array
*/
static transformAnnotationResults(results, timeField, timeEndField, textField) {
const annotations = [];
results.forEach((result) => {
const responseObject = {};
responseObject.time = Number(result[timeField]) / 1000000;
if (!responseObject.time && result.attributes) {
responseObject.time = Number(result.attributes[timeField]) / 1000000;
}

responseObject.text = result[textField];
if (!responseObject.text && result.attributes) {
responseObject.text = result.attributes[textField];
}

if (timeEndField) {
responseObject.timeEnd = Number(result[timeEndField]) / 1000000;
if (!responseObject.timeEnd && result.attributes) {
responseObject.timeEnd = Number(result.attributes[timeEndField]) / 1000000;
}
}
if (responseObject.time) {
annotations.push(responseObject);
}
});
return annotations;
}

/**
* Create powerquery query to pass to Grafana proxy.
* @param queryText text of the query
Expand Down
3 changes: 2 additions & 1 deletion src/module.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ export {
GenericDatasource as Datasource,
GenericDatasourceQueryCtrl as QueryCtrl,
GenericConfigCtrl as ConfigCtrl,
GenericQueryOptionsCtrl as QueryOptionsCtrl
GenericQueryOptionsCtrl as QueryOptionsCtrl,
GenericAnnotationsQueryCtrl as AnnotationsQueryCtrl
};
29 changes: 29 additions & 0 deletions src/partials/annotations.editor.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<div class="gf-form-group">
<div class="gf-form-inline">
<div class="gf-form gf-form--grow">
<input
class="gf-form-input"
placeholder="query expression"
ng-model="ctrl.annotation.queryText"
></input>
</div>
</div>
</div>

<div class="gf-form-group">
<h6>Field mappings</h6>
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label">Time</span>
<input type="text" class="gf-form-input max-width-14" ng-model='ctrl.annotation.timeField' placeholder="timestamp"></input>
</div>
<div class="gf-form">
<span class="gf-form-label">Time End</span>
<input type="text" class="gf-form-input max-width-14" ng-model='ctrl.annotation.timeEndField' placeholder=""></input>
</div>
<div class="gf-form">
<span class="gf-form-label">Text</span>
<input type="text" class="gf-form-input max-width-14" ng-model='ctrl.annotation.textField' placeholder="message"></input>
</div>
</div>
</div>
7 changes: 6 additions & 1 deletion src/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"type": "datasource",
"metrics": true,
"annotations": false,
"annotations": true,
"category": "logging",
"name": "Scalyr",
"id": "scalyr-datasource",
Expand Down Expand Up @@ -35,6 +35,11 @@
"path": "powerQuery",
"method": "POST",
"url": "{{.JsonData.scalyrUrl}}/api/powerQuery"
},
{
"path": "query",
"method": "POST",
"url": "{{.JsonData.scalyrUrl}}/api/query"
}
]
}
88 changes: 88 additions & 0 deletions src/specs/datasource.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ describe('Scalyr datasource tests', () => {
]
};

const annotationQueryOptions = {
range: {
from: sixHoursAgo.toISOString(),
to: now.toISOString()
},
interval: '5s',
annotation: [
{
queryText: '$foo=\'bar\'',
}
]
};

const variables = [
{
multi: true,
Expand Down Expand Up @@ -135,4 +148,79 @@ describe('Scalyr datasource tests', () => {
});
});

describe('Annotation queries', () => {
let results;
beforeEach(() => {
results = [
{
timefield: 12345,
messagefield: "testmessage1",
timeendfield: 54321
},
{
timefield: 12345,
messagefield: "testmessage2",
timeendfield: 54321,
attributes: {
timefield: 11111,
messagefield: "wrong",
timeendfield: 22222,
}
},
{
timefield: 12345,
messagefield: "testmessage4",
timeendfield: 54321,
attributes: {
timefield2: 123456,
messagefield2: "testmessage5",
timeendfield2: 543211,
}
}
];
});
it('Should create a query request', () => {
const request = datasource.createLogsQueryForAnnotation(annotationQueryOptions, variables);
expect(request.url).toBe('proxied/query');
expect(request.method).toBe('POST');
const requestBody = JSON.parse(request.data);
expect(requestBody.token).toBe('123');
expect(requestBody.queryType).toBe('log');
expect(requestBody.startTime).toBe(sixHoursAgo.toISOString());
expect(requestBody.endTime).toBe(now.toISOString());
});

it('Should transform standard query results to annotations', () => {
const transformedResults = GenericDatasource.transformAnnotationResults(results, "timefield", "timeendfield", "messagefield");
expect(transformedResults.length).toBe(3);
const resultEntry = transformedResults[0];
expect(resultEntry.text).toBe("testmessage1");
expect(resultEntry.time).toBe(0.012345);
expect(resultEntry.timeEnd).toBe(0.054321);
});

it('Should transform standard query results to annotations, falling back to attribute fields', () => {
const transformedResults = GenericDatasource.transformAnnotationResults(results, "timefield2", "timeendfield2", "messagefield2");
expect(transformedResults.length).toBe(1);
const resultEntry = transformedResults[0];
expect(resultEntry.text).toBe("testmessage5");
expect(resultEntry.time).toBe(0.123456);
expect(resultEntry.timeEnd).toBe(0.543211);
});

it('Should transform standard query results to annotations not from attributes first', () => {
const transformedResults = GenericDatasource.transformAnnotationResults(results, "timefield", "timeendfield", "messagefield");
expect(transformedResults.length).toBe(3);
const resultEntry = transformedResults[1];
expect(resultEntry.text).toBe("testmessage2");
expect(resultEntry.time).toBe(0.012345);
expect(resultEntry.timeEnd).toBe(0.054321);
});

it('Shouldn\'t transform standard query results to annotations with bad field names', () => {
const transformedResults = GenericDatasource.transformAnnotationResults(results, "missingField", null, null);
expect(transformedResults.length).toBe(0);
});
});

});

0 comments on commit d8b9a59

Please sign in to comment.