blob: 29a3408fb3d2e5ff102026a8939346acd144f968 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var App = require('app');
App.WidgetWizardStep2Controller = Em.Controller.extend({
name: "widgetWizardStep2Controller",
EXPRESSION_PREFIX: 'Expression',
/**
* @type {RegExp}
* @const
*/
EXPRESSION_REGEX: /\$\{([\w\s\.\,\+\-\*\/\(\)\:\=\[\]]*)\}/g,
/**
* list of operators that can be used in expression
* @type {Array}
* @constant
*/
OPERATORS: ["+", "-", "*", "/", "(", ")"],
/**
* actual values of properties in API format
* @type {object}
*/
widgetProperties: {},
/**
* @type {Array}
*/
widgetValues: [],
/**
* @type {Array}
*/
widgetMetrics: [],
/**
* @type {Array}
*/
expressions: [],
/**
* used only for GRAPH widget
* @type {Array}
*/
dataSets: [],
/**
* content of template of Template widget
* @type {string}
*/
templateValue: '',
/**
* views of properties
* @type {Array}
*/
widgetPropertiesViews: [],
/**
* @type {boolean}
*/
isEditWidget: Em.computed.equal('content.controllerName', 'widgetEditController'),
/**
* metrics filtered by type
* @type {Array}
*/
filteredMetrics: function () {
var type = this.get('content.widgetType');
return this.get('content.allMetrics').filter(function (metric) {
if (type === 'GRAPH') {
return metric.temporal;
} else {
return metric.point_in_time;
}
}, this);
}.property('content.allMetrics'),
/**
* @type {boolean}
*/
isSubmitDisabled: function() {
if (this.get('widgetPropertiesViews').someProperty('isValid', false)) {
return true;
}
switch (this.get('content.widgetType')) {
case "NUMBER":
case "GAUGE":
return !this.isExpressionComplete(this.get('expressions')[0]);
case "GRAPH":
return !this.isGraphDataComplete(this.get('dataSets'));
case "TEMPLATE":
return this.get('isTemplateInvalid') || !this.isTemplateDataComplete(this.get('expressions'), this.get('templateValue'));
}
return false;
}.property(
'widgetPropertiesViews.@each.isValid',
'dataSets.@each.label',
'templateValue', 'isTemplateInvalid'
),
/**
* check whether data of expression is complete
* @param {Em.Object} expression
* @returns {boolean}
*/
isExpressionComplete: function (expression) {
return Boolean(expression && !expression.get('isInvalid') && !expression.get('isEmpty')
&& expression.get('data')&& expression.get('data').someProperty('isMetric'));
},
/**
* check whether data of graph widget is complete
* @param dataSets
* @returns {boolean} isComplete
*/
isGraphDataComplete: function (dataSets) {
var isComplete = Boolean(dataSets.length);
for (var i = 0; i < dataSets.length; i++) {
if (dataSets[i].get('label').trim() === '' || !this.isExpressionComplete(dataSets[i].get('expression'))) {
isComplete = false;
break;
}
}
return isComplete;
},
/**
* check whether data of template widget is complete
* @param {Array} expressions
* @param {string} templateValue
* @returns {boolean} isComplete
*/
isTemplateDataComplete: function (expressions, templateValue) {
var isComplete = Boolean(expressions.length > 0 && templateValue.trim() !== '');
if (isComplete) {
for (var i = 0; i < expressions.length; i++) {
if (!this.isExpressionComplete(expressions[i])) {
isComplete = false;
break;
}
}
}
return isComplete;
},
/**
* Add data set
* @param {object|null} event
* @param {boolean} isDefault
* @returns {number} id
*/
addDataSet: function(event, isDefault) {
var id = (isDefault) ? 1 :(Math.max.apply(this, this.get('dataSets').mapProperty('id')) + 1);
this.get('dataSets').pushObject(Em.Object.create({
id: id,
label: Em.I18n.t('dashboard.widgets.wizard.step2.dataSeries').format(id),
isRemovable: !isDefault,
expression: Em.Object.create({
id: id,
data: [],
isInvalid: false,
isEmpty: Em.computed.equal('data.length', 0)
})
}));
return id;
},
/**
* Remove data set
* @param {object} event
*/
removeDataSet: function(event) {
this.get('dataSets').removeObject(event.context);
},
/**
* Add expression
* @param {object|null} event
* @param {boolean} isDefault
* @returns {number} id
*/
addExpression: function(event, isDefault) {
var id = (isDefault) ? 1 :(Math.max.apply(this, this.get('expressions').mapProperty('id')) + 1);
this.get('expressions').pushObject(Em.Object.create({
id: id,
isRemovable: !isDefault,
data: [],
alias: '{{' + this.get('EXPRESSION_PREFIX') + id + '}}',
isInvalid: false,
isEmpty: Em.computed.equal('data.length', 0)
}));
return id;
},
/**
* Remove expression
* @param {object} event
*/
removeExpression: function(event) {
this.get('expressions').removeObject(event.context);
},
/**
* initialize data
* widget should have at least one expression or dataSet
*/
initWidgetData: function() {
this.set('widgetProperties', this.get('content.widgetProperties'));
this.set('widgetValues', this.get('content.widgetValues'));
this.set('widgetMetrics', this.get('content.widgetMetrics'));
if (this.get('expressions.length') === 0) {
this.addExpression(null, true);
}
if (this.get('dataSets.length') === 0) {
this.addDataSet(null, true);
}
},
/**
* update preview widget with latest expression data
* Note: in order to draw widget it should be converted to API format of widget
*/
updateExpressions: function () {
var widgetType = this.get('content.widgetType');
var expressionData = {
values: [],
metrics: []
};
if (this.get('expressions').length > 0 && this.get('dataSets').length > 0) {
switch (widgetType) {
case 'GAUGE':
case 'NUMBER':
expressionData = this.parseExpression(this.get('expressions')[0]);
expressionData.values = [
{
name: "",
value: expressionData.value
}
];
break;
case 'TEMPLATE':
expressionData = this.parseTemplateExpression(this.get('templateValue'), this.get('expressions'));
break;
case 'GRAPH':
expressionData = this.parseGraphDataset(this.get('dataSets'));
break;
}
}
this.set('widgetValues', expressionData.values);
this.set('widgetMetrics', expressionData.metrics);
}.observes('templateValue', 'dataSets.@each.label'),
/**
* parse Graph data set
* @param {Array} dataSets
* @returns {{metrics: Array, values: Array}}
*/
parseGraphDataset: function (dataSets) {
var metrics = [];
var values = [];
dataSets.forEach(function (dataSet) {
var result = this.parseExpression(dataSet.get('expression'));
metrics.pushObjects(result.metrics);
values.push({
name: dataSet.get('label'),
value: result.value
});
}, this);
return {
metrics: metrics,
values: values
};
},
/**
* parse expression from template
* @param {string} templateValue
* @param {Array} expressions
* @returns {{metrics: Array, values: {value: *}[]}}
*/
parseTemplateExpression: function (templateValue, expressions) {
var metrics = [];
var self = this;
// check if there is invalid expression name eg. {{myExpre}}
var isTemplateInvalid = false;
var validExpressionName = /\{\{(Expression[\d])\}\}/g;
var expressionName = /\{\{((?!}}).)*\}\}/g;
if (templateValue) {
var expressionNames = templateValue.match(expressionName);
if (expressionNames) {
expressionNames.forEach(function(name) {
if (!name.match(validExpressionName)) {
isTemplateInvalid = true;
}
});
}
}
this.set('isTemplateInvalid', isTemplateInvalid);
var expression = templateValue.replace(/\{\{Expression[\d]\}\}/g, function (exp) {
var result;
if (expressions.someProperty('alias', exp)) {
result = self.parseExpression(expressions.findProperty('alias', exp));
metrics.pushObjects(result.metrics);
return result.value;
}
return exp;
});
return {
metrics: metrics,
values: [
{
value: expression
}
]
};
},
/**
*
* @param {object} expression
* @returns {{metrics: Array, value: string}}
*/
parseExpression: function (expression) {
var value = '';
var metrics = [];
if (expression.data.length > 0) {
value = '${';
expression.data.forEach(function (element) {
if (element.isMetric) {
var metricObj = {
"name": element.name,
"service_name": element.serviceName,
"component_name": element.componentName,
"metric_path": element.metricPath
};
if (element.hostComponentCriteria) {
metricObj.host_component_criteria = element.hostComponentCriteria;
}
metrics.push(metricObj);
}
value += element.name;
}, this);
value += '}';
}
return {
metrics: metrics,
value: value
};
},
/**
* update properties of preview widget
*/
updateProperties: function () {
var result = {};
this.get('widgetPropertiesViews').forEach(function (property) {
for (var key in property.valueMap) {
result[property.valueMap[key]] = property.get(key);
}
});
this.set('widgetProperties', result);
}.observes('widgetPropertiesViews.@each.value', 'widgetPropertiesViews.@each.bigValue', 'widgetPropertiesViews.@each.smallValue'),
/*
* Generate the thresholds, unit, time range.etc object based on the widget type selected in previous step.
*/
renderProperties: function () {
var widgetProperties = App.WidgetType.find(this.get('content.widgetType')).get('properties');
var propertiesData = this.get('widgetProperties');
var result = [];
widgetProperties.forEach(function (property) {
var definition = App.WidgetPropertyTypes.findProperty('name', property.name);
property = $.extend({}, property);
//restore previous values
for (var key in definition.valueMap) {
if (propertiesData[definition.valueMap[key]]) {
property[key] = propertiesData[definition.valueMap[key]];
}
}
result.pushObject(App.WidgetProperty.create($.extend(definition, property)));
});
this.set('widgetPropertiesViews', result);
},
/**
* convert data with model format to editable format
* Note: in order to edit widget expression it should be converted to editable format
*/
convertData: function() {
var self = this;
var expressionId = 0;
var widgetValues = this.get('content.widgetValues');
var widgetMetrics = this.get('content.widgetMetrics');
this.get('expressions').clear();
this.get('dataSets').clear();
if (widgetValues.length > 0) {
switch (this.get('content.widgetType')) {
case 'NUMBER':
case 'GAUGE':
var id = this.addExpression(null, true);
this.get('expressions').findProperty('id', id).set('data', this.parseValue(widgetValues[0].value, widgetMetrics)[0]);
break;
case 'TEMPLATE':
this.parseValue(widgetValues[0].value, widgetMetrics).forEach(function (item, index) {
var id = this.addExpression(null, (index === 0));
this.get('expressions').findProperty('id', id).set('data', item);
}, this);
this.set('templateValue', widgetValues[0].value.replace(this.get('EXPRESSION_REGEX'), function () {
return '{{' + self.get('EXPRESSION_PREFIX') + ++expressionId + '}}';
}));
break;
case 'GRAPH':
widgetValues.forEach(function (value, index) {
var id = this.addDataSet(null, (index === 0));
var dataSet = this.get('dataSets').findProperty('id', id);
dataSet.set('label', value.name);
dataSet.set('expression.data', this.parseValue(value.value, widgetMetrics)[0]);
}, this);
break;
}
}
},
/**
* parse value
* @param value
* @param metrics
* @returns {Array}
*/
parseValue: function(value, metrics) {
var pattern = this.get('EXPRESSION_REGEX'),
expressions = [],
match;
while (match = pattern.exec(value)) {
expressions.push(this.getExpressionData(match[1], metrics));
}
return expressions;
},
/**
* format values into expression data objects
* @param {string} expression
* @param {Array} metrics
* @returns {Array}
*/
getExpressionData: function(expression, metrics) {
var str = '';
var data = [];
var id = 0;
for (var i = 0, l = expression.length; i < l; i++) {
if (this.get('OPERATORS').contains(expression[i])) {
if (str.trim().length > 0) {
data.pushObject(this.getExpressionVariable(str.trim(), ++id, metrics));
str = '';
}
data.pushObject(Em.Object.create({
id: ++id,
name: expression[i],
isOperator: true
}));
} else {
str += expression[i];
}
}
if (str.trim().length > 0) {
data.pushObject(this.getExpressionVariable(str.trim(), ++id, metrics));
}
return data;
},
/**
* get variable of expression
* could be name of metric "m1" or constant number "1"
* @param {string} name
* @param {Array} metrics
* @param {number} id
* @returns {Em.Object}
*/
getExpressionVariable: function (name, id, metrics) {
var metric;
if (isNaN(Number(name))) {
metric = metrics.findProperty('name', name);
return Em.Object.create({
id: id,
name: metric.name,
isMetric: true,
componentName: metric.component_name,
serviceName: metric.service_name,
metricPath: metric.metric_path,
hostComponentCriteria: metric.host_component_criteria
});
} else {
return Em.Object.create({
id: id,
name: name,
isNumber: true
});
}
},
next: function () {
App.router.send('next');
}
});