rozetko
7 years ago
46 changed files with 20539 additions and 0 deletions
@ -0,0 +1,38 @@
|
||||
node_modules |
||||
dist |
||||
npm-debug.log |
||||
coverage/ |
||||
.aws-config.json |
||||
awsconfig |
||||
/emails/dist |
||||
/public_gen |
||||
/tmp |
||||
vendor/phantomjs/phantomjs |
||||
|
||||
docs/AWS_S3_BUCKET |
||||
docs/GIT_BRANCH |
||||
docs/VERSION |
||||
docs/GITCOMMIT |
||||
docs/changed-files |
||||
docs/changed-files |
||||
|
||||
# locally required config files |
||||
public/css/*.min.css |
||||
|
||||
# Editor junk |
||||
*.sublime-workspace |
||||
*.swp |
||||
.idea/ |
||||
*.iml |
||||
|
||||
/data/* |
||||
/bin/* |
||||
|
||||
conf/custom.ini |
||||
fig.yml |
||||
profile.cov |
||||
.notouch |
||||
.DS_Store |
||||
.tscache |
||||
|
||||
.vscode/ |
@ -0,0 +1,26 @@
|
||||
# Hastic Graph Panel |
||||
|
||||
A better version of default Grafana's Graph Panel |
||||
|
||||
Can render Anomalies & more. |
||||
|
||||
# Build |
||||
|
||||
``` |
||||
npm install |
||||
npm run build |
||||
``` |
||||
|
||||
# Changelog |
||||
|
||||
[Improvements] |
||||
|
||||
* You can zoom during update |
||||
|
||||
|
||||
# Credits |
||||
|
||||
Based on |
||||
|
||||
* [grafana-plugin-template-webpack-typescript](https://github.com/CorpGlory/grafana-plugin-template-webpack-typescript) |
||||
* [@types/grafana](https://github.com/CorpGlory/types-grafana) |
@ -0,0 +1,51 @@
|
||||
const path = require('path'); |
||||
const webpack = require('webpack'); |
||||
const CopyWebpackPlugin = require('copy-webpack-plugin'); |
||||
|
||||
function resolve(dir) { |
||||
return path.join(__dirname, '..', dir) |
||||
} |
||||
|
||||
module.exports = { |
||||
target: 'node', |
||||
context: resolve('src'), |
||||
entry: './module.ts', |
||||
output: { |
||||
filename: "module.js", |
||||
path: resolve('dist'), |
||||
libraryTarget: "amd" |
||||
}, |
||||
externals: [ |
||||
// remove the line below if you don't want to use buildin versions
|
||||
'jquery', 'lodash', 'moment', 'angular', |
||||
function(context, request, callback) { |
||||
var prefix = 'grafana/'; |
||||
if (request.indexOf(prefix) === 0) { |
||||
return callback(null, request.substr(prefix.length)); |
||||
} |
||||
callback(); |
||||
} |
||||
], |
||||
plugins: [ |
||||
new webpack.optimize.OccurrenceOrderPlugin(), |
||||
new CopyWebpackPlugin([ |
||||
{ from: 'plugin.json' }, |
||||
{ from: 'img/*' }, |
||||
{ from: 'partials/*' } |
||||
]) |
||||
], |
||||
resolve: { |
||||
extensions: [".ts", ".js"], |
||||
}, |
||||
module: { |
||||
rules: [ |
||||
{ |
||||
test: /\.tsx?$/,
|
||||
loaders: [ |
||||
"ts-loader" |
||||
], |
||||
exclude: /node_modules/, |
||||
} |
||||
] |
||||
} |
||||
} |
@ -0,0 +1,9 @@
|
||||
const baseWebpackConfig = require('./webpack.base.conf'); |
||||
|
||||
var conf = baseWebpackConfig; |
||||
conf.devtool = "source-map"; |
||||
conf.watch = true; |
||||
conf.mode = 'development'; |
||||
|
||||
module.exports = conf; |
||||
|
@ -0,0 +1,6 @@
|
||||
const baseWebpackConfig = require('./webpack.base.conf'); |
||||
|
||||
var conf = baseWebpackConfig; |
||||
conf.mode = 'development'; // cuz production wont work
|
||||
|
||||
module.exports = baseWebpackConfig; |
@ -0,0 +1,158 @@
|
||||
|
||||
{ |
||||
"alert": { |
||||
"conditions": [ |
||||
{ |
||||
"evaluator": { |
||||
"params": [ |
||||
0.5 |
||||
], |
||||
"type": "gt" |
||||
}, |
||||
"operator": { |
||||
"type": "and" |
||||
}, |
||||
"query": { |
||||
"params": [ |
||||
"A", |
||||
"5m", |
||||
"now" |
||||
] |
||||
}, |
||||
"reducer": { |
||||
"params": [], |
||||
"type": "avg" |
||||
}, |
||||
"type": "query" |
||||
} |
||||
], |
||||
"executionErrorState": "alerting", |
||||
"frequency": "60s", |
||||
"handler": 1, |
||||
"name": "OkAlert", |
||||
"noDataState": "no_data", |
||||
"notifications": [] |
||||
}, |
||||
"aliasColors": {}, |
||||
"analyticsType": "Anomaly detection", |
||||
"anomalyType": "", |
||||
"anomalyTypes": [ |
||||
{ |
||||
"algorithm": "unsupervised", |
||||
"color": "rgba(214, 131, 206, 0.95)", |
||||
"name": "cpu_utilization_unsupervised" |
||||
}, |
||||
{ |
||||
"algorithm": "unsupervised", |
||||
"color": "red", |
||||
"name": "AnomalyNamefff" |
||||
}, |
||||
{ |
||||
"algorithm": "unsupervised", |
||||
"color": "red", |
||||
"name": "AnomalyName" |
||||
}, |
||||
{ |
||||
"algorithm": "unsupervised", |
||||
"color": "red", |
||||
"name": "anomaly_nameret" |
||||
} |
||||
], |
||||
"backendURL": "http://corpglory.com:8000", |
||||
"bars": false, |
||||
"dashLength": 10, |
||||
"dashes": false, |
||||
"datasource": "accelerometer", |
||||
"fill": 1, |
||||
"gridPos": { |
||||
"h": 13, |
||||
"w": 24, |
||||
"x": 0, |
||||
"y": 0 |
||||
}, |
||||
"id": 2, |
||||
"legend": { |
||||
"avg": false, |
||||
"current": false, |
||||
"max": false, |
||||
"min": false, |
||||
"show": true, |
||||
"total": false, |
||||
"values": false |
||||
}, |
||||
"lines": true, |
||||
"linewidth": 1, |
||||
"nullPointMode": "null", |
||||
"percentage": false, |
||||
"pointradius": 5, |
||||
"points": false, |
||||
"renderer": "flot", |
||||
"seriesOverrides": [], |
||||
"spaceLength": 10, |
||||
"stack": false, |
||||
"steppedLine": false, |
||||
"targets": [ |
||||
{ |
||||
"groupBy": [], |
||||
"measurement": "ec2_cpu_utilization", |
||||
"orderByTime": "ASC", |
||||
"policy": "default", |
||||
"refId": "A", |
||||
"resultFormat": "time_series", |
||||
"select": [ |
||||
[ |
||||
{ |
||||
"params": [ |
||||
"value" |
||||
], |
||||
"type": "field" |
||||
} |
||||
] |
||||
], |
||||
"tags": [] |
||||
} |
||||
], |
||||
"thresholds": [ |
||||
{ |
||||
"colorMode": "critical", |
||||
"fill": true, |
||||
"line": true, |
||||
"op": "gt", |
||||
"value": 0.5 |
||||
} |
||||
], |
||||
"timeFrom": null, |
||||
"timeShift": null, |
||||
"title": "Panel Title", |
||||
"tooltip": { |
||||
"shared": true, |
||||
"sort": 0, |
||||
"value_type": "individual" |
||||
}, |
||||
"type": "corpglory-grafalys-graph-panel", |
||||
"xaxis": { |
||||
"buckets": null, |
||||
"mode": "time", |
||||
"name": null, |
||||
"show": true, |
||||
"values": [] |
||||
}, |
||||
"yaxes": [ |
||||
{ |
||||
"format": "short", |
||||
"label": null, |
||||
"logBase": 1, |
||||
"max": null, |
||||
"min": null, |
||||
"show": true |
||||
}, |
||||
{ |
||||
"format": "short", |
||||
"label": null, |
||||
"logBase": 1, |
||||
"max": null, |
||||
"min": null, |
||||
"show": true |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,40 @@
|
||||
{ |
||||
"name": "hastic-graph-panel", |
||||
"version": "0.1.0", |
||||
"description": "Grafalys default panel for rendering and interaction with analytic unit", |
||||
"main": "dist/module", |
||||
"scripts": { |
||||
"build": "webpack --config build/webpack.prod.conf.js", |
||||
"dev": "webpack --config build/webpack.dev.conf.js" |
||||
}, |
||||
"keywords": [], |
||||
"author": "Alexey Velikiy", |
||||
"license": "MIT", |
||||
"repository": "https://github.com/CorpGlory/hastic/hastic-grafana-graph-panel", |
||||
"devDependencies": { |
||||
"@types/angular": "^1.6.43", |
||||
"@types/flot": "0.0.31", |
||||
"@types/grafana": "github:CorpGlory/types-grafana", |
||||
"@types/jquery": "^3.3.0", |
||||
"@types/lodash": "^4.14.104", |
||||
"@types/moment": "^2.13.0", |
||||
"@types/perfect-scrollbar": "^1.3.0", |
||||
"@types/tinycolor2": "^1.4.0", |
||||
"@types/md5": "^2.1.32", |
||||
"babel-core": "^6.26.0", |
||||
"babel-loader": "^7.1.2", |
||||
"babel-preset-env": "^1.6.0", |
||||
"copy-webpack-plugin": "^4.0.1", |
||||
"loader-utils": "^1.1.0", |
||||
"ts-loader": "^4.2.0", |
||||
"typescript": "^2.8.3", |
||||
"webpack": "^4.7.0", |
||||
"webpack-cli": "^2.1.2", |
||||
"md5": "^2.2.1" |
||||
}, |
||||
"dependencies": { |
||||
"perfect-scrollbar": "^1.3.0", |
||||
"tether-drop": "^1.4.2", |
||||
"tinycolor2": "^1.4.1" |
||||
} |
||||
} |
@ -0,0 +1,88 @@
|
||||
import kbn from 'grafana/app/core/utils/kbn'; |
||||
|
||||
|
||||
export class AxesEditorCtrl { |
||||
panel: any; |
||||
panelCtrl: any; |
||||
unitFormats: any; |
||||
logScales: any; |
||||
xAxisModes: any; |
||||
xAxisStatOptions: any; |
||||
xNameSegment: any; |
||||
|
||||
/** @ngInject **/ |
||||
constructor(private $scope, private $q) { |
||||
this.panelCtrl = $scope.ctrl; |
||||
this.panel = this.panelCtrl.panel; |
||||
this.$scope.ctrl = this; |
||||
|
||||
this.unitFormats = kbn.getUnitFormats(); |
||||
|
||||
this.logScales = { |
||||
linear: 1, |
||||
'log (base 2)': 2, |
||||
'log (base 10)': 10, |
||||
'log (base 32)': 32, |
||||
'log (base 1024)': 1024, |
||||
}; |
||||
|
||||
this.xAxisModes = { |
||||
Time: 'time', |
||||
Series: 'series', |
||||
Histogram: 'histogram', |
||||
// 'Data field': 'field',
|
||||
}; |
||||
|
||||
this.xAxisStatOptions = [ |
||||
{ text: 'Avg', value: 'avg' }, |
||||
{ text: 'Min', value: 'min' }, |
||||
{ text: 'Max', value: 'max' }, |
||||
{ text: 'Total', value: 'total' }, |
||||
{ text: 'Count', value: 'count' }, |
||||
{ text: 'Current', value: 'current' }, |
||||
]; |
||||
|
||||
if (this.panel.xaxis.mode === 'custom') { |
||||
if (!this.panel.xaxis.name) { |
||||
this.panel.xaxis.name = 'specify field'; |
||||
} |
||||
} |
||||
} |
||||
|
||||
setUnitFormat(axis, subItem) { |
||||
axis.format = subItem.value; |
||||
this.panelCtrl.render(); |
||||
} |
||||
|
||||
render() { |
||||
this.panelCtrl.render(); |
||||
} |
||||
|
||||
xAxisModeChanged() { |
||||
this.panelCtrl.processor.setPanelDefaultsForNewXAxisMode(); |
||||
this.panelCtrl.onDataReceived(this.panelCtrl.dataList); |
||||
} |
||||
|
||||
xAxisValueChanged() { |
||||
this.panelCtrl.onDataReceived(this.panelCtrl.dataList); |
||||
} |
||||
|
||||
getDataFieldNames(onlyNumbers) { |
||||
var props = this.panelCtrl.processor.getDataFieldNames(this.panelCtrl.dataList, onlyNumbers); |
||||
var items = props.map(prop => { |
||||
return { text: prop, value: prop }; |
||||
}); |
||||
return this.$q.when(items); |
||||
} |
||||
} |
||||
|
||||
/** @ngInject **/ |
||||
export function axesEditorComponent() { |
||||
'use strict'; |
||||
return { |
||||
restrict: 'E', |
||||
scope: true, |
||||
templateUrl: 'public/plugins/corpglory-grafalys-graph-panel/partials/axes_editor.html', |
||||
controller: AxesEditorCtrl, |
||||
}; |
||||
} |
@ -0,0 +1,81 @@
|
||||
import _ from 'lodash'; |
||||
import tinycolor from 'tinycolor2'; |
||||
|
||||
export const PALETTE_ROWS = 4; |
||||
export const PALETTE_COLUMNS = 14; |
||||
export const DEFAULT_ANNOTATION_COLOR = 'rgba(0, 211, 255, 1)'; |
||||
export const OK_COLOR = 'rgba(11, 237, 50, 1)'; |
||||
export const ALERTING_COLOR = 'rgba(237, 46, 24, 1)'; |
||||
export const NO_DATA_COLOR = 'rgba(150, 150, 150, 1)'; |
||||
export const REGION_FILL_ALPHA = 0.09; |
||||
|
||||
let colors = [ |
||||
'#7EB26D', |
||||
'#EAB839', |
||||
'#6ED0E0', |
||||
'#EF843C', |
||||
'#E24D42', |
||||
'#1F78C1', |
||||
'#BA43A9', |
||||
'#705DA0', |
||||
'#508642', |
||||
'#CCA300', |
||||
'#447EBC', |
||||
'#C15C17', |
||||
'#890F02', |
||||
'#0A437C', |
||||
'#6D1F62', |
||||
'#584477', |
||||
'#B7DBAB', |
||||
'#F4D598', |
||||
'#70DBED', |
||||
'#F9BA8F', |
||||
'#F29191', |
||||
'#82B5D8', |
||||
'#E5A8E2', |
||||
'#AEA2E0', |
||||
'#629E51', |
||||
'#E5AC0E', |
||||
'#64B0C8', |
||||
'#E0752D', |
||||
'#BF1B00', |
||||
'#0A50A1', |
||||
'#962D82', |
||||
'#614D93', |
||||
'#9AC48A', |
||||
'#F2C96D', |
||||
'#65C5DB', |
||||
'#F9934E', |
||||
'#EA6460', |
||||
'#5195CE', |
||||
'#D683CE', |
||||
'#806EB7', |
||||
'#3F6833', |
||||
'#967302', |
||||
'#2F575E', |
||||
'#99440A', |
||||
'#58140C', |
||||
'#052B51', |
||||
'#511749', |
||||
'#3F2B5B', |
||||
'#E0F9D7', |
||||
'#FCEACA', |
||||
'#CFFAFF', |
||||
'#F9E2D2', |
||||
'#FCE2DE', |
||||
'#BADFF4', |
||||
'#F9D9F9', |
||||
'#DEDAF7', |
||||
]; |
||||
|
||||
|
||||
export function hexToHsl(color) { |
||||
return tinycolor(color).toHsl(); |
||||
} |
||||
|
||||
export function hslToHex(color) { |
||||
return tinycolor(color).toHexString(); |
||||
} |
||||
|
||||
|
||||
export default colors; |
@ -0,0 +1,348 @@
|
||||
import { AnomalyService } from '../services/anomaly_service' |
||||
|
||||
import { |
||||
AnomalyKey, AnomalyType, |
||||
AnomalyTypesSet, AnomalySegment, AnomalySegmentsSearcher, AnomalySermentPair |
||||
} from '../model/anomaly'; |
||||
import { MetricExpanded } from '../model/metric' |
||||
import { Segment, SegmentKey } from '../model/segment'; |
||||
import { SegmentsSet } from '../model/segment_set'; |
||||
import { SegmentArray } from '../model/segment_array'; |
||||
import { Emitter } from 'grafana/app/core/utils/emitter' |
||||
|
||||
import _ from 'lodash'; |
||||
|
||||
export const REGION_FILL_ALPHA = 0.7; |
||||
export const REGION_STROKE_ALPHA = 0.9; |
||||
export const REGION_DELETE_COLOR_LIGHT = '#d1d1d1'; |
||||
export const REGION_DELETE_COLOR_DARK = 'white'; |
||||
|
||||
|
||||
export class AnomalyController { |
||||
|
||||
private _anomalyTypesSet: AnomalyTypesSet; |
||||
private _selectedAnomalyKey: AnomalyKey = null; |
||||
|
||||
private _labelingDataAddedSegments: SegmentsSet<AnomalySegment>; |
||||
private _labelingDataDeletedSegments: SegmentsSet<AnomalySegment>; |
||||
private _newAnomalyType: AnomalyType = null; |
||||
private _creatingNewAnomalyType: boolean = false; |
||||
private _savingNewAnomalyType: boolean = false; |
||||
private _tempIdCounted = -1; |
||||
private _graphLocked = false; |
||||
|
||||
private _statusRunners: Set<AnomalyKey> = new Set<AnomalyKey>(); |
||||
|
||||
|
||||
constructor(private _panelObject: any, private _anomalyService: AnomalyService, private _emitter: Emitter) { |
||||
if(_panelObject.anomalyTypes === undefined) { |
||||
_panelObject.anomalyTypes = []; |
||||
} |
||||
this._labelingDataAddedSegments = new SegmentArray<AnomalySegment>(); |
||||
this._labelingDataDeletedSegments = new SegmentArray<AnomalySegment>(); |
||||
this._anomalyTypesSet = new AnomalyTypesSet(this._panelObject.anomalyTypes); |
||||
this.anomalyTypes.forEach(a => this.runAnomalyTypeAlertEnabledWaiter(a)); |
||||
} |
||||
|
||||
getAnomalySegmentsSearcher(): AnomalySegmentsSearcher { |
||||
return this._anomalySegmentsSearcher.bind(this); |
||||
} |
||||
|
||||
private _anomalySegmentsSearcher(point: number): AnomalySermentPair[] { |
||||
var result: AnomalySermentPair[] = []; |
||||
this._anomalyTypesSet.anomalyTypes.forEach(at => { |
||||
var segs = at.segments.findSegments(point); |
||||
segs.forEach(s => { |
||||
result.push({ anomalyType: at, segment: s }); |
||||
}) |
||||
}) |
||||
return result; |
||||
} |
||||
|
||||
createAnomalyType() { |
||||
this._newAnomalyType = new AnomalyType(); |
||||
this._creatingNewAnomalyType = true; |
||||
this._savingNewAnomalyType = false; |
||||
} |
||||
|
||||
async saveNewAnomalyType(metricExpanded: MetricExpanded, panelId: number) { |
||||
this._savingNewAnomalyType = true; |
||||
await this._anomalyService.postNewAnomalyType(metricExpanded, this._newAnomalyType, panelId); |
||||
this._anomalyTypesSet.addAnomalyType(this._newAnomalyType); |
||||
this._creatingNewAnomalyType = false; |
||||
this._savingNewAnomalyType = false; |
||||
this.runAnomalyTypeAlertEnabledWaiter(this._newAnomalyType); |
||||
} |
||||
|
||||
get creatingAnomalyType() { return this._creatingNewAnomalyType; } |
||||
get savingAnomalyType() { return this._savingNewAnomalyType; } |
||||
get newAnomalyType(): AnomalyType { return this._newAnomalyType; } |
||||
|
||||
get graphLocked() { return this._graphLocked; } |
||||
set graphLocked(value) { |
||||
this._graphLocked = value; |
||||
} |
||||
|
||||
get labelingAnomaly(): AnomalyType { |
||||
if(this._selectedAnomalyKey === null) { |
||||
return null; |
||||
} |
||||
return this._anomalyTypesSet.byKey(this._selectedAnomalyKey); |
||||
} |
||||
|
||||
async toggleAnomalyTypeLabelingMode(key: AnomalyKey) { |
||||
if(this.labelingAnomaly && this.labelingAnomaly.saving) { |
||||
throw new Error('Can`t toggel during saving'); |
||||
} |
||||
if(this._selectedAnomalyKey === key) { |
||||
return this.disableAnomalyLabeling(); |
||||
} |
||||
await this.disableAnomalyLabeling(); |
||||
this._selectedAnomalyKey = key; |
||||
this.labelingAnomaly.selected = true; |
||||
this.toggleAnomalyVisibility(key, true); |
||||
} |
||||
|
||||
async disableAnomalyLabeling() { |
||||
if(this._selectedAnomalyKey === null) { |
||||
return; |
||||
} |
||||
this.labelingAnomaly.saving = true; |
||||
var newIds = await this._saveLabelingData(); |
||||
this._labelingDataAddedSegments.getSegments().forEach((s, i) => { |
||||
this.labelingAnomaly.segments.updateKey(s.key, newIds[i]); |
||||
}) |
||||
this.labelingAnomaly.saving = false; |
||||
|
||||
var anomaly = this.labelingAnomaly; |
||||
this.dropLabeling(); |
||||
this._runAnomalyTypeStatusWaiter(anomaly); |
||||
} |
||||
|
||||
undoLabeling() { |
||||
this._labelingDataAddedSegments.getSegments().forEach(s => { |
||||
this.labelingAnomaly.segments.remove(s.key); |
||||
}); |
||||
this._labelingDataDeletedSegments.getSegments().forEach(s => { |
||||
this.labelingAnomaly.segments.addSegment(s); |
||||
}); |
||||
this.dropLabeling(); |
||||
} |
||||
|
||||
dropLabeling() { |
||||
this._labelingDataAddedSegments.clear(); |
||||
this._labelingDataDeletedSegments.clear(); |
||||
this.labelingAnomaly.selected = false; |
||||
this._selectedAnomalyKey = null; |
||||
this._tempIdCounted = -1; |
||||
} |
||||
|
||||
get labelingMode(): boolean { |
||||
return this._selectedAnomalyKey !== null; |
||||
} |
||||
|
||||
get labelingDeleteMode(): boolean { |
||||
if(!this.labelingMode) { |
||||
return false; |
||||
} |
||||
return this.labelingAnomaly.deleteMode; |
||||
} |
||||
|
||||
addLabelSegment(segment: Segment) { |
||||
var asegment = this.labelingAnomaly.addLabeledSegment(segment); |
||||
this._labelingDataAddedSegments.addSegment(asegment); |
||||
} |
||||
|
||||
get anomalyTypes(): AnomalyType[] { |
||||
return this._anomalyTypesSet.anomalyTypes; |
||||
} |
||||
|
||||
onAnomalyColorChange(key: AnomalyKey, value) { |
||||
this._anomalyTypesSet.byKey(key).color = value; |
||||
} |
||||
|
||||
fetchAnomalyTypesStatuses() { |
||||
this.anomalyTypes.forEach(a => this._runAnomalyTypeStatusWaiter(a)); |
||||
} |
||||
|
||||
async fetchAnomalyTypesSegments(from: number, to: number) { |
||||
if(!_.isNumber(from)) { |
||||
throw new Error('from isn`t number'); |
||||
} |
||||
if(!_.isNumber(+to)) { |
||||
throw new Error('to isn`t number'); |
||||
} |
||||
var tasks = this.anomalyTypes.map(a => this.fetchSegments(a, from, to)); |
||||
return Promise.all(tasks); |
||||
} |
||||
|
||||
async fetchSegments(anomalyType: AnomalyType, from: number, to: number): Promise<void> { |
||||
if(!_.isNumber(from)) { |
||||
throw new Error('from isn`t number'); |
||||
} |
||||
if(!_.isNumber(+to)) { |
||||
throw new Error('to isn`t number'); |
||||
} |
||||
var allSegmentsList = await this._anomalyService.getSegments(anomalyType.key, from, to); |
||||
var allSegmentsSet = new SegmentArray(allSegmentsList); |
||||
if(anomalyType.selected) { |
||||
this._labelingDataAddedSegments.getSegments().forEach(s => allSegmentsSet.addSegment(s)); |
||||
this._labelingDataDeletedSegments.getSegments().forEach(s => allSegmentsSet.remove(s.key)); |
||||
} |
||||
anomalyType.segments = allSegmentsSet; |
||||
} |
||||
|
||||
private async _saveLabelingData(): Promise<SegmentKey[]> { |
||||
var anomaly = this.labelingAnomaly; |
||||
if(anomaly === null) { |
||||
throw new Error('anomaly is not selected'); |
||||
} |
||||
|
||||
if( |
||||
this._labelingDataAddedSegments.length === 0 && |
||||
this._labelingDataDeletedSegments.length === 0 |
||||
) { |
||||
return []; |
||||
} |
||||
|
||||
return this._anomalyService.updateSegments( |
||||
anomaly.key, this._labelingDataAddedSegments, this._labelingDataDeletedSegments |
||||
); |
||||
} |
||||
|
||||
// TODO: move to renderer
|
||||
updateFlotEvents(isEditMode, options) { |
||||
if(options.grid.markings === undefined) { |
||||
options.markings = []; |
||||
} |
||||
|
||||
for(var i = 0; i < this.anomalyTypes.length; i++) { |
||||
var anomalyType = this.anomalyTypes[i]; |
||||
var borderColor = addAlphaToRGB(anomalyType.color, REGION_STROKE_ALPHA); |
||||
var fillColor = addAlphaToRGB(anomalyType.color, REGION_FILL_ALPHA); |
||||
var segments = anomalyType.segments.getSegments(); |
||||
if(!anomalyType.visible) { |
||||
continue; |
||||
} |
||||
if(isEditMode && this.labelingMode) { |
||||
if(anomalyType.selected) { |
||||
borderColor = addAlphaToRGB(borderColor, 0.7); |
||||
fillColor = addAlphaToRGB(borderColor, 0.7); |
||||
} else { |
||||
continue; |
||||
} |
||||
} |
||||
|
||||
var rangeDist = +options.xaxis.max - +options.xaxis.min; |
||||
segments.forEach(s => { |
||||
var expanded = s.expandDist(rangeDist, 0.01); |
||||
options.grid.markings.push({ |
||||
xaxis: { from: expanded.from, to: expanded.to }, |
||||
color: fillColor |
||||
}); |
||||
options.grid.markings.push({ |
||||
xaxis: { from: expanded.from, to: expanded.from }, |
||||
color: borderColor |
||||
}); |
||||
options.grid.markings.push({ |
||||
xaxis: { from: expanded.to, to: expanded.to }, |
||||
color: borderColor |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
} |
||||
|
||||
deleteLabelingAnomalySegmentsInRange(from: number, to: number) { |
||||
var allRemovedSegs = this.labelingAnomaly.removeSegmentsInRange(from, to); |
||||
allRemovedSegs.forEach(s => { |
||||
if(!this._labelingDataAddedSegments.has(s.key)) { |
||||
this._labelingDataDeletedSegments.addSegment(s); |
||||
} |
||||
}); |
||||
this._labelingDataAddedSegments.removeInRange(from, to); |
||||
} |
||||
|
||||
toggleDeleteMode() { |
||||
if(!this.labelingMode) { |
||||
throw new Error('Cant enter delete mode is labeling mode disabled'); |
||||
} |
||||
this.labelingAnomaly.deleteMode = !this.labelingAnomaly.deleteMode; |
||||
} |
||||
|
||||
removeAnomalyType(key) { |
||||
if(key === this._selectedAnomalyKey) { |
||||
this.dropLabeling(); |
||||
} |
||||
this._anomalyTypesSet.removeAnomalyType(key); |
||||
} |
||||
|
||||
private async _runAnomalyTypeStatusWaiter(anomalyType: AnomalyType) { |
||||
if(anomalyType === undefined || anomalyType === null) { |
||||
throw new Error('anomalyType not defined'); |
||||
} |
||||
|
||||
if(this._statusRunners.has(anomalyType.key)) { |
||||
return; |
||||
} |
||||
|
||||
this._statusRunners.add(anomalyType.key); |
||||
|
||||
var statusGenerator = this._anomalyService.getAnomalyTypeStatusGenerator( |
||||
anomalyType.key, 1000 |
||||
); |
||||
|
||||
for await (const status of statusGenerator) { |
||||
if(anomalyType.status !== status) { |
||||
anomalyType.status = status; |
||||
this._emitter.emit('anomaly-type-status-change', anomalyType); |
||||
} |
||||
if(!anomalyType.isActiveStatus) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
this._statusRunners.delete(anomalyType.key); |
||||
} |
||||
|
||||
async runAnomalyTypeAlertEnabledWaiter(anomalyType: AnomalyType) { |
||||
var enabled = await this._anomalyService.getAlertEnabled(anomalyType.key); |
||||
if(anomalyType.alertEnabled !== enabled) { |
||||
anomalyType.alertEnabled = enabled; |
||||
this._emitter.emit('anomaly-type-alert-change', anomalyType); |
||||
} |
||||
} |
||||
|
||||
async toggleAnomalyTypeAlertEnabled(anomalyType: AnomalyType) { |
||||
var enabled = anomalyType.alertEnabled; |
||||
anomalyType.alertEnabled = undefined; |
||||
await this._anomalyService.setAlertEnabled(anomalyType.key, enabled); |
||||
anomalyType.alertEnabled = enabled; |
||||
this._emitter.emit('anomaly-type-alert-change', anomalyType); |
||||
} |
||||
|
||||
public getIdForNewLabelSegment() { |
||||
this._tempIdCounted--; |
||||
return this._tempIdCounted; |
||||
} |
||||
|
||||
public toggleAnomalyVisibility(key: AnomalyKey, value?: boolean) { |
||||
var anomaly = this._anomalyTypesSet.byKey(key); |
||||
if(value !== undefined) { |
||||
anomaly.visible = value; |
||||
} else { |
||||
anomaly.visible = !anomaly.visible; |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
function addAlphaToRGB(colorString: string, alpha: number): string { |
||||
let color = tinycolor(colorString); |
||||
if (color.isValid()) { |
||||
color.setAlpha(color.getAlpha() * alpha); |
||||
return color.toRgbString(); |
||||
} else { |
||||
return colorString; |
||||
} |
||||
} |
@ -0,0 +1,214 @@
|
||||
import _ from 'lodash'; |
||||
import TimeSeries from 'grafana/app/core/time_series2'; |
||||
import colors from './colors'; |
||||
|
||||
export class DataProcessor { |
||||
constructor(private panel) {} |
||||
|
||||
getSeriesList(options) { |
||||
if (!options.dataList || options.dataList.length === 0) { |
||||
return []; |
||||
} |
||||
|
||||
// auto detect xaxis mode
|
||||
var firstItem; |
||||
if (options.dataList && options.dataList.length > 0) { |
||||
firstItem = options.dataList[0]; |
||||
let autoDetectMode = this.getAutoDetectXAxisMode(firstItem); |
||||
if (this.panel.xaxis.mode !== autoDetectMode) { |
||||
this.panel.xaxis.mode = autoDetectMode; |
||||
this.setPanelDefaultsForNewXAxisMode(); |
||||
} |
||||
} |
||||
|
||||
switch (this.panel.xaxis.mode) { |
||||
case 'series': |
||||
case 'time': { |
||||
return options.dataList.map((item, index) => { |
||||
return this.timeSeriesHandler(item, index, options); |
||||
}); |
||||
} |
||||
case 'histogram': { |
||||
let histogramDataList = [ |
||||
{ |
||||
target: 'count', |
||||
datapoints: _.concat([], _.flatten(_.map(options.dataList, 'datapoints'))), |
||||
}, |
||||
]; |
||||
return histogramDataList.map((item, index) => { |
||||
return this.timeSeriesHandler(item, index, options); |
||||
}); |
||||
} |
||||
case 'field': { |
||||
return this.customHandler(firstItem); |
||||
} |
||||
} |
||||
} |
||||
|
||||
getAutoDetectXAxisMode(firstItem) { |
||||
switch (firstItem.type) { |
||||
case 'docs': |
||||
return 'field'; |
||||
case 'table': |
||||
return 'field'; |
||||
default: { |
||||
if (this.panel.xaxis.mode === 'series') { |
||||
return 'series'; |
||||
} |
||||
if (this.panel.xaxis.mode === 'histogram') { |
||||
return 'histogram'; |
||||
} |
||||
return 'time'; |
||||
} |
||||
} |
||||
} |
||||
|
||||
setPanelDefaultsForNewXAxisMode() { |
||||
switch (this.panel.xaxis.mode) { |
||||
case 'time': { |
||||
this.panel.bars = false; |
||||
this.panel.lines = true; |
||||
this.panel.points = false; |
||||
this.panel.legend.show = true; |
||||
this.panel.tooltip.shared = true; |
||||
this.panel.xaxis.values = []; |
||||
break; |
||||
} |
||||
case 'series': { |
||||
this.panel.bars = true; |
||||
this.panel.lines = false; |
||||
this.panel.points = false; |
||||
this.panel.stack = false; |
||||
this.panel.legend.show = false; |
||||
this.panel.tooltip.shared = false; |
||||
this.panel.xaxis.values = ['total']; |
||||
break; |
||||
} |
||||
case 'histogram': { |
||||
this.panel.bars = true; |
||||
this.panel.lines = false; |
||||
this.panel.points = false; |
||||
this.panel.stack = false; |
||||
this.panel.legend.show = false; |
||||
this.panel.tooltip.shared = false; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
timeSeriesHandler(seriesData, index, options) { |
||||
var datapoints = seriesData.datapoints || []; |
||||
var alias = seriesData.target; |
||||
|
||||
var colorIndex = index % colors.length; |
||||
var color = seriesData.color || this.panel.aliasColors[alias] || colors[colorIndex]; |
||||
|
||||
var series = new TimeSeries({ |
||||
datapoints: datapoints, |
||||
alias: alias, |
||||
color: color, |
||||
unit: seriesData.unit, |
||||
}); |
||||
|
||||
if (datapoints && datapoints.length > 0) { |
||||
var last = datapoints[datapoints.length - 1][1]; |
||||
var from = options.range.from; |
||||
if (last - from < -10000) { |
||||
series.isOutsideRange = true; |
||||
} |
||||
} |
||||
|
||||
return series; |
||||
} |
||||
|
||||
customHandler(dataItem) { |
||||
let nameField = this.panel.xaxis.name; |
||||
if (!nameField) { |
||||
throw { |
||||
message: 'No field name specified to use for x-axis, check your axes settings', |
||||
}; |
||||
} |
||||
return []; |
||||
} |
||||
|
||||
validateXAxisSeriesValue() { |
||||
switch (this.panel.xaxis.mode) { |
||||
case 'series': { |
||||
if (this.panel.xaxis.values.length === 0) { |
||||
this.panel.xaxis.values = ['total']; |
||||
return; |
||||
} |
||||
|
||||
var validOptions = this.getXAxisValueOptions({}); |
||||
var found = _.find(validOptions, { value: this.panel.xaxis.values[0] }); |
||||
if (!found) { |
||||
this.panel.xaxis.values = ['total']; |
||||
} |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
|
||||
getDataFieldNames(dataList, onlyNumbers) { |
||||
if (dataList.length === 0) { |
||||
return []; |
||||
} |
||||
|
||||
let fields = []; |
||||
var firstItem = dataList[0]; |
||||
let fieldParts = []; |
||||
|
||||
function getPropertiesRecursive(obj) { |
||||
_.forEach(obj, (value, key) => { |
||||
if (_.isObject(value)) { |
||||
fieldParts.push(key); |
||||
getPropertiesRecursive(value); |
||||
} else { |
||||
if (!onlyNumbers || _.isNumber(value)) { |
||||
let field = fieldParts.concat(key).join('.'); |
||||
fields.push(field); |
||||
} |
||||
} |
||||
}); |
||||
fieldParts.pop(); |
||||
} |
||||
|
||||
if (firstItem.type === 'docs') { |
||||
if (firstItem.datapoints.length === 0) { |
||||
return []; |
||||
} |
||||
getPropertiesRecursive(firstItem.datapoints[0]); |
||||
} |
||||
|
||||
return fields; |
||||
} |
||||
|
||||
getXAxisValueOptions(options) { |
||||
switch (this.panel.xaxis.mode) { |
||||
case 'series': { |
||||
return [ |
||||
{ text: 'Avg', value: 'avg' }, |
||||
{ text: 'Min', value: 'min' }, |
||||
{ text: 'Max', value: 'max' }, |
||||
{ text: 'Total', value: 'total' }, |
||||
{ text: 'Count', value: 'count' }, |
||||
]; |
||||
} |
||||
} |
||||
|
||||
return []; |
||||
} |
||||
|
||||
pluckDeep(obj: any, property: string) { |
||||
let propertyParts = property.split('.'); |
||||
let value = obj; |
||||
for (let i = 0; i < propertyParts.length; ++i) { |
||||
if (value[propertyParts[i]]) { |
||||
value = value[propertyParts[i]]; |
||||
} else { |
||||
return undefined; |
||||
} |
||||
} |
||||
return value; |
||||
} |
||||
} |
@ -0,0 +1,253 @@
|
||||
import PerfectScrollbar from 'perfect-scrollbar'; |
||||
import * as $ from 'jquery'; |
||||
import _ from 'lodash'; |
||||
|
||||
|
||||
export class GraphLegend { |
||||
firstRender = true; |
||||
ctrl: any; |
||||
panel: any; |
||||
data; |
||||
seriesList; |
||||
legendScrollbar; |
||||
|
||||
constructor(private $elem: JQuery<HTMLElement>, private popoverSrv, private scope) { |
||||
this.ctrl = scope.ctrl; |
||||
this.panel = this.ctrl.panel; |
||||
scope.$on('$destroy', () => { |
||||
if (this.legendScrollbar) { |
||||
this.legendScrollbar.destroy(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
getSeriesIndexForElement(el) { |
||||
return el.parents('[data-series-index]').data('series-index'); |
||||
} |
||||
|
||||
openColorSelector(e) { |
||||
// if we clicked inside poup container ignore click
|
||||
if ($(e.target).parents('.popover').length) { |
||||
return; |
||||
} |
||||
|
||||
var el = $(e.currentTarget).find('.fa-minus'); |
||||
var index = this.getSeriesIndexForElement(el); |
||||
var series = this.seriesList[index]; |
||||
|
||||
this.popoverSrv.show({ |
||||
element: el[0], |
||||
position: 'bottom left', |
||||
targetAttachment: 'top left', |
||||
template: |
||||
'<series-color-picker series="series" onToggleAxis="toggleAxis" onColorChange="colorSelected"/>', |
||||
openOn: 'hover', |
||||
model: { |
||||
series: series, |
||||
toggleAxis: () => { |
||||
this.ctrl.toggleAxis(series); |
||||
}, |
||||
colorSelected: color => { |
||||
this.ctrl.changeSeriesColor(series, color); |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
} |
||||
|
||||
toggleSeries(e) { |
||||
var el = $(e.currentTarget); |
||||
var index = this.getSeriesIndexForElement(el); |
||||
var seriesInfo = this.seriesList[index]; |
||||
var scrollPosition = this.$elem.find('tbody').scrollTop(); |
||||
this.ctrl.toggleSeries(seriesInfo, e); |
||||
this.$elem.find('tbody').scrollTop(scrollPosition); |
||||
} |
||||
|
||||
sortLegend(e) { |
||||
var el = $(e.currentTarget); |
||||
var stat = el.data('stat'); |
||||
|
||||
if (stat !== this.panel.legend.sort) { |
||||
this.panel.legend.sortDesc = null; |
||||
} |
||||
|
||||
// if already sort ascending, disable sorting
|
||||
if (this.panel.legend.sortDesc === false) { |
||||
this.panel.legend.sort = null; |
||||
this.panel.legend.sortDesc = null; |
||||
this.ctrl.render(); |
||||
return; |
||||
} |
||||
|
||||
this.panel.legend.sortDesc = !this.panel.legend.sortDesc; |
||||
this.panel.legend.sort = stat; |
||||
this.ctrl.render(); |
||||
} |
||||
|
||||
getTableHeaderHtml(statName) { |
||||
if (!this.panel.legend[statName]) { |
||||
return ''; |
||||
} |
||||
var html = '<th class="pointer" data-stat="' + statName + '">' + statName; |
||||
|
||||
if (this.panel.legend.sort === statName) { |
||||
var cssClass = this.panel.legend.sortDesc ? 'fa fa-caret-down' : 'fa fa-caret-up'; |
||||
html += ' <span class="' + cssClass + '"></span>'; |
||||
} |
||||
|
||||
return html + '</th>'; |
||||
} |
||||
|
||||
render() { |
||||
this.data = this.ctrl.seriesList; |
||||
if (!this.ctrl.panel.legend.show) { |
||||
this.$elem.empty(); |
||||
this.firstRender = true; |
||||
return; |
||||
} |
||||
|
||||
if (this.firstRender) { |
||||
this.$elem.on('click', '.graph-legend-icon', this.openColorSelector.bind(this)); |
||||
this.$elem.on('click', '.graph-legend-alias', this.toggleSeries.bind(this)); |
||||
this.$elem.on('click', 'th', this.sortLegend.bind(this)); |
||||
this.firstRender = false; |
||||
} |
||||
|
||||
this.seriesList = this.data; |
||||
|
||||
this.$elem.empty(); |
||||
|
||||
// Set min-width if side style and there is a value, otherwise remove the CSS propery
|
||||
var width = this.panel.legend.rightSide && this.panel.legend.sideWidth ? this.panel.legend.sideWidth + 'px' : ''; |
||||
this.$elem.css('min-width', width); |
||||
|
||||
this.$elem.toggleClass('graph-legend-table', this.panel.legend.alignAsTable === true); |
||||
|
||||
var tableHeaderElem; |
||||
if (this.panel.legend.alignAsTable) { |
||||
var header = '<tr>'; |
||||
header += '<th colspan="2" style="text-align:left"></th>'; |
||||
if (this.panel.legend.values) { |
||||
header += this.getTableHeaderHtml('min'); |
||||
header += this.getTableHeaderHtml('max'); |
||||
header += this.getTableHeaderHtml('avg'); |
||||
header += this.getTableHeaderHtml('current'); |
||||
header += this.getTableHeaderHtml('total'); |
||||
} |
||||
header += '</tr>'; |
||||
tableHeaderElem = $(header); |
||||
} |
||||
|
||||
if (this.panel.legend.sort) { |
||||
this.seriesList = _.sortBy(this.seriesList, function(series) { |
||||
return series.stats[this.panel.legend.sort]; |
||||
}); |
||||
if (this.panel.legend.sortDesc) { |
||||
this.seriesList = this.seriesList.reverse(); |
||||
} |
||||
} |
||||
|
||||
// render first time for getting proper legend height
|
||||
if (!this.panel.legend.rightSide) { |
||||
this.renderLegendElement(tableHeaderElem); |
||||
this.$elem.empty(); |
||||
} |
||||
|
||||
this.renderLegendElement(tableHeaderElem); |
||||
} |
||||
|
||||
renderSeriesLegendElements() { |
||||
let seriesElements = []; |
||||
for (let i = 0; i < this.seriesList.length; i++) { |
||||
var series = this.seriesList[i]; |
||||
|
||||
if (series.hideFromLegend(this.panel.legend)) { |
||||
continue; |
||||
} |
||||
|
||||
var html = '<div class="graph-legend-series'; |
||||
|
||||
if (series.yaxis === 2) { |
||||
html += ' graph-legend-series--right-y'; |
||||
} |
||||
if (this.ctrl.hiddenSeries[series.alias]) { |
||||
html += ' graph-legend-series-hidden'; |
||||
} |
||||
html += '" data-series-index="' + i + '">'; |
||||
html += '<div class="graph-legend-icon">'; |
||||
html += '<i class="fa fa-minus pointer" style="color:' + series.color + '"></i>'; |
||||
html += '</div>'; |
||||
|
||||
html += |
||||
'<a class="graph-legend-alias pointer" title="' + series.aliasEscaped + '">' + series.aliasEscaped + '</a>'; |
||||
|
||||
if (this.panel.legend.values) { |
||||
var avg = series.formatValue(series.stats.avg); |
||||
var current = series.formatValue(series.stats.current); |
||||
var min = series.formatValue(series.stats.min); |
||||
var max = series.formatValue(series.stats.max); |
||||
var total = series.formatValue(series.stats.total); |
||||
|
||||
if (this.panel.legend.min) { |
||||
html += '<div class="graph-legend-value min">' + min + '</div>'; |
||||
} |
||||
if (this.panel.legend.max) { |
||||
html += '<div class="graph-legend-value max">' + max + '</div>'; |
||||
} |
||||
if (this.panel.legend.avg) { |
||||
html += '<div class="graph-legend-value avg">' + avg + '</div>'; |
||||
} |
||||
if (this.panel.legend.current) { |
||||
html += '<div class="graph-legend-value current">' + current + '</div>'; |
||||
} |
||||
if (this.panel.legend.total) { |
||||
html += '<div class="graph-legend-value total">' + total + '</div>'; |
||||
} |
||||
} |
||||
|
||||
html += '</div>'; |
||||
seriesElements.push($(html)); |
||||
} |
||||
return seriesElements; |
||||
} |
||||
|
||||
renderLegendElement(tableHeaderElem) { |
||||
var seriesElements = this.renderSeriesLegendElements(); |
||||
|
||||
if (this.panel.legend.alignAsTable) { |
||||
var tbodyElem = $('<tbody></tbody>'); |
||||
tbodyElem.append(tableHeaderElem); |
||||
tbodyElem.append(seriesElements); |
||||
this.$elem.append(tbodyElem); |
||||
} else { |
||||
this.$elem.append(seriesElements); |
||||
} |
||||
|
||||
if (!this.panel.legend.rightSide) { |
||||
this.addScrollbar(); |
||||
} else { |
||||
this.destroyScrollbar(); |
||||
} |
||||
} |
||||
|
||||
addScrollbar() { |
||||
const scrollbarOptions = { |
||||
// Number of pixels the content height can surpass the container height without enabling the scroll bar.
|
||||
scrollYMarginOffset: 2, |
||||
suppressScrollX: true, |
||||
}; |
||||
|
||||
if (!this.legendScrollbar) { |
||||
this.legendScrollbar = new PerfectScrollbar(this.$elem[0], scrollbarOptions); |
||||
} else { |
||||
this.legendScrollbar.update(); |
||||
} |
||||
} |
||||
|
||||
destroyScrollbar() { |
||||
if (this.legendScrollbar) { |
||||
this.legendScrollbar.destroy(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,843 @@
|
||||
import { Segment } from './model/segment'; |
||||
import { GraphTooltip } from './graph_tooltip'; |
||||
import { ThresholdManager } from './threshold_manager'; |
||||
import { convertValuesToHistogram, getSeriesValues } from './histogram'; |
||||
import { |
||||
AnomalyController, |
||||
REGION_FILL_ALPHA as ANOMALY_REGION_FILL_ALPHA, |
||||
REGION_STROKE_ALPHA as ANOMALY_REGION_STROKE_ALPHA, |
||||
REGION_DELETE_COLOR_LIGHT as ANOMALY_REGION_DELETE_COLOR_LIGHT, |
||||
REGION_DELETE_COLOR_DARK as ANOMALY_REGION_DELETE_COLOR_DARK |
||||
} from './controllers/anomaly_controller'; |
||||
|
||||
|
||||
import './vendor/flot/jquery.flot'; |
||||
import './vendor/flot/jquery.flot.time'; |
||||
import './vendor/flot/jquery.flot.selection'; |
||||
import './vendor/flot/jquery.flot.stack'; |
||||
import './vendor/flot/jquery.flot.stackpercent'; |
||||
import './vendor/flot/jquery.flot.fillbelow'; |
||||
import './vendor/flot/jquery.flot.crosshair'; |
||||
import './vendor/flot/jquery.flot.dashes'; |
||||
import './vendor/flot/jquery.flot.events'; |
||||
|
||||
// import { EventManager } from 'grafana/app/features/annotations/event_manager';
|
||||
import TimeSeries from 'grafana/app/core/time_series2'; |
||||
import { getFlotTickDecimals } from 'grafana/app/core/utils/ticks'; |
||||
import { tickStep } from 'grafana/app/core/utils/ticks'; |
||||
import { appEvents, coreModule } from 'grafana/app/core/core'; |
||||
import kbn from 'grafana/app/core/utils/kbn'; |
||||
|
||||
import * as $ from 'jquery'; |
||||
import _ from 'lodash'; |
||||
import moment from 'moment'; |
||||
|
||||
|
||||
const COLOR_SELECTION = '#666'; |
||||
|
||||
|
||||
export class GraphRenderer { |
||||
|
||||
private _anomalyController: AnomalyController; |
||||
private data: any; |
||||
private tooltip: GraphTooltip; |
||||
private thresholdManager: ThresholdManager; |
||||
private panelWidth: number; |
||||
private plot: any; |
||||
private sortedSeries: any; |
||||
private ctrl: any; |
||||
private dashboard: any; |
||||
private panel: any; |
||||
// private eventManager;
|
||||
private flotOptions: any = {} |
||||
private $elem: JQuery<HTMLElement>; |
||||
private annotations: any[]; |
||||
private contextSrv: any; |
||||
private popoverSrv: any; |
||||
private scope: any; |
||||
private timeSrv: any; |
||||
private _graphMousePosition: any; |
||||
|
||||
constructor ($elem: JQuery<HTMLElement>, timeSrv, popoverSrv, contextSrv, scope) { |
||||
|
||||
var self = this; |
||||
this.$elem = $elem; |
||||
this.ctrl = scope.ctrl; |
||||
this.dashboard = this.ctrl.dashboard; |
||||
this.panel = this.ctrl.panel; |
||||
|
||||
this.timeSrv = timeSrv; |
||||
this.popoverSrv = popoverSrv; |
||||
this.contextSrv = contextSrv; |
||||
this.scope = scope; |
||||
|
||||
this._anomalyController = this.ctrl.anomalyController; |
||||
if(this._anomalyController === undefined) { |
||||
throw new Error('anomalyController is undefined'); |
||||
} |
||||
|
||||
|
||||
this.annotations = []; |
||||
this.panelWidth = 0; |
||||
|
||||
// this.eventManager = new EventManager(this.ctrl);
|
||||
this.flotOptions = {} |
||||
this.thresholdManager = new ThresholdManager(this.ctrl); |
||||
this.tooltip = new GraphTooltip( |
||||
$elem, this.dashboard, scope, () => this.< |