Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Graylog's Geolocation and other string field -based data sources #276

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,18 +210,33 @@ Supported Databases:

It supports any datasource capable of generating a JSON response with a a custom list of locations (the same format that for the JSON enpoint).

## Graylog Geolocation data as the Data Source

Supported Databases:

- ElasticSearch

Graylog's Geolocation plugin stores the geolocation data as a string in the following format: "latitude,longitude" by default.

Three fields need to be provided by the ElasticSearch query:

- Location Name (optional - geohash value will be shown if not chosen)
- Geopoint field that provides the coordinates as a string in the following format: "latitude,longitude".
- A metric. This is free text and should match the aggregation used (Count, Average, Sum, Unique Count etc.)

### Map Data Options

#### Location Data

There are four ways to provide data for the worldmap panel:
There are seven ways to provide data for the worldmap panel:

- *countries*: This is a list of all the countries in the world. It works by matching a country code (US, FR, AU) to a node alias in a time series query.
- *states*: Similar to countries but for the states in USA e.g. CA for California
- *geohash*: An ElasticSearch query that returns geohashes.
- *json*: A json endpoint that returns custom json. Examples of the format are the [countries data used in first option](https://github.com/grafana/worldmap-panel/blob/master/src/data/countries.json) or [this list of cities](https://github.com/grafana/worldmap-panel/blob/master/src/data/probes.json).
- *jsonp*: A jsonp endpoint that returns custom json wrapped as jsonp. Use this if you are having problems with CORS.
- *table*: This expects the metric query to return data points with a field named geohash or two fields/columns named `latitude` and `longitude`. This field should contain a string in the [geohash form](https://www.elastic.co/guide/en/elasticsearch/guide/current/geohashes.html). For example: London -> "gcpvh3zgu992".
- *string*: An ElasticSearch query that returns the coordinates as a single string.

#### Aggregation

Expand Down
54 changes: 54 additions & 0 deletions src/data_formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,58 @@ export default class DataFormatter {
data.valueRange = highestValue - lowestValue;
}
}

setStringValues(dataList, data) {
if (!this.ctrl.panel.esGeoPoint || !this.ctrl.panel.esMetric) {
return;
}

if (dataList && dataList.length > 0) {
let highestValue = 0;
let lowestValue = Number.MAX_VALUE;

dataList.forEach(result => {
if (result.type === 'table') {
const columnNames = {};

result.columns.forEach((column, columnIndex) => {
columnNames[column.text] = columnIndex;
});

result.rows.forEach(row => {
const coordinatesField = row[columnNames[this.ctrl.panel.esGeoPoint]];
const geoPoint = coordinatesField.split(',');
const latitude = geoPoint[0];
const longitude = geoPoint[1];
const value = row[columnNames[this.ctrl.panel.esMetric]];
const locationName = this.ctrl.panel.esLocationName
? row[columnNames[this.ctrl.panel.esLocationName]]
: latitude + ', ' + longitude;

const dataValue = {
key: geoPoint,
locationName: locationName,
locationLatitude: latitude,
locationLongitude: longitude,
value: value,
valueFormatted: value,
valueRounded: 0,
};
if (dataValue.value > highestValue) {
highestValue = dataValue.value;
}
if (dataValue.value < lowestValue) {
lowestValue = dataValue.value;
}
dataValue.valueRounded = kbn.roundValue(dataValue.value, this.ctrl.panel.decimals || 0);
data.push(dataValue);
});

data.highestValue = highestValue;
data.lowestValue = lowestValue;
data.valueRange = highestValue - lowestValue;
}
});
}
}
}
33 changes: 31 additions & 2 deletions src/partials/editor.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ <h5 class="section-heading">Map Data Options</h5>
<div class="gf-form">
<label class="gf-form-label width-12">Location Data</label>
<div class="gf-form-select-wrapper max-width-10">
<select class="input-small gf-form-input" ng-model="ctrl.panel.locationData" ng-options="t for t in ['countries', 'countries_3letter', 'states', 'probes', 'geohash', 'json endpoint', 'jsonp endpoint', 'json result', 'table']"
<select class="input-small gf-form-input" ng-model="ctrl.panel.locationData" ng-options="t for t in ['countries', 'countries_3letter', 'states', 'probes', 'geohash', 'json endpoint', 'jsonp endpoint', 'json result', 'table', 'string']"
ng-change="ctrl.changeLocationData()"></select>
</div>
</div>
<div class="gf-form" ng-show="ctrl.panel.locationData !== 'geohash'">
<div class="gf-form" ng-show="ctrl.panel.locationData !== 'geohash' && ctrl.panel.locationData !== 'string'">
<label class="gf-form-label width-12">Aggregation</label>
<div class="gf-form-select-wrapper max-width-10">
<select class="input-small gf-form-input" ng-model="ctrl.panel.valueName" ng-options="f for f in ['min','max','avg', 'current', 'total']"
Expand Down Expand Up @@ -138,6 +138,20 @@ <h5>Mapping Between Table Query and Worldmap</h5>
how large the circle is.</li>
</ul>
</div>
<div class="grafana-info-box max-width-28" ng-show="ctrl.panel.locationData ==='string'">
<p>The query should be an Elasticsearch query. The "Group by" term should match the field that contains the coordinates.</p>
<ul>
<li>
<b>Location Name Field (optional)</b>: enter the name of the Location Name column. Used to label each circle on the
map. Add another "Group by" to the query, which contains the location name.</li>
<li>
<b>Geopoint</b>: enter the name of the column that contains the coordinates. This is used to calculate where the
circle should be drawn. Value should be formatted as "latitude,longitude", e.g. "62.3434,-33.534003".</li>
<li>
<b>Metric Field</b>: enter the name of the metric column. This is used to give the circle a value - this determines
how large the circle is.</li>
</ul>
</div>
<h6 ng-show="ctrl.panel.locationData === 'table' || ctrl.panel.locationData === 'geohash'">Field Mapping</h6>
<div class="gf-form" ng-show="ctrl.panel.locationData === 'table'">
<label class="gf-form-label width-12">Table Query Format</label>
Expand Down Expand Up @@ -186,6 +200,21 @@ <h6 ng-show="ctrl.panel.locationData === 'table' || ctrl.panel.locationData ===
<input type="text" class="input-small gf-form-input width-10" ng-model="ctrl.panel.esMetric" ng-change="ctrl.refresh()" ng-model-onblur
/>
</div>
<div class="gf-form" ng-show="ctrl.panel.locationData === 'string'">
<label class="gf-form-label width-12">Location Name Field</label>
<input type="text" class="input-small gf-form-input width-10" ng-model="ctrl.panel.esLocationName" ng-change="ctrl.refresh()"
ng-model-onblur />
</div>
<div class="gf-form" ng-show="ctrl.panel.locationData === 'string'">
<label class="gf-form-label width-12">geo_point/geohash Field</label>
<input type="text" class="input-small gf-form-input width-10" ng-model="ctrl.panel.esGeoPoint" ng-change="ctrl.refresh()"
ng-model-onblur />
</div>
<div class="gf-form" ng-show="ctrl.panel.locationData === 'string'">
<label class="gf-form-label width-12">Metric Field</label>
<input type="text" class="input-small gf-form-input width-10" ng-model="ctrl.panel.esMetric" ng-change="ctrl.refresh()" ng-model-onblur
/>
</div>
</div>

<div class="section gf-form-group">
Expand Down
3 changes: 3 additions & 0 deletions src/worldmap_ctrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ export default class WorldmapCtrl extends MetricsPanelCtrl {
} else if (this.panel.locationData === "json result") {
this.series = dataList;
this.dataFormatter.setJsonValues(data);
} else if (this.panel.locationData === "string") {
this.series = dataList;
this.dataFormatter.setStringValues(dataList, data);
} else {
this.series = dataList.map(this.seriesHandler.bind(this));
this.dataFormatter.setValues(data);
Expand Down