diff --git a/README.md b/README.md index 7ce9591..0951533 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,13 @@ See also: * [Wiki](https://github.com/hastic/hastic-grafana-app/wiki) * [FAQ](https://github.com/hastic/hastic-grafana-app/wiki/FAQ) * [Hastic-server](https://github.com/hastic/hastic-server) -* [Installation from source](https://github.com/hastic/hastic-grafana-app/wiki/Installation-from-source) +* [Install from source](https://github.com/hastic/hastic-grafana-app/wiki/Development#install-from-source) * [Changelog](https://github.com/hastic/hastic-grafana-app/wiki/Changelog) # Prerequisites * [hastic-server](https://github.com/hastic/hastic-server) -* [Grafana >= 5.4.0](https://grafana.com/grafana/download) +* [Grafana >= 5.4.0](https://grafana.com/grafana/download), we don't support Grafana 6.x.x yet ## Support and Consulting diff --git a/package.json b/package.json index 183599f..46f92bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grafana-hastic-app", - "version": "0.3.3", + "version": "0.3.4", "description": "Hastic app: labeling and rendeting analytics from hastic-server", "main": "dist/module", "scripts": { diff --git a/src/panel/graph_panel/controllers/analytic_controller.ts b/src/panel/graph_panel/controllers/analytic_controller.ts index e27ffc4..f8a4fec 100644 --- a/src/panel/graph_panel/controllers/analytic_controller.ts +++ b/src/panel/graph_panel/controllers/analytic_controller.ts @@ -1,12 +1,11 @@ // Corresponds to https://github.com/hastic/hastic-server/blob/master/server/src/models/analytic_units/analytic_unit.ts -import { AnalyticService } from '../services/analytic_service'; +import { AnalyticService, TableTimeSeries } from '../services/analytic_service'; import { AnalyticUnitId, AnalyticUnit, AnalyticSegment, AnalyticSegmentsSearcher, AnalyticSegmentPair, - LabelingMode, - DetectorType + LabelingMode } from '../models/analytic_units/analytic_unit'; import { AnomalyAnalyticUnit } from '../models/analytic_units/anomaly_analytic_unit'; import { AnalyticUnitsSet } from '../models/analytic_units/analytic_units_set'; @@ -104,9 +103,14 @@ export class AnalyticController { } } + cancelCreation() { + delete this._newAnalyticUnit; + this._creatingNewAnalyticUnit = false; + } + async saveNew(metric: MetricExpanded, datasource: DatasourceRequest) { this._savingNewAnalyticUnit = true; - const newAnalyticUnit = createAnalyticUnit(this._newAnalyticUnit.serverObject); + const newAnalyticUnit = createAnalyticUnit(this._newAnalyticUnit.toJSON()); newAnalyticUnit.id = await this._analyticService.postNewAnalyticUnit( newAnalyticUnit, metric, datasource, this._grafanaUrl, this._panelId ); @@ -143,7 +147,8 @@ export class AnalyticController { await this.disableLabeling(); this._selectedAnalyticUnitId = id; this.labelingUnit.selected = true; - this.toggleLabelingMode(LabelingMode.LABELING); + const labelingModes = this.labelingUnit.labelingModes; + this.toggleLabelingMode(labelingModes[0].value); } async disableLabeling() { @@ -196,9 +201,9 @@ export class AnalyticController { this.labelingUnit.labelingMode = labelingMode; } - addLabelSegment(segment: Segment, deleted = false) { - const asegment = this.labelingUnit.addLabeledSegment(segment, deleted); - this._labelingDataAddedSegments.addSegment(asegment); + addSegment(segment: Segment, deleted = false) { + const addedSegment = this.labelingUnit.addSegment(segment, deleted); + this._labelingDataAddedSegments.addSegment(addedSegment); } get analyticUnits(): AnalyticUnit[] { @@ -215,7 +220,7 @@ export class AnalyticController { } else { analyticUnit.labeledColor = value; } - await this.saveAnalyticUnit(analyticUnit); + analyticUnit.changed = true; } fetchAnalyticUnitsStatuses() { @@ -309,7 +314,7 @@ export class AnalyticController { return newIds; } - async redetectAll() { + async redetectAll(from?: number, to?: number) { this.analyticUnits.forEach(unit => { // TODO: remove duplication with runDetect unit.segments.clear(); @@ -317,9 +322,9 @@ export class AnalyticController { unit.status = null; }); const ids = this.analyticUnits.map(analyticUnit => analyticUnit.id); - await this._analyticService.runDetect(ids); + await this._analyticService.runDetect(ids, from, to); - _.each(this.analyticUnits, analyticUnit => this._runStatusWaiter(analyticUnit)); + this.fetchAnalyticUnitsStatuses(); } async runDetect(analyticUnitId: AnalyticUnitId, from?: number, to?: number) { @@ -453,11 +458,7 @@ export class AnalyticController { if(!this.inLabelingMode) { throw new Error(`Can't enter ${labelingMode} mode when labeling mode is disabled`); } - if(this.labelingUnit.labelingMode === labelingMode) { - this.labelingUnit.labelingMode = LabelingMode.LABELING; - } else { - this.labelingUnit.labelingMode = labelingMode; - } + this.labelingUnit.labelingMode = labelingMode; } async removeAnalyticUnit(id: AnalyticUnitId, silent: boolean = false): Promise { @@ -478,14 +479,19 @@ export class AnalyticController { await this._analyticService.setAnalyticUnitAlert(analyticUnit); } + toggleAnalyticUnitChange(analyticUnit: AnalyticUnit, value: boolean): void { + analyticUnit.changed = value; + } + async saveAnalyticUnit(analyticUnit: AnalyticUnit): Promise { if(analyticUnit.id === null || analyticUnit.id === undefined) { throw new Error('Cannot save analytic unit without id'); } analyticUnit.saving = true; - await this._analyticService.updateAnalyticUnit(analyticUnit.serverObject); + await this._analyticService.updateAnalyticUnit(analyticUnit.toJSON()); analyticUnit.saving = false; + analyticUnit.changed = false; } async getAnalyticUnits(): Promise { @@ -503,54 +509,68 @@ export class AnalyticController { this.fetchAnalyticUnitsStatuses(); } - async getHSR(from: number, to: number): Promise { - // Returns HSR (Hastic Signal Representation) for analytic unit with enabled "Show HSR" - // Returns null when there is no analytic units which have "Show HSR" enabled - if(this.hsrAnalyticUnit === null) { + async getHSR(from: number, to: number): Promise<{ + hsr: HSRTimeSeries, + lowerBound?: HSRTimeSeries, + upperBound?: HSRTimeSeries + } | null> { + // Returns HSR (Hastic Signal Representation) for analytic unit in "Inspect" mode + // Returns null when there are no analytic units in "Inspect" mode + // or if there is no response from server + if(this.inspectedAnalyticUnit === null) { return null; } - const hsr = await this._analyticService.getHSR(this.hsrAnalyticUnit.id, from, to); - const datapoints = hsr.values.map(value => value.reverse() as [number, number]); - return { target: 'HSR', datapoints }; + const response = await this._analyticService.getHSR(this.inspectedAnalyticUnit.id, from, to); + if(response === null) { + return null; + } + + const hsr = convertTableToTimeSeries('HSR', response.hsr); + const lowerBound = convertTableToTimeSeries('Lower bound', response.lowerBound); + const upperBound = convertTableToTimeSeries('Upper bound', response.upperBound); + + return { + hsr, + lowerBound, + upperBound + }; } async getHSRSeries(from: number, to: number) { - const hsr = await this.getHSR(from, to); + const response = await this.getHSR(from, to); - if(hsr === null) { + if(response === null) { return []; } - if(this.hsrAnalyticUnit.detectorType === DetectorType.ANOMALY) { - const confidence = (this.hsrAnalyticUnit as AnomalyAnalyticUnit).confidence; + const hsrSerie = { + ...response.hsr, + color: ANALYTIC_UNIT_COLORS[0], + // TODO: render it separately from Metric series + overrides: [ + { alias: 'HSR', linewidth: 3, fill: 0 } + ] + }; + + if(response.lowerBound !== undefined && response.upperBound !== undefined) { // TODO: looks bad return [ { - target: 'Confidence interval lower', - datapoints: hsr.datapoints.map(datapoint => - [datapoint[0] - confidence, datapoint[1]] - ), + target: '[AnomalyDetector]: lower bound', + datapoints: response.lowerBound.datapoints, color: ANALYTIC_UNIT_COLORS[0], - overrides: [{ alias: 'Confidence interval lower', linewidth: 1, fill: 0 }] + overrides: [{ alias: '[AnomalyDetector]: lower bound', linewidth: 1, fill: 0 }] }, { - target: 'Confidence interval upper', - datapoints: hsr.datapoints.map(datapoint => - [datapoint[0] + confidence, datapoint[1]] - ), + target: '[AnomalyDetector]: upper bound', + datapoints: response.upperBound.datapoints, color: ANALYTIC_UNIT_COLORS[0], - overrides: [{ alias: 'Confidence interval upper', linewidth: 1, fill: 0 }] + overrides: [{ alias: '[AnomalyDetector]: upper bound', linewidth: 1, fill: 0 }] }, + hsrSerie ]; } - return { - ...hsr, - color: ANALYTIC_UNIT_COLORS[0], - // TODO: render it separately from Metric series - overrides: [ - { alias: 'HSR', linewidth: 3, fill: 0 } - ] - }; + return hsrSerie; } get inspectedAnalyticUnit(): AnalyticUnit | null { @@ -562,16 +582,6 @@ export class AnalyticController { return null; } - get hsrAnalyticUnit(): AnalyticUnit | null { - // TODO: remove inspectedAnalyticUnit duplication - for(let analyticUnit of this.analyticUnits) { - if(analyticUnit.showHSR) { - return analyticUnit; - } - }; - return null; - } - public get conditions() { return _.values(Condition); } @@ -617,7 +627,7 @@ export class AnalyticController { } analyticUnit.detectionSpans = data; let isFinished = true; - for (let detection of data) { + for(let detection of data) { if(detection.status === DetectionStatus.RUNNING) { isFinished = false; } @@ -671,29 +681,28 @@ export class AnalyticController { return this._tempIdCounted.toString(); } - public async toggleVisibility(id: AnalyticUnitId, value?: boolean) { + public toggleVisibility(id: AnalyticUnitId, value?: boolean) { const analyticUnit = this._analyticUnitsSet.byId(id); if(value !== undefined) { analyticUnit.visible = value; } else { analyticUnit.visible = !analyticUnit.visible; } - await this.saveAnalyticUnit(analyticUnit); + analyticUnit.changed = true; } public toggleInspect(id: AnalyticUnitId) { - const analyticUnit = this._analyticUnitsSet.byId(id); - if(!analyticUnit.inspect) { - this.analyticUnits.forEach(unit => unit.inspect = false); - } + this.analyticUnits + .filter(analyticUnit => analyticUnit.id !== id) + .forEach(unit => unit.inspect = false); } - public toggleHSR(id: AnalyticUnitId) { - // TODO: remove toggleInspect duplication - const analyticUnit = this._analyticUnitsSet.byId(id); - if(!analyticUnit.showHSR) { - this.analyticUnits.forEach(unit => unit.showHSR = false); + public async updateSeasonality(id: AnalyticUnitId, value?: number) { + const analyticUnit = this._analyticUnitsSet.byId(id) as AnomalyAnalyticUnit; + if(value !== undefined) { + analyticUnit.seasonalityPeriod.value = value; } + analyticUnit.changed = true; } public onAnalyticUnitDetectorChange(analyticUnitTypes: any) { @@ -730,3 +739,12 @@ function addAlphaToRGB(colorString: string, alpha: number): string { return colorString; } } + +function convertTableToTimeSeries(target: string, tableData?: TableTimeSeries): HSRTimeSeries { + if(tableData === undefined) { + return undefined; + } + const datapoints = tableData.values.map(value => value.reverse() as [number, number]); + + return { target, datapoints }; +} diff --git a/src/panel/graph_panel/graph_ctrl.ts b/src/panel/graph_panel/graph_ctrl.ts index 4c08c41..8472b35 100644 --- a/src/panel/graph_panel/graph_ctrl.ts +++ b/src/panel/graph_panel/graph_ctrl.ts @@ -272,12 +272,7 @@ class GraphCtrl extends MetricsPanelCtrl { if(analyticUnit.status === '404') { await this.analyticsController.removeAnalyticUnit(analyticUnit.id, true); } - if(analyticUnit.status === 'READY') { - const { from, to } = this.rangeTimestamp; - await this.analyticsController.fetchSegments(analyticUnit, from, to); - } - this.render(this.seriesList); - this.$scope.$digest(); + this.refresh(); }); appEvents.on('ds-request-response', data => { @@ -326,7 +321,6 @@ class GraphCtrl extends MetricsPanelCtrl { } this.analyticsController = new AnalyticController(this._grafanaUrl, this._panelId, this.panel, this.events, this.analyticService); - this.analyticsController.fetchAnalyticUnitsStatuses(); this._updatePanelInfo(); this.analyticsController.updateServerInfo(); @@ -398,23 +392,27 @@ class GraphCtrl extends MetricsPanelCtrl { }; break; } + const from = _.find(series.datapoints, datapoint => datapoint[0] !== null); + const to = _.findLast(series.datapoints, datapoint => datapoint[0] !== null); + + this._dataTimerange = {}; + if(from !== undefined && to !== undefined) { + this._dataTimerange = { from: from[1], to: to[1] }; + } } } if(this.analyticsController !== undefined) { + await this.analyticsController.fetchAnalyticUnitsSegments(from, to); + // TODO: make statuses and detection spans connected + this.analyticsController.fetchAnalyticUnitsStatuses(); this.analyticsController.stopAnalyticUnitsDetectionsFetching(); - const loadTasks = [ - // this.annotationsPromise, - this.analyticsController.fetchAnalyticUnitsSegments(from, to) - ]; - - await Promise.all(loadTasks); - // this.annotations = results[0].annotations; - this.render(this.seriesList); + // TODO: re-run detection waiters if this._dataTimerange is changed this.analyticsController.fetchAnalyticUnitsDetections( this._dataTimerange.from, this._dataTimerange.to ); + this.render(this.seriesList); } this.loading = false; @@ -426,14 +424,6 @@ class GraphCtrl extends MetricsPanelCtrl { } for(let series of this.seriesList) { - const from = _.find(series.datapoints, datapoint => datapoint[0] !== null); - const to = _.findLast(series.datapoints, datapoint => datapoint[0] !== null); - - this._dataTimerange = {}; - if(from !== undefined && to !== undefined) { - this._dataTimerange = { from: from[1], to: to[1] }; - } - if (series.unit) { this.panel.yaxes[series.yaxis - 1].format = series.unit; } @@ -572,8 +562,13 @@ class GraphCtrl extends MetricsPanelCtrl { this.analyticsController.createNew(); } + cancelCreation() { + this.analyticsController.cancelCreation(); + } + redetectAll() { - this.analyticsController.redetectAll(); + const { from, to } = this.rangeTimestamp; + this.analyticsController.redetectAll(from, to); } async runDetectInCurrentRange(analyticUnitId: AnalyticUnitId) { @@ -610,7 +605,11 @@ class GraphCtrl extends MetricsPanelCtrl { await this.analyticsController.toggleAnalyticUnitAlert(analyticUnit); } - async onAnalyticUnitChange(analyticUnit: AnalyticUnit) { + onAnalyticUnitChange(analyticUnit: AnalyticUnit) { + this.analyticsController.toggleAnalyticUnitChange(analyticUnit, true); + } + + async onAnalyticUnitSave(analyticUnit: AnalyticUnit) { await this.analyticsController.saveAnalyticUnit(analyticUnit); this.refresh(); } @@ -683,8 +682,8 @@ class GraphCtrl extends MetricsPanelCtrl { this.refresh(); } - onToggleHSR(id: AnalyticUnitId) { - this.analyticsController.toggleHSR(id); + onSeasonalityChange(id: AnalyticUnitId, value?: number) { + this.analyticsController.updateSeasonality(id, value); this.refresh(); } diff --git a/src/panel/graph_panel/graph_legend.ts b/src/panel/graph_panel/graph_legend.ts index 1f887da..394c351 100644 --- a/src/panel/graph_panel/graph_legend.ts +++ b/src/panel/graph_panel/graph_legend.ts @@ -46,7 +46,7 @@ export class GraphLegend { position: 'bottom left', targetAttachment: 'top left', template: - '', + '', openOn: 'hover', model: { series: series, diff --git a/src/panel/graph_panel/graph_renderer.ts b/src/panel/graph_panel/graph_renderer.ts index 231695a..c4000fc 100644 --- a/src/panel/graph_panel/graph_renderer.ts +++ b/src/panel/graph_panel/graph_renderer.ts @@ -153,10 +153,10 @@ export class GraphRenderer { this._analyticController.deleteLabelingAnalyticUnitSegmentsInRange( segment.from, segment.to ); - this._analyticController.addLabelSegment(segment, true); + this._analyticController.addSegment(segment, true); } if(this._analyticController.labelingMode === LabelingMode.LABELING) { - this._analyticController.addLabelSegment(segment, false); + this._analyticController.addSegment(segment, false); } if(this._analyticController.labelingMode === LabelingMode.UNLABELING) { this._analyticController.deleteLabelingAnalyticUnitSegmentsInRange( diff --git a/src/panel/graph_panel/models/analytic_units/analytic_unit.ts b/src/panel/graph_panel/models/analytic_units/analytic_unit.ts index a395706..a7c48b6 100644 --- a/src/panel/graph_panel/models/analytic_units/analytic_unit.ts +++ b/src/panel/graph_panel/models/analytic_units/analytic_unit.ts @@ -33,6 +33,9 @@ export class AnalyticSegment extends Segment { if(!_.isBoolean(this.labeled)) { throw new Error('labeled value is not boolean'); } + if(labeled && deleted) { + throw new Error('Segment can`t be both labeled and deleted'); + } } } @@ -47,6 +50,8 @@ const DEFAULTS = { visible: true }; +const LABELING_MODES = []; + export class AnalyticUnit { private _labelingMode: LabelingMode = LabelingMode.LABELING; @@ -55,7 +60,7 @@ export class AnalyticUnit { private _segmentSet = new SegmentArray(); private _detectionSpans: DetectionSpan[]; private _inspect = false; - private _showHSR = false; + private _changed = false; private _status: string; private _error: string; @@ -113,12 +118,12 @@ export class AnalyticUnit { get saving(): boolean { return this._saving; } set saving(value: boolean) { this._saving = value; } + get changed(): boolean { return this._changed; } + set changed(value: boolean) { this._changed = value; } + get inspect(): boolean { return this._inspect; } set inspect(value: boolean) { this._inspect = value; } - get showHSR(): boolean { return this._showHSR; } - set showHSR(value: boolean) { this._showHSR = value; } - get visible(): boolean { return (this._serverObject.visible === undefined) ? true : this._serverObject.visible } @@ -126,10 +131,10 @@ export class AnalyticUnit { this._serverObject.visible = value; } - addLabeledSegment(segment: Segment, deleted: boolean): AnalyticSegment { - const asegment = new AnalyticSegment(!deleted, segment.id, segment.from, segment.to, deleted); - this._segmentSet.addSegment(asegment); - return asegment; + addSegment(segment: Segment, deleted: boolean): AnalyticSegment { + const addedSegment = new AnalyticSegment(!deleted, segment.id, segment.from, segment.to, deleted); + this._segmentSet.addSegment(addedSegment); + return addedSegment; } removeSegmentsInRange(from: number, to: number): AnalyticSegment[] { @@ -152,8 +157,10 @@ export class AnalyticUnit { value !== '404' && value !== 'READY' && value !== 'LEARNING' && + value !== 'DETECTION' && value !== 'PENDING' && value !== 'FAILED' && + value !== 'SUCCESS' && value !== null ) { throw new Error('Unsupported status value: ' + value); @@ -176,4 +183,8 @@ export class AnalyticUnit { get serverObject() { return this._serverObject; } + // TODO: make it abstract + get labelingModes() { + return LABELING_MODES; + } } diff --git a/src/panel/graph_panel/models/analytic_units/anomaly_analytic_unit.ts b/src/panel/graph_panel/models/analytic_units/anomaly_analytic_unit.ts index 2d185d6..cacfa1e 100644 --- a/src/panel/graph_panel/models/analytic_units/anomaly_analytic_unit.ts +++ b/src/panel/graph_panel/models/analytic_units/anomaly_analytic_unit.ts @@ -1,14 +1,30 @@ -import { AnalyticUnit, DetectorType } from './analytic_unit'; +import { AnalyticUnit, DetectorType, LabelingMode } from './analytic_unit'; import _ from 'lodash'; +import moment from 'moment'; + +type TimePeriod = { + value: number, + unit: string +}; const DEFAULTS = { detectorType: DetectorType.ANOMALY, type: 'ANOMALY', alpha: 0.5, - confidence: 1 + confidence: 1, + seasonality: 0, + seasonalityPeriod: { + value: 0, + unit: 'seconds' + } }; +const LABELING_MODES = [ + { name: 'Label Negative', value: LabelingMode.DELETING }, + { name: 'Unlabel', value: LabelingMode.UNLABELING } +]; + export class AnomalyAnalyticUnit extends AnalyticUnit { constructor(_serverObject?: any) { @@ -21,7 +37,9 @@ export class AnomalyAnalyticUnit extends AnalyticUnit { return { ...baseJSON, alpha: this.alpha, - confidence: this.confidence + confidence: this.confidence, + seasonality: this.seasonality, + seasonalityPeriod: this.seasonalityPeriod }; } @@ -30,4 +48,29 @@ export class AnomalyAnalyticUnit extends AnalyticUnit { set confidence(val: number) { this._serverObject.confidence = val; } get confidence(): number { return this._serverObject.confidence; } + + get seasonality(): number { + let seasonalityObj = {}; + seasonalityObj[this.seasonalityPeriod.unit] = this.seasonalityPeriod.value; + return moment.duration(seasonalityObj).asMilliseconds(); + } + + set seasonalityPeriod(val: TimePeriod) { this._serverObject.seasonalityPeriod = val; } + get seasonalityPeriod(): TimePeriod { return this._serverObject.seasonalityPeriod; } + + // TODO: merge seasonality and hasSeasonality + set hasSeasonality(val: boolean) { + if(val) { + this.seasonalityPeriod = { value: 1, unit: 'seconds' }; + } else { + this.seasonalityPeriod = { value: 0, unit: 'seconds' }; + } + } + get hasSeasonality(): boolean { + return this.seasonality > 0; + } + + get labelingModes() { + return LABELING_MODES; + } } diff --git a/src/panel/graph_panel/models/analytic_units/pattern_analytic_unit.ts b/src/panel/graph_panel/models/analytic_units/pattern_analytic_unit.ts index c0f6787..5222b20 100644 --- a/src/panel/graph_panel/models/analytic_units/pattern_analytic_unit.ts +++ b/src/panel/graph_panel/models/analytic_units/pattern_analytic_unit.ts @@ -1,4 +1,4 @@ -import { AnalyticUnit, DetectorType } from './analytic_unit'; +import { AnalyticUnit, DetectorType, LabelingMode } from './analytic_unit'; import _ from 'lodash'; @@ -7,6 +7,12 @@ const DEFAULTS = { type: 'GENERAL' }; +const LABELING_MODES = [ + { name: 'Label Positive', value: LabelingMode.LABELING }, + { name: 'Label Negative', value: LabelingMode.DELETING }, + { name: 'Unlabel', value: LabelingMode.UNLABELING } +]; + export class PatternAnalyticUnit extends AnalyticUnit { constructor(_serverObject?: any) { @@ -20,4 +26,8 @@ export class PatternAnalyticUnit extends AnalyticUnit { ...baseJSON }; } + + get labelingModes() { + return LABELING_MODES; + } } diff --git a/src/panel/graph_panel/models/analytic_units/threshold_analytic_unit.ts b/src/panel/graph_panel/models/analytic_units/threshold_analytic_unit.ts index 3af8a5c..73d8f3a 100644 --- a/src/panel/graph_panel/models/analytic_units/threshold_analytic_unit.ts +++ b/src/panel/graph_panel/models/analytic_units/threshold_analytic_unit.ts @@ -19,6 +19,8 @@ const DEFAULTS = { condition: Condition.ABOVE_OR_EQUAL }; +const LABELING_MODES = []; + export class ThresholdAnalyticUnit extends AnalyticUnit { constructor(_serverObject?: any) { @@ -40,4 +42,8 @@ export class ThresholdAnalyticUnit extends AnalyticUnit { set condition(val: Condition) { this._serverObject.condition = val; } get condition(): Condition { return this._serverObject.condition; } + + get labelingModes() { + return LABELING_MODES; + } } diff --git a/src/panel/graph_panel/models/detection.ts b/src/panel/graph_panel/models/detection.ts index 166f8f0..9447aea 100644 --- a/src/panel/graph_panel/models/detection.ts +++ b/src/panel/graph_panel/models/detection.ts @@ -14,7 +14,7 @@ export type DetectionSpan = { }; export const DETECTION_STATUS_TEXT = new Map([ - [DetectionStatus.READY, 'Detection is done'], - [DetectionStatus.RUNNING, 'Detection is running...'], - [DetectionStatus.FAILED, 'Detection failed'] + [DetectionStatus.READY, '[DetectionStatus]: done'], + [DetectionStatus.RUNNING, '[DetectionStatus]: running...'], + [DetectionStatus.FAILED, '[DetectionStatus]: failed'] ]); diff --git a/src/panel/graph_panel/partials/tab_analytics.html b/src/panel/graph_panel/partials/tab_analytics.html index 97c08f6..65a0e4f 100644 --- a/src/panel/graph_panel/partials/tab_analytics.html +++ b/src/panel/graph_panel/partials/tab_analytics.html @@ -1,7 +1,7 @@
- - - -
-
- +
+ +
+ - + > + + +
+ +
- +
+ + + +
- +
+ +
- +
+ +
+
- +
+
+ +
+ + +
- +
+ + +
+ +
+ + +
- + + +
-
- +
+ +
- + +
-
+
- + +
+
-
+
- + + +
-
-
-
+
diff --git a/src/panel/graph_panel/series_overrides_ctrl.ts b/src/panel/graph_panel/series_overrides_ctrl.ts index fdf6edb..c5c8f99 100644 --- a/src/panel/graph_panel/series_overrides_ctrl.ts +++ b/src/panel/graph_panel/series_overrides_ctrl.ts @@ -54,7 +54,7 @@ export class SeriesOverridesCtrl { element: $element.find('.dropdown')[0], position: 'top center', openOn: 'click', - template: '', + template: '', model: { autoClose: true, colorSelected: $scope.colorSelected, diff --git a/src/panel/graph_panel/services/analytic_service.ts b/src/panel/graph_panel/services/analytic_service.ts index 2935efa..bc6b2a2 100644 --- a/src/panel/graph_panel/services/analytic_service.ts +++ b/src/panel/graph_panel/services/analytic_service.ts @@ -12,6 +12,11 @@ import { appEvents } from 'grafana/app/core/core'; import * as _ from 'lodash'; +// TODO: TableTimeSeries is bad name +export type TableTimeSeries = { + values: [number, number][]; + columns: string[]; +}; export class AnalyticService { private _isUp: boolean = false; @@ -201,10 +206,14 @@ export class AnalyticService { } async getHSR(analyticUnitId: AnalyticUnitId, from: number, to: number): Promise<{ - values: [number, number][]; - columns: string[]; - }> { + hsr: TableTimeSeries, + lowerBound?: TableTimeSeries, + upperBound?: TableTimeSeries + } | null> { const data = await this.get('/query', { analyticUnitId, from, to }); + if(data === undefined) { + return null; + } return data.results; } @@ -242,7 +251,7 @@ export class AnalyticService { } catch(error) { // xhrStatus may be one of: ('complete', 'error', 'timeout' or 'abort') // See: https://github.com/angular/angular.js/blob/55075b840c9194b8524627a293d6166528b9a1c2/src/ng/http.js#L919-L920 - if(error.xhrStatus !== 'complete') { + if(error.xhrStatus !== 'complete' || error.status === 502) { this.displayConnectionErrorAlert(); this._isUp = false; } else { diff --git a/src/plugin.json b/src/plugin.json index e22778d..0d70fef 100644 --- a/src/plugin.json +++ b/src/plugin.json @@ -11,7 +11,7 @@ "small": "img/icn-graph-panel.png", "large": "img/icn-graph-panel.png" }, - "version": "0.3.3" + "version": "0.3.4" }, "includes": [ { "type": "panel", "name": "Hastic Graph Panel" }, diff --git a/src/utlis.ts b/src/utlis.ts index 83cbca5..f2190c8 100644 --- a/src/utlis.ts +++ b/src/utlis.ts @@ -1,7 +1,7 @@ import url from 'url-parse'; import * as _ from 'lodash'; -export const SUPPORTED_SERVER_VERSION = '0.3.3-beta'; +export const SUPPORTED_SERVER_VERSION = '0.3.4-beta'; export function normalizeUrl(inputUrl: string) { if(!inputUrl) {