Skip to content

Commit

Permalink
Merge pull request #15 from kentik/autocomplete-for-plugin-#4
Browse files Browse the repository at this point in the history
Autocomplete for plugin #4
  • Loading branch information
jonyrock authored Sep 16, 2019
2 parents dbe3dcd + e0d5f83 commit f3eaf1f
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 9,935 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ dist/
.vscode/

package-lock.json
yarn.lock
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
"moment": "^2.24.0"
},
"devDependencies": {
"@grafana/toolkit": "next",
"@grafana/ui": "latest",
"@grafana/toolkit": "6.3.2",
"@grafana/ui": "6.3.2",
"@types/angular": "1.6.54",
"@types/grafana": "github:CorpGlory/types-grafana.git",
"angular": "1.6.6"
Expand Down
32 changes: 29 additions & 3 deletions src/datasource/datasource.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { metricList, unitList, filterFieldList } from './metric_def';
import * as _ from 'lodash';
import './kentikProxy';
import TableModel from 'grafana/app/core/table_model';
import { metricList, unitList, filterFieldList, Metric, Unit } from './metric_def';
import queryBuilder from './query_builder';

import TableModel from 'grafana/app/core/table_model';

import * as _ from 'lodash';

class KentikDatasource {
name: string;
kentik: any;
Expand Down Expand Up @@ -157,6 +159,30 @@ class KentikDatasource {
});
}

findMetric(query: { text?: string; value?: string }): Metric | null {
if (query.text === undefined && query.value === undefined) {
throw new Error('At least one of text / value must be defined');
}
const metric = _.find(metricList, query);
if (metric === undefined) {
return null;
}

return metric;
}

findUnit(query: { text?: string; value?: string }): Unit | null {
if (query.text === undefined && query.value === undefined) {
throw new Error('At least one of text / value must be defined');
}
const unit = _.find(unitList, query);
if (unit === undefined) {
return null;
}

return unit;
}

async getTagKeys() {
const initialList = await this._getExtendedDimensionsList(filterFieldList);
const savedFilters = await this.kentik.getSavedFilters();
Expand Down
2 changes: 1 addition & 1 deletion src/datasource/kentikAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export class KentikAPI {
} else {
return [];
}
} catch(error) {
} catch (error) {
showAlert(error);
if (error.err) {
throw error.err;
Expand Down
32 changes: 22 additions & 10 deletions src/datasource/metric_def.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
const metricList = [
export type TransformFunction = (value: number, row: any, rangeSeconds: number) => number;
export type Metric = { text: string; value: string; field: string };
export type Unit = {
text: string;
value: string;
field: string;
outsort: string;
gfUnit: string;
gfAxisLabel: string;
transform?: TransformFunction;
tableFields: Array<{ text: string; field: string; unit: string; transform?: TransformFunction }>;
};
export type FilterField = { text: string; field: string };

export const metricList: Metric[] = [
{ text: 'Traffic', value: 'Traffic', field: 'traffic' },
{ text: 'TopFlow', value: 'TopFlow', field: 'TopFlow' },
{ text: 'Top flow IP', value: 'TopFlowsIP', field: 'TopFlowsIP' },
Expand Down Expand Up @@ -50,23 +64,23 @@ const metricList = [
{ text: 'Region Top Talkers', value: 'RegionTopTalkers', field: 'RegionTopTalkers' },
];

function toBitsPerSecond(value, row) {
function toBitsPerSecond(value: number, row: any): number {
return (value * 8) / row.i_duration;
}

function toPerSecondRate(value, row) {
function toPerSecondRate(value: number, row: any): number {
return value / row.i_duration;
}

function totalToAvgPerSecond(value, row, rangeSeconds) {
function totalToAvgPerSecond(value: number, row, rangeSeconds: number): number {
return value / rangeSeconds;
}

function totalToBitsPerSecond(value, row, rangeSeconds) {
function totalToBitsPerSecond(value: number, row, rangeSeconds: number): number {
return (value * 8) / rangeSeconds;
}

const unitList = [
export const unitList: Unit[] = [
{
text: 'Bits/s',
value: 'bytes',
Expand All @@ -87,7 +101,7 @@ const unitList = [
field: 'f_sum_both_pkts',
outsort: 'avg_both',
gfUnit: 'pps',
gfAxislabel: 'Packets/s',
gfAxisLabel: 'Packets/s',
transform: toPerSecondRate,
tableFields: [
{ text: 'Avg', field: 'avg_both', unit: 'pps', transform: totalToAvgPerSecond },
Expand Down Expand Up @@ -127,7 +141,7 @@ const unitList = [
},
];

const filterFieldList = [
export const filterFieldList: FilterField[] = [
{ text: 'Source City', field: 'src_geo_city' },
{ text: 'Source Region', field: 'src_geo_region' },
{ text: 'Source Country', field: 'src_geo' },
Expand Down Expand Up @@ -190,5 +204,3 @@ const filterFieldList = [
{ text: 'Per-flow packets (recorded outbound)', field: 'out_pkts' },
{ text: 'Per-flow bytes (recorded outbound)', field: 'out_bytes' },
];

export { metricList, unitList, filterFieldList };
71 changes: 43 additions & 28 deletions src/datasource/query_editor.html
Original file line number Diff line number Diff line change
@@ -1,30 +1,45 @@
<query-editor-row query-ctrl="ctrl" can-collapse="false">
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label width-7">Device</label>
<input type="text" class="gf-form-input width-11" ng-model="ctrl.target.device"></input>
</div>
<div class="gf-form">
<label class="gf-form-label width-7">Metric</label>
<input type="text" class="gf-form-input width-11" ng-model="ctrl.target.metric"></input>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label width-7">Unit</label>
<input type="text" class="gf-form-input width-11" ng-model="ctrl.target.unit"></input>
</div>
<div class="gf-form">
<label class="gf-form-label width-7">Data mode</label>
<div class="gf-form-select-wrapper width-11">
<select class="gf-form-input" ng-model="ctrl.target.mode" ng-options="f.value as f.text for f in ctrl.queryModes" ng-change="ctrl.refresh()"></select>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label width-7">Device</label>
<metric-segment
class="width-11"
segment="ctrl.deviceSegment"
get-options="ctrl.getDevices($query)"
on-change="ctrl.onDeviceChange()"
></metric-segment>
</div>
<div class="gf-form">
<label class="gf-form-label width-7">Metric</label>
<metric-segment
class="width-11"
segment="ctrl.metricSegment"
get-options="ctrl.getMetrics($query)"
on-change="ctrl.onMetricChange()"
></metric-segment>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label width-7">Unit</label>
<metric-segment
class="width-11"
segment="ctrl.unitSegment"
get-options="ctrl.getUnits($query)"
on-change="ctrl.onUnitChange()"
></metric-segment>
</div>
<div class="gf-form">
<label class="gf-form-label width-7">Data mode</label>
<div class="gf-form-select-wrapper width-11">
<select class="gf-form-input" ng-model="ctrl.target.mode" ng-options="f.value as f.text for f in ctrl.queryModes" ng-change="ctrl.refresh()"></select>
</div>
</div>
<div class="gf-form gf-form--grow">
<div class="gf-form-label gf-form-label--grow"></div>
</div>
</div>
</query-editor-row>
86 changes: 84 additions & 2 deletions src/datasource/query_editor.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,98 @@
import { QueryCtrl } from 'grafana/app/plugins/sdk';
import { UiSegmentSrv, MetricSegment } from 'grafana/app/core/services/segment_srv';
import { TemplateSrv } from 'grafana/app/features/templating/template_srv';

class KentikQueryCtrl extends QueryCtrl {
static templateUrl: string;
queryModes: any[];
queryModes: Array<{ value: string; text: string }>;
metricSegment: MetricSegment;
deviceSegment: MetricSegment;
unitSegment: MetricSegment;

/** @ngInject */
constructor($scope, $injector) {
constructor($scope, $injector, public templateSrv: TemplateSrv, public uiSegmentSrv: UiSegmentSrv) {
super($scope, $injector);

this.target.mode = this.target.mode || 'graph';

this.queryModes = [{ value: 'graph', text: 'Graph' }, { value: 'table', text: 'Table' }];

if (this.target.metric === undefined) {
this.metricSegment = this.uiSegmentSrv.newSegment({ value: 'select metric', fake: true });
} else {
const metric = this.datasource.findMetric({ value: this.target.metric });
if (metric !== null) {
this.metricSegment = this.uiSegmentSrv.newSegment({ value: metric.text });
} else {
this.metricSegment = this.uiSegmentSrv.newSegment({ value: this.target.metric });
}
}

if (this.target.device === undefined) {
this.deviceSegment = this.uiSegmentSrv.newSegment({ value: 'select device', fake: true });
} else {
this.deviceSegment = this.uiSegmentSrv.newSegment({ value: this.target.device });
}

if (this.target.unit === undefined) {
this.unitSegment = this.uiSegmentSrv.newSegment({ value: 'select unit', fake: true });
} else {
const unit = this.datasource.findUnit({ value: this.target.unit });
if (unit !== null) {
this.unitSegment = this.uiSegmentSrv.newSegment({ value: unit.text });
} else {
this.unitSegment = this.uiSegmentSrv.newSegment({ value: this.target.unit });
}
}
}

async getMetricSegments(query: string, variableName?: string, addTemplateVars = false): Promise<MetricSegment[]> {
let metrics = await this.datasource.metricFindQuery(query);
if (this.templateSrv.variableExists(variableName)) {
metrics = [{ text: variableName }, ...metrics];
}

return this.uiSegmentSrv.transformToSegments(addTemplateVars)(metrics);
}

async getMetrics(): Promise<MetricSegment[]> {
return this.getMetricSegments('metrics()', '$metric');
}

async getDevices(): Promise<MetricSegment[]> {
return this.getMetricSegments('devices()', '$device');
}

async getUnits(): Promise<MetricSegment[]> {
return this.getMetricSegments('units()', '$unit');
}

async onMetricChange(): Promise<void> {
const metric = await this.datasource.findMetric({ text: this.metricSegment.value });
if (metric !== null) {
this.target.metric = metric.value;
} else {
this.target.metric = this.metricSegment.value;
}

this.panelCtrl.refresh();
}

async onDeviceChange(): Promise<void> {
this.target.device = this.deviceSegment.value;

this.panelCtrl.refresh();
}

async onUnitChange(): Promise<void> {
const unit = await this.datasource.findUnit({ text: this.unitSegment.value });
if (unit !== null) {
this.target.unit = unit.value;
} else {
this.target.unit = this.unitSegment.value;
}

this.panelCtrl.refresh();
}
}

Expand Down
Loading

0 comments on commit f3eaf1f

Please sign in to comment.