Merge branch 'master' into goodbye-bootstrap

pull/10616/head
Luke Morris 2018-03-01 12:20:24 -08:00
commit b02a79f0e4
143 changed files with 6535 additions and 3796 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.4.1.3
current_version = 1.4.2.1
files = README.md server/swagger.json
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.(?P<release>\d+)
serialize = {major}.{minor}.{patch}.{release}

View File

@ -1,8 +1,15 @@
## v1.4.2.0 [unreleased]
## v1.4.3.0 [unreleased]
### Features
### UI Improvements
### Bug Fixes
## v1.4.2.1 [2018-02-28]
### Features
1. [#2837](https://github.com/influxdata/chronograf/pull/2837): Prevent execution of queries in cells that are not in view on the dashboard page
1. [#2837] (https://github.com/influxdata/chronograf/pull/2837): Prevent execution of queries in cells that are not in view on the dashboard page
1. [#2829] (https://github.com/influxdata/chronograf/pull/2829): Add an optional persistent legend which can toggle series visibility to dashboard cells
1. [#2829](https://github.com/influxdata/chronograf/pull/2829): Add an optional persistent legend which can toggle series visibility to dashboard cells
1. [#2846](https://github.com/influxdata/chronograf/pull/2846): Allow user to annotate graphs via UI or API
### UI Improvements
1. [#2848](https://github.com/influxdata/chronograf/pull/2848): Add ability to set a prefix and suffix on Single Stat and Gauge cell types
1. [#2831](https://github.com/influxdata/chronograf/pull/2831): Rename 'Create Alerts' page to 'Manage Tasks'; Redesign page to improve clarity of purpose
@ -11,6 +18,7 @@
1. [#2821](https://github.com/influxdata/chronograf/pull/2821): Save only selected template variable values into dashboards for non csv template variables
1. [#2842](https://github.com/influxdata/chronograf/pull/2842): Use Generic APIKey for Oauth2 group lookup
1. [#2850](https://github.com/influxdata/chronograf/pull/2850): Fix bug in which resizing any cell in a dashboard causes a Gauge cell to resize
1. [#2886](https://github.com/influxdata/chronograf/pull/2886): Don't sort Single Stat & Gauge thresholds when editing threshold values
1. [#2851](https://github.com/influxdata/chronograf/pull/2851): Maintain y axis labels in dashboard cells
1. [#2819](https://github.com/influxdata/chronograf/pull/2819): Deprecate --new-sources in CLI

View File

@ -136,7 +136,7 @@ option.
## Versions
The most recent version of Chronograf is
[v1.4.1.3](https://www.influxdata.com/downloads/).
[v1.4.2.1](https://www.influxdata.com/downloads/).
Spotted a bug or have a feature request? Please open
[an issue](https://github.com/influxdata/chronograf/issues/new)!
@ -178,7 +178,7 @@ By default, chronograf runs on port `8888`.
To get started right away with Docker, you can pull down our latest release:
```sh
docker pull chronograf:1.4.1.3
docker pull chronograf:1.4.2.1
```
### From Source

View File

@ -12,10 +12,6 @@ import (
// SortTemplates the templates by size, then type, then value.
func SortTemplates(ts []chronograf.TemplateVar) []chronograf.TemplateVar {
sort.Slice(ts, func(i, j int) bool {
if ts[i].Var == ":interval:" {
return false
}
if len(ts[i].Values) != len(ts[j].Values) {
return len(ts[i].Values) < len(ts[j].Values)
}
@ -63,20 +59,6 @@ func RenderTemplate(query string, t chronograf.TemplateVar, now time.Time) (stri
tv[t.Values[i].Type] = t.Values[i].Value
}
if pts, ok := tv["points"]; ok {
points, err := strconv.ParseInt(pts, 0, 64)
if err != nil {
return "", err
}
dur, err := ParseTime(query, now)
if err != nil {
return "", err
}
interval := AutoInterval(points, dur)
return strings.Replace(query, t.Var, interval, -1), nil
}
if res, ok := tv["resolution"]; ok {
resolution, err := strconv.ParseInt(res, 0, 64)
if err != nil {
@ -101,22 +83,6 @@ func RenderTemplate(query string, t chronograf.TemplateVar, now time.Time) (stri
return query, nil
}
func AutoInterval(points int64, duration time.Duration) string {
// The function is: ((total_seconds * millisecond_converstion) / group_by) = pixels / 3
// Number of points given the pixels
pixels := float64(points)
msPerPixel := float64(duration/time.Millisecond) / pixels
secPerPixel := float64(duration/time.Second) / pixels
if secPerPixel < 1.0 {
if msPerPixel < 1.0 {
msPerPixel = 1.0
}
return strconv.FormatInt(int64(msPerPixel), 10) + "ms"
}
// If groupby is more than 1 second round to the second
return strconv.FormatInt(int64(secPerPixel), 10) + "s"
}
// AutoGroupBy generates the time to group by in order to decimate the number of
// points returned in a query
func AutoGroupBy(resolution, pixelsPerPoint int64, duration time.Duration) string {

View File

@ -125,38 +125,6 @@ func TestTemplateReplace(t *testing.T) {
},
want: `SELECT :field: FROM "cpu"`,
},
{
name: "auto interval",
query: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(:interval:)`,
vars: []chronograf.TemplateVar{
{
Var: ":interval:",
Values: []chronograf.TemplateValue{
{
Value: "333",
Type: "points",
},
},
},
},
want: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(46702s)`,
},
{
name: "auto interval",
query: `SELECT derivative(mean(usage_idle),:interval:) from "cpu" where time > now() - 4320h group by time(:interval:)`,
vars: []chronograf.TemplateVar{
{
Var: ":interval:",
Values: []chronograf.TemplateValue{
{
Value: "333",
Type: "points",
},
},
},
},
want: `SELECT derivative(mean(usage_idle),46702s) from "cpu" where time > now() - 4320h group by time(46702s)`,
},
{
name: "auto group by",
query: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by :interval:`,
@ -165,7 +133,7 @@ func TestTemplateReplace(t *testing.T) {
Var: ":interval:",
Values: []chronograf.TemplateValue{
{
Value: "999",
Value: "1000",
Type: "resolution",
},
{
@ -175,7 +143,7 @@ func TestTemplateReplace(t *testing.T) {
},
},
},
want: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(46702s)`,
want: `SELECT mean(usage_idle) from "cpu" where time > now() - 4320h group by time(46655s)`,
},
{
name: "auto group by without duration",
@ -185,7 +153,7 @@ func TestTemplateReplace(t *testing.T) {
Var: ":interval:",
Values: []chronograf.TemplateValue{
{
Value: "999",
Value: "1000",
Type: "resolution",
},
{
@ -195,7 +163,7 @@ func TestTemplateReplace(t *testing.T) {
},
},
},
want: `SELECT mean(usage_idle) from "cpu" WHERE time > now() - 4320h group by time(46702s)`,
want: `SELECT mean(usage_idle) from "cpu" WHERE time > now() - 4320h group by time(46655s)`,
},
{
name: "auto group by with :dashboardTime:",

View File

@ -3,7 +3,7 @@
"info": {
"title": "Chronograf",
"description": "API endpoints for Chronograf",
"version": "1.4.1.3"
"version": "1.4.2.1"
},
"schemes": ["http"],
"basePath": "/chronograf/v1",

View File

@ -6,7 +6,7 @@
"transform-runtime",
"lodash"
],
"presets": ["es2015", "react", "stage-0"],
"presets": ["env", "react", "stage-0"],
"env": {
"production": {
"plugins": [

View File

@ -1,20 +1,26 @@
{
parser: 'babel-eslint',
"parser": "babel-eslint",
plugins: [
'react',
'prettier',
'babel',
'jest',
],
extends: [
"prettier",
"prettier/react"
],
env: {
browser: true,
mocha: true,
"jest": true,
node: true,
es6: true,
},
globals: {
expect: true,
},
"parserOptions": {
parserOptions: {
ecmaFeatures: {
arrowFunctions: true,
binaryLiterals: true,
@ -244,6 +250,10 @@
'semi': false,
}],
// jest
'jest/no-disabled-tests': "warn",
'jest/no-focused-tests': "error",
// Babel
'babel/no-invalid-this': 1
},

1
ui/.gitignore vendored
View File

@ -6,3 +6,4 @@ dist/
bower_components/
log/
.tern-project
yarn-error.log

16
ui/jest.config.js Normal file
View File

@ -0,0 +1,16 @@
module.exports = {
projects: [
{
displayName: 'test',
testPathIgnorePatterns: ['/build/'],
modulePaths: ['<rootDir>', '<rootDir>/node_modules/'],
moduleDirectories: ['src'],
setupFiles: ['<rootDir>/test/setupTests.js'],
},
{
runner: 'jest-runner-eslint',
displayName: 'lint',
testMatch: ['<rootDir>/test/**/*.test.js'],
},
],
}

View File

@ -1,11 +1,10 @@
var webpack = require('webpack')
var path = require('path')
const webpack = require('webpack')
const path = require('path')
module.exports = function(config) {
config.set({
browsers: ['PhantomJS'],
singleRun: true,
frameworks: ['mocha', 'sinon-chai'],
frameworks: ['mocha'],
files: [
'node_modules/babel-polyfill/dist/polyfill.js',
'spec/spec-helper.js',
@ -42,10 +41,6 @@ module.exports = function(config) {
test: /sinon\/pkg\/sinon\.js/,
loader: 'imports?define=>false,require=>false',
},
{
test: /\.json$/,
loader: 'json',
},
],
},
externals: {
@ -61,7 +56,6 @@ module.exports = function(config) {
shared: path.resolve(__dirname, 'src', 'shared'),
style: path.resolve(__dirname, 'src', 'style'),
utils: path.resolve(__dirname, 'src', 'utils'),
sinon: 'sinon/pkg/sinon',
},
},
},

View File

@ -1,37 +0,0 @@
{
"src_folders": ["tests"],
"output_folder": "reports",
"custom_commands_path": "",
"custom_assertions_path": "",
"page_objects_path": "",
"globals_path": "",
"selenium": {
"start_process": false,
"host": "hub-cloud.browserstack.com",
"port": 80
},
"live_output" : true,
"test_settings": {
"default": {
"selenium_port": 80,
"selenium_host": "hub-cloud.browserstack.com",
"silent": false,
"screenshots": {
"enabled": true,
"path": "screenshots"
},
"desiredCapabilities": {
"browser": "chrome",
"build": "nightwatch-browserstack",
"browserstack.user": "${BROWSERSTACK_USER}",
"browserstack.key": "${BROWSERSTACK_KEY}",
"browserstack.debug": true,
"browserstack.local": true,
"resolution": "1280x1024"
}
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "chronograf-ui",
"version": "1.4.1-3",
"version": "1.4.2-1",
"private": false,
"license": "AGPL-3.0",
"description": "",
@ -9,17 +9,19 @@
"url": "github:influxdata/chronograf"
},
"scripts": {
"build": "yarn run clean && env NODE_ENV=production webpack --optimize-minimize --config ./webpack/prodConfig.js",
"build:dev": "webpack --config ./webpack/devConfig.js",
"start": "yarn run clean && webpack --watch --config ./webpack/devConfig.js",
"start:hmr": "webpack-dev-server --open --config ./webpack/devConfig.js",
"build": "yarn run clean && webpack --config ./webpack/prod.config.js",
"build:dev": "webpack --config ./webpack/dev.config.js",
"build:vendor": "webpack --config webpack/vendor.config.js",
"start": "yarn run clean && yarn run build:vendor && webpack --watch --config ./webpack/dev.config.js",
"start:assets": "webpack --watch --config ./webpack/dev.config.js",
"start:hmr": "webpack-dev-server --open --config ./webpack/dev.config.js",
"lint": "esw src/",
"test": "karma start",
"test:integration": "nightwatch tests --skip",
"test": "jest --runInBand",
"test:lint": "yarn run lint; yarn run test",
"test:dev": "concurrently \"yarn run lint --watch\" \"yarn run test --no-single-run --reporters=verbose\"",
"clean": "rm -rf build/*",
"prettier": "prettier --single-quote --trailing-comma es5 --bracket-spacing false --semi false --write \"{src,spec}/**/*.js\"; eslint src --fix"
"test:watch": "jest --watch",
"clean": "rm -rf ./build/*",
"prettier": "prettier --single-quote --trailing-comma es5 --bracket-spacing false --semi false --write \"{src,spec}/**/*.js\"; eslint src --fix",
"eslint-check": "eslint --print-config .eslintrc | eslint-config-prettier-check"
},
"author": "",
"eslintConfig": {
@ -28,10 +30,16 @@
}
},
"devDependencies": {
"@types/chai": "^4.1.2",
"@types/lodash": "^4.14.104",
"@types/mocha": "^2.2.48",
"@types/node": "^9.4.6",
"@types/react": "^16.0.38",
"autoprefixer": "^6.3.1",
"babel-core": "^6.5.1",
"babel-eslint": "6.1.2",
"babel-loader": "^6.2.2",
"babel-jest": "^22.4.1",
"babel-loader": "^7.1.2",
"babel-plugin-lodash": "^2.0.1",
"babel-plugin-syntax-trailing-function-commas": "^6.5.0",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
@ -39,41 +47,38 @@
"babel-plugin-transform-react-remove-prop-types": "^0.2.1",
"babel-plugin-transform-runtime": "^6.5.0",
"babel-polyfill": "^6.13.0",
"babel-preset-es2015": "^6.5.0",
"babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-0": "^6.16.0",
"babel-runtime": "^6.5.0",
"bower": "^1.7.7",
"chai": "^3.5.0",
"compression-webpack-plugin": "^1.1.8",
"concurrently": "^3.5.0",
"core-js": "^2.1.3",
"css-loader": "^0.23.1",
"envify": "^3.4.0",
"enzyme": "^2.4.1",
"enzyme": "^3.3.0",
"enzyme-adapter-react-15": "^1.0.5",
"eslint": "^3.14.1",
"eslint-config-prettier": "^2.9.0",
"eslint-loader": "1.6.1",
"eslint-plugin-jest": "^21.12.2",
"eslint-plugin-prettier": "^2.1.2",
"eslint-plugin-react": "6.6.0",
"eslint-watch": "^3.1.2",
"express": "^4.14.0",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.8.5",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.7",
"fork-ts-checker-webpack-plugin": "^0.3.0",
"hanson": "^1.1.1",
"hson-loader": "^1.0.0",
"html-webpack-plugin": "^2.22.0",
"html-webpack-include-assets-plugin": "^1.0.2",
"html-webpack-plugin": "^2.30.1",
"imports-loader": "^0.6.5",
"jest": "^22.4.2",
"jest-runner-eslint": "^0.4.0",
"jsdom": "^9.0.0",
"json-loader": "^0.5.4",
"karma": "^1.3.0",
"karma-cli": "^1.0.1",
"karma-mocha": "^1.1.1",
"karma-phantomjs-launcher": "^1.0.2",
"karma-sinon-chai": "^1.2.4",
"karma-sourcemap-loader": "^0.3.7",
"karma-verbose-reporter": "^0.0.6",
"karma-webpack": "^1.8.0",
"mocha": "^2.4.5",
"mocha-loader": "^0.7.1",
"json-loader": "^0.5.7",
"mustache": "^2.2.1",
"node-sass": "^4.5.3",
"on-build-webpack": "^0.1.0",
@ -82,17 +87,21 @@
"postcss-loader": "^0.8.0",
"postcss-reporter": "^1.3.1",
"precss": "^1.4.0",
"prettier": "^1.5.3",
"prettier": "1.5.3",
"react-addons-test-utils": "^15.0.2",
"resolve-url-loader": "^1.6.0",
"sass-loader": "^3.2.0",
"sinon": "^1.17.4",
"sinon-chai": "^2.8.0",
"react-test-renderer": "^15.6.1",
"resolve-url-loader": "^2.2.1",
"sass-loader": "^6.0.6",
"style-loader": "^0.13.0",
"testem": "^1.2.1",
"uglify-js": "^2.6.1",
"webpack": "^1.13.0",
"webpack-dev-server": "^1.14.1"
"thread-loader": "^1.1.4",
"ts-loader": "^3.5.0",
"tslib": "^1.9.0",
"typescript": "^2.7.2",
"uglifyjs-webpack-plugin": "^1.2.2",
"webpack": "^3.11.0",
"webpack-bundle-analyzer": "^2.10.1",
"webpack-dev-server": "^2.11.1"
},
"dependencies": {
"@skidding/react-codemirror": "^1.0.1",
@ -108,7 +117,7 @@
"lodash": "^4.3.0",
"moment": "^2.13.0",
"nano-date": "^2.0.1",
"node-uuid": "^1.4.7",
"prop-types": "^15.6.1",
"query-string": "^5.0.0",
"react": "^15.0.2",
"react-addons-shallow-compare": "^15.0.2",
@ -122,12 +131,12 @@
"react-redux": "^4.4.0",
"react-router": "^3.0.2",
"react-router-redux": "^4.0.8",
"react-sparklines": "^1.4.2",
"react-tooltip": "^3.2.1",
"redux": "^3.3.1",
"redux-auth-wrapper": "^1.0.0",
"redux-thunk": "^1.0.3",
"rome": "^2.1.22",
"updeep": "^0.13.0"
"updeep": "^0.13.0",
"uuid": "^3.2.1"
}
}

View File

@ -1,13 +0,0 @@
window.then = function(cb, done) {
window.setTimeout(function() {
cb()
if (typeof done === 'function') {
done()
}
}, 0)
}
const chai = require('chai')
chai.use(require('sinon-chai'))
global.expect = chai.expect

View File

@ -1,5 +1,5 @@
import _ from 'lodash'
import uuid from 'node-uuid'
import uuid from 'uuid'
import {
getUsers as getUsersAJAX,

View File

@ -9,18 +9,13 @@ class ChangePassRow extends Component {
this.state = {
showForm: false,
}
this.showForm = ::this.showForm
this.handleCancel = ::this.handleCancel
this.handleKeyPress = ::this.handleKeyPress
this.handleEdit = ::this.handleEdit
this.handleSubmit = ::this.handleSubmit
}
showForm() {
showForm = () => {
this.setState({showForm: true})
}
handleCancel() {
handleCancel = () => {
this.setState({showForm: false})
}
@ -28,12 +23,12 @@ class ChangePassRow extends Component {
this.setState({showForm: false})
}
handleSubmit(user) {
handleSubmit = user => {
this.props.onApply(user)
this.setState({showForm: false})
}
handleKeyPress(user) {
handleKeyPress = user => {
return e => {
if (e.key === 'Enter') {
this.handleSubmit(user)
@ -41,7 +36,7 @@ class ChangePassRow extends Component {
}
}
handleEdit(user) {
handleEdit = user => {
return e => {
this.props.onEdit(user, {[e.target.name]: e.target.value})
}

View File

@ -7,24 +7,20 @@ class QueryRow extends Component {
constructor(props) {
super(props)
this.handleInitiateKill = ::this.handleInitiateKill
this.handleFinishHim = ::this.handleFinishHim
this.handleShowMercy = ::this.handleShowMercy
this.state = {
confirmingKill: false,
}
}
handleInitiateKill() {
handleInitiateKill = () => {
this.setState({confirmingKill: true})
}
handleFinishHim() {
handleFinishHim = () => {
this.props.onKill(this.props.query.id)
}
handleShowMercy() {
handleShowMercy = () => {
this.setState({confirmingKill: false})
}

View File

@ -5,12 +5,9 @@ import {ROLES_TABLE} from 'src/admin/constants/tableSizing'
class RoleEditingRow extends Component {
constructor(props) {
super(props)
this.handleKeyPress = ::this.handleKeyPress
this.handleEdit = ::this.handleEdit
}
handleKeyPress(role) {
handleKeyPress = role => {
return e => {
if (e.key === 'Enter') {
this.props.onSave(role)
@ -18,7 +15,7 @@ class RoleEditingRow extends Component {
}
}
handleEdit(role) {
handleEdit = role => {
return e => {
this.props.onEdit(role, {[e.target.name]: e.target.value})
}

View File

@ -5,12 +5,9 @@ import {USERS_TABLE} from 'src/admin/constants/tableSizing'
class UserEditName extends Component {
constructor(props) {
super(props)
this.handleKeyPress = ::this.handleKeyPress
this.handleEdit = ::this.handleEdit
}
handleKeyPress(user) {
handleKeyPress = user => {
return e => {
if (e.key === 'Enter') {
this.props.onSave(user)
@ -18,7 +15,7 @@ class UserEditName extends Component {
}
}
handleEdit(user) {
handleEdit = user => {
return e => {
this.props.onEdit(user, {[e.target.name]: e.target.value})
}

View File

@ -3,14 +3,7 @@ import React, {Component, PropTypes} from 'react'
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
class UserNewPassword extends Component {
constructor(props) {
super(props)
this.handleKeyPress = ::this.handleKeyPress
this.handleEdit = ::this.handleEdit
}
handleKeyPress(user) {
handleKeyPress = user => {
return e => {
if (e.key === 'Enter') {
this.props.onSave(user)
@ -18,7 +11,7 @@ class UserNewPassword extends Component {
}
}
handleEdit(user) {
handleEdit = user => {
return e => {
this.props.onEdit(user, {[e.target.name]: e.target.value})
}

View File

@ -1,6 +1,6 @@
import React, {Component, PropTypes} from 'react'
import uuid from 'node-uuid'
import uuid from 'uuid'
import AllUsersTableHeader from 'src/admin/components/chronograf/AllUsersTableHeader'
import AllUsersTableRowNew from 'src/admin/components/chronograf/AllUsersTableRowNew'

View File

@ -1,6 +1,6 @@
import React, {Component, PropTypes} from 'react'
import uuid from 'node-uuid'
import uuid from 'uuid'
import OrganizationsTableRow from 'src/admin/components/chronograf/OrganizationsTableRow'
import OrganizationsTableRowNew from 'src/admin/components/chronograf/OrganizationsTableRowNew'

View File

@ -1,6 +1,6 @@
import React, {Component, PropTypes} from 'react'
import uuid from 'node-uuid'
import uuid from 'uuid'
import ProvidersTableRow from 'src/admin/components/chronograf/ProvidersTableRow'
import ProvidersTableRowNew from 'src/admin/components/chronograf/ProvidersTableRowNew'

View File

@ -1,6 +1,6 @@
import React, {Component, PropTypes} from 'react'
import uuid from 'node-uuid'
import uuid from 'uuid'
import UsersTableHeader from 'src/admin/components/chronograf/UsersTableHeader'
import UsersTableRowNew from 'src/admin/components/chronograf/UsersTableRowNew'

View File

@ -20,12 +20,6 @@ import {
import {publishAutoDismissingNotification} from 'shared/dispatchers'
class QueriesPage extends Component {
constructor(props) {
super(props)
this.updateQueries = ::this.updateQueries
this.handleKillQuery = ::this.handleKillQuery
}
componentDidMount() {
this.updateQueries()
const updateInterval = 5000
@ -42,7 +36,7 @@ class QueriesPage extends Component {
return <QueriesTable queries={queries} onKillQuery={this.handleKillQuery} />
}
updateQueries() {
updateQueries = () => {
const {source, notify, loadQueries} = this.props
showDatabases(source.links.proxy).then(resp => {
const {databases, errors} = showDatabasesParser(resp.data)
@ -78,7 +72,7 @@ class QueriesPage extends Component {
})
}
handleKillQuery(id) {
handleKillQuery = id => {
const {source, killQuery} = this.props
killQuery(source.links.proxy, id)
}

View File

@ -5,7 +5,7 @@ import {
NEW_DEFAULT_DATABASE,
NEW_EMPTY_RP,
} from 'src/admin/constants'
import uuid from 'node-uuid'
import uuid from 'uuid'
const initialState = {
users: null,

View File

@ -3,7 +3,7 @@ import React, {Component, PropTypes} from 'react'
import _ from 'lodash'
import classnames from 'classnames'
import {Link} from 'react-router'
import uuid from 'node-uuid'
import uuid from 'uuid'
import InfiniteScroll from 'shared/components/InfiniteScroll'
@ -243,9 +243,6 @@ class SearchBar extends Component {
this.state = {
searchTerm: '',
}
this.handleSearch = ::this.handleSearch
this.handleChange = ::this.handleChange
}
componentWillMount() {
@ -253,11 +250,11 @@ class SearchBar extends Component {
this.handleSearch = _.debounce(this.handleSearch, waitPeriod)
}
handleSearch() {
handleSearch = () => {
this.props.onSearch(this.state.searchTerm)
}
handleChange(e) {
handleChange = e => {
this.setState({searchTerm: e.target.value}, this.handleSearch)
}

View File

@ -11,7 +11,7 @@ import AJAX from 'utils/ajax'
import _ from 'lodash'
import moment from 'moment'
import timeRanges from 'hson!shared/data/timeRanges.hson'
import {timeRanges} from 'shared/data/timeRanges'
class AlertsApp extends Component {
constructor(props) {
@ -35,11 +35,6 @@ class AlertsApp extends Component {
limitMultiplier: 1, // only used if AlertsApp receives a limit prop
isAlertsMaxedOut: false, // only used if AlertsApp receives a limit prop
}
this.fetchAlerts = ::this.fetchAlerts
this.renderSubComponents = ::this.renderSubComponents
this.handleGetMoreAlerts = ::this.handleGetMoreAlerts
this.handleApplyTime = ::this.handleApplyTime
}
// TODO: show a loading screen until we figure out if there is a kapacitor and fetch the alerts
@ -65,7 +60,7 @@ class AlertsApp extends Component {
}
}
fetchAlerts() {
fetchAlerts = () => {
getAlerts(
this.props.source.links.proxy,
this.state.timeRange,
@ -112,13 +107,13 @@ class AlertsApp extends Component {
})
}
handleGetMoreAlerts() {
handleGetMoreAlerts = () => {
this.setState({limitMultiplier: this.state.limitMultiplier + 1}, () => {
this.fetchAlerts(this.state.limitMultiplier)
})
}
renderSubComponents() {
renderSubComponents = () => {
const {source, isWidget, limit} = this.props
const {isAlertsMaxedOut, alerts} = this.state
@ -135,7 +130,7 @@ class AlertsApp extends Component {
: <NoKapacitorError source={source} />
}
handleApplyTime(timeRange) {
handleApplyTime = timeRange => {
this.setState({timeRange})
}

View File

@ -1,4 +1,5 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {withRouter} from 'react-router'

View File

@ -1,7 +1,7 @@
import React, {Component, PropTypes} from 'react'
import _ from 'lodash'
import uuid from 'node-uuid'
import uuid from 'uuid'
import ResizeContainer from 'shared/components/ResizeContainer'
import QueryMaker from 'src/dashboards/components/QueryMaker'

View File

@ -3,7 +3,7 @@ import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import _ from 'lodash'
import uuid from 'node-uuid'
import uuid from 'uuid'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import Threshold from 'src/dashboards/components/Threshold'
@ -46,7 +46,12 @@ class GaugeOptions extends Component {
name: GAUGE_COLORS[randomColor].name,
}
handleUpdateGaugeColors([...gaugeColors, newThreshold])
const updatedColors = _.sortBy(
[...gaugeColors, newThreshold],
color => color.value
)
handleUpdateGaugeColors(updatedColors)
} else {
onResetFocus()
}
@ -57,8 +62,9 @@ class GaugeOptions extends Component {
const gaugeColors = this.props.gaugeColors.filter(
color => color.id !== threshold.id
)
const sortedColors = _.sortBy(gaugeColors, color => color.value)
handleUpdateGaugeColors(gaugeColors)
handleUpdateGaugeColors(sortedColors)
onResetFocus()
}
@ -138,12 +144,18 @@ class GaugeOptions extends Component {
handleUpdateAxes(newAxes)
}
handleSortColors = () => {
const {gaugeColors, handleUpdateGaugeColors} = this.props
const sortedColors = _.sortBy(gaugeColors, color => color.value)
handleUpdateGaugeColors(sortedColors)
}
render() {
const {gaugeColors, axes: {y: {prefix, suffix}}} = this.props
const disableMaxColor = gaugeColors.length > MIN_THRESHOLDS
const disableAddThreshold = gaugeColors.length > MAX_THRESHOLDS
const sortedColors = _.sortBy(gaugeColors, color => color.value)
return (
<FancyScrollbar
@ -160,11 +172,11 @@ class GaugeOptions extends Component {
>
<span className="icon plus" /> Add Threshold
</button>
{sortedColors.map(color =>
{gaugeColors.map(color =>
<Threshold
isMin={color.value === sortedColors[0].value}
isMin={color.value === gaugeColors[0].value}
isMax={
color.value === sortedColors[sortedColors.length - 1].value
color.value === gaugeColors[gaugeColors.length - 1].value
}
visualizationType="gauge"
threshold={color}
@ -174,6 +186,7 @@ class GaugeOptions extends Component {
onValidateColorValue={this.handleValidateColorValue}
onUpdateColorValue={this.handleUpdateColorValue}
onDeleteThreshold={this.handleDeleteThreshold}
onSortColors={this.handleSortColors}
/>
)}
</div>

View File

@ -3,7 +3,7 @@ import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import _ from 'lodash'
import uuid from 'node-uuid'
import uuid from 'uuid'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import Threshold from 'src/dashboards/components/Threshold'
@ -67,18 +67,23 @@ class SingleStatOptions extends Component {
name: GAUGE_COLORS[randomColor].name,
}
handleUpdateSingleStatColors([...singleStatColors, newThreshold])
const updatedColors = _.sortBy(
[...singleStatColors, newThreshold],
color => color.value
)
handleUpdateSingleStatColors(updatedColors)
onResetFocus()
}
handleDeleteThreshold = threshold => () => {
const {handleUpdateSingleStatColors, onResetFocus} = this.props
const singleStatColors = this.props.singleStatColors.filter(
color => color.id !== threshold.id
)
const sortedColors = _.sortBy(singleStatColors, color => color.value)
handleUpdateSingleStatColors(singleStatColors)
handleUpdateSingleStatColors(sortedColors)
onResetFocus()
}
@ -126,6 +131,13 @@ class SingleStatOptions extends Component {
handleUpdateAxes(newAxes)
}
handleSortColors = () => {
const {singleStatColors, handleUpdateSingleStatColors} = this.props
const sortedColors = _.sortBy(singleStatColors, color => color.value)
handleUpdateSingleStatColors(sortedColors)
}
render() {
const {
singleStatColors,
@ -135,8 +147,6 @@ class SingleStatOptions extends Component {
const disableAddThreshold = singleStatColors.length > MAX_THRESHOLDS
const sortedColors = _.sortBy(singleStatColors, color => color.value)
return (
<FancyScrollbar
className="display-options--cell y-axis-controls"
@ -152,7 +162,7 @@ class SingleStatOptions extends Component {
>
<span className="icon plus" /> Add Threshold
</button>
{sortedColors.map(
{singleStatColors.map(
color =>
color.id === SINGLE_STAT_BASE
? <div className="gauge-controls--section" key={color.id}>
@ -172,6 +182,7 @@ class SingleStatOptions extends Component {
onValidateColorValue={this.handleValidateColorValue}
onUpdateColorValue={this.handleUpdateColorValue}
onDeleteThreshold={this.handleDeleteThreshold}
onSortColors={this.handleSortColors}
/>
)}
</div>

View File

@ -29,6 +29,13 @@ class Threshold extends Component {
handleBlur = () => {
this.setState({workingValue: this.props.threshold.value, valid: true})
this.props.onSortColors()
}
handleKeyUp = e => {
if (e.key === 'Enter') {
this.thresholdInputRef.blur()
}
}
render() {
@ -87,6 +94,8 @@ class Threshold extends Component {
type="number"
onChange={this.handleChangeWorkingValue}
onBlur={this.handleBlur}
onKeyUp={this.handleKeyUp}
ref={r => (this.thresholdInputRef = r)}
/>
<ColorDropdown
colors={GAUGE_COLORS}
@ -117,6 +126,7 @@ Threshold.propTypes = {
onDeleteThreshold: func.isRequired,
isMin: bool,
isMax: bool,
onSortColors: func.isRequired,
}
export default Threshold

View File

@ -1,6 +1,6 @@
import React, {Component, PropTypes} from 'react'
import classnames from 'classnames'
import uuid from 'node-uuid'
import uuid from 'uuid'
import TemplateVariableTable from 'src/dashboards/components/template_variables/Table'
@ -64,15 +64,9 @@ class TemplateVariableManagerWrapper extends Component {
rows: this.props.templates,
isEdited: false,
}
this.onRunQuerySuccess = ::this.onRunQuerySuccess
this.onSaveTemplatesSuccess = ::this.onSaveTemplatesSuccess
this.onAddVariable = ::this.onAddVariable
this.onDeleteTemplateVariable = ::this.onDeleteTemplateVariable
this.tempVarAlreadyExists = ::this.tempVarAlreadyExists
}
onAddVariable() {
onAddVariable = () => {
const {rows} = this.state
const newRow = {
@ -95,7 +89,7 @@ class TemplateVariableManagerWrapper extends Component {
this.setState({rows: newRows})
}
onRunQuerySuccess(template, queryConfig, parsedData, tempVar) {
onRunQuerySuccess = (template, queryConfig, parsedData, tempVar) => {
const {rows} = this.state
const {id, links} = template
const {
@ -156,7 +150,7 @@ class TemplateVariableManagerWrapper extends Component {
this.setState({rows: newRows, isEdited: true})
}
onSaveTemplatesSuccess() {
onSaveTemplatesSuccess = () => {
const {rows} = this.state
const newRows = rows.map(row => ({...row, isNew: false}))
@ -164,7 +158,7 @@ class TemplateVariableManagerWrapper extends Component {
this.setState({rows: newRows, isEdited: false})
}
onDeleteTemplateVariable(templateID) {
onDeleteTemplateVariable = templateID => {
const {rows} = this.state
const newRows = rows.filter(({id}) => id !== templateID)
@ -172,7 +166,7 @@ class TemplateVariableManagerWrapper extends Component {
this.setState({rows: newRows, isEdited: true})
}
tempVarAlreadyExists(testTempVar, testID) {
tempVarAlreadyExists = (testTempVar, testID) => {
const {rows: tempVars} = this.state
return tempVars.some(
({tempVar, id}) => tempVar === testTempVar && id !== testID

View File

@ -119,8 +119,6 @@ class RowWrapper extends Component {
selectedTagKey: query && query.tagKey,
autoFocusTarget: 'tempVar',
}
this.runTemplateVariableQuery = ::this.runTemplateVariableQuery
}
handleSubmit = ({

View File

@ -111,7 +111,7 @@ export const DEFAULT_SINGLESTAT_COLORS = [
hex: GAUGE_COLORS[11].hex,
id: SINGLE_STAT_BASE,
name: GAUGE_COLORS[11].name,
value: 0,
value: -999999999999999999,
},
]

View File

@ -32,7 +32,7 @@ import {
templateControlBarVisibilityToggled as templateControlBarVisibilityToggledAction,
} from 'shared/actions/app'
import {presentationButtonDispatcher} from 'shared/dispatchers'
import {interval, DASHBOARD_LAYOUT_ROW_HEIGHT} from 'shared/constants'
import {DASHBOARD_LAYOUT_ROW_HEIGHT} from 'shared/constants'
const FORMAT_INFLUXQL = 'influxql'
const defaultTimeRange = {
@ -336,6 +336,25 @@ class DashboardPage extends Component {
],
}
const interval = {
id: 'interval',
type: 'autoGroupBy',
tempVar: ':interval:',
label: 'automatically determine the best group by time',
values: [
{
value: '1000', // pixels
type: 'resolution',
selected: true,
},
{
value: '3',
type: 'pointsPerPixel',
selected: true,
},
],
}
let templatesIncludingDashTime
if (dashboard) {
templatesIncludingDashTime = [

View File

@ -1,5 +1,5 @@
import _ from 'lodash'
import timeRanges from 'hson!shared/data/timeRanges.hson'
import {timeRanges} from 'shared/data/timeRanges'
const {lower, upper} = timeRanges.find(tr => tr.lower === 'now() - 1h')

View File

@ -1,4 +1,4 @@
import uuid from 'node-uuid'
import uuid from 'uuid'
import {getQueryConfig} from 'shared/apis'

View File

@ -1,7 +1,12 @@
import React, {PropTypes} from 'react'
import React, {SFC} from 'react'
import moment from 'moment'
const CustomCell = ({data, columnName}) => {
export interface CustomCellProps {
data?: string
columnName?: string
}
const CustomCell: SFC<CustomCellProps> = ({data, columnName}) => {
if (columnName === 'time') {
const date = moment(new Date(data)).format('MM/DD/YY hh:mm:ssA')
@ -19,11 +24,4 @@ const CustomCell = ({data, columnName}) => {
)
}
const {number, oneOfType, string} = PropTypes
CustomCell.propTypes = {
data: oneOfType([number, string]),
columnName: string.isRequired,
}
export default CustomCell

View File

@ -1,7 +1,7 @@
import React, {PropTypes} from 'react'
import {withRouter} from 'react-router'
import groupByTimeOptions from 'hson!src/data_explorer/data/groupByTimes.hson'
import groupByTimeOptions from 'src/data_explorer/data/groupByTimes'
import Dropdown from 'shared/components/Dropdown'

View File

@ -10,11 +10,6 @@ class QueryEditor extends Component {
this.state = {
value: this.props.query,
}
this.handleKeyDown = ::this.handleKeyDown
this.handleChange = ::this.handleChange
this.handleUpdate = ::this.handleUpdate
this.handleChooseMetaQuery = ::this.handleChooseMetaQuery
}
componentWillReceiveProps(nextProps) {
@ -23,7 +18,7 @@ class QueryEditor extends Component {
}
}
handleKeyDown(e) {
handleKeyDown = e => {
const {value} = this.state
if (e.key === 'Escape') {
@ -37,15 +32,15 @@ class QueryEditor extends Component {
}
}
handleChange() {
handleChange = () => {
this.setState({value: this.editor.value})
}
handleUpdate() {
handleUpdate = () => {
this.props.onUpdate(this.state.value)
}
handleChooseMetaQuery(template) {
handleChooseMetaQuery = template => {
this.setState({value: template.query})
}

View File

@ -7,7 +7,7 @@ import {Table, Column, Cell} from 'fixed-data-table'
import Dropdown from 'shared/components/Dropdown'
import CustomCell from 'src/data_explorer/components/CustomCell'
import TabItem from 'src/data_explorer/components/TableTabItem'
import {TEMPLATES} from 'src/shared/constants'
import {TEMPLATES} from 'src/data_explorer/constants'
import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries'

View File

@ -5,7 +5,7 @@ import _ from 'lodash'
import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries'
import {resultsToCSV} from 'src/shared/parsing/resultsToCSV.js'
import download from 'src/external/download.js'
import {TEMPLATES} from 'src/shared/constants'
import {TEMPLATES} from 'src/data_explorer/constants'
const getCSV = (query, errorThrown) => async () => {
try {

View File

@ -81,3 +81,16 @@ export const QUERY_TEMPLATES = [
{text: 'Show Stats', query: 'SHOW STATS'},
{text: 'Show Diagnostics', query: 'SHOW DIAGNOSTICS'},
]
const interval = {
id: 'interval',
type: 'autoGroupBy',
tempVar: ':interval:',
label: 'automatically determine the best group by time',
values: [
{value: '1000', type: 'resolution', selected: true},
{value: '3', type: 'pointsPerPixel', selected: true},
],
} // pixels
export const TEMPLATES = [interval]

View File

@ -6,16 +6,16 @@ import queryString from 'query-string'
import _ from 'lodash'
import QueryMaker from 'src/data_explorer/components/QueryMaker'
import Visualization from 'src/data_explorer/components/Visualization'
import QueryMaker from '../components/QueryMaker'
import Visualization from '../components/Visualization'
import WriteDataForm from 'src/data_explorer/components/WriteDataForm'
import Header from 'src/data_explorer/containers/Header'
import ResizeContainer from 'src/shared/components/ResizeContainer'
import OverlayTechnologies from 'src/shared/components/OverlayTechnologies'
import Header from '../containers/Header'
import ResizeContainer from 'shared/components/ResizeContainer'
import OverlayTechnologies from 'shared/components/OverlayTechnologies'
import ManualRefresh from 'src/shared/components/ManualRefresh'
import {VIS_VIEWS, AUTO_GROUP_BY, TEMPLATES} from 'src/shared/constants'
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants'
import {VIS_VIEWS, AUTO_GROUP_BY} from 'shared/constants'
import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS, TEMPLATES} from '../constants'
import {errorThrown} from 'shared/actions/errors'
import {setAutoRefresh} from 'shared/actions/app'
import * as dataExplorerActionCreators from 'src/data_explorer/actions/view'

View File

@ -1,4 +1,4 @@
[
const groupByTimes = [
{defaultTimeBound: ':interval:', seconds: 604800, menuOption: 'auto'},
{defaultTimeBound: 'now() - 5m', seconds: 10, menuOption: '10s'},
{defaultTimeBound: 'now() - 15m', seconds: 60, menuOption: '1m'},
@ -10,3 +10,5 @@
{defaultTimeBound: 'now() - 7d', seconds: 86400, menuOption: '1d'},
{defaultTimeBound: 'now() - 30d', seconds: 604800, menuOption: '7d'},
]
export default groupByTimes

View File

@ -1,4 +1,4 @@
import timeRanges from 'hson!shared/data/timeRanges.hson'
import {timeRanges} from 'shared/data/timeRanges'
const {lower, upper} = timeRanges.find(tr => tr.lower === 'now() - 1h')

View File

@ -11,7 +11,7 @@ import DashboardHeader from 'src/dashboards/components/DashboardHeader'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import ManualRefresh from 'src/shared/components/ManualRefresh'
import timeRanges from 'hson!shared/data/timeRanges.hson'
import {timeRanges} from 'shared/data/timeRanges'
import {
getLayouts,
getAppsForHosts,

View File

@ -1,10 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
<title>Chronograf</title>
</head>
<body>
<div id='react-root' data-basepath=""></div>
</body>
</html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>Chronograf</title>
</head>
<body>
<div id='react-root' data-basepath=""></div>
</body>
</body>
</html>

View File

@ -1,4 +1,4 @@
import uuid from 'node-uuid'
import uuid from 'uuid'
import {getActiveKapacitor} from 'shared/apis'
import {publishNotification} from 'shared/actions/notifications'
import {

View File

@ -1,6 +1,6 @@
import React, {PropTypes} from 'react'
import classnames from 'classnames'
import uuid from 'node-uuid'
import uuid from 'uuid'
const HandlerTabs = ({
handlersOnThisAlert,

View File

@ -5,8 +5,15 @@ import FancyScrollbar from 'shared/components/FancyScrollbar'
class KapacitorForm extends Component {
render() {
const {onInputChange, onReset, kapacitor, onSubmit, exists} = this.props
const {url: kapaUrl, name, username, password} = kapacitor
const {
onInputChange,
onChangeUrl,
onReset,
kapacitor,
onSubmit,
exists,
} = this.props
const {url, name, username, password} = kapacitor
return (
<div className="page">
<div className="page-header">
@ -35,9 +42,9 @@ class KapacitorForm extends Component {
className="form-control"
id="kapaUrl"
name="kapaUrl"
placeholder={kapaUrl}
value={kapaUrl}
onChange={onInputChange}
placeholder={url}
value={url}
onChange={onChangeUrl}
spellCheck="false"
/>
</div>
@ -145,6 +152,7 @@ const {func, shape, string, bool} = PropTypes
KapacitorForm.propTypes = {
onSubmit: func.isRequired,
onInputChange: func.isRequired,
onChangeUrl: func.isRequired,
onReset: func.isRequired,
kapacitor: shape({
url: string.isRequired,

View File

@ -9,7 +9,7 @@ import FancyScrollbar from 'shared/components/FancyScrollbar'
import {createRule, editRule} from 'src/kapacitor/apis'
import buildInfluxQLQuery from 'utils/influxql'
import timeRanges from 'hson!shared/data/timeRanges.hson'
import {timeRanges} from 'shared/data/timeRanges'
import {DEFAULT_RULE_ID} from 'src/kapacitor/constants'
class KapacitorRule extends Component {

View File

@ -60,6 +60,10 @@ class KapacitorPage extends Component {
})
}
handleChangeUrl = ({value}) => {
this.setState({kapacitor: {...this.state.kapacitor, url: value}})
}
handleSubmit = e => {
e.preventDefault()
const {
@ -143,6 +147,7 @@ class KapacitorPage extends Component {
<KapacitorForm
onSubmit={this.handleSubmit}
onInputChange={this.handleInputChange}
onChangeUrl={this.handleChangeUrl}
onReset={this.handleResetToDefaults}
kapacitor={kapacitor}
source={source}

View File

@ -1,7 +1,7 @@
import React, {PropTypes, Component} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import uuid from 'node-uuid'
import uuid from 'uuid'
import Tickscript from 'src/kapacitor/components/Tickscript'
import * as kapactiorActionCreators from 'src/kapacitor/actions/view'

View File

@ -222,14 +222,14 @@ AnnotationSpan.propTypes = {
annotation: schema.annotation.isRequired,
mode: string.isRequired,
dygraph: shape({}).isRequired,
staticLegendHeight: number.isRequired,
staticLegendHeight: number,
updateAnnotationAsync: func.isRequired,
updateAnnotation: func.isRequired,
}
const mdtp = {
const mapDispatchToProps = {
updateAnnotationAsync: actions.updateAnnotationAsync,
updateAnnotation: actions.updateAnnotation,
}
export default connect(null, mdtp)(AnnotationSpan)
export default connect(null, mapDispatchToProps)(AnnotationSpan)

View File

@ -3,7 +3,6 @@ import _ from 'lodash'
import {fetchTimeSeriesAsync} from 'shared/actions/timeSeries'
import {removeUnselectedTemplateValues} from 'src/dashboards/constants'
import {intervalValuesPoints} from 'src/shared/constants'
const AutoRefresh = ComposedComponent => {
class wrapper extends Component {
@ -97,38 +96,31 @@ const AutoRefresh = ComposedComponent => {
const timeSeriesPromises = queries.map(query => {
const {host, database, rp} = query
const templatesWithIntervalVals = templates.map(temp => {
const templatesWithResolution = templates.map(temp => {
if (temp.tempVar === ':interval:') {
if (resolution) {
// resize event
return {
...temp,
values: temp.values.map(v => {
if (v.type === 'resolution') {
return {...v, value: `${resolution}`}
}
if (v.type === 'points') {
return {
...v,
value: `${_.toInteger(Number(resolution) / 3)}`,
}
}
return v
}),
values: temp.values.map(
v => (temp.type === 'resolution' ? {...v, resolution} : v)
),
}
}
return {
...temp,
values: intervalValuesPoints,
values: [
...temp.values,
{value: '1000', type: 'resolution', selected: true},
],
}
}
return temp
})
const tempVars = removeUnselectedTemplateValues(
templatesWithIntervalVals
)
const tempVars = removeUnselectedTemplateValues(templatesWithResolution)
return fetchTimeSeriesAsync(
{
source: host,
@ -206,6 +198,10 @@ const AutoRefresh = ComposedComponent => {
: false
}
wrapper.defaultProps = {
inView: true,
}
const {
array,
arrayOf,

View File

@ -2,7 +2,7 @@ import React, {PropTypes, Component} from 'react'
import classnames from 'classnames'
import OnClickOutside from 'shared/components/OnClickOutside'
import autoRefreshItems from 'hson!shared/data/autoRefreshes.hson'
import autoRefreshItems from 'shared/data/autoRefreshes'
class AutoRefreshDropdown extends Component {
constructor(props) {

View File

@ -2,7 +2,7 @@ import React, {PropTypes, Component} from 'react'
import rome from 'rome'
import moment from 'moment'
import shortcuts from 'hson!shared/data/timeRangeShortcuts.hson'
import shortcuts from 'shared/data/timeRangeShortcuts'
const dateFormat = 'YYYY-MM-DD HH:mm'
class CustomTimeRange extends Component {

View File

@ -12,20 +12,17 @@ class CustomTimeRangeDropdown extends Component {
this.state = {
expanded: false,
}
this.handleToggleDropdown = ::this.handleToggleDropdown
this.handleCloseDropdown = ::this.handleCloseDropdown
}
handleClickOutside() {
this.handleCloseDropdown()
}
handleToggleDropdown() {
handleToggleDropdown = () => {
this.setState({expanded: !this.state.expanded})
}
handleCloseDropdown() {
handleCloseDropdown = () => {
this.setState({expanded: false})
}

View File

@ -1,4 +1,5 @@
import React, {PropTypes, Component} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import Dropdown from 'shared/components/Dropdown'
import {showDatabases} from 'shared/apis/metaQuery'

View File

@ -30,15 +30,13 @@ class DeleteConfirmButtons extends Component {
this.state = {
isConfirming: false,
}
this.handleClickDelete = ::this.handleClickDelete
this.handleCancel = ::this.handleCancel
}
handleClickDelete() {
handleClickDelete = () => {
this.setState({isConfirming: true})
}
handleCancel() {
handleCancel = () => {
this.setState({isConfirming: false})
}

View File

@ -1,11 +1,12 @@
import React, {Component, PropTypes} from 'react'
import {Link} from 'react-router'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import OnClickOutside from 'shared/components/OnClickOutside'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import {DROPDOWN_MENU_MAX_HEIGHT} from 'shared/constants/index'
import DropdownMenu, {DropdownMenuEmpty} from 'shared/components/DropdownMenu'
import DropdownInput from 'shared/components/DropdownInput'
import DropdownHead from 'shared/components/DropdownHead'
class Dropdown extends Component {
export class Dropdown extends Component {
constructor(props) {
super(props)
this.state = {
@ -36,6 +37,7 @@ class Dropdown extends Component {
if (disabled) {
return
}
this.toggleMenu(e)
if (this.props.onClick) {
this.props.onClick(e)
@ -56,6 +58,7 @@ class Dropdown extends Component {
if (e) {
e.stopPropagation()
}
if (!this.state.isOpen) {
this.setState({
searchTerm: '',
@ -63,6 +66,7 @@ class Dropdown extends Component {
highlightedItemIndex: null,
})
}
this.setState({isOpen: !this.state.isOpen})
}
@ -95,17 +99,17 @@ class Dropdown extends Component {
}
handleFilterChange = e => {
if (e.target.value === null || e.target.value === '') {
this.setState({
searchTerm: '',
filteredItems: this.props.items,
highlightedItemIndex: null,
})
} else {
this.setState({searchTerm: e.target.value}, () =>
if (e.target.value) {
return this.setState({searchTerm: e.target.value}, () =>
this.applyFilter(this.state.searchTerm)
)
}
this.setState({
searchTerm: '',
filteredItems: this.props.items,
highlightedItemIndex: null,
})
}
applyFilter = searchTerm => {
@ -121,108 +125,29 @@ class Dropdown extends Component {
})
}
renderMenu() {
const {
actions,
addNew,
items,
menuWidth,
menuLabel,
menuClass,
useAutoComplete,
selected,
} = this.props
const {filteredItems, highlightedItemIndex} = this.state
const menuItems = useAutoComplete ? filteredItems : items
return (
<ul
className={classnames('dropdown-menu', {
'dropdown-menu--no-highlight': useAutoComplete,
[menuClass]: menuClass,
})}
style={{width: menuWidth}}
>
<FancyScrollbar
autoHide={false}
autoHeight={true}
maxHeight={DROPDOWN_MENU_MAX_HEIGHT}
>
{menuLabel
? <li className="dropdown-header">
{menuLabel}
</li>
: null}
{menuItems.map((item, i) => {
if (item.text === 'SEPARATOR') {
return <li key={i} className="dropdown-divider" />
}
return (
<li
className={classnames('dropdown-item', {
highlight: i === highlightedItemIndex,
active: item.text === selected,
})}
key={i}
>
<a
href="#"
onClick={this.handleSelection(item)}
onMouseOver={this.handleHighlight(i)}
>
{item.text}
</a>
{actions && actions.length
? <div className="dropdown-actions">
{actions.map(action => {
return (
<button
key={action.text}
className="dropdown-action"
onClick={this.handleAction(action, item)}
>
<span
title={action.text}
className={`icon ${action.icon}`}
/>
</button>
)
})}
</div>
: null}
</li>
)
})}
{addNew
? <li className="multi-select--apply">
<Link className="btn btn-xs btn-default" to={addNew.url}>
{addNew.text}
</Link>
</li>
: null}
</FancyScrollbar>
</ul>
)
}
render() {
const {
items,
addNew,
actions,
selected,
disabled,
iconName,
tabIndex,
className,
menuClass,
iconName,
menuWidth,
menuLabel,
buttonSize,
buttonColor,
toggleStyle,
useAutoComplete,
disabled,
tabIndex,
} = this.props
const {isOpen, searchTerm, filteredItems} = this.state
const menuItems = useAutoComplete ? filteredItems : items
const {isOpen, searchTerm, filteredItems, highlightedItemIndex} = this.state
const menuItems = useAutoComplete ? filteredItems : items
const disabledClass = disabled ? 'disabled' : null
return (
<div
onClick={this.handleClick}
@ -232,48 +157,46 @@ class Dropdown extends Component {
})}
tabIndex={tabIndex}
ref={r => (this.dropdownRef = r)}
data-test="dropdown-toggle"
>
{useAutoComplete && isOpen
? <div
className={`dropdown-autocomplete dropdown-toggle ${buttonSize} ${buttonColor} ${disabledClass}`}
style={toggleStyle}
>
<input
ref="dropdownAutoComplete"
className="dropdown-autocomplete--input"
type="text"
autoFocus={true}
placeholder="Filter items..."
spellCheck={false}
onChange={this.handleFilterChange}
onKeyDown={this.handleFilterKeyPress}
value={searchTerm}
/>
<span className="caret" />
</div>
: <div
className={`btn dropdown-toggle ${buttonSize} ${buttonColor} ${disabledClass}`}
style={toggleStyle}
>
{iconName
? <span className={classnames('icon', {[iconName]: true})} />
: null}
<span className="dropdown-selected">
{selected}
</span>
<span className="caret" />
</div>}
{isOpen && menuItems.length ? this.renderMenu() : null}
{isOpen && !menuItems.length
? <ul
className={classnames('dropdown-menu', {
'dropdown-menu--no-highlight': useAutoComplete,
[menuClass]: menuClass,
})}
>
<li className="dropdown-empty">No matching items</li>
</ul>
: null}
? <DropdownInput
searchTerm={searchTerm}
buttonSize={buttonSize}
buttonColor={buttonColor}
toggleStyle={toggleStyle}
disabledClass={disabledClass}
onFilterChange={this.handleFilterChange}
onFilterKeyPress={this.handleFilterKeyPress}
/>
: <DropdownHead
iconName={iconName}
selected={selected}
searchTerm={searchTerm}
buttonSize={buttonSize}
buttonColor={buttonColor}
toggleStyle={toggleStyle}
disabledClass={disabledClass}
/>}
{isOpen && menuItems.length
? <DropdownMenu
addNew={addNew}
actions={actions}
items={menuItems}
selected={selected}
menuClass={menuClass}
menuWidth={menuWidth}
menuLabel={menuLabel}
onAction={this.handleAction}
useAutoComplete={useAutoComplete}
onSelection={this.handleSelection}
onHighlight={this.handleHighlight}
highlightedItemIndex={highlightedItemIndex}
/>
: <DropdownMenuEmpty
useAutoComplete={useAutoComplete}
menuClass={menuClass}
/>}
</div>
)
}

View File

@ -0,0 +1,35 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
const DropdownHead = ({
iconName,
selected,
buttonSize,
toggleStyle,
buttonColor,
disabledClass,
}) =>
<div
className={`btn dropdown-toggle ${buttonSize} ${buttonColor} ${disabledClass}`}
style={toggleStyle}
>
{iconName && <span className={classnames('icon', {[iconName]: true})} />}
<span className="dropdown-selected">
{selected}
</span>
<span className="caret" />
</div>
const {string, shape} = PropTypes
DropdownHead.propTypes = {
iconName: string,
selected: string,
buttonSize: string,
toggleStyle: shape(),
buttonColor: string,
disabledClass: string,
}
export default DropdownHead

View File

@ -0,0 +1,42 @@
import React from 'react'
import PropTypes from 'prop-types'
const DropdownInput = ({
searchTerm,
buttonSize,
buttonColor,
toggleStyle,
disabledClass,
onFilterChange,
onFilterKeyPress,
}) =>
<div
className={`dropdown-autocomplete dropdown-toggle ${buttonSize} ${buttonColor} ${disabledClass}`}
style={toggleStyle}
>
<input
className="dropdown-autocomplete--input"
type="text"
autoFocus={true}
placeholder="Filter items..."
spellCheck={false}
onChange={onFilterChange}
onKeyDown={onFilterKeyPress}
value={searchTerm}
/>
<span className="caret" />
</div>
export default DropdownInput
const {func, shape, string} = PropTypes
DropdownInput.propTypes = {
searchTerm: string,
buttonSize: string,
buttonColor: string,
toggleStyle: shape({}),
disabledClass: string,
onFilterChange: func,
onFilterKeyPress: func,
}

View File

@ -0,0 +1,155 @@
import React from 'react'
import PropTypes from 'prop-types'
import {Link} from 'react-router'
import classnames from 'classnames'
import {DROPDOWN_MENU_MAX_HEIGHT} from 'shared/constants/index'
import FancyScrollbar from 'shared/components/FancyScrollbar'
// AddNewResource is an optional parameter that takes the user to another
// route defined by url prop
const AddNewButton = ({url, text}) =>
<li className="multi-select--apply">
<Link className="btn btn-xs btn-default" to={url}>
{text}
</Link>
</li>
const DropdownMenu = ({
items,
addNew,
actions,
selected,
onAction,
menuClass,
menuWidth,
menuLabel,
onSelection,
onHighlight,
useAutoComplete,
highlightedItemIndex,
}) => {
return (
<ul
className={classnames('dropdown-menu', {
'dropdown-menu--no-highlight': useAutoComplete,
[menuClass]: menuClass,
})}
style={{width: menuWidth}}
data-test="dropdown-ul"
>
<FancyScrollbar
autoHide={false}
autoHeight={true}
maxHeight={DROPDOWN_MENU_MAX_HEIGHT}
>
{menuLabel
? <li className="dropdown-header">
{menuLabel}
</li>
: null}
{items.map((item, i) => {
if (item.text === 'SEPARATOR') {
return <li key={i} className="dropdown-divider" />
}
return (
<li
className={classnames('dropdown-item', {
highlight: i === highlightedItemIndex,
active: item.text === selected,
})}
data-test="dropdown-item"
key={i}
>
<a
href="#"
onClick={onSelection(item)}
onMouseOver={onHighlight(i)}
>
{item.text}
</a>
{actions && actions.length
? <div className="dropdown-actions">
{actions.map(action => {
return (
<button
key={action.text}
className="dropdown-action"
onClick={onAction(action, item)}
>
<span
title={action.text}
className={`icon ${action.icon}`}
/>
</button>
)
})}
</div>
: null}
</li>
)
})}
{addNew && <AddNewButton url={addNew.url} text={addNew.text} />}
</FancyScrollbar>
</ul>
)
}
export const DropdownMenuEmpty = ({useAutoComplete, menuClass}) =>
<ul
className={classnames('dropdown-menu', {
'dropdown-menu--no-highlight': useAutoComplete,
[menuClass]: menuClass,
})}
>
<li className="dropdown-empty">No matching items</li>
</ul>
const {arrayOf, bool, number, shape, string, func} = PropTypes
AddNewButton.propTypes = {
url: string,
text: string,
}
DropdownMenuEmpty.propTypes = {
useAutoComplete: bool,
menuClass: string,
}
DropdownMenu.propTypes = {
onAction: func,
actions: arrayOf(
shape({
icon: string.isRequired,
text: string.isRequired,
handler: func.isRequired,
})
),
items: arrayOf(
shape({
text: string.isRequired,
})
).isRequired,
onClick: func,
addNew: shape({
url: string.isRequired,
text: string.isRequired,
}),
selected: string.isRequired,
iconName: string,
className: string,
buttonColor: string,
menuWidth: string,
menuLabel: string,
menuClass: string,
useAutoComplete: bool,
disabled: bool,
searchTerm: string,
onSelection: func,
onHighlight: func,
highlightedItemIndex: number,
}
export default DropdownMenu

View File

@ -301,8 +301,9 @@ class Dygraph extends Component {
render() {
const {isHidden, staticLegendHeight} = this.state
const {staticLegend} = this.props
const {staticLegend, children} = this.props
const nestedGraph = (children && children.length && children[0]) || children
let dygraphStyle = {...this.props.containerStyle, zIndex: '2'}
if (staticLegend) {
const cellVerticalPadding = 16
@ -339,17 +340,19 @@ class Dygraph extends Component {
/>
{staticLegend &&
<StaticLegend
dygraphSeries={this.hashColorDygraphSeries()}
dygraph={this.dygraph}
handleReceiveStaticLegendHeight={
this.handleReceiveStaticLegendHeight
}
/>}
{nestedGraph && React.cloneElement(nestedGraph, {staticLegendHeight})}
</div>
)
}
}
const {array, arrayOf, bool, func, shape, string} = PropTypes
const {array, arrayOf, bool, func, node, shape, string} = PropTypes
Dygraph.defaultProps = {
axes: {
@ -408,6 +411,7 @@ Dygraph.propTypes = {
dygraphRef: func,
onZoom: func,
mode: string,
children: node,
}
const mapStateToProps = ({annotations: {mode}}) => ({

View File

@ -1,7 +1,7 @@
import React, {PropTypes, Component} from 'react'
import _ from 'lodash'
import classnames from 'classnames'
import uuid from 'node-uuid'
import uuid from 'uuid'
import {makeLegendStyles, removeMeasurement} from 'shared/graphs/helpers'

View File

@ -1,4 +1,5 @@
import React, {Component, PropTypes} from 'react'
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import {Scrollbars} from 'react-custom-scrollbars'

View File

@ -3,7 +3,7 @@ import Dropdown from 'shared/components/Dropdown'
import {NULL_STRING, NUMBER} from 'shared/constants/queryFillOptions'
import queryFills from 'hson!shared/data/queryFills.hson'
import queryFills from 'shared/data/queryFills'
class FillQuery extends Component {
constructor(props) {

View File

@ -10,10 +10,6 @@ class FunctionSelector extends Component {
this.state = {
localSelectedItems: this.props.selectedItems,
}
this.onSelect = ::this.onSelect
this.onSingleSelect = ::this.onSingleSelect
this.handleApplyFunctions = ::this.handleApplyFunctions
}
componentWillUpdate(nextProps) {
@ -22,7 +18,7 @@ class FunctionSelector extends Component {
}
}
onSelect(item, e) {
onSelect = (item, e) => {
e.stopPropagation()
const {localSelectedItems} = this.state
@ -37,7 +33,7 @@ class FunctionSelector extends Component {
this.setState({localSelectedItems: nextItems})
}
onSingleSelect(item) {
onSingleSelect = item => {
if (item === this.state.localSelectedItems[0]) {
this.props.onApply([])
this.setState({localSelectedItems: []})
@ -47,11 +43,11 @@ class FunctionSelector extends Component {
}
}
isSelected(item) {
isSelected = item => {
return !!this.state.localSelectedItems.find(text => text === item)
}
handleApplyFunctions(e) {
handleApplyFunctions = e => {
e.stopPropagation()
this.props.onApply(this.state.localSelectedItems)

View File

@ -93,13 +93,8 @@ class LineGraph extends Component {
top: '8px',
}
let prefix
let suffix
if (axes) {
prefix = axes.y.prefix
suffix = axes.y.suffix
}
const prefix = axes ? axes.y.prefix : ''
const suffix = axes ? axes.y.suffix : ''
return (
<div className="dygraph graph--hasYLabel" style={{height: '100%'}}>
@ -123,17 +118,17 @@ class LineGraph extends Component {
containerStyle={containerStyle}
staticLegend={staticLegend}
isGraphFilled={showSingleStat ? false : isGraphFilled}
/>
{showSingleStat
? <SingleStat
>
{showSingleStat &&
<SingleStat
prefix={prefix}
suffix={suffix}
data={data}
lineGraph={true}
colors={colors}
cellHeight={cellHeight}
/>
: null}
/>}
</Dygraph>
</div>
)
}

View File

@ -1,7 +1,7 @@
import React, {Component, PropTypes} from 'react'
import classnames from 'classnames'
import {connect} from 'react-redux'
import uuid from 'node-uuid'
import uuid from 'uuid'
import OnClickOutside from 'shared/components/OnClickOutside'
import AnnotationWindow from 'shared/components/AnnotationWindow'

View File

@ -15,9 +15,6 @@ import {
class Notifications extends Component {
constructor(props) {
super(props)
this.renderNotification = ::this.renderNotification
this.renderDismiss = ::this.renderDismiss
}
componentWillReceiveProps(nextProps) {
@ -26,7 +23,7 @@ class Notifications extends Component {
}
}
renderNotification(type, message) {
renderNotification = (type, message) => {
const isDismissed = this.props.dismissedNotifications[
getNotificationID(message, type)
]
@ -48,7 +45,7 @@ class Notifications extends Component {
handleDismiss = type => () => this.props.dismissNotification(type)
renderDismiss(type) {
renderDismiss = type => {
return (
<button
className="close"

View File

@ -1,21 +1,17 @@
import React from 'react'
import React, {Component} from 'react'
import ReactDOM from 'react-dom'
export default function enhanceWithClickOutside(WrappedComponent) {
const componentName = WrappedComponent.displayName || WrappedComponent.name
return React.createClass({
displayName: `Wrapped${componentName}`,
return class extends Component {
componentDidMount() {
document.addEventListener('click', this.handleClickOutside, true)
},
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClickOutside, true)
},
}
handleClickOutside(e) {
handleClickOutside = e => {
const domNode = ReactDOM.findDOMNode(this)
if (
(!domNode || !domNode.contains(e.target)) &&
@ -23,7 +19,7 @@ export default function enhanceWithClickOutside(WrappedComponent) {
) {
this.wrappedComponent.handleClickOutside(e)
}
},
}
render() {
return (
@ -32,6 +28,6 @@ export default function enhanceWithClickOutside(WrappedComponent) {
ref={ref => (this.wrappedComponent = ref)}
/>
)
},
})
}
}
}

View File

@ -1,7 +1,7 @@
import React, {Component, PropTypes} from 'react'
import classnames from 'classnames'
import uuid from 'node-uuid'
import uuid from 'uuid'
import ClickOutsideInput from 'shared/components/ClickOutsideInput'

View File

@ -17,11 +17,6 @@ class ResizeContainer extends Component {
topHeight: props.initialTopHeight,
bottomHeight: props.initialBottomHeight,
}
this.handleStartDrag = ::this.handleStartDrag
this.handleStopDrag = ::this.handleStopDrag
this.handleMouseLeave = ::this.handleMouseLeave
this.handleDrag = ::this.handleDrag
}
static defaultProps = {
@ -38,19 +33,19 @@ class ResizeContainer extends Component {
})
}
handleStartDrag() {
handleStartDrag = () => {
this.setState({isDragging: true})
}
handleStopDrag() {
handleStopDrag = () => {
this.setState({isDragging: false})
}
handleMouseLeave() {
handleMouseLeave = () => {
this.setState({isDragging: false})
}
handleDrag(e) {
handleDrag = e => {
if (!this.state.isDragging) {
return
}

View File

@ -1,5 +1,5 @@
import React, {PropTypes} from 'react'
import uuid from 'node-uuid'
import uuid from 'uuid'
import {connect} from 'react-redux'
import ReactTooltip from 'react-tooltip'

View File

@ -3,6 +3,7 @@ import classnames from 'classnames'
import lastValues from 'shared/parsing/lastValues'
import {SMALL_CELL_HEIGHT} from 'shared/graphs/helpers'
import {DYGRAPH_CONTAINER_V_MARGIN} from 'shared/constants'
import {SINGLE_STAT_TEXT} from 'src/dashboards/constants/gaugeColors'
import {generateSingleStatHexs} from 'shared/constants/colorOperations'
@ -16,6 +17,7 @@ class SingleStat extends PureComponent {
prefix,
suffix,
lineGraph,
staticLegendHeight,
} = this.props
// If data for this graph is being fetched for the first time, show a graph-wide spinner.
@ -39,11 +41,24 @@ class SingleStat extends PureComponent {
lastValue
)
const backgroundColor = bgColor
const color = textColor
const height = `calc(100% - ${staticLegendHeight +
DYGRAPH_CONTAINER_V_MARGIN * 2}px)`
const singleStatStyles = staticLegendHeight
? {
backgroundColor,
color,
height,
}
: {
backgroundColor,
color,
}
return (
<div
className="single-stat"
style={{backgroundColor: bgColor, color: textColor}}
>
<div className="single-stat" style={singleStatStyles}>
<span
className={classnames('single-stat--value', {
'single-stat--small': cellHeight === SMALL_CELL_HEIGHT,
@ -77,6 +92,7 @@ SingleStat.propTypes = {
prefix: string,
suffix: string,
lineGraph: bool,
staticLegendHeight: number,
}
export default SingleStat

View File

@ -1,6 +1,6 @@
import React, {PropTypes} from 'react'
import _ from 'lodash'
import uuid from 'node-uuid'
import uuid from 'uuid'
import ReactTooltip from 'react-tooltip'

View File

@ -1,6 +1,6 @@
import React, {PropTypes, Component} from 'react'
import _ from 'lodash'
import uuid from 'node-uuid'
import uuid from 'uuid'
import {removeMeasurement} from 'shared/graphs/helpers'
const staticLegendItemClassname = (visibilities, i, hoverEnabled) => {
@ -65,13 +65,10 @@ class StaticLegend extends Component {
}
render() {
const {dygraph} = this.props
const {dygraphSeries} = this.props
const {visibilities} = this.state
const labels = dygraph ? _.drop(dygraph.getLabels()) : []
const colors = dygraph
? _.map(labels, l => dygraph.attributes_.series_[l].options.color)
: []
const labels = _.keys(dygraphSeries)
const colors = _.map(labels, l => dygraphSeries[l].color)
const hoverEnabled = labels.length > 1
@ -105,7 +102,7 @@ class StaticLegend extends Component {
const {shape, func} = PropTypes
StaticLegend.propTypes = {
sharedLegend: shape({}),
dygraphSeries: shape({}),
dygraph: shape({}),
handleReceiveStaticLegendHeight: func.isRequired,
}

View File

@ -1,7 +1,7 @@
import React, {Component, PropTypes} from 'react'
import OnClickOutside from 'shared/components/OnClickOutside'
import uuid from 'node-uuid'
import uuid from 'uuid'
class TagsAddButton extends Component {
constructor(props) {

View File

@ -6,7 +6,7 @@ import OnClickOutside from 'shared/components/OnClickOutside'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import CustomTimeRangeOverlay from 'shared/components/CustomTimeRangeOverlay'
import timeRanges from 'hson!shared/data/timeRanges.hson'
import {timeRanges} from 'shared/data/timeRanges'
import {DROPDOWN_MENU_MAX_HEIGHT} from 'shared/constants/index'
const dateFormat = 'YYYY-MM-DD HH:mm'

View File

@ -430,20 +430,6 @@ export const DEFAULT_SOURCE = {
metaUrl: '',
}
export const intervalValuesPoints = [
{value: '333', type: 'points', selected: true},
]
export const interval = {
id: 'interval',
type: 'autoGroupBy',
tempVar: ':interval:',
label: 'automatically determine the best group by time',
values: intervalValuesPoints,
}
export const TEMPLATES = [interval]
export const IS_STATIC_LEGEND = legend =>
_.get(legend, 'type', false) === 'static'

View File

@ -1,8 +0,0 @@
[
{milliseconds: 0, inputValue: 'Paused', menuOption: 'Paused'},
{milliseconds: 5000, inputValue: 'Every 5 seconds', menuOption: 'Every 5 seconds'},
{milliseconds: 10000, inputValue: 'Every 10 seconds', menuOption: 'Every 10 seconds'},
{milliseconds: 15000, inputValue: 'Every 15 seconds', menuOption: 'Every 15 seconds'},
{milliseconds: 30000, inputValue: 'Every 30 seconds', menuOption: 'Every 30 seconds'},
{milliseconds: 60000, inputValue: 'Every 60 seconds', menuOption: 'Every 60 seconds'}
]

View File

@ -0,0 +1,30 @@
const autoRefreshItems = [
{milliseconds: 0, inputValue: 'Paused', menuOption: 'Paused'},
{
milliseconds: 5000,
inputValue: 'Every 5 seconds',
menuOption: 'Every 5 seconds',
},
{
milliseconds: 10000,
inputValue: 'Every 10 seconds',
menuOption: 'Every 10 seconds',
},
{
milliseconds: 15000,
inputValue: 'Every 15 seconds',
menuOption: 'Every 15 seconds',
},
{
milliseconds: 30000,
inputValue: 'Every 30 seconds',
menuOption: 'Every 30 seconds',
},
{
milliseconds: 60000,
inputValue: 'Every 60 seconds',
menuOption: 'Every 60 seconds',
},
]
export default autoRefreshItems

View File

@ -1,9 +0,0 @@
[
{type: 'null', text: 'null', menuOption: '(null)', inputValue: 'null', isValidForKapaNodes: true},
{type: 'previous', text: 'previous', menuOption: '(previous)', inputValue: 'previous', isValidForKapaNodes: false},
{type: 'number', text: 'number', menuOption: '(number)', inputValue: '0', isValidForKapaNodes: true},
{type: 'none', text: 'none', menuOption: '(none)', inputValue: 'none', isValidForKapaNodes: true},
{type: 'linear', text: 'linear', menuOption: '(linear)', inputValue: 'linear', isValidForKapaNodes: false},
]
// for if/when filtering FillQuery queryFill types not supported as Kapacitor nodes:
// queryFills.filter(fill => !(isKapacitorRule && !fill.isValidForKapaNodes))

View File

@ -0,0 +1,41 @@
const queryFills = [
{
type: 'null',
text: 'null',
menuOption: '(null)',
inputValue: 'null',
isValidForKapaNodes: true,
},
{
type: 'previous',
text: 'previous',
menuOption: '(previous)',
inputValue: 'previous',
isValidForKapaNodes: false,
},
{
type: 'number',
text: 'number',
menuOption: '(number)',
inputValue: '0',
isValidForKapaNodes: true,
},
{
type: 'none',
text: 'none',
menuOption: '(none)',
inputValue: 'none',
isValidForKapaNodes: true,
},
{
type: 'linear',
text: 'linear',
menuOption: '(linear)',
inputValue: 'linear',
isValidForKapaNodes: false,
},
]
// for if/when filtering FillQuery queryFill types not supported as Kapacitor nodes:
// queryFills.filter(fill => !(isKapacitorRule && !fill.isValidForKapaNodes))
export default queryFills

View File

@ -1,26 +0,0 @@
[
{
"id": "pastWeek",
"name": "Past Week"
},
{
"id": "pastMonth",
"name": "Past Month"
},
{
"id": "pastYear",
"name": "Past Year"
},
{
"id": "thisWeek",
"name": "This Week"
},
{
"id": "thisMonth",
"name": "This Month"
},
{
"id": "thisYear",
"name": "This Year"
}
]

View File

@ -0,0 +1,28 @@
const timeRangeDefaults = [
{
id: 'pastWeek',
name: 'Past Week',
},
{
id: 'pastMonth',
name: 'Past Month',
},
{
id: 'pastYear',
name: 'Past Year',
},
{
id: 'thisWeek',
name: 'This Week',
},
{
id: 'thisMonth',
name: 'This Month',
},
{
id: 'thisYear',
name: 'This Year',
},
]
export default timeRangeDefaults

View File

@ -1,11 +0,0 @@
[
{defaultGroupBy: '10s', seconds: 300, inputValue: 'Past 5 minutes', lower: 'now() - 5m', upper: null, menuOption: 'Past 5 minutes'},
{defaultGroupBy: '1m', seconds: 900, inputValue: 'Past 15 minutes', lower: 'now() - 15m', upper: null, menuOption: 'Past 15 minutes'},
{defaultGroupBy: '1m', seconds: 3600, inputValue: 'Past hour', lower: 'now() - 1h', upper: null, menuOption: 'Past hour'},
{defaultGroupBy: '1m', seconds: 21600, inputValue: 'Past 6 hours', lower: 'now() - 6h', upper: null, menuOption: 'Past 6 hours'},
{defaultGroupBy: '5m', seconds: 43200, inputValue: 'Past 12 hours', lower: 'now() - 12h', upper: null, menuOption: 'Past 12 hours'},
{defaultGroupBy: '10m', seconds: 86400, inputValue: 'Past 24 hours', lower: 'now() - 24h', upper: null, menuOption: 'Past 24 hours'},
{defaultGroupBy: '30m', seconds: 172800, inputValue: 'Past 2 days', lower: 'now() - 2d', upper: null, menuOption: 'Past 2 days'},
{defaultGroupBy: '1h', seconds: 604800, inputValue: 'Past 7 days', lower: 'now() - 7d', upper: null, menuOption: 'Past 7 days'},
{defaultGroupBy: '6h', seconds: 2592000, inputValue: 'Past 30 days', lower: 'now() - 30d', upper: null, menuOption: 'Past 30 days'},
]

View File

@ -0,0 +1,74 @@
export const timeRanges = [
{
defaultGroupBy: '10s',
seconds: 300,
inputValue: 'Past 5 minutes',
lower: 'now() - 5m',
upper: null,
menuOption: 'Past 5 minutes',
},
{
defaultGroupBy: '1m',
seconds: 900,
inputValue: 'Past 15 minutes',
lower: 'now() - 15m',
upper: null,
menuOption: 'Past 15 minutes',
},
{
defaultGroupBy: '1m',
seconds: 3600,
inputValue: 'Past hour',
lower: 'now() - 1h',
upper: null,
menuOption: 'Past hour',
},
{
defaultGroupBy: '1m',
seconds: 21600,
inputValue: 'Past 6 hours',
lower: 'now() - 6h',
upper: null,
menuOption: 'Past 6 hours',
},
{
defaultGroupBy: '5m',
seconds: 43200,
inputValue: 'Past 12 hours',
lower: 'now() - 12h',
upper: null,
menuOption: 'Past 12 hours',
},
{
defaultGroupBy: '10m',
seconds: 86400,
inputValue: 'Past 24 hours',
lower: 'now() - 24h',
upper: null,
menuOption: 'Past 24 hours',
},
{
defaultGroupBy: '30m',
seconds: 172800,
inputValue: 'Past 2 days',
lower: 'now() - 2d',
upper: null,
menuOption: 'Past 2 days',
},
{
defaultGroupBy: '1h',
seconds: 604800,
inputValue: 'Past 7 days',
lower: 'now() - 7d',
upper: null,
menuOption: 'Past 7 days',
},
{
defaultGroupBy: '6h',
seconds: 2592000,
inputValue: 'Past 30 days',
lower: 'now() - 30d',
upper: null,
menuOption: 'Past 30 days',
},
]

View File

@ -17,8 +17,8 @@ export const timeRangeType = ({upper, lower, type}) => {
return INVALID
}
const isUpperValid = moment(upper).isValid()
const isLowerValid = moment(lower).isValid()
const isUpperValid = moment(new Date(upper)).isValid()
const isLowerValid = moment(new Date(lower)).isValid()
// {lower: <Date>, upper: <Date>}
if (isLowerValid && isUpperValid) {
@ -103,8 +103,8 @@ const getMomentUnit = unit => {
export const shiftDate = (date, quantity, unit) => {
if (!date && !quantity && !unit) {
return moment(date)
return moment(new Date(date))
}
return moment(date).add(quantity, getMomentUnit(unit))
return moment(new Date(date)).add(quantity, getMomentUnit(unit))
}

View File

@ -1,5 +1,5 @@
import {AUTOREFRESH_DEFAULT} from 'shared/constants'
import timeRanges from 'hson!shared/data/timeRanges.hson'
import {timeRanges} from 'shared/data/timeRanges'
import * as actionTypes from 'src/status/constants/actionTypes'

View File

@ -93,6 +93,9 @@
height: 100% !important;
}
}
.dygraph-child > .single-stat {
z-index: 3;
}
.single-stat--value {
position: absolute;
top: 50%;

View File

@ -1,6 +1,6 @@
import {buildQuery} from 'utils/influxql'
import {TYPE_SHIFTED, TYPE_QUERY_CONFIG} from 'src/dashboards/constants'
import timeRanges from 'hson!shared/data/timeRanges.hson'
import {timeRanges} from 'shared/data/timeRanges'
const buildCannedDashboardQuery = (query, {lower, upper}, host) => {
const {defaultGroupBy} = timeRanges.find(range => range.lower === lower) || {

View File

@ -1,4 +1,4 @@
import uuid from 'node-uuid'
import uuid from 'uuid'
import {NULL_STRING} from 'shared/constants/queryFillOptions'

View File

@ -157,9 +157,9 @@ function _buildGroupByTime(groupBy) {
return ''
}
return ` GROUP BY time(${groupBy.time === AUTO_GROUP_BY
return ` GROUP BY ${groupBy.time === AUTO_GROUP_BY
? TEMP_VAR_INTERVAL
: `${groupBy.time}`})`
: `time(${groupBy.time})`}`
}
function _buildGroupByTags(groupBy) {

View File

@ -1,19 +0,0 @@
const express = require('express')
const request = require('request')
const {default: storybook} = require('@kadira/storybook/dist/server/middleware')
const app = express()
const handler = (req, res) => {
console.log(`${req.method} ${req.url}`)
const url = 'http://localhost:8888' + req.url
req.pipe(request(url)).pipe(res)
}
app.use(storybook('./.storybook'))
app.get('/chronograf/v1/*', handler)
app.post('/chronograf/v1/*', handler)
app.listen(6006, () => {
console.log('storybook proxy server now running')
})

Some files were not shown because too many files have changed in this diff Show More