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

This commit is contained in:
ryan 2017-04-24 09:50:23 -07:00
commit 42abbf5f0d
72 changed files with 1299 additions and 562 deletions

1
.gitignore vendored
View File

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

View File

@ -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
ROADMAP.md Normal file
View File

@ -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)

View File

@ -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]

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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
```

View File

@ -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

View File

@ -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"}

View File

@ -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"
}
}

View File

@ -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

View File

@ -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",
},
},
},
}

View File

@ -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")

View File

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

View File

@ -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
})
}

View File

@ -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
pkg/tsdb/mysql/macros.go Normal file
View File

@ -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)
}
}

View File

@ -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)")
})
})
}

View File

@ -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 {

View File

@ -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();
};
}
}

View File

@ -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",

View File

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

View File

@ -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 => {

View File

@ -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 });

View File

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

View File

@ -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);
};
}

View File

@ -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);

View File

@ -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');
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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>

View File

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

View File

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

View File

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

View File

@ -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;
}

View File

@ -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) {

View File

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

View File

@ -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);

View File

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

View File

@ -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;
};

View File

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

View File

@ -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() {

View File

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

View File

@ -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);
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));
}
var data = [];
if (res.results) {
_.forEach(res.results, queryRes => {
processQueryResult(res) {
var data = [];
if (queryRes.error) {
throw {error: queryRes.error, message: queryRes.error};
}
if (!res.data.results) {
return {data: data};
}
for (let series of queryRes.series) {
data.push({
target: series.name,
datapoints: series.points
});
}
});
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,
});
}
}
return {data: data};
});
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};
}
}

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -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';

View File

@ -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>

View File

@ -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;
}
}
}
}

View File

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

View File

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

View File

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

View File

@ -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]]]];
}

View File

@ -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};

View File

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

View File

@ -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);

View File

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

View File

@ -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>

View File

@ -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;

View File

@ -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');
});
});
});

View File

@ -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');
});
});
});
});

View File

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

View File

@ -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};

View File

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

View File

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

View File

@ -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;

View File

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

View File

@ -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',

View File

@ -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'

View File

@ -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",
};

View File

@ -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
}

292
yarn.lock
View File

@ -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"