diff --git a/CHANGELOG.md b/CHANGELOG.md index 72211224636..81b315d6323 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ### Enhancements * **CloudWatch**: Support for multiple AWS Credentials, closes [#3053](https://github.com/grafana/grafana/issues/3053), [#3080](https://github.com/grafana/grafana/issues/3080) * **Elasticsearch**: Support for dynamic daily indices for annotations, closes [#3061](https://github.com/grafana/grafana/issues/3061) +* **Graph Panel**: Option to hide series with all zeroes from legend and tooltip, closes [#1381](https://github.com/grafana/grafana/issues/1381), [#3336](https://github.com/grafana/grafana/issues/3336) + ### Bug Fixes * **cloudwatch**: fix for handling of period for long time ranges, fixes [#3086](https://github.com/grafana/grafana/issues/3086) diff --git a/README.md b/README.md index 2c2adc7ab3f..99854966938 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ the latest master builds [here](http://grafana.org/download/builds) ### Dependencies -- Go 1.4 +- Go 1.5 - NodeJS ### Get Code @@ -85,11 +85,12 @@ go get github.com/grafana/grafana ``` ### Building the backend +Replace X.Y.Z by actual version number. ``` cd $GOPATH/src/github.com/grafana/grafana go run build.go setup (only needed once to install godep) godep restore (will pull down all golang lib dependencies in your current GOPATH) -go build . +godep go run build.go build ``` ### Building frontend assets @@ -112,7 +113,7 @@ bra run ### Running ``` -./grafana +./bin/grafana-server ``` Open grafana in your browser (default http://localhost:3000) and login with admin user (default user/pass = admin/admin). @@ -128,6 +129,7 @@ You only need to add the options you want to override. Config files are applied ## Create a pull request Before or after you create a pull request, sign the [contributor license agreement](http://grafana.org/docs/contributing/cla.html). + ## Contribute If you have any idea for an improvement or found a bug do not hesitate to open an issue. And if you have time clone this repo and submit a pull request and help me make Grafana diff --git a/build.go b/build.go index 3f9e53ee378..4a345c5fc73 100644 --- a/build.go +++ b/build.go @@ -328,9 +328,9 @@ func build(pkg string, tags []string) { func ldflags() string { var b bytes.Buffer b.WriteString("-w") - b.WriteString(fmt.Sprintf(" -X main.version %s", version)) - b.WriteString(fmt.Sprintf(" -X main.commit %s", getGitSha())) - b.WriteString(fmt.Sprintf(" -X main.buildstamp %d", buildStamp())) + b.WriteString(fmt.Sprintf(" -X main.version=%s", version)) + b.WriteString(fmt.Sprintf(" -X main.commit=%s", getGitSha())) + b.WriteString(fmt.Sprintf(" -X main.buildstamp=%d", buildStamp())) return b.String() } diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index b1fd4f4f9bc..4a734ac4400 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -122,7 +122,7 @@ To configure Grafana add a configuration file named `custom.ini` to the `conf` folder and override any of the settings defined in `conf/defaults.ini`. -Start Grafana by executing `./grafana web`. The `grafana` binary needs +Start Grafana by executing `./grafana-server web`. The `grafana-server` binary needs the working directory to be the root install directory (where the binary and the `public` folder is located). diff --git a/docs/sources/reference/dashboard.md b/docs/sources/reference/dashboard.md index 5b18b9e26ad..93adf5cd789 100644 --- a/docs/sources/reference/dashboard.md +++ b/docs/sources/reference/dashboard.md @@ -1,5 +1,5 @@ ---- -page_title: Dashboard JSON +page_title: Dashboard JSON page_description: Dashboard JSON Reference page_keywords: grafana, dashboard, json, documentation --- @@ -363,7 +363,7 @@ Usage of the fields is explained below: ], "query": "tag_values(cpu.utilization.average,env)", "refresh": false, - "refresh_on_load": false, + "refresh": false, "type": "query" }, { @@ -390,7 +390,7 @@ Usage of the fields is explained below: } ], "query": "tag_values(cpu.utilization.average,app)", - "refresh_on_load": false, + "refresh": false, "regex": "", "type": "query" } @@ -413,7 +413,7 @@ Usage of the above mentioned fields in the templating section is explained below | **name** | name of variable | | **options** | array of variable text/value pairs available for selection on dashboard | | **query** | datasource query used to fetch values for a variable | -| **refresh_on_load** | TODO | +| **refresh** | TODO | | **regex** | TODO | | **type** | type of variable, i.e. `custom`, `query` or `interval` | diff --git a/docs/sources/reference/http_api.md b/docs/sources/reference/http_api.md index 5c917a7096f..15e7a06f3b8 100644 --- a/docs/sources/reference/http_api.md +++ b/docs/sources/reference/http_api.md @@ -142,10 +142,10 @@ Will return the dashboard given the dashboard slug. Slug is the url friendly ver "rows": [ { } - ] + ], "schemaVersion": 6, "version": 0 - }, + } } ### Delete dashboard @@ -787,7 +787,7 @@ Update Organisation, fields *Adress 1*, *Adress 2*, *City* are not implemented y "id": 2, "name": "User", "login": "user", - "email": "user@mygraf.com" + "email": "user@mygraf.com", "isAdmin": false } ] @@ -1046,7 +1046,7 @@ Deletes the starring of the given Dashboard for the actual user. "timezone":"browser", "title":"Home", "version":5 - } + }, "expires": 3600 } @@ -1091,34 +1091,33 @@ Keys: "canStar":false, "slug":"", "expires":"2200-13-32T25:23:23+02:00", - "created":"2200-13-32T28:24:23+02:00"}, - - { - "dashboard": { - "editable":false, - "hideControls":true, - "nav":[ - { - "enable":false, - "type":"timepicker" - } - ], - "rows": [ + "created":"2200-13-32T28:24:23+02:00" + }, + "dashboard": { + "editable":false, + "hideControls":true, + "nav":[ { - + "enable":false, + "type":"timepicker" } - ], - "style":"dark", - "tags":[], - "templating":{ - "list":[ - ] - }, - "time":{ - }, - "timezone":"browser", - "title":"Home", - "version":5 + ], + "rows": [ + { + + } + ], + "style":"dark", + "tags":[], + "templating":{ + "list":[ + ] + }, + "time":{ + }, + "timezone":"browser", + "title":"Home", + "version":5 } } @@ -1181,11 +1180,10 @@ Keys: "pluginType":"datasource", "serviceName":"Grafana", "type":"grafanasearch" + } } - } - } - - defaultDatasource: "Grafana" + }, + "defaultDatasource": "Grafana" } ## Login diff --git a/main.go b/main.go index 9639d432bea..4a052ea4934 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "flag" + "fmt" "io/ioutil" "os" "os/signal" @@ -27,6 +28,7 @@ import ( var version = "master" var commit = "NA" var buildstamp string +var build_date string var configFile = flag.String("config", "", "path to config file") var homePath = flag.String("homepath", "", "path to grafana install/home path, defaults to working directory") @@ -38,6 +40,14 @@ func init() { } func main() { + + v := flag.Bool("v", false, "prints current version and exits") + flag.Parse() + if *v { + fmt.Printf("Version %s (commit: %s)\n", version, commit) + os.Exit(0) + } + buildstampInt64, _ := strconv.ParseInt(buildstamp, 10, 64) setting.BuildVersion = version diff --git a/package.json b/package.json index 4e3890be19f..75136e48f1a 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "grunt-contrib-connect": "~0.5.0", "grunt-contrib-copy": "~0.5.0", "grunt-contrib-cssmin": "~0.6.1", - "grunt-contrib-htmlmin": "~0.1.3", + "grunt-contrib-htmlmin": "~0.6.0", "grunt-contrib-jshint": "~0.10.0", "grunt-contrib-less": "~0.7.0", "grunt-contrib-requirejs": "~0.4.4", diff --git a/pkg/api/cloudwatch/cloudwatch.go b/pkg/api/cloudwatch/cloudwatch.go index 6311d62f43e..f4cde0ebd6a 100644 --- a/pkg/api/cloudwatch/cloudwatch.go +++ b/pkg/api/cloudwatch/cloudwatch.go @@ -9,6 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" + "github.com/aws/aws-sdk-go/aws/ec2metadata" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/service/ec2" @@ -40,11 +41,12 @@ func init() { } func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) { + sess := session.New() creds := credentials.NewChainCredentials( []credentials.Provider{ &credentials.EnvProvider{}, &credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database}, - &ec2rolecreds.EC2RoleProvider{ExpiryWindow: 5 * time.Minute}, + &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute}, }) cfg := &aws.Config{ @@ -87,11 +89,12 @@ func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) { } func handleListMetrics(req *cwRequest, c *middleware.Context) { + sess := session.New() creds := credentials.NewChainCredentials( []credentials.Provider{ &credentials.EnvProvider{}, &credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database}, - &ec2rolecreds.EC2RoleProvider{ExpiryWindow: 5 * time.Minute}, + &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute}, }) cfg := &aws.Config{ @@ -126,8 +129,17 @@ func handleListMetrics(req *cwRequest, c *middleware.Context) { } func handleDescribeInstances(req *cwRequest, c *middleware.Context) { + sess := session.New() + creds := credentials.NewChainCredentials( + []credentials.Provider{ + &credentials.EnvProvider{}, + &credentials.SharedCredentialsProvider{Filename: "", Profile: req.DataSource.Database}, + &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute}, + }) + cfg := &aws.Config{ - Region: aws.String(req.Region), + Region: aws.String(req.Region), + Credentials: creds, } svc := ec2.New(session.New(cfg), cfg) diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 8d7d3f6adfd..6490a118861 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -57,6 +57,8 @@ func GetDashboard(c *middleware.Context) { CanStar: c.IsSignedIn, CanSave: c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR, CanEdit: canEditDashboard(c.OrgRole), + Created: dash.Created, + Updated: dash.Updated, }, } diff --git a/pkg/api/dtos/models.go b/pkg/api/dtos/models.go index 9a4400adbdf..7af4c84f56d 100644 --- a/pkg/api/dtos/models.go +++ b/pkg/api/dtos/models.go @@ -40,6 +40,7 @@ type DashboardMeta struct { Slug string `json:"slug"` Expires time.Time `json:"expires"` Created time.Time `json:"created"` + Updated time.Time `json:"updated"` } type DashboardFullWithMeta struct { diff --git a/public/app/core/directives/metric_segment.js b/public/app/core/directives/metric_segment.js index 9babb74dc2d..1d07544bb2d 100644 --- a/public/app/core/directives/metric_segment.js +++ b/public/app/core/directives/metric_segment.js @@ -20,13 +20,13 @@ function (_, $, coreModule) { getOptions: "&", onChange: "&", }, - link: function($scope, elem) { var $input = $(inputTemplate); var $button = $(buttonTemplate); var segment = $scope.segment; var options = null; var cancelBlur = null; + var linkMode = true; $input.appendTo(elem); $button.appendTo(elem); @@ -55,19 +55,21 @@ function (_, $, coreModule) { }); }; - $scope.switchToLink = function(now) { - if (now === true || cancelBlur) { - clearTimeout(cancelBlur); - cancelBlur = null; - $input.hide(); - $button.show(); - $scope.updateVariableValue($input.val()); - } - else { - // need to have long delay because the blur - // happens long before the click event on the typeahead options - cancelBlur = setTimeout($scope.switchToLink, 100); - } + $scope.switchToLink = function() { + if (linkMode) { return; } + + clearTimeout(cancelBlur); + cancelBlur = null; + linkMode = true; + $input.hide(); + $button.show(); + $scope.updateVariableValue($input.val()); + }; + + $scope.inputBlur = function() { + // happens long before the click event on the typeahead options + // need to have long delay because the blur + cancelBlur = setTimeout($scope.switchToLink, 100); }; $scope.source = function(query, callback) { @@ -98,7 +100,7 @@ function (_, $, coreModule) { } $input.val(value); - $scope.switchToLink(true); + $scope.switchToLink(); return value; }; @@ -139,6 +141,8 @@ function (_, $, coreModule) { $input.show(); $input.focus(); + linkMode = false; + var typeahead = $input.data('typeahead'); if (typeahead) { $input.val(''); @@ -146,7 +150,7 @@ function (_, $, coreModule) { } }); - $input.blur($scope.switchToLink); + $input.blur($scope.inputBlur); $compile(elem.contents())($scope); } diff --git a/public/app/core/services/segment_srv.js b/public/app/core/services/segment_srv.js index cb11512e12f..836437a6dc5 100644 --- a/public/app/core/services/segment_srv.js +++ b/public/app/core/services/segment_srv.js @@ -7,6 +7,7 @@ function (angular, _, coreModule) { 'use strict'; coreModule.service('uiSegmentSrv', function($sce, templateSrv) { + var self = this; function MetricSegment(options) { if (options === '*' || options.value === '*') { @@ -74,6 +75,24 @@ function (angular, _, coreModule) { }); }; + this.transformToSegments = function(addTemplateVars, variableTypeFilter) { + return function(results) { + var segments = _.map(results, function(segment) { + return self.newSegment({ value: segment.text, expandable: segment.expandable }); + }); + + if (addTemplateVars) { + _.each(templateSrv.variables, function(variable) { + if (variableTypeFilter === void 0 || variableTypeFilter === variable.type) { + segments.unshift(self.newSegment({ type: 'template', value: '$' + variable.name, expandable: true })); + } + }); + } + + return segments; + }; + }; + this.newSelectMetric = function() { return new MetricSegment({value: 'select metric', fake: true}); }; diff --git a/public/app/core/settings.js b/public/app/core/settings.js index cc4ced3b0a0..4c0b84f8da5 100644 --- a/public/app/core/settings.js +++ b/public/app/core/settings.js @@ -15,7 +15,6 @@ function (_) { appSubUrl: "" }; - var settings = _.extend({}, defaults, options); - return settings; + return _.extend({}, defaults, options); }; }); diff --git a/public/app/core/store.js b/public/app/core/store.js index 84e72b96314..504b0e5aff5 100644 --- a/public/app/core/store.js +++ b/public/app/core/store.js @@ -12,7 +12,7 @@ define([], function() { if (def !== void 0 && !this.exists(key)) { return def; } - return window.localStorage[key] === 'true' ? true : false; + return window.localStorage[key] === 'true'; }, exists: function(key) { return window.localStorage[key] !== void 0; diff --git a/public/app/core/time_series.ts b/public/app/core/time_series.ts index 0fb2b83b7e0..429e3882e8b 100644 --- a/public/app/core/time_series.ts +++ b/public/app/core/time_series.ts @@ -28,6 +28,7 @@ class TimeSeries { stats: any; legend: boolean; allIsNull: boolean; + allIsZero: boolean; decimals: number; scaledDecimals: number; @@ -96,6 +97,7 @@ class TimeSeries { this.stats.avg = null; this.stats.current = null; this.allIsNull = true; + this.allIsZero = true; var ignoreNulls = fillStyle === 'connected'; var nullAsZero = fillStyle === 'null as zero'; @@ -130,6 +132,10 @@ class TimeSeries { } } + if (currentValue != 0) { + this.allIsZero = false; + } + result.push([currentTime, currentValue]); } diff --git a/public/app/core/utils/flatten.ts b/public/app/core/utils/flatten.ts new file mode 100644 index 00000000000..fb4c47d1e3d --- /dev/null +++ b/public/app/core/utils/flatten.ts @@ -0,0 +1,39 @@ +// Copyright (c) 2014, Hugh Kennedy +// Based on code from https://github.com/hughsk/flat/blob/master/index.js +// +function flatten(target, opts): any { + opts = opts || {}; + + var delimiter = opts.delimiter || '.'; + var maxDepth = opts.maxDepth || 3; + var currentDepth = 1; + var output = {}; + + function step(object, prev) { + Object.keys(object).forEach(function(key) { + var value = object[key]; + var isarray = opts.safe && Array.isArray(value); + var type = Object.prototype.toString.call(value); + var isobject = type === "[object Object]"; + + var newKey = prev ? prev + delimiter + key : key; + + if (!opts.maxDepth) { + maxDepth = currentDepth + 1; + } + + if (!isarray && isobject && Object.keys(value).length && currentDepth < maxDepth) { + ++currentDepth; + return step(value, newKey); + } + + output[newKey] = value; + }); + } + + step(target, null); + + return output; +} + +export = flatten; diff --git a/public/app/core/utils/kbn.js b/public/app/core/utils/kbn.js index 1d60ba48fd6..123e8e16be1 100644 --- a/public/app/core/utils/kbn.js +++ b/public/app/core/utils/kbn.js @@ -341,6 +341,8 @@ function($, _) { // Currencies kbn.valueFormats.currencyUSD = kbn.formatBuilders.currency('$'); kbn.valueFormats.currencyGBP = kbn.formatBuilders.currency('£'); + kbn.valueFormats.currencyEUR = kbn.formatBuilders.currency('€'); + kbn.valueFormats.currencyJPY = kbn.formatBuilders.currency('¥'); // Data kbn.valueFormats.bits = kbn.formatBuilders.binarySIPrefix('b'); @@ -430,7 +432,7 @@ function($, _) { kbn.valueFormats.s = function(size, decimals, scaledDecimals) { if (size === null) { return ""; } - if (Math.abs(size) < 600) { + if (Math.abs(size) < 60) { return kbn.toFixed(size, decimals) + " s"; } // Less than 1 hour, devide in minutes @@ -487,6 +489,57 @@ function($, _) { } }; + kbn.valueFormats.m = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + + if (Math.abs(size) < 60) { + return kbn.toFixed(size, decimals) + " min"; + } + else if (Math.abs(size) < 1440) { + return kbn.toFixedScaled(size / 60, decimals, scaledDecimals, 2, " hour"); + } + else if (Math.abs(size) < 10080) { + return kbn.toFixedScaled(size / 1440, decimals, scaledDecimals, 3, " day"); + } + else if (Math.abs(size) < 604800) { + return kbn.toFixedScaled(size / 10080, decimals, scaledDecimals, 4, " week"); + } + else { + return kbn.toFixedScaled(size / 5.25948e5, decimals, scaledDecimals, 5, " year"); + } + }; + + kbn.valueFormats.h = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + + if (Math.abs(size) < 24) { + return kbn.toFixed(size, decimals) + " hour"; + } + else if (Math.abs(size) < 168) { + return kbn.toFixedScaled(size / 24, decimals, scaledDecimals, 2, " day"); + } + else if (Math.abs(size) < 8760) { + return kbn.toFixedScaled(size / 168, decimals, scaledDecimals, 3, " week"); + } + else { + return kbn.toFixedScaled(size / 8760, decimals, scaledDecimals, 4, " year"); + } + }; + + kbn.valueFormats.d = function(size, decimals, scaledDecimals) { + if (size === null) { return ""; } + + if (Math.abs(size) < 7) { + return kbn.toFixed(size, decimals) + " day"; + } + else if (Math.abs(size) < 365) { + return kbn.toFixedScaled(size / 7, decimals, scaledDecimals, 2, " week"); + } + else { + return kbn.toFixedScaled(size / 365, decimals, scaledDecimals, 3, " year"); + } + }; + ///// FORMAT MENU ///// kbn.getUnitFormats = function() { @@ -508,6 +561,8 @@ function($, _) { submenu: [ {text: 'Dollars ($)', value: 'currencyUSD'}, {text: 'Pounds (£)', value: 'currencyGBP'}, + {text: 'Euro (€)', value: 'currencyEUR'}, + {text: 'Yen (¥)', value: 'currencyJPY'}, ] }, { @@ -518,6 +573,9 @@ function($, _) { {text: 'microseconds (µs)', value: 'µs' }, {text: 'milliseconds (ms)', value: 'ms' }, {text: 'seconds (s)', value: 's' }, + {text: 'minutes (m)', value: 'm' }, + {text: 'hours (h)', value: 'h' }, + {text: 'days (d)', value: 'd' }, ] }, { diff --git a/public/app/features/admin/partials/orgs.html b/public/app/features/admin/partials/orgs.html index fe87073eb6c..573c0799c68 100644 --- a/public/app/features/admin/partials/orgs.html +++ b/public/app/features/admin/partials/orgs.html @@ -17,6 +17,7 @@ + {{org.id}} {{org.name}} diff --git a/public/app/features/dashboard/dashboardSrv.js b/public/app/features/dashboard/dashboardSrv.js index ce7cbeb11d2..73919ce0e01 100644 --- a/public/app/features/dashboard/dashboardSrv.js +++ b/public/app/features/dashboard/dashboardSrv.js @@ -26,7 +26,7 @@ function (angular, $, _, moment) { this.tags = data.tags || []; this.style = data.style || "dark"; this.timezone = data.timezone || 'browser'; - this.editable = data.editable === false ? false : true; + this.editable = data.editable !== false; this.hideControls = data.hideControls || false; this.sharedCrosshair = data.sharedCrosshair || false; this.rows = data.rows || []; @@ -48,10 +48,10 @@ function (angular, $, _, moment) { p._initMeta = function(meta) { meta = meta || {}; - meta.canShare = meta.canShare === false ? false : true; - meta.canSave = meta.canSave === false ? false : true; - meta.canStar = meta.canStar === false ? false : true; - meta.canEdit = meta.canEdit === false ? false : true; + meta.canShare = meta.canShare !== false; + meta.canSave = meta.canSave !== false; + meta.canStar = meta.canStar !== false; + meta.canEdit = meta.canEdit !== false; if (!this.editable) { meta.canEdit = false; @@ -151,7 +151,6 @@ function (angular, $, _, moment) { result.panel = panel; result.row = row; result.index = index; - return; } }); }); @@ -230,9 +229,9 @@ function (angular, $, _, moment) { var i, j, k; var oldVersion = this.schemaVersion; var panelUpgrades = []; - this.schemaVersion = 7; + this.schemaVersion = 8; - if (oldVersion === 7) { + if (oldVersion === 8) { return; } @@ -343,6 +342,49 @@ function (angular, $, _, moment) { }); } + if (oldVersion < 8) { + panelUpgrades.push(function(panel) { + _.each(panel.targets, function(target) { + // update old influxdb query schema + if (target.fields && target.tags && target.groupBy) { + if (target.rawQuery) { + delete target.fields; + delete target.fill; + } else { + target.select = _.map(target.fields, function(field) { + var parts = []; + parts.push({type: 'field', params: [field.name]}); + parts.push({type: field.func, params: []}); + if (field.mathExpr) { + parts.push({type: 'math', params: [field.mathExpr]}); + } + if (field.asExpr) { + parts.push({type: 'alias', params: [field.asExpr]}); + } + return parts; + }); + delete target.fields; + _.each(target.groupBy, function(part) { + if (part.type === 'time' && part.interval) { + part.params = [part.interval]; + delete part.interval; + } + if (part.type === 'tag' && part.key) { + part.params = [part.key]; + delete part.key; + } + }); + + if (target.fill) { + target.groupBy.push({type: 'fill', params: [target.fill]}); + delete target.fill; + } + } + } + }); + }); + } + if (panelUpgrades.length === 0) { return; } diff --git a/public/app/features/dashboard/partials/graphiteImport.html b/public/app/features/dashboard/partials/graphiteImport.html index 343b5d52e41..9c351346fe6 100644 --- a/public/app/features/dashboard/partials/graphiteImport.html +++ b/public/app/features/dashboard/partials/graphiteImport.html @@ -25,7 +25,7 @@ diff --git a/public/app/features/dashboard/timeSrv.js b/public/app/features/dashboard/timeSrv.js index e0e0ed47dab..691bfd07904 100644 --- a/public/app/features/dashboard/timeSrv.js +++ b/public/app/features/dashboard/timeSrv.js @@ -90,11 +90,11 @@ define([ timer.cancel(this.refresh_timer); }; - this.setTime = function(time) { + this.setTime = function(time, enableRefresh) { _.extend(this.time, time); - // disable refresh if we have an absolute time - if (moment.isMoment(time.to)) { + // disable refresh if zoom in or zoom out + if (!enableRefresh && moment.isMoment(time.to)) { this.old_refresh = this.dashboard.refresh || this.old_refresh; this.setAutoRefresh(false); } diff --git a/public/app/features/dashboard/timepicker/timepicker.ts b/public/app/features/dashboard/timepicker/timepicker.ts index c6d2680ce02..0f99210f3fa 100644 --- a/public/app/features/dashboard/timepicker/timepicker.ts +++ b/public/app/features/dashboard/timepicker/timepicker.ts @@ -115,7 +115,7 @@ export class TimePickerCtrl { this.timeSrv.setAutoRefresh(this.refresh.value); } - this.timeSrv.setTime(this.timeRaw); + this.timeSrv.setTime(this.timeRaw, true); this.$rootScope.appEvent('hide-dash-editor'); } diff --git a/public/app/features/dashboard/unsavedChangesSrv.js b/public/app/features/dashboard/unsavedChangesSrv.js index 758c06b1975..bbb38d745c7 100644 --- a/public/app/features/dashboard/unsavedChangesSrv.js +++ b/public/app/features/dashboard/unsavedChangesSrv.js @@ -122,11 +122,7 @@ function(angular, _) { var currentJson = angular.toJson(current); var originalJson = angular.toJson(original); - if (currentJson !== originalJson) { - return true; - } - - return false; + return currentJson !== originalJson; }; p.open_modal = function() { diff --git a/public/app/features/dashlinks/module.js b/public/app/features/dashlinks/module.js index 6b5677e5d2d..b7029e6cf2d 100644 --- a/public/app/features/dashlinks/module.js +++ b/public/app/features/dashlinks/module.js @@ -52,7 +52,7 @@ function (angular, _) { if (link.asDropdown) { template += '
-
+
+ +
+
+
+
+ +
+
+
@@ -150,9 +167,9 @@
-
+
    -
  • +
  • Legend
  • @@ -164,18 +181,28 @@
  • +
+
+
+
+
    +
  • + Hide series +
  • +
  • + +
  • - +
-
-
-
+ +
    -
  • - Legend values +
  • + Values
  • @@ -189,16 +216,11 @@
  • -
  • +
  • -
-
-
-
-
    -
  • - Decimals +
  • + Decimals
- -
diff --git a/public/app/plugins/panels/graph/graph.tooltip.js b/public/app/plugins/panels/graph/graph.tooltip.js index 8a747b61e0c..c56d7ce8c30 100644 --- a/public/app/plugins/panels/graph/graph.tooltip.js +++ b/public/app/plugins/panels/graph/graph.tooltip.js @@ -52,6 +52,11 @@ function ($) { continue; } + if (!series.data.length || (scope.panel.legend.hideZero && series.allIsZero)) { + results.push({ hidden: true }); + continue; + } + hoverIndex = this.findHoverIndexFromData(pos.x, series); results.time = series.data[hoverIndex][0]; diff --git a/public/app/plugins/panels/graph/legend.js b/public/app/plugins/panels/graph/legend.js index 000fc623303..b41717ee792 100644 --- a/public/app/plugins/panels/graph/legend.js +++ b/public/app/plugins/panels/graph/legend.js @@ -137,6 +137,10 @@ function (angular, _, $) { if (!series.legend) { continue; } + // ignore zero series + if (panel.legend.hideZero && series.allIsZero) { + continue; + } var html = '
-
+
  • Columns @@ -27,7 +27,8 @@ {{column.text}}
  • -
  • +
@@ -158,7 +159,7 @@
diff --git a/public/app/plugins/panels/table/editor.ts b/public/app/plugins/panels/table/editor.ts index db41ead4710..1523eb2ef12 100644 --- a/public/app/plugins/panels/table/editor.ts +++ b/public/app/plugins/panels/table/editor.ts @@ -8,93 +8,101 @@ import moment = require('moment'); import {transformers} from './transformers'; -export function tablePanelEditor() { +export class TablePanelEditorCtrl { + + /** @ngInject */ + constructor($scope, $q, uiSegmentSrv) { + $scope.transformers = transformers; + $scope.unitFormats = kbn.getUnitFormats(); + $scope.colorModes = [ + {text: 'Disabled', value: null}, + {text: 'Cell', value: 'cell'}, + {text: 'Value', value: 'value'}, + {text: 'Row', value: 'row'}, + ]; + $scope.columnTypes = [ + {text: 'Number', value: 'number'}, + {text: 'String', value: 'string'}, + {text: 'Date', value: 'date'}, + ]; + $scope.fontSizes = ['80%', '90%', '100%', '110%', '120%', '130%', '150%', '160%', '180%', '200%', '220%', '250%']; + $scope.dateFormats = [ + {text: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss'}, + {text: 'MM/DD/YY h:mm:ss a', value: 'MM/DD/YY h:mm:ss a'}, + {text: 'MMMM D, YYYY LT', value: 'MMMM D, YYYY LT'}, + ]; + + $scope.addColumnSegment = uiSegmentSrv.newPlusButton(); + + $scope.getColumnOptions = function() { + if (!$scope.dataRaw) { + return $q.when([]); + } + var columns = transformers[$scope.panel.transform].getColumns($scope.dataRaw); + var segments = _.map(columns, (c: any) => uiSegmentSrv.newSegment({value: c.text})); + return $q.when(segments); + }; + + $scope.addColumn = function() { + $scope.panel.columns.push({text: $scope.addColumnSegment.value, value: $scope.addColumnSegment.value}); + $scope.render(); + + var plusButton = uiSegmentSrv.newPlusButton(); + $scope.addColumnSegment.html = plusButton.html; + }; + + $scope.transformChanged = function() { + $scope.panel.columns = []; + $scope.render(); + }; + + $scope.removeColumn = function(column) { + $scope.panel.columns = _.without($scope.panel.columns, column); + $scope.render(); + }; + + $scope.setUnitFormat = function(column, subItem) { + column.unit = subItem.value; + $scope.render(); + }; + + $scope.addColumnStyle = function() { + var columnStyleDefaults = { + unit: 'short', + type: 'number', + decimals: 2, + colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], + colorMode: null, + pattern: '/.*/', + dateFormat: 'YYYY-MM-DD HH:mm:ss', + thresholds: [], + }; + + $scope.panel.styles.push(angular.copy(columnStyleDefaults)); + }; + + $scope.removeColumnStyle = function(style) { + $scope.panel.styles = _.without($scope.panel.styles, style); + }; + + $scope.getColumnNames = function() { + if (!$scope.table) { + return []; + } + return _.map($scope.table.columns, function(col: any) { + return col.text; + }); + }; + } +} + + +export function tablePanelEditor($q, uiSegmentSrv) { 'use strict'; return { restrict: 'E', scope: true, - templateUrl: 'app/plugins/panels/table/editor.html', - link: function(scope, elem) { - scope.transformers = transformers; - scope.unitFormats = kbn.getUnitFormats(); - scope.colorModes = [ - {text: 'Disabled', value: null}, - {text: 'Cell', value: 'cell'}, - {text: 'Value', value: 'value'}, - {text: 'Row', value: 'row'}, - ]; - scope.columnTypes = [ - {text: 'Number', value: 'number'}, - {text: 'String', value: 'string'}, - {text: 'Date', value: 'date'}, - ]; - scope.fontSizes = ['80%', '90%', '100%', '110%', '120%', '130%', '150%', '160%', '180%', '200%', '220%', '250%']; - scope.dateFormats = [ - {text: 'YYYY-MM-DD HH:mm:ss', value: 'YYYY-MM-DD HH:mm:ss'}, - {text: 'MM/DD/YY h:mm:ss a', value: 'MM/DD/YY h:mm:ss a'}, - {text: 'MMMM D, YYYY LT', value: 'MMMM D, YYYY LT'}, - ]; - - scope.updateColumnsMenu = function(data) { - scope.columnsMenu = transformers[scope.panel.transform].getColumns(data); - scope.showColumnOptions = true; - }; - - scope.$on('render', function(event, table, rawData) { - scope.updateColumnsMenu(rawData); - }); - - scope.addColumn = function(menuItem) { - scope.panel.columns.push({text: menuItem.text, value: menuItem.value}); - scope.render(); - }; - - scope.transformChanged = function() { - scope.panel.columns = []; - scope.updateColumnsMenu(); - scope.render(); - }; - - scope.removeColumn = function(column) { - scope.panel.columns = _.without(scope.panel.columns, column); - scope.render(); - }; - - scope.setUnitFormat = function(column, subItem) { - column.unit = subItem.value; - scope.render(); - }; - - scope.addColumnStyle = function() { - var columnStyleDefaults = { - unit: 'short', - type: 'number', - decimals: 2, - colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], - colorMode: null, - pattern: '/.*/', - dateFormat: 'YYYY-MM-DD HH:mm:ss', - thresholds: [], - }; - - scope.panel.styles.push(angular.copy(columnStyleDefaults)); - }; - - scope.removeColumnStyle = function(style) { - scope.panel.styles = _.without(scope.panel.styles, style); - }; - - scope.getColumnNames = function() { - if (!scope.table) { - return []; - } - return _.map(scope.table.columns, function(col: any) { - return col.text; - }); - }; - - scope.updateColumnsMenu(scope.dataRaw); - } + templateUrl: 'app/panels/table/editor.html', + controller: TablePanelEditorCtrl, }; } - diff --git a/public/app/plugins/panels/table/module.ts b/public/app/plugins/panels/table/module.ts index 1f215b44e55..3f6f1002018 100644 --- a/public/app/plugins/panels/table/module.ts +++ b/public/app/plugins/panels/table/module.ts @@ -19,6 +19,7 @@ export function tablePanel() { link: function(scope, elem) { var data; var panel = scope.panel; + var pageCount = 0; var formaters = []; function getTableHeight() { @@ -26,8 +27,11 @@ export function tablePanel() { if (_.isString(panelHeight)) { panelHeight = parseInt(panelHeight.replace('px', ''), 10); } + if (pageCount > 1) { + panelHeight -= 28; + } - return (panelHeight - 40) + 'px'; + return (panelHeight - 60) + 'px'; } function appendTableRows(tbodyElem) { @@ -46,7 +50,7 @@ export function tablePanel() { footerElem.empty(); var pageSize = panel.pageSize || 100; - var pageCount = Math.ceil(data.rows.length / pageSize); + pageCount = Math.ceil(data.rows.length / pageSize); if (pageCount === 1) { return; } @@ -73,12 +77,10 @@ export function tablePanel() { appendTableRows(tbodyElem); - rootElem.css({ - 'max-height': panel.scroll ? getTableHeight() : '' - }); - container.css({'font-size': panel.fontSize}); appendPaginationControls(footerElem); + + rootElem.css({'max-height': panel.scroll ? getTableHeight() : '' }); } elem.on('click', '.table-panel-page-link', switchPage); diff --git a/public/app/plugins/panels/table/specs/transformers_specs.ts b/public/app/plugins/panels/table/specs/transformers_specs.ts index 6e22e176e66..bb42b997d33 100644 --- a/public/app/plugins/panels/table/specs/transformers_specs.ts +++ b/public/app/plugins/panels/table/specs/transformers_specs.ts @@ -1,6 +1,7 @@ import {describe, beforeEach, it, sinon, expect} from 'test/lib/common'; import {TableModel} from '../table_model'; +import {transformers} from '../transformers'; describe('when transforming time series table', () => { var table; @@ -100,7 +101,11 @@ describe('when transforming time series table', () => { describe('JSON Data', () => { var panel = { transform: 'json', - columns: [{text: 'Timestamp', value: 'timestamp'}, {text: 'Message', value: 'message'}] + columns: [ + {text: 'Timestamp', value: 'timestamp'}, + {text: 'Message', value: 'message'}, + {text: 'nested.level2', value: 'nested.level2'}, + ] }; var rawData = [ { @@ -108,26 +113,42 @@ describe('when transforming time series table', () => { datapoints: [ { timestamp: 'time', - message: 'message' + message: 'message', + nested: { + level2: 'level2-value' + } } ] } ]; - beforeEach(() => { - table = TableModel.transform(rawData, panel); + describe('getColumns', function() { + it('should return nested properties', function() { + var columns = transformers['json'].getColumns(rawData); + expect(columns[0].text).to.be('timestamp'); + expect(columns[1].text).to.be('message'); + expect(columns[2].text).to.be('nested.level2'); + }); }); - it ('should return 2 columns', () => { - expect(table.columns.length).to.be(2); - expect(table.columns[0].text).to.be('Timestamp'); - expect(table.columns[1].text).to.be('Message'); - }); + describe('transform', function() { + beforeEach(() => { + table = TableModel.transform(rawData, panel); + }); - it ('should return 2 rows', () => { - expect(table.rows.length).to.be(1); - expect(table.rows[0][0]).to.be('time'); - expect(table.rows[0][1]).to.be('message'); + it ('should return 2 columns', () => { + expect(table.columns.length).to.be(3); + expect(table.columns[0].text).to.be('Timestamp'); + expect(table.columns[1].text).to.be('Message'); + expect(table.columns[2].text).to.be('nested.level2'); + }); + + it ('should return 2 rows', () => { + expect(table.rows.length).to.be(1); + expect(table.rows[0][0]).to.be('time'); + expect(table.rows[0][1]).to.be('message'); + expect(table.rows[0][2]).to.be('level2-value'); + }); }); }); diff --git a/public/app/plugins/panels/table/transformers.ts b/public/app/plugins/panels/table/transformers.ts index 0448b4eaef2..3845e8948aa 100644 --- a/public/app/plugins/panels/table/transformers.ts +++ b/public/app/plugins/panels/table/transformers.ts @@ -2,6 +2,7 @@ import moment = require('moment'); import _ = require('lodash'); +import flatten = require('app/core/utils/flatten'); import TimeSeries = require('app/core/time_series'); var transformers = {}; @@ -149,9 +150,12 @@ transformers['json'] = { continue; } - for (var y = 0; y < series.datapoints.length; y++) { + // only look at 100 docs + var maxDocs = Math.min(series.datapoints.length, 100); + for (var y = 0; y < maxDocs; y++) { var doc = series.datapoints[y]; - for (var propName in doc) { + var flattened = flatten(doc, null); + for (var propName in flattened) { names[propName] = true; } } @@ -177,13 +181,16 @@ transformers['json'] = { for (y = 0; y < series.datapoints.length; y++) { var dp = series.datapoints[y]; var values = []; - for (z = 0; z < panel.columns.length; z++) { - values.push(dp[panel.columns[z].value]); - } - if (values.length === 0) { + if (_.isObject(dp) && panel.columns.length > 0) { + var flattened = flatten(dp, null); + for (z = 0; z < panel.columns.length; z++) { + values.push(flattened[panel.columns[z].value]); + } + } else { values.push(JSON.stringify(dp)); } + model.rows.push(values); } } diff --git a/public/dashboards/template_vars.json b/public/dashboards/template_vars.json index 43e9e37836c..fbf2ae0dc9d 100644 --- a/public/dashboards/template_vars.json +++ b/public/dashboards/template_vars.json @@ -241,7 +241,7 @@ { "type": "query", "datasource": null, - "refresh_on_load": false, + "refresh": false, "name": "metric", "options": [], "includeAll": true, diff --git a/public/test/core/utils/flatten_specs.ts b/public/test/core/utils/flatten_specs.ts new file mode 100644 index 00000000000..01815df8607 --- /dev/null +++ b/public/test/core/utils/flatten_specs.ts @@ -0,0 +1,24 @@ +import {describe, beforeEach, it, sinon, expect} from 'test/lib/common' + +import flatten = require('app/core/utils/flatten') + +describe("flatten", () => { + + it('should return flatten object', () => { + var flattened = flatten({ + level1: 'level1-value', + deeper: { + level2: 'level2-value', + deeper: { + level3: 'level3-value' + } + } + }, null); + + expect(flattened['level1']).to.be('level1-value'); + expect(flattened['deeper.level2']).to.be('level2-value'); + expect(flattened['deeper.deeper.level3']).to.be('level3-value'); + }); + +}); + diff --git a/public/test/core/utils/kbn_specs.js b/public/test/core/utils/kbn_specs.js index 2253f4d8ecd..23c752fe471 100644 --- a/public/test/core/utils/kbn_specs.js +++ b/public/test/core/utils/kbn_specs.js @@ -68,6 +68,27 @@ define([ describeValueFormat('wps', 789000000, 1000000, -1, '789M wps'); describeValueFormat('iops', 11000000000, 1000000000, -1, '11B iops'); + describeValueFormat('s', 24, 1, 0, '24 s'); + describeValueFormat('s', 246, 1, 0, '4.1 min'); + describeValueFormat('s', 24567, 100, 0, '6.82 hour'); + describeValueFormat('s', 24567890, 10000, 0, '40.62 week'); + describeValueFormat('s', 24567890000, 1000000, 0, '778.53 year'); + + describeValueFormat('m', 24, 1, 0, '24 min'); + describeValueFormat('m', 246, 10, 0, '4.1 hour'); + describeValueFormat('m', 6545, 10, 0, '4.55 day'); + describeValueFormat('m', 24567, 100, 0, '2.44 week'); + describeValueFormat('m', 24567892, 10000, 0, '46.7 year'); + + describeValueFormat('h', 21, 1, 0, '21 hour'); + describeValueFormat('h', 145, 1, 0, '6.04 day'); + describeValueFormat('h', 1234, 100, 0, '7.3 week'); + describeValueFormat('h', 9458, 1000, 0, '1.08 year'); + + describeValueFormat('d', 3, 1, 0, '3 day'); + describeValueFormat('d', 245, 100, 0, '35 week'); + describeValueFormat('d', 2456, 10, 0, '6.73 year'); + describe('kbn.toFixed and negative decimals', function() { it('should treat as zero decimals', function() { var str = kbn.toFixed(186.123, -2); diff --git a/public/test/specs/dashboardSrv-specs.js b/public/test/specs/dashboardSrv-specs.js index 8da659825b8..5b2fefd384d 100644 --- a/public/test/specs/dashboardSrv-specs.js +++ b/public/test/specs/dashboardSrv-specs.js @@ -204,7 +204,7 @@ define([ }); it('dashboard schema version should be set to latest', function() { - expect(model.schemaVersion).to.be(7); + expect(model.schemaVersion).to.be(8); }); }); @@ -248,5 +248,90 @@ define([ expect(clone.meta).to.be(undefined); }); }); + + describe('when loading dashboard with old influxdb query schema', function() { + var model; + var target; + + beforeEach(function() { + model = _dashboardSrv.create({ + rows: [{ + panels: [{ + type: 'graph', + targets: [{ + "alias": "$tag_datacenter $tag_source $col", + "column": "value", + "measurement": "logins.count", + "fields": [ + { + "func": "mean", + "name": "value", + "mathExpr": "*2", + "asExpr": "value" + }, + { + "name": "one-minute", + "func": "mean", + "mathExpr": "*3", + "asExpr": "one-minute" + } + ], + "tags": [], + "fill": "previous", + "function": "mean", + "groupBy": [ + { + "interval": "auto", + "type": "time" + }, + { + "key": "source", + "type": "tag" + }, + { + "type": "tag", + "key": "datacenter" + } + ], + }] + }] + }] + }); + + target = model.rows[0].panels[0].targets[0]; + }); + + it('should update query schema', function() { + expect(target.fields).to.be(undefined); + expect(target.select.length).to.be(2); + expect(target.select[0].length).to.be(4); + expect(target.select[0][0].type).to.be('field'); + expect(target.select[0][1].type).to.be('mean'); + expect(target.select[0][2].type).to.be('math'); + expect(target.select[0][3].type).to.be('alias'); + }); + + }); + + describe('when creating dashboard model with missing list for annoations or templating', function() { + var model; + + beforeEach(function() { + model = _dashboardSrv.create({ + annotations: { + enable: true, + }, + templating: { + enable: true + } + }); + }); + + it('should add empty list', function() { + expect(model.annotations.list.length).to.be(0); + expect(model.templating.list.length).to.be(0); + }); + }); + }); }); diff --git a/public/test/specs/time_srv_specs.js b/public/test/specs/time_srv_specs.js index 4f065af6cf8..9943aae6cc3 100644 --- a/public/test/specs/time_srv_specs.js +++ b/public/test/specs/time_srv_specs.js @@ -78,13 +78,20 @@ define([ }); describe('setTime', function() { - it('should return disable refresh for absolute times', function() { + it('should return disable refresh if refresh is disabled for any range', function() { _dashboard.refresh = false; ctx.service.setTime({from: '2011-01-01', to: '2015-01-01' }); expect(_dashboard.refresh).to.be(false); }); + it('should restore refresh for absolute time range', function() { + _dashboard.refresh = '30s'; + + ctx.service.setTime({from: '2011-01-01', to: '2015-01-01' }); + expect(_dashboard.refresh).to.be('30s'); + }); + it('should restore refresh after relative time range is set', function() { _dashboard.refresh = '10s'; ctx.service.setTime({from: moment([2011,1,1]), to: moment([2015,1,1])}); diff --git a/public/vendor/showdown.js b/public/vendor/showdown.js index 9493071ca68..0286b0598f1 100644 --- a/public/vendor/showdown.js +++ b/public/vendor/showdown.js @@ -855,7 +855,7 @@ var _DoLists = function(text) { // Turn double returns into triple returns, so that we can make a // paragraph for the last item in a list, if necessary: - list = list.replace(/\n{2,}/g,"\n\n\n");; + list = list.replace(/\n{2,}/g,"\n\n\n"); var result = _ProcessListItems(list); // Trim any trailing whitespace, to put the closing `` @@ -875,7 +875,7 @@ var _DoLists = function(text) { var list_type = (m3.search(/[*+-]/g)>-1) ? "ul" : "ol"; // Turn double returns into triple returns, so that we can make a // paragraph for the last item in a list, if necessary: - var list = list.replace(/\n{2,}/g,"\n\n\n");; + list = list.replace(/\n{2,}/g,"\n\n\n"); var result = _ProcessListItems(list); result = runup + "<"+list_type+">\n" + result + "\n"; return result; @@ -1451,4 +1451,4 @@ if (typeof define === 'function' && define.amd) { define(function() { return Showdown; }); -} \ No newline at end of file +} diff --git a/public/views/index.html b/public/views/index.html index 2932276c62f..8b4177baa5e 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -9,19 +9,19 @@ Grafana [[if .User.LightTheme]] - + [[else]] - + [[end]] [[ range $css := .PluginCss ]] [[ end ]] - + - + diff --git a/tasks/build_task.js b/tasks/build_task.js index 7773299a06d..54562dfbde1 100644 --- a/tasks/build_task.js +++ b/tasks/build_task.js @@ -13,7 +13,7 @@ module.exports = function(grunt) { 'karma:test', 'phantomjs', 'css', - 'htmlmin:build', + // 'htmlmin:build', 'ngtemplates', 'cssmin:build', 'ngAnnotate:build', @@ -34,8 +34,8 @@ module.exports = function(grunt) { for(var key in summary){ if(summary.hasOwnProperty(key)){ - var orig = key.replace(root, root+'/[[.AppSubUrl]]'); - var revved = summary[key].replace(root, root+'/[[.AppSubUrl]]'); + var orig = key.replace(root, root+'/[[.AppSubUrl]]/public'); + var revved = summary[key].replace(root, root+'/[[.AppSubUrl]]/public'); fixed[orig] = revved; } } diff --git a/tasks/options/concat.js b/tasks/options/concat.js index c15aa8a2d6e..4e6927b306a 100644 --- a/tasks/options/concat.js +++ b/tasks/options/concat.js @@ -27,7 +27,7 @@ module.exports = function(config) { js: { src: [ '<%= tempDir %>/vendor/requirejs/require.js', - '<%= tempDir %>/app/components/require.config.js', + '<%= tempDir %>/app/require_config.js', '<%= tempDir %>/app/app.js', ], dest: '<%= genDir %>/app/app.js'