mirror of
https://github.com/grafana/grafana.git
synced 2025-02-20 11:48:34 -06:00
Merge remote-tracking branch 'grafana/master' into influx-db-query2
This commit is contained in:
commit
42abbf5f0d
1
.gitignore
vendored
1
.gitignore
vendored
@ -25,6 +25,7 @@ public/css/*.min.css
|
||||
*.swp
|
||||
.idea/
|
||||
*.iml
|
||||
*.tmp
|
||||
.vscode/
|
||||
|
||||
/data/*
|
||||
|
@ -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
31
ROADMAP.md
Normal 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)
|
@ -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
docker/blocks/mysql_opendata/Dockerfile
Normal file
20
docker/blocks/mysql_opendata/Dockerfile
Normal 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
|
9
docker/blocks/mysql_opendata/fig
Normal file
9
docker/blocks/mysql_opendata/fig
Normal 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"
|
80
docker/blocks/mysql_opendata/import_csv.sql
Normal file
80
docker/blocks/mysql_opendata/import_csv.sql
Normal 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
|
@ -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
|
||||
```
|
||||
|
@ -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
|
||||
|
@ -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"}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -24,7 +24,6 @@ func AddMigrations(mg *Migrator) {
|
||||
addPreferencesMigrations(mg)
|
||||
addAlertMigrations(mg)
|
||||
addAnnotationMig(mg)
|
||||
addStatsMigrations(mg)
|
||||
addTestDataMigrations(mg)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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
80
pkg/tsdb/mysql/macros.go
Normal 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)
|
||||
}
|
||||
}
|
43
pkg/tsdb/mysql/macros_test.go
Normal file
43
pkg/tsdb/mysql/macros_test.go
Normal 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)")
|
||||
})
|
||||
|
||||
})
|
||||
}
|
@ -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 {
|
||||
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
@ -44,7 +44,7 @@ export class SignUpCtrl {
|
||||
window.location.href = config.appSubUrl + '/';
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
coreModule.controller('SignUpCtrl', SignUpCtrl);
|
||||
|
@ -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 => {
|
||||
|
@ -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 });
|
||||
|
@ -92,7 +92,7 @@ export default class TimeSeries {
|
||||
this.yaxis = override.yaxis;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getFlotPairs(fillStyle) {
|
||||
var result = [];
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -74,7 +74,7 @@ export class PlaylistEditCtrl {
|
||||
return playlistItem === listedPlaylistItem;
|
||||
});
|
||||
this.filterFoundPlaylistItems();
|
||||
};
|
||||
}
|
||||
|
||||
savePlaylist(playlist, playlistItems) {
|
||||
var savePromise;
|
||||
|
@ -154,7 +154,7 @@ export class DataSourceEditCtrl {
|
||||
this.$location.path('datasources/edit/' + result.id);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
confirmDelete() {
|
||||
this.backendSrv.delete('/api/datasources/' + this.current.id).then(() => {
|
||||
|
@ -17,4 +17,4 @@ export {
|
||||
CustomVariable,
|
||||
ConstantVariable,
|
||||
AdhocVariable,
|
||||
}
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -13,7 +13,7 @@ class GrafanaDatasource {
|
||||
|
||||
metricFindQuery() {
|
||||
return this.$q.when([]);
|
||||
};
|
||||
}
|
||||
|
||||
annotationQuery(options) {
|
||||
return this.backendSrv.get('/api/annotations', {
|
||||
|
@ -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);
|
||||
|
@ -195,7 +195,7 @@ export default class InfluxQuery {
|
||||
|
||||
var escapedValues = _.map(value, kbn.regexEscape);
|
||||
return escapedValues.join('|');
|
||||
};
|
||||
}
|
||||
|
||||
render(interpolate?) {
|
||||
var target = this.target;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -28,7 +28,7 @@ function createPart(part): any {
|
||||
}
|
||||
|
||||
return new QueryPart(part, def);
|
||||
};
|
||||
}
|
||||
|
||||
function register(options: any) {
|
||||
index[options.type] = new QueryPartDef(options);
|
||||
|
@ -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() {
|
||||
|
@ -30,4 +30,4 @@ class MixedDatasource {
|
||||
}
|
||||
}
|
||||
|
||||
export {MixedDatasource, MixedDatasource as Datasource}
|
||||
export {MixedDatasource, MixedDatasource as Datasource};
|
||||
|
@ -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};
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@ -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';
|
||||
|
@ -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) -> UNIX_TIMESTAMP(column) as time_sec
|
||||
- $__timeFilter(column) -> UNIX_TIMESTAMP(time_date_time) > from AND UNIX_TIMESTAMP(time_date_time) < 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
public/app/plugins/datasource/mysql/query_ctrl.ts
Normal file
79
public/app/plugins/datasource/mysql/query_ctrl.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,4 +139,4 @@ class AlertListPanel extends PanelCtrl {
|
||||
export {
|
||||
AlertListPanel,
|
||||
AlertListPanel as PanelCtrl
|
||||
}
|
||||
};
|
||||
|
@ -125,4 +125,4 @@ class DashListCtrl extends PanelCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
export {DashListCtrl, DashListCtrl as PanelCtrl}
|
||||
export {DashListCtrl, DashListCtrl as PanelCtrl};
|
||||
|
@ -116,4 +116,4 @@ class GettingStartedPanelCtrl extends PanelCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
export {GettingStartedPanelCtrl, GettingStartedPanelCtrl as PanelCtrl}
|
||||
export {GettingStartedPanelCtrl, GettingStartedPanelCtrl as PanelCtrl};
|
||||
|
@ -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]]]];
|
||||
}
|
||||
|
||||
|
@ -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};
|
||||
|
@ -26,8 +26,8 @@ export class AxesEditorCtrl {
|
||||
};
|
||||
|
||||
this.dataFormats = {
|
||||
'Timeseries': 'timeseries',
|
||||
'ES histogram': 'es_histogram'
|
||||
'TS': 'timeseries',
|
||||
'TS Pre-bucketed': 'tsbuckets'
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -70,4 +70,4 @@ class PluginListCtrl extends PanelCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
export {PluginListCtrl, PluginListCtrl as PanelCtrl}
|
||||
export {PluginListCtrl, PluginListCtrl as PanelCtrl};
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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
public/app/plugins/panel/singlestat/specs/singlestat_specs.ts
Normal file
260
public/app/plugins/panel/singlestat/specs/singlestat_specs.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -101,7 +101,7 @@ export class TablePanelEditorCtrl {
|
||||
setUnitFormat(column, subItem) {
|
||||
column.unit = subItem.value;
|
||||
this.panelCtrl.render();
|
||||
};
|
||||
}
|
||||
|
||||
addColumnStyle() {
|
||||
var columnStyleDefaults = {
|
||||
|
@ -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};
|
||||
|
@ -79,4 +79,4 @@ export class TextPanelCtrl extends PanelCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
export {TextPanelCtrl as PanelCtrl}
|
||||
export {TextPanelCtrl as PanelCtrl};
|
||||
|
@ -18,4 +18,4 @@ export {
|
||||
MetricsPanelCtrl,
|
||||
QueryCtrl,
|
||||
alertTab,
|
||||
}
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -21,4 +21,4 @@ export {
|
||||
sinon,
|
||||
expect,
|
||||
angularMocks,
|
||||
}
|
||||
};
|
||||
|
@ -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',
|
||||
|
@ -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'
|
||||
|
@ -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
vendor/github.com/go-sql-driver/mysql/row_columntypes.go
generated
vendored
Normal file
78
vendor/github.com/go-sql-driver/mysql/row_columntypes.go
generated
vendored
Normal 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
292
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"
|
||||
|
Loading…
Reference in New Issue
Block a user