import angular from "angular"; import _ from "lodash"; import { InfluxQueryBuilder } from "./query_builder"; import InfluxQuery from "./influx_query"; import queryPart from "./query_part"; import { QueryCtrl } from "app/plugins/sdk"; export class InfluxQueryCtrl extends QueryCtrl { static templateUrl = "partials/query.editor.html"; queryModel: InfluxQuery; queryBuilder: any; groupBySegment: any; resultFormats: any[]; orderByTime: any[]; policySegment: any; tagSegments: any[]; selectMenu: any; measurementSegment: any; removeTagFilterSegment: any; /** @ngInject **/ constructor( $scope, $injector, private templateSrv, private $q, private uiSegmentSrv ) { super($scope, $injector); this.target = this.target; this.queryModel = new InfluxQuery( this.target, templateSrv, this.panel.scopedVars ); this.queryBuilder = new InfluxQueryBuilder( this.target, this.datasource.database ); this.groupBySegment = this.uiSegmentSrv.newPlusButton(); this.resultFormats = [ { text: "Time series", value: "time_series" }, { text: "Table", value: "table" } ]; this.policySegment = uiSegmentSrv.newSegment(this.target.policy); if (!this.target.measurement) { this.measurementSegment = uiSegmentSrv.newSelectMeasurement(); } else { this.measurementSegment = uiSegmentSrv.newSegment( this.target.measurement ); } this.tagSegments = []; for (let tag of this.target.tags) { if (!tag.operator) { if (/^\/.*\/$/.test(tag.value)) { tag.operator = "=~"; } else { tag.operator = "="; } } if (tag.condition) { this.tagSegments.push(uiSegmentSrv.newCondition(tag.condition)); } this.tagSegments.push(uiSegmentSrv.newKey(tag.key)); this.tagSegments.push(uiSegmentSrv.newOperator(tag.operator)); this.tagSegments.push(uiSegmentSrv.newKeyValue(tag.value)); } this.fixTagSegments(); this.buildSelectMenu(); this.removeTagFilterSegment = uiSegmentSrv.newSegment({ fake: true, value: "-- remove tag filter --" }); } removeOrderByTime() { this.target.orderByTime = "ASC"; } buildSelectMenu() { var categories = queryPart.getCategories(); this.selectMenu = _.reduce( categories, function(memo, cat, key) { var menu = { text: key, submenu: cat.map(item => { return { text: item.type, value: item.type }; }) }; memo.push(menu); return memo; }, [] ); } getGroupByOptions() { var query = this.queryBuilder.buildExploreQuery("TAG_KEYS"); return this.datasource .metricFindQuery(query) .then(tags => { var options = []; if (!this.queryModel.hasFill()) { options.push(this.uiSegmentSrv.newSegment({ value: "fill(null)" })); } if (!this.target.limit) { options.push(this.uiSegmentSrv.newSegment({ value: "LIMIT" })); } if (!this.target.slimit) { options.push(this.uiSegmentSrv.newSegment({ value: "SLIMIT" })); } if (this.target.orderByTime === "ASC") { options.push( this.uiSegmentSrv.newSegment({ value: "ORDER BY time DESC" }) ); } if (!this.queryModel.hasGroupByTime()) { options.push( this.uiSegmentSrv.newSegment({ value: "time($interval)" }) ); } for (let tag of tags) { options.push( this.uiSegmentSrv.newSegment({ value: "tag(" + tag.text + ")" }) ); } return options; }) .catch(this.handleQueryError.bind(this)); } groupByAction() { switch (this.groupBySegment.value) { case "LIMIT": { this.target.limit = 10; break; } case "SLIMIT": { this.target.slimit = 10; break; } case "ORDER BY time DESC": { this.target.orderByTime = "DESC"; break; } default: { this.queryModel.addGroupBy(this.groupBySegment.value); } } var plusButton = this.uiSegmentSrv.newPlusButton(); this.groupBySegment.value = plusButton.value; this.groupBySegment.html = plusButton.html; this.panelCtrl.refresh(); } addSelectPart(selectParts, cat, subitem) { this.queryModel.addSelectPart(selectParts, subitem.value); this.panelCtrl.refresh(); } handleSelectPartEvent(selectParts, part, evt) { switch (evt.name) { case "get-param-options": { var fieldsQuery = this.queryBuilder.buildExploreQuery("FIELDS"); return this.datasource .metricFindQuery(fieldsQuery) .then(this.transformToSegments(true)) .catch(this.handleQueryError.bind(this)); } case "part-param-changed": { this.panelCtrl.refresh(); break; } case "action": { this.queryModel.removeSelectPart(selectParts, part); this.panelCtrl.refresh(); break; } case "get-part-actions": { return this.$q.when([{ text: "Remove", value: "remove-part" }]); } } } handleGroupByPartEvent(part, index, evt) { switch (evt.name) { case "get-param-options": { var tagsQuery = this.queryBuilder.buildExploreQuery("TAG_KEYS"); return this.datasource .metricFindQuery(tagsQuery) .then(this.transformToSegments(true)) .catch(this.handleQueryError.bind(this)); } case "part-param-changed": { this.panelCtrl.refresh(); break; } case "action": { this.queryModel.removeGroupByPart(part, index); this.panelCtrl.refresh(); break; } case "get-part-actions": { return this.$q.when([{ text: "Remove", value: "remove-part" }]); } } } fixTagSegments() { var count = this.tagSegments.length; var lastSegment = this.tagSegments[Math.max(count - 1, 0)]; if (!lastSegment || lastSegment.type !== "plus-button") { this.tagSegments.push(this.uiSegmentSrv.newPlusButton()); } } measurementChanged() { this.target.measurement = this.measurementSegment.value; this.panelCtrl.refresh(); } getPolicySegments() { var policiesQuery = this.queryBuilder.buildExploreQuery( "RETENTION POLICIES" ); return this.datasource .metricFindQuery(policiesQuery) .then(this.transformToSegments(false)) .catch(this.handleQueryError.bind(this)); } policyChanged() { this.target.policy = this.policySegment.value; this.panelCtrl.refresh(); } toggleEditorMode() { try { this.target.query = this.queryModel.render(false); } catch (err) { console.log("query render error"); } this.target.rawQuery = !this.target.rawQuery; } getMeasurements(measurementFilter) { var query = this.queryBuilder.buildExploreQuery( "MEASUREMENTS", undefined, measurementFilter ); return this.datasource .metricFindQuery(query) .then(this.transformToSegments(true)) .catch(this.handleQueryError.bind(this)); } handleQueryError(err) { this.error = err.message || "Failed to issue metric query"; return []; } transformToSegments(addTemplateVars) { return results => { var segments = _.map(results, segment => { return this.uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable }); }); if (addTemplateVars) { for (let variable of this.templateSrv.variables) { segments.unshift( this.uiSegmentSrv.newSegment({ type: "template", value: "/^$" + variable.name + "$/", expandable: true }) ); } } return segments; }; } getTagsOrValues(segment, index) { if (segment.type === "condition") { return this.$q.when([ this.uiSegmentSrv.newSegment("AND"), this.uiSegmentSrv.newSegment("OR") ]); } if (segment.type === "operator") { var nextValue = this.tagSegments[index + 1].value; if (/^\/.*\/$/.test(nextValue)) { return this.$q.when(this.uiSegmentSrv.newOperators(["=~", "!~"])); } else { return this.$q.when( this.uiSegmentSrv.newOperators(["=", "!=", "<>", "<", ">"]) ); } } var query, addTemplateVars; if (segment.type === "key" || segment.type === "plus-button") { query = this.queryBuilder.buildExploreQuery("TAG_KEYS"); addTemplateVars = false; } else if (segment.type === "value") { query = this.queryBuilder.buildExploreQuery( "TAG_VALUES", this.tagSegments[index - 2].value ); addTemplateVars = true; } return this.datasource .metricFindQuery(query) .then(this.transformToSegments(addTemplateVars)) .then(results => { if (segment.type === "key") { results.splice(0, 0, angular.copy(this.removeTagFilterSegment)); } return results; }) .catch(this.handleQueryError.bind(this)); } getFieldSegments() { var fieldsQuery = this.queryBuilder.buildExploreQuery("FIELDS"); return this.datasource .metricFindQuery(fieldsQuery) .then(this.transformToSegments(false)) .catch(this.handleQueryError); } tagSegmentUpdated(segment, index) { this.tagSegments[index] = segment; // handle remove tag condition if (segment.value === this.removeTagFilterSegment.value) { this.tagSegments.splice(index, 3); if (this.tagSegments.length === 0) { this.tagSegments.push(this.uiSegmentSrv.newPlusButton()); } else if (this.tagSegments.length > 2) { this.tagSegments.splice(Math.max(index - 1, 0), 1); if ( this.tagSegments[this.tagSegments.length - 1].type !== "plus-button" ) { this.tagSegments.push(this.uiSegmentSrv.newPlusButton()); } } } else { if (segment.type === "plus-button") { if (index > 2) { this.tagSegments.splice( index, 0, this.uiSegmentSrv.newCondition("AND") ); } this.tagSegments.push(this.uiSegmentSrv.newOperator("=")); this.tagSegments.push( this.uiSegmentSrv.newFake( "select tag value", "value", "query-segment-value" ) ); segment.type = "key"; segment.cssClass = "query-segment-key"; } if (index + 1 === this.tagSegments.length) { this.tagSegments.push(this.uiSegmentSrv.newPlusButton()); } } this.rebuildTargetTagConditions(); } rebuildTargetTagConditions() { var tags = []; var tagIndex = 0; var tagOperator = ""; _.each(this.tagSegments, (segment2, index) => { if (segment2.type === "key") { if (tags.length === 0) { tags.push({}); } tags[tagIndex].key = segment2.value; } else if (segment2.type === "value") { tagOperator = this.getTagValueOperator( segment2.value, tags[tagIndex].operator ); if (tagOperator) { this.tagSegments[index - 1] = this.uiSegmentSrv.newOperator( tagOperator ); tags[tagIndex].operator = tagOperator; } tags[tagIndex].value = segment2.value; } else if (segment2.type === "condition") { tags.push({ condition: segment2.value }); tagIndex += 1; } else if (segment2.type === "operator") { tags[tagIndex].operator = segment2.value; } }); this.target.tags = tags; this.panelCtrl.refresh(); } getTagValueOperator(tagValue, tagOperator): string { if ( tagOperator !== "=~" && tagOperator !== "!~" && /^\/.*\/$/.test(tagValue) ) { return "=~"; } else if ( (tagOperator === "=~" || tagOperator === "!~") && /^(?!\/.*\/$)/.test(tagValue) ) { return "="; } return null; } getCollapsedText() { return this.queryModel.render(false); } }