Merge branch 'master' into postgres-query-builder

This commit is contained in:
Sven Klemm
2018-08-14 12:51:03 +02:00
54 changed files with 2174 additions and 2602 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ watch_dirs = [
"$WORKDIR/public/views",
"$WORKDIR/conf",
]
watch_exts = [".go", ".ini", ".toml"]
watch_exts = [".go", ".ini", ".toml", ".template.html"]
build_delay = 1500
cmds = [
["go", "run", "build.go", "-dev", "build-server"],
+1
View File
@@ -104,6 +104,7 @@ jobs:
- run:
name: yarn install
command: 'yarn install --pure-lockfile --no-progress'
no_output_timeout: 15m
- save_cache:
key: dependency-cache-{{ checksum "yarn.lock" }}
paths:
-13
View File
@@ -1,13 +0,0 @@
{
"disallowImplicitTypeConversion": ["string"],
"disallowKeywords": ["with"],
"disallowMultipleLineBreaks": true,
"disallowMixedSpacesAndTabs": true,
"disallowTrailingWhitespace": true,
"requireSpacesInFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"disallowSpacesInsideArrayBrackets": true,
"disallowSpacesInsideParentheses": true,
"validateIndentation": 2
}
-37
View File
@@ -1,37 +0,0 @@
{
"browser": true,
"esversion": 6,
"bitwise":false,
"curly": true,
"eqnull": true,
"strict": false,
"devel": true,
"eqeqeq": true,
"forin": false,
"immed": true,
"supernew": true,
"expr": true,
"indent": 2,
"latedef": false,
"newcap": true,
"noarg": true,
"noempty": true,
"undef": true,
"boss": true,
"trailing": true,
"laxbreak": true,
"laxcomma": true,
"sub": true,
"unused": true,
"maxdepth": 6,
"maxlen": 140,
"globals": {
"System": true,
"Promise": true,
"define": true,
"require": true,
"Chromath": false,
"setImmediate": true
}
}
+11 -2
View File
@@ -5,13 +5,16 @@
* **LDAP**: Define Grafana Admin permission in ldap group mappings [#2469](https://github.com/grafana/grafana/issues/2496), PR [#12622](https://github.com/grafana/grafana/issues/12622)
* **Cloudwatch**: CloudWatch GetMetricData support [#11487](https://github.com/grafana/grafana/issues/11487), thx [@mtanda](https://github.com/mtanda)
* **Configuration**: Allow auto-assigning users to specific organization (other than Main. Org) [#1823](https://github.com/grafana/grafana/issues/1823) [#12801](https://github.com/grafana/grafana/issues/12801), thx [@gzzo](https://github.com/gzzo) and [@ofosos](https://github.com/ofosos)
* **Profile**: List teams that the user is member of in current/active organization [#12476](https://github.com/grafana/grafana/issues/12476)
* **LDAP**: Client certificates support [#12805](https://github.com/grafana/grafana/issues/12805), thx [@nyxi](https://github.com/nyxi)
* **Postgres**: TimescaleDB support, e.g. use `time_bucket` for grouping by time when option enabled [#12680](https://github.com/grafana/grafana/pull/12680), thx [svenklemm](https://github.com/svenklemm)
### Minor
* **Api**: Delete nonexistent datasource should return 404 [#12313](https://github.com/grafana/grafana/issues/12313), thx [@AustinWinstanley](https://github.com/AustinWinstanley)
* **Dashboard**: Fix selecting current dashboard from search should not reload dashboard [#12248](https://github.com/grafana/grafana/issues/12248)
* **Dashboard**: Use uid when linking to dashboards internally in a dashboard [#10705](https://github.com/grafana/grafana/issues/10705)
* **Singlestat**: Make colorization of prefix and postfix optional in singlestat [#11892](https://github.com/grafana/grafana/pull/11892), thx [@ApsOps](https://github.com/ApsOps)
* **Table**: Make table sorting stable when null values exist [#12362](https://github.com/grafana/grafana/pull/12362), thx [@bz2](https://github.com/bz2)
* **Prometheus**: Fix graph panel bar width issue in aligned prometheus queries [#12379](https://github.com/grafana/grafana/issues/12379)
* **Prometheus**: Heatmap - fix unhandled error when some points are missing [#12484](https://github.com/grafana/grafana/issues/12484)
* **Prometheus**: Add $__interval, $__interval_ms, $__range, $__range_s & $__range_ms support for dashboard and template queries [#12597](https://github.com/grafana/grafana/issues/12597) [#12882](https://github.com/grafana/grafana/issues/12882), thx [@roidelapluie](https://github.com/roidelapluie)
@@ -25,7 +28,7 @@
* **Postgres**: Escape ssl mode parameter in connectionstring [#12644](https://github.com/grafana/grafana/issues/12644), thx [@yogyrahmawan](https://github.com/yogyrahmawan)
* **Github OAuth**: Allow changes of user info at Github to be synched to Grafana when signing in [#11818](https://github.com/grafana/grafana/issues/11818), thx [@rwaweber](https://github.com/rwaweber)
* **Alerting**: Fix diff and percent_diff reducers [#11563](https://github.com/grafana/grafana/issues/11563), thx [@jessetane](https://github.com/jessetane)
* **Units**: Polish złoty currency [#12691](https://github.com/grafana/grafana/pull/12691), thx [@mwegrzynek](https://github.com/mwegrzynek)
* **Alerting**: Fix rendering timeout which could cause notifications to not be sent due to rendering timing out [#12151](https://github.com/grafana/grafana/issues/12151)
* **Cloudwatch**: Improved error handling [#12489](https://github.com/grafana/grafana/issues/12489), thx [@mtanda](https://github.com/mtanda)
* **Cloudwatch**: AppSync metrics and dimensions [#12300](https://github.com/grafana/grafana/issues/12300), thx [@franciscocpg](https://github.com/franciscocpg)
* **Cloudwatch**: Direct Connect metrics and dimensions [#12762](https://github.com/grafana/grafana/pulls/12762), thx [@mindriot88](https://github.com/mindriot88)
@@ -33,14 +36,20 @@
* **Cloudwatch**: Add new Redshift metrics and dimensions [#12063](https://github.com/grafana/grafana/pulls/12063), thx [@A21z](https://github.com/A21z)
* **Table**: Adjust header contrast for the light theme [#12668](https://github.com/grafana/grafana/issues/12668)
* **Table**: Fix link color when using light theme and thresholds in use [#12766](https://github.com/grafana/grafana/issues/12766)
om/grafana/grafana/issues/12668)
* **Table**: Fix for useless horizontal scrollbar for table panel [#9964](https://github.com/grafana/grafana/issues/9964)
* **Table**: Make table sorting stable when null values exist [#12362](https://github.com/grafana/grafana/pull/12362), thx [@bz2](https://github.com/bz2)
* **Elasticsearch**: For alerting/backend, support having index name to the right of pattern in index pattern [#12731](https://github.com/grafana/grafana/issues/12731)
* **OAuth**: Fix overriding tls_skip_verify_insecure using environment variable [#12747](https://github.com/grafana/grafana/issues/12747), thx [@jangaraj](https://github.com/jangaraj)
* **Units**: Change units to include characters for power of 2 and 3 [#12744](https://github.com/grafana/grafana/pull/12744), thx [@Worty](https://github.com/Worty)
* **Units**: Polish złoty currency [#12691](https://github.com/grafana/grafana/pull/12691), thx [@mwegrzynek](https://github.com/mwegrzynek)
* **Graph**: Option to hide series from tooltip [#3341](https://github.com/grafana/grafana/issues/3341), thx [@mtanda](https://github.com/mtanda)
* **UI**: Fix iOS home screen "app" icon and Windows 10 app experience [#12752](https://github.com/grafana/grafana/issues/12752), thx [@andig](https://github.com/andig)
* **Datasource**: Fix UI issue with secret fields after updating datasource [#11270](https://github.com/grafana/grafana/issues/11270)
* **Plugins**: Convert URL-like text to links in plugins readme [#12843](https://github.com/grafana/grafana/pull/12843), thx [pgiraud](https://github.com/pgiraud)
* **Docker**: Make it possible to set a specific plugin url [#12861](https://github.com/grafana/grafana/pull/12861), thx [ClementGautier](https://github.com/ClementGautier)
* **Graphite**: Fix for quoting of int function parameters (when using variables) [#11927](https://github.com/grafana/grafana/pull/11927)
* **InfluxDB**: Support timeFilter in query templating for InfluxDB [#12598](https://github.com/grafana/grafana/pull/12598), thx [kichristensen](https://github.com/kichristensen)
### Breaking changes
-1
View File
@@ -1,4 +1,3 @@
/* jshint node:true */
'use strict';
module.exports = function (grunt) {
var os = require('os');
+3
View File
@@ -315,6 +315,9 @@ api_url =
team_ids =
allowed_organizations =
tls_skip_verify_insecure = false
tls_client_cert =
tls_client_key =
tls_client_ca =
#################################### Basic Auth ##########################
[auth.basic]
+3
View File
@@ -15,6 +15,9 @@ start_tls = false
ssl_skip_verify = false
# set to the path to your root CA certificate or leave unset to use system defaults
# root_ca_cert = "/path/to/certificate.crt"
# Authentication against LDAP servers requiring client certificates
# client_cert = "/path/to/client.crt"
# client_key = "/path/to/client.key"
# Search user bind dn
bind_dn = "cn=admin,dc=grafana,dc=org"
+4
View File
@@ -272,6 +272,10 @@ log_queries =
;api_url = https://foo.bar/user
;team_ids =
;allowed_organizations =
;tls_skip_verify_insecure = false
;tls_client_cert =
;tls_client_key =
;tls_client_ca =
#################################### Grafana.com Auth ####################
[auth.grafana_com]
@@ -31,6 +31,7 @@ Name | Description
*User* | Database user's login/username
*Password* | Database user's password
*SSL Mode* | This option determines whether or with what priority a secure SSL TCP/IP connection will be negotiated with the server.
*TimescaleDB* | With this option enabled Grafana will use TimescaleDB features, e.g. use ```time_bucket``` for grouping by time (only available in Grafana 5.3+).
### Database User Permissions (Important!)
@@ -289,4 +290,5 @@ datasources:
password: "Password!"
jsonData:
sslmode: "disable" # disable/require/verify-ca/verify-full
timescaledb: false
```
+33
View File
@@ -363,6 +363,39 @@ Content-Type: application/json
]
```
## Teams that the actual User is member of
`GET /api/user/teams`
Return a list of all teams that the current user is member of.
**Example Request**:
```http
GET /api/user/teams HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
```http
HTTP/1.1 200
Content-Type: application/json
[
{
"id": 1,
"orgId": 1,
"name": "MyTestTeam",
"email": "",
"avatarUrl": "\/avatar\/3f49c15916554246daa714b9bd0ee398",
"memberCount": 1
}
]
```
## Star a dashboard
`POST /api/user/stars/dashboard/:dashboardId`
+3
View File
@@ -40,6 +40,9 @@ start_tls = false
ssl_skip_verify = false
# set to the path to your root CA certificate or leave unset to use system defaults
# root_ca_cert = "/path/to/certificate.crt"
# Authentication against LDAP servers requiring client certificates
# client_cert = "/path/to/client.crt"
# client_key = "/path/to/client.key"
# Search user bind dn
bind_dn = "cn=admin,dc=grafana,dc=org"
-3
View File
@@ -45,9 +45,7 @@
"grunt-contrib-concat": "^1.0.1",
"grunt-contrib-copy": "~1.0.0",
"grunt-contrib-cssmin": "~1.0.2",
"grunt-contrib-jshint": "~1.1.0",
"grunt-exec": "^1.0.1",
"grunt-jscs": "3.0.1",
"grunt-karma": "~2.0.0",
"grunt-notify": "^0.4.5",
"grunt-postcss": "^0.8.0",
@@ -60,7 +58,6 @@
"html-webpack-plugin": "^3.2.0",
"husky": "^0.14.3",
"jest": "^22.0.4",
"jshint-stylish": "~2.2.1",
"karma": "1.7.0",
"karma-chrome-launcher": "~2.2.0",
"karma-expect": "~1.1.3",
+1
View File
@@ -120,6 +120,7 @@ func (hs *HTTPServer) registerRoutes() {
userRoute.Put("/", bind(m.UpdateUserCommand{}), Wrap(UpdateSignedInUser))
userRoute.Post("/using/:id", Wrap(UserSetUsingOrg))
userRoute.Get("/orgs", Wrap(GetSignedInUserOrgList))
userRoute.Get("/teams", Wrap(GetSignedInUserTeamList))
userRoute.Post("/stars/dashboard/:id", Wrap(StarDashboard))
userRoute.Delete("/stars/dashboard/:id", Wrap(UnstarDashboard))
+15
View File
@@ -111,6 +111,21 @@ func GetSignedInUserOrgList(c *m.ReqContext) Response {
return getUserOrgList(c.UserId)
}
// GET /api/user/teams
func GetSignedInUserTeamList(c *m.ReqContext) Response {
query := m.GetTeamsByUserQuery{OrgId: c.OrgId, UserId: c.UserId}
if err := bus.Dispatch(&query); err != nil {
return Error(500, "Failed to get user teams", err)
}
for _, team := range query.Result {
team.AvatarUrl = dtos.GetGravatarUrlWithDefault(team.Email, team.Name)
}
return JSON(200, query.Result)
}
// GET /api/user/:id/orgs
func GetUserOrgList(c *m.ReqContext) Response {
return getUserOrgList(c.ParamsInt64(":id"))
+10
View File
@@ -59,6 +59,13 @@ func (a *ldapAuther) Dial() error {
}
}
}
var clientCert tls.Certificate
if a.server.ClientCert != "" && a.server.ClientKey != "" {
clientCert, err = tls.LoadX509KeyPair(a.server.ClientCert, a.server.ClientKey)
if err != nil {
return err
}
}
for _, host := range strings.Split(a.server.Host, " ") {
address := fmt.Sprintf("%s:%d", host, a.server.Port)
if a.server.UseSSL {
@@ -67,6 +74,9 @@ func (a *ldapAuther) Dial() error {
ServerName: host,
RootCAs: certPool,
}
if len(clientCert.Certificate) > 0 {
tlsCfg.Certificates = append(tlsCfg.Certificates, clientCert)
}
if a.server.StartTLS {
a.conn, err = ldap.Dial("tcp", address)
if err == nil {
+2
View File
@@ -21,6 +21,8 @@ type LdapServerConf struct {
StartTLS bool `toml:"start_tls"`
SkipVerifySSL bool `toml:"ssl_skip_verify"`
RootCACert string `toml:"root_ca_cert"`
ClientCert string `toml:"client_cert"`
ClientKey string `toml:"client_key"`
BindDN string `toml:"bind_dn"`
BindPassword string `toml:"bind_password"`
Attr LdapAttributeMap `toml:"attributes"`
+1 -2
View File
@@ -3,7 +3,6 @@ package alerting
import (
"errors"
"fmt"
"time"
"golang.org/x/sync/errgroup"
@@ -81,7 +80,7 @@ func (n *notificationService) uploadImage(context *EvalContext) (err error) {
renderOpts := rendering.Opts{
Width: 1000,
Height: 500,
Timeout: time.Second * 30,
Timeout: alertTimeout / 2,
OrgId: context.Rule.OrgId,
OrgRole: m.ROLE_ADMIN,
}
+11 -5
View File
@@ -14,12 +14,13 @@ const rsIdentifier = `([_a-zA-Z0-9]+)`
const sExpr = `\$` + rsIdentifier + `\(([^\)]*)\)`
type postgresMacroEngine struct {
timeRange *tsdb.TimeRange
query *tsdb.Query
timeRange *tsdb.TimeRange
query *tsdb.Query
timescaledb bool
}
func newPostgresMacroEngine() tsdb.SqlMacroEngine {
return &postgresMacroEngine{}
func newPostgresMacroEngine(timescaledb bool) tsdb.SqlMacroEngine {
return &postgresMacroEngine{timescaledb: timescaledb}
}
func (m *postgresMacroEngine) Interpolate(query *tsdb.Query, timeRange *tsdb.TimeRange, sql string) (string, error) {
@@ -118,7 +119,12 @@ func (m *postgresMacroEngine) evaluateMacro(name string, args []string) (string,
return "", err
}
}
return fmt.Sprintf("floor(extract(epoch from %s)/%v)*%v", args[0], interval.Seconds(), interval.Seconds()), nil
if m.timescaledb {
return fmt.Sprintf("time_bucket('%vs',%s)", interval.Seconds(), args[0]), nil
} else {
return fmt.Sprintf("floor(extract(epoch from %s)/%v)*%v", args[0], interval.Seconds(), interval.Seconds()), nil
}
case "__timeGroupAlias":
tg, err := m.evaluateMacro("__timeGroup", args)
if err == nil {
+20 -1
View File
@@ -12,7 +12,10 @@ import (
func TestMacroEngine(t *testing.T) {
Convey("MacroEngine", t, func() {
engine := newPostgresMacroEngine()
timescaledbEnabled := false
engine := newPostgresMacroEngine(timescaledbEnabled)
timescaledbEnabled = true
engineTS := newPostgresMacroEngine(timescaledbEnabled)
query := &tsdb.Query{}
Convey("Given a time range between 2018-04-12 00:00 and 2018-04-12 00:05", func() {
@@ -83,6 +86,22 @@ func TestMacroEngine(t *testing.T) {
So(sql2, ShouldEqual, sql+" AS \"time\"")
})
Convey("interpolate __timeGroup function with TimescaleDB enabled", func() {
sql, err := engineTS.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m')")
So(err, ShouldBeNil)
So(sql, ShouldEqual, "GROUP BY time_bucket('300s',time_column)")
})
Convey("interpolate __timeGroup function with spaces between args and TimescaleDB enabled", func() {
sql, err := engineTS.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column , '5m')")
So(err, ShouldBeNil)
So(sql, ShouldEqual, "GROUP BY time_bucket('300s',time_column)")
})
Convey("interpolate __timeTo function", func() {
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
So(err, ShouldBeNil)
+3 -1
View File
@@ -32,7 +32,9 @@ func newPostgresQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndp
log: logger,
}
return tsdb.NewSqlQueryEndpoint(&config, &rowTransformer, newPostgresMacroEngine(), logger)
timescaledb := datasource.JsonData.Get("timescaledb").MustBool(false)
return tsdb.NewSqlQueryEndpoint(&config, &rowTransformer, newPostgresMacroEngine(timescaledb), logger)
}
func generateConnectionString(datasource *models.DataSource) string {
+1 -1
View File
@@ -27,7 +27,7 @@ import (
// use to verify that the generated data are vizualized as expected, see
// devenv/README.md for setup instructions.
func TestPostgres(t *testing.T) {
// change to true to run the MySQL tests
// change to true to run the PostgreSQL tests
runPostgresTests := false
// runPostgresTests := true
+3 -2
View File
@@ -69,8 +69,9 @@ export class TeamMembers extends React.Component<Props, State> {
render() {
const { newTeamMember, isAdding } = this.state;
const members = this.props.team.members.values();
const members = this.props.team.filteredMembers;
const newTeamMemberValue = newTeamMember && newTeamMember.id.toString();
const { team } = this.props;
return (
<div>
@@ -81,7 +82,7 @@ export class TeamMembers extends React.Component<Props, State> {
type="text"
className="gf-form-input"
placeholder="Search members"
value={''}
value={team.search}
onChange={this.onSearchQueryChange}
/>
<i className="gf-form-input-icon fa fa-search" />
+1
View File
@@ -25,6 +25,7 @@ export class HelpCtrl {
{ keys: ['d', 'k'], description: 'Toggle kiosk mode (hides top nav)' },
{ keys: ['d', 'E'], description: 'Expand all rows' },
{ keys: ['d', 'C'], description: 'Collapse all rows' },
{ keys: ['d', 'a'], description: 'Toggle auto fit panels (experimental feature)' },
{ keys: ['mod+o'], description: 'Toggle shared graph crosshair' },
],
'Focused Panel': [
+3 -12
View File
@@ -15,14 +15,7 @@ export class KeybindingSrv {
timepickerOpen = false;
/** @ngInject */
constructor(
private $rootScope,
private $location,
private datasourceSrv,
private timeSrv,
private contextSrv,
private $route
) {
constructor(private $rootScope, private $location, private datasourceSrv, private timeSrv, private contextSrv) {
// clear out all shortcuts on route change
$rootScope.$on('$routeChangeSuccess', () => {
Mousetrap.reset();
@@ -269,10 +262,8 @@ export class KeybindingSrv {
//Autofit panels
this.bind('d a', () => {
this.$location.search('autofitpanels', this.$location.search().autofitpanels ? null : true);
//Force reload
this.$route.reload();
// this has to be a full page reload
window.location.href = window.location.href + '&autofitpanels';
});
}
}
+89 -91
View File
@@ -2,120 +2,118 @@ import angular from 'angular';
import config from 'app/core/config';
import moment from 'moment';
export class ShareModalCtrl {
/** @ngInject */
constructor($scope, $rootScope, $location, $timeout, timeSrv, templateSrv, linkSrv) {
$scope.options = {
forCurrent: true,
includeTemplateVars: true,
theme: 'current',
};
$scope.editor = { index: $scope.tabIndex || 0 };
/** @ngInject */
export function ShareModalCtrl($scope, $rootScope, $location, $timeout, timeSrv, templateSrv, linkSrv) {
$scope.options = {
forCurrent: true,
includeTemplateVars: true,
theme: 'current',
};
$scope.editor = { index: $scope.tabIndex || 0 };
$scope.init = function() {
$scope.modeSharePanel = $scope.panel ? true : false;
$scope.init = function() {
$scope.modeSharePanel = $scope.panel ? true : false;
$scope.tabs = [{ title: 'Link', src: 'shareLink.html' }];
$scope.tabs = [{ title: 'Link', src: 'shareLink.html' }];
if ($scope.modeSharePanel) {
$scope.modalTitle = 'Share Panel';
$scope.tabs.push({ title: 'Embed', src: 'shareEmbed.html' });
} else {
$scope.modalTitle = 'Share';
}
if ($scope.modeSharePanel) {
$scope.modalTitle = 'Share Panel';
$scope.tabs.push({ title: 'Embed', src: 'shareEmbed.html' });
} else {
$scope.modalTitle = 'Share';
}
if (!$scope.dashboard.meta.isSnapshot) {
$scope.tabs.push({ title: 'Snapshot', src: 'shareSnapshot.html' });
}
if (!$scope.dashboard.meta.isSnapshot) {
$scope.tabs.push({ title: 'Snapshot', src: 'shareSnapshot.html' });
}
if (!$scope.dashboard.meta.isSnapshot && !$scope.modeSharePanel) {
$scope.tabs.push({ title: 'Export', src: 'shareExport.html' });
}
if (!$scope.dashboard.meta.isSnapshot && !$scope.modeSharePanel) {
$scope.tabs.push({ title: 'Export', src: 'shareExport.html' });
}
$scope.buildUrl();
};
$scope.buildUrl();
};
$scope.buildUrl = function() {
var baseUrl = $location.absUrl();
var queryStart = baseUrl.indexOf('?');
$scope.buildUrl = function() {
var baseUrl = $location.absUrl();
var queryStart = baseUrl.indexOf('?');
if (queryStart !== -1) {
baseUrl = baseUrl.substring(0, queryStart);
}
if (queryStart !== -1) {
baseUrl = baseUrl.substring(0, queryStart);
}
var params = angular.copy($location.search());
var params = angular.copy($location.search());
var range = timeSrv.timeRange();
params.from = range.from.valueOf();
params.to = range.to.valueOf();
params.orgId = config.bootData.user.orgId;
var range = timeSrv.timeRange();
params.from = range.from.valueOf();
params.to = range.to.valueOf();
params.orgId = config.bootData.user.orgId;
if ($scope.options.includeTemplateVars) {
templateSrv.fillVariableValuesForUrl(params);
}
if ($scope.options.includeTemplateVars) {
templateSrv.fillVariableValuesForUrl(params);
}
if (!$scope.options.forCurrent) {
delete params.from;
delete params.to;
}
if (!$scope.options.forCurrent) {
delete params.from;
delete params.to;
}
if ($scope.options.theme !== 'current') {
params.theme = $scope.options.theme;
}
if ($scope.options.theme !== 'current') {
params.theme = $scope.options.theme;
}
if ($scope.modeSharePanel) {
params.panelId = $scope.panel.id;
params.fullscreen = true;
} else {
delete params.panelId;
delete params.fullscreen;
}
$scope.shareUrl = linkSrv.addParamsToUrl(baseUrl, params);
var soloUrl = baseUrl.replace(config.appSubUrl + '/dashboard/', config.appSubUrl + '/dashboard-solo/');
soloUrl = soloUrl.replace(config.appSubUrl + '/d/', config.appSubUrl + '/d-solo/');
if ($scope.modeSharePanel) {
params.panelId = $scope.panel.id;
params.fullscreen = true;
} else {
delete params.panelId;
delete params.fullscreen;
delete params.edit;
soloUrl = linkSrv.addParamsToUrl(soloUrl, params);
}
$scope.iframeHtml = '<iframe src="' + soloUrl + '" width="450" height="200" frameborder="0"></iframe>';
$scope.shareUrl = linkSrv.addParamsToUrl(baseUrl, params);
$scope.imageUrl = soloUrl.replace(
config.appSubUrl + '/dashboard-solo/',
config.appSubUrl + '/render/dashboard-solo/'
);
$scope.imageUrl = $scope.imageUrl.replace(config.appSubUrl + '/d-solo/', config.appSubUrl + '/render/d-solo/');
$scope.imageUrl += '&width=1000&height=500' + $scope.getLocalTimeZone();
};
var soloUrl = baseUrl.replace(config.appSubUrl + '/dashboard/', config.appSubUrl + '/dashboard-solo/');
soloUrl = soloUrl.replace(config.appSubUrl + '/d/', config.appSubUrl + '/d-solo/');
delete params.fullscreen;
delete params.edit;
soloUrl = linkSrv.addParamsToUrl(soloUrl, params);
// This function will try to return the proper full name of the local timezone
// Chrome does not handle the timezone offset (but phantomjs does)
$scope.getLocalTimeZone = function() {
let utcOffset = '&tz=UTC' + encodeURIComponent(moment().format('Z'));
$scope.iframeHtml = '<iframe src="' + soloUrl + '" width="450" height="200" frameborder="0"></iframe>';
// Older browser does not the internationalization API
if (!(<any>window).Intl) {
return utcOffset;
}
$scope.imageUrl = soloUrl.replace(
config.appSubUrl + '/dashboard-solo/',
config.appSubUrl + '/render/dashboard-solo/'
);
$scope.imageUrl = $scope.imageUrl.replace(config.appSubUrl + '/d-solo/', config.appSubUrl + '/render/d-solo/');
$scope.imageUrl += '&width=1000&height=500' + $scope.getLocalTimeZone();
};
const dateFormat = (<any>window).Intl.DateTimeFormat();
if (!dateFormat.resolvedOptions) {
return utcOffset;
}
// This function will try to return the proper full name of the local timezone
// Chrome does not handle the timezone offset (but phantomjs does)
$scope.getLocalTimeZone = function() {
let utcOffset = '&tz=UTC' + encodeURIComponent(moment().format('Z'));
const options = dateFormat.resolvedOptions();
if (!options.timeZone) {
return utcOffset;
}
// Older browser does not the internationalization API
if (!(<any>window).Intl) {
return utcOffset;
}
return '&tz=' + encodeURIComponent(options.timeZone);
};
const dateFormat = (<any>window).Intl.DateTimeFormat();
if (!dateFormat.resolvedOptions) {
return utcOffset;
}
$scope.getShareUrl = function() {
return $scope.shareUrl;
};
}
const options = dateFormat.resolvedOptions();
if (!options.timeZone) {
return utcOffset;
}
return '&tz=' + encodeURIComponent(options.timeZone);
};
$scope.getShareUrl = function() {
return $scope.shareUrl;
};
}
angular.module('grafana.controllers').controller('ShareModalCtrl', ShareModalCtrl);
@@ -0,0 +1,150 @@
import '../shareModalCtrl';
import { ShareModalCtrl } from '../shareModalCtrl';
import config from 'app/core/config';
import { LinkSrv } from 'app/features/panellinks/link_srv';
describe('ShareModalCtrl', () => {
var ctx = <any>{
timeSrv: {
timeRange: () => {
return { from: new Date(1000), to: new Date(2000) };
},
},
$location: {
absUrl: () => 'http://server/#!/test',
search: () => {
return { from: '', to: '' };
},
},
scope: {
dashboard: {
meta: {
isSnapshot: true,
},
},
},
templateSrv: {
fillVariableValuesForUrl: () => {},
},
};
(<any>window).Intl.DateTimeFormat = () => {
return {
resolvedOptions: () => {
return { timeZone: 'UTC' };
},
};
};
beforeEach(() => {
config.bootData = {
user: {
orgId: 1,
},
};
ctx.ctrl = new ShareModalCtrl(
ctx.scope,
{},
ctx.$location,
{},
ctx.timeSrv,
ctx.templateSrv,
new LinkSrv({}, ctx.stimeSrv)
);
});
describe('shareUrl with current time range and panel', () => {
it('should generate share url absolute time', () => {
ctx.scope.panel = { id: 22 };
ctx.scope.init();
expect(ctx.scope.shareUrl).toBe('http://server/#!/test?from=1000&to=2000&orgId=1&panelId=22&fullscreen');
});
it('should generate render url', () => {
ctx.$location.absUrl = () => 'http://dashboards.grafana.com/d/abcdefghi/my-dash';
ctx.scope.panel = { id: 22 };
ctx.scope.init();
var base = 'http://dashboards.grafana.com/render/d-solo/abcdefghi/my-dash';
var params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
expect(ctx.scope.imageUrl).toContain(base + params);
});
it('should generate render url for scripted dashboard', () => {
ctx.$location.absUrl = () => 'http://dashboards.grafana.com/dashboard/script/my-dash.js';
ctx.scope.panel = { id: 22 };
ctx.scope.init();
var base = 'http://dashboards.grafana.com/render/dashboard-solo/script/my-dash.js';
var params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
expect(ctx.scope.imageUrl).toContain(base + params);
});
it('should remove panel id when no panel in scope', () => {
ctx.$location.absUrl = () => 'http://server/#!/test';
ctx.scope.options.forCurrent = true;
ctx.scope.panel = null;
ctx.scope.init();
expect(ctx.scope.shareUrl).toBe('http://server/#!/test?from=1000&to=2000&orgId=1');
});
it('should add theme when specified', () => {
ctx.scope.options.theme = 'light';
ctx.scope.panel = null;
ctx.scope.init();
expect(ctx.scope.shareUrl).toBe('http://server/#!/test?from=1000&to=2000&orgId=1&theme=light');
});
it('should remove fullscreen from image url when is first param in querystring and modeSharePanel is true', () => {
ctx.$location.search = () => {
return { fullscreen: true, edit: true };
};
ctx.$location.absUrl = () => 'http://server/#!/test?fullscreen&edit';
ctx.scope.modeSharePanel = true;
ctx.scope.panel = { id: 1 };
ctx.scope.buildUrl();
expect(ctx.scope.shareUrl).toContain('?fullscreen&edit&from=1000&to=2000&orgId=1&panelId=1');
expect(ctx.scope.imageUrl).toContain('?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&tz=UTC');
});
it('should remove edit from image url when is first param in querystring and modeSharePanel is true', () => {
ctx.$location.search = () => {
return { edit: true, fullscreen: true };
};
ctx.$location.absUrl = () => 'http://server/#!/test?edit&fullscreen';
ctx.scope.modeSharePanel = true;
ctx.scope.panel = { id: 1 };
ctx.scope.buildUrl();
expect(ctx.scope.shareUrl).toContain('?edit&fullscreen&from=1000&to=2000&orgId=1&panelId=1');
expect(ctx.scope.imageUrl).toContain('?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&tz=UTC');
});
it('should include template variables in url', () => {
ctx.$location.search = () => {
return {};
};
ctx.$location.absUrl = () => 'http://server/#!/test';
ctx.scope.options.includeTemplateVars = true;
ctx.templateSrv.fillVariableValuesForUrl = function(params) {
params['var-app'] = 'mupp';
params['var-server'] = 'srv-01';
};
ctx.scope.buildUrl();
expect(ctx.scope.shareUrl).toContain(
'http://server/#!/test?from=1000&to=2000&orgId=1&var-app=mupp&var-server=srv-01'
);
});
});
});
@@ -1,122 +0,0 @@
import { describe, beforeEach, it, expect, sinon, angularMocks } from 'test/lib/common';
import helpers from 'test/specs/helpers';
import '../shareModalCtrl';
import config from 'app/core/config';
import 'app/features/panellinks/link_srv';
describe('ShareModalCtrl', function() {
var ctx = new helpers.ControllerTestContext();
function setTime(range) {
ctx.timeSrv.timeRange = sinon.stub().returns(range);
}
beforeEach(function() {
config.bootData = {
user: {
orgId: 1,
},
};
});
setTime({ from: new Date(1000), to: new Date(2000) });
beforeEach(angularMocks.module('grafana.controllers'));
beforeEach(angularMocks.module('grafana.services'));
beforeEach(
angularMocks.module(function($compileProvider) {
$compileProvider.preAssignBindingsEnabled(true);
})
);
beforeEach(ctx.providePhase());
beforeEach(ctx.createControllerPhase('ShareModalCtrl'));
describe('shareUrl with current time range and panel', function() {
it('should generate share url absolute time', function() {
ctx.$location.path('/test');
ctx.scope.panel = { id: 22 };
ctx.scope.init();
expect(ctx.scope.shareUrl).to.be('http://server/#!/test?from=1000&to=2000&orgId=1&panelId=22&fullscreen');
});
it('should generate render url', function() {
ctx.$location.$$absUrl = 'http://dashboards.grafana.com/d/abcdefghi/my-dash';
ctx.scope.panel = { id: 22 };
ctx.scope.init();
var base = 'http://dashboards.grafana.com/render/d-solo/abcdefghi/my-dash';
var params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
expect(ctx.scope.imageUrl).to.contain(base + params);
});
it('should generate render url for scripted dashboard', function() {
ctx.$location.$$absUrl = 'http://dashboards.grafana.com/dashboard/script/my-dash.js';
ctx.scope.panel = { id: 22 };
ctx.scope.init();
var base = 'http://dashboards.grafana.com/render/dashboard-solo/script/my-dash.js';
var params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
expect(ctx.scope.imageUrl).to.contain(base + params);
});
it('should remove panel id when no panel in scope', function() {
ctx.$location.path('/test');
ctx.scope.options.forCurrent = true;
ctx.scope.panel = null;
ctx.scope.init();
expect(ctx.scope.shareUrl).to.be('http://server/#!/test?from=1000&to=2000&orgId=1');
});
it('should add theme when specified', function() {
ctx.$location.path('/test');
ctx.scope.options.theme = 'light';
ctx.scope.panel = null;
ctx.scope.init();
expect(ctx.scope.shareUrl).to.be('http://server/#!/test?from=1000&to=2000&orgId=1&theme=light');
});
it('should remove fullscreen from image url when is first param in querystring and modeSharePanel is true', function() {
ctx.$location.url('/test?fullscreen&edit');
ctx.scope.modeSharePanel = true;
ctx.scope.panel = { id: 1 };
ctx.scope.buildUrl();
expect(ctx.scope.shareUrl).to.contain('?fullscreen&edit&from=1000&to=2000&orgId=1&panelId=1');
expect(ctx.scope.imageUrl).to.contain('?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&tz=UTC');
});
it('should remove edit from image url when is first param in querystring and modeSharePanel is true', function() {
ctx.$location.url('/test?edit&fullscreen');
ctx.scope.modeSharePanel = true;
ctx.scope.panel = { id: 1 };
ctx.scope.buildUrl();
expect(ctx.scope.shareUrl).to.contain('?edit&fullscreen&from=1000&to=2000&orgId=1&panelId=1');
expect(ctx.scope.imageUrl).to.contain('?from=1000&to=2000&orgId=1&panelId=1&width=1000&height=500&tz=UTC');
});
it('should include template variables in url', function() {
ctx.$location.path('/test');
ctx.scope.options.includeTemplateVars = true;
ctx.templateSrv.fillVariableValuesForUrl = function(params) {
params['var-app'] = 'mupp';
params['var-server'] = 'srv-01';
};
ctx.scope.buildUrl();
expect(ctx.scope.shareUrl).to.be(
'http://server/#!/test?from=1000&to=2000&orgId=1&var-app=mupp&var-server=srv-01'
);
});
});
});
+2 -2
View File
@@ -144,8 +144,8 @@ export class DashLinksContainerCtrl {
if (dash.id !== currentDashId) {
memo.push({
title: dash.title,
url: 'dashboard/' + dash.uri,
target: link.target,
url: dash.url,
target: link.target === '_self' ? '' : link.target,
icon: 'fa fa-th-large',
keepTime: link.keepTime,
includeVars: link.includeVars,
+62 -41
View File
@@ -3,53 +3,74 @@
<div class="page-container page-body">
<h3 class="page-sub-heading">User Profile</h3>
<form name="ctrl.userForm" class="gf-form-group">
<form name="ctrl.userForm" class="gf-form-group">
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Name</span>
<input class="gf-form-input max-width-22" type="text" required ng-model="ctrl.user.name" >
</div>
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Email</span>
<input class="gf-form-input max-width-22" type="email" ng-readonly="ctrl.readonlyLoginFields" required ng-model="ctrl.user.email">
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Name</span>
<input class="gf-form-input max-width-22" type="text" required ng-model="ctrl.user.name">
</div>
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Email</span>
<input class="gf-form-input max-width-22" type="email" ng-readonly="ctrl.readonlyLoginFields" required ng-model="ctrl.user.email">
<i ng-if="ctrl.readonlyLoginFields" class="fa fa-lock gf-form-icon--right-absolute" bs-tooltip="'Login Details Locked - managed in another system.'"></i>
</div>
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Username</span>
<div class="gf-form max-width-30">
<span class="gf-form-label width-8">Username</span>
<input class="gf-form-input max-width-22" type="text" ng-readonly="ctrl.readonlyLoginFields" required ng-model="ctrl.user.login">
<i ng-if="ctrl.readonlyLoginFields" class="fa fa-lock gf-form-icon--right-absolute" bs-tooltip="'Login Details Locked - managed in another system.'"></i>
</div>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-click="ctrl.update()">Save</button>
</div>
</form>
<div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-click="ctrl.update()">Save</button>
</div>
</form>
<prefs-control mode="user"></prefs-control>
<prefs-control mode="user"></prefs-control>
<h3 class="page-heading" ng-show="ctrl.showOrgsList">Organizations</h3>
<h3 class="page-heading" ng-show="ctrl.showTeamsList">Teams</h3>
<div class="gf-form-group" ng-show="ctrl.showTeamsList">
<table class="filter-table form-inline">
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Email</th>
<th>Members</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="team in ctrl.teams">
<td class="width-4 text-center"><img class="filter-table__avatar" src={{team.avatarUrl}}></td>
<td>{{team.name}}</td>
<td>{{team.email}}</td>
<td>{{team.memberCount}}</td>
</tr>
</tbody>
</table>
</div>
<h3 class="page-heading" ng-show="ctrl.showOrgsList">Organizations</h3>
<div class="gf-form-group" ng-show="ctrl.showOrgsList">
<table class="filter-table form-inline">
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="org in ctrl.orgs">
<td>{{org.name}}</td>
<td>{{org.role}}</td>
<td class="text-right">
<span class="btn btn-primary btn-mini" ng-show="org.orgId === contextSrv.user.orgId">
Current
</span>
<a ng-click="ctrl.setUsingOrg(org)" class="btn btn-inverse btn-mini" ng-show="org.orgId !== contextSrv.user.orgId">
Select
</a>
</td>
</tr>
</tbody>
</table>
</div>
<table class="filter-table form-inline">
<thead>
<tr>
<th>Name</th>
<th>Role</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="org in ctrl.orgs">
<td>{{org.name}}</td>
<td>{{org.role}}</td>
<td class="text-right">
<span class="btn btn-primary btn-mini" ng-show="org.orgId === contextSrv.user.orgId">
Current
</span>
<a ng-click="ctrl.setUsingOrg(org)" class="btn btn-inverse btn-mini" ng-show="org.orgId !== contextSrv.user.orgId">
Select
</a>
</td>
</tr>
</tbody>
</table>
</div>
+10
View File
@@ -4,8 +4,10 @@ import { coreModule } from 'app/core/core';
export class ProfileCtrl {
user: any;
old_theme: any;
teams: any = [];
orgs: any = [];
userForm: any;
showTeamsList = false;
showOrgsList = false;
readonlyLoginFields = config.disableLoginForm;
navModel: any;
@@ -13,6 +15,7 @@ export class ProfileCtrl {
/** @ngInject **/
constructor(private backendSrv, private contextSrv, private $location, navModelSrv) {
this.getUser();
this.getUserTeams();
this.getUserOrgs();
this.navModel = navModelSrv.getNav('profile', 'profile-settings', 0);
}
@@ -24,6 +27,13 @@ export class ProfileCtrl {
});
}
getUserTeams() {
this.backendSrv.get('/api/user/teams').then(teams => {
this.teams = teams;
this.showTeamsList = this.teams.length > 0;
});
}
getUserOrgs() {
this.backendSrv.get('/api/user/orgs').then(orgs => {
this.orgs = orgs;
@@ -77,6 +77,10 @@ export class LinkSrv {
info.target = link.targetBlank ? '_blank' : '_self';
info.href = this.templateSrv.replace(link.url || '', scopedVars);
info.title = this.templateSrv.replace(link.title || '', scopedVars);
} else if (link.url) {
info.href = link.url;
info.title = this.templateSrv.replace(link.title || '', scopedVars);
info.target = link.targetBlank ? '_blank' : '';
} else if (link.dashUri) {
info.href = 'dashboard/' + link.dashUri + '?';
info.title = this.templateSrv.replace(link.title || '', scopedVars);
+6 -1
View File
@@ -39,7 +39,12 @@ export class PanelLinksEditorCtrl {
backendSrv.search({ query: link.dashboard }).then(function(hits) {
var dashboard = _.find(hits, { title: link.dashboard });
if (dashboard) {
link.dashUri = dashboard.uri;
if (dashboard.url) {
link.url = dashboard.url;
} else {
// To support legacy url's
link.dashUri = dashboard.uri;
}
link.title = dashboard.title;
}
});
@@ -973,13 +973,12 @@ export class FuncInstance {
} else if (_.get(_.last(this.def.params), 'multiple')) {
paramType = _.get(_.last(this.def.params), 'type');
}
if (paramType === 'value_or_series') {
// param types that should never be quoted
if (_.includes(['value_or_series', 'boolean', 'int', 'float', 'node'], paramType)) {
return value;
}
if (paramType === 'boolean' && _.includes(['true', 'false'], value)) {
return value;
}
if (_.includes(['int', 'float', 'int_or_interval', 'node_or_tag', 'node'], paramType) && _.isFinite(+value)) {
// param types that might be quoted
if (_.includes(['int_or_interval', 'node_or_tag'], paramType) && _.isFinite(+value)) {
return _.toString(+value);
}
return "'" + value + "'";
@@ -55,6 +55,24 @@ describe('when rendering func instance', function() {
expect(func.render('hello')).toEqual("movingMedian(hello, '5min')");
});
it('should never quote boolean paramater', function() {
var func = gfunc.createFuncInstance('sortByName');
func.params[0] = '$natural';
expect(func.render('hello')).toEqual('sortByName(hello, $natural)');
});
it('should never quote int paramater', function() {
var func = gfunc.createFuncInstance('maximumAbove');
func.params[0] = '$value';
expect(func.render('hello')).toEqual('maximumAbove(hello, $value)');
});
it('should never quote node paramater', function() {
var func = gfunc.createFuncInstance('aliasByNode');
func.params[0] = '$node';
expect(func.render('hello')).toEqual('aliasByNode(hello, $node)');
});
it('should handle metric param and int param and string param', function() {
var func = gfunc.createFuncInstance('groupByNode');
func.params[0] = 5;
@@ -187,6 +187,11 @@ export default class InfluxDatasource {
return this.$q.when({ results: [] });
}
if (options && options.range) {
var timeFilter = this.getTimeFilter({ rangeRaw: options.range });
query = query.replace('$timeFilter', timeFilter);
}
return this._influxRequest('GET', '/query', { q: query, epoch: 'ms' }, options);
}
@@ -0,0 +1,53 @@
import InfluxDatasource from '../datasource';
import $q from 'q';
import { TemplateSrvStub } from 'test/specs/helpers';
describe('InfluxDataSource', () => {
let ctx: any = {
backendSrv: {},
$q: $q,
templateSrv: new TemplateSrvStub(),
instanceSettings: { url: 'url', name: 'influxDb', jsonData: {} },
};
beforeEach(function() {
ctx.instanceSettings.url = '/api/datasources/proxy/1';
ctx.ds = new InfluxDatasource(ctx.instanceSettings, ctx.$q, ctx.backendSrv, ctx.templateSrv);
});
describe('When issuing metricFindQuery', () => {
let query = 'SELECT max(value) FROM measurement WHERE $timeFilter';
let queryOptions: any = {
range: {
from: '2018-01-01T00:00:00Z',
to: '2018-01-02T00:00:00Z',
},
};
let requestQuery;
beforeEach(async () => {
ctx.backendSrv.datasourceRequest = function(req) {
requestQuery = req.params.q;
return ctx.$q.when({
results: [
{
series: [
{
name: 'measurement',
columns: ['max'],
values: [[1]],
},
],
},
],
});
};
await ctx.ds.metricFindQuery(query, queryOptions).then(function(_) {});
});
it('should replace $timefilter', () => {
expect(requestQuery).toMatch('time >= 1514764800000ms and time <= 1514851200000ms');
});
});
});
@@ -123,25 +123,7 @@ export class PostgresDatasource {
}
testDatasource() {
return this.backendSrv
.datasourceRequest({
url: '/api/tsdb/query',
method: 'POST',
data: {
from: '5m',
to: 'now',
queries: [
{
refId: 'A',
intervalMs: 1,
maxDataPoints: 1,
datasourceId: this.id,
rawSql: 'SELECT 1',
format: 'table',
},
],
},
})
return this.metricFindQuery('SELECT 1', {})
.then(res => {
return { status: 'success', message: 'Database Connection OK' };
})
@@ -38,6 +38,14 @@
</div>
</div>
<h3 class="page-heading">PostgreSQL details</h3>
<div class="gf-form-group">
<div class="gf-form">
<gf-form-switch class="gf-form" label="TimescaleDB" tooltip="Use TimescaleDB features (e.g., time_bucket) in Grafana" label-class="width-9" checked="ctrl.current.jsonData.timescaledb" switch-class="max-width-6"></gf-form-switch>
</div>
</div>
<div class="gf-form-group">
<div class="grafana-info-box">
<h5>User Permission</h5>
File diff suppressed because it is too large Load Diff
+1
View File
@@ -13,6 +13,7 @@ import { axesEditorComponent } from './axes_editor';
class GraphCtrl extends MetricsPanelCtrl {
static template = template;
renderError: boolean;
hiddenSeries: any = {};
seriesList: any = [];
dataList: any = [];
@@ -0,0 +1,518 @@
jest.mock('app/features/annotations/all', () => ({
EventManager: function() {
return {
on: () => {},
addFlotEvents: () => {},
};
},
}));
jest.mock('app/core/core', () => ({
coreModule: {
directive: () => {},
},
appEvents: {
on: () => {},
},
}));
import '../module';
import { GraphCtrl } from '../module';
import { MetricsPanelCtrl } from 'app/features/panel/metrics_panel_ctrl';
import { PanelCtrl } from 'app/features/panel/panel_ctrl';
import config from 'app/core/config';
import TimeSeries from 'app/core/time_series2';
import moment from 'moment';
import $ from 'jquery';
import { graphDirective } from '../graph';
let ctx = <any>{};
let ctrl;
let scope = {
ctrl: {},
range: {
from: moment([2015, 1, 1]),
to: moment([2015, 11, 20]),
},
$on: () => {},
};
let link;
describe('grafanaGraph', function() {
const setupCtx = (beforeRender?) => {
config.bootData = {
user: {
lightTheme: false,
},
};
GraphCtrl.prototype = <any>{
...MetricsPanelCtrl.prototype,
...PanelCtrl.prototype,
...GraphCtrl.prototype,
height: 200,
panel: {
events: {
on: () => {},
},
legend: {},
grid: {},
yaxes: [
{
min: null,
max: null,
format: 'short',
logBase: 1,
},
{
min: null,
max: null,
format: 'short',
logBase: 1,
},
],
thresholds: [],
xaxis: {},
seriesOverrides: [],
tooltip: {
shared: true,
},
},
renderingCompleted: jest.fn(),
hiddenSeries: {},
dashboard: {
getTimezone: () => 'browser',
},
range: {
from: moment([2015, 1, 1, 10]),
to: moment([2015, 1, 1, 22]),
},
};
ctx.data = [];
ctx.data.push(
new TimeSeries({
datapoints: [[1, 1], [2, 2]],
alias: 'series1',
})
);
ctx.data.push(
new TimeSeries({
datapoints: [[10, 1], [20, 2]],
alias: 'series2',
})
);
ctrl = new GraphCtrl(
{
$on: () => {},
},
{
get: () => {},
},
{}
);
$.plot = ctrl.plot = jest.fn();
scope.ctrl = ctrl;
link = graphDirective({}, {}, {}).link(scope, { width: () => 500, mouseleave: () => {}, bind: () => {} });
if (typeof beforeRender === 'function') {
beforeRender();
}
link.data = ctx.data;
//Emulate functions called by event listeners
link.buildFlotPairs(link.data);
link.render_panel();
ctx.plotData = ctrl.plot.mock.calls[0][1];
ctx.plotOptions = ctrl.plot.mock.calls[0][2];
};
describe('simple lines options', () => {
beforeEach(() => {
setupCtx(() => {
ctrl.panel.lines = true;
ctrl.panel.fill = 5;
ctrl.panel.linewidth = 3;
ctrl.panel.steppedLine = true;
});
});
it('should configure plot with correct options', () => {
expect(ctx.plotOptions.series.lines.show).toBe(true);
expect(ctx.plotOptions.series.lines.fill).toBe(0.5);
expect(ctx.plotOptions.series.lines.lineWidth).toBe(3);
expect(ctx.plotOptions.series.lines.steps).toBe(true);
});
});
describe('sorting stacked series as legend. disabled', () => {
beforeEach(() => {
setupCtx(() => {
ctrl.panel.legend.sort = undefined;
ctrl.panel.stack = false;
});
});
it('should not modify order of time series', () => {
expect(ctx.plotData[0].alias).toBe('series1');
expect(ctx.plotData[1].alias).toBe('series2');
});
});
describe('sorting stacked series as legend. min descending order', () => {
beforeEach(() => {
setupCtx(() => {
ctrl.panel.legend.sort = 'min';
ctrl.panel.legend.sortDesc = true;
ctrl.panel.stack = true;
});
});
it('highest value should be first', () => {
expect(ctx.plotData[0].alias).toBe('series2');
expect(ctx.plotData[1].alias).toBe('series1');
});
});
describe('sorting stacked series as legend. min ascending order', () => {
beforeEach(() => {
setupCtx(() => {
ctrl.panel.legend.sort = 'min';
ctrl.panel.legend.sortDesc = false;
ctrl.panel.stack = true;
});
});
it('lowest value should be first', () => {
expect(ctx.plotData[0].alias).toBe('series1');
expect(ctx.plotData[1].alias).toBe('series2');
});
});
describe('sorting stacked series as legend. stacking disabled', () => {
beforeEach(() => {
setupCtx(() => {
ctrl.panel.legend.sort = 'min';
ctrl.panel.legend.sortDesc = true;
ctrl.panel.stack = false;
});
});
it('highest value should be first', () => {
expect(ctx.plotData[0].alias).toBe('series1');
expect(ctx.plotData[1].alias).toBe('series2');
});
});
describe('sorting stacked series as legend. current descending order', () => {
beforeEach(() => {
setupCtx(() => {
ctrl.panel.legend.sort = 'current';
ctrl.panel.legend.sortDesc = true;
ctrl.panel.stack = true;
});
});
it('highest last value should be first', () => {
expect(ctx.plotData[0].alias).toBe('series2');
expect(ctx.plotData[1].alias).toBe('series1');
});
});
describe('when logBase is log 10', () => {
beforeEach(() => {
setupCtx(() => {
ctx.data[0] = new TimeSeries({
datapoints: [[2000, 1], [0.002, 2], [0, 3], [-1, 4]],
alias: 'seriesAutoscale',
});
ctx.data[0].yaxis = 1;
ctx.data[1] = new TimeSeries({
datapoints: [[2000, 1], [0.002, 2], [0, 3], [-1, 4]],
alias: 'seriesFixedscale',
});
ctx.data[1].yaxis = 2;
ctrl.panel.yaxes[0].logBase = 10;
ctrl.panel.yaxes[1].logBase = 10;
ctrl.panel.yaxes[1].min = '0.05';
ctrl.panel.yaxes[1].max = '1500';
});
});
it('should apply axis transform, autoscaling (if necessary) and ticks', function() {
var axisAutoscale = ctx.plotOptions.yaxes[0];
expect(axisAutoscale.transform(100)).toBe(2);
expect(axisAutoscale.inverseTransform(-3)).toBeCloseTo(0.001);
expect(axisAutoscale.min).toBeCloseTo(0.001);
expect(axisAutoscale.max).toBe(10000);
expect(axisAutoscale.ticks.length).toBeCloseTo(8);
expect(axisAutoscale.ticks[0]).toBeCloseTo(0.001);
if (axisAutoscale.ticks.length === 7) {
expect(axisAutoscale.ticks[axisAutoscale.ticks.length - 1]).toBeCloseTo(1000);
} else {
expect(axisAutoscale.ticks[axisAutoscale.ticks.length - 1]).toBe(10000);
}
var axisFixedscale = ctx.plotOptions.yaxes[1];
expect(axisFixedscale.min).toBe(0.05);
expect(axisFixedscale.max).toBe(1500);
expect(axisFixedscale.ticks.length).toBe(5);
expect(axisFixedscale.ticks[0]).toBe(0.1);
expect(axisFixedscale.ticks[4]).toBe(1000);
});
});
describe('when logBase is log 10 and data points contain only zeroes', () => {
beforeEach(() => {
setupCtx(() => {
ctrl.panel.yaxes[0].logBase = 10;
ctx.data[0] = new TimeSeries({
datapoints: [[0, 1], [0, 2], [0, 3], [0, 4]],
alias: 'seriesAutoscale',
});
ctx.data[0].yaxis = 1;
});
});
it('should not set min and max and should create some fake ticks', function() {
var axisAutoscale = ctx.plotOptions.yaxes[0];
expect(axisAutoscale.transform(100)).toBe(2);
expect(axisAutoscale.inverseTransform(-3)).toBeCloseTo(0.001);
expect(axisAutoscale.min).toBe(undefined);
expect(axisAutoscale.max).toBe(undefined);
expect(axisAutoscale.ticks.length).toBe(2);
expect(axisAutoscale.ticks[0]).toBe(1);
expect(axisAutoscale.ticks[1]).toBe(2);
});
});
// y-min set 0 is a special case for log scale,
// this approximates it by setting min to 0.1
describe('when logBase is log 10 and y-min is set to 0 and auto min is > 0.1', () => {
beforeEach(() => {
setupCtx(() => {
ctrl.panel.yaxes[0].logBase = 10;
ctrl.panel.yaxes[0].min = '0';
ctx.data[0] = new TimeSeries({
datapoints: [[2000, 1], [4, 2], [500, 3], [3000, 4]],
alias: 'seriesAutoscale',
});
ctx.data[0].yaxis = 1;
});
});
it('should set min to 0.1 and add a tick for 0.1', function() {
var axisAutoscale = ctx.plotOptions.yaxes[0];
expect(axisAutoscale.transform(100)).toBe(2);
expect(axisAutoscale.inverseTransform(-3)).toBeCloseTo(0.001);
expect(axisAutoscale.min).toBe(0.1);
expect(axisAutoscale.max).toBe(10000);
expect(axisAutoscale.ticks.length).toBe(6);
expect(axisAutoscale.ticks[0]).toBe(0.1);
expect(axisAutoscale.ticks[5]).toBe(10000);
});
});
describe('when logBase is log 2 and y-min is set to 0 and num of ticks exceeds max', () => {
beforeEach(() => {
setupCtx(() => {
const heightForApprox5Ticks = 125;
ctrl.height = heightForApprox5Ticks;
ctrl.panel.yaxes[0].logBase = 2;
ctrl.panel.yaxes[0].min = '0';
ctx.data[0] = new TimeSeries({
datapoints: [[2000, 1], [4, 2], [500, 3], [3000, 4], [10000, 5], [100000, 6]],
alias: 'seriesAutoscale',
});
ctx.data[0].yaxis = 1;
});
});
it('should regenerate ticks so that if fits on the y-axis', function() {
var axisAutoscale = ctx.plotOptions.yaxes[0];
expect(axisAutoscale.min).toBe(0.1);
expect(axisAutoscale.ticks.length).toBe(8);
expect(axisAutoscale.ticks[0]).toBe(0.1);
expect(axisAutoscale.ticks[7]).toBe(262144);
expect(axisAutoscale.max).toBe(262144);
});
it('should set axis max to be max tick value', function() {
expect(ctx.plotOptions.yaxes[0].max).toBe(262144);
});
});
describe('dashed lines options', () => {
beforeEach(() => {
setupCtx(() => {
ctrl.panel.lines = true;
ctrl.panel.linewidth = 2;
ctrl.panel.dashes = true;
});
});
it('should configure dashed plot with correct options', function() {
expect(ctx.plotOptions.series.lines.show).toBe(true);
expect(ctx.plotOptions.series.dashes.lineWidth).toBe(2);
expect(ctx.plotOptions.series.dashes.show).toBe(true);
});
});
describe('should use timeStep for barWidth', () => {
beforeEach(() => {
setupCtx(() => {
ctrl.panel.bars = true;
ctx.data[0] = new TimeSeries({
datapoints: [[1, 10], [2, 20]],
alias: 'series1',
});
});
});
it('should set barWidth', function() {
expect(ctx.plotOptions.series.bars.barWidth).toBe(1 / 1.5);
});
});
describe('series option overrides, fill & points', () => {
beforeEach(() => {
setupCtx(() => {
ctrl.panel.lines = true;
ctrl.panel.fill = 5;
ctx.data[0].zindex = 10;
ctx.data[1].alias = 'test';
ctx.data[1].lines = { fill: 0.001 };
ctx.data[1].points = { show: true };
});
});
it('should match second series and fill zero, and enable points', function() {
expect(ctx.plotOptions.series.lines.fill).toBe(0.5);
expect(ctx.plotData[1].lines.fill).toBe(0.001);
expect(ctx.plotData[1].points.show).toBe(true);
});
});
describe('should order series order according to zindex', () => {
beforeEach(() => {
setupCtx(() => {
ctx.data[1].zindex = 1;
ctx.data[0].zindex = 10;
});
});
it('should move zindex 2 last', function() {
expect(ctx.plotData[0].alias).toBe('series2');
expect(ctx.plotData[1].alias).toBe('series1');
});
});
describe('when series is hidden', () => {
beforeEach(() => {
setupCtx(() => {
ctrl.hiddenSeries = { series2: true };
});
});
it('should remove datapoints and disable stack', function() {
expect(ctx.plotData[0].alias).toBe('series1');
expect(ctx.plotData[1].data.length).toBe(0);
expect(ctx.plotData[1].stack).toBe(false);
});
});
describe('when stack and percent', () => {
beforeEach(() => {
setupCtx(() => {
ctrl.panel.percentage = true;
ctrl.panel.stack = true;
});
});
it('should show percentage', function() {
var axis = ctx.plotOptions.yaxes[0];
expect(axis.tickFormatter(100, axis)).toBe('100%');
});
});
describe('when panel too narrow to show x-axis dates in same granularity as wide panels', () => {
//Set width to 10px
describe('and the range is less than 24 hours', function() {
beforeEach(() => {
setupCtx(() => {
ctrl.range.from = moment([2015, 1, 1, 10]);
ctrl.range.to = moment([2015, 1, 1, 22]);
});
});
it('should format dates as hours minutes', function() {
var axis = ctx.plotOptions.xaxis;
expect(axis.timeformat).toBe('%H:%M');
});
});
describe('and the range is less than one year', function() {
beforeEach(() => {
setupCtx(() => {
ctrl.range.from = moment([2015, 1, 1]);
ctrl.range.to = moment([2015, 11, 20]);
});
});
it('should format dates as month days', function() {
var axis = ctx.plotOptions.xaxis;
expect(axis.timeformat).toBe('%m/%d');
});
});
});
describe('when graph is histogram, and enable stack', () => {
beforeEach(() => {
setupCtx(() => {
ctrl.panel.xaxis.mode = 'histogram';
ctrl.panel.stack = true;
ctrl.hiddenSeries = {};
ctx.data[0] = new TimeSeries({
datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
alias: 'series1',
});
ctx.data[1] = new TimeSeries({
datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
alias: 'series2',
});
});
});
it('should calculate correct histogram', function() {
expect(ctx.plotData[0].data[0][0]).toBe(100);
expect(ctx.plotData[0].data[0][1]).toBe(2);
expect(ctx.plotData[1].data[0][0]).toBe(100);
expect(ctx.plotData[1].data[0][1]).toBe(2);
});
});
describe('when graph is histogram, and some series are hidden', () => {
beforeEach(() => {
setupCtx(() => {
ctrl.panel.xaxis.mode = 'histogram';
ctrl.panel.stack = false;
ctrl.hiddenSeries = { series2: true };
ctx.data[0] = new TimeSeries({
datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
alias: 'series1',
});
ctx.data[1] = new TimeSeries({
datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
alias: 'series2',
});
});
});
it('should calculate correct histogram', function() {
expect(ctx.plotData[0].data[0][0]).toBe(100);
expect(ctx.plotData[0].data[0][1]).toBe(2);
});
});
});
@@ -1,454 +0,0 @@
import { describe, beforeEach, it, sinon, expect, angularMocks } from '../../../../../test/lib/common';
import '../module';
import angular from 'angular';
import $ from 'jquery';
import helpers from 'test/specs/helpers';
import TimeSeries from 'app/core/time_series2';
import moment from 'moment';
import { Emitter } from 'app/core/core';
describe('grafanaGraph', function() {
beforeEach(angularMocks.module('grafana.core'));
function graphScenario(desc, func, elementWidth = 500) {
describe(desc, () => {
var ctx: any = {};
ctx.setup = setupFunc => {
beforeEach(
angularMocks.module($provide => {
$provide.value('timeSrv', new helpers.TimeSrvStub());
})
);
beforeEach(
angularMocks.inject(($rootScope, $compile) => {
var ctrl: any = {
height: 200,
panel: {
events: new Emitter(),
legend: {},
grid: {},
yaxes: [
{
min: null,
max: null,
format: 'short',
logBase: 1,
},
{
min: null,
max: null,
format: 'short',
logBase: 1,
},
],
thresholds: [],
xaxis: {},
seriesOverrides: [],
tooltip: {
shared: true,
},
},
renderingCompleted: sinon.spy(),
hiddenSeries: {},
dashboard: {
getTimezone: sinon.stub().returns('browser'),
},
range: {
from: moment([2015, 1, 1, 10]),
to: moment([2015, 1, 1, 22]),
},
};
var scope = $rootScope.$new();
scope.ctrl = ctrl;
scope.ctrl.events = ctrl.panel.events;
$rootScope.onAppEvent = sinon.spy();
ctx.data = [];
ctx.data.push(
new TimeSeries({
datapoints: [[1, 1], [2, 2]],
alias: 'series1',
})
);
ctx.data.push(
new TimeSeries({
datapoints: [[10, 1], [20, 2]],
alias: 'series2',
})
);
setupFunc(ctrl, ctx.data);
var element = angular.element("<div style='width:" + elementWidth + "px' grafana-graph><div>");
$compile(element)(scope);
scope.$digest();
$.plot = ctx.plotSpy = sinon.spy();
ctrl.events.emit('render', ctx.data);
ctrl.events.emit('render-legend');
ctrl.events.emit('legend-rendering-complete');
ctx.plotData = ctx.plotSpy.getCall(0).args[1];
ctx.plotOptions = ctx.plotSpy.getCall(0).args[2];
})
);
};
func(ctx);
});
}
graphScenario('simple lines options', ctx => {
ctx.setup(ctrl => {
ctrl.panel.lines = true;
ctrl.panel.fill = 5;
ctrl.panel.linewidth = 3;
ctrl.panel.steppedLine = true;
});
it('should configure plot with correct options', () => {
expect(ctx.plotOptions.series.lines.show).to.be(true);
expect(ctx.plotOptions.series.lines.fill).to.be(0.5);
expect(ctx.plotOptions.series.lines.lineWidth).to.be(3);
expect(ctx.plotOptions.series.lines.steps).to.be(true);
});
});
graphScenario('sorting stacked series as legend. disabled', ctx => {
ctx.setup(ctrl => {
ctrl.panel.legend.sort = undefined;
ctrl.panel.stack = false;
});
it('should not modify order of time series', () => {
expect(ctx.plotData[0].alias).to.be('series1');
expect(ctx.plotData[1].alias).to.be('series2');
});
});
graphScenario('sorting stacked series as legend. min descending order', ctx => {
ctx.setup(ctrl => {
ctrl.panel.legend.sort = 'min';
ctrl.panel.legend.sortDesc = true;
ctrl.panel.stack = true;
});
it('highest value should be first', () => {
expect(ctx.plotData[0].alias).to.be('series2');
expect(ctx.plotData[1].alias).to.be('series1');
});
});
graphScenario('sorting stacked series as legend. min ascending order', ctx => {
ctx.setup((ctrl, data) => {
ctrl.panel.legend.sort = 'min';
ctrl.panel.legend.sortDesc = false;
ctrl.panel.stack = true;
});
it('lowest value should be first', () => {
expect(ctx.plotData[0].alias).to.be('series1');
expect(ctx.plotData[1].alias).to.be('series2');
});
});
graphScenario('sorting stacked series as legend. stacking disabled', ctx => {
ctx.setup(ctrl => {
ctrl.panel.legend.sort = 'min';
ctrl.panel.legend.sortDesc = true;
ctrl.panel.stack = false;
});
it('highest value should be first', () => {
expect(ctx.plotData[0].alias).to.be('series1');
expect(ctx.plotData[1].alias).to.be('series2');
});
});
graphScenario('sorting stacked series as legend. current descending order', ctx => {
ctx.setup(ctrl => {
ctrl.panel.legend.sort = 'current';
ctrl.panel.legend.sortDesc = true;
ctrl.panel.stack = true;
});
it('highest last value should be first', () => {
expect(ctx.plotData[0].alias).to.be('series2');
expect(ctx.plotData[1].alias).to.be('series1');
});
});
graphScenario('when logBase is log 10', function(ctx) {
ctx.setup(function(ctrl, data) {
ctrl.panel.yaxes[0].logBase = 10;
data[0] = new TimeSeries({
datapoints: [[2000, 1], [0.002, 2], [0, 3], [-1, 4]],
alias: 'seriesAutoscale',
});
data[0].yaxis = 1;
ctrl.panel.yaxes[1].logBase = 10;
ctrl.panel.yaxes[1].min = '0.05';
ctrl.panel.yaxes[1].max = '1500';
data[1] = new TimeSeries({
datapoints: [[2000, 1], [0.002, 2], [0, 3], [-1, 4]],
alias: 'seriesFixedscale',
});
data[1].yaxis = 2;
});
it('should apply axis transform, autoscaling (if necessary) and ticks', function() {
var axisAutoscale = ctx.plotOptions.yaxes[0];
expect(axisAutoscale.transform(100)).to.be(2);
expect(axisAutoscale.inverseTransform(-3)).to.within(0.00099999999, 0.00100000001);
expect(axisAutoscale.min).to.within(0.00099999999, 0.00100000001);
expect(axisAutoscale.max).to.be(10000);
expect(axisAutoscale.ticks.length).to.within(7, 8);
expect(axisAutoscale.ticks[0]).to.within(0.00099999999, 0.00100000001);
if (axisAutoscale.ticks.length === 7) {
expect(axisAutoscale.ticks[axisAutoscale.ticks.length - 1]).to.within(999.9999, 1000.0001);
} else {
expect(axisAutoscale.ticks[axisAutoscale.ticks.length - 1]).to.be(10000);
}
var axisFixedscale = ctx.plotOptions.yaxes[1];
expect(axisFixedscale.min).to.be(0.05);
expect(axisFixedscale.max).to.be(1500);
expect(axisFixedscale.ticks.length).to.be(5);
expect(axisFixedscale.ticks[0]).to.be(0.1);
expect(axisFixedscale.ticks[4]).to.be(1000);
});
});
graphScenario('when logBase is log 10 and data points contain only zeroes', function(ctx) {
ctx.setup(function(ctrl, data) {
ctrl.panel.yaxes[0].logBase = 10;
data[0] = new TimeSeries({
datapoints: [[0, 1], [0, 2], [0, 3], [0, 4]],
alias: 'seriesAutoscale',
});
data[0].yaxis = 1;
});
it('should not set min and max and should create some fake ticks', function() {
var axisAutoscale = ctx.plotOptions.yaxes[0];
expect(axisAutoscale.transform(100)).to.be(2);
expect(axisAutoscale.inverseTransform(-3)).to.within(0.00099999999, 0.00100000001);
expect(axisAutoscale.min).to.be(undefined);
expect(axisAutoscale.max).to.be(undefined);
expect(axisAutoscale.ticks.length).to.be(2);
expect(axisAutoscale.ticks[0]).to.be(1);
expect(axisAutoscale.ticks[1]).to.be(2);
});
});
// y-min set 0 is a special case for log scale,
// this approximates it by setting min to 0.1
graphScenario('when logBase is log 10 and y-min is set to 0 and auto min is > 0.1', function(ctx) {
ctx.setup(function(ctrl, data) {
ctrl.panel.yaxes[0].logBase = 10;
ctrl.panel.yaxes[0].min = '0';
data[0] = new TimeSeries({
datapoints: [[2000, 1], [4, 2], [500, 3], [3000, 4]],
alias: 'seriesAutoscale',
});
data[0].yaxis = 1;
});
it('should set min to 0.1 and add a tick for 0.1', function() {
var axisAutoscale = ctx.plotOptions.yaxes[0];
expect(axisAutoscale.transform(100)).to.be(2);
expect(axisAutoscale.inverseTransform(-3)).to.within(0.00099999999, 0.00100000001);
expect(axisAutoscale.min).to.be(0.1);
expect(axisAutoscale.max).to.be(10000);
expect(axisAutoscale.ticks.length).to.be(6);
expect(axisAutoscale.ticks[0]).to.be(0.1);
expect(axisAutoscale.ticks[5]).to.be(10000);
});
});
graphScenario('when logBase is log 2 and y-min is set to 0 and num of ticks exceeds max', function(ctx) {
ctx.setup(function(ctrl, data) {
const heightForApprox5Ticks = 125;
ctrl.height = heightForApprox5Ticks;
ctrl.panel.yaxes[0].logBase = 2;
ctrl.panel.yaxes[0].min = '0';
data[0] = new TimeSeries({
datapoints: [[2000, 1], [4, 2], [500, 3], [3000, 4], [10000, 5], [100000, 6]],
alias: 'seriesAutoscale',
});
data[0].yaxis = 1;
});
it('should regenerate ticks so that if fits on the y-axis', function() {
var axisAutoscale = ctx.plotOptions.yaxes[0];
expect(axisAutoscale.min).to.be(0.1);
expect(axisAutoscale.ticks.length).to.be(8);
expect(axisAutoscale.ticks[0]).to.be(0.1);
expect(axisAutoscale.ticks[7]).to.be(262144);
expect(axisAutoscale.max).to.be(262144);
});
it('should set axis max to be max tick value', function() {
expect(ctx.plotOptions.yaxes[0].max).to.be(262144);
});
});
graphScenario('dashed lines options', function(ctx) {
ctx.setup(function(ctrl) {
ctrl.panel.lines = true;
ctrl.panel.linewidth = 2;
ctrl.panel.dashes = true;
});
it('should configure dashed plot with correct options', function() {
expect(ctx.plotOptions.series.lines.show).to.be(true);
expect(ctx.plotOptions.series.dashes.lineWidth).to.be(2);
expect(ctx.plotOptions.series.dashes.show).to.be(true);
});
});
graphScenario('should use timeStep for barWidth', function(ctx) {
ctx.setup(function(ctrl, data) {
ctrl.panel.bars = true;
data[0] = new TimeSeries({
datapoints: [[1, 10], [2, 20]],
alias: 'series1',
});
});
it('should set barWidth', function() {
expect(ctx.plotOptions.series.bars.barWidth).to.be(1 / 1.5);
});
});
graphScenario('series option overrides, fill & points', function(ctx) {
ctx.setup(function(ctrl, data) {
ctrl.panel.lines = true;
ctrl.panel.fill = 5;
data[0].zindex = 10;
data[1].alias = 'test';
data[1].lines = { fill: 0.001 };
data[1].points = { show: true };
});
it('should match second series and fill zero, and enable points', function() {
expect(ctx.plotOptions.series.lines.fill).to.be(0.5);
expect(ctx.plotData[1].lines.fill).to.be(0.001);
expect(ctx.plotData[1].points.show).to.be(true);
});
});
graphScenario('should order series order according to zindex', function(ctx) {
ctx.setup(function(ctrl, data) {
data[1].zindex = 1;
data[0].zindex = 10;
});
it('should move zindex 2 last', function() {
expect(ctx.plotData[0].alias).to.be('series2');
expect(ctx.plotData[1].alias).to.be('series1');
});
});
graphScenario('when series is hidden', function(ctx) {
ctx.setup(function(ctrl) {
ctrl.hiddenSeries = { series2: true };
});
it('should remove datapoints and disable stack', function() {
expect(ctx.plotData[0].alias).to.be('series1');
expect(ctx.plotData[1].data.length).to.be(0);
expect(ctx.plotData[1].stack).to.be(false);
});
});
graphScenario('when stack and percent', function(ctx) {
ctx.setup(function(ctrl) {
ctrl.panel.percentage = true;
ctrl.panel.stack = true;
});
it('should show percentage', function() {
var axis = ctx.plotOptions.yaxes[0];
expect(axis.tickFormatter(100, axis)).to.be('100%');
});
});
graphScenario(
'when panel too narrow to show x-axis dates in same granularity as wide panels',
function(ctx) {
describe('and the range is less than 24 hours', function() {
ctx.setup(function(ctrl) {
ctrl.range.from = moment([2015, 1, 1, 10]);
ctrl.range.to = moment([2015, 1, 1, 22]);
});
it('should format dates as hours minutes', function() {
var axis = ctx.plotOptions.xaxis;
expect(axis.timeformat).to.be('%H:%M');
});
});
describe('and the range is less than one year', function() {
ctx.setup(function(scope) {
scope.range.from = moment([2015, 1, 1]);
scope.range.to = moment([2015, 11, 20]);
});
it('should format dates as month days', function() {
var axis = ctx.plotOptions.xaxis;
expect(axis.timeformat).to.be('%m/%d');
});
});
},
10
);
graphScenario('when graph is histogram, and enable stack', function(ctx) {
ctx.setup(function(ctrl, data) {
ctrl.panel.xaxis.mode = 'histogram';
ctrl.panel.stack = true;
ctrl.hiddenSeries = {};
data[0] = new TimeSeries({
datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
alias: 'series1',
});
data[1] = new TimeSeries({
datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
alias: 'series2',
});
});
it('should calculate correct histogram', function() {
expect(ctx.plotData[0].data[0][0]).to.be(100);
expect(ctx.plotData[0].data[0][1]).to.be(2);
expect(ctx.plotData[1].data[0][0]).to.be(100);
expect(ctx.plotData[1].data[0][1]).to.be(2);
});
});
graphScenario('when graph is histogram, and some series are hidden', function(ctx) {
ctx.setup(function(ctrl, data) {
ctrl.panel.xaxis.mode = 'histogram';
ctrl.panel.stack = false;
ctrl.hiddenSeries = { series2: true };
data[0] = new TimeSeries({
datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
alias: 'series1',
});
data[1] = new TimeSeries({
datapoints: [[100, 1], [100, 2], [200, 3], [300, 4]],
alias: 'series2',
});
});
it('should calculate correct histogram', function() {
expect(ctx.plotData[0].data[0][0]).to.be(100);
expect(ctx.plotData[0].data[0][1]).to.be(2);
});
});
});
File diff suppressed because it is too large Load Diff
@@ -1,320 +0,0 @@
import { describe, beforeEach, it, sinon, expect, angularMocks } from '../../../../../test/lib/common';
import '../module';
import angular from 'angular';
import $ from 'jquery';
import helpers from 'test/specs/helpers';
import TimeSeries from 'app/core/time_series2';
import moment from 'moment';
import { Emitter } from 'app/core/core';
import rendering from '../rendering';
import { convertToHeatMap, convertToCards, histogramToHeatmap, calculateBucketSize } from '../heatmap_data_converter';
describe('grafanaHeatmap', function() {
beforeEach(angularMocks.module('grafana.core'));
function heatmapScenario(desc, func, elementWidth = 500) {
describe(desc, function() {
var ctx: any = {};
ctx.setup = function(setupFunc) {
beforeEach(
angularMocks.module(function($provide) {
$provide.value('timeSrv', new helpers.TimeSrvStub());
})
);
beforeEach(
angularMocks.inject(function($rootScope, $compile) {
var ctrl: any = {
colorSchemes: [
{
name: 'Oranges',
value: 'interpolateOranges',
invert: 'dark',
},
{ name: 'Reds', value: 'interpolateReds', invert: 'dark' },
],
events: new Emitter(),
height: 200,
panel: {
heatmap: {},
cards: {
cardPadding: null,
cardRound: null,
},
color: {
mode: 'spectrum',
cardColor: '#b4ff00',
colorScale: 'linear',
exponent: 0.5,
colorScheme: 'interpolateOranges',
fillBackground: false,
},
legend: {
show: false,
},
xBucketSize: 1000,
xBucketNumber: null,
yBucketSize: 1,
yBucketNumber: null,
xAxis: {
show: true,
},
yAxis: {
show: true,
format: 'short',
decimals: null,
logBase: 1,
splitFactor: null,
min: null,
max: null,
removeZeroValues: false,
},
tooltip: {
show: true,
seriesStat: false,
showHistogram: false,
},
highlightCards: true,
},
renderingCompleted: sinon.spy(),
hiddenSeries: {},
dashboard: {
getTimezone: sinon.stub().returns('utc'),
},
range: {
from: moment.utc('01 Mar 2017 10:00:00', 'DD MMM YYYY HH:mm:ss'),
to: moment.utc('01 Mar 2017 11:00:00', 'DD MMM YYYY HH:mm:ss'),
},
};
var scope = $rootScope.$new();
scope.ctrl = ctrl;
ctx.series = [];
ctx.series.push(
new TimeSeries({
datapoints: [[1, 1422774000000], [2, 1422774060000]],
alias: 'series1',
})
);
ctx.series.push(
new TimeSeries({
datapoints: [[2, 1422774000000], [3, 1422774060000]],
alias: 'series2',
})
);
ctx.data = {
heatmapStats: {
min: 1,
max: 3,
minLog: 1,
},
xBucketSize: ctrl.panel.xBucketSize,
yBucketSize: ctrl.panel.yBucketSize,
};
setupFunc(ctrl, ctx);
let logBase = ctrl.panel.yAxis.logBase;
let bucketsData;
if (ctrl.panel.dataFormat === 'tsbuckets') {
bucketsData = histogramToHeatmap(ctx.series);
} else {
bucketsData = convertToHeatMap(ctx.series, ctx.data.yBucketSize, ctx.data.xBucketSize, logBase);
}
ctx.data.buckets = bucketsData;
let { cards, cardStats } = convertToCards(bucketsData);
ctx.data.cards = cards;
ctx.data.cardStats = cardStats;
let elemHtml = `
<div class="heatmap-wrapper">
<div class="heatmap-canvas-wrapper">
<div class="heatmap-panel" style='width:${elementWidth}px'></div>
</div>
</div>`;
var element = angular.element(elemHtml);
$compile(element)(scope);
scope.$digest();
ctrl.data = ctx.data;
ctx.element = element;
rendering(scope, $(element), [], ctrl);
ctrl.events.emit('render');
})
);
};
func(ctx);
});
}
heatmapScenario('default options', function(ctx) {
ctx.setup(function(ctrl) {
ctrl.panel.yAxis.logBase = 1;
});
it('should draw correct Y axis', function() {
var yTicks = getTicks(ctx.element, '.axis-y');
expect(yTicks).to.eql(['1', '2', '3']);
});
it('should draw correct X axis', function() {
var xTicks = getTicks(ctx.element, '.axis-x');
let expectedTicks = [
formatTime('01 Mar 2017 10:00:00'),
formatTime('01 Mar 2017 10:15:00'),
formatTime('01 Mar 2017 10:30:00'),
formatTime('01 Mar 2017 10:45:00'),
formatTime('01 Mar 2017 11:00:00'),
];
expect(xTicks).to.eql(expectedTicks);
});
});
heatmapScenario('when logBase is 2', function(ctx) {
ctx.setup(function(ctrl) {
ctrl.panel.yAxis.logBase = 2;
});
it('should draw correct Y axis', function() {
var yTicks = getTicks(ctx.element, '.axis-y');
expect(yTicks).to.eql(['1', '2', '4']);
});
});
heatmapScenario('when logBase is 10', function(ctx) {
ctx.setup(function(ctrl, ctx) {
ctrl.panel.yAxis.logBase = 10;
ctx.series.push(
new TimeSeries({
datapoints: [[10, 1422774000000], [20, 1422774060000]],
alias: 'series3',
})
);
ctx.data.heatmapStats.max = 20;
});
it('should draw correct Y axis', function() {
var yTicks = getTicks(ctx.element, '.axis-y');
expect(yTicks).to.eql(['1', '10', '100']);
});
});
heatmapScenario('when logBase is 32', function(ctx) {
ctx.setup(function(ctrl) {
ctrl.panel.yAxis.logBase = 32;
ctx.series.push(
new TimeSeries({
datapoints: [[10, 1422774000000], [100, 1422774060000]],
alias: 'series3',
})
);
ctx.data.heatmapStats.max = 100;
});
it('should draw correct Y axis', function() {
var yTicks = getTicks(ctx.element, '.axis-y');
expect(yTicks).to.eql(['1', '32', '1.0 K']);
});
});
heatmapScenario('when logBase is 1024', function(ctx) {
ctx.setup(function(ctrl) {
ctrl.panel.yAxis.logBase = 1024;
ctx.series.push(
new TimeSeries({
datapoints: [[2000, 1422774000000], [300000, 1422774060000]],
alias: 'series3',
})
);
ctx.data.heatmapStats.max = 300000;
});
it('should draw correct Y axis', function() {
var yTicks = getTicks(ctx.element, '.axis-y');
expect(yTicks).to.eql(['1', '1 K', '1.0 Mil']);
});
});
heatmapScenario('when Y axis format set to "none"', function(ctx) {
ctx.setup(function(ctrl) {
ctrl.panel.yAxis.logBase = 1;
ctrl.panel.yAxis.format = 'none';
ctx.data.heatmapStats.max = 10000;
});
it('should draw correct Y axis', function() {
var yTicks = getTicks(ctx.element, '.axis-y');
expect(yTicks).to.eql(['0', '2000', '4000', '6000', '8000', '10000', '12000']);
});
});
heatmapScenario('when Y axis format set to "second"', function(ctx) {
ctx.setup(function(ctrl) {
ctrl.panel.yAxis.logBase = 1;
ctrl.panel.yAxis.format = 's';
ctx.data.heatmapStats.max = 3600;
});
it('should draw correct Y axis', function() {
var yTicks = getTicks(ctx.element, '.axis-y');
expect(yTicks).to.eql(['0 ns', '17 min', '33 min', '50 min', '1.11 hour']);
});
});
heatmapScenario('when data format is Time series buckets', function(ctx) {
ctx.setup(function(ctrl, ctx) {
ctrl.panel.dataFormat = 'tsbuckets';
const series = [
{
alias: '1',
datapoints: [[1000, 1422774000000], [200000, 1422774060000]],
},
{
alias: '2',
datapoints: [[3000, 1422774000000], [400000, 1422774060000]],
},
{
alias: '3',
datapoints: [[2000, 1422774000000], [300000, 1422774060000]],
},
];
ctx.series = series.map(s => new TimeSeries(s));
ctx.data.tsBuckets = series.map(s => s.alias).concat('');
ctx.data.yBucketSize = 1;
let xBucketBoundSet = series[0].datapoints.map(dp => dp[1]);
ctx.data.xBucketSize = calculateBucketSize(xBucketBoundSet);
});
it('should draw correct Y axis', function() {
var yTicks = getTicks(ctx.element, '.axis-y');
expect(yTicks).to.eql(['1', '2', '3', '']);
});
});
});
function getTicks(element, axisSelector) {
return element
.find(axisSelector)
.find('text')
.map(function() {
return this.textContent;
})
.get();
}
function formatTime(timeStr) {
let format = 'HH:mm';
return moment.utc(timeStr, 'DD MMM YYYY HH:mm:ss').format(format);
}
+1 -1
View File
@@ -5,7 +5,7 @@
<table class="table-panel-table">
<thead>
<tr>
<th ng-repeat="col in ctrl.table.columns" ng-hide="col.hidden">
<th ng-repeat="col in ctrl.table.columns" ng-if="!col.hidden">
<div class="table-panel-table-header-inner pointer" ng-click="ctrl.toggleColumnSort(col, $index)">
{{col.title}}
<span class="table-panel-table-header-controls" ng-if="col.sort">
@@ -238,6 +238,10 @@ export class TableRenderer {
column.hidden = false;
}
if (column.hidden === true) {
return '';
}
if (column.style && column.style.preserveFormat) {
cellClasses.push('table-panel-cell-pre');
}
+2
View File
@@ -34,7 +34,9 @@ export class LoadDashboardCtrl {
const url = locationUtil.stripBaseFromUrl(result.meta.url);
if (url !== $location.path()) {
// replace url to not create additional history items and then return so that initDashboard below isn't executed multiple times.
$location.path(url).replace();
return;
}
}
+4 -4
View File
@@ -11,7 +11,7 @@
<base href="[[.AppSubUrl]]/" />
<link rel="stylesheet" href="public/build/grafana.[[ .Theme ]].css?v[[ .BuildVersion ]]">
<link rel="stylesheet" href="public/build/grafana.[[ .Theme ]].css?v[[ .BuildVersion ]]+[[ .BuildCommit ]]">
<link rel="icon" type="image/png" href="public/img/fav32.png">
<link rel="mask-icon" href="public/img/grafana_mask_icon.svg" color="#F05A28">
@@ -107,12 +107,12 @@
<iframe src="//www.googletagmanager.com/ns.html?id=[[.GoogleTagManagerId]]" height="0" width="0" style="display:none;visibility:hidden"></iframe>
</noscript>
<script>(function (w, d, s, l, i) {
w[l] = w[l] || []; w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' }); var f = d.getElementsByTagName(s)[0],
j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : ''; j.async = true; j.src = '//www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f);
w[l] = w[l] || []; w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' }); var f = d.getElementsByTagName(s)[0],
j = d.createElement(s), dl = l != 'dataLayer' ? '&l=' + l : ''; j.async = true; j.src = '//www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f);
})(window, document, 'script', 'dataLayer', '[[.GoogleTagManagerId]]');</script>
<!-- End Google Tag Manager -->
[[end]]
</body>
</html>
</html>
-4
View File
@@ -9,8 +9,6 @@ module.exports = function(grunt) {
]);
grunt.registerTask('test', [
'jscs',
'jshint',
'sasslint',
'exec:tslint',
"exec:jest",
@@ -19,8 +17,6 @@ module.exports = function(grunt) {
]);
grunt.registerTask('precommit', [
'jscs',
'jshint',
'sasslint',
'exec:tslint',
'no-only-tests'
-22
View File
@@ -1,22 +0,0 @@
module.exports = function(config) {
return {
src: [
'Gruntfile.js',
'<%= srcDir %>/app/**/*.js',
'<%= srcDir %>/plugin/**/*.js',
'!<%= srcDir %>/app/dashboards/*'
],
options: {
config: ".jscs.json",
},
};
};
/*
"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
"disallowLeftStickedOperators": ["?", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
"disallowRightStickedOperators": ["?", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
"requireRightStickedOperators": ["!"],
"requireLeftStickedOperators": [","],
*/
-20
View File
@@ -1,20 +0,0 @@
module.exports = function(config) {
return {
source: {
files: {
src: ['Gruntfile.js', '<%= srcDir %>/app/**/*.js'],
}
},
options: {
jshintrc: true,
reporter: require('jshint-stylish'),
ignores: [
'node_modules/*',
'dist/*',
'sample/*',
'<%= srcDir %>/vendor/*',
'<%= srcDir %>/app/dashboards/*'
]
}
};
};
-45
View File
@@ -1,45 +0,0 @@
module.exports = function(config) {
return {
// copy source to temp, we will minify in place for the dist build
everything_but_less_to_temp: {
cwd: '<%= srcDir %>',
expand: true,
src: ['**/*', '!**/*.less'],
dest: '<%= tempDir %>'
},
public_to_gen: {
cwd: '<%= srcDir %>',
expand: true,
src: ['**/*', '!**/*.less'],
dest: '<%= genDir %>'
},
node_modules: {
cwd: './node_modules',
expand: true,
src: [
'ace-builds/src-noconflict/**/*',
'eventemitter3/*.js',
'systemjs/dist/*.js',
'es6-promise/**/*',
'es6-shim/*.js',
'reflect-metadata/*.js',
'reflect-metadata/*.ts',
'reflect-metadata/*.d.ts',
'rxjs/**/*',
'tether/**/*',
'tether-drop/**/*',
'tether-drop/**/*',
'remarkable/dist/*',
'remarkable/dist/*',
'virtual-scroll/**/*',
'mousetrap/**/*',
'twemoji/2/twemoji.amd*',
'twemoji/2/svg/*.svg',
],
dest: '<%= srcDir %>/vendor/npm'
}
};
};
+28 -340
View File
@@ -414,10 +414,6 @@ JSONStream@^1.3.2:
jsonparse "^1.2.0"
through ">=2.2.7 <3"
JSV@^4.0.x:
version "4.0.2"
resolved "https://registry.yarnpkg.com/JSV/-/JSV-4.0.2.tgz#d077f6825571f82132f9dffaed587b4029feff57"
abab@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"
@@ -869,10 +865,6 @@ async-limiter@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
async@0.2.x, async@~0.2.6, async@~0.2.9:
version "0.2.10"
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
async@^1.4.0, async@^1.5.0, async@^1.5.2, async@~1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
@@ -883,6 +875,10 @@ async@^2.0.0, async@^2.1.4, async@^2.4.1, async@^2.6.0:
dependencies:
lodash "^4.17.10"
async@~0.2.6:
version "0.2.10"
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -1564,7 +1560,7 @@ babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26
lodash "^4.17.4"
to-fast-properties "^1.0.3"
babylon@^6.17.3, babylon@^6.18.0, babylon@^6.8.1:
babylon@^6.17.3, babylon@^6.18.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
@@ -1626,10 +1622,6 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"
beeper@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809"
better-assert@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522"
@@ -2109,7 +2101,7 @@ center-align@^0.1.1:
align-text "^0.1.3"
lazy-cache "^1.0.3"
chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3, chalk@~1.1.0, chalk@~1.1.1:
chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3, chalk@~1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
dependencies:
@@ -2315,7 +2307,7 @@ cli-table2@^0.2.0, cli-table2@~0.2.0:
optionalDependencies:
colors "^1.1.2"
cli-table@^0.3.1, cli-table@~0.3.1:
cli-table@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23"
dependencies:
@@ -2332,13 +2324,6 @@ cli-width@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
cli@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cli/-/cli-1.0.1.tgz#22817534f24bfa4950c34d532d48ecbc621b8c14"
dependencies:
exit "0.1.2"
glob "^7.1.1"
clipboard@^1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-1.7.1.tgz#360d6d6946e99a7a1fef395e42ba92b5e9b5a16b"
@@ -2490,10 +2475,6 @@ colors@0.5.x:
version "0.5.1"
resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774"
colors@0.6.x:
version "0.6.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc"
colors@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
@@ -2539,7 +2520,7 @@ commander@2.8.x:
dependencies:
graceful-readlink ">= 1.0.0"
commander@2.9.x, commander@~2.9.0:
commander@2.9.x:
version "2.9.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
dependencies:
@@ -2549,12 +2530,6 @@ commander@~2.13.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
comment-parser@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-0.3.2.tgz#3c03f0776b86a36dfd9a0a2c97c6307f332082fe"
dependencies:
readable-stream "^2.0.4"
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -2660,7 +2635,7 @@ connect@^3.6.0:
parseurl "~1.3.2"
utils-merge "1.0.1"
console-browserify@1.1.x, console-browserify@^1.1.0:
console-browserify@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
dependencies:
@@ -2978,14 +2953,6 @@ csstype@^2.2.0:
version "2.5.3"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.3.tgz#2504152e6e1cc59b32098b7f5d6a63f16294c1f7"
cst@^0.4.3:
version "0.4.10"
resolved "https://registry.yarnpkg.com/cst/-/cst-0.4.10.tgz#9c05c825290a762f0a85c0aabb8c0fe035ae8516"
dependencies:
babel-runtime "^6.9.2"
babylon "^6.8.1"
source-map-support "^0.4.0"
currently-unhandled@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
@@ -2996,10 +2963,6 @@ custom-event@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
cycle@1.0.x:
version "1.0.3"
resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
cyclist@~0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
@@ -3324,7 +3287,7 @@ dedent@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
deep-equal@*, deep-equal@^1.0.1:
deep-equal@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
@@ -3604,12 +3567,6 @@ domhandler@2.1:
dependencies:
domelementtype "1"
domhandler@2.3:
version "2.3.0"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738"
dependencies:
domelementtype "1"
domhandler@^2.3.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
@@ -3622,7 +3579,7 @@ domutils@1.1:
dependencies:
domelementtype "1"
domutils@1.5, domutils@1.5.1:
domutils@1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf"
dependencies:
@@ -3813,10 +3770,6 @@ ent@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d"
entities@1.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26"
entities@^1.1.1, entities@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
@@ -4181,7 +4134,7 @@ exit-hook@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
exit@0.1.2, exit@0.1.x, exit@^0.1.2, exit@~0.1.1, exit@~0.1.2:
exit@^0.1.2, exit@~0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
@@ -4362,10 +4315,6 @@ extsprintf@^1.2.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
eyes@0.1.x:
version "0.1.8"
resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
fast-deep-equal@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614"
@@ -4945,9 +4894,9 @@ glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glo
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^5.0.1, glob@~5.0.0:
version "5.0.15"
resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
glob@^6.0.4:
version "6.0.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
dependencies:
inflight "^1.0.4"
inherits "2"
@@ -4955,9 +4904,9 @@ glob@^5.0.1, glob@~5.0.0:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^6.0.4:
version "6.0.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
glob@~5.0.0:
version "5.0.15"
resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
dependencies:
inflight "^1.0.4"
inherits "2"
@@ -5199,27 +5148,10 @@ grunt-contrib-cssmin@~1.0.2:
clean-css "~3.4.2"
maxmin "^1.1.0"
grunt-contrib-jshint@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/grunt-contrib-jshint/-/grunt-contrib-jshint-1.1.0.tgz#369d909b2593c40e8be79940b21340850c7939ac"
dependencies:
chalk "^1.1.1"
hooker "^0.2.3"
jshint "~2.9.4"
grunt-exec@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/grunt-exec/-/grunt-exec-1.0.1.tgz#e5d53a39c5f346901305edee5c87db0f2af999c4"
grunt-jscs@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/grunt-jscs/-/grunt-jscs-3.0.1.tgz#1fae50e3e955df9e3a9d9425aec22accae008092"
dependencies:
hooker "~0.2.3"
jscs "~3.0.5"
lodash "~4.6.1"
vow "~0.4.1"
grunt-karma@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/grunt-karma/-/grunt-karma-2.0.0.tgz#753583d115dfdc055fe57e58f96d6b3c7e612118"
@@ -5530,7 +5462,7 @@ homedir-polyfill@^1.0.1:
dependencies:
parse-passwd "^1.0.0"
hooker@^0.2.3, hooker@~0.2.3:
hooker@~0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/hooker/-/hooker-0.2.3.tgz#b834f723cc4a242aa65963459df6d984c5d3d959"
@@ -5614,16 +5546,6 @@ html-webpack-plugin@^3.2.0:
toposort "^1.0.0"
util.promisify "1.0.0"
htmlparser2@3.8.3, htmlparser2@3.8.x:
version "3.8.3"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.8.3.tgz#996c28b191516a8be86501a7d79757e5c70c1068"
dependencies:
domelementtype "1"
domhandler "2.3"
domutils "1.5"
entities "1.0"
readable-stream "1.1"
htmlparser2@^3.9.1:
version "3.9.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
@@ -5739,10 +5661,6 @@ husky@^0.14.3:
normalize-path "^1.0.0"
strip-indent "^2.0.0"
i@0.3.x:
version "0.3.6"
resolved "https://registry.yarnpkg.com/i/-/i-0.3.6.tgz#d96c92732076f072711b6b10fd7d4f65ad8ee23d"
iconv-lite@0.4, iconv-lite@0.4.23, iconv-lite@^0.4.17, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
version "0.4.23"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
@@ -5838,10 +5756,6 @@ inflight@^1.0.4, inflight@~1.0.6:
once "^1.3.0"
wrappy "1"
inherit@^2.2.2:
version "2.2.6"
resolved "https://registry.yarnpkg.com/inherit/-/inherit-2.2.6.tgz#f1614b06c8544e8128e4229c86347db73ad9788d"
inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
@@ -5938,10 +5852,6 @@ ipaddr.js@1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b"
irregular-plurals@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.4.0.tgz#2ca9b033651111855412f16be5d77c62a458a766"
is-absolute-url@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
@@ -6348,7 +6258,7 @@ isomorphic-fetch@^2.1.1:
node-fetch "^1.0.1"
whatwg-fetch ">=0.10.0"
isstream@0.1.x, isstream@~0.1.2:
isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
@@ -6748,14 +6658,6 @@ js-yaml@^3.4.3, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.7.0,
argparse "^1.0.7"
esprima "^4.0.0"
js-yaml@~3.4.0:
version "3.4.6"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.4.6.tgz#6be1b23f6249f53d293370fd4d1aaa63ce1b4eb0"
dependencies:
argparse "^1.0.2"
esprima "^2.6.0"
inherit "^2.2.2"
js-yaml@~3.5.2:
version "3.5.5"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.5.5.tgz#0377c38017cabc7322b0d1fbcd25a491641f2fbe"
@@ -6814,54 +6716,6 @@ jscodeshift@^0.5.0:
temp "^0.8.1"
write-file-atomic "^1.2.0"
jscs-jsdoc@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/jscs-jsdoc/-/jscs-jsdoc-2.0.0.tgz#f53ebce029aa3125bd88290ba50d64d4510a4871"
dependencies:
comment-parser "^0.3.1"
jsdoctypeparser "~1.2.0"
jscs-preset-wikimedia@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/jscs-preset-wikimedia/-/jscs-preset-wikimedia-1.0.1.tgz#a6a5fa5967fd67a5d609038e1c794eaf41d4233d"
jscs@~3.0.5:
version "3.0.7"
resolved "https://registry.yarnpkg.com/jscs/-/jscs-3.0.7.tgz#7141b4dff5b86e32d0e99d764b836767c30d201a"
dependencies:
chalk "~1.1.0"
cli-table "~0.3.1"
commander "~2.9.0"
cst "^0.4.3"
estraverse "^4.1.0"
exit "~0.1.2"
glob "^5.0.1"
htmlparser2 "3.8.3"
js-yaml "~3.4.0"
jscs-jsdoc "^2.0.0"
jscs-preset-wikimedia "~1.0.0"
jsonlint "~1.6.2"
lodash "~3.10.0"
minimatch "~3.0.0"
natural-compare "~1.2.2"
pathval "~0.1.1"
prompt "~0.2.14"
reserved-words "^0.1.1"
resolve "^1.1.6"
strip-bom "^2.0.0"
strip-json-comments "~1.0.2"
to-double-quotes "^2.0.0"
to-single-quotes "^2.0.0"
vow "~0.4.8"
vow-fs "~0.3.4"
xmlbuilder "^3.1.0"
jsdoctypeparser@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/jsdoctypeparser/-/jsdoctypeparser-1.2.0.tgz#e7dedc153a11849ffc5141144ae86a7ef0c25392"
dependencies:
lodash "^3.7.0"
jsdom@^11.5.1:
version "11.11.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.11.0.tgz#df486efad41aee96c59ad7a190e2449c7eb1110e"
@@ -6901,30 +6755,6 @@ jsesc@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
jshint-stylish@~2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/jshint-stylish/-/jshint-stylish-2.2.1.tgz#242082a2c035ae03fd81044e0570cc4208cf6e61"
dependencies:
beeper "^1.1.0"
chalk "^1.0.0"
log-symbols "^1.0.0"
plur "^2.1.0"
string-length "^1.0.0"
text-table "^0.2.0"
jshint@~2.9.4:
version "2.9.5"
resolved "https://registry.yarnpkg.com/jshint/-/jshint-2.9.5.tgz#1e7252915ce681b40827ee14248c46d34e9aa62c"
dependencies:
cli "~1.0.0"
console-browserify "1.1.x"
exit "0.1.x"
htmlparser2 "3.8.x"
lodash "3.7.x"
minimatch "~3.0.2"
shelljs "0.3.x"
strip-json-comments "1.0.x"
json-buffer@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
@@ -6981,13 +6811,6 @@ jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
jsonlint@~1.6.2:
version "1.6.3"
resolved "https://registry.yarnpkg.com/jsonlint/-/jsonlint-1.6.3.tgz#cb5e31efc0b78291d0d862fbef05900adf212988"
dependencies:
JSV "^4.0.x"
nomnom "^1.5.x"
jsonparse@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
@@ -7497,11 +7320,7 @@ lodash.without@~4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac"
lodash@3.7.x:
version "3.7.0"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.7.0.tgz#3678bd8ab995057c07ade836ed2ef087da811d45"
lodash@^3.10.1, lodash@^3.5.0, lodash@^3.6.0, lodash@^3.7.0, lodash@^3.8.0, lodash@~3.10.0:
lodash@^3.10.1, lodash@^3.6.0, lodash@^3.8.0:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
@@ -7513,11 +7332,7 @@ lodash@~4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.3.0.tgz#efd9c4a6ec53f3b05412429915c3e4824e4d25a4"
lodash@~4.6.1:
version "4.6.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.6.1.tgz#df00c1164ad236b183cfc3887a5e8d38cc63cbbc"
log-symbols@^1.0.0, log-symbols@^1.0.2:
log-symbols@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
dependencies:
@@ -7990,7 +7805,7 @@ mixin-object@^2.0.1:
for-in "^0.1.3"
is-extendable "^0.1.1"
mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@0.x.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
dependencies:
@@ -8121,20 +7936,12 @@ natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
natural-compare@~1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.2.2.tgz#1f96d60e3141cac1b6d05653ce0daeac763af6aa"
ncname@1.0.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/ncname/-/ncname-1.0.0.tgz#5b57ad18b1ca092864ef62b0b1ed8194f383b71c"
dependencies:
xml-char-classes "^1.0.0"
ncp@0.4.x:
version "0.4.2"
resolved "https://registry.yarnpkg.com/ncp/-/ncp-0.4.2.tgz#abcc6cbd3ec2ed2a729ff6e7c1fa8f01784a8574"
nearley@^2.7.10:
version "2.13.0"
resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.13.0.tgz#6e7b0f4e68bfc3e74c99eaef2eda39e513143439"
@@ -8359,7 +8166,7 @@ node-sass@^4.7.2:
stdout-stream "^1.4.0"
"true-case-path" "^1.0.2"
nomnom@^1.5.x, nomnom@^1.8.1:
nomnom@^1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.8.1.tgz#2151f722472ba79e50a76fc125bb8c8f2e4dc2a7"
dependencies:
@@ -9207,10 +9014,6 @@ path-type@^3.0.0:
dependencies:
pify "^3.0.0"
pathval@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/pathval/-/pathval-0.1.1.tgz#08f911cdca9cce5942880da7817bc0b723b66d82"
pbkdf2@^3.0.3:
version "3.0.16"
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.16.tgz#7404208ec6b01b62d85bf83853a8064f8d9c2a5c"
@@ -9273,20 +9076,6 @@ pkg-up@^1.0.0:
dependencies:
find-up "^1.0.0"
pkginfo@0.3.x:
version "0.3.1"
resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21"
pkginfo@0.x.x:
version "0.4.1"
resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff"
plur@^2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/plur/-/plur-2.1.2.tgz#7482452c1a0f508e3e344eaec312c91c29dc655a"
dependencies:
irregular-plurals "^1.0.0"
pluralize@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"
@@ -9810,16 +9599,6 @@ promise@^7.1.1:
dependencies:
asap "~2.0.3"
prompt@~0.2.14:
version "0.2.14"
resolved "https://registry.yarnpkg.com/prompt/-/prompt-0.2.14.tgz#57754f64f543fd7b0845707c818ece618f05ffdc"
dependencies:
pkginfo "0.x.x"
read "1.0.x"
revalidator "0.1.x"
utile "0.2.x"
winston "0.8.x"
promzard@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee"
@@ -10304,7 +10083,7 @@ read-pkg@^3.0.0:
normalize-package-data "^2.3.2"
path-type "^3.0.0"
read@1, read@1.0.x, read@~1.0.1, read@~1.0.7:
read@1, read@~1.0.1, read@~1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4"
dependencies:
@@ -10331,15 +10110,6 @@ readable-stream@1.0, readable-stream@~1.0.2:
isarray "0.0.1"
string_decoder "~0.10.x"
readable-stream@1.1:
version "1.1.13"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e"
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.1"
isarray "0.0.1"
string_decoder "~0.10.x"
readable-stream@~1.1.10:
version "1.1.14"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
@@ -10660,10 +10430,6 @@ requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
reserved-words@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1"
resolve-cwd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@@ -10754,17 +10520,13 @@ retry@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
revalidator@0.1.x:
version "0.1.8"
resolved "https://registry.yarnpkg.com/revalidator/-/revalidator-0.1.8.tgz#fece61bfa0c1b52a206bd6b18198184bdd523a3b"
right-align@^0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
dependencies:
align-text "^0.1.1"
rimraf@2, rimraf@2.x.x, rimraf@^2.2.8, rimraf@^2.4.4, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@~2.6.2:
rimraf@2, rimraf@^2.2.8, rimraf@^2.4.4, rimraf@^2.5.1, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@~2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
dependencies:
@@ -11116,10 +10878,6 @@ shell-quote@^1.6.1:
array-reduce "~0.0.0"
jsonify "~0.0.0"
shelljs@0.3.x:
version "0.3.0"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.3.0.tgz#3596e6307a781544f591f37da618360f31db57b1"
shelljs@^0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.6.1.tgz#ec6211bed1920442088fe0f70b2837232ed2c8a8"
@@ -11432,7 +11190,7 @@ source-map-resolve@^0.5.0:
source-map-url "^0.4.0"
urix "^0.1.0"
source-map-support@^0.4.0, source-map-support@^0.4.15:
source-map-support@^0.4.15:
version "0.4.18"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"
dependencies:
@@ -11555,10 +11313,6 @@ stack-parser@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/stack-parser/-/stack-parser-0.0.1.tgz#7d3b63a17887e9e2c2bf55dbd3318fe34a39d1e7"
stack-trace@0.0.x:
version "0.0.10"
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
stack-utils@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620"
@@ -11649,12 +11403,6 @@ strict-uri-encode@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
string-length@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-1.0.1.tgz#56970fb1c38558e9e70b728bf3de269ac45adfac"
dependencies:
strip-ansi "^3.0.0"
string-length@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
@@ -11766,7 +11514,7 @@ strip-indent@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
strip-json-comments@1.0.x, strip-json-comments@~1.0.1, strip-json-comments@~1.0.2:
strip-json-comments@~1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
@@ -12029,10 +11777,6 @@ to-buffer@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
to-double-quotes@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-double-quotes/-/to-double-quotes-2.0.0.tgz#aaf231d6fa948949f819301bbab4484d8588e4a7"
to-fast-properties@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
@@ -12059,10 +11803,6 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2"
safe-regex "^1.1.0"
to-single-quotes@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/to-single-quotes/-/to-single-quotes-2.0.1.tgz#7cc29151f0f5f2c41946f119f5932fe554170125"
toposort@^1.0.0:
version "1.0.7"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
@@ -12527,25 +12267,10 @@ utila@~0.4:
version "0.4.0"
resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
utile@0.2.x:
version "0.2.1"
resolved "https://registry.yarnpkg.com/utile/-/utile-0.2.1.tgz#930c88e99098d6220834c356cbd9a770522d90d7"
dependencies:
async "~0.2.9"
deep-equal "*"
i "0.3.x"
mkdirp "0.x.x"
ncp "0.4.x"
rimraf "2.x.x"
utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
uuid@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a"
uuid@^3.0.0, uuid@^3.0.1, uuid@^3.1.0, uuid@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14"
@@ -12623,25 +12348,6 @@ void-elements@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
vow-fs@~0.3.4:
version "0.3.6"
resolved "https://registry.yarnpkg.com/vow-fs/-/vow-fs-0.3.6.tgz#2d4c59be22e2bf2618ddf597ab4baa923be7200d"
dependencies:
glob "^7.0.5"
uuid "^2.0.2"
vow "^0.4.7"
vow-queue "^0.4.1"
vow-queue@^0.4.1:
version "0.4.3"
resolved "https://registry.yarnpkg.com/vow-queue/-/vow-queue-0.4.3.tgz#4ba8f64b56e9212c0dbe57f1405aeebd54cce78d"
dependencies:
vow "^0.4.17"
vow@^0.4.17, vow@^0.4.7, vow@~0.4.1, vow@~0.4.8:
version "0.4.17"
resolved "https://registry.yarnpkg.com/vow/-/vow-0.4.17.tgz#b16e08fae58c52f3ebc6875f2441b26a92682904"
vue-parser@^1.1.5:
version "1.1.6"
resolved "https://registry.yarnpkg.com/vue-parser/-/vue-parser-1.1.6.tgz#3063c8431795664ebe429c23b5506899706e6355"
@@ -12960,18 +12666,6 @@ window-size@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
winston@0.8.x:
version "0.8.3"
resolved "https://registry.yarnpkg.com/winston/-/winston-0.8.3.tgz#64b6abf4cd01adcaefd5009393b1d8e8bec19db0"
dependencies:
async "0.2.x"
colors "0.6.x"
cycle "1.0.x"
eyes "0.1.x"
isstream "0.1.x"
pkginfo "0.3.x"
stack-trace "0.0.x"
wordwrap@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
@@ -13053,12 +12747,6 @@ xml-name-validator@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
xmlbuilder@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-3.1.0.tgz#2c86888f2d4eade850fa38ca7f7223f7209516e1"
dependencies:
lodash "^3.5.0"
xmlhttprequest-ssl@1.5.3:
version "1.5.3"
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d"