<NME> dashboard.js
<BEF> 'use strict';
/*global angular:false, $:false */

angular.module('argus.services.dashboard', [])
        this.getDashboardById = function(dashboardId){
            return $http.get(CONFIG.wsUrl + 'dashboards/' + dashboardId);
        };
        
        this.populateView = function(metricList, annotationExpressionList, optionList, divId, attributes, elementType, scope){

            if(metricList && metricList.length>0 && divId) {
		};

		// TODO: refactor this duplicate code also in: viewMetrics.js $scope function
		// 'populateSeries' below makes same API call, refactor both to separate factories
		this.getMetricData = function(metricExpression) {
			if (!metricExpression) return;

			var metricData =
				$http({
					method: 'GET',
					url: CONFIG.wsUrl + 'metrics',
					params: {'expression': metricExpression}
				}).
				success(function(data) {
					if ( data && data.length > 0 ) {
						return data[0];
					} else {
						growl.info('No data found for the metric expression: ' + JSON.stringify(metricExpression));
						return;
					}
				}).
				error(function(data) {
					if (data) {
						growl.error(data.message);
					}
					return;
				});

			return metricData;
		};

		this.augmentExpressionWithControlsData = function(event, expression, controls) {
			var result = expression;

			for (var controlIndex in controls) {
				var controlName = controls[controlIndex].name;
        this.getMetricData = function(metricExpression) {
            if (!metricExpression) return;

            var metricData = 
                $http({
                    method: 'GET',
                    url: CONFIG.wsUrl + 'metrics',
						controlValue = controlValue.replace('GMT','').trim();
					}

					if(result.match(new RegExp('\\$' + controlName + '\\$', 'g')) !== null) {
						result = result.replace(new RegExp('\\$' + controlName + '\\$', 'g'), controlValue);
					}

					var match = null;
					//Check if it either matches something like $start-7h$ or $start-$diff$$
					if((match = result.match(new RegExp('\\$' + controlName + '\\-\\d+[smhd]\\$', 'g'))) != null) {
						controlValue = modifyControlValue(controlValue, match[0]);
						result = result.replace(new RegExp('\\$' + controlName + '\\-\\d+[smhd]\\$', 'g'), controlValue);
					} else if((match = result.match(new RegExp('\\$' + controlName + '\\-\\$.*\\$\\$', 'g'))) != null) {
						match = match[0].substring(1, match[0].length - 1);
						var subtractControlName = match.match(/\$.*\$/)[0];
						subtractControlName = subtractControlName.substring(1, subtractControlName.length - 1);
						var value = getControlValueFromName(controls, subtractControlName);

        this.augmentExpressionWithControlsData = function(event, expression, controls) {
            var result = expression;
                
            for (var controlIndex in controls) {
                var controlName = '\\$' + controls[controlIndex].name + '\\$';
                var controlValue = controls[controlIndex].value;
				}
			}

			result = result.replace(/(\r\n|\n|\r|\s+)/gm, '');
                controlValue = controlValue == undefined ? "" : controlValue;
                result = result.replace(new RegExp(controlName, "g"), controlValue);
            }
    
            result = result.replace(/(\r\n|\n|\r|\s+)/gm, "");
            return result;
        };
				}
        this.updateIndicatorStatus = updateIndicatorStatus;

        this.buildViewElement = buildViewElement;
        
        function populateChart(metricList, annotationExpressionList, optionList, divId, attributes, elementType, scope){
	         
        	var objMetricCount = {};
        	objMetricCount.totalCount = metricList.length;
        	
        	$('#' + divId).empty();
	        $('#' + divId).show();
            
            // 'smallChart' currently viewed in the 'Services Dashboard'
            var smallChart = attributes.smallchart ? true : false;
            var chartType = attributes.type ? attributes.type : 'LINE';
            var highChartOptions = getOptionsByChartType(CONFIG, chartType, smallChart);

	        setCustomOptions(highChartOptions, optionList);
	         
	        $('#' + divId).highcharts('StockChart', highChartOptions);
	         
	        var chart = $('#' + divId).highcharts('StockChart');
            
            // show loading spinner & hide 'no data message' during api request
            chart.showLoading();
            chart.hideNoData();
	       	 
	       	for (var i = 0; i < metricList.length; i++) {
                var metricExpression = metricList[i].expression;
                var metricOptions = metricList[i].metricSpecificOptions;

                populateSeries(metricList[i], highChartOptions, divId, attributes, annotationExpressionList, objMetricCount);
            }
	       	//populateAnnotations(annotationExpressionList, chart);
        };

			return secs;
		}


		this.buildViewElement = function(scope, element, attributes, dashboardCtrl, elementType, index, DashboardService) {
			var elementId = 'element_' + elementType + index;
			var smallChartCss = ( attributes.smallchart ) ? 'class="smallChart"' : '';
			element.prepend('<div id=' + elementId + ' ' + smallChartCss +'></div>');

			scope.$on(dashboardCtrl.getSubmitBtnEventName(), function(event, controls){
				// console.log(dashboardCtrl.getSubmitBtnEventName() + ' event received.');
            var elementId = 'element_' + elementType + index;
            var smallChartCss = ( attributes.smallchart ) ? 'class="smallChart"' : '';
            element.prepend('<div id=' + elementId + ' ' + smallChartCss +'></div>');
            
            scope.$on(dashboardCtrl.getSubmitBtnEventName(), function(event, controls){
                console.log(dashboardCtrl.getSubmitBtnEventName() + ' event received.');
                populateView(event, controls);
				var updatedMetricList = [];
				var updatedAnnotationList = [];
				var updatedOptionList = [];
				var processedExpression;

				// TODO: move these 3 items to 'utils' folder
				for (var key in scope.metrics) {
                // TODO: move these 3 items to 'utils' folder
                for (var key in scope.metrics) {
                    if (scope.metrics.hasOwnProperty(key)) {
                        
                        // get metricExpression, and name & color attributes from scope
                        var metrics = scope.metrics[key];
                        var metricExpression = metrics.expression;
						processedExpression = DashboardService.augmentExpressionWithControlsData(event, metricExpression, controls);

						if (processedExpression.length > 0 /* && (/\$/.test(processedExpression)==false) */) {
							var processedMetric = {};
							processedMetric['expression'] = processedExpression;
							processedMetric['name'] = metrics.name;
                            processedMetric['name'] = metrics.seriesName;
                            processedMetric['color'] = metrics.seriesColor;
                            processedMetric['metricSpecificOptions'] = getMetricSpecificOptionsInArray(metricSpecificOptions);
                            
                            // update metric list with new processed metric object
                            updatedMetricList.push(processedMetric);
                        }
				}

				for (var key in scope.annotations) {
					if (scope.annotations.hasOwnProperty(key)) {
						processedExpression = DashboardService.augmentExpressionWithControlsData(event, scope.annotations[key],controls);
						if (processedExpression.length > 0 /* && (/\$/.test(processedExpression)==false) */) {
							updatedAnnotationList.push(processedExpression);
						}
					}
				}

				for (var key in scope.options) {
					if (scope.options.hasOwnProperty(key)) {
						updatedOptionList.push({name: key, value: scope.options[key]});
					}
				}

				if (updatedMetricList.length > 0) {
					DashboardService.populateView(updatedMetricList, updatedAnnotationList, updatedOptionList, elementId, attributes, elementType, scope);
				}
			}

                    $('#' + elementId).hide();
                }
            }
            
            function getMetricSpecificOptionsInArray(metricSpecificOptions){
                var options = [];
                for (var key in metricSpecificOptions) {
				return options;
			}
		};

                return options;
            }
        };
        
        function populateSeries(metricItem, highChartOptions, divId, attributes, annotationExpressionList, objMetricCount) {
            var series = [];
            
            $http({
                method: 'GET',
                url: CONFIG.wsUrl + 'metrics',
                params: {'expression': metricItem.expression}
            }).success(function(data, status, headers, config){
                if (data && data.length > 0) {
                    
                    // check to update services dashboard
                    if (attributes.smallchart) {
                        // get last status values & broadcast to 'agStatusIndicator' directive
					method: 'GET',
					url: CONFIG.wsUrl + 'metrics',
                        // updateServiceStatus(attributes, lastStatusVal);
                        updateIndicatorStatus(attributes, lastStatusVal);
                    }
                    
                    var seriesWithOptions = copySeriesDataNSetOptions(data, metricItem.metricSpecificOptions);

                    Array.prototype.push.apply(series, seriesWithOptions);

                    // bind additional metric attribute(s) to series data for chart rendering
                    for (var i = 0; i < series.length > 0; i++) {
                        series[i].name = (metricItem && metricItem.name) ? metricItem.name : series.name;
                        series[i].color = (metricItem && metricItem.color) ? metricItem.color : '';
                    }
                } else{
                	growl.info('No data found for the metric expression: ' + JSON.stringify(metricItem.expression));
                }

                objMetricCount.totalCount = objMetricCount.totalCount - 1;
                 
                if (objMetricCount.totalCount == 0) {
            		bindDataToChart(divId, highChartOptions, series, annotationExpressionList);
            	}
            }).error(function(data, status, headers, config) {
            	growl.error(data.message);
            	objMetricCount.totalCount = objMetricCount.totalCount - 1;
            	
                if (objMetricCount.totalCount == 0) {
            	   bindDataToChart(divId, highChartOptions, series, annotationExpressionList);
            	}
            });
        };
        
        function bindDataToChart(divId, highChartOptions, series, annotationExpressionList) {
            // bind series data to highchart options
            highChartOptions.series = series;
            
            // display chart in DOM
            $('#' + divId).highcharts('StockChart', highChartOptions);
            
            var chart = $('#' + divId).highcharts('StockChart');
            
            // hide the loading spinner after data loads.
            if (chart) {
                chart.hideLoading();
            }
            
            // check if data exists, otherwise, show the 'no data' message.
            if ( chart && !chart.hasData() ) {
                chart.showNoData();
            }
    		
            populateAnnotations(annotationExpressionList, chart);
        };
       
        function getMetricExpressionList(metrics){
	       	var result = [];
	       	for(var i=0;i<metrics.length; i++){
				method: 'GET',
	       	}
	       	return result;
        };
        
        function updateTable(data, scope, divId, options) {
        	if(data && data.length > 0) {
        		
        		var allTimestamps = {};
        		for(var i in data) {
        			var dps = data[i].datapoints;
						// updateServiceStatus(attributes, lastStatusVal);
						updateIndicatorStatus(attributes, lastStatusVal);
					}
        				}
        			}
        		}
        		
        		var columns = [{title: "timestamp", value: "Timestamp"}];
        		for(var i in data) {
        			var dps = data[i].datapoints;
				} else{
					growl.info('No data found for the metric expression: ' + JSON.stringify(metricItem.expression));
        					title: "value" + i,
        					value: createSeriesName(data[i])
        				});
        				
        				for(var timestamp in allTimestamps) {
            				var values = allTimestamps[timestamp];
            				if(dps[timestamp]) {
			}).error(function(data) {
				growl.error(data.message);
				objMetricCount.totalCount = objMetricCount.totalCount - 1;

				if (objMetricCount.totalCount == 0) {
            			}
        			}
        		}
        		
        		var tData = [];
        		for(var timestamp in allTimestamps) {
        			var obj = {
        					timestamp: parseInt(timestamp),
        					date: $filter('date')(timestamp, "medium")
        			};
        			
        			for(var i in columns) {
        				if(columns[i].title !== "timestamp")
        					obj[columns[i].title] = allTimestamps[timestamp][i-1];
        			}
        			tData.push(obj);
        		}
        		
        		var tableConfig = {
        				itemsPerPage: 10,
        			    fillLastPage: true
        		};
        		
        		for(var i in options) {
        			var option = options[i];
        			if(option.name && option.value)
        				tableConfig[option.name] = option.value;
        		}
        		
        		
        		scope.tData = tData;
        		scope.config = tableConfig;
        		
        		var html = '<div style="overflow-x: scroll"><table class="table table-striped table-header-rotated" at-table at-paginated at-list="tData" at-config="config">';
        		
        		html += '<thead>';
        		html += '<tr>';
        		for(var i in columns) {
			for(var idx in optionList) {
        		}
        		html += '</tr>';
        		html += '</thead>';
        		
        		html += '<tbody>';
        		html += '<tr>';
        		
        		for(var i in columns) {
        			if(columns[i].title === 'timestamp')
        				html += '<td at-sortable at-attribute="' + columns[i].title + '">{{ item.date }}</td>';
        			else
        				html += '<td at-sortable at-attribute="' + columns[i].title + '">{{ item.' + columns[i].title + '}}</td>';
        		}
        		
        		html += '</tr>';
        		html += '</tbody>';
        		
        		html += '</table></div>';
        		
        		html += '<at-pagination at-list="tData" at-config="config"></at-pagination>';
        		
        		$("#" + divId).empty();
        		$compile($("#" + divId).prepend(html))(scope);
        	}
				chart.showNoData();

        function updateChart(config, data, divId, annotationExpressionList, optionList, attributes) {
            var chartType = attributes.type ? attributes.type : 'LINE';
            
            if (data && data.length > 0) {
                var options = getOptionsByChartType(config,chartType);
                options.series = copySeries(data);
		// 'dataProcessing'
		function populateAnnotations(annotationsList, chart){
			if (annotationsList && annotationsList.length>0 && chart) {
            } else {
                $('#' + divId).highcharts('StockChart', getOptionsByChartType(config, chartType));
            }
            
            var chart = $('#' + divId).highcharts('StockChart');
            //chart.chart={renderTo: 'container',defaultSeriesType: 'line'};
            //chart.renderTo='container';
            //chart.defaultSeriesType='line';
            
            populateAnnotations(annotationExpressionList, chart);
        };

					series.linkedTo = forName;

					for(var i=0;i<chart.series.length;i++){
						if(chart.series[i].name == forName){
							series.color = chart.series[i].color;
							break;
						}
					}

					chart.addSeries(series);
				}
			});
		}

		// 'dataProcessing'
		function getMetricExpressionList(metrics){
			var result = [];
			for(var i=0; i < metrics.length; i++){
				result.push(metrics[i].expression);
			}
			return result;
		}

            	type: 'datetime',
            	ordinal: false
            };
            
            options.lang = {
                loading: '',    // override default 'Loading...' msg from displaying under spinner img.
                noData: 'No Data to Display'
			for (var i = 0; i < data.length; i++) {
				for (var j = 0; j < table[0].length; j++) {
					var intValue = table[data.length - 1 - i][j] ? Math.floor(table[data.length - 1 - i][j]) : null;
					dataSeries.push([j, i, intValue]);
				}
			}
			return dataSeries;
		}

		// 'dataProcessing'
		function copySeries(data) {
			var result = [];
			if (data) {
				for (var i = 0; i < data.length; i++) {
					var series = [];
					for(var key in data[i].datapoints) {
						var timestamp = parseInt(key);
						if(data[i].datapoints[key] !=null){
							var value = parseFloat(data[i].datapoints[key]);
							series.push([timestamp, value]);
						}
					}
					result.push({name: createSeriesName(data[i]), data: series});
				}
			} else {
				result.push({name: 'result', data: []});
			}
			return result;
		}

		// 'dataProcessing'
		function createSeriesName(metric) {
			var scope = metric.scope;
			var name = metric.metric;
			var tags = createTagString(metric.tags);
			return scope + ':' + name + tags;
		}

                options.chart = {animation: false, borderWidth: 1, borderColor: 'lightGray', borderRadius: 5};
            }

            // override options for a 'small' chart, e.g. 'Services Status' dashboard 
            if ( smallChart ) {
                options.legend.enabled = false;
                options.rangeSelector.enabled = false;
						tagString += (key + '=' + tags[key] + ',');
					}
				}
				if(tagString.length) {
					result += '{';
					result += tagString.substring(0, tagString.length - 1);
					result += '}';
				}
			}
			return result;
		}

		// 'dataProcessing'
		function copyFlagSeries(data) {
			var result;
			if (data) {
				result = {type: 'flags', shape: 'circlepin', stackDistance: 20, width: 16, lineWidth: 2};
				result.data = [];
				for (var i = 0; i < data.length; i++) {
					var flagData = data[i];
					result.data.push({x: flagData.timestamp, title: 'A', text: formatFlagText(flagData.fields)});
				}
                data = data.slice(0, Math.min(top, data.length));
                var orgAxis = data.map(createSeriesName);
                var timeSpan = getTimeSpan(data);
                var timeAxis = getTimeAxis(timeSpan); 
                var dataSeries = copyHeatmapSeries(data, timeSpan); 
                options.series[0].data = dataSeries;
                options.xAxis.categories = timeAxis;
                options.yAxis.categories = orgAxis.reverse();
			var result = '';
			if (fields) {
				for (var field in fields) {
                $('#' + divId).highcharts('StockChart', getOptionsByChartType(config, 'LINE'));
            }
        };
        
        function getOptionsByHeatmapType(config, top){
            var options = config ? angular.copy(config) : {};
            options.credits = {enabled: false};

		// --------

		// 'chartRendering'
		function updateIndicatorStatus(attributes, lastStatusVal) {
			if (lastStatusVal < attributes.lo) {
				$('#' + attributes.name + '-status').removeClass('red orange green').addClass('red');
			} else if (lastStatusVal > attributes.lo && lastStatusVal < attributes.hi) {
				$('#' + attributes.name + '-status').removeClass('red orange green').addClass('orange');
			} else if (lastStatusVal > attributes.hi) {
				$('#' + attributes.name + '-status').removeClass('red orange green').addClass('green');
			}
		}

		// 'chartRendering'
		function updateChart(config, data, divId, annotationExpressionList, optionList, attributes) {
			var chartType = attributes.type ? attributes.type : 'LINE';

			if (data && data.length > 0) {
				var options = getOptionsByChartType(config,chartType);
				options.series = copySeries(data);
				//options.chart={renderTo: 'container',defaultSeriesType: 'line'};
				setCustomOptions(options,optionList);
				$('#' + divId).highcharts('StockChart', options);
			} else {
				$('#' + divId).highcharts('StockChart', getOptionsByChartType(config, chartType));
			}

			var chart = $('#' + divId).highcharts('StockChart');
			//chart.chart={renderTo: 'container',defaultSeriesType: 'line'};
			//chart.renderTo='container';
			//chart.defaultSeriesType='line';

			populateAnnotations(annotationExpressionList, chart);
		}

		// 'chartRendering'
		function updateTable(data, scope, divId, options) {
			if(data && data.length > 0) {

				var allTimestamps = {};
				var dps, timestamp;

				for(var i in data) {
            }];
            return options;
        };
        
        function compareAverage(a,b) {
            if (getAverage(a) < getAverage(b)) return 1;
            if (getAverage(a) > getAverage(b)) return -1;
            return 0;
        };
        
        function getTimeSpan(data) {
            var begin = 9999999999999;
            var end = 0;
							title: 'value' + i,
							value: createSeriesName(data[i])
						});

						for(timestamp in allTimestamps) {
							var values = allTimestamps[timestamp];
            var span = Math.floor(end/1000/60/60) - Math.floor(begin/1000/60/60) + 1;
            return {begin: begin, end: end, span: span};
        };
        
        function getTimeAxis(timeSpan) {
            var hours = [
                '12AM', '1AM', '2AM', '3AM', '4AM', '5AM', 
                '6AM', '7AM', '8AM', '9AM', '10AM', '11AM',
                '12PM', '1PM', '2PM', '3PM', '4PM', '5PM', 
                '6PM', '7PM', '8PM', '9PM', '10PM', '11PM'
            ];
            var axis = [];
					var obj = {
						timestamp: parseInt(timestamp),
						date: $filter('date')(timestamp, 'medium')
					};
            axis.push('<b><i>Average</i></b>');
            return axis;
        };
        
        function getAverage(data) {
            var total = 0;
            var count = 0;

				var tableConfig = {
					itemsPerPage: 10,
					fillLastPage: true
				};

            else
                return 0;
        };
        
        function getHourlyAverage(timeSpan, data) {
            var sums = Array.apply(null, Array(timeSpan.span)).map(Number.prototype.valueOf,0);
            var counts = Array.apply(null, Array(timeSpan.span)).map(Number.prototype.valueOf,0);
            var pivotHour = Math.floor(timeSpan.begin / 1000 / 60 / 60);
            for (var time in data.datapoints) {        
                var hour = Math.floor(parseInt(time) / 1000 / 60 / 60);
                sums[hour - pivotHour] += parseInt(data.datapoints[time]);
                counts[hour - pivotHour] += 1;
				html += '<thead>';
				html += '<tr>';
				for(var i in columns) {
					html += '<th class="rotate-45" at-attribute="' + columns[i].title + '"><div><span>' + columns[i].value + '</span></div></th>';
				}
            }
            return avgs;
        };
        
        function copyHeatmapSeries(data, timeSpan) {
            var table = data.map(getHourlyAverage.bind(null, timeSpan));
            for (var i = 0; i < data.length; i++) {
					if(columns[i].title === 'timestamp')
						html += '<td at-sortable at-attribute="' + columns[i].title + '">{{ item.date }}</td>';
					else
						html += '<td at-sortable at-attribute="' + columns[i].title + '">{{ item.' + columns[i].title + '}}</td>';
				}

				html += '</tr>';
				html += '</tbody>';

				html += '</table></div>';

				html += '<at-pagination at-list="tData" at-config="config"></at-pagination>';

				$('#' + divId).empty();
				$compile($('#' + divId).prepend(html))(scope);
			}
		}

		// 'chartRendering'
		function updateHeatmap(config, data, divId, optionList, attributes) {
			if(data && data.length>0) {
				var top = attributes.top? parseInt(attributes.top) : data.length;
				var options = getOptionsByHeatmapType(config, top);
				data.sort(compareAverage);
				data = data.slice(0, Math.min(top, data.length));
				var orgAxis = data.map(createSeriesName);
				var timeSpan = getTimeSpan(data);
				var timeAxis = getTimeAxis(timeSpan);
            }
            return result;
        };
        
        function copySeriesDataNSetOptions(data, metricOptions) {
            var result = [];
            if (data) {
                for (var i = 0; i < data.length; i++) {
                	var series = [];
                	for(var key in data[i].datapoints) {
                		var timestamp = parseInt(key);
                		if(data[i].datapoints[key] !=null){
                			var value = parseFloat(data[i].datapoints[key]);
                			series.push([timestamp, value]);
                		}
                	}
                	var objSeries = {name: createSeriesName(data[i]), data: series};
                	var objSeriesWithOptions=setCustomOptions(objSeries, metricOptions);
                    result.push(objSeriesWithOptions);
                }
            } else {
			options.xAxis = {
            }
            return result;
        };
        
        function createSeriesName(metric) {
            var scope = metric.scope;
            var name = metric.metric;
			};

			// loading spinner for graph
			options.loading = {
				labelStyle: {
					top: '25%',
					backgroundImage: 'url("img/ajax-loader.gif")',
					backgroundSize: '80px 80px',
					backgroundRepeat: 'no-repeat',
					display: 'inline-block',
					width: '80px',
					height: '80px',
					backgroundColor: '#FFF'
				}
			};

			if(chartType && chartType.toUpperCase() === 'AREA'){
				options.plotOptions = {series: {animation: false}};
				options.chart = {animation: false, borderWidth: 1, borderColor: 'lightGray', borderRadius: 5, type: 'area'};
			}else  if(chartType && chartType.toUpperCase() === 'STACKAREA'){
				options.plotOptions = {
					area: {
						stacking: 'normal',
						// lineWidth: 1.5,
						dataGrouping: {
							enabled: true//,
							// groupPixelWidth: 2
						},
						animation: false,
						marker: {
							enabled: false
						}
					}
				};
				options.chart = {animation: false, borderWidth: 1, borderColor: 'lightGray', borderRadius: 5, type: 'area'};
			}
			else {
				options.plotOptions = {series: {animation: false}};
				options.chart = {animation: false, borderWidth: 1, borderColor: 'lightGray', borderRadius: 5};
			}

			// override options for a 'small' chart, e.g. 'Services Status' dashboard
			if ( smallChart ) {
				options.legend.enabled = false;
				options.rangeSelector.enabled = false;

				options['scrollbar'] = {enabled: false};
				options['navigator'] = {enabled: false};

				options.chart.height = '120';
				options.chart.borderWidth = 0;

				// reset loading options, no spinner required
				options.lang = {
					loading: 'Loading...'
				};
				options.loading = {};
			}

			return options;
		}

		function getOptionsByHeatmapType(config, top){
			var options = config ? angular.copy(config) : {};
			options.credits = {enabled: false};
			options.chart = {
				type: 'heatmap',
				marginTop: 0,
				marginBottom: 60,
				height: 40 * top
			};
			options.title = {text: ''};
			options.xAxis = {
				categories: null
			};
			options.yAxis = {
				categories: null,
				title: null,
				labels: {
				}
			};
			options.colorAxis = {
				dataClasses: [{
					from: 0,
					to: 300,
					color: '#00FF00'
				},{
					from:300,
					to:400,
					color:'#FF8000'
				},{
					from:400,
					color:'#FF0040'
				}]
			};
			options.legend = {enabled: true};
			options.tooltip = {enabled: false};
			options.series = [{
				name: '',
				borderWidth: 1,
				data: null,
				dataLabels: {
					enabled: true,
					color: 'black',
					style: {
						textShadow: 'none',
						HcTextStroke: null
					}
                return result;
            }
        };
        
        function getParsedValue(value){
        	
        	if(value instanceof Object || value.length==0){
        		return value;
        	}
				'6AM', '7AM', '8AM', '9AM', '10AM', '11AM',
				'12PM', '1PM', '2PM', '3PM', '4PM', '5PM',
				'6PM', '7PM', '8PM', '9PM', '10PM', '11PM'
			];
			var axis = [];
			var firstHour = (new Date(timeSpan.begin)).getHours();
			for (var i = 0; i < timeSpan.span; i++) {
				axis.push(hours[(firstHour + i) % 24]);
			}
			axis.push('<b><i>Average</i></b>');
			return axis;
		}

		// --------

		// 'utilService'

		function compareAverage(a,b) {
			if (getAverage(a) < getAverage(b)) return 1;
			if (getAverage(a) > getAverage(b)) return -1;
			return 0;
		}

		function getTimeSpan(data) {
			var begin = 9999999999999;
			var end = 0;
			for (var i = 0; i < data.length; i++) {
				for (var time in data[i].datapoints) {
					begin = Math.min(begin, parseInt(time));
					end = Math.max(end, parseInt(time));
				}
			}
			var span = Math.floor(end/1000/60/60) - Math.floor(begin/1000/60/60) + 1;
			return {begin: begin, end: end, span: span};
		}

		function getAverage(data) {
			var total = 0;
			var count = 0;
			for (var time in data.datapoints) {
				total += parseInt(data.datapoints[time]);
				count += 1;
			}
			if (count > 0)
				return total / count;
			else
				return 0;
		}

		function getHourlyAverage(timeSpan, data) {
			var sums = Array.apply(null, Array(timeSpan.span)).map(Number.prototype.valueOf,0);
			var counts = Array.apply(null, Array(timeSpan.span)).map(Number.prototype.valueOf,0);
			var pivotHour = Math.floor(timeSpan.begin / 1000 / 60 / 60);
			for (var time in data.datapoints) {
				var hour = Math.floor(parseInt(time) / 1000 / 60 / 60);
				sums[hour - pivotHour] += parseInt(data.datapoints[time]);
				counts[hour - pivotHour] += 1;
			}
			var avgs = [];
			for (var i = 0; i < timeSpan.span; i++) {
				if (counts[i] > 0) avgs.push(sums[i] / counts[i]);
				else avgs.push(null);
			}
			return avgs;
		}

		function copyProperties(from, to){
			for (var key in from) {
				if (from.hasOwnProperty(key)) {
					if(!to[key] || typeof from[key] == 'string' || from[key] instanceof String ){//if from[key] is not an object and is last property then just copy so that it will overwrite the existing value
						to[key]=from[key];
					}else{
						copyProperties(from[key],to[key]);
					}
				}
			}
		}

		function constructObjectTree(name, value) {
			var result = {};
			var index = name.indexOf('.');
			if (index == -1) {
				result[name] = getParsedValue(value);
				return result;
			} else {
				var property = name.substring(0, index);
				result[property] = constructObjectTree(name.substring(index + 1), value);
				return result;
			}
		}

		function getParsedValue(value){

			if(value instanceof Object || value.length==0){
				return value;
			}

			if(value=='true'){
				return true;
			}else if(value=='false'){
				return false;
			}else if(!isNaN(value)){
				return parseInt(value);
			}
			return value;
		}
	}]);

<MSG> bug fix: render series name & color attributes for multiple ag-metric tags or none.

<DFF> @@ -5,7 +5,7 @@ angular.module('argus.services.dashboard', [])
         this.getDashboardById = function(dashboardId){
             return $http.get(CONFIG.wsUrl + 'dashboards/' + dashboardId);
         };
-        
+
         this.populateView = function(metricList, annotationExpressionList, optionList, divId, attributes, elementType, scope){
 
             if(metricList && metricList.length>0 && divId) {
@@ -48,7 +48,7 @@ angular.module('argus.services.dashboard', [])
         this.getMetricData = function(metricExpression) {
             if (!metricExpression) return;
 
-            var metricData = 
+            var metricData =
                 $http({
                     method: 'GET',
                     url: CONFIG.wsUrl + 'metrics',
@@ -72,7 +72,7 @@ angular.module('argus.services.dashboard', [])
 
         this.augmentExpressionWithControlsData = function(event, expression, controls) {
             var result = expression;
-                
+
             for (var controlIndex in controls) {
                 var controlName = '\\$' + controls[controlIndex].name + '\\$';
                 var controlValue = controls[controlIndex].value;
@@ -83,7 +83,7 @@ angular.module('argus.services.dashboard', [])
                 controlValue = controlValue == undefined ? "" : controlValue;
                 result = result.replace(new RegExp(controlName, "g"), controlValue);
             }
-    
+
             result = result.replace(/(\r\n|\n|\r|\s+)/gm, "");
             return result;
         };
@@ -91,35 +91,39 @@ angular.module('argus.services.dashboard', [])
         this.updateIndicatorStatus = updateIndicatorStatus;
 
         this.buildViewElement = buildViewElement;
-        
+
         function populateChart(metricList, annotationExpressionList, optionList, divId, attributes, elementType, scope){
-	         
+
         	var objMetricCount = {};
         	objMetricCount.totalCount = metricList.length;
-        	
+
         	$('#' + divId).empty();
 	        $('#' + divId).show();
-            
+
             // 'smallChart' currently viewed in the 'Services Dashboard'
             var smallChart = attributes.smallchart ? true : false;
             var chartType = attributes.type ? attributes.type : 'LINE';
             var highChartOptions = getOptionsByChartType(CONFIG, chartType, smallChart);
 
 	        setCustomOptions(highChartOptions, optionList);
-	         
+
 	        $('#' + divId).highcharts('StockChart', highChartOptions);
-	         
+
 	        var chart = $('#' + divId).highcharts('StockChart');
-            
+
             // show loading spinner & hide 'no data message' during api request
             chart.showLoading();
             chart.hideNoData();
-	       	 
+
+            // define series first; then build list for each metric expression
+            var series = [];
+
 	       	for (var i = 0; i < metricList.length; i++) {
                 var metricExpression = metricList[i].expression;
                 var metricOptions = metricList[i].metricSpecificOptions;
 
-                populateSeries(metricList[i], highChartOptions, divId, attributes, annotationExpressionList, objMetricCount);
+                // make api call to get data for each metric item
+                populateSeries(metricList[i], highChartOptions, series, divId, attributes, annotationExpressionList, objMetricCount);
             }
 	       	//populateAnnotations(annotationExpressionList, chart);
         };
@@ -138,7 +142,7 @@ angular.module('argus.services.dashboard', [])
             var elementId = 'element_' + elementType + index;
             var smallChartCss = ( attributes.smallchart ) ? 'class="smallChart"' : '';
             element.prepend('<div id=' + elementId + ' ' + smallChartCss +'></div>');
-            
+
             scope.$on(dashboardCtrl.getSubmitBtnEventName(), function(event, controls){
                 console.log(dashboardCtrl.getSubmitBtnEventName() + ' event received.');
                 populateView(event, controls);
@@ -152,7 +156,7 @@ angular.module('argus.services.dashboard', [])
                 // TODO: move these 3 items to 'utils' folder
                 for (var key in scope.metrics) {
                     if (scope.metrics.hasOwnProperty(key)) {
-                        
+
                         // get metricExpression, and name & color attributes from scope
                         var metrics = scope.metrics[key];
                         var metricExpression = metrics.expression;
@@ -165,7 +169,7 @@ angular.module('argus.services.dashboard', [])
                             processedMetric['name'] = metrics.seriesName;
                             processedMetric['color'] = metrics.seriesColor;
                             processedMetric['metricSpecificOptions'] = getMetricSpecificOptionsInArray(metricSpecificOptions);
-                            
+
                             // update metric list with new processed metric object
                             updatedMetricList.push(processedMetric);
                         }
@@ -194,7 +198,7 @@ angular.module('argus.services.dashboard', [])
                     $('#' + elementId).hide();
                 }
             }
-            
+
             function getMetricSpecificOptionsInArray(metricSpecificOptions){
                 var options = [];
                 for (var key in metricSpecificOptions) {
@@ -205,17 +209,15 @@ angular.module('argus.services.dashboard', [])
                 return options;
             }
         };
-        
-        function populateSeries(metricItem, highChartOptions, divId, attributes, annotationExpressionList, objMetricCount) {
-            var series = [];
-            
+
+        function populateSeries(metricItem, highChartOptions, series, divId, attributes, annotationExpressionList, objMetricCount) {
             $http({
                 method: 'GET',
                 url: CONFIG.wsUrl + 'metrics',
                 params: {'expression': metricItem.expression}
             }).success(function(data, status, headers, config){
                 if (data && data.length > 0) {
-                    
+
                     // check to update services dashboard
                     if (attributes.smallchart) {
                         // get last status values & broadcast to 'agStatusIndicator' directive
@@ -224,57 +226,54 @@ angular.module('argus.services.dashboard', [])
                         // updateServiceStatus(attributes, lastStatusVal);
                         updateIndicatorStatus(attributes, lastStatusVal);
                     }
-                    
-                    var seriesWithOptions = copySeriesDataNSetOptions(data, metricItem.metricSpecificOptions);
 
+                    // metric item attributes are assigned to the data (i.e. name, color, etc.)
+                    var seriesWithOptions = copySeriesDataNSetOptions(data, metricItem);
+
+                    // add each metric item & data to series list
                     Array.prototype.push.apply(series, seriesWithOptions);
 
-                    // bind additional metric attribute(s) to series data for chart rendering
-                    for (var i = 0; i < series.length > 0; i++) {
-                        series[i].name = (metricItem && metricItem.name) ? metricItem.name : series.name;
-                        series[i].color = (metricItem && metricItem.color) ? metricItem.color : '';
-                    }
                 } else{
                 	growl.info('No data found for the metric expression: ' + JSON.stringify(metricItem.expression));
                 }
 
                 objMetricCount.totalCount = objMetricCount.totalCount - 1;
-                 
+
                 if (objMetricCount.totalCount == 0) {
             		bindDataToChart(divId, highChartOptions, series, annotationExpressionList);
             	}
             }).error(function(data, status, headers, config) {
             	growl.error(data.message);
             	objMetricCount.totalCount = objMetricCount.totalCount - 1;
-            	
+
                 if (objMetricCount.totalCount == 0) {
             	   bindDataToChart(divId, highChartOptions, series, annotationExpressionList);
             	}
             });
         };
-        
+
         function bindDataToChart(divId, highChartOptions, series, annotationExpressionList) {
             // bind series data to highchart options
             highChartOptions.series = series;
-            
+
             // display chart in DOM
             $('#' + divId).highcharts('StockChart', highChartOptions);
-            
+
             var chart = $('#' + divId).highcharts('StockChart');
-            
+
             // hide the loading spinner after data loads.
             if (chart) {
                 chart.hideLoading();
             }
-            
+
             // check if data exists, otherwise, show the 'no data' message.
             if ( chart && !chart.hasData() ) {
                 chart.showNoData();
             }
-    		
+
             populateAnnotations(annotationExpressionList, chart);
         };
-       
+
         function getMetricExpressionList(metrics){
 	       	var result = [];
 	       	for(var i=0;i<metrics.length; i++){
@@ -282,10 +281,10 @@ angular.module('argus.services.dashboard', [])
 	       	}
 	       	return result;
         };
-        
+
         function updateTable(data, scope, divId, options) {
         	if(data && data.length > 0) {
-        		
+
         		var allTimestamps = {};
         		for(var i in data) {
         			var dps = data[i].datapoints;
@@ -295,7 +294,7 @@ angular.module('argus.services.dashboard', [])
         				}
         			}
         		}
-        		
+
         		var columns = [{title: "timestamp", value: "Timestamp"}];
         		for(var i in data) {
         			var dps = data[i].datapoints;
@@ -304,7 +303,7 @@ angular.module('argus.services.dashboard', [])
         					title: "value" + i,
         					value: createSeriesName(data[i])
         				});
-        				
+
         				for(var timestamp in allTimestamps) {
             				var values = allTimestamps[timestamp];
             				if(dps[timestamp]) {
@@ -316,38 +315,38 @@ angular.module('argus.services.dashboard', [])
             			}
         			}
         		}
-        		
+
         		var tData = [];
         		for(var timestamp in allTimestamps) {
         			var obj = {
         					timestamp: parseInt(timestamp),
         					date: $filter('date')(timestamp, "medium")
         			};
-        			
+
         			for(var i in columns) {
         				if(columns[i].title !== "timestamp")
         					obj[columns[i].title] = allTimestamps[timestamp][i-1];
         			}
         			tData.push(obj);
         		}
-        		
+
         		var tableConfig = {
         				itemsPerPage: 10,
         			    fillLastPage: true
         		};
-        		
+
         		for(var i in options) {
         			var option = options[i];
         			if(option.name && option.value)
         				tableConfig[option.name] = option.value;
         		}
-        		
-        		
+
+
         		scope.tData = tData;
         		scope.config = tableConfig;
-        		
+
         		var html = '<div style="overflow-x: scroll"><table class="table table-striped table-header-rotated" at-table at-paginated at-list="tData" at-config="config">';
-        		
+
         		html += '<thead>';
         		html += '<tr>';
         		for(var i in columns) {
@@ -355,24 +354,24 @@ angular.module('argus.services.dashboard', [])
         		}
         		html += '</tr>';
         		html += '</thead>';
-        		
+
         		html += '<tbody>';
         		html += '<tr>';
-        		
+
         		for(var i in columns) {
         			if(columns[i].title === 'timestamp')
         				html += '<td at-sortable at-attribute="' + columns[i].title + '">{{ item.date }}</td>';
         			else
         				html += '<td at-sortable at-attribute="' + columns[i].title + '">{{ item.' + columns[i].title + '}}</td>';
         		}
-        		
+
         		html += '</tr>';
         		html += '</tbody>';
-        		
+
         		html += '</table></div>';
-        		
+
         		html += '<at-pagination at-list="tData" at-config="config"></at-pagination>';
-        		
+
         		$("#" + divId).empty();
         		$compile($("#" + divId).prepend(html))(scope);
         	}
@@ -380,7 +379,7 @@ angular.module('argus.services.dashboard', [])
 
         function updateChart(config, data, divId, annotationExpressionList, optionList, attributes) {
             var chartType = attributes.type ? attributes.type : 'LINE';
-            
+
             if (data && data.length > 0) {
                 var options = getOptionsByChartType(config,chartType);
                 options.series = copySeries(data);
@@ -390,12 +389,12 @@ angular.module('argus.services.dashboard', [])
             } else {
                 $('#' + divId).highcharts('StockChart', getOptionsByChartType(config, chartType));
             }
-            
+
             var chart = $('#' + divId).highcharts('StockChart');
             //chart.chart={renderTo: 'container',defaultSeriesType: 'line'};
             //chart.renderTo='container';
             //chart.defaultSeriesType='line';
-            
+
             populateAnnotations(annotationExpressionList, chart);
         };
 
@@ -425,7 +424,7 @@ angular.module('argus.services.dashboard', [])
             	type: 'datetime',
             	ordinal: false
             };
-            
+
             options.lang = {
                 loading: '',    // override default 'Loading...' msg from displaying under spinner img.
                 noData: 'No Data to Display'
@@ -470,7 +469,7 @@ angular.module('argus.services.dashboard', [])
                 options.chart = {animation: false, borderWidth: 1, borderColor: 'lightGray', borderRadius: 5};
             }
 
-            // override options for a 'small' chart, e.g. 'Services Status' dashboard 
+            // override options for a 'small' chart, e.g. 'Services Status' dashboard
             if ( smallChart ) {
                 options.legend.enabled = false;
                 options.rangeSelector.enabled = false;
@@ -499,8 +498,8 @@ angular.module('argus.services.dashboard', [])
                 data = data.slice(0, Math.min(top, data.length));
                 var orgAxis = data.map(createSeriesName);
                 var timeSpan = getTimeSpan(data);
-                var timeAxis = getTimeAxis(timeSpan); 
-                var dataSeries = copyHeatmapSeries(data, timeSpan); 
+                var timeAxis = getTimeAxis(timeSpan);
+                var dataSeries = copyHeatmapSeries(data, timeSpan);
                 options.series[0].data = dataSeries;
                 options.xAxis.categories = timeAxis;
                 options.yAxis.categories = orgAxis.reverse();
@@ -510,7 +509,7 @@ angular.module('argus.services.dashboard', [])
                 $('#' + divId).highcharts('StockChart', getOptionsByChartType(config, 'LINE'));
             }
         };
-        
+
         function getOptionsByHeatmapType(config, top){
             var options = config ? angular.copy(config) : {};
             options.credits = {enabled: false};
@@ -561,13 +560,13 @@ angular.module('argus.services.dashboard', [])
             }];
             return options;
         };
-        
+
         function compareAverage(a,b) {
             if (getAverage(a) < getAverage(b)) return 1;
             if (getAverage(a) > getAverage(b)) return -1;
             return 0;
         };
-        
+
         function getTimeSpan(data) {
             var begin = 9999999999999;
             var end = 0;
@@ -580,12 +579,12 @@ angular.module('argus.services.dashboard', [])
             var span = Math.floor(end/1000/60/60) - Math.floor(begin/1000/60/60) + 1;
             return {begin: begin, end: end, span: span};
         };
-        
+
         function getTimeAxis(timeSpan) {
             var hours = [
-                '12AM', '1AM', '2AM', '3AM', '4AM', '5AM', 
+                '12AM', '1AM', '2AM', '3AM', '4AM', '5AM',
                 '6AM', '7AM', '8AM', '9AM', '10AM', '11AM',
-                '12PM', '1PM', '2PM', '3PM', '4PM', '5PM', 
+                '12PM', '1PM', '2PM', '3PM', '4PM', '5PM',
                 '6PM', '7PM', '8PM', '9PM', '10PM', '11PM'
             ];
             var axis = [];
@@ -596,7 +595,7 @@ angular.module('argus.services.dashboard', [])
             axis.push('<b><i>Average</i></b>');
             return axis;
         };
-        
+
         function getAverage(data) {
             var total = 0;
             var count = 0;
@@ -609,12 +608,12 @@ angular.module('argus.services.dashboard', [])
             else
                 return 0;
         };
-        
+
         function getHourlyAverage(timeSpan, data) {
             var sums = Array.apply(null, Array(timeSpan.span)).map(Number.prototype.valueOf,0);
             var counts = Array.apply(null, Array(timeSpan.span)).map(Number.prototype.valueOf,0);
             var pivotHour = Math.floor(timeSpan.begin / 1000 / 60 / 60);
-            for (var time in data.datapoints) {        
+            for (var time in data.datapoints) {
                 var hour = Math.floor(parseInt(time) / 1000 / 60 / 60);
                 sums[hour - pivotHour] += parseInt(data.datapoints[time]);
                 counts[hour - pivotHour] += 1;
@@ -626,7 +625,7 @@ angular.module('argus.services.dashboard', [])
             }
             return avgs;
         };
-        
+
         function copyHeatmapSeries(data, timeSpan) {
             var table = data.map(getHourlyAverage.bind(null, timeSpan));
             for (var i = 0; i < data.length; i++) {
@@ -661,21 +660,30 @@ angular.module('argus.services.dashboard', [])
             }
             return result;
         };
-        
-        function copySeriesDataNSetOptions(data, metricOptions) {
+
+        function copySeriesDataNSetOptions(data, metricItem) {
             var result = [];
             if (data) {
                 for (var i = 0; i < data.length; i++) {
                 	var series = [];
-                	for(var key in data[i].datapoints) {
+
+                    for (var key in data[i].datapoints) {
                 		var timestamp = parseInt(key);
-                		if(data[i].datapoints[key] !=null){
+                		if (data[i].datapoints[key] != null) {
                 			var value = parseFloat(data[i].datapoints[key]);
                 			series.push([timestamp, value]);
                 		}
                 	}
-                	var objSeries = {name: createSeriesName(data[i]), data: series};
-                	var objSeriesWithOptions=setCustomOptions(objSeries, metricOptions);
+
+                    var metricName = (metricItem.name) ? metricItem.name : createSeriesName(data[i]);
+                    var metricColor = (metricItem.color) ? metricItem.color : null;
+                    var objSeries = {
+                        name: metricName,
+                        color: metricColor,
+                        data: series
+                    };
+                    var objSeriesWithOptions = setCustomOptions(objSeries, metricItem.metricSpecificOptions);
+
                     result.push(objSeriesWithOptions);
                 }
             } else {
@@ -683,7 +691,7 @@ angular.module('argus.services.dashboard', [])
             }
             return result;
         };
-        
+
         function createSeriesName(metric) {
             var scope = metric.scope;
             var name = metric.metric;
@@ -798,9 +806,9 @@ angular.module('argus.services.dashboard', [])
                 return result;
             }
         };
-        
+
         function getParsedValue(value){
-        	
+
         	if(value instanceof Object || value.length==0){
         		return value;
         	}

{'addition_count': 90, 'commit_subject': 'bug fix: render series name & color attributes for multiple ag-metric tags or none.', 'deletion_count': 82, 'file_extension': '.js', 'lang': 'js', 'license': 'bsd-3-clause', 'repo_name': 'salesforce/Argus'}