Просмотр исходного кода

Merge remote-tracking branch 'grafana/master' into influx-db-query2

ryan 8 лет назад
Родитель
Сommit
42abbf5f0d
72 измененных файлов с 1301 добавлено и 564 удалено
  1. 1 0
      .gitignore
  2. 1 0
      CHANGELOG.md
  3. 31 0
      ROADMAP.md
  4. 2 0
      docker/blocks/mysql/fig
  5. 20 0
      docker/blocks/mysql_opendata/Dockerfile
  6. 9 0
      docker/blocks/mysql_opendata/fig
  7. 80 0
      docker/blocks/mysql_opendata/import_csv.sql
  8. 19 1
      docs/sources/administration/cli.md
  9. 1 1
      docs/sources/http_api/alerting.md
  10. 77 0
      docs/sources/http_api/auth.md
  11. 2 2
      package.json
  12. 4 1
      pkg/api/metrics.go
  13. 13 7
      pkg/cmd/grafana-cli/commands/commands.go
  14. 14 1
      pkg/services/alerting/notifiers/telegram.go
  15. 0 1
      pkg/services/sqlstore/migrations/migrations.go
  16. 2 7
      pkg/services/sqlstore/sql_test_data.go
  17. 18 5
      pkg/tsdb/models.go
  18. 80 0
      pkg/tsdb/mysql/macros.go
  19. 43 0
      pkg/tsdb/mysql/macros_test.go
  20. 117 14
      pkg/tsdb/mysql/mysql.go
  21. 6 6
      public/app/core/components/search/search.ts
  22. 5 1
      public/app/core/components/sidemenu/sidemenu.ts
  23. 1 1
      public/app/core/controllers/signup_ctrl.ts
  24. 1 1
      public/app/core/directives/plugin_component.ts
  25. 3 3
      public/app/core/services/backend_srv.ts
  26. 1 1
      public/app/core/time_series2.ts
  27. 4 4
      public/app/core/utils/file_export.ts
  28. 2 2
      public/app/features/annotations/editor_ctrl.ts
  29. 0 1
      public/app/features/dashboard/row/row_ctrl.ts
  30. 5 5
      public/app/features/dashboard/time_srv.ts
  31. 13 2
      public/app/features/panel/metrics_panel_ctrl.ts
  32. 2 2
      public/app/features/panel/partials/query_editor_row.html
  33. 1 1
      public/app/features/playlist/playlist_edit_ctrl.ts
  34. 1 1
      public/app/features/plugins/ds_edit_ctrl.ts
  35. 1 1
      public/app/features/templating/all.ts
  36. 1 1
      public/app/features/templating/variable_srv.ts
  37. 1 1
      public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js
  38. 1 1
      public/app/plugins/datasource/grafana/datasource.ts
  39. 4 4
      public/app/plugins/datasource/influxdb/datasource.ts
  40. 1 1
      public/app/plugins/datasource/influxdb/influx_query.ts
  41. 7 1
      public/app/plugins/datasource/influxdb/query_builder.js
  42. 1 1
      public/app/plugins/datasource/influxdb/query_part.ts
  43. 5 5
      public/app/plugins/datasource/influxdb/specs/query_builder_specs.ts
  44. 1 1
      public/app/plugins/datasource/mixed/datasource.ts
  45. 55 26
      public/app/plugins/datasource/mysql/datasource.ts
  46. 0 0
      public/app/plugins/datasource/mysql/img/mysql_logo.svg
  47. 2 20
      public/app/plugins/datasource/mysql/module.ts
  48. 40 6
      public/app/plugins/datasource/mysql/partials/query.editor.html
  49. 79 0
      public/app/plugins/datasource/mysql/query_ctrl.ts
  50. 1 1
      public/app/plugins/panel/alertlist/module.ts
  51. 1 1
      public/app/plugins/panel/dashlist/module.ts
  52. 1 1
      public/app/plugins/panel/gettingstarted/module.ts
  53. 2 2
      public/app/plugins/panel/graph/graph.ts
  54. 2 2
      public/app/plugins/panel/graph/module.ts
  55. 2 2
      public/app/plugins/panel/heatmap/axes_editor.ts
  56. 1 1
      public/app/plugins/panel/heatmap/heatmap_ctrl.ts
  57. 1 1
      public/app/plugins/panel/pluginlist/module.ts
  58. 9 1
      public/app/plugins/panel/singlestat/editor.html
  59. 97 23
      public/app/plugins/panel/singlestat/module.ts
  60. 0 131
      public/app/plugins/panel/singlestat/specs/singlestat-specs.ts
  61. 260 0
      public/app/plugins/panel/singlestat/specs/singlestat_specs.ts
  62. 1 1
      public/app/plugins/panel/table/editor.ts
  63. 3 3
      public/app/plugins/panel/table/transformers.ts
  64. 1 1
      public/app/plugins/panel/text/module.ts
  65. 1 1
      public/app/plugins/sdk.ts
  66. 10 0
      public/sass/components/_gf-form.scss
  67. 1 1
      public/test/lib/common.ts
  68. 1 1
      tasks/build_task.js
  69. 2 2
      tasks/default_task.js
  70. 2 2
      tasks/options/exec.js
  71. 78 0
      vendor/github.com/go-sql-driver/mysql/row_columntypes.go
  72. 46 246
      yarn.lock

+ 1 - 0
.gitignore

@@ -25,6 +25,7 @@ public/css/*.min.css
 *.swp
 .idea/
 *.iml
+*.tmp
 .vscode/
 
 /data/*

+ 1 - 0
CHANGELOG.md

@@ -19,6 +19,7 @@
 * **Units**: New number format: Scientific notation [#7781](https://github.com/grafana/grafana/issues/7781) thx [@cadnce](https://github.com/cadnce)
 * **Oauth**: Add common type for oauth authorization errors [#6428](https://github.com/grafana/grafana/issues/6428) thx [@amenzhinsky](https://github.com/amenzhinsky)
 * **Templating**: Data source variable now supports multi value and panel repeats [#7030](https://github.com/grafana/grafana/issues/7030) thx [@mtanda](https://github.com/mtanda)
+* **Telegram**: Telegram alert is not sending metric and legend. [#8110](https://github.com/grafana/grafana/issues/8110), thx [@bashgeek](https://github.com/bashgeek)
 
 ## Fixes
 * **Table Panel**: Fixed annotation display in table panel, [#8023](https://github.com/grafana/grafana/issues/8023)

+ 31 - 0
ROADMAP.md

@@ -0,0 +1,31 @@
+# Roadmap (2017-04-23)
+
+This roadmap is a tentative plan for the core development team. Things change constantly as PRs come in and priorities change. 
+But it will give you an idea of our current vision and plan. 
+
+### Short term (1-4 months)
+
+ - New Heatmap Panel (Implemented and available in master)
+ - Support for MySQL & Postgres as data sources (Work started and a alpha version for MySQL is available in master)
+ - User Groups & Dashboard folders with ACLs (work started, not yet completed, https://github.com/grafana/grafana/issues/1611#issuecomment-287742633)
+ - Improve new user UX
+ - Improve docs
+ - Support for alerting for Elasticsearch (can be tested in [branch](https://github.com/grafana/grafana/tree/alerting-elasticsearch) but needs more work)
+  - Graph annotations (create from grafana, region annotations, better annotation viz)
+  - Improve alerting (clustering, silence rules)
+  
+### Long term 
+
+- Improved dashboard panel layout engine (to make it easier and enable more flexible layouts) 
+- Backend plugins to support more Auth options, Alerting data sources & notifications
+- Universial time series transformations for any data source (meta queries)
+- Reporting
+- Web socket & live data streams
+- Migrate to Angular2 
+
+
+### Outside contributions
+We know this is being worked on right now by contributors (and we hope to merge it when it's ready). 
+
+- Dashboard revisions (be able to revert dashboard changes)
+- Clustering for alert engine (load distribution)  

+ 2 - 0
docker/blocks/mysql/fig

@@ -10,3 +10,5 @@ mysql:
   volumes:
     - /etc/localtime:/etc/localtime:ro
     - /etc/timezone:/etc/timezone:ro
+  command: [mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --innodb_monitor_enable=all]
+

+ 20 - 0
docker/blocks/mysql_opendata/Dockerfile

@@ -0,0 +1,20 @@
+## MySQL with Open Data Set from NYC Open Data (https://data.cityofnewyork.us)
+
+FROM mysql:latest
+
+ENV MYSQL_DATABASE="testdata" \
+    MYSQL_ROOT_PASSWORD="rootpass" \
+    MYSQL_USER="grafana" \
+    MYSQL_PASSWORD="password"
+
+# Install requirement (wget)
+RUN apt-get update && apt-get install -y wget && apt-get install unzip
+
+# Fetch NYC Data Set
+RUN wget https://data.cityofnewyork.us/download/57g5-etyj/application%2Fzip -O /tmp/data.zip && \
+  unzip -j /tmp/data.zip 311_Service_Requests_from_2015.csv -d /var/lib/mysql-files && \
+  rm /tmp/data.zip
+
+ADD import_csv.sql /docker-entrypoint-initdb.d/
+
+EXPOSE 3306

+ 9 - 0
docker/blocks/mysql_opendata/fig

@@ -0,0 +1,9 @@
+mysql_opendata:
+  build: blocks/mysql_opendata
+  environment:
+    MYSQL_ROOT_PASSWORD: rootpass
+    MYSQL_DATABASE: testdata
+    MYSQL_USER: grafana
+    MYSQL_PASSWORD: password
+  ports:
+    - "3307:3306"

+ 80 - 0
docker/blocks/mysql_opendata/import_csv.sql

@@ -0,0 +1,80 @@
+use testdata;
+DROP TABLE IF EXISTS `nyc_open_data`;
+CREATE TABLE IF NOT EXISTS `nyc_open_data` (
+  UniqueKey bigint(255),
+  `CreatedDate` varchar(255),
+  `ClosedDate` varchar(255),
+  Agency varchar(255),
+  AgencyName varchar(255),
+  ComplaintType varchar(255),
+  Descriptor varchar(255),
+  LocationType varchar(255),
+  IncidentZip varchar(255),
+  IncidentAddress varchar(255),
+  StreetName varchar(255),
+  CrossStreet1 varchar(255),
+  CrossStreet2 varchar(255),
+  IntersectionStreet1 varchar(255),
+  IntersectionStreet2 varchar(255),
+  AddressType varchar(255),
+  City varchar(255),
+  Landmark varchar(255),
+  FacilityType varchar(255),
+  Status varchar(255),
+  `DueDate` varchar(255),
+  ResolutionDescription varchar(2048),
+  `ResolutionActionUpdatedDate` varchar(255),
+  CommunityBoard varchar(255),
+  Borough varchar(255),
+  XCoordinateStatePlane varchar(255),
+  YCoordinateStatePlane varchar(255),
+  ParkFacilityName varchar(255),
+  ParkBorough varchar(255),
+  SchoolName varchar(255),
+  SchoolNumber varchar(255),
+  SchoolRegion varchar(255),
+  SchoolCode varchar(255),
+  SchoolPhoneNumber varchar(255),
+  SchoolAddress varchar(255),
+  SchoolCity varchar(255),
+  SchoolState varchar(255),
+  SchoolZip varchar(255),
+  SchoolNotFound varchar(255),
+  SchoolOrCitywideComplaint varchar(255),
+  VehicleType varchar(255),
+  TaxiCompanyBorough varchar(255),
+  TaxiPickUpLocation varchar(255),
+  BridgeHighwayName varchar(255),
+  BridgeHighwayDirection varchar(255),
+  RoadRamp varchar(255),
+  BridgeHighwaySegment varchar(255),
+  GarageLotName varchar(255),
+  FerryDirection varchar(255),
+  FerryTerminalName varchar(255),
+  Latitude varchar(255),
+  Longitude varchar(255),
+  Location varchar(255)
+);
+LOAD DATA INFILE '/var/lib/mysql-files/311_Service_Requests_from_2015.csv' INTO TABLE nyc_open_data FIELDS OPTIONALLY ENCLOSED BY '"' TERMINATED BY ',' IGNORE 1 LINES;
+UPDATE nyc_open_data SET CreatedDate = STR_TO_DATE(CreatedDate, '%m/%d/%Y %r') WHERE CreatedDate <> '';
+UPDATE nyc_open_data SET ClosedDate = STR_TO_DATE(ClosedDate, '%m/%d/%Y %r') WHERE ClosedDate <> '';
+UPDATE nyc_open_data SET DueDate = STR_TO_DATE(DueDate, '%m/%d/%Y %r') WHERE DueDate <> '';
+UPDATE nyc_open_data SET ResolutionActionUpdatedDate = STR_TO_DATE(ResolutionActionUpdatedDate, '%m/%d/%Y %r') WHERE ResolutionActionUpdatedDate <> '';
+
+UPDATE nyc_open_data SET CreatedDate=null WHERE CreatedDate = '';
+UPDATE nyc_open_data SET ClosedDate=null WHERE ClosedDate = '';
+UPDATE nyc_open_data SET DueDate=null WHERE DueDate = '';
+UPDATE nyc_open_data SET ResolutionActionUpdatedDate=null WHERE ResolutionActionUpdatedDate = '';
+
+ALTER TABLE nyc_open_data modify CreatedDate datetime NULL;
+ALTER TABLE nyc_open_data modify ClosedDate datetime NULL;
+ALTER TABLE nyc_open_data modify DueDate datetime NULL;
+ALTER TABLE nyc_open_data modify ResolutionActionUpdatedDate datetime NULL;
+
+ALTER TABLE `nyc_open_data` ADD INDEX `IX_ComplaintType` (`ComplaintType`);
+ALTER TABLE `nyc_open_data` ADD INDEX `IX_CreatedDate` (`CreatedDate`);
+ALTER TABLE `nyc_open_data` ADD INDEX `IX_LocationType` (`LocationType`);
+ALTER TABLE `nyc_open_data` ADD INDEX `IX_AgencyName` (`AgencyName`);
+ALTER TABLE `nyc_open_data` ADD INDEX `IX_City` (`City`);
+
+SYSTEM rm /var/lib/mysql-files/311_Service_Requests_from_2015.csv

+ 19 - 1
docs/sources/administration/cli.md

@@ -27,6 +27,24 @@ To show all admin commands:
 
 ### Reset admin password
 
-You can reset the password for the admin user using the CLI.
+You can reset the password for the admin user using the CLI. The use case for this command is when you have lost the admin password.
 
 `grafana-cli admin reset-admin-password ...`
+
+If running the command returns this error:
+
+> Could not find config defaults, make sure homepath command line parameter is set or working directory is homepath
+
+then there are two flags that can be used to set homepath and the config file path.
+
+`grafana-cli admin reset-admin-password --homepath "/usr/share/grafana" newpass`
+
+If you have not lost the admin password then it is better to set in the Grafana UI. If you need to set the password in a script then the [Grafana API](http://docs.grafana.org/http_api/user/#change-password) can be used. Here is an example with curl using basic auth:
+
+```
+curl -X PUT -H "Content-Type: application/json" -d '{
+  "oldPassword": "admin",
+  "newPassword": "newpass",
+  "confirmNew": "newpass"
+}' http://admin:admin@<your_grafana_host>:3000/api/user/password
+```

+ 1 - 1
docs/sources/http_api/alerting.md

@@ -202,7 +202,7 @@ This API can also be used to create, update and delete alert notifications.
 
 **Example Request**:
 
-    DELETE /api/alerts-notifications/1 HTTP/1.1
+    DELETE /api/alert-notifications/1 HTTP/1.1
     Accept: application/json
     Content-Type: application/json
     Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk

+ 77 - 0
docs/sources/http_api/auth.md

@@ -41,3 +41,80 @@ You use the token in all requests in the `Authorization` header, like this:
     Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
 
 The `Authorization` header value should be `Bearer <your api key>`.
+
+# Auth HTTP resources / actions
+
+## Api Keys
+
+`GET /api/auth/keys`
+
+**Example Request**:
+
+    GET /api/auth/keys HTTP/1.1
+    Accept: application/json
+    Content-Type: application/json
+    Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+
+**Example Response**:
+
+    HTTP/1.1 200
+    Content-Type: application/json
+
+    [
+      {
+        "id": 3,
+        "name": "API",
+        "role": "Admin"
+      },
+      {
+        "id": 1,
+        "name": "TestAdmin",
+        "role": "Admin"
+      }
+    ]
+
+## Create API Key
+
+`POST /api/auth/keys`
+
+**Example Request**:
+
+    POST /api/auth/keys HTTP/1.1
+    Accept: application/json
+    Content-Type: application/json
+    Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+
+    {
+      "name": "mykey",
+      "role": "Admin"
+    }
+
+JSON Body schema:
+
+- **name** – The key name
+- **role** – Sets the access level/Grafana Role for the key. Can be one of the following values: `Viewer`, `Editor`, `Read Only Editor` or `Admin`.
+
+**Example Response**:
+
+    HTTP/1.1 200
+    Content-Type: application/json
+
+    {"name":"mykey","key":"eyJrIjoiWHZiSWd3NzdCYUZnNUtibE9obUpESmE3bzJYNDRIc0UiLCJuIjoibXlrZXkiLCJpZCI6MX1="}
+
+## Delete API Key
+
+`DELETE /api/auth/keys/:id`
+
+**Example Request**:
+
+    DELETE /api/auth/keys/3 HTTP/1.1
+    Accept: application/json
+    Content-Type: application/json
+    Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+
+**Example Response**:
+
+    HTTP/1.1 200
+    Content-Type: application/json
+
+    {"message":"API key deleted"}

+ 2 - 2
package.json

@@ -76,8 +76,8 @@
     "systemjs-builder": "^0.15.34",
     "tether": "^1.4.0",
     "tether-drop": "https://github.com/torkelo/drop",
-    "tslint": "^4.5.1",
-    "typescript": "^2.1.4",
+    "tslint": "^5.1.0",
+    "typescript": "^2.2.2",
     "virtual-scroll": "^1.1.1"
   }
 }

+ 4 - 1
pkg/api/metrics.go

@@ -50,13 +50,16 @@ func QueryMetrics(c *middleware.Context, reqDto dtos.MetricRequest) Response {
 		return ApiError(500, "Metric request error", err)
 	}
 
+	statusCode := 200
 	for _, res := range resp.Results {
 		if res.Error != nil {
 			res.ErrorString = res.Error.Error()
+			resp.Message = res.ErrorString
+			statusCode = 500
 		}
 	}
 
-	return Json(200, &resp)
+	return Json(statusCode, &resp)
 }
 
 // GET /api/tsdb/testdata/scenarios

+ 13 - 7
pkg/cmd/grafana-cli/commands/commands.go

@@ -11,22 +11,18 @@ import (
 	"github.com/grafana/grafana/pkg/setting"
 )
 
-var configFile = flag.String("config", "", "path to config file")
-var homePath = flag.String("homepath", "", "path to grafana install/home path, defaults to working directory")
-
 func runDbCommand(command func(commandLine CommandLine) error) func(context *cli.Context) {
 	return func(context *cli.Context) {
+		cmd := &contextCommandLine{context}
 
-		flag.Parse()
 		setting.NewConfigContext(&setting.CommandLineArgs{
-			Config:   *configFile,
-			HomePath: *homePath,
+			Config:   cmd.String("config"),
+			HomePath: cmd.String("homepath"),
 			Args:     flag.Args(),
 		})
 
 		sqlstore.NewEngine()
 
-		cmd := &contextCommandLine{context}
 		if err := command(cmd); err != nil {
 			logger.Errorf("\n%s: ", color.RedString("Error"))
 			logger.Errorf("%s\n\n", err)
@@ -95,6 +91,16 @@ var adminCommands = []cli.Command{
 		Name:   "reset-admin-password",
 		Usage:  "reset-admin-password <new password>",
 		Action: runDbCommand(resetPasswordCommand),
+		Flags: []cli.Flag{
+			cli.StringFlag{
+				Name:  "homepath",
+				Usage: "path to grafana install/home path, defaults to working directory",
+			},
+			cli.StringFlag{
+				Name:  "config",
+				Usage: "path to config file",
+			},
+		},
 	},
 }
 

+ 14 - 1
pkg/services/alerting/notifiers/telegram.go

@@ -87,7 +87,7 @@ func (this *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error {
 	bodyJSON.Set("chat_id", this.ChatID)
 	bodyJSON.Set("parse_mode", "html")
 
-	message := fmt.Sprintf("%s\nState: %s\nMessage: %s\n", evalContext.GetNotificationTitle(), evalContext.Rule.Name, evalContext.Rule.Message)
+	message := fmt.Sprintf("<b>%s</b>\nState: %s\nMessage: %s\n", evalContext.GetNotificationTitle(), evalContext.Rule.Name, evalContext.Rule.Message)
 
 	ruleUrl, err := evalContext.GetRuleUrl()
 	if err == nil {
@@ -96,6 +96,19 @@ func (this *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error {
 	if evalContext.ImagePublicUrl != "" {
 		message = message + fmt.Sprintf("Image: %s\n", evalContext.ImagePublicUrl)
 	}
+
+	metrics := ""
+	fieldLimitCount := 4
+	for index, evt := range evalContext.EvalMatches {
+		metrics += fmt.Sprintf("\n%s: %s", evt.Metric, evt.Value)
+		if index > fieldLimitCount {
+			break
+		}
+	}
+	if metrics != "" {
+		message = message + fmt.Sprintf("\n<i>Metrics:</i>%s", metrics)
+	}
+
 	bodyJSON.Set("text", message)
 
 	url := fmt.Sprintf(telegeramApiUrl, this.BotToken, "sendMessage")

+ 0 - 1
pkg/services/sqlstore/migrations/migrations.go

@@ -24,7 +24,6 @@ func AddMigrations(mg *Migrator) {
 	addPreferencesMigrations(mg)
 	addAlertMigrations(mg)
 	addAnnotationMig(mg)
-	addStatsMigrations(mg)
 	addTestDataMigrations(mg)
 }
 

+ 2 - 7
pkg/services/sqlstore/sql_test_data.go

@@ -14,7 +14,7 @@ func init() {
 
 func sqlRandomWalk(m1 string, m2 string, intWalker int64, floatWalker float64, sess *session) error {
 
-	timeWalker := time.Now().UTC().Add(time.Hour * -1)
+	timeWalker := time.Now().UTC().Add(time.Hour * -200)
 	now := time.Now().UTC()
 	step := time.Minute
 
@@ -29,7 +29,7 @@ func sqlRandomWalk(m1 string, m2 string, intWalker int64, floatWalker float64, s
 		timeWalker = timeWalker.Add(step)
 
 		row.Id = 0
-		row.ValueBigInt += rand.Int63n(100) - 100
+		row.ValueBigInt += rand.Int63n(200) - 100
 		row.ValueDouble += rand.Float64() - 0.5
 		row.ValueFloat += rand.Float32() - 0.5
 		row.TimeEpoch = timeWalker.Unix()
@@ -61,11 +61,6 @@ func InsertSqlTestData(cmd *m.InsertSqlTestDataCommand) error {
 		sqlRandomWalk("server2", "frontend", 100, 1.123, sess)
 		sqlRandomWalk("server3", "frontend", 100, 1.123, sess)
 
-		sqlRandomWalk("server1", "backend", 100, 1.123, sess)
-		sqlRandomWalk("server2", "backend", 100, 1.123, sess)
-		sqlRandomWalk("server3", "backend", 100, 1.123, sess)
-		sqlRandomWalk("db-server1", "backend", 100, 1.123, sess)
-
 		return err
 	})
 }

+ 18 - 5
pkg/tsdb/models.go

@@ -27,6 +27,7 @@ type Request struct {
 type Response struct {
 	BatchTimings []*BatchTiming          `json:"timings"`
 	Results      map[string]*QueryResult `json:"results"`
+	Message      string                  `json:"message,omitempty"`
 }
 
 type BatchTiming struct {
@@ -45,18 +46,30 @@ func (br *BatchResult) WithError(err error) *BatchResult {
 }
 
 type QueryResult struct {
-	Error       error           `json:"-"`
-	ErrorString string          `json:"error"`
-	RefId       string          `json:"refId"`
-	Series      TimeSeriesSlice `json:"series"`
+	Error       error            `json:"-"`
+	ErrorString string           `json:"error,omitempty"`
+	RefId       string           `json:"refId"`
+	Meta        *simplejson.Json `json:"meta,omitempty"`
+	Series      TimeSeriesSlice  `json:"series"`
+	Tables      []*Table         `json:"tables"`
 }
 
 type TimeSeries struct {
 	Name   string            `json:"name"`
 	Points TimeSeriesPoints  `json:"points"`
-	Tags   map[string]string `json:"tags"`
+	Tags   map[string]string `json:"tags,omitempty"`
 }
 
+type Table struct {
+	Columns []TableColumn `json:"columns"`
+	Rows    []RowValues   `json:"rows"`
+}
+
+type TableColumn struct {
+	Text string `json:"text"`
+}
+
+type RowValues []interface{}
 type TimePoint [2]null.Float
 type TimeSeriesPoints []TimePoint
 type TimeSeriesSlice []*TimeSeries

+ 80 - 0
pkg/tsdb/mysql/macros.go

@@ -0,0 +1,80 @@
+package mysql
+
+import (
+	"fmt"
+	"regexp"
+
+	"github.com/grafana/grafana/pkg/tsdb"
+)
+
+//const rsString = `(?:"([^"]*)")`;
+const rsIdentifier = `([_a-zA-Z0-9]+)`
+const sExpr = `\$` + rsIdentifier + `\(([^\)]*)\)`
+
+type SqlMacroEngine interface {
+	Interpolate(sql string) (string, error)
+}
+
+type MySqlMacroEngine struct {
+	TimeRange *tsdb.TimeRange
+}
+
+func NewMysqlMacroEngine(timeRange *tsdb.TimeRange) SqlMacroEngine {
+	return &MySqlMacroEngine{
+		TimeRange: timeRange,
+	}
+}
+
+func (m *MySqlMacroEngine) Interpolate(sql string) (string, error) {
+	rExp, _ := regexp.Compile(sExpr)
+	var macroError error
+
+	sql = ReplaceAllStringSubmatchFunc(rExp, sql, func(groups []string) string {
+		res, err := m.EvaluateMacro(groups[1], groups[2:])
+		if err != nil && macroError == nil {
+			macroError = err
+			return "macro_error()"
+		}
+		return res
+	})
+
+	if macroError != nil {
+		return "", macroError
+	}
+
+	return sql, nil
+}
+
+func ReplaceAllStringSubmatchFunc(re *regexp.Regexp, str string, repl func([]string) string) string {
+	result := ""
+	lastIndex := 0
+
+	for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) {
+		groups := []string{}
+		for i := 0; i < len(v); i += 2 {
+			groups = append(groups, str[v[i]:v[i+1]])
+		}
+
+		result += str[lastIndex:v[0]] + repl(groups)
+		lastIndex = v[1]
+	}
+
+	return result + str[lastIndex:]
+}
+
+func (m *MySqlMacroEngine) EvaluateMacro(name string, args []string) (string, error) {
+	switch name {
+	case "__time":
+		if len(args) == 0 {
+			return "", fmt.Errorf("missing time column argument for macro %v", name)
+		}
+		return fmt.Sprintf("UNIX_TIMESTAMP(%s) as time_sec", args[0]), nil
+	case "__timeFilter":
+		if len(args) == 0 {
+			return "", fmt.Errorf("missing time column argument for macro %v", name)
+		}
+		return fmt.Sprintf("%s > FROM_UNIXTIME(%d) AND %s < FROM_UNIXTIME(%d)", args[0], uint64(m.TimeRange.GetFromAsMsEpoch()/1000), args[0], uint64(m.TimeRange.GetToAsMsEpoch()/1000)), nil
+	default:
+		return "", fmt.Errorf("Unknown macro %v", name)
+	}
+}

+ 43 - 0
pkg/tsdb/mysql/macros_test.go

@@ -0,0 +1,43 @@
+package mysql
+
+import (
+	"testing"
+
+	"github.com/grafana/grafana/pkg/tsdb"
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func TestMacroEngine(t *testing.T) {
+	Convey("MacroEngine", t, func() {
+
+		Convey("interpolate __time function", func() {
+			engine := &MySqlMacroEngine{}
+
+			sql, err := engine.Interpolate("select $__time(time_column)")
+			So(err, ShouldBeNil)
+
+			So(sql, ShouldEqual, "select UNIX_TIMESTAMP(time_column) as time_sec")
+		})
+
+		Convey("interpolate __time function wrapped in aggregation", func() {
+			engine := &MySqlMacroEngine{}
+
+			sql, err := engine.Interpolate("select min($__time(time_column))")
+			So(err, ShouldBeNil)
+
+			So(sql, ShouldEqual, "select min(UNIX_TIMESTAMP(time_column) as time_sec)")
+		})
+
+		Convey("interpolate __timeFilter function", func() {
+			engine := &MySqlMacroEngine{
+				TimeRange: &tsdb.TimeRange{From: "5m", To: "now"},
+			}
+
+			sql, err := engine.Interpolate("WHERE $__timeFilter(time_column)")
+			So(err, ShouldBeNil)
+
+			So(sql, ShouldEqual, "WHERE time_column > FROM_UNIXTIME(18446744066914186738) AND time_column < FROM_UNIXTIME(18446744066914187038)")
+		})
+
+	})
+}

+ 117 - 14
pkg/tsdb/mysql/mysql.go

@@ -7,9 +7,13 @@ import (
 	"strconv"
 	"sync"
 
+	"time"
+
+	"github.com/go-sql-driver/mysql"
 	"github.com/go-xorm/core"
 	"github.com/go-xorm/xorm"
 	"github.com/grafana/grafana/pkg/components/null"
+	"github.com/grafana/grafana/pkg/components/simplejson"
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/tsdb"
@@ -81,6 +85,7 @@ func (e *MysqlExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, co
 		QueryResults: make(map[string]*tsdb.QueryResult),
 	}
 
+	macroEngine := NewMysqlMacroEngine(context.TimeRange)
 	session := e.engine.NewSession()
 	defer session.Close()
 	db := session.DB()
@@ -91,48 +96,145 @@ func (e *MysqlExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, co
 			continue
 		}
 
+		queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: query.RefId}
+		result.QueryResults[query.RefId] = queryResult
+
+		rawSql, err := macroEngine.Interpolate(rawSql)
+		if err != nil {
+			queryResult.Error = err
+			continue
+		}
+
+		queryResult.Meta.Set("sql", rawSql)
+
 		rows, err := db.Query(rawSql)
 		if err != nil {
-			result.QueryResults[query.RefId] = &tsdb.QueryResult{Error: err}
+			queryResult.Error = err
 			continue
 		}
 
 		defer rows.Close()
 
-		result.QueryResults[query.RefId] = e.TransformToTimeSeries(query, rows)
+		format := query.Model.Get("format").MustString("time_series")
+
+		switch format {
+		case "time_series":
+			err := e.TransformToTimeSeries(query, rows, queryResult)
+			if err != nil {
+				queryResult.Error = err
+				continue
+			}
+		case "table":
+			err := e.TransformToTable(query, rows, queryResult)
+			if err != nil {
+				queryResult.Error = err
+				continue
+			}
+		}
 	}
 
 	return result
 }
 
-func (e MysqlExecutor) TransformToTimeSeries(query *tsdb.Query, rows *core.Rows) *tsdb.QueryResult {
-	result := &tsdb.QueryResult{RefId: query.RefId}
+func (e MysqlExecutor) TransformToTable(query *tsdb.Query, rows *core.Rows, result *tsdb.QueryResult) error {
+	columnNames, err := rows.Columns()
+	columnCount := len(columnNames)
+
+	if err != nil {
+		return err
+	}
+
+	table := &tsdb.Table{
+		Columns: make([]tsdb.TableColumn, columnCount),
+		Rows:    make([]tsdb.RowValues, 0),
+	}
+
+	for i, name := range columnNames {
+		table.Columns[i].Text = name
+	}
+
+	columnTypes, err := rows.ColumnTypes()
+	if err != nil {
+		return err
+	}
+
+	rowLimit := 1000000
+	rowCount := 0
+
+	for ; rows.Next(); rowCount += 1 {
+		if rowCount > rowLimit {
+			return fmt.Errorf("MySQL query row limit exceeded, limit %d", rowLimit)
+		}
+
+		values, err := e.getTypedRowData(columnTypes, rows)
+		if err != nil {
+			return err
+		}
+
+		table.Rows = append(table.Rows, values)
+	}
+
+	result.Tables = append(result.Tables, table)
+	result.Meta.Set("rowCount", rowCount)
+	return nil
+}
+
+func (e MysqlExecutor) getTypedRowData(types []*sql.ColumnType, rows *core.Rows) (tsdb.RowValues, error) {
+	values := make([]interface{}, len(types))
+
+	for i, stype := range types {
+		switch stype.DatabaseTypeName() {
+		case mysql.FieldTypeNameVarString:
+			values[i] = new(string)
+		case mysql.FieldTypeNameLongLong:
+			values[i] = new(int64)
+		case mysql.FieldTypeNameDouble:
+			values[i] = new(float64)
+		case mysql.FieldTypeNameDateTime:
+			values[i] = new(time.Time)
+		default:
+			return nil, fmt.Errorf("Database type %s not supported", stype.DatabaseTypeName())
+		}
+	}
+
+	if err := rows.Scan(values...); err != nil {
+		return nil, err
+	}
+
+	return values, nil
+}
+
+func (e MysqlExecutor) TransformToTimeSeries(query *tsdb.Query, rows *core.Rows, result *tsdb.QueryResult) error {
 	pointsBySeries := make(map[string]*tsdb.TimeSeries)
 	columnNames, err := rows.Columns()
 
 	if err != nil {
-		result.Error = err
-		return result
+		return err
 	}
 
 	rowData := NewStringStringScan(columnNames)
-	for rows.Next() {
+	rowLimit := 1000000
+	rowCount := 0
+
+	for ; rows.Next(); rowCount += 1 {
+		if rowCount > rowLimit {
+			return fmt.Errorf("MySQL query row limit exceeded, limit %d", rowLimit)
+		}
+
 		err := rowData.Update(rows.Rows)
 		if err != nil {
-			e.log.Error("Mysql response parsing", "error", err)
-			result.Error = err
-			return result
+			e.log.Error("MySQL response parsing", "error", err)
+			return fmt.Errorf("MySQL response parsing error %v", err)
 		}
 
 		if rowData.metric == "" {
 			rowData.metric = "Unknown"
 		}
 
-		e.log.Info("Rows", "metric", rowData.metric, "time", rowData.time, "value", rowData.value)
+		//e.log.Debug("Rows", "metric", rowData.metric, "time", rowData.time, "value", rowData.value)
 
 		if !rowData.time.Valid {
-			result.Error = fmt.Errorf("Found row with no time value")
-			return result
+			return fmt.Errorf("Found row with no time value")
 		}
 
 		if series, exist := pointsBySeries[rowData.metric]; exist {
@@ -148,7 +250,8 @@ func (e MysqlExecutor) TransformToTimeSeries(query *tsdb.Query, rows *core.Rows)
 		result.Series = append(result.Series, value)
 	}
 
-	return result
+	result.Meta.Set("rowCount", rowCount)
+	return nil
 }
 
 type stringStringScan struct {

+ 6 - 6
public/app/core/components/search/search.ts

@@ -117,7 +117,7 @@ export class SearchCtrl {
   queryHasNoFilters() {
     var query = this.query;
     return query.query === '' && query.starred === false && query.tag.length === 0;
-  };
+  }
 
   filterByTag(tag, evt) {
     this.query.tag.push(tag);
@@ -127,7 +127,7 @@ export class SearchCtrl {
       evt.stopPropagation();
       evt.preventDefault();
     }
-  };
+  }
 
   removeTag(tag, evt) {
     this.query.tag = _.without(this.query.tag, tag);
@@ -135,7 +135,7 @@ export class SearchCtrl {
     this.giveSearchFocus = this.giveSearchFocus + 1;
     evt.stopPropagation();
     evt.preventDefault();
-  };
+  }
 
   getTags() {
     return this.backendSrv.get('/api/dashboards/tags').then((results) => {
@@ -146,19 +146,19 @@ export class SearchCtrl {
         this.search();
       }
     });
-  };
+  }
 
   showStarred() {
     this.query.starred = !this.query.starred;
     this.giveSearchFocus = this.giveSearchFocus + 1;
     this.search();
-  };
+  }
 
   search() {
     this.showImport = false;
     this.selectedIndex = 0;
     this.searchDashboards();
-  };
+  }
 
 }
 

+ 5 - 1
public/app/core/components/sidemenu/sidemenu.ts

@@ -84,7 +84,11 @@ export class SideMenuCtrl {
        return;
      }
 
-     if (this.orgItems.length < this.maxShownOrgs && (this.orgFilter === '' || org.name.indexOf(this.orgFilter) !== -1)){
+     if (this.orgItems.length === this.maxShownOrgs) {
+       return;
+     }
+
+     if (this.orgFilter === '' || (org.name.toLowerCase().indexOf(this.orgFilter.toLowerCase()) !== -1)) {
        this.orgItems.push({
          text: "Switch to " + org.name,
          icon: "fa fa-fw fa-random",

+ 1 - 1
public/app/core/controllers/signup_ctrl.ts

@@ -44,7 +44,7 @@ export class SignUpCtrl {
         window.location.href = config.appSubUrl + '/';
       }
     });
-  };
+  }
 }
 
 coreModule.controller('SignUpCtrl', SignUpCtrl);

+ 1 - 1
public/app/core/directives/plugin_component.ts

@@ -75,7 +75,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
 
       if (!PanelCtrl || PanelCtrl.registered) {
         return componentInfo;
-      };
+      }
 
       if (PanelCtrl.templatePromise) {
         return PanelCtrl.templatePromise.then(res => {

+ 3 - 3
public/app/core/services/backend_srv.ts

@@ -23,7 +23,7 @@ export class BackendSrv {
 
   post(url, data) {
     return this.request({ method: 'POST', url: url, data: data });
-  };
+  }
 
   patch(url, data) {
     return this.request({ method: 'PATCH', url: url, data: data });
@@ -98,7 +98,7 @@ export class BackendSrv {
       this.$timeout(this.requestErrorHandler.bind(this, err), 50);
       throw err;
     });
-  };
+  }
 
   addCanceler(requestId, canceler) {
     if (requestId in this.inFlightRequests) {
@@ -186,7 +186,7 @@ export class BackendSrv {
         this.inFlightRequests[options.requestId].shift();
       }
     });
-  };
+  }
 
   loginPing() {
     return this.request({url: '/api/login/ping', method: 'GET', retry: 1 });

+ 1 - 1
public/app/core/time_series2.ts

@@ -92,7 +92,7 @@ export default class TimeSeries {
         this.yaxis = override.yaxis;
       }
     }
-  };
+  }
 
   getFlotPairs(fillStyle) {
     var result = [];

+ 4 - 4
public/app/core/utils/file_export.ts

@@ -12,7 +12,7 @@ export function exportSeriesListToCsv(seriesList) {
         });
     });
     saveSaveBlob(text, 'grafana_data_export.csv');
-};
+}
 
 export function exportSeriesListToCsvColumns(seriesList) {
     var text = 'sep=;\nTime;';
@@ -47,7 +47,7 @@ export function exportSeriesListToCsvColumns(seriesList) {
         text += '\n';
     }
     saveSaveBlob(text, 'grafana_data_export.csv');
-};
+}
 
 export function exportTableDataToCsv(table) {
     var text = 'sep=;\n';
@@ -64,9 +64,9 @@ export function exportTableDataToCsv(table) {
         text += '\n';
     });
     saveSaveBlob(text, 'grafana_data_export.csv');
-};
+}
 
 export function saveSaveBlob(payload, fname) {
     var blob = new Blob([payload], { type: "text/csv;charset=utf-8" });
     window.saveAs(blob, fname);
-};
+}

+ 2 - 2
public/app/features/annotations/editor_ctrl.ts

@@ -69,7 +69,7 @@ export class AnnotationsEditorCtrl {
     this.reset();
     this.mode = 'list';
     this.$scope.broadcastRefresh();
-  };
+  }
 
   add() {
     this.annotations.push(this.currentAnnotation);
@@ -77,7 +77,7 @@ export class AnnotationsEditorCtrl {
     this.mode = 'list';
     this.$scope.broadcastRefresh();
     this.$scope.dashboard.updateSubmenuVisibility();
-  };
+  }
 
   removeAnnotation(annotation) {
     var index = _.indexOf(this.annotations, annotation);

+ 0 - 1
public/app/features/dashboard/row/row_ctrl.ts

@@ -216,7 +216,6 @@ coreModule.directive('panelDropZone', function($timeout) {
       }
 
       if (indrag === true) {
-        var dropZoneSpan = 12 - row.span;
         if (dropZoneSpan > 1) {
           return showPanel(dropZoneSpan, 'Drop Here');
         }

+ 5 - 5
public/app/features/dashboard/time_srv.ts

@@ -60,7 +60,7 @@ class TimeSrv {
     if (_.isString(this.time.to) && this.time.to.indexOf('Z') >= 0) {
       this.time.to = moment(this.time.to).utc();
     }
-  };
+  }
 
   private parseUrlParam(value) {
     if (value.indexOf('now') !== -1) {
@@ -92,7 +92,7 @@ class TimeSrv {
     if (params.refresh) {
       this.refresh = params.refresh || this.refresh;
     }
-  };
+  }
 
   private routeUpdated() {
     var params = this.$location.search();
@@ -154,7 +154,7 @@ class TimeSrv {
 
   private cancelNextRefresh() {
     this.timer.cancel(this.refreshTimer);
-  };
+  }
 
   setTime(time, fromRouteUpdate?) {
     _.extend(this.time, time);
@@ -184,8 +184,8 @@ class TimeSrv {
   timeRangeForUrl() {
     var range = this.timeRange().raw;
 
-    if (moment.isMoment(range.from)) { range.from = range.from.valueOf(); }
-    if (moment.isMoment(range.to)) { range.to = range.to.valueOf(); }
+    if (moment.isMoment(range.from)) { range.from = range.from.valueOf().toString(); }
+    if (moment.isMoment(range.to)) { range.to = range.to.valueOf().toString(); }
 
     return range;
   }

+ 13 - 2
public/app/features/panel/metrics_panel_ctrl.ts

@@ -31,6 +31,7 @@ class MetricsPanelCtrl extends PanelCtrl {
   skipDataOnInit: boolean;
   dataStream: any;
   dataSubscription: any;
+  dataList: any;
 
   constructor($scope, $injector) {
     super($scope, $injector);
@@ -106,6 +107,16 @@ class MetricsPanelCtrl extends PanelCtrl {
       this.loading = false;
       this.error = err.message || "Request Error";
       this.inspector = {error: err};
+
+      if (err.data) {
+        if (err.data.message) {
+          this.error = err.data.message;
+        }
+        if (err.data.error) {
+          this.error = err.data.error;
+        }
+      }
+
       this.events.emit('data-error', err);
       console.log('Panel data error:', err);
     });
@@ -136,7 +147,7 @@ class MetricsPanelCtrl extends PanelCtrl {
     this.calculateInterval();
 
     return this.datasource;
-  };
+  }
 
   calculateInterval() {
     var intervalOverride = this.panel.interval;
@@ -194,7 +205,7 @@ class MetricsPanelCtrl extends PanelCtrl {
     if (this.panel.hideTimeOverride) {
       this.timeInfo = '';
     }
-  };
+  }
 
   issueQueries(datasource) {
     this.datasource = datasource;

+ 2 - 2
public/app/features/panel/partials/query_editor_row.html

@@ -1,7 +1,7 @@
 
 <div class="gf-form-query">
-	<div class="gf-form">
-    <label class="gf-form-label gf-form-query-letter-cell">
+	<div class="gf-form gf-form-query-letter-cell">
+    <label class="gf-form-label">
       <a class="pointer" tabindex="1" ng-click="ctrl.toggleCollapse()">
         <span  ng-class="{muted: !ctrl.canCollapse}" class="gf-form-query-letter-cell-carret">
           <i class="fa fa-caret-down" ng-hide="ctrl.collapsed"></i>

+ 1 - 1
public/app/features/playlist/playlist_edit_ctrl.ts

@@ -74,7 +74,7 @@ export class PlaylistEditCtrl {
       return playlistItem === listedPlaylistItem;
     });
     this.filterFoundPlaylistItems();
-  };
+  }
 
   savePlaylist(playlist, playlistItems) {
     var savePromise;

+ 1 - 1
public/app/features/plugins/ds_edit_ctrl.ts

@@ -154,7 +154,7 @@ export class DataSourceEditCtrl {
           this.$location.path('datasources/edit/' + result.id);
         });
       }
-    };
+    }
 
     confirmDelete() {
       this.backendSrv.delete('/api/datasources/' + this.current.id).then(() => {

+ 1 - 1
public/app/features/templating/all.ts

@@ -17,4 +17,4 @@ export {
   CustomVariable,
   ConstantVariable,
   AdhocVariable,
-}
+};

+ 1 - 1
public/app/features/templating/variable_srv.ts

@@ -92,7 +92,7 @@ export class VariableSrv {
 
   addVariable(model) {
     var variable = this.createVariableFromModel(model);
-    this.variables.push(this.createVariableFromModel(variable));
+    this.variables.push(variable);
     return variable;
   }
 

+ 1 - 1
public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js

@@ -104,7 +104,7 @@ function (angular, _) {
         query = $scope.datasource.getDimensionKeys($scope.target.namespace, $scope.target.region);
       } else if (segment.type === 'value')  {
         var dimensionKey = $scope.dimSegments[$index-2].value;
-        query = $scope.datasource.getDimensionValues(target.region, target.namespace, target.metricName, dimensionKey, {});
+        query = $scope.datasource.getDimensionValues(target.region, target.namespace, target.metricName, dimensionKey, target.dimensions);
       }
 
       return query.then($scope.transformToSegments(true)).then(function(results) {

+ 1 - 1
public/app/plugins/datasource/grafana/datasource.ts

@@ -13,7 +13,7 @@ class GrafanaDatasource {
 
   metricFindQuery() {
     return this.$q.when([]);
-  };
+  }
 
   annotationQuery(options) {
     return this.backendSrv.get('/api/annotations', {

+ 4 - 4
public/app/plugins/datasource/influxdb/datasource.ts

@@ -120,7 +120,7 @@ export default class InfluxDatasource {
 
       return {data: seriesList};
     });
-  };
+  }
 
   annotationQuery(options) {
     if (!options.annotation.query) {
@@ -137,7 +137,7 @@ export default class InfluxDatasource {
       }
       return new InfluxSeries({series: data.results[0].series, annotation: options.annotation}).getAnnotations();
     });
-  };
+  }
 
   targetContainsTemplate(target) {
     for (let group of target.groupBy) {
@@ -155,7 +155,7 @@ export default class InfluxDatasource {
     }
 
     return false;
-  };
+  }
 
   metricFindQuery(query: string, options?: any) {
     var interpolated = this.templateSrv.replace(query, null, 'regex');
@@ -256,7 +256,7 @@ export default class InfluxDatasource {
         }
       }
     });
-  };
+  }
 
   getTimeFilter(options) {
     var from = this.getInfluxTime(options.rangeRaw.from, false);

+ 1 - 1
public/app/plugins/datasource/influxdb/influx_query.ts

@@ -195,7 +195,7 @@ export default class InfluxQuery {
 
     var escapedValues = _.map(value, kbn.regexEscape);
     return escapedValues.join('|');
-  };
+  }
 
   render(interpolate?) {
     var target = this.target;

+ 7 - 1
public/app/plugins/datasource/influxdb/query_builder.js

@@ -91,7 +91,13 @@ function (_) {
         query +=  ' WHERE ' + whereConditions.join(' ');
       }
     }
-
+    if (type === 'MEASUREMENTS')
+    {
+      query += ' LIMIT 100';
+      //Solve issue #2524 by limiting the number of measurements returned
+      //LIMIT must be after WITH MEASUREMENT and WHERE clauses
+      //This also could be used for TAG KEYS and TAG VALUES, if desired
+    }
     return query;
   };
 

+ 1 - 1
public/app/plugins/datasource/influxdb/query_part.ts

@@ -28,7 +28,7 @@ function createPart(part): any {
   }
 
   return new QueryPart(part, def);
-};
+}
 
 function register(options: any) {
   index[options.type] = new QueryPartDef(options);

+ 5 - 5
public/app/plugins/datasource/influxdb/specs/query_builder_specs.ts

@@ -34,31 +34,31 @@ describe('InfluxQueryBuilder', function() {
     it('should have no conditions in measurement query for query with no tags', function() {
       var builder = new InfluxQueryBuilder({ measurement: '', tags: [] });
       var query = builder.buildExploreQuery('MEASUREMENTS');
-      expect(query).to.be('SHOW MEASUREMENTS');
+      expect(query).to.be('SHOW MEASUREMENTS LIMIT 100');
     });
 
     it('should have no conditions in measurement query for query with no tags and empty query', function() {
       var builder = new InfluxQueryBuilder({ measurement: '', tags: [] });
       var query = builder.buildExploreQuery('MEASUREMENTS', undefined, '');
-      expect(query).to.be('SHOW MEASUREMENTS');
+      expect(query).to.be('SHOW MEASUREMENTS LIMIT 100');
     });
 
     it('should have WITH MEASUREMENT in measurement query for non-empty query with no tags', function() {
       var builder = new InfluxQueryBuilder({ measurement: '', tags: [] });
       var query = builder.buildExploreQuery('MEASUREMENTS', undefined, 'something');
-      expect(query).to.be('SHOW MEASUREMENTS WITH MEASUREMENT =~ /something/');
+      expect(query).to.be('SHOW MEASUREMENTS WITH MEASUREMENT =~ /something/ LIMIT 100');
     });
 
     it('should have WITH MEASUREMENT WHERE in measurement query for non-empty query with tags', function() {
           var builder = new InfluxQueryBuilder({ measurement: '', tags: [{key: 'app', value: 'email'}] });
           var query = builder.buildExploreQuery('MEASUREMENTS', undefined, 'something');
-          expect(query).to.be("SHOW MEASUREMENTS WITH MEASUREMENT =~ /something/ WHERE \"app\" = 'email'");
+          expect(query).to.be("SHOW MEASUREMENTS WITH MEASUREMENT =~ /something/ WHERE \"app\" = 'email' LIMIT 100");
     });
 
     it('should have where condition in measurement query for query with tags', function() {
       var builder = new InfluxQueryBuilder({measurement: '', tags: [{key: 'app', value: 'email'}]});
       var query = builder.buildExploreQuery('MEASUREMENTS');
-      expect(query).to.be("SHOW MEASUREMENTS WHERE \"app\" = 'email'");
+      expect(query).to.be("SHOW MEASUREMENTS WHERE \"app\" = 'email' LIMIT 100");
     });
 
     it('should have where tag name IN filter in tag values query for query with one tag', function() {

+ 1 - 1
public/app/plugins/datasource/mixed/datasource.ts

@@ -30,4 +30,4 @@ class MixedDatasource {
   }
 }
 
-export {MixedDatasource, MixedDatasource as Datasource}
+export {MixedDatasource, MixedDatasource as Datasource};

+ 55 - 26
public/app/plugins/datasource/mysql/datasource.ts

@@ -6,12 +6,23 @@ export class MysqlDatasource {
   id: any;
   name: any;
 
-  /** @ngInject */
-  constructor(instanceSettings, private backendSrv, private $q) {
+  /** @ngInject **/
+  constructor(instanceSettings, private backendSrv, private $q, private templateSrv) {
     this.name = instanceSettings.name;
     this.id = instanceSettings.id;
   }
 
+  interpolateVariable(value) {
+    if (typeof value === 'string') {
+      return '\"' + value + '\"';
+    }
+
+    var quotedValues = _.map(value, function(val) {
+      return '\"' + val + '\"';
+    });
+    return  quotedValues.join(',');
+  }
+
   query(options) {
     var queries = _.filter(options.targets, item => {
       return item.hide !== true;
@@ -21,7 +32,8 @@ export class MysqlDatasource {
         intervalMs: options.intervalMs,
         maxDataPoints: options.maxDataPoints,
         datasourceId: this.id,
-        rawSql: item.rawSql,
+        rawSql: this.templateSrv.replace(item.rawSql, options.scopedVars, this.interpolateVariable),
+        format: item.format,
       };
     });
 
@@ -29,32 +41,49 @@ export class MysqlDatasource {
       return this.$q.when({data: []});
     }
 
-    return this.backendSrv.post('/api/tsdb/query', {
-      from: options.range.from.valueOf().toString(),
-      to: options.range.to.valueOf().toString(),
-      queries: queries,
-    }).then(res => {
-      console.log('mysql response', res);
-
-      var data = [];
-      if (res.results) {
-        _.forEach(res.results, queryRes => {
-
-          if (queryRes.error) {
-            throw {error: queryRes.error, message: queryRes.error};
-          }
-
-          for (let series of queryRes.series) {
-            data.push({
-              target: series.name,
-              datapoints: series.points
-            });
-          }
-        });
+    return this.backendSrv.datasourceRequest({
+      url: '/api/tsdb/query',
+      method: 'POST',
+      data: {
+        from: options.range.from.valueOf().toString(),
+        to: options.range.to.valueOf().toString(),
+        queries: queries,
       }
+    }).then(this.processQueryResult.bind(this));
+  }
 
+  processQueryResult(res) {
+    var data = [];
+
+    if (!res.data.results) {
       return {data: data};
-    });
+    }
+
+    for (let key in res.data.results) {
+      let queryRes = res.data.results[key];
+
+      if (queryRes.series) {
+        for (let series of queryRes.series) {
+          data.push({
+            target: series.name,
+            datapoints: series.points,
+            refId: queryRes.refId,
+            meta: queryRes.meta,
+          });
+        }
+      }
+
+      if (queryRes.tables) {
+        for (let table of queryRes.tables) {
+          table.type = 'table';
+          table.refId = queryRes.refId;
+          table.meta = queryRes.meta;
+          data.push(table);
+        }
+      }
+    }
+
+    return {data: data};
   }
 }
 

+ 0 - 0
public/img/mysql_logo.svg → public/app/plugins/datasource/mysql/img/mysql_logo.svg


+ 2 - 20
public/app/plugins/datasource/mysql/module.ts

@@ -1,27 +1,9 @@
 ///<reference path="../../../headers/common.d.ts" />
 
 import angular from 'angular';
+import _ from 'lodash';
 import {MysqlDatasource} from './datasource';
-import {QueryCtrl} from 'app/plugins/sdk';
-
-class MysqlQueryCtrl extends QueryCtrl {
-  static templateUrl = 'partials/query.editor.html';
-
-  resultFormats: any;
-  target: any;
-
-  constructor($scope, $injector) {
-    super($scope, $injector);
-
-    this.target.resultFormat = 'time_series';
-    this.target.alias = "{{table}}{{col_3}}";
-    this.resultFormats = [
-      {text: 'Time series', value: 'time_series'},
-      {text: 'Table', value: 'table'},
-    ];
-
-  }
-}
+import {MysqlQueryCtrl} from './query_ctrl';
 
 class MysqlConfigCtrl {
   static templateUrl = 'partials/config.html';

+ 40 - 6
public/app/plugins/datasource/mysql/partials/query.editor.html

@@ -1,7 +1,7 @@
 <query-editor-row query-ctrl="ctrl" can-collapse="false">
   <div class="gf-form-inline">
 		<div class="gf-form gf-form--grow">
-			<textarea rows="6" class="gf-form-input" ng-model="ctrl.target.rawSql" spellcheck="false" placeholder="query expression" data-min-length=0 data-items=100 ng-model-onblur ng-change="ctrl.panelCtrl.refresh()"></textarea>
+			<textarea rows="10" class="gf-form-input" ng-model="ctrl.target.rawSql" spellcheck="false" placeholder="query expression" data-min-length=0 data-items=100 ng-model-onblur ng-change="ctrl.panelCtrl.refresh()"></textarea>
 		</div>
 	</div>
 
@@ -9,17 +9,51 @@
     <div class="gf-form">
 			<label class="gf-form-label query-keyword">Format as</label>
 			<div class="gf-form-select-wrapper">
-				<select class="gf-form-input gf-size-auto" ng-model="ctrl.target.resultFormat" ng-options="f.value as f.text for f in ctrl.resultFormats" ng-change="ctrl.refresh()"></select>
+				<select class="gf-form-input gf-size-auto" ng-model="ctrl.target.format" ng-options="f.value as f.text for f in ctrl.formats" ng-change="ctrl.refresh()"></select>
 			</div>
 		</div>
-    <div class="gf-form max-width-30">
-			<label class="gf-form-label query-keyword">Name by</label>
-			<input type="text" class="gf-form-input" ng-model="ctrl.target.alias" spellcheck='false' placeholder="pattern" ng-blur="ctrl.refresh()">
+		<div class="gf-form">
+      <label class="gf-form-label query-keyword" ng-click="ctrl.showHelp = !ctrl.showHelp">
+        Show Help
+        <i class="fa fa-caret-down" ng-show="ctrl.showHelp"></i>
+        <i class="fa fa-caret-right" ng-hide="ctrl.showHelp"></i>
+      </label>
+		</div>
+		<div class="gf-form" ng-show="ctrl.lastQueryMeta">
+      <label class="gf-form-label query-keyword" ng-click="ctrl.showLastQuerySQL = !ctrl.showLastQuerySQL">
+        Generated SQL
+        <i class="fa fa-caret-down" ng-show="ctrl.showLastQuerySQL"></i>
+        <i class="fa fa-caret-right" ng-hide="ctrl.showLastQuerySQL"></i>
+      </label>
 		</div>
-
 		<div class="gf-form gf-form--grow">
 			<div class="gf-form-label gf-form-label--grow"></div>
 		</div>
 	</div>
 
+	<div class="gf-form" ng-show="ctrl.showLastQuerySQL">
+		<pre class="gf-form-pre">{{ctrl.lastQueryMeta.sql}}</pre>
+	</div>
+
+	<div class="gf-form"  ng-show="ctrl.showHelp">
+		<pre class="gf-form-pre alert alert-info">Time series:
+- return column named time_sec (UTC in seconds), use UNIX_TIMESTAMP(column)
+- return column named value for the time point value
+- return column named metric to represent the series name
+
+Table:
+- return any set of columns
+
+Macros:
+- $__time(column) -&gt; UNIX_TIMESTAMP(column) as time_sec
+- $__timeFilter(column) -&gt;  UNIX_TIMESTAMP(time_date_time) &gt; from AND UNIX_TIMESTAMP(time_date_time) &lt; 1492750877
+		</pre>
+	</div>
+
+	</div>
+
+	<div class="gf-form" ng-show="ctrl.lastQueryError">
+		<pre class="gf-form-pre alert alert-error">{{ctrl.lastQueryError}}</pre>
+	</div>
+
 </query-editor-row>

+ 79 - 0
public/app/plugins/datasource/mysql/query_ctrl.ts

@@ -0,0 +1,79 @@
+///<reference path="../../../headers/common.d.ts" />
+
+import angular from 'angular';
+import _ from 'lodash';
+import {MysqlDatasource} from './datasource';
+import {QueryCtrl} from 'app/plugins/sdk';
+
+export interface MysqlQuery {
+  refId: string;
+  format: string;
+  alias: string;
+  rawSql: string;
+}
+
+export interface QueryMeta {
+  sql: string;
+}
+
+
+var defaulQuery = `SELECT
+  UNIX_TIMESTAMP(<time_column>) as time_sec,
+  <value column> as value,
+  <series name column> as metric
+FROM <table name>
+WHERE $__timeFilter(time_column)
+ORDER BY <time_column> ASC
+`;
+
+export class MysqlQueryCtrl extends QueryCtrl {
+  static templateUrl = 'partials/query.editor.html';
+
+  showLastQuerySQL: boolean;
+  formats: any[];
+  target: MysqlQuery;
+  lastQueryMeta: QueryMeta;
+  lastQueryError: string;
+  showHelp: boolean;
+
+  /** @ngInject **/
+  constructor($scope, $injector) {
+    super($scope, $injector);
+
+    this.target.format = this.target.format || 'time_series';
+    this.target.alias = "";
+    this.formats = [
+      {text: 'Time series', value: 'time_series'},
+      {text: 'Table', value: 'table'},
+    ];
+
+    if (!this.target.rawSql) {
+      this.target.rawSql = defaulQuery;
+    }
+
+    this.panelCtrl.events.on('data-received', this.onDataReceived.bind(this), $scope);
+    this.panelCtrl.events.on('data-error', this.onDataError.bind(this), $scope);
+  }
+
+  onDataReceived(dataList) {
+    this.lastQueryMeta = null;
+    this.lastQueryError = null;
+
+    let anySeriesFromQuery = _.find(dataList, {refId: this.target.refId});
+    if (anySeriesFromQuery) {
+      this.lastQueryMeta = anySeriesFromQuery.meta;
+    }
+  }
+
+  onDataError(err) {
+    if (err.data && err.data.results) {
+      let queryRes = err.data.results[this.target.refId];
+      if (queryRes) {
+        this.lastQueryMeta = queryRes.meta;
+        this.lastQueryError = queryRes.error;
+      }
+    }
+  }
+}
+
+

+ 1 - 1
public/app/plugins/panel/alertlist/module.ts

@@ -139,4 +139,4 @@ class AlertListPanel extends PanelCtrl {
 export {
   AlertListPanel,
   AlertListPanel as PanelCtrl
-}
+};

+ 1 - 1
public/app/plugins/panel/dashlist/module.ts

@@ -125,4 +125,4 @@ class DashListCtrl extends PanelCtrl {
   }
 }
 
-export {DashListCtrl, DashListCtrl as PanelCtrl}
+export {DashListCtrl, DashListCtrl as PanelCtrl};

+ 1 - 1
public/app/plugins/panel/gettingstarted/module.ts

@@ -116,4 +116,4 @@ class GettingStartedPanelCtrl extends PanelCtrl {
   }
 }
 
-export {GettingStartedPanelCtrl, GettingStartedPanelCtrl as PanelCtrl}
+export {GettingStartedPanelCtrl, GettingStartedPanelCtrl as PanelCtrl};

+ 2 - 2
public/app/plugins/panel/graph/graph.ts

@@ -271,7 +271,7 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) {
         };
 
         for (let i = 0; i < data.length; i++) {
-          var series = data[i];
+          let series = data[i];
           series.data = series.getFlotPairs(series.nullPointMode || panel.nullPointMode);
 
           // if hidden remove points and disable stack
@@ -287,7 +287,7 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) {
             options.series.bars.align = 'center';
 
             for (let i = 0; i < data.length; i++) {
-              var series = data[i];
+              let series = data[i];
               series.data = [[i + 1, series.stats[panel.xaxis.values[0]]]];
             }
 

+ 2 - 2
public/app/plugins/panel/graph/module.ts

@@ -284,7 +284,7 @@ class GraphCtrl extends MetricsPanelCtrl {
     }
     info.yaxis = override.yaxis = info.yaxis === 2 ? 1 : 2;
     this.render();
-  };
+  }
 
   addSeriesOverride(override) {
     this.panel.seriesOverrides.push(override || {});
@@ -316,4 +316,4 @@ class GraphCtrl extends MetricsPanelCtrl {
 
 }
 
-export {GraphCtrl, GraphCtrl as PanelCtrl}
+export {GraphCtrl, GraphCtrl as PanelCtrl};

+ 2 - 2
public/app/plugins/panel/heatmap/axes_editor.ts

@@ -26,8 +26,8 @@ export class AxesEditorCtrl {
     };
 
     this.dataFormats = {
-      'Timeseries': 'timeseries',
-      'ES histogram': 'es_histogram'
+      'TS': 'timeseries',
+      'TS Pre-bucketed': 'tsbuckets'
     };
   }
 

+ 1 - 1
public/app/plugins/panel/heatmap/heatmap_ctrl.ts

@@ -135,7 +135,7 @@ export class HeatmapCtrl extends MetricsPanelCtrl {
     let xBucketSize, yBucketSize, heatmapStats, bucketsData;
     let logBase = this.panel.yAxis.logBase;
 
-    if (this.panel.dataFormat === 'es_histogram') {
+    if (this.panel.dataFormat === 'tsbuckets') {
       heatmapStats = this.parseHistogramSeries(this.series);
       bucketsData = elasticHistogramToHeatmap(this.series);
 

+ 1 - 1
public/app/plugins/panel/pluginlist/module.ts

@@ -70,4 +70,4 @@ class PluginListCtrl extends PanelCtrl {
   }
 }
 
-export {PluginListCtrl, PluginListCtrl as PanelCtrl}
+export {PluginListCtrl, PluginListCtrl as PanelCtrl};

+ 9 - 1
public/app/plugins/panel/singlestat/editor.html

@@ -3,11 +3,19 @@
     <h5 class="section-heading">Value</h5>
 
     <div class="gf-form-inline">
-      <div class="gf-form">
+      <div class="gf-form" ng-show="ctrl.dataType === 'timeseries'">
         <label class="gf-form-label width-6">Stat</label>
         <div class="gf-form-select-wrapper width-7">
           <select class="gf-form-input" ng-model="ctrl.panel.valueName" ng-options="f for f in ctrl.valueNameOptions" ng-change="ctrl.render()"></select>
         </div>
+      </div>
+      <div class="gf-form" ng-show="ctrl.dataType === 'table'">
+        <label class="gf-form-label width-6">Column</label>
+        <div class="gf-form-select-wrapper width-7">
+          <select class="gf-form-input" ng-model="ctrl.panel.tableColumn" ng-options="f for f in ctrl.tableColumnOptions" ng-change="ctrl.refresh()"></select>
+        </div>
+      </div>
+      <div class="gf-form">
         <label class="gf-form-label width-6">Font size</label>
         <div class="gf-form-select-wrapper">
           <select class="gf-form-input" ng-model="ctrl.panel.valueFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()"></select>

+ 97 - 23
public/app/plugins/panel/singlestat/module.ts

@@ -14,6 +14,7 @@ import {MetricsPanelCtrl} from 'app/plugins/sdk';
 class SingleStatCtrl extends MetricsPanelCtrl {
   static templateUrl = 'module.html';
 
+  dataType = 'timeseries';
   series: any[];
   data: any;
   fontSizes: any[];
@@ -22,6 +23,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
   panel: any;
   events: any;
   valueNameOptions: any[] = ['min','max','avg', 'current', 'total', 'name', 'first', 'delta', 'diff', 'range'];
+  tableColumnOptions: any;
 
   // Set and populate defaults
   panelDefaults = {
@@ -67,7 +69,8 @@ class SingleStatCtrl extends MetricsPanelCtrl {
       maxValue: 100,
       thresholdMarkers: true,
       thresholdLabels: false
-    }
+    },
+    tableColumn: ''
   };
 
   /** @ngInject */
@@ -98,11 +101,16 @@ class SingleStatCtrl extends MetricsPanelCtrl {
   }
 
   onDataReceived(dataList) {
-    this.series = dataList.map(this.seriesHandler.bind(this));
-
-    var data: any = {};
-    this.setValues(data);
-
+    const data: any = {};
+    if (dataList.length > 0 && dataList[0].type === 'table'){
+      this.dataType = 'table';
+      const tableData = dataList.map(this.tableHandler.bind(this));
+      this.setTableValues(tableData, data);
+    } else {
+      this.dataType = 'timeseries';
+      this.series = dataList.map(this.seriesHandler.bind(this));
+      this.setValues(data);
+    }
     this.data = data;
     this.render();
   }
@@ -117,6 +125,69 @@ class SingleStatCtrl extends MetricsPanelCtrl {
     return series;
   }
 
+  tableHandler(tableData) {
+    const datapoints = [];
+    const columnNames = {};
+
+    tableData.columns.forEach((column, columnIndex) => {
+      columnNames[columnIndex] = column.text;
+    });
+
+    this.tableColumnOptions = columnNames;
+    if (!_.find(tableData.columns, ['text', this.panel.tableColumn])) {
+      this.setTableColumnToSensibleDefault(tableData);
+    }
+
+    tableData.rows.forEach((row) => {
+      const datapoint = {};
+
+      row.forEach((value, columnIndex) => {
+        const key = columnNames[columnIndex];
+        datapoint[key] = value;
+      });
+
+      datapoints.push(datapoint);
+    });
+
+    return datapoints;
+  }
+
+  setTableColumnToSensibleDefault(tableData) {
+    if (this.tableColumnOptions.length === 1) {
+      this.panel.tableColumn = this.tableColumnOptions[0];
+    } else {
+      this.panel.tableColumn = _.find(tableData.columns, (col) => { return col.type !== 'time'; }).text;
+    }
+  }
+
+  setTableValues(tableData, data) {
+    if (!tableData || tableData.length === 0) {
+      return;
+    }
+
+    if (tableData[0].length === 0 || !tableData[0][0][this.panel.tableColumn]) {
+      return;
+    }
+
+    let highestValue = 0;
+    let lowestValue = Number.MAX_VALUE;
+    const datapoint = tableData[0][0];
+    data.value = datapoint[this.panel.tableColumn];
+
+    if (_.isString(data.value)) {
+      data.valueFormatted = _.escape(data.value);
+      data.value = 0;
+      data.valueRounded = 0;
+    } else {
+      const decimalInfo = this.getDecimalsForValue(data.value);
+      const formatFunc = kbn.valueFormats[this.panel.format];
+      data.valueFormatted = formatFunc(datapoint[this.panel.tableColumn], decimalInfo.decimals, decimalInfo.scaledDecimals);
+      data.valueRounded = kbn.roundValue(data.value, this.panel.decimals || 0);
+    }
+
+    this.setValueMapping(data);
+  }
+
   setColoring(options) {
     if (options.background) {
       this.panel.colorValue = false;
@@ -192,10 +263,10 @@ class SingleStatCtrl extends MetricsPanelCtrl {
       if (this.panel.valueName === 'name') {
         data.value = 0;
         data.valueRounded = 0;
-        data.valueFormated = this.series[0].alias;
+        data.valueFormatted = this.series[0].alias;
       } else if (_.isString(lastValue)) {
         data.value = 0;
-        data.valueFormated = _.escape(lastValue);
+        data.valueFormatted = _.escape(lastValue);
         data.valueRounded = 0;
       } else {
         data.value = this.series[0].stats[this.panel.valueName];
@@ -203,7 +274,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
 
         var decimalInfo = this.getDecimalsForValue(data.value);
         var formatFunc = kbn.valueFormats[this.panel.format];
-        data.valueFormated = formatFunc(data.value, decimalInfo.decimals, decimalInfo.scaledDecimals);
+        data.valueFormatted = formatFunc(data.value, decimalInfo.decimals, decimalInfo.scaledDecimals);
         data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
       }
 
@@ -211,15 +282,18 @@ class SingleStatCtrl extends MetricsPanelCtrl {
       data.scopedVars = _.extend({}, this.panel.scopedVars);
       data.scopedVars["__name"] = {value: this.series[0].label};
     }
+    this.setValueMapping(data);
+  }
 
+  setValueMapping(data) {
     // check value to text mappings if its enabled
     if (this.panel.mappingType === 1) {
-      for (var i = 0; i < this.panel.valueMaps.length; i++) {
-        var map = this.panel.valueMaps[i];
+      for (let i = 0; i < this.panel.valueMaps.length; i++) {
+        let map = this.panel.valueMaps[i];
         // special null case
         if (map.value === 'null') {
           if (data.value === null || data.value === void 0) {
-            data.valueFormated = map.text;
+            data.valueFormatted = map.text;
             return;
           }
           continue;
@@ -228,17 +302,17 @@ class SingleStatCtrl extends MetricsPanelCtrl {
         // value/number to text mapping
         var value = parseFloat(map.value);
         if (value === data.valueRounded) {
-          data.valueFormated = map.text;
+          data.valueFormatted = map.text;
           return;
         }
       }
     } else if (this.panel.mappingType === 2) {
-      for (var i = 0; i < this.panel.rangeMaps.length; i++) {
-        var map = this.panel.rangeMaps[i];
+      for (let i = 0; i < this.panel.rangeMaps.length; i++) {
+        let map = this.panel.rangeMaps[i];
         // special null case
         if (map.from === 'null' && map.to === 'null') {
           if (data.value === null || data.value === void 0) {
-            data.valueFormated = map.text;
+            data.valueFormatted = map.text;
             return;
           }
           continue;
@@ -248,22 +322,22 @@ class SingleStatCtrl extends MetricsPanelCtrl {
         var from = parseFloat(map.from);
         var to = parseFloat(map.to);
         if (to >= data.valueRounded && from <= data.valueRounded) {
-          data.valueFormated = map.text;
+          data.valueFormatted = map.text;
           return;
         }
       }
     }
 
     if (data.value === null || data.value === void 0) {
-      data.valueFormated = "no value";
+      data.valueFormatted = "no value";
     }
-  };
+  }
 
   removeValueMap(map) {
     var index = _.indexOf(this.panel.valueMaps, map);
     this.panel.valueMaps.splice(index, 1);
     this.render();
-  };
+  }
 
   addValueMap() {
     this.panel.valueMaps.push({value: '', op: '=', text: '' });
@@ -273,7 +347,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
     var index = _.indexOf(this.panel.rangeMaps, rangeMap);
     this.panel.rangeMaps.splice(index, 1);
     this.render();
-  };
+  }
 
   addRangeMap() {
     this.panel.rangeMaps.push({from: '', to: '', text: ''});
@@ -317,7 +391,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
 
       if (panel.prefix) { body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, panel.prefix); }
 
-      var value = applyColoringThresholds(data.value, data.valueFormated);
+      var value = applyColoringThresholds(data.value, data.valueFormatted);
       body += getSpan('singlestat-panel-value', panel.valueFontSize, value);
 
       if (panel.postfix) { body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.postfix); }
@@ -329,7 +403,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
 
     function getValueText() {
       var result = panel.prefix ? panel.prefix : '';
-      result += data.valueFormated;
+      result += data.valueFormatted;
       result += panel.postfix ? panel.postfix : '';
 
       return result;

+ 0 - 131
public/app/plugins/panel/singlestat/specs/singlestat-specs.ts

@@ -1,131 +0,0 @@
-///<reference path="../../../../headers/common.d.ts" />
-
-import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common';
-
-import angular from 'angular';
-import helpers from '../../../../../test/specs/helpers';
-import {SingleStatCtrl} from '../module';
-
-describe('SingleStatCtrl', function() {
-  var ctx = new helpers.ControllerTestContext();
-
-  function singleStatScenario(desc, func) {
-
-    describe(desc, function() {
-
-      ctx.setup = function (setupFunc) {
-
-        beforeEach(angularMocks.module('grafana.services'));
-        beforeEach(angularMocks.module('grafana.controllers'));
-        beforeEach(angularMocks.module(function($compileProvider) {
-          $compileProvider.preAssignBindingsEnabled(true);
-        }));
-
-        beforeEach(ctx.providePhase());
-        beforeEach(ctx.createPanelController(SingleStatCtrl));
-
-        beforeEach(function() {
-          setupFunc();
-          var data = [
-            {target: 'test.cpu1', datapoints: ctx.datapoints}
-          ];
-
-          ctx.ctrl.onDataReceived(data);
-          ctx.data = ctx.ctrl.data;
-        });
-      };
-
-      func(ctx);
-    });
-  }
-
-  singleStatScenario('with defaults', function(ctx) {
-    ctx.setup(function() {
-      ctx.datapoints = [[10,1], [20,2]];
-    });
-
-    it('Should use series avg as default main value', function() {
-      expect(ctx.data.value).to.be(15);
-      expect(ctx.data.valueRounded).to.be(15);
-    });
-
-    it('should set formated falue', function() {
-      expect(ctx.data.valueFormated).to.be('15');
-    });
-  });
-
-  singleStatScenario('showing serie name instead of value', function(ctx) {
-    ctx.setup(function() {
-      ctx.datapoints = [[10,1], [20,2]];
-      ctx.ctrl.panel.valueName = 'name';
-    });
-
-    it('Should use series avg as default main value', function() {
-      expect(ctx.data.value).to.be(0);
-      expect(ctx.data.valueRounded).to.be(0);
-    });
-
-    it('should set formated falue', function() {
-      expect(ctx.data.valueFormated).to.be('test.cpu1');
-    });
-  });
-
-  singleStatScenario('MainValue should use same number for decimals as displayed when checking thresholds', function(ctx) {
-    ctx.setup(function() {
-      ctx.datapoints = [[99.999,1], [99.99999,2]];
-    });
-
-    it('Should be rounded', function() {
-      expect(ctx.data.value).to.be(99.999495);
-      expect(ctx.data.valueRounded).to.be(100);
-    });
-
-    it('should set formated falue', function() {
-      expect(ctx.data.valueFormated).to.be('100');
-    });
-  });
-
-  singleStatScenario('When value to text mapping is specified', function(ctx) {
-    ctx.setup(function() {
-      ctx.datapoints = [[9.9,1]];
-      ctx.ctrl.panel.valueMaps = [{value: '10', text: 'OK'}];
-    });
-
-    it('value should remain', function() {
-      expect(ctx.data.value).to.be(9.9);
-    });
-
-    it('round should be rounded up', function() {
-      expect(ctx.data.valueRounded).to.be(10);
-    });
-
-    it('Should replace value with text', function() {
-      expect(ctx.data.valueFormated).to.be('OK');
-    });
-  });
-
-  singleStatScenario('When range to text mapping is specifiedfor first range', function(ctx) {
-    ctx.setup(function() {
-      ctx.datapoints = [[41,50]];
-      ctx.ctrl.panel.mappingType = 2;
-      ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}];
-    });
-
-    it('Should replace value with text OK', function() {
-      expect(ctx.data.valueFormated).to.be('OK');
-    });
-  });
-
-  singleStatScenario('When range to text mapping is specified for other ranges', function(ctx) {
-    ctx.setup(function() {
-      ctx.datapoints = [[65,75]];
-      ctx.ctrl.panel.mappingType = 2;
-      ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}];
-    });
-
-    it('Should replace value with text NOT OK', function() {
-      expect(ctx.data.valueFormated).to.be('NOT OK');
-    });
-  });
-
-});

+ 260 - 0
public/app/plugins/panel/singlestat/specs/singlestat_specs.ts

@@ -0,0 +1,260 @@
+///<reference path="../../../../headers/common.d.ts" />
+
+import {describe, beforeEach, it, sinon, expect, angularMocks} from '../../../../../test/lib/common';
+
+import angular from 'angular';
+import helpers from '../../../../../test/specs/helpers';
+import {SingleStatCtrl} from '../module';
+
+describe('SingleStatCtrl', function() {
+  var ctx = new helpers.ControllerTestContext();
+
+  function singleStatScenario(desc, func) {
+
+    describe(desc, function() {
+
+      ctx.setup = function (setupFunc) {
+
+        beforeEach(angularMocks.module('grafana.services'));
+        beforeEach(angularMocks.module('grafana.controllers'));
+        beforeEach(angularMocks.module(function($compileProvider) {
+          $compileProvider.preAssignBindingsEnabled(true);
+        }));
+
+        beforeEach(ctx.providePhase());
+        beforeEach(ctx.createPanelController(SingleStatCtrl));
+
+        beforeEach(function() {
+          setupFunc();
+          ctx.ctrl.onDataReceived(ctx.data);
+          ctx.data = ctx.ctrl.data;
+        });
+      };
+
+      func(ctx);
+    });
+  }
+
+  singleStatScenario('with defaults', function(ctx) {
+    ctx.setup(function() {
+      ctx.data = [
+        {target: 'test.cpu1', datapoints: [[10,1], [20,2]]}
+      ];
+    });
+
+    it('Should use series avg as default main value', function() {
+      expect(ctx.data.value).to.be(15);
+      expect(ctx.data.valueRounded).to.be(15);
+    });
+
+    it('should set formatted falue', function() {
+      expect(ctx.data.valueFormatted).to.be('15');
+    });
+  });
+
+  singleStatScenario('showing serie name instead of value', function(ctx) {
+    ctx.setup(function() {
+       ctx.data = [
+        {target: 'test.cpu1', datapoints: [[10,1], [20,2]]}
+       ];
+      ctx.ctrl.panel.valueName = 'name';
+    });
+
+    it('Should use series avg as default main value', function() {
+      expect(ctx.data.value).to.be(0);
+      expect(ctx.data.valueRounded).to.be(0);
+    });
+
+    it('should set formatted falue', function() {
+      expect(ctx.data.valueFormatted).to.be('test.cpu1');
+    });
+  });
+
+  singleStatScenario('MainValue should use same number for decimals as displayed when checking thresholds', function(ctx) {
+    ctx.setup(function() {
+      ctx.data = [
+        {target: 'test.cpu1', datapoints: [[99.999,1], [99.99999,2]]}
+      ];
+    });
+
+    it('Should be rounded', function() {
+      expect(ctx.data.value).to.be(99.999495);
+      expect(ctx.data.valueRounded).to.be(100);
+    });
+
+    it('should set formatted falue', function() {
+      expect(ctx.data.valueFormatted).to.be('100');
+    });
+  });
+
+  singleStatScenario('When value to text mapping is specified', function(ctx) {
+    ctx.setup(function() {
+      ctx.data = [
+        {target: 'test.cpu1', datapoints: [[9.9,1]]}
+      ];
+      ctx.ctrl.panel.valueMaps = [{value: '10', text: 'OK'}];
+    });
+
+    it('value should remain', function() {
+      expect(ctx.data.value).to.be(9.9);
+    });
+
+    it('round should be rounded up', function() {
+      expect(ctx.data.valueRounded).to.be(10);
+    });
+
+    it('Should replace value with text', function() {
+      expect(ctx.data.valueFormatted).to.be('OK');
+    });
+  });
+
+  singleStatScenario('When range to text mapping is specifiedfor first range', function(ctx) {
+    ctx.setup(function() {
+      ctx.data = [
+        {target: 'test.cpu1', datapoints: [[41,50]]}
+      ];
+      ctx.ctrl.panel.mappingType = 2;
+      ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}];
+    });
+
+    it('Should replace value with text OK', function() {
+      expect(ctx.data.valueFormatted).to.be('OK');
+    });
+  });
+
+  singleStatScenario('When range to text mapping is specified for other ranges', function(ctx) {
+    ctx.setup(function() {
+      ctx.data = [
+        {target: 'test.cpu1', datapoints: [[65,75]]}
+      ];
+      ctx.ctrl.panel.mappingType = 2;
+      ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}];
+    });
+
+    it('Should replace value with text NOT OK', function() {
+      expect(ctx.data.valueFormatted).to.be('NOT OK');
+    });
+  });
+
+  describe('When table data', function() {
+    const tableData = [{
+      "columns": [
+        { "text": "Time", "type": "time" },
+        { "text": "test1" },
+        { "text": "mean" },
+        { "text": "test2" }
+      ],
+      "rows": [
+        [1492759673649, 'ignore1', 15, 'ignore2']
+      ],
+      "type": "table"
+    }];
+
+    singleStatScenario('with default values', function(ctx) {
+      ctx.setup(function() {
+        ctx.data = tableData;
+        ctx.ctrl.panel.tableColumn = 'mean';
+      });
+
+      it('Should use first rows value as default main value', function() {
+        expect(ctx.data.value).to.be(15);
+        expect(ctx.data.valueRounded).to.be(15);
+      });
+
+      it('should set formatted value', function() {
+        expect(ctx.data.valueFormatted).to.be('15');
+      });
+    });
+
+    singleStatScenario('When table data has multiple columns', function(ctx) {
+      ctx.setup(function() {
+        ctx.data = tableData;
+        ctx.ctrl.panel.tableColumn = '';
+      });
+
+      it('Should set column to first column that is not time', function() {
+        expect(ctx.ctrl.panel.tableColumn).to.be('test1');
+      });
+    });
+
+    singleStatScenario('MainValue should use same number for decimals as displayed when checking thresholds', function(ctx) {
+      ctx.setup(function() {
+        ctx.data = tableData;
+        ctx.data[0].rows[0] = [1492759673649,'ignore1', 99.99999, 'ignore2'];
+        ctx.ctrl.panel.tableColumn = 'mean';
+      });
+
+      it('Should be rounded', function() {
+        expect(ctx.data.value).to.be(99.99999);
+        expect(ctx.data.valueRounded).to.be(100);
+      });
+
+      it('should set formatted falue', function() {
+        expect(ctx.data.valueFormatted).to.be('100');
+      });
+    });
+
+    singleStatScenario('When value to text mapping is specified', function(ctx) {
+      ctx.setup(function() {
+        ctx.data = tableData;
+        ctx.data[0].rows[0] = [1492759673649,'ignore1', 9.9, 'ignore2'];
+        ctx.ctrl.panel.tableColumn = 'mean';
+        ctx.ctrl.panel.valueMaps = [{value: '10', text: 'OK'}];
+      });
+
+      it('value should remain', function() {
+        expect(ctx.data.value).to.be(9.9);
+      });
+
+      it('round should be rounded up', function() {
+        expect(ctx.data.valueRounded).to.be(10);
+      });
+
+      it('Should replace value with text', function() {
+        expect(ctx.data.valueFormatted).to.be('OK');
+      });
+    });
+
+    singleStatScenario('When range to text mapping is specified for first range', function(ctx) {
+      ctx.setup(function() {
+        ctx.data = tableData;
+        ctx.data[0].rows[0] = [1492759673649,'ignore1', 41, 'ignore2'];
+        ctx.ctrl.panel.tableColumn = 'mean';
+        ctx.ctrl.panel.mappingType = 2;
+        ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}];
+      });
+
+      it('Should replace value with text OK', function() {
+        expect(ctx.data.valueFormatted).to.be('OK');
+      });
+    });
+
+    singleStatScenario('When range to text mapping is specified for other ranges', function(ctx) {
+      ctx.setup(function() {
+        ctx.data = tableData;
+        ctx.data[0].rows[0] = [1492759673649,'ignore1', 65, 'ignore2'];
+        ctx.ctrl.panel.tableColumn = 'mean';
+        ctx.ctrl.panel.mappingType = 2;
+        ctx.ctrl.panel.rangeMaps = [{from: '10', to: '50', text: 'OK'},{from: '51', to: '100', text: 'NOT OK'}];
+      });
+
+      it('Should replace value with text NOT OK', function() {
+        expect(ctx.data.valueFormatted).to.be('NOT OK');
+      });
+    });
+
+    singleStatScenario('When value is string', function(ctx) {
+      ctx.setup(function() {
+        ctx.data = tableData;
+        ctx.data[0].rows[0] = [1492759673649,'ignore1', 65, 'ignore2'];
+        ctx.ctrl.panel.tableColumn = 'test1';
+      });
+
+      it('Should replace value with text NOT OK', function() {
+        expect(ctx.data.valueFormatted).to.be('ignore1');
+      });
+    });
+  });
+});
+
+

+ 1 - 1
public/app/plugins/panel/table/editor.ts

@@ -101,7 +101,7 @@ export class TablePanelEditorCtrl {
   setUnitFormat(column, subItem) {
     column.unit = subItem.value;
     this.panelCtrl.render();
-  };
+  }
 
   addColumnStyle() {
     var columnStyleDefaults = {

+ 3 - 3
public/app/plugins/panel/table/transformers.ts

@@ -42,7 +42,7 @@ transformers['timeseries_to_columns'] = {
     // group by time
     var points = {};
 
-    for (var i = 0; i < data.length; i++) {
+    for (let i = 0; i < data.length; i++) {
       var series = data[i];
       model.columns.push({text: series.target});
 
@@ -63,7 +63,7 @@ transformers['timeseries_to_columns'] = {
       var point = points[time];
       var values = [point.time];
 
-      for (var i = 0; i < data.length; i++) {
+      for (let i = 0; i < data.length; i++) {
         var value = point[i];
         values.push(value);
       }
@@ -242,4 +242,4 @@ function transformDataToTable(data, panel) {
   return model;
 }
 
-export {transformers, transformDataToTable}
+export {transformers, transformDataToTable};

+ 1 - 1
public/app/plugins/panel/text/module.ts

@@ -79,4 +79,4 @@ export class TextPanelCtrl extends PanelCtrl {
   }
 }
 
-export {TextPanelCtrl as PanelCtrl}
+export {TextPanelCtrl as PanelCtrl};

+ 1 - 1
public/app/plugins/sdk.ts

@@ -18,4 +18,4 @@ export {
   MetricsPanelCtrl,
   QueryCtrl,
   alertTab,
-}
+};

+ 10 - 0
public/sass/components/_gf-form.scss

@@ -66,6 +66,16 @@ $gf-form-margin: 0.25rem;
   }
 }
 
+.gf-form-pre {
+  display: block;
+  flex-grow: 1;
+  font-size: $font-size-sm;
+  margin: 0;
+  margin-right: $gf-form-margin;
+  border: $input-btn-border-width solid transparent;
+  @include border-radius($label-border-radius-sm);
+}
+
 .gf-form-error {
   padding: $input-padding-y $input-padding-x;
   margin-right: $gf-form-margin;

+ 1 - 1
public/test/lib/common.ts

@@ -21,4 +21,4 @@ export {
   sinon,
   expect,
   angularMocks,
-}
+};

+ 1 - 1
tasks/build_task.js

@@ -8,10 +8,10 @@ module.exports = function(grunt) {
     'jshint:source',
     'jshint:tests',
     'jscs',
-    'exec:tslint',
     'clean:release',
     'copy:node_modules',
     'copy:public_to_gen',
+    'exec:tslint',
     'exec:tscompile',
     'karma:test',
     'phantomjs',

+ 2 - 2
tasks/default_task.js

@@ -14,12 +14,12 @@ module.exports = function(grunt) {
   );
 
   grunt.registerTask('default', [
+    'clean:gen',
     'jscs',
     'jshint',
-    'exec:tslint',
-    'clean:gen',
     'copy:node_modules',
     'copy:public_to_gen',
+    'exec:tslint',
     'phantomjs',
     'css',
     'exec:tscompile'

+ 2 - 2
tasks/options/exec.js

@@ -1,8 +1,8 @@
 module.exports = function(config, grunt) {
   'use strict'
   return {
-    tslint : "node ./node_modules/tslint/lib/tslint-cli.js -c tslint.json --project ./tsconfig.json",
-    tslintfile : "node ./node_modules/tslint/lib/tslint-cli.js -c tslint.json --project ./tsconfig.json <%= tslint.source.files.src %>",
+    tslint : "node ./node_modules/tslint/lib/tslint-cli.js -c tslint.json --project ./tsconfig.json --type-check",
+    tslintfile : "node ./node_modules/tslint/lib/tslint-cli.js -c tslint.json --project ./tsconfig.json --type-check <%= tslint.source.files.src %>",
     tscompile: "node ./node_modules/typescript/lib/tsc.js -p tsconfig.json --diagnostics",
     tswatch: "node ./node_modules/typescript/lib/tsc.js -p tsconfig.json --diagnostics --watch",
   };

+ 78 - 0
vendor/github.com/go-sql-driver/mysql/row_columntypes.go

@@ -0,0 +1,78 @@
+package mysql
+
+const (
+	// In case we get something unexpected
+	FieldTypeUnknown = "UNKNOWN"
+
+	// Human-readable names for each distinct type byte
+	FieldTypeNameDecimal    = "DECIMAL"
+	FieldTypeNameTiny       = "TINY"
+	FieldTypeNameShort      = "SHORT"
+	FieldTypeNameLong       = "LONG"
+	FieldTypeNameFloat      = "FLOAT"
+	FieldTypeNameDouble     = "DOUBLE"
+	FieldTypeNameNULL       = "NULL"
+	FieldTypeNameTimestamp  = "TIMESTAMP"
+	FieldTypeNameLongLong   = "LONGLONG"
+	FieldTypeNameInt24      = "INT24"
+	FieldTypeNameDate       = "DATE"
+	FieldTypeNameTime       = "TIME"
+	FieldTypeNameDateTime   = "DATETIME"
+	FieldTypeNameYear       = "YEAR"
+	FieldTypeNameNewDate    = "NEWDATE"
+	FieldTypeNameVarChar    = "VARCHAR"
+	FieldTypeNameBit        = "BIT"
+	FieldTypeNameJSON       = "JSON"
+	FieldTypeNameNewDecimal = "NEWDECIMAL"
+	FieldTypeNameEnum       = "ENUM"
+	FieldTypeNameSet        = "SET"
+	FieldTypeNameTinyBLOB   = "TINYBLOB"
+	FieldTypeNameMediumBLOB = "MEDIUMBLOB"
+	FieldTypeNameLongBLOB   = "LONGBLOB"
+	FieldTypeNameBLOB       = "BLOB"
+	FieldTypeNameVarString  = "VARSTRING"
+	FieldTypeNameString     = "STRING"
+	FieldTypeNameGeometry   = "GEOMETRY"
+)
+
+// mapping from each type identifier to human readable string
+var mysqlTypeMap = map[byte]string{
+	fieldTypeDecimal:    FieldTypeNameDecimal,
+	fieldTypeTiny:       FieldTypeNameTiny,
+	fieldTypeShort:      FieldTypeNameShort,
+	fieldTypeLong:       FieldTypeNameLong,
+	fieldTypeFloat:      FieldTypeNameFloat,
+	fieldTypeDouble:     FieldTypeNameDouble,
+	fieldTypeNULL:       FieldTypeNameNULL,
+	fieldTypeTimestamp:  FieldTypeNameTimestamp,
+	fieldTypeLongLong:   FieldTypeNameLongLong,
+	fieldTypeInt24:      FieldTypeNameInt24,
+	fieldTypeDate:       FieldTypeNameDate,
+	fieldTypeTime:       FieldTypeNameTime,
+	fieldTypeDateTime:   FieldTypeNameDateTime,
+	fieldTypeYear:       FieldTypeNameYear,
+	fieldTypeNewDate:    FieldTypeNameNewDate,
+	fieldTypeVarChar:    FieldTypeNameVarChar,
+	fieldTypeBit:        FieldTypeNameBit,
+	fieldTypeJSON:       FieldTypeNameJSON,
+	fieldTypeNewDecimal: FieldTypeNameNewDecimal,
+	fieldTypeEnum:       FieldTypeNameEnum,
+	fieldTypeSet:        FieldTypeNameSet,
+	fieldTypeTinyBLOB:   FieldTypeNameTinyBLOB,
+	fieldTypeMediumBLOB: FieldTypeNameMediumBLOB,
+	fieldTypeLongBLOB:   FieldTypeNameLongBLOB,
+	fieldTypeBLOB:       FieldTypeNameBLOB,
+	fieldTypeVarString:  FieldTypeNameVarString,
+	fieldTypeString:     FieldTypeNameString,
+	fieldTypeGeometry:   FieldTypeNameGeometry,
+}
+
+// Make Rows implement the optional RowsColumnTypeDatabaseTypeName interface.
+// See https://github.com/golang/go/commit/2a85578b0ecd424e95b29d810b7a414a299fd6a7
+// - (go 1.8 required for this to have any effect)
+func (rows *mysqlRows) ColumnTypeDatabaseTypeName(index int) string {
+	if typeName, ok := mysqlTypeMap[rows.rs.columns[index].fieldType]; ok {
+		return typeName
+	}
+	return FieldTypeUnknown
+}

+ 46 - 246
yarn.lock

@@ -68,12 +68,6 @@ amdefine@>=0.0.4:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
 
-ansi-align@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-1.1.0.tgz#2f0c1658829739add5ebb15e6b0c6e3423f016ba"
-  dependencies:
-    string-width "^1.0.1"
-
 ansi-escapes@^1.1.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
@@ -255,7 +249,7 @@ aws4@^1.2.1:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.5.0.tgz#0a29ffb79c31c9e712eeb087e8e7a64b4a56d755"
 
-babel-code-frame@^6.20.0, babel-code-frame@^6.22.0:
+babel-code-frame@^6.22.0:
   version "6.22.0"
   resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4"
   dependencies:
@@ -499,20 +493,6 @@ boom@2.x.x:
   dependencies:
     hoek "2.x.x"
 
-boxen@^0.6.0:
-  version "0.6.0"
-  resolved "https://registry.yarnpkg.com/boxen/-/boxen-0.6.0.tgz#8364d4248ac34ff0ef1b2f2bf49a6c60ce0d81b6"
-  dependencies:
-    ansi-align "^1.1.0"
-    camelcase "^2.1.0"
-    chalk "^1.1.1"
-    cli-boxes "^1.0.0"
-    filled-array "^1.0.0"
-    object-assign "^4.0.1"
-    repeating "^2.0.0"
-    string-width "^1.0.1"
-    widest-line "^1.0.0"
-
 brace-expansion@^1.0.0:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9"
@@ -603,7 +583,7 @@ camelcase@^1.0.2:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
 
-camelcase@^2.0.0, camelcase@^2.1.0:
+camelcase@^2.0.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
 
@@ -615,10 +595,6 @@ caniuse-db@^1.0.30000617:
   version "1.0.30000618"
   resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000618.tgz#821258ff484f662864f28ffbcf849a6247acf1fa"
 
-capture-stack-trace@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d"
-
 caseless@~0.11.0:
   version "0.11.0"
   resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7"
@@ -697,10 +673,6 @@ clean-css@3.4.x, clean-css@~3.4.2:
     commander "2.8.x"
     source-map "0.4.x"
 
-cli-boxes@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
-
 cli-cursor@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
@@ -823,7 +795,7 @@ concat-map@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
 
-concat-stream@1.5.0:
+concat-stream@1.5.0, concat-stream@^1.4.1, concat-stream@^1.4.6:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.0.tgz#53f7d43c51c5e43f81c8fdd03321c631be68d611"
   dependencies:
@@ -831,28 +803,6 @@ concat-stream@1.5.0:
     readable-stream "~2.0.0"
     typedarray "~0.0.5"
 
-concat-stream@^1.4.1, concat-stream@^1.4.6:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7"
-  dependencies:
-    inherits "^2.0.3"
-    readable-stream "^2.2.2"
-    typedarray "^0.0.6"
-
-configstore@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1"
-  dependencies:
-    dot-prop "^3.0.0"
-    graceful-fs "^4.1.2"
-    mkdirp "^0.5.0"
-    object-assign "^4.0.1"
-    os-tmpdir "^1.0.0"
-    osenv "^0.1.0"
-    uuid "^2.0.1"
-    write-file-atomic "^1.1.2"
-    xdg-basedir "^2.0.0"
-
 connect@^3.3.5:
   version "3.5.0"
   resolved "https://registry.yarnpkg.com/connect/-/connect-3.5.0.tgz#b357525a0b4c1f50599cd983e1d9efeea9677198"
@@ -910,12 +860,6 @@ crc@^3.4.4:
   version "3.4.4"
   resolved "https://registry.yarnpkg.com/crc/-/crc-3.4.4.tgz#9da1e980e3bd44fc5c93bf5ab3da3378d85e466b"
 
-create-error-class@^3.0.1:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
-  dependencies:
-    capture-stack-trace "^1.0.0"
-
 cross-spawn@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
@@ -1062,7 +1006,7 @@ diff@^2.0.2:
   version "2.2.3"
   resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99"
 
-diff@^3.0.1:
+diff@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9"
 
@@ -1116,18 +1060,6 @@ dot-case@^2.1.0:
   dependencies:
     no-case "^2.2.0"
 
-dot-prop@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177"
-  dependencies:
-    is-obj "^1.0.0"
-
-duplexer2@^0.1.4:
-  version "0.1.4"
-  resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
-  dependencies:
-    readable-stream "^2.0.2"
-
 each-async@^0.1.3:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/each-async/-/each-async-0.1.3.tgz#b436025b08da2f86608025519e3096763dedfca3"
@@ -1534,10 +1466,6 @@ fill-range@^2.1.0:
     repeat-element "^1.1.2"
     repeat-string "^1.5.2"
 
-filled-array@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/filled-array/-/filled-array-1.1.0.tgz#c3c4f6c663b923459a9aa29912d2d031f1507f84"
-
 finalhandler@0.5.0:
   version "0.5.0"
   resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.0.tgz#e9508abece9b6dba871a6942a1d7911b91911ac7"
@@ -1735,9 +1663,9 @@ glob@7.0.5:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-glob@^7.0.0, glob@^7.1.1, glob@~7.1.1:
-  version "7.1.1"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
+glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@~7.0.0:
+  version "7.0.6"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a"
   dependencies:
     fs.realpath "^1.0.0"
     inflight "^1.0.4"
@@ -1746,9 +1674,9 @@ glob@^7.0.0, glob@^7.1.1, glob@~7.1.1:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-glob@^7.0.3, glob@^7.0.5, glob@~7.0.0:
-  version "7.0.6"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a"
+glob@^7.1.1, glob@~7.1.1:
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
   dependencies:
     fs.realpath "^1.0.0"
     inflight "^1.0.4"
@@ -1801,27 +1729,7 @@ gonzales-pe@3.4.7:
   dependencies:
     minimist "1.1.x"
 
-got@^5.0.0:
-  version "5.7.1"
-  resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35"
-  dependencies:
-    create-error-class "^3.0.1"
-    duplexer2 "^0.1.4"
-    is-redirect "^1.0.0"
-    is-retry-allowed "^1.0.0"
-    is-stream "^1.0.0"
-    lowercase-keys "^1.0.0"
-    node-status-codes "^1.0.0"
-    object-assign "^4.0.1"
-    parse-json "^2.1.0"
-    pinkie-promise "^2.0.0"
-    read-all-stream "^3.0.0"
-    readable-stream "^2.0.5"
-    timed-out "^3.0.0"
-    unzip-response "^1.0.2"
-    url-parse-lax "^1.0.0"
-
-graceful-fs@^4.1.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
+graceful-fs@^4.1.0, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
   version "4.1.11"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
 
@@ -2311,7 +2219,7 @@ inherits@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b"
 
-inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1:
+inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@~2.0.0, inherits@~2.0.1:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
 
@@ -2430,10 +2338,6 @@ is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4:
     jsonpointer "^4.0.0"
     xtend "^4.0.0"
 
-is-npm@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4"
-
 is-number@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-0.1.1.tgz#69a7af116963d47206ec9bd9b48a14216f1e3806"
@@ -2444,10 +2348,6 @@ is-number@^2.0.2, is-number@^2.1.0:
   dependencies:
     kind-of "^3.0.2"
 
-is-obj@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
-
 is-path-cwd@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
@@ -2476,21 +2376,13 @@ is-property@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
 
-is-redirect@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
-
 is-resolvable@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62"
   dependencies:
     tryit "^1.0.1"
 
-is-retry-allowed@^1.0.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
-
-is-stream@^1.0.0, is-stream@^1.0.1:
+is-stream@^1.0.1:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
 
@@ -2811,20 +2703,10 @@ klaw@^1.0.0:
   optionalDependencies:
     graceful-fs "^4.1.9"
 
-latest-version@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-2.0.0.tgz#56f8d6139620847b8017f8f1f4d78e211324168b"
-  dependencies:
-    package-json "^2.0.0"
-
 lazy-cache@^1.0.3:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
 
-lazy-req@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/lazy-req/-/lazy-req-1.1.0.tgz#bdaebead30f8d824039ce0ce149d4daa07ba1fac"
-
 lazystream@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4"
@@ -3006,10 +2888,6 @@ lower-case@^1.1.0, lower-case@^1.1.1, lower-case@^1.1.2:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.3.tgz#c92393d976793eee5ba4edb583cf8eae35bd9bfb"
 
-lowercase-keys@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
-
 lru-cache@2:
   version "2.7.3"
   resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
@@ -3294,10 +3172,6 @@ node-sass@^3.7.0:
     request "^2.61.0"
     sass-graph "^2.1.1"
 
-node-status-codes@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f"
-
 "nomnom@>= 1.5.x":
   version "1.8.1"
   resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.8.1.tgz#2151f722472ba79e50a76fc125bb8c8f2e4dc2a7"
@@ -3442,22 +3316,13 @@ os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
 
-osenv@0, osenv@^0.1.0:
+osenv@0:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644"
   dependencies:
     os-homedir "^1.0.0"
     os-tmpdir "^1.0.0"
 
-package-json@^2.0.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/package-json/-/package-json-2.4.0.tgz#0d15bd67d1cbbddbb2ca222ff2edb86bcb31a8bb"
-  dependencies:
-    got "^5.0.0"
-    registry-auth-token "^3.0.1"
-    registry-url "^3.0.3"
-    semver "^5.1.0"
-
 pako@~0.2.0:
   version "0.2.9"
   resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
@@ -3477,7 +3342,7 @@ parse-glob@^3.0.4:
     is-extglob "^1.0.0"
     is-glob "^2.0.0"
 
-parse-json@^2.1.0, parse-json@^2.2.0:
+parse-json@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
   dependencies:
@@ -3536,6 +3401,10 @@ path-is-inside@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
 
+path-parse@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
+
 path-type@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
@@ -3621,10 +3490,6 @@ prelude-ls@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
 
-prepend-http@^1.0.1:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
-
 preserve@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
@@ -3723,7 +3588,7 @@ raw-body@~2.2.0:
     iconv-lite "0.4.15"
     unpipe "1.0.0"
 
-rc@^1.0.1, rc@^1.1.6, rc@~1.1.6:
+rc@~1.1.6:
   version "1.1.6"
   resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.6.tgz#43651b76b6ae53b5c802f1151fa3fc3b059969c9"
   dependencies:
@@ -3732,13 +3597,6 @@ rc@^1.0.1, rc@^1.1.6, rc@~1.1.6:
     minimist "^1.2.0"
     strip-json-comments "~1.0.4"
 
-read-all-stream@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa"
-  dependencies:
-    pinkie-promise "^2.0.0"
-    readable-stream "^2.0.0"
-
 read-pkg-up@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
@@ -3769,7 +3627,7 @@ readable-stream@1.1:
     isarray "0.0.1"
     string_decoder "~0.10.x"
 
-readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.2.2:
+readable-stream@^2.0.0, "readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e"
   dependencies:
@@ -3852,18 +3710,6 @@ regex-cache@^0.4.2:
     is-equal-shallow "^0.1.3"
     is-primitive "^2.0.0"
 
-registry-auth-token@^3.0.1:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.1.0.tgz#997c08256e0c7999837b90e944db39d8a790276b"
-  dependencies:
-    rc "^1.1.6"
-
-registry-url@^3.0.3:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942"
-  dependencies:
-    rc "^1.0.1"
-
 relateurl@0.2.x:
   version "0.2.7"
   resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
@@ -3961,10 +3807,16 @@ resolve-pkg@^0.1.0:
   dependencies:
     resolve-from "^2.0.0"
 
-resolve@1.1.x, resolve@^1.1.6, resolve@^1.1.7, resolve@~1.1.0:
+resolve@1.1.x, resolve@^1.1.6, resolve@~1.1.0:
   version "1.1.7"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
 
+resolve@^1.3.2:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5"
+  dependencies:
+    path-parse "^1.0.5"
+
 restore-cursor@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
@@ -4048,13 +3900,7 @@ sass-lint@^1.10.2:
     path-is-absolute "^1.0.0"
     util "^0.10.3"
 
-semver-diff@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
-  dependencies:
-    semver "^5.0.3"
-
-"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@~5.3.0:
+"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@~5.3.0:
   version "5.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
 
@@ -4122,10 +3968,6 @@ slice-ansi@0.0.4:
   version "0.0.4"
   resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
 
-slide@^1.1.5:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
-
 snake-case@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-2.1.0.tgz#41bdb1b73f30ec66a04d4e2cad1b76387d4d6d9f"
@@ -4468,10 +4310,6 @@ through@^2.3.6:
   version "2.3.8"
   resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
 
-timed-out@^3.0.0:
-  version "3.1.3"
-  resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217"
-
 tiny-emitter@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-1.1.0.tgz#ab405a21ffed814a76c19739648093d70654fecb"
@@ -4544,18 +4382,23 @@ tryor@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/tryor/-/tryor-0.1.2.tgz#8145e4ca7caff40acde3ccf946e8b8bb75b4172b"
 
-tslint@^4.0.2:
-  version "4.4.2"
-  resolved "https://registry.yarnpkg.com/tslint/-/tslint-4.4.2.tgz#b14cb79ae039c72471ab4c2627226b940dda19c6"
+tslint@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.1.0.tgz#51a47baeeb58956fcd617bd2cf00e2ef0eea2ed9"
   dependencies:
-    babel-code-frame "^6.20.0"
+    babel-code-frame "^6.22.0"
     colors "^1.1.2"
-    diff "^3.0.1"
+    diff "^3.2.0"
     findup-sync "~0.3.0"
     glob "^7.1.1"
     optimist "~0.6.0"
-    resolve "^1.1.7"
-    update-notifier "^1.0.2"
+    resolve "^1.3.2"
+    semver "^5.3.0"
+    tsutils "^1.4.0"
+
+tsutils@^1.4.0:
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.7.0.tgz#2e63ccc2d6912bb095f7e363ff4100721dc86f50"
 
 tunnel-agent@~0.4.1:
   version "0.4.3"
@@ -4578,13 +4421,13 @@ type-is@~1.6.10, type-is@~1.6.14:
     media-typer "0.3.0"
     mime-types "~2.1.13"
 
-typedarray@^0.0.6, typedarray@~0.0.5:
+typedarray@~0.0.5:
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
 
-typescript@^2.1.4:
-  version "2.1.5"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.1.5.tgz#6fe9479e00e01855247cea216e7561bafcdbcd4a"
+typescript@^2.2.2:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.2.2.tgz#606022508479b55ffa368b58fee963a03dfd7b0c"
 
 uglify-js@2.6.x:
   version "2.6.4"
@@ -4640,23 +4483,6 @@ unpipe@1.0.0, unpipe@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
 
-unzip-response@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe"
-
-update-notifier@^1.0.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-1.0.3.tgz#8f92c515482bd6831b7c93013e70f87552c7cf5a"
-  dependencies:
-    boxen "^0.6.0"
-    chalk "^1.0.0"
-    configstore "^2.0.0"
-    is-npm "^1.0.0"
-    latest-version "^2.0.0"
-    lazy-req "^1.1.0"
-    semver-diff "^2.0.0"
-    xdg-basedir "^2.0.0"
-
 upper-case-first@^1.1.0, upper-case-first@^1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-1.1.2.tgz#5d79bedcff14419518fd2edb0a0507c9b6859115"
@@ -4671,12 +4497,6 @@ uri-path@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/uri-path/-/uri-path-1.0.0.tgz#9747f018358933c31de0fccfd82d138e67262e32"
 
-url-parse-lax@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
-  dependencies:
-    prepend-http "^1.0.1"
-
 user-home@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f"
@@ -4718,7 +4538,7 @@ utils-merge@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8"
 
-uuid@^2.0.1, uuid@^2.0.2:
+uuid@^2.0.2:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
 
@@ -4809,12 +4629,6 @@ wide-align@^1.1.0:
   dependencies:
     string-width "^1.0.1"
 
-widest-line@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-1.0.0.tgz#0c09c85c2a94683d0d7eaf8ee097d564bf0e105c"
-  dependencies:
-    string-width "^1.0.1"
-
 window-size@0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
@@ -4858,14 +4672,6 @@ wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
 
-write-file-atomic@^1.1.2:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.1.tgz#7d45ba32316328dd1ec7d90f60ebc0d845bb759a"
-  dependencies:
-    graceful-fs "^4.1.11"
-    imurmurhash "^0.1.4"
-    slide "^1.1.5"
-
 write@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757"
@@ -4879,12 +4685,6 @@ ws@1.0.1:
     options ">=0.0.5"
     ultron "1.0.x"
 
-xdg-basedir@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2"
-  dependencies:
-    os-homedir "^1.0.0"
-
 xml-char-classes@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/xml-char-classes/-/xml-char-classes-1.0.0.tgz#64657848a20ffc5df583a42ad8a277b4512bbc4d"