mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'elastic_ds'
Conflicts: public/app/plugins/datasource/influxdb/queryCtrl.js
This commit is contained in:
@@ -45,6 +45,7 @@ require.config({
|
||||
modernizr: '../vendor/modernizr-2.6.1',
|
||||
|
||||
'bootstrap-tagsinput': '../vendor/tagsinput/bootstrap-tagsinput',
|
||||
'aws-sdk': '../vendor/aws-sdk/dist/aws-sdk.min',
|
||||
},
|
||||
shim: {
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ function (angular, app, _, $) {
|
||||
return {
|
||||
scope: {
|
||||
segment: "=",
|
||||
getAltSegments: "&",
|
||||
onValueChanged: "&"
|
||||
getOptions: "&",
|
||||
onChange: "&",
|
||||
},
|
||||
|
||||
link: function($scope, elem) {
|
||||
@@ -47,13 +47,14 @@ function (angular, app, _, $) {
|
||||
segment.fake = false;
|
||||
segment.expandable = selected.expandable;
|
||||
}
|
||||
else {
|
||||
else if (segment.custom !== 'false') {
|
||||
segment.value = value;
|
||||
segment.html = $sce.trustAsHtml(value);
|
||||
segment.expandable = true;
|
||||
segment.fake = false;
|
||||
}
|
||||
$scope.onValueChanged();
|
||||
|
||||
$scope.onChange();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -76,13 +77,15 @@ function (angular, app, _, $) {
|
||||
if (options) { return options; }
|
||||
|
||||
$scope.$apply(function() {
|
||||
$scope.getAltSegments().then(function(altSegments) {
|
||||
$scope.getOptions().then(function(altSegments) {
|
||||
$scope.altSegments = altSegments;
|
||||
options = _.map($scope.altSegments, function(alt) { return alt.value; });
|
||||
|
||||
// add custom values
|
||||
if (!segment.fake && _.indexOf(options, segment.value) === -1) {
|
||||
options.unshift(segment.value);
|
||||
if (segment.custom !== 'false') {
|
||||
if (!segment.fake && _.indexOf(options, segment.value) === -1) {
|
||||
options.unshift(segment.value);
|
||||
}
|
||||
}
|
||||
|
||||
callback(options);
|
||||
@@ -92,7 +95,6 @@ function (angular, app, _, $) {
|
||||
|
||||
$scope.updater = function(value) {
|
||||
if (value === segment.value) {
|
||||
console.log('cancel blur');
|
||||
clearTimeout(cancelBlur);
|
||||
$input.focus();
|
||||
return value;
|
||||
@@ -153,4 +155,63 @@ function (angular, app, _, $) {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
angular
|
||||
.module('grafana.directives')
|
||||
.directive('metricSegmentModel', function(uiSegmentSrv, $q) {
|
||||
return {
|
||||
template: '<metric-segment segment="segment" get-options="getOptionsInternal()" on-change="onSegmentChange()"></metric-segment>',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
property: "=",
|
||||
options: "=",
|
||||
getOptions: "&",
|
||||
onChange: "&",
|
||||
},
|
||||
link: {
|
||||
pre: function postLink($scope, elem, attrs) {
|
||||
|
||||
$scope.valueToSegment = function(value) {
|
||||
var option = _.findWhere($scope.options, {value: value});
|
||||
var segment = {
|
||||
cssClass: attrs.cssClass,
|
||||
custom: attrs.custom,
|
||||
value: option ? option.text : value,
|
||||
};
|
||||
return uiSegmentSrv.newSegment(segment);
|
||||
};
|
||||
|
||||
$scope.getOptionsInternal = function() {
|
||||
if ($scope.options) {
|
||||
var optionSegments = _.map($scope.options, function(option) {
|
||||
return uiSegmentSrv.newSegment({value: option.text});
|
||||
});
|
||||
return $q.when(optionSegments);
|
||||
} else {
|
||||
return $scope.getOptions();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.onSegmentChange = function() {
|
||||
if ($scope.options) {
|
||||
var option = _.findWhere($scope.options, {text: $scope.segment.value});
|
||||
if (option && option.value !== $scope.property) {
|
||||
$scope.property = option.value;
|
||||
}
|
||||
} else {
|
||||
$scope.property = $scope.segment.value;
|
||||
}
|
||||
|
||||
// needs to call this after digest so
|
||||
// property is synced with outerscope
|
||||
$scope.$$postDigest(function() {
|
||||
$scope.onChange();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.segment = $scope.valueToSegment($scope.property);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ function (angular, kbn) {
|
||||
link: function(scope, elem, attrs) {
|
||||
var _t = '<i class="grafana-tip fa fa-'+(attrs.icon||'question-circle')+'" bs-tooltip="\''+
|
||||
kbn.addslashes(elem.text())+'\'"></i>';
|
||||
_t = _t.replace(/{/g, '\\{').replace(/}/g, '\\}');
|
||||
elem.replaceWith($compile(angular.element(_t))(scope));
|
||||
}
|
||||
};
|
||||
@@ -62,15 +63,16 @@ function (angular, kbn) {
|
||||
restrict: 'E',
|
||||
link: function(scope, elem, attrs) {
|
||||
var text = $interpolate(attrs.text)(scope);
|
||||
var model = $interpolate(attrs.model)(scope);
|
||||
var ngchange = attrs.change ? (' ng-change="' + attrs.change + '"') : '';
|
||||
var tip = attrs.tip ? (' <tip>' + attrs.tip + '</tip>') : '';
|
||||
var label = '<label for="' + scope.$id + attrs.model + '" class="checkbox-label">' +
|
||||
var label = '<label for="' + scope.$id + model + '" class="checkbox-label">' +
|
||||
text + tip + '</label>';
|
||||
|
||||
var template = '<input class="cr1" id="' + scope.$id + attrs.model + '" type="checkbox" ' +
|
||||
' ng-model="' + attrs.model + '"' + ngchange +
|
||||
' ng-checked="' + attrs.model + '"></input>' +
|
||||
' <label for="' + scope.$id + attrs.model + '" class="cr1"></label>';
|
||||
var template = '<input class="cr1" id="' + scope.$id + model + '" type="checkbox" ' +
|
||||
' ng-model="' + model + '"' + ngchange +
|
||||
' ng-checked="' + model + '"></input>' +
|
||||
' <label for="' + scope.$id + model + '" class="cr1"></label>';
|
||||
|
||||
template = label + template;
|
||||
elem.replaceWith($compile(angular.element(template))(scope));
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<div class="editor-row">
|
||||
<div class="tight-form-section">
|
||||
<h5>Toggles</h5>
|
||||
<div class="tight-form">
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item">
|
||||
<editor-checkbox text="Editable" model="dashboard.editable"></editor-checkbox>
|
||||
@@ -65,7 +65,7 @@
|
||||
<li class="tight-form-item">
|
||||
<editor-checkbox text="Hide Controls (CTRL+H)" model="dashboard.hideControls"></editor-checkbox>
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
<li class="tight-form-item last">
|
||||
<editor-checkbox text="Shared Crosshair (CTRL+O)" model="dashboard.sharedCrosshair"></editor-checkbox>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
define([
|
||||
'angular',
|
||||
'config',
|
||||
'lodash',
|
||||
],
|
||||
function (angular, config) {
|
||||
function (angular, config, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
@@ -12,12 +13,16 @@ function (angular, config) {
|
||||
|
||||
$scope.httpConfigPartialSrc = 'app/features/org/partials/datasourceHttpConfig.html';
|
||||
|
||||
var defaults = {
|
||||
name: '',
|
||||
type: 'graphite',
|
||||
url: '',
|
||||
access: 'proxy'
|
||||
};
|
||||
var defaults = {name: '', type: 'graphite', url: '', access: 'proxy' };
|
||||
|
||||
$scope.indexPatternTypes = [
|
||||
{name: 'No pattern', value: undefined},
|
||||
{name: 'Hourly', value: 'Hourly', example: '[logstash-]YYYY.MM.DD.HH'},
|
||||
{name: 'Daily', value: 'Daily', example: '[logstash-]YYYY.MM.DD'},
|
||||
{name: 'Weekly', value: 'Weekly', example: '[logstash-]GGGG.WW'},
|
||||
{name: 'Monthly', value: 'Monthly', example: '[logstash-]YYYY.MM'},
|
||||
{name: 'Yearly', value: 'Yearly', example: '[logstash-]YYYY'},
|
||||
];
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.isNew = true;
|
||||
@@ -117,6 +122,11 @@ function (angular, config) {
|
||||
}
|
||||
};
|
||||
|
||||
$scope.indexPatternTypeChanged = function() {
|
||||
var def = _.findWhere($scope.indexPatternTypes, {value: $scope.current.jsonData.interval});
|
||||
$scope.current.database = def.example || 'es-index-name';
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
@@ -23,9 +23,7 @@
|
||||
Basic Auth
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
Enable
|
||||
<input class="cr1" id="current.basicAuth" type="checkbox" ng-model="current.basicAuth" ng-checked="current.basicAuth">
|
||||
<label for="current.basicAuth" class="cr1"></label>
|
||||
<editor-checkbox text="Enable" model="current.basicAuth"></editor-checkbox>
|
||||
</li>
|
||||
<li class="tight-form-item" ng-if="current.basicAuth">
|
||||
User
|
||||
|
||||
16
public/app/plugins/datasource/cloudwatch/_plugin.json
Normal file
16
public/app/plugins/datasource/cloudwatch/_plugin.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"pluginType": "datasource",
|
||||
"name": "CloudWatch",
|
||||
|
||||
"type": "cloudwatch",
|
||||
"serviceName": "CloudWatchDatasource",
|
||||
|
||||
"module": "plugins/datasource/cloudwatch/datasource",
|
||||
|
||||
"partials": {
|
||||
"config": "app/plugins/datasource/cloudwatch/partials/config.html",
|
||||
"query": "app/plugins/datasource/cloudwatch/partials/query.editor.html"
|
||||
},
|
||||
|
||||
"metrics": true
|
||||
}
|
||||
561
public/app/plugins/datasource/cloudwatch/datasource.js
Normal file
561
public/app/plugins/datasource/cloudwatch/datasource.js
Normal file
@@ -0,0 +1,561 @@
|
||||
/* global AWS */
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'kbn',
|
||||
'moment',
|
||||
'./queryCtrl',
|
||||
'./directives',
|
||||
'aws-sdk',
|
||||
],
|
||||
function (angular, _, kbn) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.factory('CloudWatchDatasource', function($q, $http, templateSrv) {
|
||||
|
||||
function CloudWatchDatasource(datasource) {
|
||||
this.type = 'cloudwatch';
|
||||
this.name = datasource.name;
|
||||
this.supportMetrics = true;
|
||||
this.proxyMode = (datasource.jsonData.access === 'proxy');
|
||||
this.proxyUrl = datasource.url;
|
||||
|
||||
this.defaultRegion = datasource.jsonData.defaultRegion;
|
||||
this.credentials = {
|
||||
accessKeyId: datasource.jsonData.accessKeyId,
|
||||
secretAccessKey: datasource.jsonData.secretAccessKey
|
||||
};
|
||||
|
||||
/* jshint -W101 */
|
||||
this.supportedRegion = [
|
||||
'us-east-1', 'us-west-2', 'us-west-1', 'eu-west-1', 'eu-central-1', 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'sa-east-1'
|
||||
];
|
||||
|
||||
this.supportedMetrics = {
|
||||
'AWS/AutoScaling': [
|
||||
'GroupMinSize', 'GroupMaxSize', 'GroupDesiredCapacity', 'GroupInServiceInstances', 'GroupPendingInstances', 'GroupStandbyInstances', 'GroupTerminatingInstances', 'GroupTotalInstances'
|
||||
],
|
||||
'AWS/Billing': [
|
||||
'EstimatedCharges'
|
||||
],
|
||||
'AWS/CloudFront': [
|
||||
'Requests', 'BytesDownloaded', 'BytesUploaded', 'TotalErrorRate', '4xxErrorRate', '5xxErrorRate'
|
||||
],
|
||||
'AWS/CloudSearch': [
|
||||
'SuccessfulRequests', 'SearchableDocuments', 'IndexUtilization', 'Partitions'
|
||||
],
|
||||
'AWS/DynamoDB': [
|
||||
'ConditionalCheckFailedRequests', 'ConsumedReadCapacityUnits', 'ConsumedWriteCapacityUnits', 'OnlineIndexConsumedWriteCapacity', 'OnlineIndexPercentageProgress', 'OnlineIndexThrottleEvents', 'ProvisionedReadCapacityUnits', 'ProvisionedWriteCapacityUnits', 'ReadThrottleEvents', 'ReturnedItemCount', 'SuccessfulRequestLatency', 'SystemErrors', 'ThrottledRequests', 'UserErrors', 'WriteThrottleEvents'
|
||||
],
|
||||
'AWS/ElastiCache': [
|
||||
'CPUUtilization', 'SwapUsage', 'FreeableMemory', 'NetworkBytesIn', 'NetworkBytesOut',
|
||||
'BytesUsedForCacheItems', 'BytesReadIntoMemcached', 'BytesWrittenOutFromMemcached', 'CasBadval', 'CasHits', 'CasMisses', 'CmdFlush', 'CmdGet', 'CmdSet', 'CurrConnections', 'CurrItems', 'DecrHits', 'DecrMisses', 'DeleteHits', 'DeleteMisses', 'Evictions', 'GetHits', 'GetMisses', 'IncrHits', 'IncrMisses', 'Reclaimed',
|
||||
'CurrConnections', 'Evictions', 'Reclaimed', 'NewConnections', 'BytesUsedForCache', 'CacheHits', 'CacheMisses', 'ReplicationLag', 'GetTypeCmds', 'SetTypeCmds', 'KeyBasedCmds', 'StringBasedCmds', 'HashBasedCmds', 'ListBasedCmds', 'SetBasedCmds', 'SortedSetBasedCmds', 'CurrItems'
|
||||
],
|
||||
'AWS/EBS': [
|
||||
'VolumeReadBytes', 'VolumeWriteBytes', 'VolumeReadOps', 'VolumeWriteOps', 'VolumeTotalReadTime', 'VolumeTotalWriteTime', 'VolumeIdleTime', 'VolumeQueueLength', 'VolumeThroughputPercentage', 'VolumeConsumedReadWriteOps'
|
||||
],
|
||||
'AWS/EC2': [
|
||||
'CPUCreditUsage', 'CPUCreditBalance', 'CPUUtilization', 'DiskReadOps', 'DiskWriteOps', 'DiskReadBytes', 'DiskWriteBytes', 'NetworkIn', 'NetworkOut', 'StatusCheckFailed', 'StatusCheckFailed_Instance', 'StatusCheckFailed_System'
|
||||
],
|
||||
'AWS/ELB': [
|
||||
'HealthyHostCount', 'UnHealthyHostCount', 'RequestCount', 'Latency', 'HTTPCode_ELB_4XX', 'HTTPCode_ELB_5XX', 'HTTPCode_Backend_2XX', 'HTTPCode_Backend_3XX', 'HTTPCode_Backend_4XX', 'HTTPCode_Backend_5XX', 'BackendConnectionErrors', 'SurgeQueueLength', 'SpilloverCount'
|
||||
],
|
||||
'AWS/ElasticMapReduce': [
|
||||
'CoreNodesPending', 'CoreNodesRunning', 'HBaseBackupFailed', 'HBaseMostRecentBackupDuration', 'HBaseTimeSinceLastSuccessfulBackup', 'HDFSBytesRead', 'HDFSBytesWritten', 'HDFSUtilization', 'IsIdle', 'JobsFailed', 'JobsRunning', 'LiveDataNodes', 'LiveTaskTrackers', 'MapSlotsOpen', 'MissingBlocks', 'ReduceSlotsOpen', 'RemainingMapTasks', 'RemainingMapTasksPerSlot', 'RemainingReduceTasks', 'RunningMapTasks', 'RunningReduceTasks', 'S3BytesRead', 'S3BytesWritten', 'TaskNodesPending', 'TaskNodesRunning', 'TotalLoad'
|
||||
],
|
||||
'AWS/Kinesis': [
|
||||
'PutRecord.Bytes', 'PutRecord.Latency', 'PutRecord.Success', 'PutRecords.Bytes', 'PutRecords.Latency', 'PutRecords.Records', 'PutRecords.Success', 'IncomingBytes', 'IncomingRecords', 'GetRecords.Bytes', 'GetRecords.IteratorAgeMilliseconds', 'GetRecords.Latency', 'GetRecords.Success'
|
||||
],
|
||||
'AWS/ML': [
|
||||
'PredictCount', 'PredictFailureCount'
|
||||
],
|
||||
'AWS/OpsWorks': [
|
||||
'cpu_idle', 'cpu_nice', 'cpu_system', 'cpu_user', 'cpu_waitio', 'load_1', 'load_5', 'load_15', 'memory_buffers', 'memory_cached', 'memory_free', 'memory_swap', 'memory_total', 'memory_used', 'procs'
|
||||
],
|
||||
'AWS/Redshift': [
|
||||
'CPUUtilization', 'DatabaseConnections', 'HealthStatus', 'MaintenanceMode', 'NetworkReceiveThroughput', 'NetworkTransmitThroughput', 'PercentageDiskSpaceUsed', 'ReadIOPS', 'ReadLatency', 'ReadThroughput', 'WriteIOPS', 'WriteLatency', 'WriteThroughput'
|
||||
],
|
||||
'AWS/RDS': [
|
||||
'BinLogDiskUsage', 'CPUUtilization', 'DatabaseConnections', 'DiskQueueDepth', 'FreeableMemory', 'FreeStorageSpace', 'ReplicaLag', 'SwapUsage', 'ReadIOPS', 'WriteIOPS', 'ReadLatency', 'WriteLatency', 'ReadThroughput', 'WriteThroughput', 'NetworkReceiveThroughput', 'NetworkTransmitThroughput'
|
||||
],
|
||||
'AWS/Route53': [
|
||||
'HealthCheckStatus', 'HealthCheckPercentageHealthy'
|
||||
],
|
||||
'AWS/SNS': [
|
||||
'NumberOfMessagesPublished', 'PublishSize', 'NumberOfNotificationsDelivered', 'NumberOfNotificationsFailed'
|
||||
],
|
||||
'AWS/SQS': [
|
||||
'NumberOfMessagesSent', 'SentMessageSize', 'NumberOfMessagesReceived', 'NumberOfEmptyReceives', 'NumberOfMessagesDeleted', 'ApproximateNumberOfMessagesDelayed', 'ApproximateNumberOfMessagesVisible', 'ApproximateNumberOfMessagesNotVisible'
|
||||
],
|
||||
'AWS/S3': [
|
||||
'BucketSizeBytes', 'NumberOfObjects'
|
||||
],
|
||||
'AWS/SWF': [
|
||||
'DecisionTaskScheduleToStartTime', 'DecisionTaskStartToCloseTime', 'DecisionTasksCompleted', 'StartedDecisionTasksTimedOutOnClose', 'WorkflowStartToCloseTime', 'WorkflowsCanceled', 'WorkflowsCompleted', 'WorkflowsContinuedAsNew', 'WorkflowsFailed', 'WorkflowsTerminated', 'WorkflowsTimedOut'
|
||||
],
|
||||
'AWS/StorageGateway': [
|
||||
'CacheHitPercent', 'CachePercentUsed', 'CachePercentDirty', 'CloudBytesDownloaded', 'CloudDownloadLatency', 'CloudBytesUploaded', 'UploadBufferFree', 'UploadBufferPercentUsed', 'UploadBufferUsed', 'QueuedWrites', 'ReadBytes', 'ReadTime', 'TotalCacheSize', 'WriteBytes', 'WriteTime', 'WorkingStorageFree', 'WorkingStoragePercentUsed', 'WorkingStorageUsed', 'CacheHitPercent', 'CachePercentUsed', 'CachePercentDirty', 'ReadBytes', 'ReadTime', 'WriteBytes', 'WriteTime', 'QueuedWrites'
|
||||
],
|
||||
'AWS/WorkSpaces': [
|
||||
'Available', 'Unhealthy', 'ConnectionAttempt', 'ConnectionSuccess', 'ConnectionFailure', 'SessionLaunchTime', 'InSessionLatency', 'SessionDisconnect'
|
||||
],
|
||||
};
|
||||
|
||||
this.supportedDimensions = {
|
||||
'AWS/AutoScaling': [
|
||||
'AutoScalingGroupName'
|
||||
],
|
||||
'AWS/Billing': [
|
||||
'ServiceName', 'LinkedAccount', 'Currency'
|
||||
],
|
||||
'AWS/CloudFront': [
|
||||
'DistributionId', 'Region'
|
||||
],
|
||||
'AWS/CloudSearch': [
|
||||
|
||||
],
|
||||
'AWS/DynamoDB': [
|
||||
'TableName', 'GlobalSecondaryIndexName', 'Operation'
|
||||
],
|
||||
'AWS/ElastiCache': [
|
||||
'CacheClusterId', 'CacheNodeId'
|
||||
],
|
||||
'AWS/EBS': [
|
||||
'VolumeId'
|
||||
],
|
||||
'AWS/EC2': [
|
||||
'AutoScalingGroupName', 'ImageId', 'InstanceId', 'InstanceType'
|
||||
],
|
||||
'AWS/ELB': [
|
||||
'LoadBalancerName', 'AvailabilityZone'
|
||||
],
|
||||
'AWS/ElasticMapReduce': [
|
||||
'ClusterId', 'JobId'
|
||||
],
|
||||
'AWS/Kinesis': [
|
||||
'StreamName'
|
||||
],
|
||||
'AWS/ML': [
|
||||
'MLModelId', 'RequestMode'
|
||||
],
|
||||
'AWS/OpsWorks': [
|
||||
'StackId', 'LayerId', 'InstanceId'
|
||||
],
|
||||
'AWS/Redshift': [
|
||||
'NodeID', 'ClusterIdentifier'
|
||||
],
|
||||
'AWS/RDS': [
|
||||
'DBInstanceIdentifier', 'DatabaseClass', 'EngineName'
|
||||
],
|
||||
'AWS/Route53': [
|
||||
'HealthCheckId'
|
||||
],
|
||||
'AWS/SNS': [
|
||||
'Application', 'Platform', 'TopicName'
|
||||
],
|
||||
'AWS/SQS': [
|
||||
'QueueName'
|
||||
],
|
||||
'AWS/S3': [
|
||||
'BucketName', 'StorageType'
|
||||
],
|
||||
'AWS/SWF': [
|
||||
'Domain', 'ActivityTypeName', 'ActivityTypeVersion'
|
||||
],
|
||||
'AWS/StorageGateway': [
|
||||
'GatewayId', 'GatewayName', 'VolumeId'
|
||||
],
|
||||
'AWS/WorkSpaces': [
|
||||
'DirectoryId', 'WorkspaceId'
|
||||
],
|
||||
};
|
||||
/* jshint +W101 */
|
||||
|
||||
/* load custom metrics definitions */
|
||||
var self = this;
|
||||
$q.all(
|
||||
_.chain(datasource.jsonData.customMetricsAttributes)
|
||||
.reject(function(u) {
|
||||
return _.isEmpty(u);
|
||||
})
|
||||
.map(function(u) {
|
||||
return $http({ method: 'GET', url: u });
|
||||
})
|
||||
)
|
||||
.then(function(allResponse) {
|
||||
_.chain(allResponse)
|
||||
.map(function(d) {
|
||||
return d.data.Metrics;
|
||||
})
|
||||
.flatten()
|
||||
.reject(function(metric) {
|
||||
return metric.Namespace.indexOf('AWS/') === 0;
|
||||
})
|
||||
.map(function(metric) {
|
||||
metric.Dimensions = _.chain(metric.Dimensions)
|
||||
.map(function(d) {
|
||||
return d.Name;
|
||||
})
|
||||
.value().sort();
|
||||
return metric;
|
||||
})
|
||||
.uniq(function(metric) {
|
||||
return metric.Namespace + metric.MetricName + metric.Dimensions.join('');
|
||||
})
|
||||
.each(function(metric) {
|
||||
if (!_.has(self.supportedMetrics, metric.Namespace)) {
|
||||
self.supportedMetrics[metric.Namespace] = [];
|
||||
}
|
||||
self.supportedMetrics[metric.Namespace].push(metric.MetricName);
|
||||
|
||||
if (!_.has(self.supportedDimensions, metric.Namespace)) {
|
||||
self.supportedDimensions[metric.Namespace] = [];
|
||||
}
|
||||
|
||||
self.supportedDimensions[metric.Namespace] = _.union(self.supportedDimensions[metric.Namespace], metric.Dimensions);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Called once per panel (graph)
|
||||
CloudWatchDatasource.prototype.query = function(options) {
|
||||
var start = convertToCloudWatchTime(options.range.from);
|
||||
var end = convertToCloudWatchTime(options.range.to);
|
||||
|
||||
var queries = [];
|
||||
_.each(options.targets, _.bind(function(target) {
|
||||
if (!target.namespace || !target.metricName || _.isEmpty(target.statistics)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var query = {};
|
||||
query.region = templateSrv.replace(target.region, options.scopedVars);
|
||||
query.namespace = templateSrv.replace(target.namespace, options.scopedVars);
|
||||
query.metricName = templateSrv.replace(target.metricName, options.scopedVars);
|
||||
query.dimensions = convertDimensionFormat(target.dimensions);
|
||||
query.statistics = getActivatedStatistics(target.statistics);
|
||||
query.period = parseInt(target.period, 10);
|
||||
|
||||
var range = end - start;
|
||||
// CloudWatch limit datapoints up to 1440
|
||||
if (range / query.period >= 1440) {
|
||||
query.period = Math.floor(range / 1440 / 60) * 60;
|
||||
}
|
||||
|
||||
queries.push(query);
|
||||
}, this));
|
||||
|
||||
// No valid targets, return the empty result to save a round trip.
|
||||
if (_.isEmpty(queries)) {
|
||||
var d = $q.defer();
|
||||
d.resolve({ data: [] });
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
var allQueryPromise = _.map(queries, _.bind(function(query) {
|
||||
return this.performTimeSeriesQuery(query, start, end);
|
||||
}, this));
|
||||
|
||||
return $q.all(allQueryPromise)
|
||||
.then(function(allResponse) {
|
||||
var result = [];
|
||||
|
||||
_.each(allResponse, function(response, index) {
|
||||
var metrics = transformMetricData(response, options.targets[index]);
|
||||
_.each(metrics, function(m) {
|
||||
result.push(m);
|
||||
});
|
||||
});
|
||||
|
||||
return { data: result };
|
||||
});
|
||||
};
|
||||
|
||||
CloudWatchDatasource.prototype.performTimeSeriesQuery = function(query, start, end) {
|
||||
var cloudwatch = this.getCloudWatchClient(query.region);
|
||||
|
||||
var params = {
|
||||
Namespace: query.namespace,
|
||||
MetricName: query.metricName,
|
||||
Dimensions: query.dimensions,
|
||||
Statistics: query.statistics,
|
||||
StartTime: start,
|
||||
EndTime: end,
|
||||
Period: query.period
|
||||
};
|
||||
|
||||
var d = $q.defer();
|
||||
cloudwatch.getMetricStatistics(params, function(err, data) {
|
||||
if (err) {
|
||||
return d.reject(err);
|
||||
}
|
||||
return d.resolve(data);
|
||||
});
|
||||
|
||||
return d.promise;
|
||||
};
|
||||
|
||||
CloudWatchDatasource.prototype.performSuggestRegion = function() {
|
||||
return this.supportedRegion;
|
||||
};
|
||||
|
||||
CloudWatchDatasource.prototype.performSuggestNamespace = function() {
|
||||
return _.keys(this.supportedMetrics);
|
||||
};
|
||||
|
||||
CloudWatchDatasource.prototype.performSuggestMetrics = function(namespace) {
|
||||
namespace = templateSrv.replace(namespace);
|
||||
return this.supportedMetrics[namespace] || [];
|
||||
};
|
||||
|
||||
CloudWatchDatasource.prototype.performSuggestDimensionKeys = function(namespace) {
|
||||
namespace = templateSrv.replace(namespace);
|
||||
return this.supportedDimensions[namespace] || [];
|
||||
};
|
||||
|
||||
CloudWatchDatasource.prototype.performSuggestDimensionValues = function(region, namespace, metricName, dimensions) {
|
||||
region = templateSrv.replace(region);
|
||||
namespace = templateSrv.replace(namespace);
|
||||
metricName = templateSrv.replace(metricName);
|
||||
|
||||
var cloudwatch = this.getCloudWatchClient(region);
|
||||
|
||||
var params = {
|
||||
Namespace: namespace,
|
||||
MetricName: metricName
|
||||
};
|
||||
if (!_.isEmpty(dimensions)) {
|
||||
params.Dimensions = convertDimensionFormat(dimensions);
|
||||
}
|
||||
|
||||
var d = $q.defer();
|
||||
|
||||
cloudwatch.listMetrics(params, function(err, data) {
|
||||
if (err) {
|
||||
return d.reject(err);
|
||||
}
|
||||
|
||||
var suggestData = _.chain(data.Metrics)
|
||||
.map(function(metric) {
|
||||
return metric.Dimensions;
|
||||
})
|
||||
.reject(function(metric) {
|
||||
return _.isEmpty(metric);
|
||||
})
|
||||
.value();
|
||||
|
||||
return d.resolve(suggestData);
|
||||
});
|
||||
|
||||
return d.promise;
|
||||
};
|
||||
|
||||
CloudWatchDatasource.prototype.getTemplateVariableNames = function() {
|
||||
var variables = [];
|
||||
templateSrv.fillVariableValuesForUrl(variables);
|
||||
|
||||
return _.map(_.keys(variables), function(k) {
|
||||
return k.replace(/var-/, '$');
|
||||
});
|
||||
};
|
||||
|
||||
CloudWatchDatasource.prototype.metricFindQuery = function(query) {
|
||||
var region;
|
||||
var namespace;
|
||||
var metricName;
|
||||
|
||||
var transformSuggestData = function(suggestData) {
|
||||
return _.map(suggestData, function(v) {
|
||||
return { text: v };
|
||||
});
|
||||
};
|
||||
|
||||
var d = $q.defer();
|
||||
|
||||
var regionQuery = query.match(/^region\(\)/);
|
||||
if (regionQuery) {
|
||||
d.resolve(transformSuggestData(this.performSuggestRegion()));
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
var namespaceQuery = query.match(/^namespace\(\)/);
|
||||
if (namespaceQuery) {
|
||||
d.resolve(transformSuggestData(this.performSuggestNamespace()));
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
var metricNameQuery = query.match(/^metrics\(([^\)]+?)\)/);
|
||||
if (metricNameQuery) {
|
||||
namespace = templateSrv.replace(metricNameQuery[1]);
|
||||
d.resolve(transformSuggestData(this.performSuggestMetrics(namespace)));
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
var dimensionKeysQuery = query.match(/^dimension_keys\(([^\)]+?)\)/);
|
||||
if (dimensionKeysQuery) {
|
||||
namespace = templateSrv.replace(dimensionKeysQuery[1]);
|
||||
d.resolve(transformSuggestData(this.performSuggestDimensionKeys(namespace)));
|
||||
return d.promise;
|
||||
}
|
||||
|
||||
var dimensionValuesQuery = query.match(/^dimension_values\(([^,]+?),\s?([^,]+?),\s?([^,]+?)(,\s?([^)]*))?\)/);
|
||||
if (dimensionValuesQuery) {
|
||||
region = templateSrv.replace(dimensionValuesQuery[1]);
|
||||
namespace = templateSrv.replace(dimensionValuesQuery[2]);
|
||||
metricName = templateSrv.replace(dimensionValuesQuery[3]);
|
||||
var dimensionPart = templateSrv.replace(dimensionValuesQuery[5]);
|
||||
|
||||
var dimensions = {};
|
||||
if (!_.isEmpty(dimensionPart)) {
|
||||
_.each(dimensionPart.split(','), function(v) {
|
||||
var t = v.split('=');
|
||||
if (t.length !== 2) {
|
||||
throw new Error('Invalid query format');
|
||||
}
|
||||
dimensions[t[0]] = t[1];
|
||||
});
|
||||
}
|
||||
|
||||
return this.performSuggestDimensionValues(region, namespace, metricName, dimensions)
|
||||
.then(function(suggestData) {
|
||||
return _.map(suggestData, function(dimensions) {
|
||||
var result = _.chain(dimensions)
|
||||
.sortBy(function(dimension) {
|
||||
return dimension.Name;
|
||||
})
|
||||
.map(function(dimension) {
|
||||
return dimension.Name + '=' + dimension.Value;
|
||||
})
|
||||
.value().join(',');
|
||||
|
||||
return { text: result };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return $q.when([]);
|
||||
};
|
||||
|
||||
CloudWatchDatasource.prototype.testDatasource = function() {
|
||||
/* use billing metrics for test */
|
||||
var region = 'us-east-1';
|
||||
var namespace = 'AWS/Billing';
|
||||
var metricName = 'EstimatedCharges';
|
||||
var dimensions = {};
|
||||
|
||||
return this.performSuggestDimensionValues(region, namespace, metricName, dimensions).then(function () {
|
||||
return { status: 'success', message: 'Data source is working', title: 'Success' };
|
||||
});
|
||||
};
|
||||
|
||||
CloudWatchDatasource.prototype.getCloudWatchClient = function(region) {
|
||||
if (!this.proxyMode) {
|
||||
return new AWS.CloudWatch({
|
||||
region: region,
|
||||
accessKeyId: this.credentials.accessKeyId,
|
||||
secretAccessKey: this.credentials.secretAccessKey
|
||||
});
|
||||
} else {
|
||||
var self = this;
|
||||
var generateRequestProxy = function(service, action) {
|
||||
return function(params, callback) {
|
||||
var data = {
|
||||
region: region,
|
||||
service: service,
|
||||
action: action,
|
||||
parameters: params
|
||||
};
|
||||
|
||||
var options = {
|
||||
method: 'POST',
|
||||
url: self.proxyUrl,
|
||||
data: data
|
||||
};
|
||||
|
||||
$http(options).then(function(response) {
|
||||
callback(null, response.data);
|
||||
}, function(err) {
|
||||
callback(err, []);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
getMetricStatistics: generateRequestProxy('CloudWatch', 'GetMetricStatistics'),
|
||||
listMetrics: generateRequestProxy('CloudWatch', 'ListMetrics')
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
CloudWatchDatasource.prototype.getDefaultRegion = function() {
|
||||
return this.defaultRegion;
|
||||
};
|
||||
|
||||
function transformMetricData(md, options) {
|
||||
var result = [];
|
||||
|
||||
var dimensionPart = templateSrv.replace(JSON.stringify(options.dimensions));
|
||||
_.each(getActivatedStatistics(options.statistics), function(s) {
|
||||
var originalSettings = _.templateSettings;
|
||||
_.templateSettings = {
|
||||
interpolate: /\{\{(.+?)\}\}/g
|
||||
};
|
||||
var template = _.template(options.legendFormat);
|
||||
|
||||
var metricLabel;
|
||||
if (_.isEmpty(options.legendFormat)) {
|
||||
metricLabel = md.Label + '_' + s + dimensionPart;
|
||||
} else {
|
||||
var d = convertDimensionFormat(options.dimensions);
|
||||
metricLabel = template({
|
||||
Region: templateSrv.replace(options.region),
|
||||
Namespace: templateSrv.replace(options.namespace),
|
||||
MetricName: templateSrv.replace(options.metricName),
|
||||
Dimensions: d,
|
||||
Statistics: s
|
||||
});
|
||||
}
|
||||
|
||||
_.templateSettings = originalSettings;
|
||||
|
||||
var dps = _.map(md.Datapoints, function(value) {
|
||||
return [value[s], new Date(value.Timestamp).getTime()];
|
||||
});
|
||||
dps = _.sortBy(dps, function(dp) { return dp[1]; });
|
||||
|
||||
result.push({ target: metricLabel, datapoints: dps });
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getActivatedStatistics(statistics) {
|
||||
var activatedStatistics = [];
|
||||
_.each(statistics, function(v, k) {
|
||||
if (v) {
|
||||
activatedStatistics.push(k);
|
||||
}
|
||||
});
|
||||
return activatedStatistics;
|
||||
}
|
||||
|
||||
function convertToCloudWatchTime(date) {
|
||||
return Math.round(kbn.parseDate(date).getTime() / 1000);
|
||||
}
|
||||
|
||||
function convertDimensionFormat(dimensions) {
|
||||
return _.map(_.keys(dimensions), function(key) {
|
||||
return {
|
||||
Name: templateSrv.replace(key),
|
||||
Value: templateSrv.replace(dimensions[key])
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return CloudWatchDatasource;
|
||||
});
|
||||
|
||||
});
|
||||
13
public/app/plugins/datasource/cloudwatch/directives.js
Normal file
13
public/app/plugins/datasource/cloudwatch/directives.js
Normal file
@@ -0,0 +1,13 @@
|
||||
define([
|
||||
'angular',
|
||||
],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.directive('metricQueryEditorCloudwatch', function() {
|
||||
return {controller: 'CloudWatchQueryCtrl', templateUrl: 'app/plugins/datasource/cloudwatch/partials/query.editor.html'};
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
<h5>CloudWatch details</h5>
|
||||
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 160px">
|
||||
Default Region
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="tight-form-input input-xlarge" ng-model='current.jsonData.defaultRegion' placeholder="" required></input>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item">
|
||||
Access <tip>Direct = url is used directly from browser, Proxy = Grafana backend will proxy the request</label>
|
||||
</li>
|
||||
<li>
|
||||
<select class="input-small tight-form-input" ng-model="current.jsonData.access" ng-options="f for f in ['direct', 'proxy']" ng-init="current.jsonData.access = current.jsonData.access || 'direct'"></select>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form" ng-show="current.jsonData.access === 'direct'">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 160px">
|
||||
Access Key Id
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="tight-form-input input-xlarge" ng-model='current.jsonData.accessKeyId' placeholder="" ng-required="current.jsonData.access === 'direct'"></input>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 160px">
|
||||
Secret Access Key
|
||||
</li>
|
||||
<li>
|
||||
<input type="password" class="tight-form-input input-xlarge" ng-model='current.jsonData.secretAccessKey' placeholder="" ng-required="current.jsonData.access === 'direct'"></input>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 160px">
|
||||
Custom Metric Attributes
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="tight-form-input input-xlarge" ng-model='current.jsonData.customMetricsAttributes[0]' ng-init="current.jsonData.customMetricsAttributes = current.jsonData.customMetricsAttributes || []" placeholder="JSON url" bs-tooltip="'Set JSON url of the result, \'aws cloudwatch list-metrics --output json\''"></input>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
@@ -0,0 +1,153 @@
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list pull-right">
|
||||
<li class="tight-form-item">
|
||||
<div class="dropdown">
|
||||
<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
|
||||
<i class="fa fa-bars"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right" role="menu">
|
||||
<li role="menuitem"><a tabindex="1" ng-click="duplicateDataQuery(target)">Duplicate</a></li>
|
||||
<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index-1)">Move up</a></li>
|
||||
<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index+1)">Move down</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
<li class="tight-form-item last">
|
||||
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="min-width: 15px; text-align: center">
|
||||
{{target.refId}}
|
||||
</li>
|
||||
<li>
|
||||
<a class="tight-form-item"
|
||||
ng-click="target.hide = !target.hide; get_data();"
|
||||
role="menuitem">
|
||||
<i class="fa fa-eye"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="tight-form-list" role="menu">
|
||||
<li class="tight-form-item" style="width: 100px">
|
||||
Metric
|
||||
</li>
|
||||
<li>
|
||||
<metric-segment segment="regionSegment" get-alt-segments="getRegions()" on-value-changed="regionChanged()"></metric-segment>
|
||||
</li>
|
||||
<li>
|
||||
<metric-segment segment="namespaceSegment" get-alt-segments="getNamespaces()" on-value-changed="namespaceChanged()"></metric-segment>
|
||||
</li>
|
||||
<li>
|
||||
<metric-segment segment="metricSegment" get-alt-segments="getMetrics()" on-value-changed="metricChanged()"></metric-segment>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list" role="menu">
|
||||
<li class="tight-form-item tight-form-align" style="width: 100px">
|
||||
Dimensions
|
||||
</li>
|
||||
<li ng-repeat="(key, value) in target.escapedDimensions track by $index" class="tight-form-item">
|
||||
{{key}} = {{value}}
|
||||
<a ng-click="removeDimension(key)">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="tight-form-item" ng-hide="addDimensionMode">
|
||||
<a ng-click="addDimension()">
|
||||
<i class="fa fa-plus"></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li ng-show="addDimensionMode">
|
||||
<input type="text"
|
||||
class="input-small tight-form-input"
|
||||
spellcheck='false'
|
||||
bs-typeahead="suggestDimensionKeys"
|
||||
data-min-length=0 data-items=100
|
||||
ng-model="target.currentDimensionKey"
|
||||
placeholder="key">
|
||||
<input type="text"
|
||||
class="input-small tight-form-input"
|
||||
spellcheck='false'
|
||||
bs-typeahead="suggestDimensionValues"
|
||||
data-min-length=0 data-items=100
|
||||
ng-model="target.currentDimensionValue"
|
||||
placeholder="value">
|
||||
<a ng-click="addDimension()">
|
||||
add dimension
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list" role="menu">
|
||||
<li class="tight-form-item tight-form-align" style="width: 100px">
|
||||
Statistics
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
<editor-checkbox text="Min" model="target.statistics.Minimum" change="statisticsOptionChanged()"></editor-checkbox>
|
||||
<editor-checkbox text="Max" model="target.statistics.Maximum" change="statisticsOptionChanged()"></editor-checkbox>
|
||||
<editor-checkbox text="Avg" model="target.statistics.Average" change="statisticsOptionChanged()"></editor-checkbox>
|
||||
<editor-checkbox text="Sum" model="target.statistics.Sum" change="statisticsOptionChanged()"></editor-checkbox>
|
||||
<editor-checkbox text="SampleCount" model="target.statistics.SampleCount" change="statisticsOptionChanged()"></editor-checkbox>
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
Period
|
||||
</li>
|
||||
<li>
|
||||
<input type="text"
|
||||
class="input-mini tight-form-input"
|
||||
ng-model="target.period"
|
||||
data-placement="right"
|
||||
spellcheck='false'
|
||||
placeholder="period"
|
||||
data-min-length=0 data-items=100
|
||||
ng-model-onblur
|
||||
ng-change="refreshMetricData()"
|
||||
/>
|
||||
<a bs-tooltip="target.errors.period"
|
||||
style="color: rgb(229, 189, 28)"
|
||||
ng-show="target.errors.period">
|
||||
<i class="fa fa-warning"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list" role="menu">
|
||||
<li class="tight-form-item tight-form-align" style="width: 100px">
|
||||
Alias Pattern
|
||||
</li>
|
||||
<li>
|
||||
<input type="text"
|
||||
class="input-xxlarge tight-form-input"
|
||||
ng-model="target.legendFormat"
|
||||
spellcheck='false'
|
||||
placeholder="Syntax: {{Region}} {{Namespace}} {{MetricName}} {{Statistics}} {{Dimensions[N].Name}} {{Dimensions[N].Value}}"
|
||||
data-min-length=0 data-items=100
|
||||
ng-model-onblur
|
||||
ng-change="refreshMetricData()"
|
||||
>
|
||||
<tip>Syntax: {{Region}} {{Namespace}} {{MetricName}} {{Statistics}} {{Dimensions[N].Name}} {{Dimensions[N].Value}}</tip>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
172
public/app/plugins/datasource/cloudwatch/queryCtrl.js
Normal file
172
public/app/plugins/datasource/cloudwatch/queryCtrl.js
Normal file
@@ -0,0 +1,172 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
],
|
||||
function (angular, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('CloudWatchQueryCtrl', function($scope, templateSrv, uiSegmentSrv) {
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.target.namespace = $scope.target.namespace || '';
|
||||
$scope.target.metricName = $scope.target.metricName || '';
|
||||
$scope.target.dimensions = $scope.target.dimensions || {};
|
||||
$scope.target.escapedDimensions = this.escapeDimensions($scope.target.dimensions);
|
||||
$scope.target.statistics = $scope.target.statistics || {};
|
||||
$scope.target.period = $scope.target.period || 60;
|
||||
$scope.target.region = $scope.target.region || $scope.datasource.getDefaultRegion();
|
||||
$scope.target.errors = validateTarget();
|
||||
|
||||
$scope.regionSegment = uiSegmentSrv.getSegmentForValue($scope.target.region, 'select region');
|
||||
$scope.namespaceSegment = uiSegmentSrv.getSegmentForValue($scope.target.namespace, 'select namespace');
|
||||
$scope.metricSegment = uiSegmentSrv.getSegmentForValue($scope.target.metricName, 'select metric');
|
||||
};
|
||||
|
||||
$scope.getRegions = function() {
|
||||
return $scope.datasource.metricFindQuery('region()')
|
||||
.then($scope.transformToSegments(true));
|
||||
};
|
||||
|
||||
$scope.getNamespaces = function() {
|
||||
return $scope.datasource.metricFindQuery('namespace()')
|
||||
.then($scope.transformToSegments(true));
|
||||
};
|
||||
|
||||
$scope.getMetrics = function() {
|
||||
return $scope.datasource.metricFindQuery('metrics(' + $scope.target.namespace + ')')
|
||||
.then($scope.transformToSegments(true));
|
||||
};
|
||||
|
||||
$scope.regionChanged = function() {
|
||||
$scope.target.region = $scope.regionSegment.value;
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
$scope.namespaceChanged = function() {
|
||||
$scope.target.namespace = $scope.namespaceSegment.value;
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
$scope.metricChanged = function() {
|
||||
$scope.target.metricName = $scope.metricSegment.value;
|
||||
$scope.get_data();
|
||||
};
|
||||
|
||||
$scope.transformToSegments = function(addTemplateVars) {
|
||||
return function(results) {
|
||||
var segments = _.map(results, function(segment) {
|
||||
return uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable });
|
||||
});
|
||||
|
||||
if (addTemplateVars) {
|
||||
_.each(templateSrv.variables, function(variable) {
|
||||
segments.unshift(uiSegmentSrv.newSegment({ type: 'template', value: '$' + variable.name, expandable: true }));
|
||||
});
|
||||
}
|
||||
|
||||
return segments;
|
||||
};
|
||||
};
|
||||
|
||||
$scope.refreshMetricData = function() {
|
||||
$scope.target.errors = validateTarget($scope.target);
|
||||
|
||||
// this does not work so good
|
||||
if (!_.isEqual($scope.oldTarget, $scope.target) && _.isEmpty($scope.target.errors)) {
|
||||
$scope.oldTarget = angular.copy($scope.target);
|
||||
$scope.get_data();
|
||||
}
|
||||
};
|
||||
|
||||
$scope.suggestDimensionKeys = function(query, callback) { // jshint unused:false
|
||||
return _.union($scope.datasource.performSuggestDimensionKeys($scope.target.namespace), $scope.datasource.getTemplateVariableNames());
|
||||
};
|
||||
|
||||
$scope.suggestDimensionValues = function(query, callback) {
|
||||
if (!$scope.target.namespace || !$scope.target.metricName) {
|
||||
return callback([]);
|
||||
}
|
||||
|
||||
$scope.datasource.performSuggestDimensionValues(
|
||||
$scope.target.region,
|
||||
$scope.target.namespace,
|
||||
$scope.target.metricName,
|
||||
$scope.target.dimensions
|
||||
)
|
||||
.then(function(result) {
|
||||
var suggestData = _.chain(result)
|
||||
.flatten(true)
|
||||
.filter(function(dimension) {
|
||||
return dimension.Name === templateSrv.replace($scope.target.currentDimensionKey);
|
||||
})
|
||||
.pluck('Value')
|
||||
.uniq()
|
||||
.value();
|
||||
|
||||
suggestData = _.union(suggestData, $scope.datasource.getTemplateVariableNames());
|
||||
callback(suggestData);
|
||||
}, function() {
|
||||
callback([]);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addDimension = function() {
|
||||
if (!$scope.addDimensionMode) {
|
||||
$scope.addDimensionMode = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$scope.target.dimensions) {
|
||||
$scope.target.dimensions = {};
|
||||
}
|
||||
|
||||
$scope.target.dimensions[$scope.target.currentDimensionKey] = $scope.target.currentDimensionValue;
|
||||
$scope.target.escapedDimensions = this.escapeDimensions($scope.target.dimensions);
|
||||
$scope.target.currentDimensionKey = '';
|
||||
$scope.target.currentDimensionValue = '';
|
||||
$scope.refreshMetricData();
|
||||
|
||||
$scope.addDimensionMode = false;
|
||||
};
|
||||
|
||||
$scope.removeDimension = function(key) {
|
||||
key = key.replace(/\\\$/g, '$');
|
||||
delete $scope.target.dimensions[key];
|
||||
$scope.target.escapedDimensions = this.escapeDimensions($scope.target.dimensions);
|
||||
$scope.refreshMetricData();
|
||||
};
|
||||
|
||||
$scope.escapeDimensions = function(d) {
|
||||
var result = {};
|
||||
_.chain(d)
|
||||
.keys(d)
|
||||
.each(function(k) {
|
||||
var v = d[k];
|
||||
result[k.replace(/\$/g, '\uFF04')] = v.replace(/\$/g, '\$');
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
$scope.statisticsOptionChanged = function() {
|
||||
$scope.refreshMetricData();
|
||||
};
|
||||
|
||||
// TODO: validate target
|
||||
function validateTarget() {
|
||||
var errs = {};
|
||||
|
||||
if ($scope.target.period < 60 || ($scope.target.period % 60) !== 0) {
|
||||
errs.period = 'Period must be at least 60 seconds and must be a multiple of 60';
|
||||
}
|
||||
|
||||
return errs;
|
||||
}
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
115
public/app/plugins/datasource/elasticsearch/bucketAgg.js
Normal file
115
public/app/plugins/datasource/elasticsearch/bucketAgg.js
Normal file
@@ -0,0 +1,115 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'./queryDef',
|
||||
],
|
||||
function (angular, _, queryDef) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.controller('ElasticBucketAggCtrl', function($scope, uiSegmentSrv, $q, $rootScope) {
|
||||
var bucketAggs = $scope.target.bucketAggs;
|
||||
|
||||
$scope.orderByOptions = [];
|
||||
$scope.bucketAggTypes = queryDef.bucketAggTypes;
|
||||
$scope.orderOptions = queryDef.orderOptions;
|
||||
$scope.sizeOptions = queryDef.sizeOptions;
|
||||
$scope.intervalOptions = queryDef.intervalOptions;
|
||||
|
||||
$rootScope.onAppEvent('elastic-query-updated', function() {
|
||||
$scope.validateModel();
|
||||
$scope.updateOrderByOptions();
|
||||
});
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.agg = bucketAggs[$scope.index];
|
||||
$scope.validateModel();
|
||||
};
|
||||
|
||||
$scope.onChangeInternal = function() {
|
||||
$scope.onChange();
|
||||
};
|
||||
|
||||
$scope.onTypeChanged = function() {
|
||||
$scope.agg.settings = {};
|
||||
$scope.showOptions = false;
|
||||
|
||||
$scope.validateModel();
|
||||
$scope.onChange();
|
||||
};
|
||||
|
||||
$scope.validateModel = function() {
|
||||
$scope.index = _.indexOf(bucketAggs, $scope.agg);
|
||||
$scope.isFirst = $scope.index === 0;
|
||||
$scope.isLast = $scope.index === bucketAggs.length - 1;
|
||||
|
||||
var settingsLinkText = "";
|
||||
var settings = $scope.agg.settings || {};
|
||||
|
||||
switch($scope.agg.type) {
|
||||
case 'terms': {
|
||||
settings.order = settings.order || "asc";
|
||||
settings.size = settings.size || "0";
|
||||
settings.orderBy = settings.orderBy || "_term";
|
||||
|
||||
if (settings.size !== '0') {
|
||||
settingsLinkText = queryDef.describeOrder(settings.order) + ' ' + settings.size + ', ';
|
||||
}
|
||||
|
||||
settingsLinkText += 'Order by: ' + queryDef.describeOrderBy(settings.orderBy, $scope.target);
|
||||
|
||||
if (settings.size === '0') {
|
||||
settingsLinkText += ' (' + settings.order + ')';
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'date_histogram': {
|
||||
settings.interval = settings.interval || 'auto';
|
||||
$scope.agg.field = $scope.target.timeField;
|
||||
settingsLinkText = 'Interval: ' + settings.interval;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.settingsLinkText = settingsLinkText;
|
||||
$scope.agg.settings = settings;
|
||||
return true;
|
||||
};
|
||||
|
||||
$scope.toggleOptions = function() {
|
||||
$scope.showOptions = !$scope.showOptions;
|
||||
$scope.updateOrderByOptions();
|
||||
};
|
||||
|
||||
$scope.updateOrderByOptions = function() {
|
||||
$scope.orderByOptions = queryDef.getOrderByOptions($scope.target);
|
||||
};
|
||||
|
||||
$scope.addBucketAgg = function() {
|
||||
// if last is date histogram add it before
|
||||
var lastBucket = bucketAggs[bucketAggs.length - 1];
|
||||
var addIndex = bucketAggs.length - 1;
|
||||
|
||||
if (lastBucket && lastBucket.type === 'date_histogram') {
|
||||
addIndex - 1;
|
||||
}
|
||||
|
||||
var id = _.reduce($scope.target.bucketAggs.concat($scope.target.metrics), function(max, val) {
|
||||
return parseInt(val.id) > max ? parseInt(val.id) : max;
|
||||
}, 0);
|
||||
|
||||
bucketAggs.splice(addIndex, 0, {type: "terms", field: "select field", id: (id+1).toString()});
|
||||
$scope.onChange();
|
||||
};
|
||||
|
||||
$scope.removeBucketAgg = function() {
|
||||
bucketAggs.splice($scope.index, 1);
|
||||
$scope.onChange();
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,12 +1,15 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'config',
|
||||
'kbn',
|
||||
'moment',
|
||||
'kbn',
|
||||
'./queryBuilder',
|
||||
'./indexPattern',
|
||||
'./elasticResponse',
|
||||
'./queryCtrl',
|
||||
'./directives'
|
||||
],
|
||||
function (angular, _, config, kbn, moment) {
|
||||
function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticResponse) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
@@ -19,15 +22,16 @@ function (angular, _, config, kbn, moment) {
|
||||
this.url = datasource.url;
|
||||
this.name = datasource.name;
|
||||
this.index = datasource.index;
|
||||
this.searchMaxResults = config.search.max_results || 20;
|
||||
|
||||
this.saveTemp = _.isUndefined(datasource.save_temp) ? true : datasource.save_temp;
|
||||
this.saveTempTTL = _.isUndefined(datasource.save_temp_ttl) ? '30d' : datasource.save_temp_ttl;
|
||||
this.timeField = datasource.jsonData.timeField;
|
||||
this.indexPattern = new IndexPattern(datasource.index, datasource.jsonData.interval);
|
||||
this.queryBuilder = new ElasticQueryBuilder({
|
||||
timeField: this.timeField
|
||||
});
|
||||
}
|
||||
|
||||
ElasticDatasource.prototype._request = function(method, url, index, data) {
|
||||
ElasticDatasource.prototype._request = function(method, url, data) {
|
||||
var options = {
|
||||
url: this.url + "/" + index + url,
|
||||
url: this.url + "/" + url,
|
||||
method: method,
|
||||
data: data
|
||||
};
|
||||
@@ -43,14 +47,14 @@ function (angular, _, config, kbn, moment) {
|
||||
};
|
||||
|
||||
ElasticDatasource.prototype._get = function(url) {
|
||||
return this._request('GET', url, this.index)
|
||||
return this._request('GET', this.indexPattern.getIndexForToday() + url)
|
||||
.then(function(results) {
|
||||
return results.data;
|
||||
});
|
||||
};
|
||||
|
||||
ElasticDatasource.prototype._post = function(url, data) {
|
||||
return this._request('POST', url, this.index, data)
|
||||
return this._request('POST', url, data)
|
||||
.then(function(results) {
|
||||
return results.data;
|
||||
});
|
||||
@@ -78,7 +82,7 @@ function (angular, _, config, kbn, moment) {
|
||||
"size": 10000
|
||||
};
|
||||
|
||||
return this._request('POST', '/_search', annotation.index, data).then(function(results) {
|
||||
return this._request('POST', annotation.index + '/_search', data).then(function(results) {
|
||||
var list = [];
|
||||
var hits = results.data.hits.hits;
|
||||
|
||||
@@ -125,175 +129,92 @@ function (angular, _, config, kbn, moment) {
|
||||
});
|
||||
};
|
||||
|
||||
ElasticDatasource.prototype._getDashboardWithSlug = function(id) {
|
||||
return this._get('/dashboard/' + kbn.slugifyForUrl(id))
|
||||
.then(function(result) {
|
||||
return angular.fromJson(result._source.dashboard);
|
||||
}, function() {
|
||||
throw "Dashboard not found";
|
||||
});
|
||||
};
|
||||
|
||||
ElasticDatasource.prototype.getDashboard = function(id, isTemp) {
|
||||
var url = '/dashboard/' + id;
|
||||
if (isTemp) { url = '/temp/' + id; }
|
||||
|
||||
var self = this;
|
||||
return this._get(url)
|
||||
.then(function(result) {
|
||||
return angular.fromJson(result._source.dashboard);
|
||||
}, function(data) {
|
||||
if(data.status === 0) {
|
||||
throw "Could not contact Elasticsearch. Please ensure that Elasticsearch is reachable from your browser.";
|
||||
} else {
|
||||
// backward compatible fallback
|
||||
return self._getDashboardWithSlug(id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ElasticDatasource.prototype.saveDashboard = function(dashboard) {
|
||||
var title = dashboard.title;
|
||||
var temp = dashboard.temp;
|
||||
if (temp) { delete dashboard.temp; }
|
||||
|
||||
var data = {
|
||||
user: 'guest',
|
||||
group: 'guest',
|
||||
title: title,
|
||||
tags: dashboard.tags,
|
||||
dashboard: angular.toJson(dashboard)
|
||||
};
|
||||
|
||||
if (temp) {
|
||||
return this._saveTempDashboard(data);
|
||||
}
|
||||
else {
|
||||
|
||||
var id = encodeURIComponent(kbn.slugifyForUrl(title));
|
||||
var self = this;
|
||||
|
||||
return this._request('PUT', '/dashboard/' + id, this.index, data)
|
||||
.then(function(results) {
|
||||
self._removeUnslugifiedDashboard(results, title, id);
|
||||
return { title: title, url: '/dashboard/db/' + id };
|
||||
}, function() {
|
||||
throw 'Failed to save to elasticsearch';
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
ElasticDatasource.prototype._removeUnslugifiedDashboard = function(saveResult, title, id) {
|
||||
if (saveResult.statusText !== 'Created') { return; }
|
||||
if (title === id) { return; }
|
||||
|
||||
var self = this;
|
||||
this._get('/dashboard/' + title).then(function() {
|
||||
self.deleteDashboard(title);
|
||||
ElasticDatasource.prototype.testDatasource = function() {
|
||||
return this._get('/_stats').then(function() {
|
||||
return { status: "success", message: "Data source is working", title: "Success" };
|
||||
}, function(err) {
|
||||
if (err.data && err.data.error) {
|
||||
return { status: "error", message: err.data.error, title: "Error" };
|
||||
} else {
|
||||
return { status: "error", message: err.status, title: "Error" };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ElasticDatasource.prototype._saveTempDashboard = function(data) {
|
||||
return this._request('POST', '/temp/?ttl=' + this.saveTempTTL, this.index, data)
|
||||
.then(function(result) {
|
||||
|
||||
var baseUrl = window.location.href.replace(window.location.hash,'');
|
||||
var url = baseUrl + "#dashboard/temp/" + result.data._id;
|
||||
|
||||
return { title: data.title, url: url };
|
||||
|
||||
}, function(err) {
|
||||
throw "Failed to save to temp dashboard to elasticsearch " + err.data;
|
||||
});
|
||||
ElasticDatasource.prototype.getQueryHeader = function(timeRange) {
|
||||
var header = {search_type: "count", "ignore_unavailable": true};
|
||||
var from = kbn.parseDate(timeRange.from);
|
||||
var to = kbn.parseDate(timeRange.to);
|
||||
header.index = this.indexPattern.getIndexList(from, to);
|
||||
return angular.toJson(header);
|
||||
};
|
||||
|
||||
ElasticDatasource.prototype.deleteDashboard = function(id) {
|
||||
return this._request('DELETE', '/dashboard/' + id, this.index)
|
||||
.then(function(result) {
|
||||
return result.data._id;
|
||||
}, function(err) {
|
||||
throw err.data;
|
||||
});
|
||||
ElasticDatasource.prototype.query = function(options) {
|
||||
var payload = "";
|
||||
var target;
|
||||
var sentTargets = [];
|
||||
|
||||
var header = this.getQueryHeader(options.range);
|
||||
var timeFrom = this.translateTime(options.range.from);
|
||||
var timeTo = this.translateTime(options.range.to);
|
||||
|
||||
for (var i = 0; i < options.targets.length; i++) {
|
||||
target = options.targets[i];
|
||||
if (target.hide) {return;}
|
||||
|
||||
var esQuery = this.queryBuilder.build(target, timeFrom, timeTo);
|
||||
payload += header + '\n';
|
||||
payload += angular.toJson(esQuery) + '\n';
|
||||
|
||||
sentTargets.push(target);
|
||||
}
|
||||
|
||||
payload = payload.replace(/\$interval/g, options.interval);
|
||||
payload = payload.replace(/\$timeFrom/g, this.translateTime(options.range.from));
|
||||
payload = payload.replace(/\$timeTo/g, this.translateTime(options.range.to));
|
||||
payload = payload.replace(/\$maxDataPoints/g, options.maxDataPoints);
|
||||
payload = templateSrv.replace(payload, options.scopedVars);
|
||||
|
||||
return this._post('/_msearch?search_type=count', payload).then(function(res) {
|
||||
return new ElasticResponse(sentTargets, res).getTimeSeries();
|
||||
});
|
||||
};
|
||||
|
||||
ElasticDatasource.prototype.searchDashboards = function(queryString) {
|
||||
var endsInOpen = function(string, opener, closer) {
|
||||
var character;
|
||||
var count = 0;
|
||||
for (var i = 0, len = string.length; i < len; i++) {
|
||||
character = string[i];
|
||||
|
||||
if (character === opener) {
|
||||
count++;
|
||||
} else if (character === closer) {
|
||||
count--;
|
||||
}
|
||||
}
|
||||
|
||||
return count > 0;
|
||||
};
|
||||
|
||||
var tagsOnly = queryString.indexOf('tags!:') === 0;
|
||||
if (tagsOnly) {
|
||||
var tagsQuery = queryString.substring(6, queryString.length);
|
||||
queryString = 'tags:' + tagsQuery + '*';
|
||||
}
|
||||
else {
|
||||
if (queryString.length === 0) {
|
||||
queryString = 'title:';
|
||||
}
|
||||
|
||||
// make this a partial search if we're not in some reserved portion of the language, comments on conditionals, in order:
|
||||
// 1. ends in reserved character, boosting, boolean operator ( -foo)
|
||||
// 2. typing a reserved word like AND, OR, NOT
|
||||
// 3. open parens (groupiing)
|
||||
// 4. open " (term phrase)
|
||||
// 5. open [ (range)
|
||||
// 6. open { (range)
|
||||
// see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html#query-string-syntax
|
||||
if (!queryString.match(/(\*|\]|}|~|\)|"|^\d+|\s[\-+]\w+)$/) &&
|
||||
!queryString.match(/[A-Z]$/) &&
|
||||
!endsInOpen(queryString, '(', ')') &&
|
||||
!endsInOpen(queryString, '"', '"') &&
|
||||
!endsInOpen(queryString, '[', ']') && !endsInOpen(queryString, '[', '}') &&
|
||||
!endsInOpen(queryString, '{', ']') && !endsInOpen(queryString, '{', '}')
|
||||
){
|
||||
queryString += '*';
|
||||
}
|
||||
ElasticDatasource.prototype.translateTime = function(date) {
|
||||
if (_.isString(date)) {
|
||||
return date;
|
||||
}
|
||||
|
||||
var query = {
|
||||
query: { query_string: { query: queryString } },
|
||||
facets: { tags: { terms: { field: "tags", order: "term", size: 50 } } },
|
||||
size: 10000,
|
||||
sort: ["_uid"],
|
||||
};
|
||||
return date.getTime();
|
||||
};
|
||||
|
||||
return this._post('/dashboard/_search', query)
|
||||
.then(function(results) {
|
||||
if(_.isUndefined(results.hits)) {
|
||||
return { dashboards: [], tags: [] };
|
||||
ElasticDatasource.prototype.metricFindQuery = function() {
|
||||
return this._get('/_mapping').then(function(res) {
|
||||
var fields = {};
|
||||
|
||||
for (var indexName in res) {
|
||||
var index = res[indexName];
|
||||
var mappings = index.mappings;
|
||||
if (!mappings) { continue; }
|
||||
for (var typeName in mappings) {
|
||||
var properties = mappings[typeName].properties;
|
||||
for (var field in properties) {
|
||||
var prop = properties[field];
|
||||
if (prop.type && field[0] !== '_') {
|
||||
fields[field] = prop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var resultsHits = results.hits.hits;
|
||||
var displayHits = { dashboards: [], tags: results.facets.tags.terms || [] };
|
||||
|
||||
for (var i = 0, len = resultsHits.length; i < len; i++) {
|
||||
var hit = resultsHits[i];
|
||||
displayHits.dashboards.push({
|
||||
id: hit._id,
|
||||
title: hit._source.title,
|
||||
tags: hit._source.tags
|
||||
});
|
||||
}
|
||||
|
||||
displayHits.tagsOnly = tagsOnly;
|
||||
return displayHits;
|
||||
fields = _.map(_.keys(fields), function(field) {
|
||||
return {text: field};
|
||||
});
|
||||
|
||||
return fields;
|
||||
});
|
||||
};
|
||||
|
||||
return ElasticDatasource;
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,13 +1,51 @@
|
||||
define([
|
||||
'angular',
|
||||
'./bucketAgg',
|
||||
'./metricAgg',
|
||||
],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.directive('metricQueryEditorElasticsearch', function() {
|
||||
return {controller: 'ElasticQueryCtrl', templateUrl: 'app/plugins/datasource/elasticsearch/partials/query.editor.html'};
|
||||
});
|
||||
|
||||
module.directive('metricQueryOptionsElasticsearch', function() {
|
||||
return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/query.options.html'};
|
||||
});
|
||||
|
||||
module.directive('annotationsQueryEditorElasticsearch', function() {
|
||||
return {templateUrl: 'app/plugins/datasource/elasticsearch/partials/annotations.editor.html'};
|
||||
});
|
||||
|
||||
module.directive('elasticMetricAgg', function() {
|
||||
return {
|
||||
templateUrl: 'app/plugins/datasource/elasticsearch/partials/metricAgg.html',
|
||||
controller: 'ElasticMetricAggCtrl',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
target: "=",
|
||||
index: "=",
|
||||
onChange: "&",
|
||||
getFields: "&",
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.directive('elasticBucketAgg', function() {
|
||||
return {
|
||||
templateUrl: 'app/plugins/datasource/elasticsearch/partials/bucketAgg.html',
|
||||
controller: 'ElasticBucketAggCtrl',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
target: "=",
|
||||
index: "=",
|
||||
onChange: "&",
|
||||
getFields: "&",
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
187
public/app/plugins/datasource/elasticsearch/elasticResponse.js
Normal file
187
public/app/plugins/datasource/elasticsearch/elasticResponse.js
Normal file
@@ -0,0 +1,187 @@
|
||||
define([
|
||||
"lodash",
|
||||
"./queryDef"
|
||||
],
|
||||
function (_, queryDef) {
|
||||
'use strict';
|
||||
|
||||
function ElasticResponse(targets, response) {
|
||||
this.targets = targets;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
// This is quite complex
|
||||
// neeed to recurise down the nested buckets to build series
|
||||
ElasticResponse.prototype.processBuckets = function(aggs, target, seriesList, level, props) {
|
||||
var value, metric, i, y, bucket, aggDef, esAgg, newSeries;
|
||||
|
||||
aggDef = target.bucketAggs[level];
|
||||
esAgg = aggs[aggDef.id];
|
||||
|
||||
if (level < target.bucketAggs.length - 1) {
|
||||
for (i = 0; i < esAgg.buckets.length; i++) {
|
||||
bucket = esAgg.buckets[i];
|
||||
props = _.clone(props);
|
||||
props[aggDef.field] = bucket.key;
|
||||
this.processBuckets(bucket, target, seriesList, level+1, props);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (y = 0; y < target.metrics.length; y++) {
|
||||
metric = target.metrics[y];
|
||||
|
||||
switch(metric.type) {
|
||||
case 'count': {
|
||||
newSeries = { datapoints: [], metric: 'count', props: props};
|
||||
for (i = 0; i < esAgg.buckets.length; i++) {
|
||||
bucket = esAgg.buckets[i];
|
||||
value = bucket.doc_count;
|
||||
newSeries.datapoints.push([value, bucket.key]);
|
||||
}
|
||||
seriesList.push(newSeries);
|
||||
break;
|
||||
}
|
||||
case 'percentiles': {
|
||||
if (esAgg.buckets.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
var firstBucket = esAgg.buckets[0];
|
||||
var percentiles = firstBucket[metric.id].values;
|
||||
|
||||
for (var percentileName in percentiles) {
|
||||
newSeries = {datapoints: [], metric: 'p' + percentileName, props: props};
|
||||
|
||||
for (i = 0; i < esAgg.buckets.length; i++) {
|
||||
bucket = esAgg.buckets[i];
|
||||
var values = bucket[metric.id].values;
|
||||
newSeries.datapoints.push([values[percentileName], bucket.key]);
|
||||
}
|
||||
seriesList.push(newSeries);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'extended_stats': {
|
||||
for (var statName in metric.meta) {
|
||||
if (!metric.meta[statName]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
newSeries = {datapoints: [], metric: statName, props: props};
|
||||
|
||||
for (i = 0; i < esAgg.buckets.length; i++) {
|
||||
bucket = esAgg.buckets[i];
|
||||
var stats = bucket[metric.id];
|
||||
|
||||
// add stats that are in nested obj to top level obj
|
||||
stats.std_deviation_bounds_upper = stats.std_deviation_bounds.upper;
|
||||
stats.std_deviation_bounds_lower = stats.std_deviation_bounds.lower;
|
||||
|
||||
newSeries.datapoints.push([stats[statName], bucket.key]);
|
||||
}
|
||||
|
||||
seriesList.push(newSeries);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
newSeries = { datapoints: [], metric: metric.type, field: metric.field, props: props};
|
||||
for (i = 0; i < esAgg.buckets.length; i++) {
|
||||
bucket = esAgg.buckets[i];
|
||||
value = bucket[metric.id].value;
|
||||
newSeries.datapoints.push([value, bucket.key]);
|
||||
}
|
||||
seriesList.push(newSeries);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ElasticResponse.prototype._getMetricName = function(metric) {
|
||||
var metricDef = _.findWhere(queryDef.metricAggTypes, {value: metric});
|
||||
if (!metricDef) {
|
||||
metricDef = _.findWhere(queryDef.extendedStats, {value: metric});
|
||||
}
|
||||
|
||||
return metricDef ? metricDef.text : metric;
|
||||
};
|
||||
|
||||
ElasticResponse.prototype._getSeriesName = function(series, target, metricTypeCount) {
|
||||
var metricName = this._getMetricName(series.metric);
|
||||
|
||||
if (target.alias) {
|
||||
var regex = /\{\{([\s\S]+?)\}\}/g;
|
||||
|
||||
return target.alias.replace(regex, function(match, g1, g2) {
|
||||
var group = g1 || g2;
|
||||
|
||||
if (group.indexOf('term ') === 0) { return series.props[group.substring(5)]; }
|
||||
if (series.props[group]) { return series.props[group]; }
|
||||
if (group === 'metric') { return metricName; }
|
||||
if (group === 'field') { return series.field; }
|
||||
|
||||
return match;
|
||||
});
|
||||
}
|
||||
|
||||
if (series.field) {
|
||||
metricName += ' ' + series.field;
|
||||
}
|
||||
|
||||
var propKeys = _.keys(series.props);
|
||||
if (propKeys.length === 0) {
|
||||
return metricName;
|
||||
}
|
||||
|
||||
var name = '';
|
||||
for (var propName in series.props) {
|
||||
name += series.props[propName] + ' ';
|
||||
}
|
||||
|
||||
if (metricTypeCount === 1) {
|
||||
return name.trim();
|
||||
}
|
||||
|
||||
return name.trim() + ' ' + metricName;
|
||||
};
|
||||
|
||||
ElasticResponse.prototype.nameSeries = function(seriesList, target) {
|
||||
var metricTypeCount = _.uniq(_.pluck(seriesList, 'metric')).length;
|
||||
var fieldNameCount = _.uniq(_.pluck(seriesList, 'field')).length;
|
||||
|
||||
for (var i = 0; i < seriesList.length; i++) {
|
||||
var series = seriesList[i];
|
||||
series.target = this._getSeriesName(series, target, metricTypeCount, fieldNameCount);
|
||||
}
|
||||
};
|
||||
|
||||
ElasticResponse.prototype.getTimeSeries = function() {
|
||||
var seriesList = [];
|
||||
|
||||
for (var i = 0; i < this.response.responses.length; i++) {
|
||||
var response = this.response.responses[i];
|
||||
if (response.error) {
|
||||
throw { message: response.error };
|
||||
}
|
||||
|
||||
var aggregations = response.aggregations;
|
||||
var target = this.targets[i];
|
||||
var tmpSeriesList = [];
|
||||
|
||||
this.processBuckets(aggregations, target, tmpSeriesList, 0, {});
|
||||
this.nameSeries(tmpSeriesList, target);
|
||||
|
||||
for (var y = 0; y < tmpSeriesList.length; y++) {
|
||||
seriesList.push(tmpSeriesList[y]);
|
||||
}
|
||||
}
|
||||
|
||||
return { data: seriesList };
|
||||
};
|
||||
|
||||
return ElasticResponse;
|
||||
});
|
||||
48
public/app/plugins/datasource/elasticsearch/indexPattern.js
Normal file
48
public/app/plugins/datasource/elasticsearch/indexPattern.js
Normal file
@@ -0,0 +1,48 @@
|
||||
define([
|
||||
'lodash',
|
||||
'moment',
|
||||
],
|
||||
function (_, moment) {
|
||||
'use strict';
|
||||
|
||||
function IndexPattern(pattern, interval) {
|
||||
this.pattern = pattern;
|
||||
this.interval = interval;
|
||||
}
|
||||
|
||||
IndexPattern.intervalMap = {
|
||||
"Hours": { startOf: 'hour', amount: 'hours'},
|
||||
"Daily": { startOf: 'day', amount: 'days'},
|
||||
"Weekly": { startOf: 'isoWeek', amount: 'weeks'},
|
||||
"Monthly": { startOf: 'month', amount: 'months'},
|
||||
"Yearly": { startOf: 'year', amount: 'years'},
|
||||
};
|
||||
|
||||
IndexPattern.prototype.getIndexForToday = function() {
|
||||
if (this.interval) {
|
||||
return moment().format(this.pattern);
|
||||
} else {
|
||||
return this.pattern;
|
||||
}
|
||||
};
|
||||
|
||||
IndexPattern.prototype.getIndexList = function(from, to) {
|
||||
if (!this.interval) {
|
||||
return this.pattern;
|
||||
}
|
||||
|
||||
var intervalInfo = IndexPattern.intervalMap[this.interval];
|
||||
var start = moment(from).utc().startOf(intervalInfo.startOf);
|
||||
var end = moment(to).utc().startOf(intervalInfo.startOf).valueOf();
|
||||
var indexList = [];
|
||||
|
||||
while (start <= end) {
|
||||
indexList.push(start.format(this.pattern));
|
||||
start.add(1, intervalInfo.amount);
|
||||
}
|
||||
|
||||
return indexList;
|
||||
};
|
||||
|
||||
return IndexPattern;
|
||||
});
|
||||
93
public/app/plugins/datasource/elasticsearch/metricAgg.js
Normal file
93
public/app/plugins/datasource/elasticsearch/metricAgg.js
Normal file
@@ -0,0 +1,93 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
'./queryDef'
|
||||
],
|
||||
function (angular, _, queryDef) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
|
||||
module.controller('ElasticMetricAggCtrl', function($scope, uiSegmentSrv, $q, $rootScope) {
|
||||
var metricAggs = $scope.target.metrics;
|
||||
|
||||
$scope.metricAggTypes = queryDef.metricAggTypes;
|
||||
$scope.extendedStats = queryDef.extendedStats;
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.agg = metricAggs[$scope.index];
|
||||
$scope.validateModel();
|
||||
};
|
||||
|
||||
$rootScope.onAppEvent('elastic-query-updated', function() {
|
||||
$scope.index = _.indexOf(metricAggs, $scope.agg);
|
||||
$scope.validateModel();
|
||||
});
|
||||
|
||||
$scope.validateModel = function() {
|
||||
$scope.isFirst = $scope.index === 0;
|
||||
$scope.isSingle = metricAggs.length === 1;
|
||||
$scope.settingsLinkText = '';
|
||||
|
||||
if (!$scope.agg.field) {
|
||||
$scope.agg.field = 'select field';
|
||||
}
|
||||
|
||||
switch($scope.agg.type) {
|
||||
case 'percentiles': {
|
||||
$scope.agg.settings.percents = $scope.agg.settings.percents || [25,50,75,95,99];
|
||||
$scope.settingsLinkText = 'values: ' + $scope.agg.settings.percents.join(',');
|
||||
break;
|
||||
}
|
||||
case 'extended_stats': {
|
||||
var stats = _.reduce($scope.agg.meta, function(memo, val, key) {
|
||||
if (val) {
|
||||
var def = _.findWhere($scope.extendedStats, {value: key});
|
||||
memo.push(def.text);
|
||||
}
|
||||
return memo;
|
||||
}, []);
|
||||
$scope.settingsLinkText = 'Stats: ' + stats.join(', ');
|
||||
|
||||
if (stats.length === 0) {
|
||||
$scope.agg.meta.std_deviation_bounds_lower = true;
|
||||
$scope.agg.meta.std_deviation_bounds_upper = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleOptions = function() {
|
||||
$scope.showOptions = !$scope.showOptions;
|
||||
};
|
||||
|
||||
$scope.onTypeChange = function() {
|
||||
$scope.agg.settings = {};
|
||||
$scope.agg.meta = {};
|
||||
$scope.showOptions = false;
|
||||
|
||||
$scope.validateModel();
|
||||
$scope.onChange();
|
||||
};
|
||||
|
||||
$scope.addMetricAgg = function() {
|
||||
var addIndex = metricAggs.length;
|
||||
|
||||
var id = _.reduce($scope.target.bucketAggs.concat($scope.target.metrics), function(max, val) {
|
||||
return parseInt(val.id) > max ? parseInt(val.id) : max;
|
||||
}, 0);
|
||||
|
||||
metricAggs.splice(addIndex, 0, {type: "count", field: "select field", id: (id+1).toString()});
|
||||
$scope.onChange();
|
||||
};
|
||||
|
||||
$scope.removeMetricAgg = function() {
|
||||
metricAggs.splice($scope.index, 1);
|
||||
$scope.onChange();
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
|
||||
<span ng-show="isFirst">Group by</span>
|
||||
<span ng-hide="isFirst">Then by</span>
|
||||
</li>
|
||||
<li>
|
||||
<metric-segment-model property="agg.type" options="bucketAggTypes" on-change="onTypeChanged()" custom="false" css-class="tight-form-item-large"></metric-segment-model>
|
||||
</li>
|
||||
<li>
|
||||
<metric-segment-model property="agg.field" get-options="getFields()" on-change="onChange()" css-class="tight-form-item-xxlarge"></metric-segment>
|
||||
</li>
|
||||
<li class="tight-form-item last" ng-if="settingsLinkText">
|
||||
<a ng-click="toggleOptions()">{{settingsLinkText}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="tight-form-list pull-right">
|
||||
<li class="tight-form-item last" ng-if="isFirst">
|
||||
<a class="pointer" ng-click="addBucketAgg()"><i class="fa fa-plus"></i></a>
|
||||
</li>
|
||||
<li class="tight-form-item last" ng-if="!isLast">
|
||||
<a class="pointer" ng-click="removeBucketAgg()"><i class="fa fa-minus"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="tight-form" ng-if="showOptions">
|
||||
<div class="tight-form-inner-box" ng-if="agg.type === 'date_histogram'">
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 60px">
|
||||
Interval
|
||||
</li>
|
||||
<li>
|
||||
<metric-segment-model property="agg.settings.interval" options="intervalOptions" on-change="onChangeInternal()" css-class="last" custom="true"></metric-segment-model>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tight-form-inner-box" ng-if="agg.type === 'terms'">
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 60px">
|
||||
Order
|
||||
</li>
|
||||
<li>
|
||||
<metric-segment-model property="agg.settings.order" options="orderOptions" on-change="onChangeInternal()" css-class="last"></metric-segment-model>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 60px">
|
||||
Size
|
||||
</li>
|
||||
<li>
|
||||
<metric-segment-model property="agg.settings.size" options="sizeOptions" on-change="onChangeInternal()" css-class="last"></metric-segment-model>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 60px">
|
||||
Order By
|
||||
</li>
|
||||
<li>
|
||||
<metric-segment-model property="agg.settings.orderBy" options="orderByOptions" on-change="onChangeInternal()" css-class="last"></metric-segment-model>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,18 +1,33 @@
|
||||
<div ng-include="httpConfigPartialSrc"></div>
|
||||
|
||||
<br>
|
||||
|
||||
<h5>Elastic search details</h5>
|
||||
|
||||
<div class="tight-form last">
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 80px">
|
||||
<li class="tight-form-item" style="width: 144px">
|
||||
Index name
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="tight-form-input input-xlarge" ng-model='current.database' placeholder="" required></input>
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
Pattern
|
||||
</li>
|
||||
<li>
|
||||
<select class="input-medium tight-form-input" ng-model="current.jsonData.interval" ng-options="f.value as f.name for f in indexPatternTypes" ng-change="indexPatternTypeChanged()" ></select>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 144px">
|
||||
Time field name
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="tight-form-input input-xlarge" ng-model='current.jsonData.timeField' placeholder="" required ng-init="current.jsonData.timeField = current.jsonData.timeField || '@timestamp'"></input>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item query-keyword tight-form-align" style="width: 75px;">
|
||||
Metric
|
||||
</li>
|
||||
<li>
|
||||
<metric-segment-model property="agg.type" options="metricAggTypes" on-change="onTypeChange()" custom="false" css-class="tight-form-item-large"></metric-segment-model>
|
||||
</li>
|
||||
<li ng-if="agg.type !== 'count'">
|
||||
<metric-segment-model property="agg.field" get-options="getFields()" on-change="onChange()" css-class="tight-form-item-xxlarge"></metric-segment>
|
||||
</li>
|
||||
<li class="tight-form-item last" ng-if="settingsLinkText">
|
||||
<a ng-click="toggleOptions()">{{settingsLinkText}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="tight-form-list pull-right">
|
||||
<li class="tight-form-item last" ng-if="isFirst">
|
||||
<a class="pointer" ng-click="addMetricAgg()"><i class="fa fa-plus"></i></a>
|
||||
</li>
|
||||
<li class="tight-form-item last" ng-if="!isSingle">
|
||||
<a class="pointer" ng-click="removeMetricAgg()"><i class="fa fa-minus"></i></a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="tight-form" ng-if="showOptions">
|
||||
<div class="tight-form-inner-box">
|
||||
<div class="tight-form last" ng-if="agg.type === 'percentiles'">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item">
|
||||
Percentiles
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="input-xlarge tight-form-input last" ng-model="agg.settings.percents" array-join ng-blur="onChange()"></input>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div ng-if="agg.type === 'extended_stats'">
|
||||
<div class="tight-form" ng-repeat="stat in extendedStats">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 100px">
|
||||
{{stat.text}}
|
||||
</li>
|
||||
<li class="tight-form-item last">
|
||||
<editor-checkbox text="" model="agg.meta.{{stat.value}}" change="onChange()"></editor-checkbox>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tight-form last" ng-if="agg.type === 'extended_stats'">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="width: 100px">
|
||||
Sigma
|
||||
</li>
|
||||
<li>
|
||||
<input type="number" class="input-mini tight-form-input last" placeholder="3" ng-model="agg.settings.sigma" ng-blur="onChange()"></input>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,81 @@
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list pull-right">
|
||||
<li ng-show="parserError" class="tight-form-item">
|
||||
<a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
|
||||
<i class="fa fa-warning"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="tight-form-item small" ng-show="target.datasource">
|
||||
<em>{{target.datasource}}</em>
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
<div class="dropdown">
|
||||
<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
|
||||
<i class="fa fa-bars"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right" role="menu">
|
||||
<li role="menuitem"><a tabindex="1" ng-click="toggleQueryMode()">Switch editor mode</a></li>
|
||||
<li role="menuitem"><a tabindex="1" ng-click="duplicateDataQuery(target)">Duplicate</a></li>
|
||||
<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index-1)">Move up</a></li>
|
||||
<li role="menuitem"><a tabindex="1" ng-click="moveDataQuery($index, $index+1)">Move down</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="tight-form-item last">
|
||||
<a class="pointer" tabindex="1" ng-click="removeDataQuery(target)">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item" style="min-width: 15px; text-align: center">
|
||||
{{target.refId}}
|
||||
</li>
|
||||
<li>
|
||||
<a class="tight-form-item" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
|
||||
<i class="fa fa-eye"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="tight-form-list" ng-hide="target.rawQuery">
|
||||
<li class="tight-form-item query-keyword" style="width: 75px">
|
||||
Query
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="tight-form-input" style="width: 345px;" ng-model="target.query" spellcheck='false' placeholder="Lucence query" ng-blur="get_data()">
|
||||
</li>
|
||||
<li class="tight-form-item query-keyword">
|
||||
Alias
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="tight-form-input" style="width: 260px;" ng-model="target.alias" spellcheck='false' placeholder="alias patterns (empty = auto)" ng-blur="get_data()">
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div style="padding: 10px" ng-if="target.rawQuery">
|
||||
<textarea ng-model="target.rawQuery" rows="8" spellcheck="false" style="width: 100%; box-sizing: border-box;" ng-blur="queryUpdated()"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-hide="target.rawQuery">
|
||||
<div ng-repeat="agg in target.metrics">
|
||||
<elastic-metric-agg
|
||||
target="target" index="$index"
|
||||
get-fields="getFields()"
|
||||
on-change="queryUpdated()">
|
||||
</elastic-metric-agg>
|
||||
</div>
|
||||
|
||||
<div ng-repeat="agg in target.bucketAggs">
|
||||
<elastic-bucket-agg
|
||||
target="target" index="$index"
|
||||
get-fields="getFields()"
|
||||
on-change="queryUpdated()">
|
||||
</elastic-bucket-agg>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,30 @@
|
||||
<section class="grafana-metric-options">
|
||||
<div class="tight-form last">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item tight-form-item-icon">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
</li>
|
||||
<li class="tight-form-item">
|
||||
<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
|
||||
alias patterns
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="pull-left" style="margin-top: 30px;">
|
||||
<div class="grafana-info-box span6" ng-if="editorHelpIndex === 1">
|
||||
<h5>Alias patterns</h5>
|
||||
<ul ng-non-bindable>
|
||||
<li>{{term fieldname}} = replaced with value of term group by</li>
|
||||
<li>{{metric}} = replaced with metric name (ex. Average, Min, Max)</li>
|
||||
<li>{{field}} = replaced with the metric field name</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -12,5 +12,6 @@
|
||||
"annotations": "app/plugins/datasource/elasticsearch/partials/annotations.editor.html"
|
||||
},
|
||||
|
||||
"annotations": true
|
||||
"annotations": true,
|
||||
"metrics": true
|
||||
}
|
||||
|
||||
134
public/app/plugins/datasource/elasticsearch/queryBuilder.js
Normal file
134
public/app/plugins/datasource/elasticsearch/queryBuilder.js
Normal file
@@ -0,0 +1,134 @@
|
||||
define([
|
||||
"angular"
|
||||
],
|
||||
function (angular) {
|
||||
'use strict';
|
||||
|
||||
function ElasticQueryBuilder(options) {
|
||||
this.timeField = options.timeField;
|
||||
}
|
||||
|
||||
ElasticQueryBuilder.prototype.getRangeFilter = function() {
|
||||
var filter = {};
|
||||
filter[this.timeField] = {"gte": "$timeFrom", "lte": "$timeTo"};
|
||||
return filter;
|
||||
};
|
||||
|
||||
ElasticQueryBuilder.prototype.buildTermsAgg = function(aggDef, queryNode, target) {
|
||||
var metricRef, metric, size, y;
|
||||
queryNode.terms = { "field": aggDef.field };
|
||||
|
||||
if (!aggDef.settings) {
|
||||
return queryNode;
|
||||
}
|
||||
|
||||
size = parseInt(aggDef.settings.size, 10);
|
||||
if (size > 0) { queryNode.terms.size = size; }
|
||||
if (aggDef.settings.orderBy !== void 0) {
|
||||
queryNode.terms.order = {};
|
||||
queryNode.terms.order[aggDef.settings.orderBy] = aggDef.settings.order;
|
||||
|
||||
// if metric ref, look it up and add it to this agg level
|
||||
metricRef = parseInt(aggDef.settings.orderBy, 10);
|
||||
if (!isNaN(metricRef)) {
|
||||
for (y = 0; y < target.metrics.length; y++) {
|
||||
metric = target.metrics[y];
|
||||
if (metric.id === aggDef.settings.orderBy) {
|
||||
queryNode.aggs = {};
|
||||
queryNode.aggs[metric.id] = {};
|
||||
queryNode.aggs[metric.id][metric.type] = {field: metric.field};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return queryNode;
|
||||
};
|
||||
|
||||
ElasticQueryBuilder.prototype.getInterval = function(agg) {
|
||||
if (agg.settings && agg.settings.interval !== 'auto') {
|
||||
return agg.settings.interval;
|
||||
} else {
|
||||
return '$interval';
|
||||
}
|
||||
};
|
||||
|
||||
ElasticQueryBuilder.prototype.build = function(target) {
|
||||
if (target.rawQuery) {
|
||||
return angular.fromJson(target.rawQuery);
|
||||
}
|
||||
|
||||
var i, nestedAggs, metric;
|
||||
var query = {
|
||||
"size": 0,
|
||||
"query": {
|
||||
"filtered": {
|
||||
"query": {
|
||||
"query_string": {
|
||||
"analyze_wildcard": true,
|
||||
"query": target.query || '*' ,
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
"bool": {
|
||||
"must": [{"range": this.getRangeFilter()}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
nestedAggs = query;
|
||||
|
||||
for (i = 0; i < target.bucketAggs.length; i++) {
|
||||
var aggDef = target.bucketAggs[i];
|
||||
var esAgg = {};
|
||||
|
||||
switch(aggDef.type) {
|
||||
case 'date_histogram': {
|
||||
esAgg["date_histogram"] = {
|
||||
"interval": this.getInterval(aggDef),
|
||||
"field": this.timeField,
|
||||
"min_doc_count": 1,
|
||||
"extended_bounds": { "min": "$timeFrom", "max": "$timeTo" }
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'terms': {
|
||||
this.buildTermsAgg(aggDef, esAgg, target);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
nestedAggs.aggs = nestedAggs.aggs || {};
|
||||
nestedAggs.aggs[aggDef.id] = esAgg;
|
||||
nestedAggs = esAgg;
|
||||
}
|
||||
|
||||
nestedAggs.aggs = {};
|
||||
|
||||
for (i = 0; i < target.metrics.length; i++) {
|
||||
metric = target.metrics[i];
|
||||
if (metric.type === 'count') {
|
||||
continue;
|
||||
}
|
||||
|
||||
var metricAgg = {field: metric.field};
|
||||
for (var prop in metric.settings) {
|
||||
if (metric.settings.hasOwnProperty(prop) && metric.settings[prop] !== null) {
|
||||
metricAgg[prop] = metric.settings[prop];
|
||||
}
|
||||
}
|
||||
|
||||
var aggField = {};
|
||||
aggField[metric.type] = metricAgg;
|
||||
nestedAggs.aggs[metric.id] = aggField;
|
||||
}
|
||||
|
||||
return query;
|
||||
};
|
||||
|
||||
return ElasticQueryBuilder;
|
||||
|
||||
});
|
||||
69
public/app/plugins/datasource/elasticsearch/queryCtrl.js
Normal file
69
public/app/plugins/datasource/elasticsearch/queryCtrl.js
Normal file
@@ -0,0 +1,69 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
],
|
||||
function (angular, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('ElasticQueryCtrl', function($scope, $timeout, uiSegmentSrv, templateSrv) {
|
||||
|
||||
$scope.init = function() {
|
||||
var target = $scope.target;
|
||||
if (!target) { return; }
|
||||
|
||||
target.metrics = target.metrics || [{ type: 'count', id: '1' }];
|
||||
target.bucketAggs = target.bucketAggs || [{type: 'date_histogram', id: '2', settings: {interval: 'auto'}}];
|
||||
target.timeField = $scope.datasource.timeField;
|
||||
};
|
||||
|
||||
$scope.getFields = function() {
|
||||
return $scope.datasource.metricFindQuery('fields()')
|
||||
.then($scope.transformToSegments(false))
|
||||
.then(null, $scope.handleQueryError);
|
||||
};
|
||||
|
||||
$scope.queryUpdated = function() {
|
||||
var newJson = angular.toJson($scope.datasource.queryBuilder.build($scope.target), true);
|
||||
if (newJson !== $scope.oldQueryRaw) {
|
||||
$scope.rawQueryOld = newJson;
|
||||
$scope.get_data();
|
||||
}
|
||||
|
||||
$scope.appEvent('elastic-query-updated');
|
||||
};
|
||||
|
||||
$scope.transformToSegments = function(addTemplateVars) {
|
||||
return function(results) {
|
||||
var segments = _.map(results, function(segment) {
|
||||
return uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable });
|
||||
});
|
||||
|
||||
if (addTemplateVars) {
|
||||
_.each(templateSrv.variables, function(variable) {
|
||||
segments.unshift(uiSegmentSrv.newSegment({ type: 'template', value: '$' + variable.name, expandable: true }));
|
||||
});
|
||||
}
|
||||
return segments;
|
||||
};
|
||||
};
|
||||
|
||||
$scope.handleQueryError = function(err) {
|
||||
$scope.parserError = err.message || 'Failed to issue metric query';
|
||||
return [];
|
||||
};
|
||||
|
||||
$scope.toggleQueryMode = function () {
|
||||
if ($scope.target.rawQuery) {
|
||||
delete $scope.target.rawQuery;
|
||||
} else {
|
||||
$scope.target.rawQuery = $scope.rawQueryOld;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
103
public/app/plugins/datasource/elasticsearch/queryDef.js
Normal file
103
public/app/plugins/datasource/elasticsearch/queryDef.js
Normal file
@@ -0,0 +1,103 @@
|
||||
define([
|
||||
'lodash'
|
||||
],
|
||||
function (_) {
|
||||
'use strict';
|
||||
|
||||
return {
|
||||
metricAggTypes: [
|
||||
{text: "Count", value: 'count' },
|
||||
{text: "Average", value: 'avg' },
|
||||
{text: "Sum", value: 'sum' },
|
||||
{text: "Max", value: 'max' },
|
||||
{text: "Min", value: 'min' },
|
||||
{text: "Extended Stats", value: 'extended_stats' },
|
||||
{text: "Percentiles", value: 'percentiles' },
|
||||
{text: "Unique Count", value: "cardinality" }
|
||||
],
|
||||
|
||||
bucketAggTypes: [
|
||||
{text: "Terms", value: 'terms' },
|
||||
{text: "Date Histogram", value: 'date_histogram' },
|
||||
],
|
||||
|
||||
orderByOptions: [
|
||||
{text: "Doc Count", value: '_count' },
|
||||
{text: "Term value", value: '_term' },
|
||||
],
|
||||
|
||||
orderOptions: [
|
||||
{text: "Top", value: 'desc' },
|
||||
{text: "Bottom", value: 'asc' },
|
||||
],
|
||||
|
||||
sizeOptions: [
|
||||
{text: "No limit", value: '0' },
|
||||
{text: "1", value: '1' },
|
||||
{text: "2", value: '2' },
|
||||
{text: "3", value: '4' },
|
||||
{text: "5", value: '5' },
|
||||
{text: "10", value: '10' },
|
||||
{text: "15", value: '15' },
|
||||
{text: "20", value: '20' },
|
||||
],
|
||||
|
||||
extendedStats: [
|
||||
{text: 'Avg', value: 'avg'},
|
||||
{text: 'Min', value: 'min'},
|
||||
{text: 'Max', value: 'max'},
|
||||
{text: 'Sum', value: 'sum'},
|
||||
{text: 'Count', value: 'count'},
|
||||
{text: 'Std Dev', value: 'std_deviation'},
|
||||
{text: 'Std Dev Upper', value: 'std_deviation_bounds_upper'},
|
||||
{text: 'Std Dev Lower', value: 'std_deviation_bounds_lower'},
|
||||
],
|
||||
|
||||
intervalOptions: [
|
||||
{text: 'auto', value: 'auto'},
|
||||
{text: '10s', value: '10s'},
|
||||
{text: '1m', value: '1m'},
|
||||
{text: '5m', value: '5m'},
|
||||
{text: '10m', value: '10m'},
|
||||
{text: '20m', value: '20m'},
|
||||
{text: '1h', value: '1h'},
|
||||
{text: '1d', value: '1d'},
|
||||
],
|
||||
|
||||
getOrderByOptions: function(target) {
|
||||
var self = this;
|
||||
var metricRefs = [];
|
||||
_.each(target.metrics, function(metric) {
|
||||
if (metric.type !== 'count') {
|
||||
metricRefs.push({text: self.describeMetric(metric), value: metric.id});
|
||||
}
|
||||
});
|
||||
|
||||
return this.orderByOptions.concat(metricRefs);
|
||||
},
|
||||
|
||||
describeOrder: function(order) {
|
||||
var def = _.findWhere(this.orderOptions, {value: order});
|
||||
return def.text;
|
||||
},
|
||||
|
||||
describeMetric: function(metric) {
|
||||
var def = _.findWhere(this.metricAggTypes, {value: metric.type});
|
||||
return def.text + ' ' + metric.field;
|
||||
},
|
||||
|
||||
describeOrderBy: function(orderBy, target) {
|
||||
var def = _.findWhere(this.orderByOptions, {value: orderBy});
|
||||
if (def) {
|
||||
return def.text;
|
||||
}
|
||||
var metric = _.findWhere(target.metrics, {id: orderBy});
|
||||
if (metric) {
|
||||
return this.describeMetric(metric);
|
||||
} else {
|
||||
return "metric not found";
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
});
|
||||
@@ -49,24 +49,18 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<input type="text" class="tight-form-clear-input span10"
|
||||
ng-model="target.target"
|
||||
give-focus="target.textEditor"
|
||||
spellcheck='false'
|
||||
ng-model-onblur ng-change="get_data()"
|
||||
ng-show="target.textEditor" />
|
||||
<input type="text" class="tight-form-clear-input span10" ng-model="target.target" give-focus="target.textEditor" spellcheck='false' ng-model-onblur ng-change="get_data()" ng-show="target.textEditor"></input>
|
||||
|
||||
<ul class="tight-form-list" role="menu" ng-hide="target.textEditor">
|
||||
<li ng-repeat="segment in segments" role="menuitem">
|
||||
<metric-segment segment="segment" get-alt-segments="getAltSegments($index)" on-value-changed="segmentValueChanged(segment, $index)"></metric-segment>
|
||||
</li>
|
||||
<li ng-repeat="func in functions">
|
||||
<span graphite-func-editor class="tight-form-item tight-form-func">
|
||||
</span>
|
||||
</li>
|
||||
<li class="dropdown" graphite-add-func>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<ul class="tight-form-list" role="menu" ng-hide="target.textEditor">
|
||||
<li ng-repeat="segment in segments" role="menuitem">
|
||||
<metric-segment segment="segment" get-options="getAltSegments($index)" on-change="segmentValueChanged(segment, $index)"></metric-segment>
|
||||
</li>
|
||||
<li ng-repeat="func in functions">
|
||||
<span graphite-func-editor class="tight-form-item tight-form-func">
|
||||
</span>
|
||||
</li>
|
||||
<li class="dropdown" graphite-add-func>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ function (angular, _, config, gfunc, Parser) {
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('GraphiteQueryCtrl', function($scope, $sce, templateSrv) {
|
||||
module.controller('GraphiteQueryCtrl', function($scope, uiSegmentSrv, templateSrv) {
|
||||
|
||||
$scope.init = function() {
|
||||
if ($scope.target) {
|
||||
@@ -104,7 +104,7 @@ function (angular, _, config, gfunc, Parser) {
|
||||
}
|
||||
|
||||
$scope.segments = _.map(astNode.segments, function(segment) {
|
||||
return new MetricSegment(segment);
|
||||
return uiSegmentSrv.newSegment(segment);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ function (angular, _, config, gfunc, Parser) {
|
||||
|
||||
function checkOtherSegments(fromIndex) {
|
||||
if (fromIndex === 0) {
|
||||
$scope.segments.push(MetricSegment.newSelectMetric());
|
||||
$scope.segments.push(uiSegmentSrv.newSelectMetric());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -129,13 +129,13 @@ function (angular, _, config, gfunc, Parser) {
|
||||
if (segments.length === 0) {
|
||||
if (path !== '') {
|
||||
$scope.segments = $scope.segments.splice(0, fromIndex);
|
||||
$scope.segments.push(MetricSegment.newSelectMetric());
|
||||
$scope.segments.push(uiSegmentSrv.newSelectMetric());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (segments[0].expandable) {
|
||||
if ($scope.segments.length === fromIndex) {
|
||||
$scope.segments.push(MetricSegment.newSelectMetric());
|
||||
$scope.segments.push(uiSegmentSrv.newSelectMetric());
|
||||
}
|
||||
else {
|
||||
return checkOtherSegments(fromIndex + 1);
|
||||
@@ -162,14 +162,14 @@ function (angular, _, config, gfunc, Parser) {
|
||||
|
||||
return $scope.datasource.metricFindQuery(query).then(function(segments) {
|
||||
var altSegments = _.map(segments, function(segment) {
|
||||
return new MetricSegment({ value: segment.text, expandable: segment.expandable });
|
||||
return uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable });
|
||||
});
|
||||
|
||||
if (altSegments.length === 0) { return altSegments; }
|
||||
|
||||
// add template variables
|
||||
_.each(templateSrv.variables, function(variable) {
|
||||
altSegments.unshift(new MetricSegment({
|
||||
altSegments.unshift(uiSegmentSrv.newSegment({
|
||||
type: 'template',
|
||||
value: '$' + variable.name,
|
||||
expandable: true,
|
||||
@@ -177,7 +177,7 @@ function (angular, _, config, gfunc, Parser) {
|
||||
});
|
||||
|
||||
// add wildcard option
|
||||
altSegments.unshift(new MetricSegment('*'));
|
||||
altSegments.unshift(uiSegmentSrv.newSegment('*'));
|
||||
return altSegments;
|
||||
})
|
||||
.then(null, function(err) {
|
||||
@@ -284,25 +284,6 @@ function (angular, _, config, gfunc, Parser) {
|
||||
}
|
||||
};
|
||||
|
||||
function MetricSegment(options) {
|
||||
if (options === '*' || options.value === '*') {
|
||||
this.value = '*';
|
||||
this.html = $sce.trustAsHtml('<i class="fa fa-asterisk"><i>');
|
||||
this.expandable = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.fake = options.fake;
|
||||
this.value = options.value;
|
||||
this.type = options.type;
|
||||
this.expandable = options.expandable;
|
||||
this.html = $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value));
|
||||
}
|
||||
|
||||
MetricSegment.newSelectMetric = function() {
|
||||
return new MetricSegment({value: 'select metric', fake: true});
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
@@ -108,25 +108,5 @@ function (_) {
|
||||
return list;
|
||||
};
|
||||
|
||||
p.createNameForSeries = function(seriesName, groupByColValue) {
|
||||
var regex = /\$(\w+)/g;
|
||||
var segments = seriesName.split('.');
|
||||
|
||||
return this.alias.replace(regex, function(match, group) {
|
||||
if (group === 's') {
|
||||
return seriesName;
|
||||
}
|
||||
else if (group === 'g') {
|
||||
return groupByColValue;
|
||||
}
|
||||
var index = parseInt(group);
|
||||
if (_.isNumber(index) && index < segments.length) {
|
||||
return segments[index];
|
||||
}
|
||||
return match;
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
return InfluxSeries;
|
||||
});
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<metric-segment segment="addFieldSegment" get-alt-segments="getFieldSegments()" on-value-changed="addField()"></metric-segment>
|
||||
<metric-segment segment="addFieldSegment" get-options="getFieldSegments()" on-change="addField()"></metric-segment>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
FROM
|
||||
</li>
|
||||
<li>
|
||||
<metric-segment segment="measurementSegment" get-alt-segments="getMeasurements()" on-value-changed="measurementChanged()"></metric-segment>
|
||||
<metric-segment segment="measurementSegment" get-options="getMeasurements()" on-change="measurementChanged()"></metric-segment>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
</li>
|
||||
|
||||
<li ng-repeat="segment in tagSegments">
|
||||
<metric-segment segment="segment" get-alt-segments="getTagsOrValues(segment, $index)" on-value-changed="tagSegmentUpdated(segment, $index)"></metric-segment>
|
||||
<metric-segment segment="segment" get-options="getTagsOrValues(segment, $index)" on-change="tagSegmentUpdated(segment, $index)"></metric-segment>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
@@ -95,7 +95,7 @@
|
||||
</li>
|
||||
|
||||
<li ng-repeat="segment in groupBySegments">
|
||||
<metric-segment segment="segment" get-alt-segments="getGroupByTagSegments(segment, 0)" on-value-changed="groupByTagUpdated(segment, $index)"></metric-segment>
|
||||
<metric-segment segment="segment" get-options="getGroupByTagSegments(segment, 0)" on-change="groupByTagUpdated(segment, $index)"></metric-segment>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a class="tight-form-item pointer" data-toggle="dropdown" bs-tooltip="'Insert missing values, important when stacking'" data-placement="right">
|
||||
|
||||
@@ -8,7 +8,7 @@ function (angular, _, InfluxQueryBuilder) {
|
||||
|
||||
var module = angular.module('grafana.controllers');
|
||||
|
||||
module.controller('InfluxQueryCtrl', function($scope, $timeout, $sce, templateSrv, $q) {
|
||||
module.controller('InfluxQueryCtrl', function($scope, $timeout, $sce, templateSrv, $q, uiSegmentSrv) {
|
||||
|
||||
$scope.init = function() {
|
||||
if (!$scope.target) { return; }
|
||||
@@ -24,12 +24,12 @@ function (angular, _, InfluxQueryBuilder) {
|
||||
$scope.queryBuilder = new InfluxQueryBuilder(target);
|
||||
|
||||
if (!target.measurement) {
|
||||
$scope.measurementSegment = MetricSegment.newSelectMeasurement();
|
||||
$scope.measurementSegment = uiSegmentSrv.newSelectMeasurement();
|
||||
} else {
|
||||
$scope.measurementSegment = new MetricSegment(target.measurement);
|
||||
$scope.measurementSegment = uiSegmentSrv.newSegment(target.measurement);
|
||||
}
|
||||
|
||||
$scope.addFieldSegment = MetricSegment.newPlusButton();
|
||||
$scope.addFieldSegment = uiSegmentSrv.newPlusButton();
|
||||
|
||||
$scope.tagSegments = [];
|
||||
_.each(target.tags, function(tag) {
|
||||
@@ -42,24 +42,25 @@ function (angular, _, InfluxQueryBuilder) {
|
||||
}
|
||||
|
||||
if (tag.condition) {
|
||||
$scope.tagSegments.push(MetricSegment.newCondition(tag.condition));
|
||||
$scope.tagSegments.push(uiSegmentSrv.newCondition(tag.condition));
|
||||
}
|
||||
$scope.tagSegments.push(new MetricSegment({value: tag.key, type: 'key', cssClass: 'query-segment-key' }));
|
||||
$scope.tagSegments.push(MetricSegment.newOperator(tag.operator));
|
||||
$scope.tagSegments.push(new MetricSegment({value: tag.value, type: 'value', cssClass: 'query-segment-value'}));
|
||||
|
||||
$scope.tagSegments.push(uiSegmentSrv.newKey(tag.Key));
|
||||
$scope.tagSegments.push(uiSegmentSrv.newOperator(tag.operator));
|
||||
$scope.tagSegments.push(uiSegmentSrv.newKeyValue(tag.value));
|
||||
});
|
||||
|
||||
$scope.fixTagSegments();
|
||||
|
||||
$scope.groupBySegments = [];
|
||||
_.each(target.groupByTags, function(tag) {
|
||||
$scope.groupBySegments.push(new MetricSegment(tag));
|
||||
$scope.groupBySegments.push(uiSegmentSrv.newSegment(tag));
|
||||
});
|
||||
|
||||
$scope.groupBySegments.push(MetricSegment.newPlusButton());
|
||||
$scope.groupBySegments.push(uiSegmentSrv.newPlusButton());
|
||||
|
||||
$scope.removeTagFilterSegment = new MetricSegment({fake: true, value: '-- remove tag filter --'});
|
||||
$scope.removeGroupBySegment = new MetricSegment({fake: true, value: '-- remove group by --'});
|
||||
$scope.removeTagFilterSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove tag filter --'});
|
||||
$scope.removeGroupBySegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove group by --'});
|
||||
};
|
||||
|
||||
$scope.fixTagSegments = function() {
|
||||
@@ -67,7 +68,7 @@ function (angular, _, InfluxQueryBuilder) {
|
||||
var lastSegment = $scope.tagSegments[Math.max(count-1, 0)];
|
||||
|
||||
if (!lastSegment || lastSegment.type !== 'plus-button') {
|
||||
$scope.tagSegments.push(MetricSegment.newPlusButton());
|
||||
$scope.tagSegments.push(uiSegmentSrv.newPlusButton());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -80,7 +81,7 @@ function (angular, _, InfluxQueryBuilder) {
|
||||
}
|
||||
|
||||
if (index === $scope.groupBySegments.length-1) {
|
||||
$scope.groupBySegments.push(MetricSegment.newPlusButton());
|
||||
$scope.groupBySegments.push(uiSegmentSrv.newPlusButton());
|
||||
}
|
||||
|
||||
segment.type = 'group-by-key';
|
||||
@@ -131,12 +132,12 @@ function (angular, _, InfluxQueryBuilder) {
|
||||
$scope.transformToSegments = function(addTemplateVars) {
|
||||
return function(results) {
|
||||
var segments = _.map(results, function(segment) {
|
||||
return new MetricSegment({ value: segment.text, expandable: segment.expandable });
|
||||
return uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable });
|
||||
});
|
||||
|
||||
if (addTemplateVars) {
|
||||
_.each(templateSrv.variables, function(variable) {
|
||||
segments.unshift(new MetricSegment({ type: 'template', value: '/$' + variable.name + '$/', expandable: true }));
|
||||
segments.unshift(uiSegmentSrv.newSegment({ type: 'template', value: '/$' + variable.name + '$/', expandable: true }));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -146,14 +147,14 @@ function (angular, _, InfluxQueryBuilder) {
|
||||
|
||||
$scope.getTagsOrValues = function(segment, index) {
|
||||
if (segment.type === 'condition') {
|
||||
return $q.when([new MetricSegment('AND'), new MetricSegment('OR')]);
|
||||
return $q.when([uiSegmentSrv.newSegment('AND'), uiSegmentSrv.newSegment('OR')]);
|
||||
}
|
||||
if (segment.type === 'operator') {
|
||||
var nextValue = $scope.tagSegments[index+1].value;
|
||||
if (/^\/.*\/$/.test(nextValue)) {
|
||||
return $q.when(MetricSegment.newOperators(['=~', '!~']));
|
||||
return $q.when(uiSegmentSrv.newOperators(['=~', '!~']));
|
||||
} else {
|
||||
return $q.when(MetricSegment.newOperators(['=', '<>', '<', '>']));
|
||||
return $q.when(uiSegmentSrv.newOperators(['=', '<>', '<', '>']));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +187,7 @@ function (angular, _, InfluxQueryBuilder) {
|
||||
|
||||
$scope.addField = function() {
|
||||
$scope.target.fields.push({name: $scope.addFieldSegment.value, func: 'mean'});
|
||||
_.extend($scope.addFieldSegment, MetricSegment.newPlusButton());
|
||||
_.extend($scope.addFieldSegment, uiSegmentSrv.newPlusButton());
|
||||
};
|
||||
|
||||
$scope.fieldChanged = function(field) {
|
||||
@@ -217,27 +218,27 @@ function (angular, _, InfluxQueryBuilder) {
|
||||
if (segment.value === $scope.removeTagFilterSegment.value) {
|
||||
$scope.tagSegments.splice(index, 3);
|
||||
if ($scope.tagSegments.length === 0) {
|
||||
$scope.tagSegments.push(MetricSegment.newPlusButton());
|
||||
$scope.tagSegments.push(uiSegmentSrv.newPlusButton());
|
||||
} else if ($scope.tagSegments.length > 2) {
|
||||
$scope.tagSegments.splice(Math.max(index-1, 0), 1);
|
||||
if ($scope.tagSegments[$scope.tagSegments.length-1].type !== 'plus-button') {
|
||||
$scope.tagSegments.push(MetricSegment.newPlusButton());
|
||||
$scope.tagSegments.push(uiSegmentSrv.newPlusButton());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (segment.type === 'plus-button') {
|
||||
if (index > 2) {
|
||||
$scope.tagSegments.splice(index, 0, MetricSegment.newCondition('AND'));
|
||||
$scope.tagSegments.splice(index, 0, uiSegmentSrv.newCondition('AND'));
|
||||
}
|
||||
$scope.tagSegments.push(MetricSegment.newOperator('='));
|
||||
$scope.tagSegments.push(MetricSegment.newFake('select tag value', 'value', 'query-segment-value'));
|
||||
$scope.tagSegments.push(uiSegmentSrv.newOperator('='));
|
||||
$scope.tagSegments.push(uiSegmentSrv.newFake('select tag value', 'value', 'query-segment-value'));
|
||||
segment.type = 'key';
|
||||
segment.cssClass = 'query-segment-key';
|
||||
}
|
||||
|
||||
if ((index+1) === $scope.tagSegments.length) {
|
||||
$scope.tagSegments.push(MetricSegment.newPlusButton());
|
||||
$scope.tagSegments.push(uiSegmentSrv.newPlusButton());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +259,7 @@ function (angular, _, InfluxQueryBuilder) {
|
||||
else if (segment2.type === 'value') {
|
||||
tagOperator = $scope.getTagValueOperator(segment2.value, tags[tagIndex].operator);
|
||||
if (tagOperator) {
|
||||
$scope.tagSegments[index-1] = MetricSegment.newOperator(tagOperator);
|
||||
$scope.tagSegments[index-1] = uiSegmentSrv.newOperator(tagOperator);
|
||||
tags[tagIndex].operator = tagOperator;
|
||||
}
|
||||
tags[tagIndex].value = segment2.value;
|
||||
@@ -285,59 +286,6 @@ function (angular, _, InfluxQueryBuilder) {
|
||||
}
|
||||
};
|
||||
|
||||
function MetricSegment(options) {
|
||||
if (options === '*' || options.value === '*') {
|
||||
this.value = '*';
|
||||
this.html = $sce.trustAsHtml('<i class="fa fa-asterisk"><i>');
|
||||
this.expandable = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_.isString(options)) {
|
||||
this.value = options;
|
||||
this.html = $sce.trustAsHtml(this.value);
|
||||
return;
|
||||
}
|
||||
|
||||
this.cssClass = options.cssClass;
|
||||
this.type = options.type;
|
||||
this.fake = options.fake;
|
||||
this.value = options.value;
|
||||
this.type = options.type;
|
||||
this.expandable = options.expandable;
|
||||
this.html = options.html || $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value));
|
||||
}
|
||||
|
||||
MetricSegment.newSelectMeasurement = function() {
|
||||
return new MetricSegment({value: 'select measurement', fake: true});
|
||||
};
|
||||
|
||||
MetricSegment.newFake = function(text, type, cssClass) {
|
||||
return new MetricSegment({value: text, fake: true, type: type, cssClass: cssClass});
|
||||
};
|
||||
|
||||
MetricSegment.newCondition = function(condition) {
|
||||
return new MetricSegment({value: condition, type: 'condition', cssClass: 'query-keyword' });
|
||||
};
|
||||
|
||||
MetricSegment.newOperator = function(op) {
|
||||
return new MetricSegment({value: op, type: 'operator', cssClass: 'query-segment-operator' });
|
||||
};
|
||||
|
||||
MetricSegment.newOperators = function(ops) {
|
||||
return _.map(ops, function(op) {
|
||||
return new MetricSegment({value: op, type: 'operator', cssClass: 'query-segment-operator' });
|
||||
});
|
||||
};
|
||||
|
||||
MetricSegment.newPlusButton = function() {
|
||||
return new MetricSegment({fake: true, html: '<i class="fa fa-plus "></i>', type: 'plus-button' });
|
||||
};
|
||||
|
||||
MetricSegment.newSelectTagValue = function() {
|
||||
return new MetricSegment({value: 'select tag value', fake: true});
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ define([
|
||||
'./keyboardManager',
|
||||
'./analytics',
|
||||
'./popoverSrv',
|
||||
'./uiSegmentSrv',
|
||||
'./backendSrv',
|
||||
],
|
||||
function () {});
|
||||
|
||||
92
public/app/services/uiSegmentSrv.js
Normal file
92
public/app/services/uiSegmentSrv.js
Normal file
@@ -0,0 +1,92 @@
|
||||
define([
|
||||
'angular',
|
||||
'lodash',
|
||||
],
|
||||
function (angular, _) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.services');
|
||||
|
||||
module.service('uiSegmentSrv', function($sce, templateSrv) {
|
||||
|
||||
function MetricSegment(options) {
|
||||
if (options === '*' || options.value === '*') {
|
||||
this.value = '*';
|
||||
this.html = $sce.trustAsHtml('<i class="fa fa-asterisk"><i>');
|
||||
this.expandable = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_.isString(options)) {
|
||||
this.value = options;
|
||||
this.html = $sce.trustAsHtml(this.value);
|
||||
return;
|
||||
}
|
||||
|
||||
this.cssClass = options.cssClass;
|
||||
this.custom = options.custom;
|
||||
this.type = options.type;
|
||||
this.fake = options.fake;
|
||||
this.value = options.value;
|
||||
this.type = options.type;
|
||||
this.expandable = options.expandable;
|
||||
this.html = options.html || $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value));
|
||||
}
|
||||
|
||||
this.getSegmentForValue = function(value, fallbackText) {
|
||||
if (value) {
|
||||
return this.newSegment(value);
|
||||
} else {
|
||||
return this.newSegment({value: fallbackText, fake: true});
|
||||
}
|
||||
};
|
||||
|
||||
this.newSelectMeasurement = function() {
|
||||
return new MetricSegment({value: 'select measurement', fake: true});
|
||||
};
|
||||
|
||||
this.newFake = function(text, type, cssClass) {
|
||||
return new MetricSegment({value: text, fake: true, type: type, cssClass: cssClass});
|
||||
};
|
||||
|
||||
this.newSegment = function(options) {
|
||||
return new MetricSegment(options);
|
||||
};
|
||||
|
||||
this.newKey = function(key) {
|
||||
return new MetricSegment({value: key, type: 'key', cssClass: 'query-segment-key' });
|
||||
};
|
||||
|
||||
this.newKeyValue = function(value) {
|
||||
return new MetricSegment({value: value, type: 'value', cssClass: 'query-segment-value' });
|
||||
};
|
||||
|
||||
this.newCondition = function(condition) {
|
||||
return new MetricSegment({value: condition, type: 'condition', cssClass: 'query-keyword' });
|
||||
};
|
||||
|
||||
this.newOperator = function(op) {
|
||||
return new MetricSegment({value: op, type: 'operator', cssClass: 'query-segment-operator' });
|
||||
};
|
||||
|
||||
this.newOperators = function(ops) {
|
||||
return _.map(ops, function(op) {
|
||||
return new MetricSegment({value: op, type: 'operator', cssClass: 'query-segment-operator' });
|
||||
});
|
||||
};
|
||||
|
||||
this.newSelectMetric = function() {
|
||||
return new MetricSegment({value: 'select metric', fake: true});
|
||||
};
|
||||
|
||||
this.newPlusButton = function() {
|
||||
return new MetricSegment({fake: true, html: '<i class="fa fa-plus "></i>', type: 'plus-button' });
|
||||
};
|
||||
|
||||
this.newSelectTagValue = function() {
|
||||
return new MetricSegment({value: 'select tag value', fake: true});
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -4,7 +4,7 @@
|
||||
border-right: 1px solid @grafanaTargetBorder;
|
||||
background: @grafanaTargetBackground;
|
||||
|
||||
&:last-child, &.last {
|
||||
&.last {
|
||||
border-bottom: 1px solid @grafanaTargetBorder;
|
||||
}
|
||||
|
||||
@@ -45,10 +45,6 @@
|
||||
|
||||
.tight-form-container {
|
||||
border-bottom: 1px solid @grafanaTargetBorder;
|
||||
|
||||
.tight-form:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tight-form-btn {
|
||||
@@ -214,3 +210,11 @@ select.tight-form-input {
|
||||
padding-left: 66px;
|
||||
}
|
||||
|
||||
.tight-form-item-large { width: 115px; }
|
||||
.tight-form-item-xlarge { width: 150px; }
|
||||
.tight-form-item-xxlarge { width: 200px; }
|
||||
|
||||
.tight-form-inner-box {
|
||||
margin: 20px 0 20px 148px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
154
public/test/specs/cloudwatch-datasource-specs.js
Normal file
154
public/test/specs/cloudwatch-datasource-specs.js
Normal file
@@ -0,0 +1,154 @@
|
||||
define([
|
||||
'helpers',
|
||||
'plugins/datasource/cloudwatch/datasource',
|
||||
'aws-sdk',
|
||||
], function(helpers) {
|
||||
'use strict';
|
||||
|
||||
describe('CloudWatchDatasource', function() {
|
||||
var ctx = new helpers.ServiceTestContext();
|
||||
|
||||
beforeEach(module('grafana.services'));
|
||||
beforeEach(ctx.providePhase(['templateSrv']));
|
||||
beforeEach(ctx.createService('CloudWatchDatasource'));
|
||||
beforeEach(function() {
|
||||
ctx.ds = new ctx.service({
|
||||
jsonData: {
|
||||
defaultRegion: 'us-east-1',
|
||||
access: 'proxy'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('When performing CloudWatch query', function() {
|
||||
var requestParams;
|
||||
|
||||
var query = {
|
||||
range: { from: 'now-1h', to: 'now' },
|
||||
targets: [
|
||||
{
|
||||
region: 'us-east-1',
|
||||
namespace: 'AWS/EC2',
|
||||
metricName: 'CPUUtilization',
|
||||
dimensions: {
|
||||
InstanceId: 'i-12345678'
|
||||
},
|
||||
statistics: {
|
||||
Average: true
|
||||
},
|
||||
period: 300
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var response = {
|
||||
Datapoints: [
|
||||
{
|
||||
Average: 1,
|
||||
Timestamp: 'Wed Dec 31 1969 16:00:00 GMT-0800 (PST)'
|
||||
}
|
||||
],
|
||||
Label: 'CPUUtilization'
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.ds.getCloudWatchClient = function() {
|
||||
return {
|
||||
getMetricStatistics: function(params, callback) {
|
||||
setTimeout(function() {
|
||||
requestParams = params;
|
||||
callback(null, response);
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
it('should generate the correct query', function() {
|
||||
ctx.ds.query(query).then(function() {
|
||||
expect(requestParams.Namespace).to.be(query.targets[0].namespace);
|
||||
expect(requestParams.MetricName).to.be(query.targets[0].metricName);
|
||||
expect(requestParams.Dimensions[0].Name).to.be(Object.keys(query.targets[0].dimensions)[0]);
|
||||
expect(requestParams.Dimensions[0].Value).to.be(query.targets[0].dimensions[Object.keys(query.targets[0].dimensions)[0]]);
|
||||
expect(requestParams.Statistics).to.eql(Object.keys(query.targets[0].statistics));
|
||||
expect(requestParams.Period).to.be(query.targets[0].period);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return series list', function() {
|
||||
ctx.ds.query(query).then(function(result) {
|
||||
var s = Object.keys(query.targets[0].statistics)[0];
|
||||
expect(result.data[0].target).to.be(response.Label + s);
|
||||
expect(result.data[0].datapoints[0][0]).to.be(response.Datapoints[0][s]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('When performing CloudWatch metricFindQuery', function() {
|
||||
var requestParams;
|
||||
|
||||
var response = {
|
||||
Metrics: [
|
||||
{
|
||||
Namespace: 'AWS/EC2',
|
||||
MetricName: 'CPUUtilization',
|
||||
Dimensions: [
|
||||
{
|
||||
Name: 'InstanceId',
|
||||
Value: 'i-12345678'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.ds.getCloudWatchClient = function() {
|
||||
return {
|
||||
listMetrics: function(params, callback) {
|
||||
setTimeout(function() {
|
||||
requestParams = params;
|
||||
callback(null, response);
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
it('should return suggest list for region()', function() {
|
||||
var query = 'region()';
|
||||
ctx.ds.metricFindQuery(query).then(function(result) {
|
||||
expect(result).to.contain('us-east-1');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return suggest list for namespace()', function() {
|
||||
var query = 'namespace()';
|
||||
ctx.ds.metricFindQuery(query).then(function(result) {
|
||||
expect(result).to.contain('AWS/EC2');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return suggest list for metrics()', function() {
|
||||
var query = 'metrics(AWS/EC2)';
|
||||
ctx.ds.metricFindQuery(query).then(function(result) {
|
||||
expect(result).to.contain('CPUUtilization');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return suggest list for dimension_keys()', function() {
|
||||
var query = 'dimension_keys(AWS/EC2)';
|
||||
ctx.ds.metricFindQuery(query).then(function(result) {
|
||||
expect(result).to.contain('InstanceId');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return suggest list for dimension_values()', function() {
|
||||
var query = 'dimension_values(us-east-1,AWS/EC2,CPUUtilization)';
|
||||
ctx.ds.metricFindQuery(query).then(function(result) {
|
||||
expect(result).to.contain('InstanceId');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
52
public/test/specs/elasticsearch-indexPattern-specs.js
Normal file
52
public/test/specs/elasticsearch-indexPattern-specs.js
Normal file
@@ -0,0 +1,52 @@
|
||||
define([
|
||||
'moment',
|
||||
'plugins/datasource/elasticsearch/indexPattern'
|
||||
], function(moment, IndexPattern) {
|
||||
'use strict';
|
||||
|
||||
describe('IndexPattern', function() {
|
||||
|
||||
describe('when getting index for today', function() {
|
||||
it('should return correct index name', function() {
|
||||
var pattern = new IndexPattern('[asd-]YYYY.MM.DD', 'Daily');
|
||||
var expected = 'asd-' + moment().format('YYYY.MM.DD');
|
||||
|
||||
expect(pattern.getIndexForToday()).to.be(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when getting index list for time range', function() {
|
||||
|
||||
describe('no interval', function() {
|
||||
it('should return correct index', function() {
|
||||
var pattern = new IndexPattern('my-metrics');
|
||||
var from = new Date(2015, 4, 30, 1, 2, 3);
|
||||
var to = new Date(2015, 5, 1, 12, 5 , 6);
|
||||
expect(pattern.getIndexList(from, to)).to.eql('my-metrics');
|
||||
});
|
||||
});
|
||||
|
||||
describe('daily', function() {
|
||||
|
||||
it('should return correct index list', function() {
|
||||
var pattern = new IndexPattern('[asd-]YYYY.MM.DD', 'Daily');
|
||||
var from = new Date(2015, 4, 30, 1, 2, 3);
|
||||
var to = new Date(2015, 5, 1, 12, 5 , 6);
|
||||
|
||||
var expected = [
|
||||
'asd-2015.05.29',
|
||||
'asd-2015.05.30',
|
||||
'asd-2015.05.31',
|
||||
'asd-2015.06.01',
|
||||
];
|
||||
|
||||
expect(pattern.getIndexList(from, to)).to.eql(expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
92
public/test/specs/elasticsearch-querybuilder-specs.js
Normal file
92
public/test/specs/elasticsearch-querybuilder-specs.js
Normal file
@@ -0,0 +1,92 @@
|
||||
define([
|
||||
'plugins/datasource/elasticsearch/queryBuilder'
|
||||
], function(ElasticQueryBuilder) {
|
||||
'use strict';
|
||||
|
||||
describe('ElasticQueryBuilder', function() {
|
||||
var builder;
|
||||
|
||||
beforeEach(function() {
|
||||
builder = new ElasticQueryBuilder({timeField: '@timestamp'});
|
||||
});
|
||||
|
||||
it('with defaults', function() {
|
||||
var query = builder.build({
|
||||
metrics: [{type: 'Count', id: '0'}],
|
||||
timeField: '@timestamp',
|
||||
bucketAggs: [{type: 'date_histogram', field: '@timestamp', id: '1'}],
|
||||
});
|
||||
|
||||
expect(query.query.filtered.filter.bool.must[0].range["@timestamp"].gte).to.be("$timeFrom");
|
||||
expect(query.aggs["1"].date_histogram.extended_bounds.min).to.be("$timeFrom");
|
||||
});
|
||||
|
||||
it('with multiple bucket aggs', function() {
|
||||
var query = builder.build({
|
||||
metrics: [{type: 'count', id: '1'}],
|
||||
timeField: '@timestamp',
|
||||
bucketAggs: [
|
||||
{type: 'terms', field: '@host', id: '2'},
|
||||
{type: 'date_histogram', field: '@timestamp', id: '3'}
|
||||
],
|
||||
});
|
||||
|
||||
expect(query.aggs["2"].terms.field).to.be("@host");
|
||||
expect(query.aggs["2"].aggs["3"].date_histogram.field).to.be("@timestamp");
|
||||
});
|
||||
|
||||
it('with select field', function() {
|
||||
var query = builder.build({
|
||||
metrics: [{type: 'avg', field: '@value', id: '1'}],
|
||||
bucketAggs: [{type: 'date_histogram', field: '@timestamp', id: '2'}],
|
||||
}, 100, 1000);
|
||||
|
||||
var aggs = query.aggs["2"].aggs;
|
||||
expect(aggs["1"].avg.field).to.be("@value");
|
||||
});
|
||||
|
||||
it('with term agg and order by metric agg', function() {
|
||||
var query = builder.build({
|
||||
metrics: [
|
||||
{type: 'count', id: '1'},
|
||||
{type: 'avg', field: '@value', id: '5'}
|
||||
],
|
||||
bucketAggs: [
|
||||
{type: 'terms', field: '@host', settings: {size: 5, order: 'asc', orderBy: '5'}, id: '2' },
|
||||
{type: 'date_histogram', field: '@timestamp', id: '3'}
|
||||
],
|
||||
}, 100, 1000);
|
||||
|
||||
var firstLevel = query.aggs["2"];
|
||||
var secondLevel = firstLevel.aggs["3"];
|
||||
|
||||
expect(firstLevel.aggs["5"].avg.field).to.be("@value");
|
||||
expect(secondLevel.aggs["5"].avg.field).to.be("@value");
|
||||
});
|
||||
|
||||
it('with metric percentiles', function() {
|
||||
var query = builder.build({
|
||||
metrics: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'percentiles',
|
||||
field: '@load_time',
|
||||
settings: {
|
||||
percents: [1,2,3,4]
|
||||
}
|
||||
}
|
||||
],
|
||||
bucketAggs: [
|
||||
{type: 'date_histogram', field: '@timestamp', id: '3'}
|
||||
],
|
||||
}, 100, 1000);
|
||||
|
||||
var firstLevel = query.aggs["3"];
|
||||
|
||||
expect(firstLevel.aggs["1"].percentiles.field).to.be("@load_time");
|
||||
expect(firstLevel.aggs["1"].percentiles.percents).to.eql([1,2,3,4]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
32
public/test/specs/elasticsearch-queryctrl-specs.js
Normal file
32
public/test/specs/elasticsearch-queryctrl-specs.js
Normal file
@@ -0,0 +1,32 @@
|
||||
define([
|
||||
'helpers',
|
||||
'plugins/datasource/elasticsearch/queryCtrl',
|
||||
'services/uiSegmentSrv'
|
||||
], function(helpers) {
|
||||
'use strict';
|
||||
|
||||
describe('ElasticQueryCtrl', function() {
|
||||
var ctx = new helpers.ControllerTestContext();
|
||||
|
||||
beforeEach(module('grafana.controllers'));
|
||||
beforeEach(module('grafana.services'));
|
||||
beforeEach(ctx.providePhase());
|
||||
beforeEach(ctx.createControllerPhase('ElasticQueryCtrl'));
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.scope.target = {};
|
||||
ctx.scope.$parent = { get_data: sinon.spy() };
|
||||
|
||||
ctx.scope.datasource = ctx.datasource;
|
||||
ctx.scope.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([]));
|
||||
});
|
||||
|
||||
describe('init', function() {
|
||||
beforeEach(function() {
|
||||
ctx.scope.init();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
355
public/test/specs/elasticsearch-response-specs.js
Normal file
355
public/test/specs/elasticsearch-response-specs.js
Normal file
@@ -0,0 +1,355 @@
|
||||
define([
|
||||
'plugins/datasource/elasticsearch/elasticResponse',
|
||||
], function(ElasticResponse) {
|
||||
'use strict';
|
||||
|
||||
describe('ElasticResponse', function() {
|
||||
var targets;
|
||||
var response;
|
||||
var result;
|
||||
|
||||
describe('simple query and count', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
targets = [{
|
||||
refId: 'A',
|
||||
metrics: [{type: 'count', id: '1'}],
|
||||
bucketAggs: [{type: 'date_histogram', field: '@timestamp', id: '2'}],
|
||||
}];
|
||||
response = {
|
||||
responses: [{
|
||||
aggregations: {
|
||||
"2": {
|
||||
buckets: [
|
||||
{
|
||||
doc_count: 10,
|
||||
key: 1000
|
||||
},
|
||||
{
|
||||
doc_count: 15,
|
||||
key: 2000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
result = new ElasticResponse(targets, response).getTimeSeries();
|
||||
});
|
||||
|
||||
it('should return 1 series', function() {
|
||||
expect(result.data.length).to.be(1);
|
||||
expect(result.data[0].target).to.be('Count');
|
||||
expect(result.data[0].datapoints.length).to.be(2);
|
||||
expect(result.data[0].datapoints[0][0]).to.be(10);
|
||||
expect(result.data[0].datapoints[0][1]).to.be(1000);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('simple query count & avg aggregation', function() {
|
||||
var result;
|
||||
|
||||
beforeEach(function() {
|
||||
targets = [{
|
||||
refId: 'A',
|
||||
metrics: [{type: 'count', id: '1'}, {type: 'avg', field: 'value', id: '2'}],
|
||||
bucketAggs: [{type: 'date_histogram', field: '@timestamp', id: '3'}],
|
||||
}];
|
||||
response = {
|
||||
responses: [{
|
||||
aggregations: {
|
||||
"3": {
|
||||
buckets: [
|
||||
{
|
||||
"2": {value: 88},
|
||||
doc_count: 10,
|
||||
key: 1000
|
||||
},
|
||||
{
|
||||
"2": {value: 99},
|
||||
doc_count: 15,
|
||||
key: 2000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
result = new ElasticResponse(targets, response).getTimeSeries();
|
||||
});
|
||||
|
||||
it('should return 2 series', function() {
|
||||
expect(result.data.length).to.be(2);
|
||||
expect(result.data[0].datapoints.length).to.be(2);
|
||||
expect(result.data[0].datapoints[0][0]).to.be(10);
|
||||
expect(result.data[0].datapoints[0][1]).to.be(1000);
|
||||
|
||||
expect(result.data[1].target).to.be("Average value");
|
||||
expect(result.data[1].datapoints[0][0]).to.be(88);
|
||||
expect(result.data[1].datapoints[1][0]).to.be(99);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('single group by query one metric', function() {
|
||||
var result;
|
||||
|
||||
beforeEach(function() {
|
||||
targets = [{
|
||||
refId: 'A',
|
||||
metrics: [{type: 'count', id: '1'}],
|
||||
bucketAggs: [{type: 'terms', field: 'host', id: '2'}, {type: 'date_histogram', field: '@timestamp', id: '3'}],
|
||||
}];
|
||||
response = {
|
||||
responses: [{
|
||||
aggregations: {
|
||||
"2": {
|
||||
buckets: [
|
||||
{
|
||||
"3": {
|
||||
buckets: [
|
||||
{doc_count: 1, key: 1000},
|
||||
{doc_count: 3, key: 2000}
|
||||
]
|
||||
},
|
||||
doc_count: 4,
|
||||
key: 'server1',
|
||||
},
|
||||
{
|
||||
"3": {
|
||||
buckets: [
|
||||
{doc_count: 2, key: 1000},
|
||||
{doc_count: 8, key: 2000}
|
||||
]
|
||||
},
|
||||
doc_count: 10,
|
||||
key: 'server2',
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
result = new ElasticResponse(targets, response).getTimeSeries();
|
||||
});
|
||||
|
||||
it('should return 2 series', function() {
|
||||
expect(result.data.length).to.be(2);
|
||||
expect(result.data[0].datapoints.length).to.be(2);
|
||||
expect(result.data[0].target).to.be('server1');
|
||||
expect(result.data[1].target).to.be('server2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('single group by query two metrics', function() {
|
||||
var result;
|
||||
|
||||
beforeEach(function() {
|
||||
targets = [{
|
||||
refId: 'A',
|
||||
metrics: [{type: 'count', id: '1'}, {type: 'avg', field: '@value', id: '4'}],
|
||||
bucketAggs: [{type: 'terms', field: 'host', id: '2'}, {type: 'date_histogram', field: '@timestamp', id: '3'}],
|
||||
}];
|
||||
response = {
|
||||
responses: [{
|
||||
aggregations: {
|
||||
"2": {
|
||||
buckets: [
|
||||
{
|
||||
"3": {
|
||||
buckets: [
|
||||
{ "4": {value: 10}, doc_count: 1, key: 1000},
|
||||
{ "4": {value: 12}, doc_count: 3, key: 2000}
|
||||
]
|
||||
},
|
||||
doc_count: 4,
|
||||
key: 'server1',
|
||||
},
|
||||
{
|
||||
"3": {
|
||||
buckets: [
|
||||
{ "4": {value: 20}, doc_count: 1, key: 1000},
|
||||
{ "4": {value: 32}, doc_count: 3, key: 2000}
|
||||
]
|
||||
},
|
||||
doc_count: 10,
|
||||
key: 'server2',
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
result = new ElasticResponse(targets, response).getTimeSeries();
|
||||
});
|
||||
|
||||
it('should return 2 series', function() {
|
||||
expect(result.data.length).to.be(4);
|
||||
expect(result.data[0].datapoints.length).to.be(2);
|
||||
expect(result.data[0].target).to.be('server1 Count');
|
||||
expect(result.data[1].target).to.be('server1 Average @value');
|
||||
expect(result.data[2].target).to.be('server2 Count');
|
||||
expect(result.data[3].target).to.be('server2 Average @value');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with percentiles ', function() {
|
||||
var result;
|
||||
|
||||
beforeEach(function() {
|
||||
targets = [{
|
||||
refId: 'A',
|
||||
metrics: [{type: 'percentiles', settings: {percents: [75, 90]}, id: '1'}],
|
||||
bucketAggs: [{type: 'date_histogram', field: '@timestamp', id: '3'}],
|
||||
}];
|
||||
response = {
|
||||
responses: [{
|
||||
aggregations: {
|
||||
"3": {
|
||||
buckets: [
|
||||
{
|
||||
"1": {values: {"75": 3.3, "90": 5.5}},
|
||||
doc_count: 10,
|
||||
key: 1000
|
||||
},
|
||||
{
|
||||
"1": {values: {"75": 2.3, "90": 4.5}},
|
||||
doc_count: 15,
|
||||
key: 2000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
result = new ElasticResponse(targets, response).getTimeSeries();
|
||||
});
|
||||
|
||||
it('should return 2 series', function() {
|
||||
expect(result.data.length).to.be(2);
|
||||
expect(result.data[0].datapoints.length).to.be(2);
|
||||
expect(result.data[0].target).to.be('p75');
|
||||
expect(result.data[1].target).to.be('p90');
|
||||
expect(result.data[0].datapoints[0][0]).to.be(3.3);
|
||||
expect(result.data[0].datapoints[0][1]).to.be(1000);
|
||||
expect(result.data[1].datapoints[1][0]).to.be(4.5);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with extended_stats', function() {
|
||||
var result;
|
||||
|
||||
beforeEach(function() {
|
||||
targets = [{
|
||||
refId: 'A',
|
||||
metrics: [{type: 'extended_stats', meta: {max: true, std_deviation_bounds_upper: true}, id: '1'}],
|
||||
bucketAggs: [{type: 'terms', field: 'host', id: '3'}, {type: 'date_histogram', id: '4'}],
|
||||
}];
|
||||
response = {
|
||||
responses: [{
|
||||
aggregations: {
|
||||
"3": {
|
||||
buckets: [
|
||||
{
|
||||
key: 'server1',
|
||||
"4": {
|
||||
buckets: [{
|
||||
"1": {max: 10.2, min: 5.5, std_deviation_bounds: {upper: 3, lower: -2}},
|
||||
doc_count: 10,
|
||||
key: 1000
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'server2',
|
||||
"4": {
|
||||
buckets: [{
|
||||
"1": {max: 10.2, min: 5.5, std_deviation_bounds: {upper: 3, lower: -2}},
|
||||
doc_count: 10,
|
||||
key: 1000
|
||||
}]
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
result = new ElasticResponse(targets, response).getTimeSeries();
|
||||
});
|
||||
|
||||
it('should return 4 series', function() {
|
||||
expect(result.data.length).to.be(4);
|
||||
expect(result.data[0].datapoints.length).to.be(1);
|
||||
expect(result.data[0].target).to.be('server1 Max');
|
||||
expect(result.data[1].target).to.be('server1 Std Dev Upper');
|
||||
|
||||
expect(result.data[0].datapoints[0][0]).to.be(10.2);
|
||||
expect(result.data[1].datapoints[0][0]).to.be(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('single group by with alias pattern', function() {
|
||||
var result;
|
||||
|
||||
beforeEach(function() {
|
||||
targets = [{
|
||||
refId: 'A',
|
||||
metrics: [{type: 'count', id: '1'}],
|
||||
alias: '{{term @host}} {{metric}} and!',
|
||||
bucketAggs: [
|
||||
{type: 'terms', field: '@host', id: '2'},
|
||||
{type: 'date_histogram', field: '@timestamp', id: '3'}
|
||||
],
|
||||
}];
|
||||
response = {
|
||||
responses: [{
|
||||
aggregations: {
|
||||
"2": {
|
||||
buckets: [
|
||||
{
|
||||
"3": {
|
||||
buckets: [
|
||||
{doc_count: 1, key: 1000},
|
||||
{doc_count: 3, key: 2000}
|
||||
]
|
||||
},
|
||||
doc_count: 4,
|
||||
key: 'server1',
|
||||
},
|
||||
{
|
||||
"3": {
|
||||
buckets: [
|
||||
{doc_count: 2, key: 1000},
|
||||
{doc_count: 8, key: 2000}
|
||||
]
|
||||
},
|
||||
doc_count: 10,
|
||||
key: 'server2',
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
result = new ElasticResponse(targets, response).getTimeSeries();
|
||||
});
|
||||
|
||||
it('should return 2 series', function() {
|
||||
expect(result.data.length).to.be(2);
|
||||
expect(result.data[0].datapoints.length).to.be(2);
|
||||
expect(result.data[0].target).to.be('server1 Count and!');
|
||||
expect(result.data[1].target).to.be('server2 Count and!');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
77
public/test/specs/elasticsearch-specs.js
Normal file
77
public/test/specs/elasticsearch-specs.js
Normal file
@@ -0,0 +1,77 @@
|
||||
define([
|
||||
'helpers',
|
||||
'moment',
|
||||
'angular',
|
||||
'plugins/datasource/elasticsearch/datasource',
|
||||
], function(helpers, moment, angular) {
|
||||
'use strict';
|
||||
|
||||
describe('ElasticDatasource', function() {
|
||||
var ctx = new helpers.ServiceTestContext();
|
||||
|
||||
beforeEach(module('grafana.services'));
|
||||
beforeEach(ctx.providePhase(['templateSrv', 'backendSrv']));
|
||||
beforeEach(ctx.createService('ElasticDatasource'));
|
||||
beforeEach(function() {
|
||||
ctx.ds = new ctx.service({jsonData: {}});
|
||||
});
|
||||
|
||||
describe('When testing datasource with index pattern', function() {
|
||||
beforeEach(function() {
|
||||
ctx.ds = new ctx.service({
|
||||
url: 'http://es.com',
|
||||
index: '[asd-]YYYY.MM.DD',
|
||||
jsonData: { interval: 'Daily' }
|
||||
});
|
||||
});
|
||||
|
||||
it('should translate index pattern to current day', function() {
|
||||
var requestOptions;
|
||||
ctx.backendSrv.datasourceRequest = function(options) {
|
||||
requestOptions = options;
|
||||
return ctx.$q.when({});
|
||||
};
|
||||
|
||||
ctx.ds.testDatasource();
|
||||
ctx.$rootScope.$apply();
|
||||
|
||||
var today = moment().format("YYYY.MM.DD");
|
||||
expect(requestOptions.url).to.be("http://es.com/asd-" + today + '/_stats');
|
||||
});
|
||||
});
|
||||
|
||||
describe('When issueing metric query with interval pattern', function() {
|
||||
beforeEach(function() {
|
||||
ctx.ds = new ctx.service({
|
||||
url: 'http://es.com',
|
||||
index: '[asd-]YYYY.MM.DD',
|
||||
jsonData: { interval: 'Daily' }
|
||||
});
|
||||
});
|
||||
|
||||
it('should translate index pattern to current day', function() {
|
||||
var requestOptions;
|
||||
ctx.backendSrv.datasourceRequest = function(options) {
|
||||
requestOptions = options;
|
||||
return ctx.$q.when({data: {responses: []}});
|
||||
};
|
||||
|
||||
ctx.ds.query({
|
||||
range: {
|
||||
from: new Date(2015, 4, 30, 10),
|
||||
to: new Date(2015, 5, 1, 10)
|
||||
},
|
||||
targets: [{ bucketAggs: [], metrics: [] }]
|
||||
});
|
||||
|
||||
ctx.$rootScope.$apply();
|
||||
var parts = requestOptions.data.split('\n');
|
||||
var header = angular.fromJson(parts[0]);
|
||||
expect(header.index).to.eql(['asd-2015.05.30', 'asd-2015.05.31', 'asd-2015.06.01']);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,7 +1,8 @@
|
||||
define([
|
||||
'helpers',
|
||||
'plugins/datasource/graphite/gfunc',
|
||||
'plugins/datasource/graphite/queryCtrl'
|
||||
'plugins/datasource/graphite/queryCtrl',
|
||||
'services/uiSegmentSrv'
|
||||
], function(helpers, gfunc) {
|
||||
'use strict';
|
||||
|
||||
@@ -9,6 +10,7 @@ define([
|
||||
var ctx = new helpers.ControllerTestContext();
|
||||
|
||||
beforeEach(module('grafana.controllers'));
|
||||
beforeEach(module('grafana.services'));
|
||||
beforeEach(ctx.providePhase());
|
||||
beforeEach(ctx.createControllerPhase('GraphiteQueryCtrl'));
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
define([
|
||||
'helpers',
|
||||
'plugins/datasource/influxdb/queryCtrl'
|
||||
'plugins/datasource/influxdb/queryCtrl',
|
||||
'services/uiSegmentSrv'
|
||||
], function(helpers) {
|
||||
'use strict';
|
||||
|
||||
@@ -8,6 +9,7 @@ define([
|
||||
var ctx = new helpers.ControllerTestContext();
|
||||
|
||||
beforeEach(module('grafana.controllers'));
|
||||
beforeEach(module('grafana.services'));
|
||||
beforeEach(ctx.providePhase());
|
||||
beforeEach(ctx.createControllerPhase('InfluxQueryCtrl'));
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ require.config({
|
||||
'jquery.flot.fillbelow': '../vendor/flot/jquery.flot.fillbelow',
|
||||
|
||||
modernizr: '../vendor/modernizr-2.6.1',
|
||||
'aws-sdk': '../vendor/aws-sdk/dist/aws-sdk.min',
|
||||
},
|
||||
|
||||
shim: {
|
||||
@@ -150,6 +151,12 @@ require([
|
||||
'specs/unsavedChangesSrv-specs',
|
||||
'specs/valueSelectDropdown-specs',
|
||||
'specs/opentsdbDatasource-specs',
|
||||
'specs/cloudwatch-datasource-specs',
|
||||
'specs/elasticsearch-specs',
|
||||
'specs/elasticsearch-querybuilder-specs',
|
||||
'specs/elasticsearch-queryctrl-specs',
|
||||
'specs/elasticsearch-indexPattern-specs',
|
||||
'specs/elasticsearch-response-specs',
|
||||
];
|
||||
|
||||
var pluginSpecs = (config.plugins.specs || []).map(function (spec) {
|
||||
|
||||
33
public/vendor/aws-sdk/.bower.json
vendored
Normal file
33
public/vendor/aws-sdk/.bower.json
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "aws-sdk",
|
||||
"ignore": [
|
||||
"apis",
|
||||
"doc-src",
|
||||
"dist-tools",
|
||||
"eslint-rules",
|
||||
"features",
|
||||
"lib",
|
||||
"scripts",
|
||||
"tasks",
|
||||
"test",
|
||||
"Gemfile*",
|
||||
"configuration*",
|
||||
"Rakefile",
|
||||
"package.json",
|
||||
"testem.json",
|
||||
".*",
|
||||
"index.js"
|
||||
],
|
||||
"main": "dist/aws-sdk.js",
|
||||
"homepage": "https://github.com/aws/aws-sdk-js",
|
||||
"version": "2.1.42",
|
||||
"_release": "2.1.42",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "v2.1.42",
|
||||
"commit": "6ad65d3e09a3a4531c84d12b980e6fb9af136a0a"
|
||||
},
|
||||
"_source": "git://github.com/aws/aws-sdk-js.git",
|
||||
"_target": "~2.1.41",
|
||||
"_originalSource": "aws-sdk"
|
||||
}
|
||||
84
public/vendor/aws-sdk/CONTRIBUTING.md
vendored
Normal file
84
public/vendor/aws-sdk/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
# Contributing to the AWS SDK for JavaScript
|
||||
|
||||
We work hard to provide a high-quality and useful SDK, and we greatly value
|
||||
feedback and contributions from our community. Whether it's a bug report,
|
||||
new feature, correction, or additional documentation, we welcome your issues
|
||||
and pull requests. Please read through this document before submitting any
|
||||
issues or pull requests to ensure we have all the necessary information to
|
||||
effectively respond to your bug report or contribution.
|
||||
|
||||
|
||||
## Filing Bug Reports
|
||||
|
||||
You can file bug reports against the SDK on the [GitHub issues][issues] page.
|
||||
|
||||
If you are filing a report for a bug or regression in the SDK, it's extremely
|
||||
helpful to provide as much information as possible when opening the original
|
||||
issue. This helps us reproduce and investigate the possible bug without having
|
||||
to wait for this extra information to be provided. Please read the following
|
||||
guidelines prior to filing a bug report.
|
||||
|
||||
1. Search through existing [issues][] to ensure that your specific issue has
|
||||
not yet been reported. If it is a common issue, it is likely there is
|
||||
already a bug report for your problem.
|
||||
|
||||
2. Ensure that you have tested the latest version of the SDK. Although you
|
||||
may have an issue against an older version of the SDK, we cannot provide
|
||||
bug fixes for old versions. It's also possible that the bug may have been
|
||||
fixed in the latest release.
|
||||
|
||||
3. Provide as much information about your environment, SDK version, and
|
||||
relevant dependencies as possible. For example, let us know what version
|
||||
of Node.js you are using, or if it's a browser issue, which browser you
|
||||
are using. If the issue only occurs with a specific dependency loaded,
|
||||
please provide that dependency name and version.
|
||||
|
||||
4. Provide a minimal test case that reproduces your issue or any error
|
||||
information you related to your problem. We can provide feedback much
|
||||
more quickly if we know what operations you are calling in the SDK. If
|
||||
you cannot provide a full test case, provide as much code as you can
|
||||
to help us diagnose the problem. Any relevant information should be provided
|
||||
as well, like whether this is a persistent issue, or if it only occurs
|
||||
some of the time.
|
||||
|
||||
|
||||
## Submitting Pull Requests
|
||||
|
||||
We are always happy to receive code and documentation contributions to the SDK.
|
||||
Please be aware of the following notes prior to opening a pull request:
|
||||
|
||||
1. The SDK is released under the [Apache license][license]. Any code you submit
|
||||
will be released under that license. For substantial contributions, we may
|
||||
ask you to sign a [Contributor License Agreement (CLA)][cla].
|
||||
|
||||
2. If you would like to implement support for a significant feature that is not
|
||||
yet available in the SDK, please talk to us beforehand to avoid any
|
||||
duplication of effort.
|
||||
|
||||
### Testing
|
||||
|
||||
To run the tests locally, install `phantomjs`. You can do so using [Homebrew][homebrew]:
|
||||
|
||||
```
|
||||
brew install phantomjs
|
||||
```
|
||||
|
||||
Then, to run all tests:
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
|
||||
To run a particular test subset e.g. just the unit tests:
|
||||
|
||||
```
|
||||
npm run-script unit
|
||||
```
|
||||
|
||||
See the implementation of the `test` script in `package.json` for more options.
|
||||
|
||||
[issues]: https://github.com/aws/aws-sdk-js/issues
|
||||
[pr]: https://github.com/aws/aws-sdk-js/pulls
|
||||
[license]: http://aws.amazon.com/apache2.0/
|
||||
[cla]: http://en.wikipedia.org/wiki/Contributor_License_Agreement
|
||||
[homebrew]: http://brew.sh/
|
||||
201
public/vendor/aws-sdk/LICENSE.txt
vendored
Normal file
201
public/vendor/aws-sdk/LICENSE.txt
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2012-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
Licensed 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.
|
||||
5
public/vendor/aws-sdk/NOTICE.txt
vendored
Normal file
5
public/vendor/aws-sdk/NOTICE.txt
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
AWS SDK for JavaScript
|
||||
Copyright 2012-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
This product includes software developed at
|
||||
Amazon Web Services, Inc. (http://aws.amazon.com/).
|
||||
124
public/vendor/aws-sdk/README.md
vendored
Normal file
124
public/vendor/aws-sdk/README.md
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
# AWS SDK for JavaScript
|
||||
|
||||
[](https://nodei.co/npm/aws-sdk/)
|
||||
|
||||
[](https://gitter.im/aws/aws-sdk-js)
|
||||
|
||||
[](http://badge.fury.io/js/aws-sdk) [](https://travis-ci.org/aws/aws-sdk-js) [](https://coveralls.io/r/aws/aws-sdk-js?branch=master)
|
||||
|
||||
The official AWS SDK for JavaScript, available for browsers and mobile devices,
|
||||
or Node.js backends
|
||||
|
||||
Release notes can be found at http://aws.amazon.com/releasenotes/SDK/JavaScript
|
||||
|
||||
<p class="note">
|
||||
If you are upgrading from 1.x to 2.0 of the SDK, please see
|
||||
the {file:UPGRADING.md} notes for information on how to migrate existing code
|
||||
to work with the new major version.
|
||||
</p>
|
||||
|
||||
## Installing
|
||||
|
||||
### In the Browser
|
||||
|
||||
To use the SDK in the browser, simply add the following script tag to your
|
||||
HTML pages:
|
||||
|
||||
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.42.min.js"></script>
|
||||
|
||||
The AWS SDK is also compatible with [browserify](http://browserify.org).
|
||||
|
||||
### In Node.js
|
||||
|
||||
The preferred way to install the AWS SDK for Node.js is to use the
|
||||
[npm](http://npmjs.org) package manager for Node.js. Simply type the following
|
||||
into a terminal window:
|
||||
|
||||
```sh
|
||||
npm install aws-sdk
|
||||
```
|
||||
|
||||
### Using Bower
|
||||
|
||||
You can also use [Bower](http://bower.io) to install the SDK by typing the
|
||||
following into a terminal window:
|
||||
|
||||
```sh
|
||||
bower install aws-sdk-js
|
||||
```
|
||||
|
||||
## Usage and Getting Started
|
||||
|
||||
You can find a getting started guide at:
|
||||
|
||||
http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/
|
||||
|
||||
## Supported Services
|
||||
|
||||
<p class="note"><strong>Note</strong>:
|
||||
Although all services are supported in the browser version of the SDK,
|
||||
not all of the services are available in the default hosted build (using the
|
||||
script tag provided above). A list of services in the hosted build are provided
|
||||
in the "<a href="http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/browser-services.html">Working With Services</a>"
|
||||
section of the browser SDK guide, including instructions on how to build a
|
||||
custom version of the SDK with extra services.
|
||||
</p>
|
||||
|
||||
The SDK currently supports the following services:
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<th>Service Name</th>
|
||||
<th>Class Name</th>
|
||||
<th>API Version</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Amazon CloudFront</td><td>AWS.CloudFront</td><td>2014-10-21</td></tr>
|
||||
<tr><td>Amazon CloudSearch</td><td>AWS.CloudSearch</td><td>2013-01-01</td></tr>
|
||||
<tr><td>Amazon CloudSearch Domain</td><td>AWS.CloudSearchDomain</td><td>2013-01-01</td></tr>
|
||||
<tr><td>Amazon CloudWatch</td><td>AWS.CloudWatch</td><td>2010-08-01</td></tr>
|
||||
<tr><td>Amazon CloudWatch Logs</td><td>AWS.CloudWatchLogs</td><td>2014-03-28</td></tr>
|
||||
<tr><td>Amazon Cognito Identity</td><td>AWS.CognitoIdentity</td><td>2014-06-30</td></tr>
|
||||
<tr><td>Amazon Cognito Sync</td><td>AWS.CognitoSync</td><td>2014-06-30</td></tr>
|
||||
<tr><td>Amazon DynamoDB</td><td>AWS.DynamoDB</td><td>2012-08-10</td></tr>
|
||||
<tr><td>Amazon Elastic Compute Cloud</td><td>AWS.EC2</td><td>2014-10-01</td></tr>
|
||||
<tr><td>Amazon Elastic MapReduce</td><td>AWS.EMR</td><td>2009-03-31</td></tr>
|
||||
<tr><td>Amazon Elastic Transcoder</td><td>AWS.ElasticTranscoder</td><td>2012-09-25</td></tr>
|
||||
<tr><td>Amazon ElastiCache</td><td>AWS.ElastiCache</td><td>2014-09-30</td></tr>
|
||||
<tr><td>Amazon Glacier</td><td>AWS.Glacier</td><td>2012-06-01</td></tr>
|
||||
<tr><td>Amazon Kinesis</td><td>AWS.Kinesis</td><td>2013-12-02</td></tr>
|
||||
<tr><td>Amazon Redshift</td><td>AWS.Redshift</td><td>2012-12-01</td></tr>
|
||||
<tr><td>Amazon Relational Database Service</td><td>AWS.RDS</td><td>2014-09-01</td></tr>
|
||||
<tr><td>Amazon Route 53</td><td>AWS.Route53</td><td>2013-04-01</td></tr>
|
||||
<tr><td>Amazon Route 53 Domains</td><td>AWS.Route53Domains</td><td>2014-05-15</td></tr>
|
||||
<tr><td>Amazon Simple Email Service</td><td>AWS.SES</td><td>2010-12-01</td></tr>
|
||||
<tr><td>Amazon Simple Notification Service</td><td>AWS.SNS</td><td>2010-03-31</td></tr>
|
||||
<tr><td>Amazon Simple Queue Service</td><td>AWS.SQS</td><td>2012-11-05</td></tr>
|
||||
<tr><td>Amazon Simple Storage Service</td><td>AWS.S3</td><td>2006-03-01</td></tr>
|
||||
<tr><td>Amazon Simple Workflow Service</td><td>AWS.SWF</td><td>2012-01-25</td></tr>
|
||||
<tr><td>Amazon SimpleDB</td><td>AWS.SimpleDB</td><td>2009-04-15</td></tr>
|
||||
<tr><td>Auto Scaling</td><td>AWS.AutoScaling</td><td>2011-01-01</td></tr>
|
||||
<tr><td>AWS CloudFormation</td><td>AWS.CloudFormation</td><td>2010-05-15</td></tr>
|
||||
<tr><td>AWS CloudTrail</td><td>AWS.CloudTrail</td><td>2013-11-01</td></tr>
|
||||
<tr><td>AWS CodeDeploy</td><td>AWS.CodeDeploy</td><td>2014-10-06</td></tr>
|
||||
<tr><td>AWS Config</td><td>AWS.ConfigService</td><td>2014-11-12</td></tr>
|
||||
<tr><td>AWS Data Pipeline</td><td>AWS.DataPipeline</td><td>2012-10-29</td></tr>
|
||||
<tr><td>AWS Direct Connect</td><td>AWS.DirectConnect</td><td>2012-10-25</td></tr>
|
||||
<tr><td>AWS Elastic Beanstalk</td><td>AWS.ElasticBeanstalk</td><td>2010-12-01</td></tr>
|
||||
<tr><td>AWS Identity and Access Management</td><td>AWS.IAM</td><td>2010-05-08</td></tr>
|
||||
<tr><td>AWS Import/Export</td><td>AWS.ImportExport</td><td>2010-06-01</td></tr>
|
||||
<tr><td>AWS Key Management Service</td><td>AWS.KMS</td><td>2014-11-01</td></tr>
|
||||
<tr><td>AWS Lambda</td><td>AWS.Lambda</td><td>2014-11-11</td></tr>
|
||||
<tr><td>AWS OpsWorks</td><td>AWS.OpsWorks</td><td>2013-02-18</td></tr>
|
||||
<tr><td>AWS Security Token Service</td><td>AWS.STS</td><td>2011-06-15</td></tr>
|
||||
<tr><td>AWS Storage Gateway</td><td>AWS.StorageGateway</td><td>2013-06-30</td></tr>
|
||||
<tr><td>AWS Support</td><td>AWS.Support</td><td>2013-04-15</td></tr>
|
||||
<tr><td>Elastic Load Balancing</td><td>AWS.ELB</td><td>2012-06-01</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## License
|
||||
|
||||
This SDK is distributed under the
|
||||
[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0),
|
||||
see LICENSE.txt and NOTICE.txt for more information.
|
||||
157
public/vendor/aws-sdk/UPGRADING.md
vendored
Normal file
157
public/vendor/aws-sdk/UPGRADING.md
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
# @!title Upgrading Notes (1.x to 2.0)
|
||||
|
||||
# Upgrading Notes (1.x to 2.0)
|
||||
|
||||
This document captures breaking changes from 1.x versions to the first
|
||||
stable 2.x (non-RC) release of the AWS SDK for JavaScript.
|
||||
|
||||
## 1. Automatic Conversion of Base64 and Timestamp Types on Input/Output
|
||||
|
||||
The SDK will now automatically encode and decode base64-encoded values, as well
|
||||
as timestamp values, on the user's behalf. This change affects any operation
|
||||
where Base64 or Timestamp values were sent by a request or returned in a
|
||||
response, i.e., `AWS.DynamoDB` and `AWS.SQS`, which allow for Base64
|
||||
encoded values.
|
||||
|
||||
User code that previously did base64 conversion no longer requires this.
|
||||
Furthermore, values encoded as base64 are now returned as Buffer objects
|
||||
from server responses (and can also be passed as Buffer input). For
|
||||
example, the following 1.x `SQS.sendMessage()` parameters:
|
||||
|
||||
```javascript
|
||||
var params = {
|
||||
MessageBody: 'Some Message',
|
||||
MessageAttributes: {
|
||||
attrName: {
|
||||
DataType: 'Binary',
|
||||
BinaryValue: new Buffer('example text').toString('base64')
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Can be rewritten as:
|
||||
|
||||
```javascript
|
||||
var params = {
|
||||
MessageBody: 'Some Message',
|
||||
MessageAttributes: {
|
||||
attrName: {
|
||||
DataType: 'Binary',
|
||||
BinaryValue: 'example text'
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
And the message will be read as:
|
||||
|
||||
```javascript
|
||||
sqs.receiveMessage(params, function(err, data) {
|
||||
// buf is <Buffer 65 78 61 6d 70 6c 65 20 74 65 78 74>
|
||||
var buf = data.Messages[0].MessageAttributes.attrName.BinaryValue;
|
||||
console.log(buf.toString()); // "example text"
|
||||
});
|
||||
```
|
||||
|
||||
## 2. Moved response.data.RequestId to response.requestId
|
||||
|
||||
The SDK now stores request IDs for all services in a consistent place on the
|
||||
response object, rather than inside the response.data property. This is to
|
||||
improve consistency across services that expose request IDs in different ways.
|
||||
Note that this is also a breaking change that renames the
|
||||
`response.data.RequestId` property to `response.requestId`
|
||||
(or `this.requestId` inside of a callback).
|
||||
|
||||
To migrate your code, change:
|
||||
|
||||
```javascript
|
||||
svc.operation(params, function (err, data) {
|
||||
console.log('Request ID:', data.RequestId);
|
||||
});
|
||||
```
|
||||
|
||||
To the following:
|
||||
|
||||
```javascript
|
||||
svc.operation(params, function () {
|
||||
console.log('Request ID:', this.requestId);
|
||||
});
|
||||
```
|
||||
|
||||
## 3. Exposed Wrapper Elements
|
||||
|
||||
If you use {AWS.ElastiCache}, {AWS.RDS}, or {AWS.Redshift}, you must now access
|
||||
the response through the top-level output property in the response for certain
|
||||
operations. This change corrects the SDK to behave according to documentation
|
||||
output, which was previously listing this wrapper element.
|
||||
|
||||
Example:
|
||||
|
||||
`RDS.describeEngineDefaultParameters()` used to return:
|
||||
|
||||
```javascript
|
||||
{ Parameters: [ ... ] }
|
||||
```
|
||||
|
||||
This operation now returns:
|
||||
|
||||
```javascript
|
||||
{ EngineDefaults: { Parameters: [ ... ] } }
|
||||
```
|
||||
|
||||
The full list of affected operations for each service are:
|
||||
|
||||
**AWS.ElastiCache**: authorizeCacheSecurityGroupIngress, createCacheCluster,
|
||||
createCacheParameterGroup, createCacheSecurityGroup, createCacheSubnetGroup,
|
||||
createReplicationGroup, deleteCacheCluster, deleteReplicationGroup,
|
||||
describeEngineDefaultParameters, modifyCacheCluster, modifyCacheSubnetGroup,
|
||||
modifyReplicationGroup, purchaseReservedCacheNodesOffering, rebootCacheCluster,
|
||||
revokeCacheSecurityGroupIngress
|
||||
|
||||
**AWS.RDS**: addSourceIdentifierToSubscription, authorizeDBSecurityGroupIngress,
|
||||
copyDBSnapshot, createDBInstance, createDBInstanceReadReplica,
|
||||
createDBParameterGroup, createDBSecurityGroup, createDBSnapshot,
|
||||
createDBSubnetGroup, createEventSubscription, createOptionGroup,
|
||||
deleteDBInstance, deleteDBSnapshot, deleteEventSubscription,
|
||||
describeEngineDefaultParameters, modifyDBInstance, modifyDBSubnetGroup,
|
||||
modifyEventSubscription, modifyOptionGroup, promoteReadReplica,
|
||||
purchaseReservedDBInstancesOffering, rebootDBInstance,
|
||||
removeSourceIdentifierFromSubscription, restoreDBInstanceFromDBSnapshot,
|
||||
restoreDBInstanceToPointInTime, revokeDBSecurityGroupIngress
|
||||
|
||||
**AWS.Redshift**: authorizeClusterSecurityGroupIngress, authorizeSnapshotAccess,
|
||||
copyClusterSnapshot, createCluster, createClusterParameterGroup,
|
||||
createClusterSecurityGroup, createClusterSnapshot, createClusterSubnetGroup,
|
||||
createEventSubscription, createHsmClientCertificate, createHsmConfiguration,
|
||||
deleteCluster, deleteClusterSnapshot, describeDefaultClusterParameters,
|
||||
disableSnapshotCopy, enableSnapshotCopy, modifyCluster,
|
||||
modifyClusterSubnetGroup, modifyEventSubscription,
|
||||
modifySnapshotCopyRetentionPeriod, purchaseReservedNodeOffering, rebootCluster,
|
||||
restoreFromClusterSnapshot, revokeClusterSecurityGroupIngress,
|
||||
revokeSnapshotAccess, rotateEncryptionKey
|
||||
|
||||
## 4. Dropped `.Client` and `.client` Properties
|
||||
|
||||
The `.Client` and `.client` properties have been removed from Service objects.
|
||||
If you are using the `.Client` property on a Service class or a `.client`
|
||||
property on an instance of the service, remove these properties from your code.
|
||||
|
||||
Upgrading example:
|
||||
|
||||
The following 1.x code:
|
||||
|
||||
```
|
||||
var sts = new AWS.STS.Client();
|
||||
// or
|
||||
var sts = new AWS.STS();
|
||||
|
||||
sts.client.operation(...);
|
||||
```
|
||||
|
||||
Should be changed to the following:
|
||||
|
||||
```
|
||||
var sts = new AWS.STS();
|
||||
sts.operation(...)
|
||||
```
|
||||
9
public/vendor/aws-sdk/bower.json
vendored
Normal file
9
public/vendor/aws-sdk/bower.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "aws-sdk",
|
||||
"ignore": [
|
||||
"apis", "doc-src", "dist-tools", "eslint-rules", "features", "lib",
|
||||
"scripts", "tasks", "test", "Gemfile*", "configuration*",
|
||||
"Rakefile", "package.json", "testem.json", ".*", "index.js"
|
||||
],
|
||||
"main": "dist/aws-sdk.js"
|
||||
}
|
||||
96
public/vendor/aws-sdk/dist/BUNDLE_LICENSE.txt
vendored
Normal file
96
public/vendor/aws-sdk/dist/BUNDLE_LICENSE.txt
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
The bundled package of the AWS SDK for JavaScript is available under the
|
||||
Apache License, Version 2.0:
|
||||
|
||||
Copyright 2012-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"). You
|
||||
may not use this file except in compliance with the License. A copy of
|
||||
the License is located at
|
||||
|
||||
http://aws.amazon.com/apache2.0/
|
||||
|
||||
or in the "license" file accompanying this file. This file 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.
|
||||
|
||||
This product bundles browserify, which is available under a
|
||||
"3-clause BSD" license:
|
||||
|
||||
Copyright Joyent, Inc. and other Node contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
This product bundles crypto-browserify, which is available under
|
||||
the MIT license:
|
||||
|
||||
Copyright (c) 2013 Dominic Tarr
|
||||
|
||||
Permission is hereby granted, free of charge,
|
||||
to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify,
|
||||
merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
This product bundles MD5, SHA-1, and SHA-256 hashing algorithm components,
|
||||
which are available under a BSD license:
|
||||
|
||||
Copyright (c) 1998 - 2009, Paul Johnston & Contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyrightnotice,
|
||||
this list of conditions and the following disclaimer. Redistributions
|
||||
in binary form must reproduce the above copyright notice, this list of
|
||||
conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
Neither the name of the author nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
THE POSSIBILITY OF SUCH DAMAGE.
|
||||
10800
public/vendor/aws-sdk/dist/aws-sdk.js
vendored
Normal file
10800
public/vendor/aws-sdk/dist/aws-sdk.js
vendored
Normal file
File diff suppressed because one or more lines are too long
17
public/vendor/aws-sdk/dist/aws-sdk.min.js
vendored
Normal file
17
public/vendor/aws-sdk/dist/aws-sdk.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user