import { SegmentId } from '../models/segment'; import { MetricExpanded } from '../models/metric'; import { DatasourceRequest } from '../models/datasource'; import { SegmentsSet } from '../models/segment_set'; import { AnalyticUnitId, AnalyticUnit, AnalyticSegment } from '../models/analytic_unit'; import { HasticServerInfo, HasticServerInfoUnknown } from '../models/hastic_server_info'; import { Threshold } from '../models/threshold'; import { appEvents } from 'grafana/app/core/core'; export class AnalyticService { private _isUp: boolean = false; constructor( private _hasticDatasourceURL: string, private $http ) { if(this._hasticDatasourceURL === undefined) { throw new TypeError('_hasticDatasourceURL is undefined'); } } async getAnalyticUnitTypes() { return this.get('/analyticUnits/types'); } async getAnalyticUnits(panelId: string) { const resp = await this.get('/analyticUnits/units', { panelId }); if(resp === undefined) { return []; } return resp.analyticUnits; } async getThresholds(ids: AnalyticUnitId[]) { const resp = await this.get('/threshold', { ids: ids.join(',') }); if(resp === undefined) { return []; } return resp.thresholds.filter(t => t !== null); } async updateThreshold(threshold: Threshold): Promise { return this.patch('/threshold', threshold); } async postNewItem( newItem: AnalyticUnit, metric: MetricExpanded, datasource: DatasourceRequest, grafanaUrl: string, panelId: string ): Promise { const response = await this.post('/analyticUnits', { grafanaUrl, panelId, type: newItem.type, name: newItem.name, metric: metric.toJSON(), datasource }); return response.id as AnalyticUnitId; } async updateMetric( analyticUnitId: AnalyticUnitId, metric: MetricExpanded, datasource: DatasourceRequest ) { await this.patch('/analyticUnits/metric', { analyticUnitId, metric: metric.toJSON(), datasource }); } async removeAnalyticUnit(id: AnalyticUnitId) { return this.delete('/analyticUnits', { id }); } async isDatasourceOk(): Promise { if(!this._checkDatasourceConfig()) { this._isUp = false; return false; } await this.get('/'); return this._isUp; } async updateSegments( id: AnalyticUnitId, addedSegments: SegmentsSet, removedSegments: SegmentsSet ): Promise { const getJSONs = (segs: SegmentsSet) => segs.getSegments().map(segment => ({ from: segment.from, to: segment.to, labeled: segment.labeled, deleted: segment.deleted })); const payload = { id, addedSegments: getJSONs(addedSegments), removedSegments: removedSegments.getSegments().map(s => s.id) }; const data = await this.patch('/segments', payload); if(data.addedIds === undefined) { throw new Error('Server didn`t send addedIds'); } return data.addedIds as SegmentId[]; } async getSegments(id: AnalyticUnitId, from?: number, to?: number): Promise { if(id === undefined) { throw new Error('id is undefined'); } var payload: any = { id }; if(from !== undefined) { payload['from'] = from; } if(to !== undefined) { payload['to'] = to; } var data = await this.get('/segments', payload); if(data.segments === undefined) { throw new Error('Server didn`t return segments array'); } var segments = data.segments as { id: SegmentId, from: number, to: number, labeled: boolean, deleted: boolean }[]; return segments.map(s => new AnalyticSegment(s.labeled, s.id, s.from, s.to, s.deleted)); } async * getStatusGenerator(id: AnalyticUnitId, duration: number): AsyncIterableIterator<{ status: string, errorMessage?: string }> { if(id === undefined) { throw new Error('id is undefined'); } let statusCheck = async () => { try { return await this.get('/analyticUnits/status', { id }); } catch(error) { if(error.status === 404) { return { status: '404' }; } throw error; } } let timeout = async () => new Promise( resolve => setTimeout(resolve, duration) ); while(true) { yield await statusCheck(); await timeout(); } } async getServerInfo(): Promise { const data = await this.get('/'); if(data === undefined) { return HasticServerInfoUnknown; } return { nodeVersion: data.nodeVersion, packageVersion: data.packageVersion, npmUserAgent: data.npmUserAgent, docker: data.docker, zmqConectionString: data.zmqConectionString, serverPort: data.serverPort, gitBranch: data.git.branch, gitCommitHash: data.git.commitHash }; } async setAnalyticUnitAlert(analyticUnit: AnalyticUnit) { return this.patch('/analyticUnits/alert', { analyticUnitId: analyticUnit.id, alert: analyticUnit.alert }); } async updateAnalyticUnit(updateObj: any) { return this.patch('/analyticUnits', updateObj); } async runDetect(id: AnalyticUnitId) { return this.post('/analyticUnits/detect', { id }); } private async _analyticRequest(method: string, url: string, data?: any) { try { method = method.toUpperCase(); url = this._hasticDatasourceURL + url; let requestObject: any = { method, url }; if(method === 'GET' || method === 'DELETE') { requestObject.params = data; } else { requestObject.data = data; } let response = await this.$http(requestObject); this._isUp = true; return response.data; } catch(error) { if(error.xhrStatus === 'error') { this.displayConnectionErrorAlert(); this._isUp = false; } else { this._isUp = true; } // throw error; } } get hasticDatasourceURL(): string { return this._hasticDatasourceURL; } private _checkDatasourceConfig(): boolean { if(this._hasticDatasourceURL === null || this._hasticDatasourceURL === undefined || this._hasticDatasourceURL === '') { appEvents.emit( 'alert-warning', [ `Hastic Datasource is missing`, `Please setup Hastic Datasource. More info: https://github.com/hastic/hastic-grafana-app/wiki/Getting-started` ] ); return false; } return true; } private async get(url, params?) { return this._analyticRequest('GET', url, params); } private async post(url, data?) { return this._analyticRequest('POST', url, data); } private async patch(url, data?) { return this._analyticRequest('PATCH', url, data); } private async delete(url, data?) { return this._analyticRequest('DELETE', url, data); } private displayConnectionErrorAlert() { appEvents.emit( 'alert-error', [ 'No connection to Hastic Datasource', `Hastic Datasource URL: "${this._hasticDatasourceURL}"`, ] ); } public get isUp(): boolean { return this._isUp; } }