diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b420dcc374..867253ccd18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * **LDAP**: Upgrade go-ldap to v3 [#14548](https://github.com/grafana/grafana/issues/14548) * **Proxy whitelist**: Add CIDR capability to auth_proxy whitelist [#14546](https://github.com/grafana/grafana/issues/14546), thx [@jacobrichard](https://github.com/jacobrichard) * **OAuth**: Support OAuth providers that are not RFC6749 compliant [#14562](https://github.com/grafana/grafana/issues/14562), thx [@tdabasinskas](https://github.com/tdabasinskas) +* **Units**: Add blood glucose level units mg/dL and mmol/L [#14519](https://github.com/grafana/grafana/issues/14519), thx [@kjedamzik](https://github.com/kjedamzik) ### Bug fixes * **Search**: Fix for issue with scrolling the "tags filter" dropdown, fixes [#14486](https://github.com/grafana/grafana/issues/14486) @@ -24,7 +25,7 @@ * **Datasource admin**: Fix for issue creating new data source when same name exists [#14467](https://github.com/grafana/grafana/issues/14467) * **OAuth**: Fix for oauth auto login setting, can now be set using env variable [#14435](https://github.com/grafana/grafana/issues/14435) -* **Dashboard search**: Fix for searching tags in tags filter dropdown. +* **Dashboard search**: Fix for searching tags in tags filter dropdown. # 5.4.1 (2018-12-10) diff --git a/conf/defaults.ini b/conf/defaults.ini index 97c87edb8b2..7f61ac96870 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -103,6 +103,9 @@ server_cert_name = # For "sqlite3" only, path relative to data_path setting path = grafana.db +# For "sqlite3" only. cache mode setting used for connecting to the database +cache_mode = private + #################################### Session ############################# [session] # Either "memory", "file", "redis", "mysql", "postgres", "memcache", default is "file" diff --git a/conf/sample.ini b/conf/sample.ini index 473e4e8450c..014016d45bc 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -99,6 +99,9 @@ # Set to true to log the sql calls and execution times. log_queries = +# For "sqlite3" only. cache mode setting used for connecting to the database. (private, shared) +;cache_mode = private + #################################### Session #################################### [session] # Either "memory", "file", "redis", "mysql", "postgres", default is "file" diff --git a/devenv/docker/blocks/alert_webhook_listener/Dockerfile b/devenv/docker/blocks/alert_webhook_listener/Dockerfile new file mode 100644 index 00000000000..a6bb87f15f5 --- /dev/null +++ b/devenv/docker/blocks/alert_webhook_listener/Dockerfile @@ -0,0 +1,7 @@ + +FROM golang:latest +ADD main.go / +WORKDIR / +RUN go build -o main . +EXPOSE 3010 +ENTRYPOINT ["/main"] diff --git a/devenv/docker/blocks/alert_webhook_listener/docker-compose.yaml b/devenv/docker/blocks/alert_webhook_listener/docker-compose.yaml new file mode 100644 index 00000000000..3e6c5389f6c --- /dev/null +++ b/devenv/docker/blocks/alert_webhook_listener/docker-compose.yaml @@ -0,0 +1,5 @@ + alert_webhook_listener: + build: docker/blocks/alert_webhook_listener + network_mode: host + ports: + - "3010:3010" diff --git a/devenv/docker/blocks/alert_webhook_listener/main.go b/devenv/docker/blocks/alert_webhook_listener/main.go new file mode 100644 index 00000000000..355c903e9f6 --- /dev/null +++ b/devenv/docker/blocks/alert_webhook_listener/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "io" + "io/ioutil" + "net/http" +) + +func hello(w http.ResponseWriter, r *http.Request) { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return + } + + line := fmt.Sprintf("webbhook: -> %s", string(body)) + fmt.Println(line) + io.WriteString(w, line) +} + +func main() { + http.HandleFunc("/", hello) + http.ListenAndServe(":3010", nil) +} diff --git a/docs/sources/auth/auth-proxy.md b/docs/sources/auth/auth-proxy.md index e066eed9190..2274771b91a 100644 --- a/docs/sources/auth/auth-proxy.md +++ b/docs/sources/auth/auth-proxy.md @@ -31,9 +31,10 @@ auto_sign_up = true ldap_sync_ttl = 60 # Limit where auth proxy requests come from by configuring a list of IP addresses. # This can be used to prevent users spoofing the X-WEBAUTH-USER header. +# Example `whitelist = 192.168.1.1, 192.168.1.0/24, 2001::23, 2001::0/120` whitelist = # Optionally define more headers to sync other user attributes -# Example `headers = Name:X-WEBAUTH-NAME Email:X-WEBAUTH-EMAIL`` +# Example `headers = Name:X-WEBAUTH-NAME Email:X-WEBAUTH-EMAIL` headers = ``` diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index 30ef020a3de..0e5a55b3c0e 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -250,6 +250,12 @@ Sets the maximum amount of time a connection may be reused. The default is 14400 Set to `true` to log the sql calls and execution times. +### cache_mode + +For "sqlite3" only. [Shared cache](https://www.sqlite.org/sharedcache.html) setting used for connecting to the database. (private, shared) +Defaults to private. + +
## [security] diff --git a/jest.config.js b/jest.config.js index cac634fbf10..c5c6bcb9f5f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,7 +6,9 @@ module.exports = { }, "moduleDirectories": ["node_modules", "public"], "roots": [ - "/public" + "/public/app", + "/public/test", + "/packages" ], "testRegex": "(\\.|/)(test)\\.(jsx?|tsx?)$", "moduleFileExtensions": [ diff --git a/package.json b/package.json index d374cd524b4..eefe2cbbe53 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,5 @@ { + "private": true, "author": { "name": "Torkel Ödegaard", "company": "Grafana Labs" @@ -11,14 +12,16 @@ }, "devDependencies": { "@babel/core": "^7.1.2", - "@rtsao/plugin-proposal-class-properties": "^7.0.1-patch.1", "@babel/plugin-syntax-dynamic-import": "^7.0.0", "@babel/preset-env": "^7.1.0", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.1.0", + "@rtsao/plugin-proposal-class-properties": "^7.0.1-patch.1", + "@types/classnames": "^2.2.6", "@types/d3": "^4.10.1", "@types/enzyme": "^3.1.13", "@types/jest": "^23.3.2", + "@types/jquery": "^1.10.35", "@types/node": "^8.0.31", "@types/react": "^16.7.6", "@types/react-custom-scrollbars": "^4.0.5", @@ -49,15 +52,12 @@ "grunt-cli": "~1.2.0", "grunt-contrib-clean": "~1.0.0", "grunt-contrib-compress": "^1.3.0", - "grunt-contrib-concat": "^1.0.1", "grunt-contrib-copy": "~1.0.0", - "grunt-contrib-cssmin": "~1.0.2", "grunt-exec": "^1.0.1", "grunt-newer": "^1.3.0", "grunt-notify": "^0.4.5", "grunt-postcss": "^0.8.0", - "grunt-sass": "^2.0.0", - "grunt-sass-lint": "^0.2.2", + "grunt-sass-lint": "^0.2.4", "grunt-usemin": "3.1.1", "grunt-webpack": "^3.0.2", "html-loader": "^0.5.1", @@ -73,6 +73,7 @@ "ng-annotate-webpack-plugin": "^0.3.0", "ngtemplate-loader": "^2.0.1", "npm": "^5.4.2", + "node-sass": "^4.11.0", "optimize-css-assets-webpack-plugin": "^4.0.2", "phantomjs-prebuilt": "^2.1.15", "postcss-browser-reporter": "^0.5.0", @@ -92,6 +93,7 @@ "tslib": "^1.9.3", "tslint": "^5.8.0", "tslint-loader": "^3.5.3", + "tslint-react": "^3.6.0", "typescript": "^3.0.3", "uglifyjs-webpack-plugin": "^1.2.7", "webpack": "4.19.1", @@ -108,15 +110,30 @@ "watch": "webpack --progress --colors --watch --mode development --config scripts/webpack/webpack.dev.js", "build": "grunt build", "test": "grunt test", - "lint": "tslint -c tslint.json --project tsconfig.json", + "tslint": "tslint -c tslint.json --project tsconfig.json", + "typecheck": "tsc --noEmit", "jest": "jest --notify --watch", "api-tests": "jest --notify --watch --config=tests/api/jest.js", - "precommit": "lint-staged && grunt precommit" + "precommit": "grunt precommit" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged && grunt precommit" + } }, "lint-staged": { - "*.{ts,tsx}": ["prettier --write", "git add"], - "*.scss": ["prettier --write", "git add"], - "*pkg/**/*.go": ["gofmt -w -s", "git add"] + "*.{ts,tsx}": [ + "prettier --write", + "git add" + ], + "*.scss": [ + "prettier --write", + "git add" + ], + "*pkg/**/*.go": [ + "gofmt -w -s", + "git add" + ] }, "prettier": { "trailingComma": "es5", @@ -126,6 +143,7 @@ "license": "Apache-2.0", "dependencies": { "@babel/polyfill": "^7.0.0", + "@torkelo/react-select": "2.1.1", "angular": "1.6.6", "angular-bindonce": "0.3.1", "angular-native-dragdrop": "1.2.2", @@ -133,7 +151,7 @@ "angular-sanitize": "1.6.6", "baron": "^3.0.3", "brace": "^0.10.0", - "classnames": "^2.2.5", + "classnames": "^2.2.6", "clipboard": "^1.7.1", "d3": "^4.11.0", "d3-scale-chromatic": "^1.3.0", @@ -152,10 +170,9 @@ "react-custom-scrollbars": "^4.2.1", "react-dom": "^16.6.3", "react-grid-layout": "0.16.6", - "react-popper": "^1.3.0", "react-highlight-words": "0.11.0", + "react-popper": "^1.3.0", "react-redux": "^5.0.7", - "@torkelo/react-select": "2.1.1", "react-sizeme": "^2.3.6", "react-table": "^6.8.6", "react-transition-group": "^2.2.1", @@ -165,18 +182,26 @@ "redux-thunk": "^2.3.0", "remarkable": "^1.7.1", "rst2html": "github:thoward/rst2html#990cb89", - "rxjs": "^5.4.3", + "rxjs": "^6.3.3", "slate": "^0.33.4", "slate-plain-serializer": "^0.5.10", "slate-prism": "^0.5.0", "slate-react": "^0.12.4", "tether": "^1.4.0", "tether-drop": "https://github.com/torkelo/drop/tarball/master", - "tinycolor2": "^1.4.1", - "tslint-react": "^3.6.0" + "tinycolor2": "^1.4.1" }, "resolutions": { "caniuse-db": "1.0.30000772", "**/@types/react": "16.7.6" + }, + "workspaces": { + "packages": [ + "packages/*" + ], + "nohoist": [ + "**/@types/*", + "**/@types/*/**" + ] } } diff --git a/packages/grafana-build/README.md b/packages/grafana-build/README.md new file mode 100644 index 00000000000..588d91861d3 --- /dev/null +++ b/packages/grafana-build/README.md @@ -0,0 +1,4 @@ +# Shared build scripts + +Shared build scripts for plugins & internal packages. + diff --git a/packages/grafana-build/package.json b/packages/grafana-build/package.json new file mode 100644 index 00000000000..24fb648c8d4 --- /dev/null +++ b/packages/grafana-build/package.json @@ -0,0 +1,13 @@ +{ + "name": "@grafana/build", + "private": true, + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "tslint": "echo \"Nothing to do\"", + "typecheck": "echo \"Nothing to do\"" + }, + "author": "", + "license": "ISC" +} diff --git a/packages/grafana-ui/README.md b/packages/grafana-ui/README.md new file mode 100644 index 00000000000..1413965f7da --- /dev/null +++ b/packages/grafana-ui/README.md @@ -0,0 +1,3 @@ +# Grafana (WIP) shared component library + +Used by internal & external plugins. diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json new file mode 100644 index 00000000000..2fb210e3b46 --- /dev/null +++ b/packages/grafana-ui/package.json @@ -0,0 +1,33 @@ +{ + "name": "@grafana/ui", + "version": "1.0.0", + "description": "", + "main": "src/index.ts", + "scripts": { + "tslint": "tslint -c tslint.json --project tsconfig.json", + "typecheck": "tsc --noEmit" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@torkelo/react-select": "2.1.1", + "classnames": "^2.2.5", + "jquery": "^3.2.1", + "lodash": "^4.17.10", + "moment": "^2.22.2", + "react": "^16.6.3", + "react-dom": "^16.6.3", + "react-highlight-words": "0.11.0", + "react-popper": "^1.3.0", + "react-transition-group": "^2.2.1", + "react-virtualized": "^9.21.0" + }, + "devDependencies": { + "@types/jest": "^23.3.2", + "@types/lodash": "^4.14.119", + "@types/react": "^16.7.6", + "@types/classnames": "^2.2.6", + "@types/jquery": "^1.10.35", + "typescript": "^3.2.2" + } +} diff --git a/public/app/core/components/DeleteButton/DeleteButton.test.tsx b/packages/grafana-ui/src/components/DeleteButton/DeleteButton.test.tsx similarity index 85% rename from public/app/core/components/DeleteButton/DeleteButton.test.tsx rename to packages/grafana-ui/src/components/DeleteButton/DeleteButton.test.tsx index 12acadee18a..f6d5a676971 100644 --- a/public/app/core/components/DeleteButton/DeleteButton.test.tsx +++ b/packages/grafana-ui/src/components/DeleteButton/DeleteButton.test.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import DeleteButton from './DeleteButton'; +import { DeleteButton } from './DeleteButton'; import { shallow } from 'enzyme'; describe('DeleteButton', () => { - let wrapper; - let deleted; + let wrapper: any; + let deleted: any; beforeAll(() => { deleted = false; @@ -12,7 +12,8 @@ describe('DeleteButton', () => { function deleteItem() { deleted = true; } - wrapper = shallow( deleteItem()} />); + + wrapper = shallow( deleteItem()} />); }); it('should show confirm delete when clicked', () => { diff --git a/public/app/core/components/DeleteButton/DeleteButton.tsx b/packages/grafana-ui/src/components/DeleteButton/DeleteButton.tsx similarity index 74% rename from public/app/core/components/DeleteButton/DeleteButton.tsx rename to packages/grafana-ui/src/components/DeleteButton/DeleteButton.tsx index a83ce6097ad..df65d156ab3 100644 --- a/public/app/core/components/DeleteButton/DeleteButton.tsx +++ b/packages/grafana-ui/src/components/DeleteButton/DeleteButton.tsx @@ -1,19 +1,19 @@ -import React, { PureComponent } from 'react'; +import React, { PureComponent, SyntheticEvent } from 'react'; -export interface DeleteButtonProps { - onConfirmDelete(); +interface Props { + onConfirm(): void; } -export interface DeleteButtonStates { +interface State { showConfirm: boolean; } -export default class DeleteButton extends PureComponent { - state: DeleteButtonStates = { +export class DeleteButton extends PureComponent { + state: State = { showConfirm: false, }; - onClickDelete = event => { + onClickDelete = (event: SyntheticEvent) => { if (event) { event.preventDefault(); } @@ -23,7 +23,7 @@ export default class DeleteButton extends PureComponent { + onClickCancel = (event: SyntheticEvent) => { if (event) { event.preventDefault(); } @@ -33,7 +33,7 @@ export default class DeleteButton extends PureComponent Cancel - + Confirm Delete diff --git a/public/sass/components/_delete_button.scss b/packages/grafana-ui/src/components/DeleteButton/_DeleteButton.scss similarity index 100% rename from public/sass/components/_delete_button.scss rename to packages/grafana-ui/src/components/DeleteButton/_DeleteButton.scss diff --git a/packages/grafana-ui/src/components/index.scss b/packages/grafana-ui/src/components/index.scss new file mode 100644 index 00000000000..d52508c946c --- /dev/null +++ b/packages/grafana-ui/src/components/index.scss @@ -0,0 +1 @@ +@import 'DeleteButton/DeleteButton'; diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts new file mode 100644 index 00000000000..b57b9bcfdb7 --- /dev/null +++ b/packages/grafana-ui/src/components/index.ts @@ -0,0 +1 @@ +export { DeleteButton } from './DeleteButton/DeleteButton'; diff --git a/packages/grafana-ui/src/forms/GfFormLabel/GfFormLabel.tsx b/packages/grafana-ui/src/forms/GfFormLabel/GfFormLabel.tsx new file mode 100644 index 00000000000..8b80de64696 --- /dev/null +++ b/packages/grafana-ui/src/forms/GfFormLabel/GfFormLabel.tsx @@ -0,0 +1,23 @@ +import React, { SFC, ReactNode } from 'react'; +import classNames from 'classnames'; + +interface Props { + children: ReactNode; + htmlFor?: string; + className?: string; + isFocused?: boolean; + isInvalid?: boolean; +} + +export const GfFormLabel: SFC = ({ children, isFocused, isInvalid, className, htmlFor, ...rest }) => { + const classes = classNames('gf-form-label', className, { + 'gf-form-label--is-focused': isFocused, + 'gf-form-label--is-invalid': isInvalid, + }); + + return ( + + ); +}; diff --git a/packages/grafana-ui/src/forms/index.ts b/packages/grafana-ui/src/forms/index.ts new file mode 100644 index 00000000000..bb6998b0025 --- /dev/null +++ b/packages/grafana-ui/src/forms/index.ts @@ -0,0 +1 @@ +export { GfFormLabel } from './GfFormLabel/GfFormLabel'; diff --git a/packages/grafana-ui/src/index.scss b/packages/grafana-ui/src/index.scss new file mode 100644 index 00000000000..841415620d6 --- /dev/null +++ b/packages/grafana-ui/src/index.scss @@ -0,0 +1 @@ +@import 'components/index'; diff --git a/packages/grafana-ui/src/index.ts b/packages/grafana-ui/src/index.ts new file mode 100644 index 00000000000..b22152497b9 --- /dev/null +++ b/packages/grafana-ui/src/index.ts @@ -0,0 +1,5 @@ +export * from './components'; +export * from './visualizations'; +export * from './types'; +export * from './utils'; +export * from './forms'; diff --git a/packages/grafana-ui/src/types/index.ts b/packages/grafana-ui/src/types/index.ts new file mode 100644 index 00000000000..f618ce6db34 --- /dev/null +++ b/packages/grafana-ui/src/types/index.ts @@ -0,0 +1,3 @@ +export * from './series'; +export * from './time'; +export * from './panel'; diff --git a/packages/grafana-ui/src/types/jquery.d.ts b/packages/grafana-ui/src/types/jquery.d.ts new file mode 100644 index 00000000000..4a6f60b6029 --- /dev/null +++ b/packages/grafana-ui/src/types/jquery.d.ts @@ -0,0 +1,17 @@ +interface JQueryPlot { + (element: HTMLElement | JQuery, data: any, options: any): void; + plugins: any[]; +} + +interface JQueryStatic { + plot: JQueryPlot; +} + +interface JQuery { + place_tt: any; + modal: any; + tagsinput: any; + typeahead: any; + accessKey: any; + tooltip: any; +} diff --git a/packages/grafana-ui/src/types/panel.ts b/packages/grafana-ui/src/types/panel.ts new file mode 100644 index 00000000000..44336555a81 --- /dev/null +++ b/packages/grafana-ui/src/types/panel.ts @@ -0,0 +1,31 @@ +import { TimeSeries, LoadingState } from './series'; +import { TimeRange } from './time'; + +export interface PanelProps { + timeSeries: TimeSeries[]; + timeRange: TimeRange; + loading: LoadingState; + options: T; + renderCounter: number; + width: number; + height: number; +} + +export interface PanelOptionsProps { + options: T; + onChange: (options: T) => void; +} + +export interface PanelSize { + width: number; + height: number; +} + +export interface PanelMenuItem { + type?: 'submenu' | 'divider'; + text?: string; + iconClassName?: string; + onClick?: () => void; + shortcut?: string; + subMenu?: PanelMenuItem[]; +} diff --git a/packages/grafana-ui/src/types/series.ts b/packages/grafana-ui/src/types/series.ts new file mode 100644 index 00000000000..49662e9872d --- /dev/null +++ b/packages/grafana-ui/src/types/series.ts @@ -0,0 +1,53 @@ +export enum LoadingState { + NotStarted = 'NotStarted', + Loading = 'Loading', + Done = 'Done', + Error = 'Error', +} + +export type TimeSeriesValue = number | null; + +export type TimeSeriesPoints = TimeSeriesValue[][]; + +export interface TimeSeries { + target: string; + datapoints: TimeSeriesPoints; + unit?: string; +} + +/** View model projection of a time series */ +export interface TimeSeriesVM { + label: string; + color: string; + data: TimeSeriesValue[][]; + stats: TimeSeriesStats; +} + +export interface TimeSeriesStats { + total: number | null; + max: number | null; + min: number | null; + logmin: number; + avg: number | null; + current: number | null; + first: number | null; + delta: number; + diff: number | null; + range: number | null; + timeStep: number; + count: number; + allIsNull: boolean; + allIsZero: boolean; +} + +export enum NullValueMode { + Null = 'null', + Ignore = 'connected', + AsZero = 'null as zero', +} + +/** View model projection of many time series */ +export interface TimeSeriesVMs { + [index: number]: TimeSeriesVM; + length: number; +} diff --git a/packages/grafana-ui/src/types/time.ts b/packages/grafana-ui/src/types/time.ts new file mode 100644 index 00000000000..b6acf7f07b6 --- /dev/null +++ b/packages/grafana-ui/src/types/time.ts @@ -0,0 +1,17 @@ +import { Moment } from 'moment'; + +export interface RawTimeRange { + from: Moment | string; + to: Moment | string; +} + +export interface TimeRange { + from: Moment; + to: Moment; + raw: RawTimeRange; +} + +export interface IntervalValues { + interval: string; // 10s,5m + intervalMs: number; +} diff --git a/packages/grafana-ui/src/utils/index.ts b/packages/grafana-ui/src/utils/index.ts new file mode 100644 index 00000000000..4d9b9a4b948 --- /dev/null +++ b/packages/grafana-ui/src/utils/index.ts @@ -0,0 +1 @@ +export * from './processTimeSeries'; diff --git a/packages/grafana-ui/src/utils/processTimeSeries.ts b/packages/grafana-ui/src/utils/processTimeSeries.ts new file mode 100644 index 00000000000..e92aaf0c1a6 --- /dev/null +++ b/packages/grafana-ui/src/utils/processTimeSeries.ts @@ -0,0 +1,174 @@ +// Libraries +import _ from 'lodash'; + +// Types +import { TimeSeries, TimeSeriesVMs, NullValueMode, TimeSeriesValue } from '../types'; + +interface Options { + timeSeries: TimeSeries[]; + nullValueMode: NullValueMode; + colorPalette: string[]; +} + +export function processTimeSeries({ timeSeries, nullValueMode, colorPalette }: Options): TimeSeriesVMs { + const vmSeries = timeSeries.map((item, index) => { + const colorIndex = index % colorPalette.length; + const label = item.target; + const result = []; + + // stat defaults + let total = 0; + let max: TimeSeriesValue = -Number.MAX_VALUE; + let min: TimeSeriesValue = Number.MAX_VALUE; + let logmin = Number.MAX_VALUE; + let avg: TimeSeriesValue = null; + let current: TimeSeriesValue = null; + let first: TimeSeriesValue = null; + let delta: TimeSeriesValue = 0; + let diff: TimeSeriesValue = null; + let range: TimeSeriesValue = null; + let timeStep = Number.MAX_VALUE; + let allIsNull = true; + let allIsZero = true; + + const ignoreNulls = nullValueMode === NullValueMode.Ignore; + const nullAsZero = nullValueMode === NullValueMode.AsZero; + + let currentTime: TimeSeriesValue = null; + let currentValue: TimeSeriesValue = null; + let nonNulls = 0; + let previousTime: TimeSeriesValue = null; + let previousValue = 0; + let previousDeltaUp = true; + + for (let i = 0; i < item.datapoints.length; i++) { + currentValue = item.datapoints[i][0]; + currentTime = item.datapoints[i][1]; + + if (typeof currentTime !== 'number') { + continue; + } + + if (typeof currentValue !== 'number') { + continue; + } + + // Due to missing values we could have different timeStep all along the series + // so we have to find the minimum one (could occur with aggregators such as ZimSum) + if (previousTime !== null && currentTime !== null) { + const currentStep = currentTime - previousTime; + if (currentStep < timeStep) { + timeStep = currentStep; + } + } + + previousTime = currentTime; + + if (currentValue === null) { + if (ignoreNulls) { + continue; + } + if (nullAsZero) { + currentValue = 0; + } + } + + if (currentValue !== null) { + if (_.isNumber(currentValue)) { + total += currentValue; + allIsNull = false; + nonNulls++; + } + + if (currentValue > max) { + max = currentValue; + } + + if (currentValue < min) { + min = currentValue; + } + + if (first === null) { + first = currentValue; + } else { + if (previousValue > currentValue) { + // counter reset + previousDeltaUp = false; + if (i === item.datapoints.length - 1) { + // reset on last + delta += currentValue; + } + } else { + if (previousDeltaUp) { + delta += currentValue - previousValue; // normal increment + } else { + delta += currentValue; // account for counter reset + } + previousDeltaUp = true; + } + } + previousValue = currentValue; + + if (currentValue < logmin && currentValue > 0) { + logmin = currentValue; + } + + if (currentValue !== 0) { + allIsZero = false; + } + } + + result.push([currentTime, currentValue]); + } + + if (max === -Number.MAX_VALUE) { + max = null; + } + + if (min === Number.MAX_VALUE) { + min = null; + } + + if (result.length && !allIsNull) { + avg = total / nonNulls; + current = result[result.length - 1][1]; + if (current === null && result.length > 1) { + current = result[result.length - 2][1]; + } + } + + if (max !== null && min !== null) { + range = max - min; + } + + if (current !== null && first !== null) { + diff = current - first; + } + + const count = result.length; + + return { + data: result, + label: label, + color: colorPalette[colorIndex], + stats: { + total, + min, + max, + current, + logmin, + avg, + diff, + delta, + timeStep, + range, + count, + first, + allIsZero, + allIsNull, + }, + }; + }); + + return vmSeries; +} diff --git a/public/app/viz/Graph.tsx b/packages/grafana-ui/src/visualizations/Graph/Graph.tsx similarity index 90% rename from public/app/viz/Graph.tsx rename to packages/grafana-ui/src/visualizations/Graph/Graph.tsx index bdababb3e50..51afb33802d 100644 --- a/public/app/viz/Graph.tsx +++ b/packages/grafana-ui/src/visualizations/Graph/Graph.tsx @@ -1,11 +1,9 @@ // Libraries import $ from 'jquery'; import React, { PureComponent } from 'react'; -import 'vendor/flot/jquery.flot'; -import 'vendor/flot/jquery.flot.time'; // Types -import { TimeRange, TimeSeriesVMs } from 'app/types'; +import { TimeRange, TimeSeriesVMs } from '../../types'; interface GraphProps { timeSeries: TimeSeriesVMs; @@ -24,7 +22,7 @@ export class Graph extends PureComponent { showBars: false, }; - element: HTMLElement; + element: HTMLElement | null; componentDidUpdate() { this.draw(); @@ -35,6 +33,10 @@ export class Graph extends PureComponent { } draw() { + if (this.element === null) { + return; + } + const { width, timeSeries, timeRange, showLines, showBars, showPoints } = this.props; if (!width) { @@ -76,7 +78,7 @@ export class Graph extends PureComponent { max: max, label: 'Datetime', ticks: ticks, - timeformat: time_format(ticks, min, max), + timeformat: timeFormat(ticks, min, max), }, grid: { minBorderMargin: 0, @@ -109,7 +111,7 @@ export class Graph extends PureComponent { } // Copied from graph.ts -function time_format(ticks, min, max) { +function timeFormat(ticks: number, min: number, max: number): string { if (min && max && ticks) { const range = max - min; const secPerTick = range / ticks / 1000; diff --git a/packages/grafana-ui/src/visualizations/index.ts b/packages/grafana-ui/src/visualizations/index.ts new file mode 100644 index 00000000000..967432d37c9 --- /dev/null +++ b/packages/grafana-ui/src/visualizations/index.ts @@ -0,0 +1 @@ +export { Graph } from './Graph/Graph'; diff --git a/packages/grafana-ui/tsconfig.json b/packages/grafana-ui/tsconfig.json new file mode 100644 index 00000000000..ed6009f1ebd --- /dev/null +++ b/packages/grafana-ui/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "src/**/*.ts", + "src/**/*.tsx" + ], + "exclude": [ + "dist" + ], + "compilerOptions": { + "rootDir": ".", + "module": "esnext", + "outDir": "dist", + "declaration": true, + "noImplicitAny": true, + "strictNullChecks": true + } +} diff --git a/packages/grafana-ui/tslint.json b/packages/grafana-ui/tslint.json new file mode 100644 index 00000000000..0946f20963a --- /dev/null +++ b/packages/grafana-ui/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tslint.json" +} diff --git a/pkg/login/ldap.go b/pkg/login/ldap.go index 218f7321e65..402160ef5e3 100644 --- a/pkg/login/ldap.go +++ b/pkg/login/ldap.go @@ -292,6 +292,8 @@ func (a *ldapAuther) searchForUser(username string) (*LdapUserInfo, error) { Filter: strings.Replace(a.server.SearchFilter, "%s", ldap.EscapeFilter(username), -1), } + a.log.Debug("Ldap Search For User Request", "info", spew.Sdump(searchReq)) + searchResult, err = a.conn.Search(&searchReq) if err != nil { return nil, err diff --git a/pkg/services/dashboards/dashboard_service.go b/pkg/services/dashboards/dashboard_service.go index 7e334ff656f..59ceefa0be5 100644 --- a/pkg/services/dashboards/dashboard_service.go +++ b/pkg/services/dashboards/dashboard_service.go @@ -76,7 +76,7 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO, return nil, models.ErrDashboardFolderCannotHaveParent } - if dash.IsFolder && strings.ToLower(dash.Title) == strings.ToLower(models.RootFolderName) { + if dash.IsFolder && strings.EqualFold(dash.Title, models.RootFolderName) { return nil, models.ErrDashboardFolderNameExists } @@ -175,7 +175,9 @@ func (dr *dashboardServiceImpl) SaveProvisionedDashboard(dto *SaveDashboardDTO, dto.User = &models.SignedInUser{ UserId: 0, OrgRole: models.ROLE_ADMIN, + OrgId: dto.OrgId, } + cmd, err := dr.buildSaveDashboardCommand(dto, true, false) if err != nil { return nil, err diff --git a/pkg/services/notifications/webhook.go b/pkg/services/notifications/webhook.go index a236a1d1c4e..dbe441c915e 100644 --- a/pkg/services/notifications/webhook.go +++ b/pkg/services/notifications/webhook.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "io" "io/ioutil" "net" "net/http" @@ -69,11 +70,14 @@ func (ns *NotificationService) sendWebRequestSync(ctx context.Context, webhook * return err } + defer resp.Body.Close() + if resp.StatusCode/100 == 2 { + // flushing the body enables the transport to reuse the same connection + io.Copy(ioutil.Discard, resp.Body) return nil } - defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return err diff --git a/pkg/services/session/mysql.go b/pkg/services/session/mysql.go index f8c5d828cfa..694e16dfb90 100644 --- a/pkg/services/session/mysql.go +++ b/pkg/services/session/mysql.go @@ -29,18 +29,22 @@ import ( // MysqlStore represents a mysql session store implementation. type MysqlStore struct { - c *sql.DB - sid string - lock sync.RWMutex - data map[interface{}]interface{} + c *sql.DB + sid string + lock sync.RWMutex + data map[interface{}]interface{} + expiry int64 + dirty bool } // NewMysqlStore creates and returns a mysql session store. -func NewMysqlStore(c *sql.DB, sid string, kv map[interface{}]interface{}) *MysqlStore { +func NewMysqlStore(c *sql.DB, sid string, kv map[interface{}]interface{}, expiry int64) *MysqlStore { return &MysqlStore{ - c: c, - sid: sid, - data: kv, + c: c, + sid: sid, + data: kv, + expiry: expiry, + dirty: false, } } @@ -50,6 +54,7 @@ func (s *MysqlStore) Set(key, val interface{}) error { defer s.lock.Unlock() s.data[key] = val + s.dirty = true return nil } @@ -67,6 +72,7 @@ func (s *MysqlStore) Delete(key interface{}) error { defer s.lock.Unlock() delete(s.data, key) + s.dirty = true return nil } @@ -77,13 +83,20 @@ func (s *MysqlStore) ID() string { // Release releases resource and save data to provider. func (s *MysqlStore) Release() error { + newExpiry := time.Now().Unix() + if !s.dirty && (s.expiry+60) >= newExpiry { + return nil + } + data, err := session.EncodeGob(s.data) if err != nil { return err } _, err = s.c.Exec("UPDATE session SET data=?, expiry=? WHERE `key`=?", - data, time.Now().Unix(), s.sid) + data, newExpiry, s.sid) + s.dirty = false + s.expiry = newExpiry return err } @@ -93,6 +106,7 @@ func (s *MysqlStore) Flush() error { defer s.lock.Unlock() s.data = make(map[interface{}]interface{}) + s.dirty = true return nil } @@ -117,11 +131,12 @@ func (p *MysqlProvider) Init(expire int64, connStr string) (err error) { // Read returns raw session store by session ID. func (p *MysqlProvider) Read(sid string) (session.RawStore, error) { + expiry := time.Now().Unix() var data []byte - err := p.c.QueryRow("SELECT data FROM session WHERE `key`=?", sid).Scan(&data) + err := p.c.QueryRow("SELECT data,expiry FROM session WHERE `key`=?", sid).Scan(&data, &expiry) if err == sql.ErrNoRows { _, err = p.c.Exec("INSERT INTO session(`key`,data,expiry) VALUES(?,?,?)", - sid, "", time.Now().Unix()) + sid, "", expiry) } if err != nil { return nil, err @@ -137,7 +152,7 @@ func (p *MysqlProvider) Read(sid string) (session.RawStore, error) { } } - return NewMysqlStore(p.c, sid, kv), nil + return NewMysqlStore(p.c, sid, kv, expiry), nil } // Exist returns true if session with given ID exists. diff --git a/pkg/services/sqlstore/migrator/conditions.go b/pkg/services/sqlstore/migrator/conditions.go index 79bf7c7ed27..b6adfdbfa29 100644 --- a/pkg/services/sqlstore/migrator/conditions.go +++ b/pkg/services/sqlstore/migrator/conditions.go @@ -2,12 +2,47 @@ package migrator type MigrationCondition interface { Sql(dialect Dialect) (string, []interface{}) + IsFulfilled(results []map[string][]byte) bool } -type IfTableExistsCondition struct { +type ExistsMigrationCondition struct{} + +func (c *ExistsMigrationCondition) IsFulfilled(results []map[string][]byte) bool { + return len(results) >= 1 +} + +type NotExistsMigrationCondition struct{} + +func (c *NotExistsMigrationCondition) IsFulfilled(results []map[string][]byte) bool { + return len(results) == 0 +} + +type IfIndexExistsCondition struct { + ExistsMigrationCondition TableName string + IndexName string } -func (c *IfTableExistsCondition) Sql(dialect Dialect) (string, []interface{}) { - return dialect.TableCheckSql(c.TableName) +func (c *IfIndexExistsCondition) Sql(dialect Dialect) (string, []interface{}) { + return dialect.IndexCheckSql(c.TableName, c.IndexName) +} + +type IfIndexNotExistsCondition struct { + NotExistsMigrationCondition + TableName string + IndexName string +} + +func (c *IfIndexNotExistsCondition) Sql(dialect Dialect) (string, []interface{}) { + return dialect.IndexCheckSql(c.TableName, c.IndexName) +} + +type IfColumnNotExistsCondition struct { + NotExistsMigrationCondition + TableName string + ColumnName string +} + +func (c *IfColumnNotExistsCondition) Sql(dialect Dialect) (string, []interface{}) { + return dialect.ColumnCheckSql(c.TableName, c.ColumnName) } diff --git a/pkg/services/sqlstore/migrator/dialect.go b/pkg/services/sqlstore/migrator/dialect.go index 506a01c3ed8..d2e3c3e0777 100644 --- a/pkg/services/sqlstore/migrator/dialect.go +++ b/pkg/services/sqlstore/migrator/dialect.go @@ -29,10 +29,12 @@ type Dialect interface { DropTable(tableName string) string DropIndexSql(tableName string, index *Index) string - TableCheckSql(tableName string) (string, []interface{}) RenameTable(oldName string, newName string) string UpdateTableSql(tableName string, columns []*Column) string + IndexCheckSql(tableName, indexName string) (string, []interface{}) + ColumnCheckSql(tableName, columnName string) (string, []interface{}) + ColString(*Column) string ColStringNoPk(*Column) string @@ -182,6 +184,10 @@ func (db *BaseDialect) RenameTable(oldName string, newName string) string { return fmt.Sprintf("ALTER TABLE %s RENAME TO %s", quote(oldName), quote(newName)) } +func (db *BaseDialect) ColumnCheckSql(tableName, columnName string) (string, []interface{}) { + return "", nil +} + func (db *BaseDialect) DropIndexSql(tableName string, index *Index) string { quote := db.dialect.Quote name := index.XName(tableName) diff --git a/pkg/services/sqlstore/migrator/migrations.go b/pkg/services/sqlstore/migrator/migrations.go index fd71cc3d290..cb32c6212e6 100644 --- a/pkg/services/sqlstore/migrator/migrations.go +++ b/pkg/services/sqlstore/migrator/migrations.go @@ -85,7 +85,9 @@ type AddColumnMigration struct { } func NewAddColumnMigration(table Table, col *Column) *AddColumnMigration { - return &AddColumnMigration{tableName: table.Name, column: col} + m := &AddColumnMigration{tableName: table.Name, column: col} + m.Condition = &IfColumnNotExistsCondition{TableName: table.Name, ColumnName: col.Name} + return m } func (m *AddColumnMigration) Table(tableName string) *AddColumnMigration { @@ -109,7 +111,9 @@ type AddIndexMigration struct { } func NewAddIndexMigration(table Table, index *Index) *AddIndexMigration { - return &AddIndexMigration{tableName: table.Name, index: index} + m := &AddIndexMigration{tableName: table.Name, index: index} + m.Condition = &IfIndexNotExistsCondition{TableName: table.Name, IndexName: index.XName(table.Name)} + return m } func (m *AddIndexMigration) Table(tableName string) *AddIndexMigration { @@ -128,7 +132,9 @@ type DropIndexMigration struct { } func NewDropIndexMigration(table Table, index *Index) *DropIndexMigration { - return &DropIndexMigration{tableName: table.Name, index: index} + m := &DropIndexMigration{tableName: table.Name, index: index} + m.Condition = &IfIndexExistsCondition{TableName: table.Name, IndexName: index.XName(table.Name)} + return m } func (m *DropIndexMigration) Sql(dialect Dialect) string { @@ -179,11 +185,6 @@ func NewRenameTableMigration(oldName string, newName string) *RenameTableMigrati return &RenameTableMigration{oldName: oldName, newName: newName} } -func (m *RenameTableMigration) IfTableExists(tableName string) *RenameTableMigration { - m.Condition = &IfTableExistsCondition{TableName: tableName} - return m -} - func (m *RenameTableMigration) Rename(oldName string, newName string) *RenameTableMigration { m.oldName = oldName m.newName = newName @@ -212,11 +213,6 @@ func NewCopyTableDataMigration(targetTable string, sourceTable string, colMap ma return m } -func (m *CopyTableDataMigration) IfTableExists(tableName string) *CopyTableDataMigration { - m.Condition = &IfTableExistsCondition{TableName: tableName} - return m -} - func (m *CopyTableDataMigration) Sql(d Dialect) string { return d.CopyTableData(m.sourceTable, m.targetTable, m.sourceCols, m.targetCols) } diff --git a/pkg/services/sqlstore/migrator/migrator.go b/pkg/services/sqlstore/migrator/migrator.go index dead6f2b416..ce0b2663def 100644 --- a/pkg/services/sqlstore/migrator/migrator.go +++ b/pkg/services/sqlstore/migrator/migrator.go @@ -94,8 +94,6 @@ func (mg *Migrator) Start() error { Timestamp: time.Now(), } - mg.Logger.Debug("Executing", "sql", sql) - err := mg.inTransaction(func(sess *xorm.Session) error { err := mg.exec(m, sess) if err != nil { @@ -123,18 +121,30 @@ func (mg *Migrator) exec(m Migration, sess *xorm.Session) error { condition := m.GetCondition() if condition != nil { sql, args := condition.Sql(mg.Dialect) - results, err := sess.SQL(sql).Query(args...) - if err != nil || len(results) == 0 { - mg.Logger.Debug("Skipping migration condition not fulfilled", "id", m.Id()) - return sess.Rollback() + + if sql != "" { + mg.Logger.Debug("Executing migration condition sql", "id", m.Id(), "sql", sql, "args", args) + results, err := sess.SQL(sql, args...).Query() + if err != nil { + mg.Logger.Error("Executing migration condition failed", "id", m.Id(), "error", err) + return err + } + + if !condition.IsFulfilled(results) { + mg.Logger.Warn("Skipping migration: Already executed, but not recorded in migration log", "id", m.Id()) + return nil + } } } var err error if codeMigration, ok := m.(CodeMigration); ok { + mg.Logger.Debug("Executing code migration", "id", m.Id()) err = codeMigration.Exec(sess, mg) } else { - _, err = sess.Exec(m.Sql(mg.Dialect)) + sql := m.Sql(mg.Dialect) + mg.Logger.Debug("Executing sql migration", "id", m.Id(), "sql", sql) + _, err = sess.Exec(sql) } if err != nil { diff --git a/pkg/services/sqlstore/migrator/mysql_dialect.go b/pkg/services/sqlstore/migrator/mysql_dialect.go index 7daa4597430..4de55c9a12f 100644 --- a/pkg/services/sqlstore/migrator/mysql_dialect.go +++ b/pkg/services/sqlstore/migrator/mysql_dialect.go @@ -90,12 +90,6 @@ func (db *Mysql) SqlType(c *Column) string { return res } -func (db *Mysql) TableCheckSql(tableName string) (string, []interface{}) { - args := []interface{}{"grafana", tableName} - sql := "SELECT `TABLE_NAME` from `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? and `TABLE_NAME`=?" - return sql, args -} - func (db *Mysql) UpdateTableSql(tableName string, columns []*Column) string { var statements = []string{} @@ -108,6 +102,18 @@ func (db *Mysql) UpdateTableSql(tableName string, columns []*Column) string { return "ALTER TABLE " + db.Quote(tableName) + " " + strings.Join(statements, ", ") + ";" } +func (db *Mysql) IndexCheckSql(tableName, indexName string) (string, []interface{}) { + args := []interface{}{tableName, indexName} + sql := "SELECT 1 FROM " + db.Quote("INFORMATION_SCHEMA") + "." + db.Quote("STATISTICS") + " WHERE " + db.Quote("TABLE_SCHEMA") + " = DATABASE() AND " + db.Quote("TABLE_NAME") + "=? AND " + db.Quote("INDEX_NAME") + "=?" + return sql, args +} + +func (db *Mysql) ColumnCheckSql(tableName, columnName string) (string, []interface{}) { + args := []interface{}{tableName, columnName} + sql := "SELECT 1 FROM " + db.Quote("INFORMATION_SCHEMA") + "." + db.Quote("COLUMNS") + " WHERE " + db.Quote("TABLE_SCHEMA") + " = DATABASE() AND " + db.Quote("TABLE_NAME") + "=? AND " + db.Quote("COLUMN_NAME") + "=?" + return sql, args +} + func (db *Mysql) CleanDB() error { tables, _ := db.engine.DBMetas() sess := db.engine.NewSession() diff --git a/pkg/services/sqlstore/migrator/postgres_dialect.go b/pkg/services/sqlstore/migrator/postgres_dialect.go index ab8812a1e26..ce529920a39 100644 --- a/pkg/services/sqlstore/migrator/postgres_dialect.go +++ b/pkg/services/sqlstore/migrator/postgres_dialect.go @@ -101,9 +101,9 @@ func (db *Postgres) SqlType(c *Column) string { return res } -func (db *Postgres) TableCheckSql(tableName string) (string, []interface{}) { - args := []interface{}{"grafana", tableName} - sql := "SELECT table_name FROM information_schema.tables WHERE table_schema=? and table_name=?" +func (db *Postgres) IndexCheckSql(tableName, indexName string) (string, []interface{}) { + args := []interface{}{tableName, indexName} + sql := "SELECT 1 FROM " + db.Quote("pg_indexes") + " WHERE" + db.Quote("tablename") + "=? AND " + db.Quote("indexname") + "=?" return sql, args } diff --git a/pkg/services/sqlstore/migrator/sqlite_dialect.go b/pkg/services/sqlstore/migrator/sqlite_dialect.go index 446e3fcef12..9c0dec05727 100644 --- a/pkg/services/sqlstore/migrator/sqlite_dialect.go +++ b/pkg/services/sqlstore/migrator/sqlite_dialect.go @@ -68,9 +68,10 @@ func (db *Sqlite3) SqlType(c *Column) string { } } -func (db *Sqlite3) TableCheckSql(tableName string) (string, []interface{}) { - args := []interface{}{tableName} - return "SELECT name FROM sqlite_master WHERE type='table' and name = ?", args +func (db *Sqlite3) IndexCheckSql(tableName, indexName string) (string, []interface{}) { + args := []interface{}{tableName, indexName} + sql := "SELECT 1 FROM " + db.Quote("sqlite_master") + " WHERE " + db.Quote("type") + "='index' AND " + db.Quote("tbl_name") + "=? AND " + db.Quote("name") + "=?" + return sql, args } func (db *Sqlite3) DropIndexSql(tableName string, index *Index) string { diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 95b53be9d4a..d0e93177d8b 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -243,7 +243,7 @@ func (ss *SqlStore) buildConnectionString() (string, error) { ss.dbCfg.Path = filepath.Join(ss.Cfg.DataPath, ss.dbCfg.Path) } os.MkdirAll(path.Dir(ss.dbCfg.Path), os.ModePerm) - cnnstr = "file:" + ss.dbCfg.Path + "?cache=shared&mode=rwc" + cnnstr = fmt.Sprintf("file:%s?cache=%s&mode=rwc", ss.dbCfg.Path, ss.dbCfg.CacheMode) default: return "", fmt.Errorf("Unknown database type: %s", ss.dbCfg.Type) } @@ -319,6 +319,8 @@ func (ss *SqlStore) readConfig() { ss.dbCfg.ClientCertPath = sec.Key("client_cert_path").String() ss.dbCfg.ServerCertName = sec.Key("server_cert_name").String() ss.dbCfg.Path = sec.Key("path").MustString("data/grafana.db") + + ss.dbCfg.CacheMode = sec.Key("cache_mode").MustString("private") } func InitTestDB(t *testing.T) *SqlStore { @@ -391,13 +393,20 @@ func IsTestDbPostgres() bool { } type DatabaseConfig struct { - Type, Host, Name, User, Pwd, Path, SslMode string - CaCertPath string - ClientKeyPath string - ClientCertPath string - ServerCertName string - ConnectionString string - MaxOpenConn int - MaxIdleConn int - ConnMaxLifetime int + Type string + Host string + Name string + User string + Pwd string + Path string + SslMode string + CaCertPath string + ClientKeyPath string + ClientCertPath string + ServerCertName string + ConnectionString string + MaxOpenConn int + MaxIdleConn int + ConnMaxLifetime int + CacheMode string } diff --git a/pkg/tsdb/influxdb/model_parser.go b/pkg/tsdb/influxdb/model_parser.go index f1113511bae..94f37904eee 100644 --- a/pkg/tsdb/influxdb/model_parser.go +++ b/pkg/tsdb/influxdb/model_parser.go @@ -16,6 +16,7 @@ func (qp *InfluxdbQueryParser) Parse(model *simplejson.Json, dsInfo *models.Data rawQuery := model.Get("query").MustString("") useRawQuery := model.Get("rawQuery").MustBool(false) alias := model.Get("alias").MustString("") + tz := model.Get("tz").MustString("") measurement := model.Get("measurement").MustString("") @@ -55,6 +56,7 @@ func (qp *InfluxdbQueryParser) Parse(model *simplejson.Json, dsInfo *models.Data Interval: parsedInterval, Alias: alias, UseRawQuery: useRawQuery, + Tz: tz, }, nil } diff --git a/pkg/tsdb/influxdb/model_parser_test.go b/pkg/tsdb/influxdb/model_parser_test.go index 7be9cae9702..e620b0c631d 100644 --- a/pkg/tsdb/influxdb/model_parser_test.go +++ b/pkg/tsdb/influxdb/model_parser_test.go @@ -41,6 +41,7 @@ func TestInfluxdbQueryParser(t *testing.T) { } ], "measurement": "logins.count", + "tz": "Europe/Paris", "policy": "default", "refId": "B", "resultFormat": "time_series", @@ -115,6 +116,7 @@ func TestInfluxdbQueryParser(t *testing.T) { So(len(res.GroupBy), ShouldEqual, 3) So(len(res.Selects), ShouldEqual, 3) So(len(res.Tags), ShouldEqual, 2) + So(res.Tz, ShouldEqual, "Europe/Paris") So(res.Interval, ShouldEqual, time.Second*20) So(res.Alias, ShouldEqual, "serie alias") }) diff --git a/pkg/tsdb/influxdb/models.go b/pkg/tsdb/influxdb/models.go index 82ed72c2a18..cbe82e5e2ef 100644 --- a/pkg/tsdb/influxdb/models.go +++ b/pkg/tsdb/influxdb/models.go @@ -13,6 +13,7 @@ type Query struct { UseRawQuery bool Alias string Interval time.Duration + Tz string } type Tag struct { diff --git a/pkg/tsdb/influxdb/query.go b/pkg/tsdb/influxdb/query.go index 7cb8f0ecd82..f1a8c745956 100644 --- a/pkg/tsdb/influxdb/query.go +++ b/pkg/tsdb/influxdb/query.go @@ -26,6 +26,7 @@ func (query *Query) Build(queryContext *tsdb.TsdbQuery) (string, error) { res += query.renderWhereClause() res += query.renderTimeFilter(queryContext) res += query.renderGroupBy(queryContext) + res += query.renderTz() } calculator := tsdb.NewIntervalCalculator(&tsdb.IntervalOptions{}) @@ -154,3 +155,12 @@ func (query *Query) renderGroupBy(queryContext *tsdb.TsdbQuery) string { return groupBy } + +func (query *Query) renderTz() string { + tz := query.Tz + if tz == "" { + return "" + } else { + return fmt.Sprintf(" tz('%s')", tz) + } +} diff --git a/pkg/tsdb/influxdb/query_test.go b/pkg/tsdb/influxdb/query_test.go index cc1358a72d7..f85419c8ae2 100644 --- a/pkg/tsdb/influxdb/query_test.go +++ b/pkg/tsdb/influxdb/query_test.go @@ -47,6 +47,20 @@ func TestInfluxdbQueryBuilder(t *testing.T) { So(rawQuery, ShouldEqual, `SELECT mean("value") FROM "policy"."cpu" WHERE time > now() - 5m GROUP BY time(10s) fill(null)`) }) + Convey("can build query with tz", func() { + query := &Query{ + Selects: []*Select{{*qp1, *qp2}}, + Measurement: "cpu", + GroupBy: []*QueryPart{groupBy1}, + Tz: "Europe/Paris", + Interval: time.Second * 5, + } + + rawQuery, err := query.Build(queryContext) + So(err, ShouldBeNil) + So(rawQuery, ShouldEqual, `SELECT mean("value") FROM "cpu" WHERE time > now() - 5m GROUP BY time(5s) tz('Europe/Paris')`) + }) + Convey("can build query with group bys", func() { query := &Query{ Selects: []*Select{{*qp1, *qp2}}, diff --git a/pkg/tsdb/postgres/macros.go b/pkg/tsdb/postgres/macros.go index 26a3d1e53ee..0b3685c2610 100644 --- a/pkg/tsdb/postgres/macros.go +++ b/pkg/tsdb/postgres/macros.go @@ -86,11 +86,11 @@ func (m *postgresMacroEngine) evaluateMacro(name string, args []string) (string, return "", fmt.Errorf("missing time column argument for macro %v", name) } - return fmt.Sprintf("%s BETWEEN '%s' AND '%s'", args[0], m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339), m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil + return fmt.Sprintf("%s BETWEEN '%s' AND '%s'", args[0], m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339Nano), m.timeRange.GetToAsTimeUTC().Format(time.RFC3339Nano)), nil case "__timeFrom": - return fmt.Sprintf("'%s'", m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339)), nil + return fmt.Sprintf("'%s'", m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339Nano)), nil case "__timeTo": - return fmt.Sprintf("'%s'", m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil + return fmt.Sprintf("'%s'", m.timeRange.GetToAsTimeUTC().Format(time.RFC3339Nano)), nil case "__timeGroup": if len(args) < 2 { return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name) diff --git a/pkg/tsdb/postgres/macros_test.go b/pkg/tsdb/postgres/macros_test.go index 6a71caedeb9..90f6c6d879a 100644 --- a/pkg/tsdb/postgres/macros_test.go +++ b/pkg/tsdb/postgres/macros_test.go @@ -41,7 +41,7 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") So(err, ShouldBeNil) - So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339))) + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339Nano), to.Format(time.RFC3339Nano))) }) Convey("interpolate __timeFrom function", func() { @@ -138,7 +138,7 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") So(err, ShouldBeNil) - So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339))) + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339Nano), to.Format(time.RFC3339Nano))) }) Convey("interpolate __unixEpochFilter function", func() { @@ -158,7 +158,7 @@ func TestMacroEngine(t *testing.T) { sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") So(err, ShouldBeNil) - So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339))) + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339Nano), to.Format(time.RFC3339Nano))) }) Convey("interpolate __unixEpochFilter function", func() { @@ -168,5 +168,22 @@ func TestMacroEngine(t *testing.T) { So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix())) }) }) + + Convey("Given a time range between 1960-02-01 07:00:00.5 and 1980-02-03 08:00:00.5", func() { + from := time.Date(1960, 2, 1, 7, 0, 0, 500e6, time.UTC) + to := time.Date(1980, 2, 3, 8, 0, 0, 500e6, time.UTC) + timeRange := tsdb.NewTimeRange(strconv.FormatInt(from.UnixNano()/int64(time.Millisecond), 10), strconv.FormatInt(to.UnixNano()/int64(time.Millisecond), 10)) + + So(from.Format(time.RFC3339Nano), ShouldEqual, "1960-02-01T07:00:00.5Z") + So(to.Format(time.RFC3339Nano), ShouldEqual, "1980-02-03T08:00:00.5Z") + Convey("interpolate __timeFilter function", func() { + sql, err := engine.Interpolate(query, timeRange, "WHERE $__timeFilter(time_column)") + So(err, ShouldBeNil) + + So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339Nano), to.Format(time.RFC3339Nano))) + }) + + }) + }) } diff --git a/public/app/core/components/Form/Element.tsx b/public/app/core/components/Form/Element.tsx deleted file mode 100644 index 997d7f0e717..00000000000 --- a/public/app/core/components/Form/Element.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { PureComponent, ReactNode, ReactElement } from 'react'; -import { Label } from './Label'; -import { uniqueId } from 'lodash'; - -interface Props { - label?: ReactNode; - labelClassName?: string; - id?: string; - children: ReactElement; -} - -export class Element extends PureComponent { - elementId: string = this.props.id || uniqueId('form-element-'); - - get elementLabel() { - const { label, labelClassName } = this.props; - - if (label) { - return ( - - ); - } - - return null; - } - - get children() { - const { children } = this.props; - - return React.cloneElement(children, { id: this.elementId }); - } - - render() { - return ( -
- {this.elementLabel} - {this.children} -
- ); - } -} diff --git a/public/app/core/components/Form/Label.tsx b/public/app/core/components/Form/Label.tsx deleted file mode 100644 index 385a1b325be..00000000000 --- a/public/app/core/components/Form/Label.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React, { PureComponent, ReactNode } from 'react'; - -interface Props { - children: ReactNode; - htmlFor?: string; - className?: string; -} - -export class Label extends PureComponent { - render() { - const { children, htmlFor, className } = this.props; - - return ( - - ); - } -} diff --git a/public/app/core/components/Form/index.ts b/public/app/core/components/Form/index.ts index e4c8197aaa9..6322cf3241a 100644 --- a/public/app/core/components/Form/index.ts +++ b/public/app/core/components/Form/index.ts @@ -1,3 +1 @@ -export { Element } from './Element'; export { Input } from './Input'; -export { Label } from './Label'; diff --git a/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx b/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx index 872f2f28bb8..2524a265054 100644 --- a/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx +++ b/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx @@ -52,7 +52,11 @@ export const ToggleButton: SFC = ({ ); if (tooltip) { - return {button}; + return ( + + {button} + + ); } else { return button; } diff --git a/public/app/core/directives/tags.ts b/public/app/core/directives/tags.ts index 33a2252a683..27bddfb1883 100644 --- a/public/app/core/directives/tags.ts +++ b/public/app/core/directives/tags.ts @@ -69,7 +69,7 @@ function bootstrapTagsinput() { }, }); - select.on('itemAdded', event => { + select.on('itemAdded', (event: any) => { if (scope.model.indexOf(event.item) === -1) { scope.model.push(event.item); if (scope.onTagsUpdated) { @@ -85,7 +85,7 @@ function bootstrapTagsinput() { setColor(event.item, tagElement); }); - select.on('itemRemoved', event => { + select.on('itemRemoved', (event: any) => { const idx = scope.model.indexOf(event.item); if (idx !== -1) { scope.model.splice(idx, 1); diff --git a/public/app/core/live/live_srv.ts b/public/app/core/live/live_srv.ts index b49c38b9007..3666fde3b91 100644 --- a/public/app/core/live/live_srv.ts +++ b/public/app/core/live/live_srv.ts @@ -1,7 +1,7 @@ import _ from 'lodash'; import config from 'app/core/config'; -import { Observable } from 'rxjs/Observable'; +import { Observable } from 'rxjs'; export class LiveSrv { conn: any; diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index fabb29c6e18..4e8c6207959 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -2,14 +2,23 @@ import _ from 'lodash'; import { TimeSeries } from 'app/core/core'; import colors, { getThemeColor } from 'app/core/utils/colors'; +/** + * Mapping of log level abbreviation to canonical log level. + * Supported levels are reduce to limit color variation. + */ export enum LogLevel { + emerg = 'critical', + alert = 'critical', crit = 'critical', critical = 'critical', warn = 'warning', warning = 'warning', err = 'error', + eror = 'error', error = 'error', info = 'info', + notice = 'info', + dbug = 'debug', debug = 'debug', trace = 'trace', unkown = 'unkown', @@ -81,7 +90,9 @@ export interface LogsStream { export interface LogsStreamEntry { line: string; - timestamp: string; + ts: string; + // Legacy, was renamed to ts + timestamp?: string; } export interface LogsStreamLabels { diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index 7ceebbd8047..a3b08516d16 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -14,7 +14,6 @@ const DEFAULT_EXPLORE_STATE: ExploreState = { datasourceError: null, datasourceLoading: null, datasourceMissing: false, - datasourceName: '', exploreDatasources: [], graphInterval: 1000, history: [], @@ -69,7 +68,7 @@ describe('state functions', () => { it('returns url parameter value for a state object', () => { const state = { ...DEFAULT_EXPLORE_STATE, - datasourceName: 'foo', + initialDatasource: 'foo', range: { from: 'now-5h', to: 'now', @@ -94,7 +93,7 @@ describe('state functions', () => { it('returns url parameter value for a state object', () => { const state = { ...DEFAULT_EXPLORE_STATE, - datasourceName: 'foo', + initialDatasource: 'foo', range: { from: 'now-5h', to: 'now', @@ -120,7 +119,7 @@ describe('state functions', () => { it('can parse the serialized state into the original state', () => { const state = { ...DEFAULT_EXPLORE_STATE, - datasourceName: 'foo', + initialDatasource: 'foo', range: { from: 'now - 5h', to: 'now', @@ -144,7 +143,7 @@ describe('state functions', () => { const resultState = { ...rest, datasource: DEFAULT_EXPLORE_STATE.datasource, - datasourceName: datasource, + initialDatasource: datasource, initialQueries: queries, }; diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index f978ec1ef8c..bea166075dc 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -9,7 +9,8 @@ import { parse as parseDate } from 'app/core/utils/datemath'; import TimeSeries from 'app/core/time_series2'; import TableModel, { mergeTablesIntoModel } from 'app/core/table_model'; import { ExploreState, ExploreUrlState, HistoryItem, QueryTransaction } from 'app/types/explore'; -import { DataQuery, RawTimeRange, IntervalValues, DataSourceApi } from 'app/types/series'; +import { DataQuery, DataSourceApi } from 'app/types/series'; +import { RawTimeRange, IntervalValues } from '@grafana/ui'; export const DEFAULT_RANGE = { from: 'now-6h', @@ -104,7 +105,7 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState { export function serializeStateToUrlParam(state: ExploreState, compact?: boolean): string { const urlState: ExploreUrlState = { - datasource: state.datasourceName, + datasource: state.initialDatasource, queries: state.initialQueries.map(clearQueryKeys), range: state.range, }; diff --git a/public/app/core/utils/kbn.ts b/public/app/core/utils/kbn.ts index b8539ea5013..d32844c44ed 100644 --- a/public/app/core/utils/kbn.ts +++ b/public/app/core/utils/kbn.ts @@ -629,6 +629,8 @@ kbn.valueFormats.conmgm3 = kbn.formatBuilders.fixedUnit('mg/m³'); kbn.valueFormats.conmgNm3 = kbn.formatBuilders.fixedUnit('mg/Nm³'); kbn.valueFormats.congm3 = kbn.formatBuilders.fixedUnit('g/m³'); kbn.valueFormats.congNm3 = kbn.formatBuilders.fixedUnit('g/Nm³'); +kbn.valueFormats.conmgdL = kbn.formatBuilders.fixedUnit('mg/dL'); +kbn.valueFormats.conmmolL = kbn.formatBuilders.fixedUnit('mmol/L'); // Time kbn.valueFormats.hertz = kbn.formatBuilders.decimalSIPrefix('Hz'); @@ -1209,6 +1211,8 @@ kbn.getUnitFormats = () => { { text: 'milligram per normal cubic meter (mg/Nm³)', value: 'conmgNm3' }, { text: 'gram per cubic meter (g/m³)', value: 'congm3' }, { text: 'gram per normal cubic meter (g/Nm³)', value: 'congNm3' }, + { text: 'milligrams per decilitre (mg/dL)', value: 'conmgdL' }, + { text: 'millimoles per litre (mmol/L)', value: 'conmmolL' }, ], }, ]; diff --git a/public/app/core/utils/rangeutil.ts b/public/app/core/utils/rangeutil.ts index 0150e80f1ed..310c8ab8533 100644 --- a/public/app/core/utils/rangeutil.ts +++ b/public/app/core/utils/rangeutil.ts @@ -1,7 +1,7 @@ import _ from 'lodash'; import moment from 'moment'; -import { RawTimeRange } from 'app/types/series'; +import { RawTimeRange } from '@grafana/ui'; import * as dateMath from './datemath'; diff --git a/public/app/features/alerting/AlertRuleItem.tsx b/public/app/features/alerting/AlertRuleItem.tsx index f47a6348303..86bb0207460 100644 --- a/public/app/features/alerting/AlertRuleItem.tsx +++ b/public/app/features/alerting/AlertRuleItem.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import Highlighter from 'react-highlight-words'; -import classNames from 'classnames/bind'; +import classNames from 'classnames'; import { AlertRule } from '../../types'; export interface Props { @@ -23,7 +23,7 @@ class AlertRuleItem extends PureComponent { render() { const { rule, onTogglePause } = this.props; - const stateClass = classNames({ + const iconClassName = classNames({ fa: true, 'fa-play': rule.state === 'paused', 'fa-pause': rule.state !== 'paused', @@ -55,7 +55,7 @@ class AlertRuleItem extends PureComponent { title="Pausing an alert rule prevents it from executing" onClick={onTogglePause} > - + diff --git a/public/app/features/api-keys/ApiKeysPage.tsx b/public/app/features/api-keys/ApiKeysPage.tsx index d2aa1f24c57..e14873fa9f6 100644 --- a/public/app/features/api-keys/ApiKeysPage.tsx +++ b/public/app/features/api-keys/ApiKeysPage.tsx @@ -13,7 +13,7 @@ import ApiKeysAddedModal from './ApiKeysAddedModal'; import config from 'app/core/config'; import appEvents from 'app/core/app_events'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; -import DeleteButton from 'app/core/components/DeleteButton/DeleteButton'; +import { DeleteButton } from '@grafana/ui'; export interface Props { navModel: NavModel; @@ -224,7 +224,7 @@ export class ApiKeysPage extends PureComponent { {key.name} {key.role} - this.onDeleteApiKey(key)} /> + this.onDeleteApiKey(key)} /> ); diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index 481dd2d5e19..9de298e0799 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -160,14 +160,14 @@ export class DashboardPanel extends PureComponent { return (
( + render={styles => (
{plugin.exports.Panel && this.renderReactPanel()} {plugin.exports.PanelCtrl && this.renderAngularPanel()} diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index 9926410f40d..30a939b50aa 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DataPanel.tsx @@ -8,7 +8,8 @@ import { getDatasourceSrv, DatasourceSrv } from 'app/features/plugins/datasource import kbn from 'app/core/utils/kbn'; // Types -import { TimeRange, LoadingState, DataQueryOptions, DataQueryResponse, TimeSeries } from 'app/types'; +import { DataQueryOptions, DataQueryResponse } from 'app/types'; +import { TimeRange, TimeSeries, LoadingState } from '@grafana/ui'; interface RenderProps { loading: LoadingState; diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 5df6f20fc23..94719dfe6e0 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -16,7 +16,8 @@ import { PANEL_HEADER_HEIGHT } from 'app/core/constants'; // Types import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; -import { PanelPlugin, TimeRange } from 'app/types'; +import { PanelPlugin } from 'app/types'; +import { TimeRange } from '@grafana/ui'; export interface Props { panel: PanelModel; diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx index cde540c0509..1d17ec6cefc 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenu.tsx @@ -3,7 +3,7 @@ import { DashboardModel } from 'app/features/dashboard/dashboard_model'; import { PanelModel } from 'app/features/dashboard/panel_model'; import { PanelHeaderMenuItem } from './PanelHeaderMenuItem'; import { getPanelMenu } from 'app/features/dashboard/utils/getPanelMenu'; -import { PanelMenuItem } from 'app/types/panel'; +import { PanelMenuItem } from '@grafana/ui'; export interface Props { panel: PanelModel; diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx index 92a64a2f24d..d42b48fe1d6 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderMenuItem.tsx @@ -1,5 +1,5 @@ import React, { SFC } from 'react'; -import { PanelMenuItem } from 'app/types/panel'; +import { PanelMenuItem } from '@grafana/ui'; interface Props { children: any; diff --git a/public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx b/public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx index 82008b10d2f..18b307b5ea5 100644 --- a/public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx +++ b/public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx @@ -1,6 +1,10 @@ +// Libraries import _ from 'lodash'; import React, { PureComponent } from 'react'; -import { PanelPlugin, PanelProps } from 'app/types'; + +// Types +import { PanelProps } from '@grafana/ui'; +import { PanelPlugin } from 'app/types'; interface Props { pluginId: string; diff --git a/public/app/features/dashboard/dashgrid/PanelResizer.tsx b/public/app/features/dashboard/dashgrid/PanelResizer.tsx index 7d52076b54b..2a4bf8379a6 100644 --- a/public/app/features/dashboard/dashgrid/PanelResizer.tsx +++ b/public/app/features/dashboard/dashgrid/PanelResizer.tsx @@ -1,4 +1,4 @@ -import React, { PureComponent } from 'react'; +import React, { PureComponent } from 'react'; import { throttle } from 'lodash'; import Draggable from 'react-draggable'; @@ -6,7 +6,7 @@ import { PanelModel } from '../panel_model'; interface Props { isEditing: boolean; - render: (height: number | 'inherit') => JSX.Element; + render: (styles: object) => JSX.Element; panel: PanelModel; } @@ -19,6 +19,7 @@ export class PanelResizer extends PureComponent { prevEditorHeight: number; throttledChangeHeight: (height: number) => void; throttledResizeDone: () => void; + noStyles: object = {}; constructor(props) { super(props); @@ -65,7 +66,7 @@ export class PanelResizer extends PureComponent { return ( <> - {render(isEditing ? editorHeight : 'inherit')} + {render(isEditing ? {height: editorHeight} : this.noStyles)} {isEditing && (
diff --git a/public/app/features/dashboard/dashgrid/QueryOptions.tsx b/public/app/features/dashboard/dashgrid/QueryOptions.tsx index dd084418c40..fad70d92990 100644 --- a/public/app/features/dashboard/dashgrid/QueryOptions.tsx +++ b/public/app/features/dashboard/dashgrid/QueryOptions.tsx @@ -10,6 +10,7 @@ import { Input } from 'app/core/components/Form'; import { EventsWithValidation } from 'app/core/components/Form/Input'; import { InputStatus } from 'app/core/components/Form/Input'; import DataSourceOption from './DataSourceOption'; +import { GfFormLabel } from '@grafana/ui'; // Types import { PanelModel } from '../panel_model'; @@ -163,7 +164,7 @@ export class QueryOptions extends PureComponent { {this.renderOptions()}
- Relative time + Relative time { const link = scope.link; + const dashboard = scope.dashboard; + let template = '
' + '
- +
diff --git a/public/app/features/dashboard/time_srv.ts b/public/app/features/dashboard/time_srv.ts index ac717de15c9..b4d18e0279a 100644 --- a/public/app/features/dashboard/time_srv.ts +++ b/public/app/features/dashboard/time_srv.ts @@ -6,9 +6,9 @@ import _ from 'lodash'; import kbn from 'app/core/utils/kbn'; import coreModule from 'app/core/core_module'; import * as dateMath from 'app/core/utils/datemath'; -// Types -import { TimeRange } from 'app/types'; +// Types +import { TimeRange } from '@grafana/ui'; export class TimeSrv { time: any; diff --git a/public/app/features/dashboard/utils/getPanelMenu.ts b/public/app/features/dashboard/utils/getPanelMenu.ts index 07ce54108f3..190451671ad 100644 --- a/public/app/features/dashboard/utils/getPanelMenu.ts +++ b/public/app/features/dashboard/utils/getPanelMenu.ts @@ -4,7 +4,7 @@ import { store } from 'app/store/store'; import { removePanel, duplicatePanel, copyPanel, editPanelJson, sharePanel } from 'app/features/dashboard/utils/panel'; import { PanelModel } from 'app/features/dashboard/panel_model'; import { DashboardModel } from 'app/features/dashboard/dashboard_model'; -import { PanelMenuItem } from 'app/types/panel'; +import { PanelMenuItem } from '@grafana/ui'; export const getPanelMenu = (dashboard: DashboardModel, panel: PanelModel) => { const onViewPanel = () => { diff --git a/public/app/features/dashboard/utils/panel.ts b/public/app/features/dashboard/utils/panel.ts index 0b0f127e2aa..f7ed0efd910 100644 --- a/public/app/features/dashboard/utils/panel.ts +++ b/public/app/features/dashboard/utils/panel.ts @@ -4,7 +4,7 @@ import store from 'app/core/store'; // Models import { DashboardModel } from 'app/features/dashboard/dashboard_model'; import { PanelModel } from 'app/features/dashboard/panel_model'; -import { TimeRange } from 'app/types/series'; +import { TimeRange } from '@grafana/ui'; // Utils import { isString as _isString } from 'lodash'; diff --git a/public/app/features/datasources/DataSourcesList.tsx b/public/app/features/datasources/DataSourcesList.tsx index 904ed0cf679..0895b92461b 100644 --- a/public/app/features/datasources/DataSourcesList.tsx +++ b/public/app/features/datasources/DataSourcesList.tsx @@ -1,5 +1,5 @@ import React, { PureComponent } from 'react'; -import classNames from 'classnames/bind'; +import classNames from 'classnames'; import DataSourcesListItem from './DataSourcesListItem'; import { DataSource } from 'app/types'; import { LayoutMode, LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector'; diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index e8897c6926a..a905034f302 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -11,7 +11,8 @@ import { QueryHintGetter, QueryHint, } from 'app/types/explore'; -import { TimeRange, DataQuery } from 'app/types/series'; +import { TimeRange } from '@grafana/ui'; +import { DataQuery } from 'app/types/series'; import store from 'app/core/store'; import { DEFAULT_RANGE, @@ -39,6 +40,8 @@ import ErrorBoundary from './ErrorBoundary'; import { Alert } from './Error'; import TimePicker, { parseTime } from './TimePicker'; +const LAST_USED_DATASOURCE_KEY = 'grafana.explore.datasource'; + interface ExploreProps { datasourceSrv: DatasourceSrv; onChangeSplit: (split: boolean, state?: ExploreState) => void; @@ -89,6 +92,10 @@ interface ExploreProps { export class Explore extends React.PureComponent { el: any; exploreEvents: Emitter; + /** + * Set via URL or local storage + */ + initialDatasource: string; /** * Current query expressions of the rows including their modifications, used for running queries. * Not kept in component state to prevent edit-render roundtrips. @@ -114,6 +121,7 @@ export class Explore extends React.PureComponent { initialQueries = splitState.initialQueries; } else { const { datasource, queries, range } = props.urlState as ExploreUrlState; + const initialDatasource = datasource || store.get(LAST_USED_DATASOURCE_KEY); initialQueries = ensureQueries(queries); const initialRange = { from: parseTime(range.from), to: parseTime(range.to) } || { ...DEFAULT_RANGE }; // Millies step for helper bar charts @@ -123,10 +131,10 @@ export class Explore extends React.PureComponent { datasourceError: null, datasourceLoading: null, datasourceMissing: false, - datasourceName: datasource, exploreDatasources: [], graphInterval: initialGraphInterval, graphResult: [], + initialDatasource, initialQueries, history: [], logsResult: null, @@ -150,7 +158,7 @@ export class Explore extends React.PureComponent { async componentDidMount() { const { datasourceSrv } = this.props; - const { datasourceName } = this.state; + const { initialDatasource } = this.state; if (!datasourceSrv) { throw new Error('No datasource service passed as props.'); } @@ -164,10 +172,10 @@ export class Explore extends React.PureComponent { if (datasources.length > 0) { this.setState({ datasourceLoading: true, exploreDatasources }); - // Priority: datasource in url, default datasource, first explore datasource + // Priority for datasource preselection: URL, localstorage, default datasource let datasource; - if (datasourceName) { - datasource = await datasourceSrv.get(datasourceName); + if (initialDatasource) { + datasource = await datasourceSrv.get(initialDatasource); } else { datasource = await datasourceSrv.get(); } @@ -252,13 +260,15 @@ export class Explore extends React.PureComponent { supportsLogs, supportsTable, datasourceLoading: false, - datasourceName: datasource.name, + initialDatasource: datasource.name, initialQueries: nextQueries, logsHighlighterExpressions: undefined, showingStartPage: Boolean(StartPage), }, () => { if (datasourceError === null) { + // Save last-used datasource + store.set(LAST_USED_DATASOURCE_KEY, datasource.name); this.onSubmit(); } } diff --git a/public/app/features/explore/Graph.tsx b/public/app/features/explore/Graph.tsx index 10f3faa1267..5d64dde28ce 100644 --- a/public/app/features/explore/Graph.tsx +++ b/public/app/features/explore/Graph.tsx @@ -8,7 +8,7 @@ import 'vendor/flot/jquery.flot.time'; import 'vendor/flot/jquery.flot.selection'; import 'vendor/flot/jquery.flot.stack'; -import { RawTimeRange } from 'app/types/series'; +import { RawTimeRange } from '@grafana/ui'; import * as dateMath from 'app/core/utils/datemath'; import TimeSeries from 'app/core/time_series2'; diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index 2119f5a96b7..1a384cf011d 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -4,7 +4,7 @@ import Highlighter from 'react-highlight-words'; import classnames from 'classnames'; import * as rangeUtil from 'app/core/utils/rangeutil'; -import { RawTimeRange } from 'app/types/series'; +import { RawTimeRange } from '@grafana/ui'; import { LogsDedupDescription, LogsDedupStrategy, diff --git a/public/app/features/explore/QueryEditor.tsx b/public/app/features/explore/QueryEditor.tsx index 7ad659ec784..ce0a8a6e03e 100644 --- a/public/app/features/explore/QueryEditor.tsx +++ b/public/app/features/explore/QueryEditor.tsx @@ -3,7 +3,7 @@ import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoa import { Emitter } from 'app/core/utils/emitter'; import { getIntervals } from 'app/core/utils/explore'; import { DataQuery } from 'app/types'; -import { RawTimeRange } from 'app/types/series'; +import { RawTimeRange } from '@grafana/ui'; import { getTimeSrv } from 'app/features/dashboard/time_srv'; import 'app/features/plugins/plugin_loader'; diff --git a/public/app/features/explore/QueryField.tsx b/public/app/features/explore/QueryField.tsx index d5cba981951..24b8b8f5b16 100644 --- a/public/app/features/explore/QueryField.tsx +++ b/public/app/features/explore/QueryField.tsx @@ -4,6 +4,7 @@ import ReactDOM from 'react-dom'; import { Change, Value } from 'slate'; import { Editor } from 'slate-react'; import Plain from 'slate-plain-serializer'; +import classnames from 'classnames'; import { CompletionItem, CompletionItemGroup, TypeaheadOutput } from 'app/types/explore'; @@ -30,6 +31,7 @@ function hasSuggestions(suggestions: CompletionItemGroup[]): boolean { export interface QueryFieldProps { additionalPlugins?: any[]; cleanText?: (text: string) => string; + disabled?: boolean; initialQuery: string | null; onBlur?: () => void; onFocus?: () => void; @@ -78,7 +80,7 @@ export class QueryField extends React.PureComponent p); + this.plugins = [ClearPlugin(), NewlinePlugin(), ...(props.additionalPlugins || [])].filter(p => p); this.state = { suggestions: [], @@ -440,12 +442,17 @@ export class QueryField extends React.PureComponent +
{this.renderMenu()} qt.hints && qt.hints.length > 0); diff --git a/public/app/features/explore/TimePicker.tsx b/public/app/features/explore/TimePicker.tsx index b99618d257a..8476c6b2b27 100644 --- a/public/app/features/explore/TimePicker.tsx +++ b/public/app/features/explore/TimePicker.tsx @@ -3,7 +3,7 @@ import moment from 'moment'; import * as dateMath from 'app/core/utils/datemath'; import * as rangeUtil from 'app/core/utils/rangeutil'; -import { RawTimeRange, TimeRange } from 'app/types/series'; +import { RawTimeRange, TimeRange } from '@grafana/ui'; const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss'; export const DEFAULT_RANGE = { diff --git a/public/app/features/panel/panel_ctrl.ts b/public/app/features/panel/panel_ctrl.ts index 2bd43ee2a29..432d22fecdf 100644 --- a/public/app/features/panel/panel_ctrl.ts +++ b/public/app/features/panel/panel_ctrl.ts @@ -198,11 +198,10 @@ export class PanelCtrl { } calculatePanelHeight() { - if (this.panel.fullscreen) { - const docHeight = $('.react-grid-layout').height(); - const editHeight = Math.floor(docHeight * 0.35); - const fullscreenHeight = Math.floor(docHeight * 0.8); - this.containerHeight = this.panel.isEditing ? editHeight : fullscreenHeight; + if (this.panel.isEditing) { + this.containerHeight = $('.panel-wrapper--edit').height(); + } else if (this.panel.fullscreen) { + this.containerHeight = $('.panel-wrapper--view').height(); } else { this.containerHeight = this.panel.gridPos.h * GRID_CELL_HEIGHT + (this.panel.gridPos.h - 1) * GRID_CELL_VMARGIN; } diff --git a/public/app/features/panel/partials/general_tab.html b/public/app/features/panel/partials/general_tab.html index 7a4f1f7e17f..d6c2d4804a0 100644 --- a/public/app/features/panel/partials/general_tab.html +++ b/public/app/features/panel/partials/general_tab.html @@ -22,7 +22,7 @@
- Repat + Repeat
@@ -42,7 +42,7 @@
-
Drildown Links
+
Drilldown Links
diff --git a/public/app/features/plugins/PluginList.tsx b/public/app/features/plugins/PluginList.tsx index 0074839e754..fd490fcfe1e 100644 --- a/public/app/features/plugins/PluginList.tsx +++ b/public/app/features/plugins/PluginList.tsx @@ -1,5 +1,5 @@ import React, { SFC } from 'react'; -import classNames from 'classnames/bind'; +import classNames from 'classnames'; import PluginListItem from './PluginListItem'; import { Plugin } from 'app/types'; import { LayoutMode, LayoutModes } from '../../core/components/LayoutSelector/LayoutSelector'; diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index 8e0958f6c1b..3c4fa29382d 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -26,16 +26,10 @@ import * as ticks from 'app/core/utils/ticks'; import impressionSrv from 'app/core/services/impression_srv'; import builtInPlugins from './built_in_plugins'; import * as d3 from 'd3'; +import * as grafanaUI from '@grafana/ui'; // rxjs -import { Observable } from 'rxjs/Observable'; -import { Subject } from 'rxjs/Subject'; - -// these imports add functions to Observable -import 'rxjs/add/observable/empty'; -import 'rxjs/add/observable/from'; -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/combineAll'; +import { Observable, Subject } from 'rxjs'; // add cache busting const bust = `?_cache=${Date.now()}`; @@ -71,6 +65,7 @@ function exposeToPlugin(name: string, component: any) { }); } +exposeToPlugin('@grafana/ui', grafanaUI); exposeToPlugin('lodash', _); exposeToPlugin('moment', moment); exposeToPlugin('jquery', jquery); diff --git a/public/app/features/teams/TeamList.tsx b/public/app/features/teams/TeamList.tsx index d8e12e338e9..d1551d6baa6 100644 --- a/public/app/features/teams/TeamList.tsx +++ b/public/app/features/teams/TeamList.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; import { hot } from 'react-hot-loader'; import PageHeader from 'app/core/components/PageHeader/PageHeader'; -import DeleteButton from 'app/core/components/DeleteButton/DeleteButton'; +import { DeleteButton } from '@grafana/ui'; import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import PageLoader from 'app/core/components/PageLoader/PageLoader'; import { NavModel, Team } from '../../types'; @@ -58,7 +58,7 @@ export class TeamList extends PureComponent {
{team.memberCount} - this.deleteTeam(team)} /> + this.deleteTeam(team)} /> ); diff --git a/public/app/features/teams/TeamMembers.tsx b/public/app/features/teams/TeamMembers.tsx index 0e20f4be664..a25f1786a5b 100644 --- a/public/app/features/teams/TeamMembers.tsx +++ b/public/app/features/teams/TeamMembers.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; import SlideDown from 'app/core/components/Animations/SlideDown'; import { UserPicker } from 'app/core/components/Select/UserPicker'; -import DeleteButton from 'app/core/components/DeleteButton/DeleteButton'; +import { DeleteButton } from '@grafana/ui'; import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; import { TeamMember, User } from 'app/types'; import { loadTeamMembers, addTeamMember, removeTeamMember, setSearchMemberQuery } from './state/actions'; @@ -76,7 +76,7 @@ export class TeamMembers extends PureComponent { {member.email} {syncEnabled && this.renderLabels(member.labels)} - this.onRemoveMember(member)} /> + this.onRemoveMember(member)} /> ); diff --git a/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap index 73f081d496a..ae94691df0e 100644 --- a/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap @@ -124,7 +124,7 @@ exports[`Render should render teams table 1`] = ` className="text-right" > @@ -174,7 +174,7 @@ exports[`Render should render teams table 1`] = ` className="text-right" > @@ -224,7 +224,7 @@ exports[`Render should render teams table 1`] = ` className="text-right" > @@ -274,7 +274,7 @@ exports[`Render should render teams table 1`] = ` className="text-right" > @@ -324,7 +324,7 @@ exports[`Render should render teams table 1`] = ` className="text-right" > diff --git a/public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap index d0a88bd97b0..5ebddb36d48 100644 --- a/public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap @@ -204,7 +204,7 @@ exports[`Render should render team members 1`] = ` className="text-right" > @@ -229,7 +229,7 @@ exports[`Render should render team members 1`] = ` className="text-right" > @@ -254,7 +254,7 @@ exports[`Render should render team members 1`] = ` className="text-right" > @@ -279,7 +279,7 @@ exports[`Render should render team members 1`] = ` className="text-right" > @@ -304,7 +304,7 @@ exports[`Render should render team members 1`] = ` className="text-right" > @@ -441,7 +441,7 @@ exports[`Render should render team members when sync enabled 1`] = ` className="text-right" > @@ -482,7 +482,7 @@ exports[`Render should render team members when sync enabled 1`] = ` className="text-right" > @@ -523,7 +523,7 @@ exports[`Render should render team members when sync enabled 1`] = ` className="text-right" > @@ -564,7 +564,7 @@ exports[`Render should render team members when sync enabled 1`] = ` className="text-right" > @@ -605,7 +605,7 @@ exports[`Render should render team members when sync enabled 1`] = ` className="text-right" > diff --git a/public/app/features/users/UsersActionBar.tsx b/public/app/features/users/UsersActionBar.tsx index 82fae2ed059..28ed4754d01 100644 --- a/public/app/features/users/UsersActionBar.tsx +++ b/public/app/features/users/UsersActionBar.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; -import classNames from 'classnames/bind'; +import classNames from 'classnames'; import { setUsersSearchQuery } from './state/actions'; import { getInviteesCount, getUsersSearchQuery } from './state/selectors'; diff --git a/public/app/plugins/datasource/influxdb/influx_query.ts b/public/app/plugins/datasource/influxdb/influx_query.ts index 60eac1d3f2b..3ee9d703c54 100644 --- a/public/app/plugins/datasource/influxdb/influx_query.ts +++ b/public/app/plugins/datasource/influxdb/influx_query.ts @@ -256,6 +256,10 @@ export default class InfluxQuery { query += ' SLIMIT ' + target.slimit; } + if (target.tz) { + query += " tz('" + target.tz + "')"; + } + return query; } diff --git a/public/app/plugins/datasource/influxdb/partials/query.editor.html b/public/app/plugins/datasource/influxdb/partials/query.editor.html index e1c35d77115..658cf18daee 100644 --- a/public/app/plugins/datasource/influxdb/partials/query.editor.html +++ b/public/app/plugins/datasource/influxdb/partials/query.editor.html @@ -119,6 +119,16 @@
+
+
+ + +
+
+
+
+
+
diff --git a/public/app/plugins/datasource/influxdb/query_ctrl.ts b/public/app/plugins/datasource/influxdb/query_ctrl.ts index f531fe6c4d9..57460ec4d2a 100644 --- a/public/app/plugins/datasource/influxdb/query_ctrl.ts +++ b/public/app/plugins/datasource/influxdb/query_ctrl.ts @@ -100,6 +100,9 @@ export class InfluxQueryCtrl extends QueryCtrl { if (!this.target.slimit) { options.push(this.uiSegmentSrv.newSegment({ value: 'SLIMIT' })); } + if (!this.target.tz) { + options.push(this.uiSegmentSrv.newSegment({ value: 'tz' })); + } if (this.target.orderByTime === 'ASC') { options.push(this.uiSegmentSrv.newSegment({ value: 'ORDER BY time DESC' })); } @@ -124,6 +127,10 @@ export class InfluxQueryCtrl extends QueryCtrl { this.target.slimit = 10; break; } + case 'tz': { + this.target.tz = 'UTC'; + break; + } case 'ORDER BY time DESC': { this.target.orderByTime = 'DESC'; break; diff --git a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx index 01c6519916d..49f6b74e8b6 100644 --- a/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx +++ b/public/app/plugins/datasource/loki/components/LokiCheatSheet.tsx @@ -2,14 +2,23 @@ import React from 'react'; const CHEAT_SHEET_ITEMS = [ { - title: 'Logs From a Job', + title: 'See your logs', + label: 'Start by selecting a log stream from the Log labels selector.', + }, + { + title: 'Logs from a "job"', expression: '{job="default/prometheus"}', label: 'Returns all log lines emitted by instances of this job.', }, { - title: 'Search For Text', - expression: '{app="cassandra"} Maximum memory usage', - label: 'Returns all log lines for the selector and highlights the given text in the results.', + title: 'Combine stream selectors', + expression: '{app="cassandra",namespace="prod"}', + label: 'Returns all log lines from streams that have both labels.', + }, + { + title: 'Search for text', + expression: '{app="cassandra"} (duration|latency)\\s*(=|is|of)\\s*[\\d\\.]+', + label: 'Add a regular expression after the selector to filter for.', }, ]; @@ -19,12 +28,14 @@ export default (props: any) => ( {CHEAT_SHEET_ITEMS.map(item => (
{item.title}
-
props.onClickExample({ refId: '1', expr: item.expression })} - > - {item.expression} -
+ {item.expression && ( +
props.onClickExample({ refId: '1', expr: item.expression })} + > + {item.expression} +
+ )}
{item.label}
))} diff --git a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx index 005706bb8d1..820c964f7b1 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryField.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryField.tsx @@ -15,6 +15,16 @@ import { DataQuery } from 'app/types'; const PRISM_SYNTAX = 'promql'; +function getChooserText(hasSytax, hasLogLabels) { + if (!hasSytax) { + return 'Loading labels...'; + } + if (!hasLogLabels) { + return '(No labels found)'; + } + return 'Log labels'; +} + export function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadText }: QueryFieldState): string { // Modify suggestion based on context switch (typeaheadContext) { @@ -67,7 +77,10 @@ interface LokiQueryFieldState { class LokiQueryField extends React.PureComponent { plugins: any[]; + pluginsSearch: any[]; languageProvider: any; + modifiedSearch: string; + modifiedQuery: string; constructor(props: LokiQueryFieldProps, context) { super(props, context); @@ -85,6 +98,8 @@ class LokiQueryField extends React.PureComponent 0; + const chooserText = getChooserText(syntaxLoaded, hasLogLabels); return (
@@ -208,7 +224,7 @@ class LokiQueryField extends React.PureComponent diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index ddb4d6ed549..b7f67ffc0e7 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -1,10 +1,39 @@ import LokiDatasource from './datasource'; describe('LokiDatasource', () => { - const instanceSettings = { + const instanceSettings: any = { url: 'myloggingurl', }; + describe('when querying', () => { + const backendSrvMock = { datasourceRequest: jest.fn() }; + + const templateSrvMock = { + getAdhocFilters: () => [], + replace: a => a, + }; + + const range = { from: 'now-6h', to: 'now' }; + + test('should use default max lines when no limit given', () => { + const ds = new LokiDatasource(instanceSettings, backendSrvMock, templateSrvMock); + backendSrvMock.datasourceRequest = jest.fn(); + ds.query({ range, targets: [{ expr: 'foo' }] }); + expect(backendSrvMock.datasourceRequest.mock.calls.length).toBe(1); + expect(backendSrvMock.datasourceRequest.mock.calls[0][0].url).toContain('limit=1000'); + }); + + test('should use custom max lines if limit is set', () => { + const customData = { ...(instanceSettings.jsonData || {}), maxLines: 20 }; + const customSettings = { ...instanceSettings, jsonData: customData }; + const ds = new LokiDatasource(customSettings, backendSrvMock, templateSrvMock); + backendSrvMock.datasourceRequest = jest.fn(); + ds.query({ range, targets: [{ expr: 'foo' }] }); + expect(backendSrvMock.datasourceRequest.mock.calls.length).toBe(1); + expect(backendSrvMock.datasourceRequest.mock.calls[0][0].url).toContain('limit=20'); + }); + }); + describe('when performing testDataSource', () => { let ds; let result; diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index ebbe6bb4b56..eb33c7ef285 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -9,11 +9,11 @@ import LanguageProvider from './language_provider'; import { mergeStreamsToLogs } from './result_transformer'; import { formatQuery, parseQuery } from './query_utils'; -export const DEFAULT_LIMIT = 1000; +export const DEFAULT_MAX_LINES = 1000; const DEFAULT_QUERY_PARAMS = { direction: 'BACKWARD', - limit: DEFAULT_LIMIT, + limit: DEFAULT_MAX_LINES, regexp: '', query: '', }; @@ -29,10 +29,13 @@ function serializeParams(data: any) { export default class LokiDatasource { languageProvider: LanguageProvider; + maxLines: number; /** @ngInject */ constructor(private instanceSettings, private backendSrv, private templateSrv) { this.languageProvider = new LanguageProvider(this); + const settingsData = instanceSettings.jsonData || {}; + this.maxLines = parseInt(settingsData.maxLines, 10) || DEFAULT_MAX_LINES; } _request(apiUrl: string, data?, options?: any) { @@ -47,7 +50,7 @@ export default class LokiDatasource { } mergeStreams(streams: LogsStream[], intervalMs: number): LogsModel { - const logs = mergeStreamsToLogs(streams); + const logs = mergeStreamsToLogs(streams, this.maxLines); logs.series = makeSeriesForLogs(logs.rows, intervalMs); return logs; } @@ -61,6 +64,7 @@ export default class LokiDatasource { ...parseQuery(interpolated), start, end, + limit: this.maxLines, }; } @@ -77,6 +81,9 @@ export default class LokiDatasource { return Promise.all(queries).then((results: any[]) => { // Flatten streams from multiple queries const allStreams: LogsStream[] = results.reduce((acc, response, i) => { + if (!response) { + return acc; + } const streams: LogsStream[] = response.data.streams || []; // Inject search for match highlighting const search: string = queryTargets[i].regexp; diff --git a/public/app/plugins/datasource/loki/partials/config.html b/public/app/plugins/datasource/loki/partials/config.html index 8e79cc0adcc..d209b51730a 100644 --- a/public/app/plugins/datasource/loki/partials/config.html +++ b/public/app/plugins/datasource/loki/partials/config.html @@ -1,2 +1,16 @@ - \ No newline at end of file + + +
+
+
+ Maximum lines + + + Loki queries must contain a limit of the maximum number of lines returned (default: 1000). + Increase this limit to have a bigger result set for ad-hoc analysis. + Decrease this limit if your browser becomes sluggish when displaying the log results. + +
+
+
diff --git a/public/app/plugins/datasource/loki/query_utils.ts b/public/app/plugins/datasource/loki/query_utils.ts index 795d1e2ceeb..4f246ea8e28 100644 --- a/public/app/plugins/datasource/loki/query_utils.ts +++ b/public/app/plugins/datasource/loki/query_utils.ts @@ -1,5 +1,6 @@ const selectorRegexp = /(?:^|\s){[^{]*}/g; export function parseQuery(input: string) { + input = input || ''; const match = input.match(selectorRegexp); let query = ''; let regexp = input; diff --git a/public/app/plugins/datasource/loki/result_transformer.test.ts b/public/app/plugins/datasource/loki/result_transformer.test.ts index c23a370ab81..bacc96c6f41 100644 --- a/public/app/plugins/datasource/loki/result_transformer.test.ts +++ b/public/app/plugins/datasource/loki/result_transformer.test.ts @@ -99,7 +99,7 @@ describe('mergeStreamsToLogs()', () => { entries: [ { line: 'WARN boooo', - timestamp: '1970-01-01T00:00:00Z', + ts: '1970-01-01T00:00:00Z', }, ], }; @@ -120,7 +120,7 @@ describe('mergeStreamsToLogs()', () => { entries: [ { line: 'WARN boooo', - timestamp: '1970-01-01T00:00:01Z', + ts: '1970-01-01T00:00:01Z', }, ], }; @@ -129,11 +129,11 @@ describe('mergeStreamsToLogs()', () => { entries: [ { line: 'INFO 1', - timestamp: '1970-01-01T00:00:00Z', + ts: '1970-01-01T00:00:00Z', }, { line: 'INFO 2', - timestamp: '1970-01-01T00:00:02Z', + ts: '1970-01-01T00:00:02Z', }, ], }; diff --git a/public/app/plugins/datasource/loki/result_transformer.ts b/public/app/plugins/datasource/loki/result_transformer.ts index f922595e4b8..1f86f20d6fd 100644 --- a/public/app/plugins/datasource/loki/result_transformer.ts +++ b/public/app/plugins/datasource/loki/result_transformer.ts @@ -11,7 +11,7 @@ import { LogsStreamLabels, LogsMetaKind, } from 'app/core/logs_model'; -import { DEFAULT_LIMIT } from './datasource'; +import { DEFAULT_MAX_LINES } from './datasource'; /** * Returns the log level of a log line. @@ -116,10 +116,11 @@ export function processEntry( uniqueLabels: LogsStreamLabels, search: string ): LogRow { - const { line, timestamp } = entry; + const { line } = entry; + const ts = entry.ts || entry.timestamp; // Assumes unique-ness, needs nanosec precision for timestamp - const key = `EK${timestamp}${labels}`; - const time = moment(timestamp); + const key = `EK${ts}${labels}`; + const time = moment(ts); const timeEpochMs = time.valueOf(); const timeFromNow = time.fromNow(); const timeLocal = time.format('YYYY-MM-DD HH:mm:ss'); @@ -135,11 +136,11 @@ export function processEntry( entry: line, labels: parsedLabels, searchWords: search ? [search] : [], - timestamp: timestamp, + timestamp: ts, }; } -export function mergeStreamsToLogs(streams: LogsStream[], limit = DEFAULT_LIMIT): LogsModel { +export function mergeStreamsToLogs(streams: LogsStream[], limit = DEFAULT_MAX_LINES): LogsModel { // Unique model identifier const id = streams.map(stream => stream.labels).join(); diff --git a/public/app/plugins/panel/gauge/Threshold.test.tsx b/public/app/plugins/panel/gauge/Threshold.test.tsx index 3b2becd9859..3fa508b98a9 100644 --- a/public/app/plugins/panel/gauge/Threshold.test.tsx +++ b/public/app/plugins/panel/gauge/Threshold.test.tsx @@ -2,7 +2,8 @@ import React from 'react'; import { shallow } from 'enzyme'; import Thresholds from './Thresholds'; import { defaultProps, OptionsProps } from './module'; -import { BasicGaugeColor, PanelOptionsProps } from 'app/types'; +import { BasicGaugeColor } from 'app/types'; +import { PanelOptionsProps } from '@grafana/ui'; const setup = (propOverrides?: object) => { const props: PanelOptionsProps = { diff --git a/public/app/plugins/panel/gauge/module.tsx b/public/app/plugins/panel/gauge/module.tsx index 152e7c20b5e..245a17abe52 100644 --- a/public/app/plugins/panel/gauge/module.tsx +++ b/public/app/plugins/panel/gauge/module.tsx @@ -5,15 +5,8 @@ import ValueOptions from './ValueOptions'; import GaugeOptions from './GaugeOptions'; import Thresholds from './Thresholds'; import ValueMappings from './ValueMappings'; -import { - BasicGaugeColor, - NullValueMode, - PanelOptionsProps, - PanelProps, - RangeMap, - Threshold, - ValueMap, -} from 'app/types'; +import { PanelOptionsProps, PanelProps, NullValueMode } from '@grafana/ui'; +import { BasicGaugeColor, RangeMap, Threshold, ValueMap } from 'app/types'; export interface OptionsProps { baseColor: string; diff --git a/public/app/plugins/panel/graph/specs/graph.test.ts b/public/app/plugins/panel/graph/specs/graph.test.ts index be243587820..58a35ea2a5f 100644 --- a/public/app/plugins/panel/graph/specs/graph.test.ts +++ b/public/app/plugins/panel/graph/specs/graph.test.ts @@ -114,6 +114,7 @@ describe('grafanaGraph', () => { {} ); + // @ts-ignore $.plot = ctrl.plot = jest.fn(); scope.ctrl = ctrl; diff --git a/public/app/plugins/panel/graph2/GraphOptions.tsx b/public/app/plugins/panel/graph2/GraphOptions.tsx index e87c03da634..6bb4b2c13d5 100644 --- a/public/app/plugins/panel/graph2/GraphOptions.tsx +++ b/public/app/plugins/panel/graph2/GraphOptions.tsx @@ -6,7 +6,7 @@ import React, { PureComponent } from 'react'; import { Switch } from 'app/core/components/Switch/Switch'; // Types -import { PanelOptionsProps } from 'app/types'; +import { PanelOptionsProps } from '@grafana/ui'; import { Options } from './types'; export class GraphOptions extends PureComponent> { diff --git a/public/app/plugins/panel/graph2/GraphPanel.tsx b/public/app/plugins/panel/graph2/GraphPanel.tsx index a7ef45e5428..a08276e5179 100644 --- a/public/app/plugins/panel/graph2/GraphPanel.tsx +++ b/public/app/plugins/panel/graph2/GraphPanel.tsx @@ -1,15 +1,10 @@ // Libraries import _ from 'lodash'; import React, { PureComponent } from 'react'; +import colors from 'app/core/utils/colors'; -// Components -import Graph from 'app/viz/Graph'; - -// Services & Utils -import { getTimeSeriesVMs } from 'app/viz/state/timeSeries'; - -// Types -import { PanelProps, NullValueMode } from 'app/types'; +// Components & Types +import { Graph, PanelProps, NullValueMode, processTimeSeries } from '@grafana/ui'; import { Options } from './types'; interface Props extends PanelProps {} @@ -23,9 +18,10 @@ export class GraphPanel extends PureComponent { const { timeSeries, timeRange, width, height } = this.props; const { showLines, showBars, showPoints } = this.props.options; - const vmSeries = getTimeSeriesVMs({ + const vmSeries = processTimeSeries({ timeSeries: timeSeries, nullValueMode: NullValueMode.Ignore, + colorPalette: colors, }); return ( diff --git a/public/app/plugins/panel/text2/module.tsx b/public/app/plugins/panel/text2/module.tsx index aaca23ccbf0..68523ff0880 100644 --- a/public/app/plugins/panel/text2/module.tsx +++ b/public/app/plugins/panel/text2/module.tsx @@ -1,5 +1,5 @@ import React, { PureComponent } from 'react'; -import { PanelProps } from 'app/types'; +import { PanelProps } from '@grafana/ui'; export class Text2 extends PureComponent { constructor(props) { diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 80d55eedb60..c2c59d35f5b 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -1,6 +1,7 @@ import { Value } from 'slate'; -import { DataQuery, RawTimeRange } from './series'; +import { DataQuery } from './series'; +import { RawTimeRange } from '@grafana/ui'; import TableModel from 'app/core/table_model'; import { LogsModel } from 'app/core/logs_model'; import { DataSourceSelectItem } from 'app/types/datasources'; @@ -154,11 +155,11 @@ export interface ExploreState { datasourceError: any; datasourceLoading: boolean | null; datasourceMissing: boolean; - datasourceName?: string; exploreDatasources: DataSourceSelectItem[]; graphInterval: number; // in ms graphResult?: any[]; history: HistoryItem[]; + initialDatasource?: string; initialQueries: DataQuery[]; logsHighlighterExpressions?: string[]; logsResult?: LogsModel; diff --git a/public/app/types/index.ts b/public/app/types/index.ts index f2a5f807645..ab52b03ab17 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -8,20 +8,8 @@ import { DashboardAcl, OrgRole, PermissionLevel } from './acl'; import { ApiKey, ApiKeysState, NewApiKey } from './apiKeys'; import { Invitee, OrgUser, User, UsersState, UserState } from './user'; import { DataSource, DataSourceSelectItem, DataSourcesState } from './datasources'; -import { - TimeRange, - LoadingState, - TimeSeries, - TimeSeriesVM, - TimeSeriesVMs, - TimeSeriesStats, - NullValueMode, - DataQuery, - DataQueryResponse, - DataQueryOptions, - IntervalValues, -} from './series'; -import { BasicGaugeColor, MappingType, PanelProps, PanelOptionsProps, RangeMap, Threshold, ValueMap } from './panel'; +import { DataQuery, DataQueryResponse, DataQueryOptions } from './series'; +import { BasicGaugeColor, MappingType, RangeMap, Threshold, ValueMap } from './panel'; import { PluginDashboard, PluginMeta, Plugin, PanelPlugin, PluginsState } from './plugins'; import { Organization, OrganizationState } from './organization'; import { @@ -68,16 +56,7 @@ export { OrgUser, User, UsersState, - TimeRange, - LoadingState, PanelPlugin, - PanelProps, - PanelOptionsProps, - TimeSeries, - TimeSeriesVM, - TimeSeriesVMs, - NullValueMode, - TimeSeriesStats, DataQuery, DataQueryResponse, DataQueryOptions, @@ -95,7 +74,6 @@ export { ValidationRule, ValueMap, RangeMap, - IntervalValues, MappingType, BasicGaugeColor, }; diff --git a/public/app/types/jquery.d.ts b/public/app/types/jquery.d.ts new file mode 100644 index 00000000000..4a6f60b6029 --- /dev/null +++ b/public/app/types/jquery.d.ts @@ -0,0 +1,17 @@ +interface JQueryPlot { + (element: HTMLElement | JQuery, data: any, options: any): void; + plugins: any[]; +} + +interface JQueryStatic { + plot: JQueryPlot; +} + +interface JQuery { + place_tt: any; + modal: any; + tagsinput: any; + typeahead: any; + accessKey: any; + tooltip: any; +} diff --git a/public/app/types/panel.ts b/public/app/types/panel.ts index af371e16573..31674d20304 100644 --- a/public/app/types/panel.ts +++ b/public/app/types/panel.ts @@ -1,34 +1,3 @@ -import { LoadingState, TimeSeries, TimeRange } from './series'; - -export interface PanelProps { - timeSeries: TimeSeries[]; - timeRange: TimeRange; - loading: LoadingState; - options: T; - renderCounter: number; - width: number; - height: number; -} - -export interface PanelOptionsProps { - options: T; - onChange: (options: T) => void; -} - -export interface PanelSize { - width: number; - height: number; -} - -export interface PanelMenuItem { - type?: 'submenu' | 'divider'; - text?: string; - iconClassName?: string; - onClick?: () => void; - shortcut?: string; - subMenu?: PanelMenuItem[]; -} - export interface Threshold { index: number; value: number; diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index a3519e5b5cc..a1403c7a71c 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -1,5 +1,5 @@ import { ComponentClass } from 'react'; -import { PanelProps, PanelOptionsProps } from './panel'; +import { PanelProps, PanelOptionsProps } from '@grafana/ui'; export interface PluginExports { Datasource?: any; diff --git a/public/app/types/series.ts b/public/app/types/series.ts index a9585a2c842..9fe68955da5 100644 --- a/public/app/types/series.ts +++ b/public/app/types/series.ts @@ -1,75 +1,5 @@ -import { Moment } from 'moment'; import { PluginMeta } from './plugins'; - -export enum LoadingState { - NotStarted = 'NotStarted', - Loading = 'Loading', - Done = 'Done', - Error = 'Error', -} - -export interface RawTimeRange { - from: Moment | string; - to: Moment | string; -} - -export interface TimeRange { - from: Moment; - to: Moment; - raw: RawTimeRange; -} - -export interface IntervalValues { - interval: string; // 10s,5m - intervalMs: number; -} - -export type TimeSeriesValue = string | number | null; - -export type TimeSeriesPoints = TimeSeriesValue[][]; - -export interface TimeSeries { - target: string; - datapoints: TimeSeriesPoints; - unit?: string; -} - -/** View model projection of a time series */ -export interface TimeSeriesVM { - label: string; - color: string; - data: TimeSeriesValue[][]; - stats: TimeSeriesStats; -} - -export interface TimeSeriesStats { - total: number; - max: number; - min: number; - logmin: number; - avg: number | null; - current: number | null; - first: number | null; - delta: number; - diff: number | null; - range: number | null; - timeStep: number; - count: number; - allIsNull: boolean; - allIsZero: boolean; -} - -export enum NullValueMode { - Null = 'null', - Ignore = 'connected', - AsZero = 'null as zero', -} - -/** View model projection of many time series */ -export interface TimeSeriesVMs { - [index: number]: TimeSeriesVM; - length: number; -} +import { TimeSeries, TimeRange, RawTimeRange } from '@grafana/ui'; export interface DataQueryResponse { data: TimeSeries[]; diff --git a/public/app/viz/Gauge.tsx b/public/app/viz/Gauge.tsx index 9907ddf575f..031d856f492 100644 --- a/public/app/viz/Gauge.tsx +++ b/public/app/viz/Gauge.tsx @@ -1,6 +1,7 @@ import React, { PureComponent } from 'react'; import $ from 'jquery'; -import { BasicGaugeColor, MappingType, RangeMap, Threshold, TimeSeriesVMs, ValueMap } from 'app/types'; +import { BasicGaugeColor, MappingType, RangeMap, Threshold, ValueMap } from 'app/types'; +import { TimeSeriesVMs } from '@grafana/ui'; import config from '../core/config'; import kbn from '../core/utils/kbn'; diff --git a/public/app/viz/state/timeSeries.ts b/public/app/viz/state/timeSeries.ts index e22cb4681b7..782383957bc 100644 --- a/public/app/viz/state/timeSeries.ts +++ b/public/app/viz/state/timeSeries.ts @@ -5,7 +5,7 @@ import _ from 'lodash'; import colors from 'app/core/utils/colors'; // Types -import { TimeSeries, TimeSeriesVMs, NullValueMode } from 'app/types'; +import { TimeSeries, TimeSeriesVMs, NullValueMode } from '@grafana/ui'; interface Options { timeSeries: TimeSeries[]; diff --git a/public/sass/.sass-lint.yml b/public/sass/.sass-lint.yml index 2e79bc584db..1d1e2fd9c73 100644 --- a/public/sass/.sass-lint.yml +++ b/public/sass/.sass-lint.yml @@ -1,23 +1,41 @@ options: formatter: stylish -files: - include: '**/*.s+(a|c)ss' - ignore: - - './utils/*.scss' - rules: + quotes: + - 0 + - + style: 'single' + + brace-style: + - 2 + - + style: '1tbs' + + space-before-brace: + - 2 + + no-duplicate-properties: + - 0 + - + exclude: + - 'font-size' + - 'word-break' + + empty-line-between-blocks: + - 0 + - + allow-single-line-rulesets: 1 + # Extends extends-before-mixins: 0 extends-before-declarations: 0 placeholder-in-extend: 0 - # Mixins mixins-before-declarations: 0 # Line Spacing one-declaration-per-line: 0 - empty-line-between-blocks: 0 single-line-per-selector: 0 # Disallows @@ -25,7 +43,6 @@ rules: no-color-literals: 0 no-css-comments: 0 no-debug: 0 - no-duplicate-properties: 0 no-empty-rulesets: 1 no-extends: 0 no-ids: 0 @@ -40,6 +57,7 @@ rules: no-vendor-prefixes: 0 no-warn: 0 property-units: 0 + pseudo-element: 0 # Nesting force-attribute-nesting: 0 @@ -57,7 +75,6 @@ rules: # Style Guide bem-depth: 0 border-zero: 0 - brace-style: 0 clean-import-paths: 0 empty-args: 0 hex-length: 0 @@ -66,7 +83,6 @@ rules: leading-zero: 0 nesting-depth: 0 property-sort-order: 0 - quotes: 0 shorthand-values: 0 url-quotes: 0 variable-for-property: 0 @@ -76,12 +92,10 @@ rules: space-after-comma: 0 space-before-colon: 0 space-after-colon: 0 - space-before-brace: 0 space-before-bang: 0 space-after-bang: 0 space-between-parens: 0 space-around-operator: 0 # Final Items - trailing-semicolon: 0 final-newline: 0 diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index bc6b024f643..404a212241b 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -38,6 +38,9 @@ @import 'layout/lists'; @import 'layout/page'; +// LOAD @grafana/ui components +@import '../../packages/grafana-ui/src/index'; + // COMPONENTS @import 'components/scrollbar'; @import 'components/cards'; @@ -98,7 +101,6 @@ @import 'components/form_select_box'; @import 'components/panel_editor'; @import 'components/toolbar'; -@import 'components/delete_button'; @import 'components/add_data_source.scss'; @import 'components/page_loader'; @import 'components/thresholds'; diff --git a/public/sass/components/_drop.scss b/public/sass/components/_drop.scss index 6568414ed88..8d9d4fc6b7d 100644 --- a/public/sass/components/_drop.scss +++ b/public/sass/components/_drop.scss @@ -5,13 +5,13 @@ $useDropShadow: false; $attachmentOffset: 0%; $easing: cubic-bezier(0, 0, 0.265, 1); -@include drop-theme("error", $popover-error-bg, $popover-color); -@include drop-theme("popover", $popover-bg, $popover-color, $popover-border-color); -@include drop-theme("help", $popover-help-bg, $popover-help-color); +@include drop-theme('error', $popover-error-bg, $popover-color); +@include drop-theme('popover', $popover-bg, $popover-color, $popover-border-color); +@include drop-theme('help', $popover-help-bg, $popover-help-color); -@include drop-animation-scale("drop", "help", $attachmentOffset: $attachmentOffset, $easing: $easing); -@include drop-animation-scale("drop", "error", $attachmentOffset: $attachmentOffset, $easing: $easing); -@include drop-animation-scale("drop", "popover", $attachmentOffset: $attachmentOffset, $easing: $easing); +@include drop-animation-scale('drop', 'help', $attachmentOffset: $attachmentOffset, $easing: $easing); +@include drop-animation-scale('drop', 'error', $attachmentOffset: $attachmentOffset, $easing: $easing); +@include drop-animation-scale('drop', 'popover', $attachmentOffset: $attachmentOffset, $easing: $easing); .drop-element { z-index: 10000; diff --git a/public/sass/components/_filter-list.scss b/public/sass/components/_filter-list.scss index 7713aa05ac2..90d0a21c539 100644 --- a/public/sass/components/_filter-list.scss +++ b/public/sass/components/_filter-list.scss @@ -67,17 +67,17 @@ text-transform: uppercase; &.online { - background-image: url("/img/online.svg"); + background-image: url('/img/online.svg'); color: $online; } &.warn { - background-image: url("/img/warn-tiny.svg"); + background-image: url('/img/warn-tiny.svg'); color: $warn; } &.critical { - background-image: url("/img/critical.svg"); + background-image: url('/img/critical.svg'); color: $critical; } } diff --git a/public/sass/components/_jsontree.scss b/public/sass/components/_jsontree.scss index 665deda0f12..39804a9c240 100644 --- a/public/sass/components/_jsontree.scss +++ b/public/sass/components/_jsontree.scss @@ -3,16 +3,20 @@ json-tree { .json-tree-key { vertical-align: middle; } + .expandable { position: relative; + &::before { pointer-events: none; } + &::before, & > .json-tree-key { cursor: pointer; } } + .json-tree-branch-preview { display: inline-block; vertical-align: middle; @@ -24,36 +28,44 @@ json-tree { ul { padding-left: $spacer; } + li, ul { list-style: none; } + li { line-height: 1.3rem; } + .json-tree-key { color: $variable; padding: 5px 10px 5px 15px; + &::after { - content: ":"; + content: ':'; } } + json-node.expandable { &::before { - content: "\25b6"; + content: '\25b6'; position: absolute; left: 0px; font-size: 8px; transition: transform 0.1s ease; } + &.expanded::before { transform: rotate(90deg); } } + .json-tree-leaf-value, .json-tree-branch-preview { word-break: break-all; } + .json-tree-branch-preview { overflow: hidden; font-style: italic; diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 871bc0a6747..bfc1c4bb9b5 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -14,7 +14,7 @@ &--view { flex: 1 1 0; - height: 80%; + height: 90%; margin: 0 $dashboard-padding; padding-top: $dashboard-padding; } @@ -26,6 +26,7 @@ flex-direction: row; flex: 1 1 0; position: relative; + min-height: 0; } .panel-editor__right { @@ -36,6 +37,7 @@ margin: 0 20px 0 84px; border-radius: 3px; box-shadow: $panel-editor-shadow; + min-height: 0; } .panel-editor__close { @@ -60,6 +62,7 @@ flex-grow: 1; min-width: 0; display: flex; + min-height: 0; } .panel-editor__content { @@ -171,7 +174,6 @@ } .panel-editor-tabs { - position: relative; z-index: 2; display: flex; flex-direction: column; @@ -218,7 +220,6 @@ &:hover { filter: $panel-editor-side-menu-shadow; - transform: translate(-2px, -2px); transform: scale(1.1); } } diff --git a/public/sass/components/_panel_gettingstarted.scss b/public/sass/components/_panel_gettingstarted.scss index f46c4569589..5bbc4ba29ca 100644 --- a/public/sass/components/_panel_gettingstarted.scss +++ b/public/sass/components/_panel_gettingstarted.scss @@ -51,8 +51,7 @@ $path-position: $marker-size-half - ($path-height / 2); min-width: $marker-size; &::after { - right: -50%; - content: ""; + content: ''; display: block; position: absolute; z-index: 1; @@ -105,7 +104,7 @@ $path-position: $marker-size-half - ($path-height / 2); // change icon to check .icon-gf::before { - content: "\e604"; + content: '\e604'; } } .progress-text { diff --git a/public/sass/components/_panel_graph.scss b/public/sass/components/_panel_graph.scss index 9f7a9575b61..b35a600b9e1 100644 --- a/public/sass/components/_panel_graph.scss +++ b/public/sass/components/_panel_graph.scss @@ -368,7 +368,6 @@ font-size: $font-size-sm; position: absolute; text-align: center; - font-size: 12px; } .alert-handle-wrapper { diff --git a/public/sass/components/_panel_logs.scss b/public/sass/components/_panel_logs.scss index 7b4244902f1..dac962057bf 100644 --- a/public/sass/components/_panel_logs.scss +++ b/public/sass/components/_panel_logs.scss @@ -18,7 +18,7 @@ $column-horizontal-spacing: 10px; flex-wrap: wrap; > * { - margin-right: 1em; + margin-right: $spacer*2; } } @@ -31,20 +31,21 @@ $column-horizontal-spacing: 10px; .logs-panel-meta { flex: 1; color: $text-color-weak; - margin-bottom: 10px; + margin-bottom: $spacer; min-width: 30%; display: flex; } .logs-panel-meta__item { - margin-right: 1em; + margin-right: $spacer; display: flex; + align-items: baseline; } .logs-panel-meta__label { - margin-right: 0.5em; - font-size: 0.9em; - font-weight: 500; + margin-right: $spacer / 2; + font-size: $font-size-sm; + font-weight: $font-weight-semi-bold; } .logs-panel-meta__value { @@ -59,7 +60,7 @@ $column-horizontal-spacing: 10px; .logs-rows { font-family: $font-family-monospace; - font-size: 12px; + font-size: $font-size-sm; display: table; table-layout: fixed; } diff --git a/public/sass/components/_shortcuts.scss b/public/sass/components/_shortcuts.scss index b5f61872585..447ca8c12a2 100644 --- a/public/sass/components/_shortcuts.scss +++ b/public/sass/components/_shortcuts.scss @@ -33,9 +33,8 @@ text-align: center; margin-right: 0.3rem; padding: 3px 5px; - font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace; + font: 11px Consolas, 'Liberation Mono', Menlo, Courier, monospace; line-height: 10px; - color: #555; vertical-align: middle; background-color: $btn-inverse-bg; border: solid 1px $btn-inverse-bg-hl; diff --git a/public/sass/components/_sidemenu.scss b/public/sass/components/_sidemenu.scss index 5fdb1a5e32e..237574b93bc 100644 --- a/public/sass/components/_sidemenu.scss +++ b/public/sass/components/_sidemenu.scss @@ -29,12 +29,12 @@ .sidemenu-open { .sidemenu { background: $side-menu-bg; - position: initial; height: auto; box-shadow: $side-menu-shadow; position: relative; z-index: $zindex-sidemenu; } + .sidemenu__top, .sidemenu__bottom { display: block; @@ -212,7 +212,6 @@ li.sidemenu-org-switcher { .sidemenu { width: 100%; background: $side-menu-bg-mobile; - position: initial; height: auto; box-shadow: $side-menu-shadow; position: relative; diff --git a/public/sass/components/_slate_editor.scss b/public/sass/components/_slate_editor.scss index 25b20f180ef..200f102d69a 100644 --- a/public/sass/components/_slate_editor.scss +++ b/public/sass/components/_slate_editor.scss @@ -5,7 +5,7 @@ word-break: break-word; } -.slate-query-field-wrapper { +.slate-query-field__wrapper { position: relative; display: inline-block; padding: 6px 7px 4px; @@ -20,6 +20,10 @@ transition: all 0.3s; } +.slate-query-field__wrapper--disabled { + background-color: inherit; +} + .slate-typeahead { .typeahead { position: absolute; @@ -28,7 +32,6 @@ left: -10000px; opacity: 0; border-radius: $border-radius; - transition: opacity 0.75s; border: $panel-border; max-height: calc(66vh); overflow-y: scroll; diff --git a/public/sass/components/_submenu.scss b/public/sass/components/_submenu.scss index a6844b9e66b..d4be5264d9d 100644 --- a/public/sass/components/_submenu.scss +++ b/public/sass/components/_submenu.scss @@ -22,10 +22,8 @@ } .submenu-item { - margin-right: 20px; display: inline-block; margin-right: 15px; - display: inline-block; float: left; .fa-caret-down { @@ -42,7 +40,6 @@ background-color: $input-bg; border: 1px solid $input-border-color; border-radius: $input-border-radius; - color: $input-color; box-sizing: content-box; display: inline-block; color: $text-color; diff --git a/public/sass/components/_switch.scss b/public/sass/components/_switch.scss index ab100a8c752..92756f81a76 100644 --- a/public/sass/components/_switch.scss +++ b/public/sass/components/_switch.scss @@ -19,15 +19,14 @@ gf-form-switch[disabled] { } .gf-form-switch { + display: flex; position: relative; - display: inline-block; width: 60px; height: $gf-form-input-height; background: $switch-bg; border: 1px solid $input-border-color; border-left: none; border-radius: $input-border-radius; - display: flex; align-items: center; justify-content: center; @@ -77,14 +76,13 @@ input:checked + .gf-form-switch__slider::before { .gf-form-checkbox { position: relative; - display: inline-block; + display: flex; width: 50px; height: $gf-form-input-height; background: $switch-bg; border: 1px solid $input-border-color; border-left: none; border-radius: $input-border-radius; - display: flex; align-items: center; justify-content: center; diff --git a/public/sass/components/_tabbed_view.scss b/public/sass/components/_tabbed_view.scss index ef21d54f71c..a47d9d65ceb 100644 --- a/public/sass/components/_tabbed_view.scss +++ b/public/sass/components/_tabbed_view.scss @@ -31,7 +31,6 @@ .tabbed-view-close-btn { float: right; - padding: 0; margin: 0; background-color: transparent; border: none; diff --git a/public/sass/components/_tags.scss b/public/sass/components/_tags.scss index 014d9f0be1e..692259facb3 100644 --- a/public/sass/components/_tags.scss +++ b/public/sass/components/_tags.scss @@ -25,6 +25,7 @@ border-width: 1px; border-style: solid; box-shadow: 0 0 1px rgba($white, 0.2); + .icon-tag { position: relative; top: 1px; diff --git a/public/sass/components/_tagsinput.scss b/public/sass/components/_tagsinput.scss index f4f0ed4c84d..e8cf9ea44e9 100644 --- a/public/sass/components/_tagsinput.scss +++ b/public/sass/components/_tagsinput.scss @@ -24,12 +24,15 @@ [data-role='remove'] { margin-left: 8px; cursor: pointer; + &::after { content: 'x'; padding: 0px 2px; } + &:hover { box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + &:active { box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } diff --git a/public/sass/components/_timepicker.scss b/public/sass/components/_timepicker.scss index f3a2079f749..f424e42ded5 100644 --- a/public/sass/components/_timepicker.scss +++ b/public/sass/components/_timepicker.scss @@ -20,9 +20,12 @@ display: flex; flex-direction: column; position: absolute; + left: 20px; right: 20px; top: $navbarHeight; - width: 550px; + @include media-breakpoint-up(md) { + width: 550px; + } .popover-box { max-width: 100%; @@ -60,9 +63,11 @@ list-style: none; float: left; margin: 0 30px 10px 0px; + li { line-height: 22px; } + li.active { border-bottom: 1px solid $blue; margin: 3px 0; @@ -77,26 +82,32 @@ td { padding: 1px; } + button { @include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl); background-image: none; border: none; color: $text-color; + &.active span { color: $blue; font-weight: bold; } + .text-info { color: $orange; font-weight: bold; } + &.btn-sm { font-size: $font-size-sm; padding: 5px 11px; } + &:hover { color: $text-color-strong; } + &[disabled] { color: $text-color; } @@ -119,6 +130,7 @@ .fa-chevron-left::before { content: '\f053'; } + .fa-chevron-right::before { content: '\f054'; } diff --git a/public/sass/components/_tooltip.scss b/public/sass/components/_tooltip.scss index b5799e49301..650d8ba1b1a 100644 --- a/public/sass/components/_tooltip.scss +++ b/public/sass/components/_tooltip.scss @@ -11,21 +11,26 @@ font-size: 11px; line-height: 1.4; @include opacity(0); + &.in { @include opacity(100); } + &.top { margin-top: -3px; padding: 5px 0; } + &.right { margin-left: 3px; padding: 0 5px; } + &.bottom { margin-top: 3px; padding: 5px 0; } + &.left { margin-left: -3px; padding: 0 5px; @@ -60,6 +65,7 @@ border-width: $tooltipArrowWidth $tooltipArrowWidth 0; border-top-color: $tooltipArrowColor; } + &.right .tooltip-arrow { top: 50%; left: 0; @@ -67,6 +73,7 @@ border-width: $tooltipArrowWidth $tooltipArrowWidth $tooltipArrowWidth 0; border-right-color: $tooltipArrowColor; } + &.left .tooltip-arrow { top: 50%; right: 0; @@ -74,6 +81,7 @@ border-width: $tooltipArrowWidth 0 $tooltipArrowWidth $tooltipArrowWidth; border-left-color: $tooltipArrowColor; } + &.bottom .tooltip-arrow { top: 0; left: 50%; @@ -98,6 +106,7 @@ max-height: 600px; overflow: hidden; line-height: 14px; + a { color: $tooltipLinkColor; } diff --git a/public/sass/components/_view_states.scss b/public/sass/components/_view_states.scss index c0f8c72ab4f..b92bd596193 100644 --- a/public/sass/components/_view_states.scss +++ b/public/sass/components/_view_states.scss @@ -10,6 +10,7 @@ .navbar-page-btn { transform: translate3d(-36px, 0, 0); + i { opacity: 0; } @@ -40,9 +41,11 @@ .navbar { display: none; } + .scroll-canvas--dashboard { height: 100%; } + .submenu-controls { display: none; } diff --git a/public/sass/components/edit_sidemenu.scss b/public/sass/components/edit_sidemenu.scss index 4fc6795c068..8d60851b4d3 100644 --- a/public/sass/components/edit_sidemenu.scss +++ b/public/sass/components/edit_sidemenu.scss @@ -46,6 +46,7 @@ li { float: left; } + a { margin: 0.3rem 1rem; } diff --git a/public/sass/fonts.scss b/public/sass/fonts.scss index 8d50752f4e7..02fb498356a 100644 --- a/public/sass/fonts.scss +++ b/public/sass/fonts.scss @@ -1 +1 @@ -@import "base/fonts"; +@import 'base/fonts'; diff --git a/public/sass/grafana.dark.scss b/public/sass/grafana.dark.scss index 53193d213e6..f7f5163f36f 100644 --- a/public/sass/grafana.dark.scss +++ b/public/sass/grafana.dark.scss @@ -1,3 +1,3 @@ -@import "variables"; -@import "variables.dark"; -@import "grafana"; +@import 'variables'; +@import 'variables.dark'; +@import 'grafana'; diff --git a/public/sass/grafana.light.scss b/public/sass/grafana.light.scss index e03d24470e6..d7824edfc5d 100644 --- a/public/sass/grafana.light.scss +++ b/public/sass/grafana.light.scss @@ -1,3 +1,3 @@ -@import "variables"; -@import "variables.light"; -@import "grafana"; +@import 'variables'; +@import 'variables.light'; +@import 'grafana'; diff --git a/public/sass/layout/_page.scss b/public/sass/layout/_page.scss index a17c1c67d7b..818dc6c3e60 100644 --- a/public/sass/layout/_page.scss +++ b/public/sass/layout/_page.scss @@ -42,15 +42,13 @@ overflow: auto; height: 100%; -webkit-overflow-scrolling: touch; + display: flex; + flex-direction: column; &--dashboard { height: calc(100% - 56px); } - // Sticky footer - display: flex; - flex-direction: column; - > div { flex-grow: 1; } diff --git a/public/sass/mixins/_forms.scss b/public/sass/mixins/_forms.scss index ce488f0f636..5d2a4421353 100644 --- a/public/sass/mixins/_forms.scss +++ b/public/sass/mixins/_forms.scss @@ -13,16 +13,9 @@ .custom-control { color: $color; } - // Set the border and box shadow on specific inputs to match + .form-control { border-color: $color; - // @include box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work - - &:focus { - // border-color: darken($border-color, 10%); - // $shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten($border-color, 20%); - // @include box-shadow($shadow); - } } // Set validation states also for addons diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index 37ed0bcbc92..dd1afea7394 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -135,7 +135,6 @@ position: absolute; animation: loader 2s cubic-bezier(0.17, 0.67, 0.83, 0.67); animation-iteration-count: 100; - z-index: 2; background: $blue; } diff --git a/public/sass/pages/_history.scss b/public/sass/pages/_history.scss index ea845b78445..42aa58f0bb7 100644 --- a/public/sass/pages/_history.scss +++ b/public/sass/pages/_history.scss @@ -29,10 +29,6 @@ white-space: nowrap; position: relative; - &:before, - &:after { - } - &:after { left: -40px; } diff --git a/public/sass/pages/_login.scss b/public/sass/pages/_login.scss index 4baff47b2a8..091391da7ff 100644 --- a/public/sass/pages/_login.scss +++ b/public/sass/pages/_login.scss @@ -13,9 +13,11 @@ $login-border: #8daac5; background-image: url(../img/heatmap_bg_test.svg); background-size: cover; color: #d8d9da; + & a { color: #d8d9da !important; } + & .btn-primary { @include buttonBackground(#ff6600, #bc3e06); } @@ -198,6 +200,10 @@ select:-webkit-autofill:focus { border: none; font-size: 15px; padding: 10px 10px; + font-weight: bold; + display: inline-block; + width: 170px; + color: $text-color; &.active { background: darken($tight-form-bg, 5%); @@ -207,11 +213,6 @@ select:-webkit-autofill:focus { &:focus { outline: 0; } - - font-weight: bold; - display: inline-block; - width: 170px; - color: $text-color; } .password-strength { @@ -222,10 +223,12 @@ select:-webkit-autofill:focus { padding-top: 3px; color: darken($text-color, 20%); border-top: 3px solid $red; + &.password-strength-ok { width: 40%; border-top: 3px solid lighten($yellow, 10%); } + &.password-strength-good { width: 100%; border-top: 3px solid lighten($green, 10%); @@ -252,6 +255,7 @@ select:-webkit-autofill:focus { .password-recovery { background: $tight-form-bg; padding: 10px; + a { color: $gray-2; } diff --git a/public/sass/pages/_plugins.scss b/public/sass/pages/_plugins.scss index 15149dd0d35..2570eb6c791 100644 --- a/public/sass/pages/_plugins.scss +++ b/public/sass/pages/_plugins.scss @@ -3,9 +3,11 @@ font-size: $font-size-sm; position: relative; top: 1.2rem; + &:hover { color: $link-hover-color; } + img { vertical-align: top; } @@ -18,12 +20,12 @@ } .plugin-info-list-item { - img { - width: 16px; - } - white-space: nowrap; max-width: $page-sidebar-width; text-overflow: ellipsis; overflow: hidden; + + img { + width: 16px; + } } diff --git a/public/sass/utils/_validation.scss b/public/sass/utils/_validation.scss index 657d1f0414b..307afde42d1 100644 --- a/public/sass/utils/_validation.scss +++ b/public/sass/utils/_validation.scss @@ -1,6 +1,3 @@ -input[type='text'].ng-dirty.ng-invalid { -} - input.validation-error, input.ng-dirty.ng-invalid { box-shadow: inset 0 0px 5px $red; diff --git a/scripts/circle-test-frontend.sh b/scripts/circle-test-frontend.sh index 68c044b395c..3af199d3ff2 100755 --- a/scripts/circle-test-frontend.sh +++ b/scripts/circle-test-frontend.sh @@ -11,4 +11,3 @@ function exit_if_fail { } exit_if_fail npm run test -exit_if_fail npm run build diff --git a/scripts/grunt/default_task.js b/scripts/grunt/default_task.js index 7b975aac977..8a71ea26627 100644 --- a/scripts/grunt/default_task.js +++ b/scripts/grunt/default_task.js @@ -10,15 +10,26 @@ module.exports = function (grunt) { grunt.registerTask('test', [ 'sasslint', - 'exec:tslint', + 'tslint', + 'typecheck', "exec:jest", 'no-only-tests' ]); + grunt.registerTask('tslint', [ + 'newer:exec:tslintPackages', + 'newer:exec:tslintRoot', + ]); + + grunt.registerTask('typecheck', [ + 'newer:exec:typecheckPackages', + 'newer:exec:typecheckRoot', + ]); + grunt.registerTask('precommit', [ - 'sasslint', - 'newer:exec:tslint', - 'newer:exec:tsc', + 'newer:sasslint', + 'typecheck', + 'tslint', 'no-only-tests' ]); diff --git a/scripts/grunt/options/exec.js b/scripts/grunt/options/exec.js index 3b60c5c3be6..27bfd7ae43d 100644 --- a/scripts/grunt/options/exec.js +++ b/scripts/grunt/options/exec.js @@ -2,12 +2,20 @@ module.exports = function (config, grunt) { 'use strict'; return { - tslint: { - command: 'node ./node_modules/tslint/lib/tslintCli.js -c tslint.json --project ./tsconfig.json', + tslintPackages: { + command: 'yarn workspaces run tslint', + src: ['packages/**/*.ts*'], + }, + tslintRoot: { + command: 'yarn run tslint', src: ['public/app/**/*.ts*'], }, - tsc: { - command: 'yarn tsc --noEmit', + typecheckPackages: { + command: 'yarn workspaces run typecheck', + src: ['packages/**/*.ts*'], + }, + typecheckRoot: { + command: 'yarn run typecheck', src: ['public/app/**/*.ts*'], }, jest: 'node ./node_modules/jest-cli/bin/jest.js --maxWorkers 2', diff --git a/scripts/grunt/options/sasslint.js b/scripts/grunt/options/sasslint.js index 8ba5ea3047f..e7651b5ece8 100644 --- a/scripts/grunt/options/sasslint.js +++ b/scripts/grunt/options/sasslint.js @@ -4,9 +4,9 @@ module.exports = function(config) { options: { configFile: 'public/sass/.sass-lint.yml', }, - target: [ - 'public/sass/*.scss', - 'public/sass/components/*.scss', - ] + src: [ + 'public/sass/**/*.scss', + 'packages/**/*.scss', + ], }; }; diff --git a/scripts/grunt/options/tslint.js b/scripts/grunt/options/tslint.js deleted file mode 100644 index d51c2062676..00000000000 --- a/scripts/grunt/options/tslint.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = function(config, grunt) { - 'use strict' - // dummy to avoid template compile error - return { - source: { - files: { - src: "" - } - } - }; -}; diff --git a/tsconfig.json b/tsconfig.json index 58b29ba428e..3c8c41f34e2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,6 +26,7 @@ "noUnusedLocals": true, "baseUrl": "public", "pretty": true, + "typeRoots": ["node_modules/@types", "types"], "paths": { "app": ["app"] } diff --git a/yarn.lock b/yarn.lock index 9bc6c7dcd63..78300be233e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -804,6 +804,11 @@ resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.9.tgz#b5990152604c2ada749b7f88cab3476f21f39d7b" integrity sha512-q6LuBI0t5u04f0Q4/R+cGBqIbZMtJkVvCSF+nTfFBBdQqQvJR/mNHeWjRkszyLl7oyf2rDoKUYMEjTw5AV0hiw== +"@types/classnames@^2.2.6": + version "2.2.6" + resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.6.tgz#dbe8a666156d556ed018e15a4c65f08937c3f628" + integrity sha512-XHcYvVdbtAxVstjKxuULYqYaWIzHR15yr1pZj4fnGChuBVJlIAp9StJna0ZJNSgxPh4Nac2FL4JM3M11Tm6fqQ== + "@types/d3-array@*": version "1.2.3" resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-1.2.3.tgz#dd141e3ba311485fffbf0792a1b01a7f2ec12dc1" @@ -1029,6 +1034,16 @@ resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.4.tgz#cc43ae176a91dcb1504839b0b9d6659386cf0af5" integrity sha512-46jSw0QMerCRkhJZbOwPA0Eb9T1p74HtECsfa0GXdgjkenSGhgvK96w+e2PEPu4GF0/brUK5WQKq/rUQQFyAxA== +"@types/jquery@^1.10.35": + version "1.10.35" + resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-1.10.35.tgz#4e5c2b1e5b3bf0b863efb8c5e70081f52e6c9518" + integrity sha512-SVtqEcudm7yjkTwoRA1gC6CNMhGDdMx4Pg8BPdiqI7bXXdCn1BPmtxgeWYQOgDxrq53/5YTlhq5ULxBEAlWIBg== + +"@types/lodash@^4.14.119": + version "4.14.119" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.119.tgz#be847e5f4bc3e35e46d041c394ead8b603ad8b39" + integrity sha512-Z3TNyBL8Vd/M9D9Ms2S3LmFq2sSMzahodD6rCS9V2N44HUMINb75jNkSuwAx7eo2ufqTdfOdtGQpNbieUjPQmw== + "@types/node@*": version "10.11.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.11.4.tgz#e8bd933c3f78795d580ae41d86590bfc1f4f389d" @@ -1397,7 +1412,7 @@ ajv@^4.7.0: co "^4.6.0" json-stable-stringify "^1.0.1" -ajv@^5.1.0, ajv@^5.3.0: +ajv@^5.3.0: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= @@ -1847,7 +1862,7 @@ aws-sign2@~0.7.0: resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= -aws4@^1.6.0, aws4@^1.8.0: +aws4@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== @@ -2878,13 +2893,6 @@ browserify-sign@^4.0.0: inherits "^2.0.1" parse-asn1 "^5.0.0" -browserify-zlib@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d" - integrity sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0= - dependencies: - pako "~0.2.0" - browserify-zlib@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" @@ -3350,7 +3358,7 @@ classnames@2.x, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== -clean-css@3.4.x, clean-css@~3.4.2: +clean-css@3.4.x: version "3.4.28" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.28.tgz#bf1945e82fc808f55695e6ddeaec01400efd03ff" integrity sha1-vxlF6C/ICPVWlebd6uwBQA79A/8= @@ -3655,7 +3663,7 @@ combined-stream@1.0.6: dependencies: delayed-stream "~1.0.0" -combined-stream@~1.0.5, combined-stream@~1.0.6: +combined-stream@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== @@ -3753,7 +3761,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@1.6.2, concat-stream@^1.4.1, concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.5.2: +concat-stream@1.6.2, concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.5.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -5020,14 +5028,6 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" -each-async@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/each-async/-/each-async-1.1.1.tgz#dee5229bdf0ab6ba2012a395e1b869abf8813473" - integrity sha1-3uUim98KtrogEqOV4bhpq/iBNHM= - dependencies: - onetime "^1.0.0" - set-immediate-shim "^1.0.0" - eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -5700,7 +5700,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@~3.0.1, extend@~3.0.2: +extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -5847,7 +5847,7 @@ figgy-pudding@^3.0.0, figgy-pudding@^3.1.0, figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== -figures@^1.0.1, figures@^1.3.5, figures@^1.7.0: +figures@^1.3.5, figures@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4= @@ -6080,7 +6080,7 @@ fork-ts-checker-webpack-plugin@^0.4.9: resolve "^1.5.0" tapable "^1.0.0" -form-data@~2.3.1, form-data@~2.3.2: +form-data@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" integrity sha1-SXBJi+YEwgwAXU9cI67NIda0kJk= @@ -6669,14 +6669,6 @@ grunt-contrib-compress@^1.3.0: optionalDependencies: iltorb "^1.0.13" -grunt-contrib-concat@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/grunt-contrib-concat/-/grunt-contrib-concat-1.0.1.tgz#61509863084e871d7e86de48c015259ed97745bd" - integrity sha1-YVCYYwhOhx1+ht5IwBUlntl3Rb0= - dependencies: - chalk "^1.0.0" - source-map "^0.5.3" - grunt-contrib-copy@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz#7060c6581e904b8ab0d00f076e0a8f6e3e7c3573" @@ -6685,15 +6677,6 @@ grunt-contrib-copy@~1.0.0: chalk "^1.1.1" file-sync-cmp "^0.1.0" -grunt-contrib-cssmin@~1.0.2: - version "1.0.2" - resolved "http://registry.npmjs.org/grunt-contrib-cssmin/-/grunt-contrib-cssmin-1.0.2.tgz#1734cbd3d84ca7364758b7e58ff18e52aa60bb76" - integrity sha1-FzTL09hMpzZHWLflj/GOUqpgu3Y= - dependencies: - chalk "^1.0.0" - clean-css "~3.4.2" - maxmin "^1.1.0" - grunt-exec@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/grunt-exec/-/grunt-exec-1.0.1.tgz#e5d53a39c5f346901305edee5c87db0f2af999c4" @@ -6761,22 +6744,13 @@ grunt-postcss@^0.8.0: diff "^2.0.2" postcss "^5.0.0" -grunt-sass-lint@^0.2.2: +grunt-sass-lint@^0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/grunt-sass-lint/-/grunt-sass-lint-0.2.4.tgz#06f77635ad8a5048968ea33c5584b40a18281e35" integrity sha512-jV88yXoxFFvr4R3WVBl0uz4YBzNxXTrCJ7ZBKrYby/SjRCw2sieKPkt5tpWDcQZIj9XrKsOpKuHQn08MaECVwg== dependencies: sass-lint "^1.12.0" -grunt-sass@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/grunt-sass/-/grunt-sass-2.1.0.tgz#b7ba1d85ef4c2d9b7d8195fe65f664ac7554efa1" - integrity sha512-XkexnQt/9rhReNd+Y7T0n/2g5FqYOQKfi2iSlpwDqvgs7EgEaGTxNhnWzHnbW5oNRvzL9AHopBG3AgRxL0d+DA== - dependencies: - each-async "^1.0.0" - node-sass "^4.7.2" - object-assign "^4.0.1" - grunt-usemin@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/grunt-usemin/-/grunt-usemin-3.1.1.tgz#5ab679510d672cea566cc717abe8b8a009f641c2" @@ -6822,14 +6796,6 @@ gud@^1.0.0: resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== -gzip-size@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-1.0.0.tgz#66cf8b101047227b95bace6ea1da0c177ed5c22f" - integrity sha1-Zs+LEBBHInuVus5uodoMF37Vwi8= - dependencies: - browserify-zlib "^0.1.4" - concat-stream "^1.4.1" - gzip-size@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-4.1.0.tgz#8ae096257eabe7d69c45be2b67c448124ffb517c" @@ -6859,14 +6825,6 @@ har-schema@^2.0.0: resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" - integrity sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0= - dependencies: - ajv "^5.1.0" - har-schema "^2.0.0" - har-validator@~5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29" @@ -9372,16 +9330,6 @@ math-random@^1.0.1: resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" integrity sha1-izqsWIuKZuSXXjzepn97sylgH6w= -maxmin@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/maxmin/-/maxmin-1.1.0.tgz#71365e84a99dd8f8b3f7d5fde2f00d1e7f73be61" - integrity sha1-cTZehKmd2Piz99X94vANHn9zvmE= - dependencies: - chalk "^1.0.0" - figures "^1.0.1" - gzip-size "^1.0.0" - pretty-bytes "^1.0.0" - md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -9456,7 +9404,7 @@ memory-fs@^0.4.0, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" -meow@^3.1.0, meow@^3.3.0, meow@^3.7.0: +meow@^3.3.0, meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= @@ -10082,10 +10030,10 @@ node-releases@^1.0.1: dependencies: semver "^5.3.0" -node-sass@^4.7.2: - version "4.9.3" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.9.3.tgz#f407cf3d66f78308bb1e346b24fa428703196224" - integrity sha512-XzXyGjO+84wxyH7fV6IwBOTrEBe2f0a6SBze9QWWYR/cL74AcQUks2AsqcCZenl/Fp/JVbuEaLpgrLtocwBUww== +node-sass@^4.11.0: + version "4.11.0" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.11.0.tgz#183faec398e9cbe93ba43362e2768ca988a6369a" + integrity sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA== dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -10102,7 +10050,7 @@ node-sass@^4.7.2: nan "^2.10.0" node-gyp "^3.8.0" npmlog "^4.0.0" - request "2.87.0" + request "^2.88.0" sass-graph "^2.2.4" stdout-stream "^1.4.0" "true-case-path" "^1.0.2" @@ -10483,11 +10431,6 @@ nwsapi@^2.0.7: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.9.tgz#77ac0cdfdcad52b6a1151a84e73254edc33ed016" integrity sha512-nlWFSCTYQcHk/6A9FFnfhKc14c3aFhfdNBXgo8Qgi9QTBu/qg3Ww+Uiz9wMzXd1T8GFxPc2QIHB6Qtf2XFryFQ== -oauth-sign@~0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" - integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= - oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -10910,11 +10853,6 @@ pacote@^8.1.6: unique-filename "^1.1.0" which "^1.3.0" -pako@~0.2.0: - version "0.2.9" - resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" - integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU= - pako@~1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" @@ -11659,14 +11597,6 @@ prettier@^1.12.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.3.tgz#90238dd4c0684b7edce5f83b0fb7328e48bd0895" integrity sha512-qZDVnCrnpsRJJq5nSsiHCE3BYMED2OtsI+cmzIzF1QIfqm5ALf8tEJcO27zV1gKNKRPdhjO0dNWnrzssDQ1tFg== -pretty-bytes@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84" - integrity sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ= - dependencies: - get-stdin "^4.0.1" - meow "^3.1.0" - pretty-bytes@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9" @@ -11881,7 +11811,7 @@ qs@6.5.1: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" integrity sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A== -qs@~6.5.1, qs@~6.5.2: +qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== @@ -12685,33 +12615,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.0" tough-cookie ">=2.3.3" -request@2.87.0: - version "2.87.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" - integrity sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.6.0" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" - forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" - performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - tough-cookie "~2.3.3" - tunnel-agent "^0.6.0" - uuid "^3.1.0" - -request@^2.74.0, request@^2.81.0, request@^2.85.0, request@^2.87.0: +request@^2.74.0, request@^2.81.0, request@^2.85.0, request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -12949,14 +12853,14 @@ rx-lite@^3.1.2: resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI= -rxjs@^5.4.2, rxjs@^5.4.3, rxjs@^5.5.2: +rxjs@^5.4.2, rxjs@^5.5.2: version "5.5.12" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc" integrity sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw== dependencies: symbol-observable "1.0.1" -rxjs@^6.1.0: +rxjs@^6.1.0, rxjs@^6.3.3: version "6.3.3" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz#3c6a7fa420e844a81390fb1158a9ec614f4bad55" integrity sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw== @@ -13202,11 +13106,6 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -set-immediate-shim@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" - integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= - set-value@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" @@ -14368,13 +14267,6 @@ tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.4.3: psl "^1.1.24" punycode "^1.4.1" -tough-cookie@~2.3.3: - version "2.3.4" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" - integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA== - dependencies: - punycode "^1.4.1" - tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -14544,6 +14436,11 @@ typescript@^3.0.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.1.tgz#3362ba9dd1e482ebb2355b02dfe8bcd19a2c7c96" integrity sha512-Veu0w4dTc/9wlWNf2jeRInNodKlcdLgemvPsrNpfu5Pq39sgfFjvIIgTsvUHCoLBnMhPoUA+tFxsXjU6VexVRQ== +typescript@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.2.tgz#fe8101c46aa123f8353523ebdcf5730c2ae493e5" + integrity sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg== + ua-parser-js@^0.7.18: version "0.7.19" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b" @@ -14878,7 +14775,7 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^3.0.1, uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2: +uuid@^3.0.1, uuid@^3.2.1, uuid@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==