From e642fce4a5070fb0b4db61ac2e8a13995e1fa91a Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Fri, 12 Oct 2018 16:16:06 +0200 Subject: [PATCH 001/432] created component for http settings --- .../components/InfoPopover/InfoPopover.tsx | 7 + .../datasources/DataSourceHttpSettings.tsx | 223 ++++++++++++++++++ .../datasources/DataSourceSettings.tsx | 58 ++++- .../datasources/EditDataSourcePage.test.tsx | 74 ++++++ .../datasources/EditDataSourcePage.tsx | 10 +- .../EditDataSourcePage.test.tsx.snap | 27 +++ public/app/routes/routes.ts | 5 - 7 files changed, 386 insertions(+), 18 deletions(-) create mode 100644 public/app/core/components/InfoPopover/InfoPopover.tsx create mode 100644 public/app/features/datasources/DataSourceHttpSettings.tsx create mode 100644 public/app/features/datasources/EditDataSourcePage.test.tsx create mode 100644 public/app/features/datasources/__snapshots__/EditDataSourcePage.test.tsx.snap diff --git a/public/app/core/components/InfoPopover/InfoPopover.tsx b/public/app/core/components/InfoPopover/InfoPopover.tsx new file mode 100644 index 00000000000..eb08349aef1 --- /dev/null +++ b/public/app/core/components/InfoPopover/InfoPopover.tsx @@ -0,0 +1,7 @@ +import React, { SFC } from 'react'; +interface Props {} +const InfoPopover: SFC = props => { + return
; +}; + +export default InfoPopover; diff --git a/public/app/features/datasources/DataSourceHttpSettings.tsx b/public/app/features/datasources/DataSourceHttpSettings.tsx new file mode 100644 index 00000000000..1112d048d0f --- /dev/null +++ b/public/app/features/datasources/DataSourceHttpSettings.tsx @@ -0,0 +1,223 @@ +import React, { PureComponent } from 'react'; + +interface Props { + access: any; + basicAuth: any; + showAccessOption: any; + tlsAuth: any; + tlsAuthWithCACert: any; + tlsCACert: any; + tlsClientCert: any; + tlsClientKey: any; + url: any; +} + +interface State { + basicAuthUser: string; + basicAuthPassword: string; + showAccessHelp: boolean; +} + +export default class DataSourceHttpSettings extends PureComponent { + state = { + basicAuthUser: '', + basicAuthPassword: '', + showAccessHelp: false, + }; + + onToggleAccessHelp = () => {}; + + render() { + const { + access, + basicAuth, + showAccessOption, + tlsAuth, + tlsAuthWithCACert, + tlsCACert, + tlsClientCert, + tlsClientKey, + url, + } = this.props; + + const { showAccessHelp, basicAuthUser, basicAuthPassword } = this.state; + + // const accessOptions = [{key: 'proxy', value: 'Server (Default)'}, { key: 'direct', value: 'Browser'}]; + + return ( +
+

HTTP

+
+
+
+ URL + +
+
+ {showAccessOption && ( +
+
+ Access +
+
+
+ +
+
+ )} + + {showAccessHelp && ( +
+

+ Access mode controls how requests to the data source will be handled. + + Server + {' '} + should be the preferred way if nothing else stated. +

+
Server access mode (Default):
+

+ All requests will be made from the browser to Grafana backend/server which in turn will forward the + requests to the data source and by that circumvent possible Cross-Origin Resource Sharing (CORS) + requirements. The URL needs to be accessible from the grafana backend/server if you select this access + mode. +

+
Browser access mode:
+

+ All requests will be made from the browser directly to the data source and may be subject to + Cross-Origin Resource Sharing (CORS) requirements. The URL needs to be accessible from the browser if + you select this access mode. +

+
+ )} + {access === 'proxy' && ( +
+
+ Whitelisted Cookies +
+
+ )} + +

Auth

+
+
+
+
+
+ + {basicAuth && ( +
+
Basic Auth Details
+
+ User + +
+
+ Password + +
+
+ )} + + {(tlsAuth || tlsAuthWithCACert) && + access === 'proxy' && ( +
+
+
TLS Auth Details
+
+ {tlsAuthWithCACert && ( +
+
+
+ +
+ {!tlsCACert && ( +
+ */} +
+
+ )} ); } diff --git a/public/sass/components/_buttons.scss b/public/sass/components/_buttons.scss index dcb4686701d..87947965fc3 100644 --- a/public/sass/components/_buttons.scss +++ b/public/sass/components/_buttons.scss @@ -172,6 +172,12 @@ padding-right: 20px; } +// No horizontal padding +.btn-p-x-0 { + padding-left: 0; + padding-right: 0; +} + // External services // Usage: // From 13d0a117980cdec06dfbe82f4ebc847523857e7b Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 27 Nov 2018 12:13:31 +0100 Subject: [PATCH 230/432] react-panel: Remove json-formatter-js since we will continue with the "patched" version --- package.json | 1 - yarn.lock | 5 ----- 2 files changed, 6 deletions(-) diff --git a/package.json b/package.json index fa9a0dcad2e..452e4ecb742 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,6 @@ "file-saver": "^1.3.3", "immutable": "^3.8.2", "jquery": "^3.2.1", - "json-formatter-js": "^2.2.1", "lodash": "^4.17.10", "moment": "^2.22.2", "mousetrap": "^1.6.0", diff --git a/yarn.lock b/yarn.lock index be55f6fd52e..2cebcae1d9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6811,11 +6811,6 @@ json-buffer@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" -json-formatter-js@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json-formatter-js/-/json-formatter-js-2.2.1.tgz#b101d628e86f028dc9cf9a7e1c83c65e536c9f87" - integrity sha1-sQHWKOhvAo3Jz5p+HIPGXlNsn4c= - json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" From 3908e64ef0319093f758ec7b052ef33979f4e190 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 27 Nov 2018 12:23:11 +0100 Subject: [PATCH 231/432] react-panel: Use correct type for children prop to avoid the use of fragments <> --- .../app/core/components/CopyToClipboard/CopyToClipboard.tsx | 4 ++-- public/app/features/dashboard/dashgrid/QueryInspector.tsx | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/public/app/core/components/CopyToClipboard/CopyToClipboard.tsx b/public/app/core/components/CopyToClipboard/CopyToClipboard.tsx index 153dae2a9c6..ea63de58b47 100644 --- a/public/app/core/components/CopyToClipboard/CopyToClipboard.tsx +++ b/public/app/core/components/CopyToClipboard/CopyToClipboard.tsx @@ -1,4 +1,4 @@ -import React, { PureComponent } from 'react'; +import React, { PureComponent, ReactNode } from 'react'; import ClipboardJS from 'clipboard'; interface Props { @@ -7,7 +7,7 @@ interface Props { onSuccess?: (evt: any) => void; onError?: (evt: any) => void; className?: string; - children?: JSX.Element | string; + children?: ReactNode; } export class CopyToClipboard extends PureComponent { diff --git a/public/app/features/dashboard/dashgrid/QueryInspector.tsx b/public/app/features/dashboard/dashgrid/QueryInspector.tsx index 08da527c035..6fc2669ab33 100644 --- a/public/app/features/dashboard/dashgrid/QueryInspector.tsx +++ b/public/app/features/dashboard/dashgrid/QueryInspector.tsx @@ -211,9 +211,7 @@ export class QueryInspector extends PureComponent { text={this.getTextForClipboard} onSuccess={this.onClipboardSuccess} > - <> - Copy to Clipboard - + Copy to Clipboard
From a03900e6cf795b51b973041620bc2f7ebce82238 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 27 Nov 2018 12:33:30 +0100 Subject: [PATCH 232/432] react-panel: Remove comments and improve readability in render() --- .../dashboard/dashgrid/QueryInspector.tsx | 49 +++++++------------ 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/QueryInspector.tsx b/public/app/features/dashboard/dashgrid/QueryInspector.tsx index 6fc2669ab33..870fb4e7c96 100644 --- a/public/app/features/dashboard/dashgrid/QueryInspector.tsx +++ b/public/app/features/dashboard/dashgrid/QueryInspector.tsx @@ -74,19 +74,12 @@ export class QueryInspector extends PureComponent { }; onDataSourceResponse = (response: any = {}) => { - // ignore if closed - // if (!this.isOpen) { - // return; - // } - if (this.state.isMocking) { this.handleMocking(response); return; } - // this.isLoading = false; - // data = _.cloneDeep(data); - response = { ...response }; // clone + response = { ...response }; // clone - dont modify the response if (response.headers) { delete response.headers; @@ -109,15 +102,6 @@ export class QueryInspector extends PureComponent { if (response.data) { response.response = response.data; - // if (response.status === 200) { - // // if we are in error state, assume we automatically opened - // // and auto close it again - // if (this.hasError) { - // this.hasError = false; - // this.isOpen = false; - // } - // } - delete response.data; delete response.status; delete response.statusText; @@ -175,10 +159,26 @@ export class QueryInspector extends PureComponent { })); }; + renderExpandCollapse = () => { + const { allNodesExpanded } = this.state; + + const collapse = ( + <> + Collapse All + + ); + const expand = ( + <> + Expand All + + ); + return allNodesExpanded ? collapse : expand; + }; + render() { const { response, isLoading } = this.state.dsQuery; const { LoadingPlaceholder } = this.props; - const { allNodesExpanded, isMocking } = this.state; + const { isMocking } = this.state; const openNodes = this.getNrOfOpenNodes(); if (isLoading) { @@ -187,23 +187,12 @@ export class QueryInspector extends PureComponent { return ( <> - {/*
- Mock Response - */}
Date: Tue, 27 Nov 2018 12:36:57 +0100 Subject: [PATCH 233/432] react-panel: Remove mock response button for now --- public/app/features/dashboard/dashgrid/QueryInspector.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/QueryInspector.tsx b/public/app/features/dashboard/dashgrid/QueryInspector.tsx index 870fb4e7c96..ec618404651 100644 --- a/public/app/features/dashboard/dashgrid/QueryInspector.tsx +++ b/public/app/features/dashboard/dashgrid/QueryInspector.tsx @@ -188,9 +188,11 @@ export class QueryInspector extends PureComponent { return ( <>
+ {/* + */} @@ -215,12 +217,6 @@ export class QueryInspector extends PureComponent { onInput={this.setMockedResponse} placeholder="JSON" /> - {/* */}
)} From 69ae3d2e6ac0540809ed061b38ca45f18b9624f9 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Wed, 28 Nov 2018 15:15:31 +0100 Subject: [PATCH 234/432] react-panel: Time range options moved to "Queries" tab --- public/app/core/components/Form/Element.tsx | 43 ++++++++ public/app/core/components/Form/Input.tsx | 96 +++++++++++++++++ public/app/core/components/Form/Label.tsx | 19 ++++ public/app/core/components/Form/index.ts | 3 + public/app/core/utils/rangeutil.ts | 9 ++ .../dashboard/dashgrid/QueriesTab.tsx | 101 +++++++++++++++++- public/app/types/form.ts | 4 + public/app/types/index.ts | 3 +- public/sass/utils/_validation.scss | 6 +- 9 files changed, 280 insertions(+), 4 deletions(-) create mode 100644 public/app/core/components/Form/Element.tsx create mode 100644 public/app/core/components/Form/Input.tsx create mode 100644 public/app/core/components/Form/Label.tsx create mode 100644 public/app/core/components/Form/index.ts create mode 100644 public/app/types/form.ts diff --git a/public/app/core/components/Form/Element.tsx b/public/app/core/components/Form/Element.tsx new file mode 100644 index 00000000000..997d7f0e717 --- /dev/null +++ b/public/app/core/components/Form/Element.tsx @@ -0,0 +1,43 @@ +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/Input.tsx b/public/app/core/components/Form/Input.tsx new file mode 100644 index 00000000000..a261203b3f3 --- /dev/null +++ b/public/app/core/components/Form/Input.tsx @@ -0,0 +1,96 @@ +import React, { PureComponent } from 'react'; +import { ValidationRule } from 'app/types'; + +export enum InputStatus { + Default = 'default', + Loading = 'loading', + Invalid = 'invalid', + Valid = 'valid', +} + +export enum InputTypes { + Text = 'text', + Number = 'number', + Password = 'password', + Email = 'email', +} + +interface Props { + status?: InputStatus; + validationRules: ValidationRule[]; + hideErrorMessage?: boolean; + onBlurWithStatus?: (evt, status: InputStatus) => void; + emptyToNull?: boolean; +} + +const validator = (value: string, validationRules: ValidationRule[]) => { + const errors = validationRules.reduce((acc, currRule) => { + if (!currRule.rule(value)) { + return acc.concat(currRule.errorMessage); + } + return acc; + }, []); + return errors.length > 0 ? errors : null; +}; + +export class Input extends PureComponent> { + state = { + error: null, + }; + + get status() { + const { error } = this.state; + if (error) { + return InputStatus.Invalid; + } + return InputStatus.Valid; + } + + onBlurWithValidation = evt => { + const { validationRules, onBlurWithStatus, onBlur } = this.props; + + let errors = null; + if (validationRules) { + errors = validator(evt.currentTarget.value, validationRules); + this.setState(prevState => { + return { + ...prevState, + error: errors ? errors[0] : null, + }; + }); + } + + if (onBlurWithStatus) { + onBlurWithStatus(evt, errors ? InputStatus.Invalid : InputStatus.Valid); + } + + if (onBlur) { + onBlur(evt); + } + }; + + render() { + const { + status, + validationRules, + onBlurWithStatus, + onBlur, + className, + hideErrorMessage, + emptyToNull, + ...restProps + } = this.props; + + const { error } = this.state; + + let inputClassName = 'gf-form-input'; + inputClassName = this.status === InputStatus.Invalid ? inputClassName + ' invalid' : inputClassName; + + return ( +
+ + {error && !hideErrorMessage && {error}} +
+ ); + } +} diff --git a/public/app/core/components/Form/Label.tsx b/public/app/core/components/Form/Label.tsx new file mode 100644 index 00000000000..385a1b325be --- /dev/null +++ b/public/app/core/components/Form/Label.tsx @@ -0,0 +1,19 @@ +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 new file mode 100644 index 00000000000..e4c8197aaa9 --- /dev/null +++ b/public/app/core/components/Form/index.ts @@ -0,0 +1,3 @@ +export { Element } from './Element'; +export { Input } from './Input'; +export { Label } from './Label'; diff --git a/public/app/core/utils/rangeutil.ts b/public/app/core/utils/rangeutil.ts index 2079aa39006..0150e80f1ed 100644 --- a/public/app/core/utils/rangeutil.ts +++ b/public/app/core/utils/rangeutil.ts @@ -159,3 +159,12 @@ export function describeTimeRange(range: RawTimeRange): string { return range.from.toString() + ' to ' + range.to.toString(); } + +export const isValidTimeSpan = (value: string) => { + if (value.indexOf('$') === 0 || value.indexOf('+$') === 0) { + return true; + } + + const info = describeTextRange(value); + return info.invalid !== true; +}; diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 9a679832048..3c40c8a3568 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -8,6 +8,11 @@ import { DashboardModel } from '../dashboard_model'; import './../../panel/metrics_tab'; import config from 'app/core/config'; import { QueryInspector } from './QueryInspector'; +import { Switch } from 'app/core/components/Switch/Switch'; +import { Input } from 'app/core/components/Form'; +import { InputStatus } from 'app/core/components/Form/Input'; +import { isValidTimeSpan } from 'app/core/utils/rangeutil'; +import { ValidationRule } from 'app/types'; // Services import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; @@ -29,6 +34,7 @@ interface Help { interface State { currentDatasource: DataSourceSelectItem; help: Help; + hideTimeOverride: boolean; } interface LoadingPlaceholderProps { @@ -36,6 +42,17 @@ interface LoadingPlaceholderProps { } const LoadingPlaceholder: SFC = ({ text }) =>

{text}

; +const validationRules: ValidationRule[] = [ + { + rule: value => { + if (!value) { + return true; + } + return isValidTimeSpan(value); + }, + errorMessage: 'Not a valid timespan', + }, +]; export class QueriesTab extends PureComponent { element: any; @@ -53,6 +70,7 @@ export class QueriesTab extends PureComponent { isLoading: false, helpHtml: null, }, + hideTimeOverride: false, }; } @@ -215,9 +233,40 @@ export class QueriesTab extends PureComponent { return isLoading ? : helpHtml; }; + emptyToNull = (value: string) => { + return value === '' ? null : value; + }; + + onOverrideTime = (evt, status: InputStatus) => { + const { value } = evt.target; + const { panel } = this.props; + const emptyToNullValue = this.emptyToNull(value); + if (status === InputStatus.Valid && panel.timeFrom !== emptyToNullValue) { + panel.timeFrom = emptyToNullValue; + panel.refresh(); + } + }; + + onTimeShift = (evt, status: InputStatus) => { + const { value } = evt.target; + const { panel } = this.props; + const emptyToNullValue = this.emptyToNull(value); + if (status === InputStatus.Valid && panel.timeShift !== emptyToNullValue) { + panel.timeShift = emptyToNullValue; + panel.refresh(); + } + }; + + onToggleTimeOverride = () => { + const { panel } = this.props; + panel.hideTimeOverride = !panel.hideTimeOverride; + panel.refresh(); + }; + render() { const { currentDatasource } = this.state; - + const hideTimeOverride = this.props.panel.hideTimeOverride; + console.log('hideTimeOverride', hideTimeOverride); const { hasQueryHelp, queryOptions } = currentDatasource.meta; const hasQueryOptions = !!queryOptions; const dsInformation = { @@ -256,7 +305,55 @@ export class QueriesTab extends PureComponent { return ( -
(this.element = element)} style={{ width: '100%' }} /> + <> +
(this.element = element)} style={{ width: '100%' }} /> + +
Time Range
+ +
+
+ + + + + Override relative time + Last + +
+ +
+ + + + Add time shift + Amount + +
+ +
+
+ + + +
+ +
+
+ ); } diff --git a/public/app/types/form.ts b/public/app/types/form.ts new file mode 100644 index 00000000000..180b41d8730 --- /dev/null +++ b/public/app/types/form.ts @@ -0,0 +1,4 @@ +export interface ValidationRule { + rule: (value: string) => boolean; + errorMessage: string; +} diff --git a/public/app/types/index.ts b/public/app/types/index.ts index bf19e52468b..e60dcb0993d 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -30,7 +30,7 @@ import { AppNotificationTimeout, } from './appNotifications'; import { DashboardSearchHit } from './search'; - +import { ValidationRule } from './form'; export { Team, TeamsState, @@ -89,6 +89,7 @@ export { AppNotificationTimeout, DashboardSearchHit, UserState, + ValidationRule, }; export interface StoreState { diff --git a/public/sass/utils/_validation.scss b/public/sass/utils/_validation.scss index 86b7c008bfd..657d1f0414b 100644 --- a/public/sass/utils/_validation.scss +++ b/public/sass/utils/_validation.scss @@ -1,7 +1,11 @@ -input[type="text"].ng-dirty.ng-invalid { +input[type='text'].ng-dirty.ng-invalid { } input.validation-error, input.ng-dirty.ng-invalid { box-shadow: inset 0 0px 5px $red; } + +input.invalid { + box-shadow: inset 0 0px 5px $red; +} From a8e184c02572ecb06fcc622d490d5bce7440eaa7 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Fri, 30 Nov 2018 10:16:04 +0100 Subject: [PATCH 235/432] react-panel: Clean up input validation and increase code readability --- public/app/core/components/Form/Input.tsx | 94 +++++++++---------- public/app/core/utils/validate.ts | 11 +++ .../dashboard/dashgrid/QueriesTab.tsx | 35 +++---- public/app/types/form.ts | 6 +- public/app/types/index.ts | 3 +- 5 files changed, 79 insertions(+), 70 deletions(-) create mode 100644 public/app/core/utils/validate.ts diff --git a/public/app/core/components/Form/Input.tsx b/public/app/core/components/Form/Input.tsx index a261203b3f3..315acbd645c 100644 --- a/public/app/core/components/Form/Input.tsx +++ b/public/app/core/components/Form/Input.tsx @@ -1,9 +1,8 @@ import React, { PureComponent } from 'react'; -import { ValidationRule } from 'app/types'; +import { ValidationEvents, ValidationRule } from 'app/types'; +import { validate } from 'app/core/utils/validate'; export enum InputStatus { - Default = 'default', - Loading = 'loading', Invalid = 'invalid', Valid = 'valid', } @@ -15,80 +14,71 @@ export enum InputTypes { Email = 'email', } -interface Props { - status?: InputStatus; - validationRules: ValidationRule[]; - hideErrorMessage?: boolean; - onBlurWithStatus?: (evt, status: InputStatus) => void; - emptyToNull?: boolean; +export enum EventsWithValidation { + onBlur = 'onBlur', + onFocus = 'onFocus', + onChange = 'onChange', } -const validator = (value: string, validationRules: ValidationRule[]) => { - const errors = validationRules.reduce((acc, currRule) => { - if (!currRule.rule(value)) { - return acc.concat(currRule.errorMessage); - } - return acc; - }, []); - return errors.length > 0 ? errors : null; -}; +interface Props extends React.HTMLProps { + validationEvents: ValidationEvents; + hideErrorMessage?: boolean; -export class Input extends PureComponent> { + // Override event props and append status as argument + onBlur?: (event: React.FocusEvent, status?: InputStatus) => void; + onFocus?: (event: React.FocusEvent, status?: InputStatus) => void; + onChange?: (event: React.FormEvent, status?: InputStatus) => void; +} + +export class Input extends PureComponent { state = { error: null, }; get status() { - const { error } = this.state; - if (error) { - return InputStatus.Invalid; - } - return InputStatus.Valid; + return this.state.error ? InputStatus.Invalid : InputStatus.Valid; } - onBlurWithValidation = evt => { - const { validationRules, onBlurWithStatus, onBlur } = this.props; + get isInvalid() { + return this.status === InputStatus.Invalid; + } - let errors = null; - if (validationRules) { - errors = validator(evt.currentTarget.value, validationRules); + validatorAsync = (validationRules: ValidationRule[]) => { + return evt => { + const errors = validate(evt.currentTarget.value, validationRules); this.setState(prevState => { return { ...prevState, error: errors ? errors[0] : null, }; }); - } + }; + }; - if (onBlurWithStatus) { - onBlurWithStatus(evt, errors ? InputStatus.Invalid : InputStatus.Valid); - } - - if (onBlur) { - onBlur(evt); - } + populateEventPropsWithStatus = (restProps, validationEvents: ValidationEvents) => { + const inputElementProps = { ...restProps }; + Object.keys(EventsWithValidation).forEach(eventName => { + inputElementProps[eventName] = async evt => { + if (validationEvents[eventName]) { + await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]); + } + if (restProps[eventName]) { + restProps[eventName].apply(null, [evt, this.status]); + } + }; + }); + return inputElementProps; }; render() { - const { - status, - validationRules, - onBlurWithStatus, - onBlur, - className, - hideErrorMessage, - emptyToNull, - ...restProps - } = this.props; - + const { validationEvents, className, hideErrorMessage, ...restProps } = this.props; const { error } = this.state; - - let inputClassName = 'gf-form-input'; - inputClassName = this.status === InputStatus.Invalid ? inputClassName + ' invalid' : inputClassName; + const inputClassName = 'gf-form-input' + (this.isInvalid ? ' invalid' : ''); + const inputElementProps = this.populateEventPropsWithStatus(restProps, validationEvents); return (
- + {error && !hideErrorMessage && {error}}
); diff --git a/public/app/core/utils/validate.ts b/public/app/core/utils/validate.ts new file mode 100644 index 00000000000..34f8125833f --- /dev/null +++ b/public/app/core/utils/validate.ts @@ -0,0 +1,11 @@ +import { ValidationRule } from 'app/types'; + +export const validate = (value: string, validationRules: ValidationRule[]) => { + const errors = validationRules.reduce((acc, currRule) => { + if (!currRule.rule(value)) { + return acc.concat(currRule.errorMessage); + } + return acc; + }, []); + return errors.length > 0 ? errors : null; +}; diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 3c40c8a3568..016299f574e 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -10,9 +10,9 @@ import config from 'app/core/config'; import { QueryInspector } from './QueryInspector'; import { Switch } from 'app/core/components/Switch/Switch'; import { Input } from 'app/core/components/Form'; -import { InputStatus } from 'app/core/components/Form/Input'; +import { InputStatus, EventsWithValidation } from 'app/core/components/Form/Input'; import { isValidTimeSpan } from 'app/core/utils/rangeutil'; -import { ValidationRule } from 'app/types'; +import { ValidationEvents } from 'app/types'; // Services import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; @@ -42,17 +42,20 @@ interface LoadingPlaceholderProps { } const LoadingPlaceholder: SFC = ({ text }) =>

{text}

; -const validationRules: ValidationRule[] = [ - { - rule: value => { - if (!value) { - return true; - } - return isValidTimeSpan(value); + +const timeRangeValidationEvents: ValidationEvents = { + [EventsWithValidation.onBlur]: [ + { + rule: value => { + if (!value) { + return true; + } + return isValidTimeSpan(value); + }, + errorMessage: 'Not a valid timespan', }, - errorMessage: 'Not a valid timespan', - }, -]; + ], +}; export class QueriesTab extends PureComponent { element: any; @@ -322,8 +325,8 @@ export class QueriesTab extends PureComponent { type="text" className="gf-form-input max-width-8" placeholder="1h" - onBlurWithStatus={this.onOverrideTime} - validationRules={validationRules} + onBlur={this.onOverrideTime} + validationEvents={timeRangeValidationEvents} hideErrorMessage={true} />
@@ -338,8 +341,8 @@ export class QueriesTab extends PureComponent { type="text" className="gf-form-input max-width-8" placeholder="1h" - onBlurWithStatus={this.onTimeShift} - validationRules={validationRules} + onBlur={this.onTimeShift} + validationEvents={timeRangeValidationEvents} hideErrorMessage={true} />
diff --git a/public/app/types/form.ts b/public/app/types/form.ts index 180b41d8730..95026c30be9 100644 --- a/public/app/types/form.ts +++ b/public/app/types/form.ts @@ -1,4 +1,8 @@ export interface ValidationRule { - rule: (value: string) => boolean; + rule: (valueToValidate: string) => boolean; errorMessage: string; } + +export interface ValidationEvents { + [eventName: string]: ValidationRule[]; +} diff --git a/public/app/types/index.ts b/public/app/types/index.ts index e60dcb0993d..1a56fad7bf0 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -30,7 +30,7 @@ import { AppNotificationTimeout, } from './appNotifications'; import { DashboardSearchHit } from './search'; -import { ValidationRule } from './form'; +import { ValidationEvents, ValidationRule } from './form'; export { Team, TeamsState, @@ -89,6 +89,7 @@ export { AppNotificationTimeout, DashboardSearchHit, UserState, + ValidationEvents, ValidationRule, }; From c722ea4f768220969867d979d6bf3feb8ea37a63 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Fri, 30 Nov 2018 11:04:56 +0100 Subject: [PATCH 236/432] react-panel: Input validation should be optional --- public/app/core/components/Form/Input.tsx | 24 ++++++++++++----------- public/app/core/utils/validate.ts | 7 ++++++- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/public/app/core/components/Form/Input.tsx b/public/app/core/components/Form/Input.tsx index 315acbd645c..e815336edbe 100644 --- a/public/app/core/components/Form/Input.tsx +++ b/public/app/core/components/Form/Input.tsx @@ -1,6 +1,6 @@ import React, { PureComponent } from 'react'; import { ValidationEvents, ValidationRule } from 'app/types'; -import { validate } from 'app/core/utils/validate'; +import { validate, hasValidationEvent } from 'app/core/utils/validate'; export enum InputStatus { Invalid = 'invalid', @@ -21,7 +21,7 @@ export enum EventsWithValidation { } interface Props extends React.HTMLProps { - validationEvents: ValidationEvents; + validationEvents?: ValidationEvents; hideErrorMessage?: boolean; // Override event props and append status as argument @@ -57,15 +57,17 @@ export class Input extends PureComponent { populateEventPropsWithStatus = (restProps, validationEvents: ValidationEvents) => { const inputElementProps = { ...restProps }; - Object.keys(EventsWithValidation).forEach(eventName => { - inputElementProps[eventName] = async evt => { - if (validationEvents[eventName]) { - await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]); - } - if (restProps[eventName]) { - restProps[eventName].apply(null, [evt, this.status]); - } - }; + Object.keys(EventsWithValidation).forEach((eventName: EventsWithValidation) => { + if (hasValidationEvent(eventName, validationEvents) || restProps[eventName]) { + inputElementProps[eventName] = async evt => { + if (hasValidationEvent(eventName, validationEvents)) { + await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]); + } + if (restProps[eventName]) { + restProps[eventName].apply(null, [evt, this.status]); + } + }; + } }); return inputElementProps; }; diff --git a/public/app/core/utils/validate.ts b/public/app/core/utils/validate.ts index 34f8125833f..c6663882808 100644 --- a/public/app/core/utils/validate.ts +++ b/public/app/core/utils/validate.ts @@ -1,4 +1,5 @@ -import { ValidationRule } from 'app/types'; +import { ValidationRule, ValidationEvents } from 'app/types'; +import { EventsWithValidation } from 'app/core/components/Form/Input'; export const validate = (value: string, validationRules: ValidationRule[]) => { const errors = validationRules.reduce((acc, currRule) => { @@ -9,3 +10,7 @@ export const validate = (value: string, validationRules: ValidationRule[]) => { }, []); return errors.length > 0 ? errors : null; }; + +export const hasValidationEvent = (event: EventsWithValidation, validationEvents: ValidationEvents) => { + return validationEvents && validationEvents[event]; +}; From 58da6e8c267b63e17cde977c20d2423b064cf90d Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Fri, 30 Nov 2018 12:26:45 +0100 Subject: [PATCH 237/432] react-panel: Add test for Input with validation on blur --- .../app/core/components/Form/Input.test.tsx | 53 +++++++++++++++++++ public/app/core/components/Form/Input.tsx | 3 +- .../Form/__snapshots__/Input.test.tsx.snap | 11 ++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 public/app/core/components/Form/Input.test.tsx create mode 100644 public/app/core/components/Form/__snapshots__/Input.test.tsx.snap diff --git a/public/app/core/components/Form/Input.test.tsx b/public/app/core/components/Form/Input.test.tsx new file mode 100644 index 00000000000..9e903208e80 --- /dev/null +++ b/public/app/core/components/Form/Input.test.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; +import { shallow } from 'enzyme'; +import { Input, EventsWithValidation } from './Input'; +import { ValidationEvents } from 'app/types'; + +const TEST_ERROR_MESSAGE = 'Value must be empty or less than 3 chars'; +const testBlurValidation: ValidationEvents = { + [EventsWithValidation.onBlur]: [ + { + rule: (value: string) => { + if (!value || value.length < 3) { + return true; + } + return false; + }, + errorMessage: TEST_ERROR_MESSAGE, + }, + ], +}; + +describe('Input', () => { + it('renders correctly', () => { + const tree = renderer.create().toJSON(); + expect(tree).toMatchSnapshot(); + }); + + it('should validate with error onBlur', () => { + const wrapper = shallow(); + const evt = { + persist: jest.fn, + target: { + value: 'I can not be more than 2 chars', + }, + }; + + wrapper.find('input').simulate('blur', evt); + expect(wrapper.state('error')).toBe(TEST_ERROR_MESSAGE); + }); + + it('should validate without error onBlur', () => { + const wrapper = shallow(); + const evt = { + persist: jest.fn, + target: { + value: 'Hi', + }, + }; + + wrapper.find('input').simulate('blur', evt); + expect(wrapper.state('error')).toBe(null); + }); +}); diff --git a/public/app/core/components/Form/Input.tsx b/public/app/core/components/Form/Input.tsx index e815336edbe..6ba58b45e91 100644 --- a/public/app/core/components/Form/Input.tsx +++ b/public/app/core/components/Form/Input.tsx @@ -45,7 +45,7 @@ export class Input extends PureComponent { validatorAsync = (validationRules: ValidationRule[]) => { return evt => { - const errors = validate(evt.currentTarget.value, validationRules); + const errors = validate(evt.target.value, validationRules); this.setState(prevState => { return { ...prevState, @@ -60,6 +60,7 @@ export class Input extends PureComponent { Object.keys(EventsWithValidation).forEach((eventName: EventsWithValidation) => { if (hasValidationEvent(eventName, validationEvents) || restProps[eventName]) { inputElementProps[eventName] = async evt => { + evt.persist(); // Needed for async. https://reactjs.org/docs/events.html#event-pooling if (hasValidationEvent(eventName, validationEvents)) { await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]); } diff --git a/public/app/core/components/Form/__snapshots__/Input.test.tsx.snap b/public/app/core/components/Form/__snapshots__/Input.test.tsx.snap new file mode 100644 index 00000000000..249d6b0be8d --- /dev/null +++ b/public/app/core/components/Form/__snapshots__/Input.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Input renders correctly 1`] = ` +
+ +
+`; From c40bfb8eda332c985e0fe7e9aece961d5b8d657d Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 30 Nov 2018 12:49:05 +0100 Subject: [PATCH 238/432] make sure target obj is not destructured so that angular copy of objected can be mutated --- public/app/features/explore/Explore.tsx | 7 +------ public/app/features/explore/QueryEditor.tsx | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 1c609897995..26884ec58dc 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -541,12 +541,7 @@ export class Explore extends React.PureComponent { const { datasource, range } = this.state; const { interval, intervalMs } = getIntervals(range, datasource, this.el.offsetWidth); - const configuredQueries = [ - { - ...queryOptions, - ...query, - }, - ]; + const configuredQueries = [Object.assign(query, queryOptions)]; // Clone range for query request // const queryRange: RawTimeRange = { ...range }; diff --git a/public/app/features/explore/QueryEditor.tsx b/public/app/features/explore/QueryEditor.tsx index 0a4c0b78c3c..6fc1106576c 100644 --- a/public/app/features/explore/QueryEditor.tsx +++ b/public/app/features/explore/QueryEditor.tsx @@ -36,7 +36,7 @@ export default class QueryEditor extends PureComponent { target, ctrl: { refresh: () => { - this.props.onQueryChange({ refId: initialQuery.refId, ...target }, false); + this.props.onQueryChange(target, false); this.props.onExecuteQuery(); }, events: exploreEvents, From 4c6897b41179bd69f8915187a3bf73de8f5deb5b Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Fri, 30 Nov 2018 13:17:17 +0100 Subject: [PATCH 239/432] created color enum --- public/app/plugins/panel/gauge/Thresholds.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/panel/gauge/Thresholds.tsx b/public/app/plugins/panel/gauge/Thresholds.tsx index 56f84621770..36f633687c7 100644 --- a/public/app/plugins/panel/gauge/Thresholds.tsx +++ b/public/app/plugins/panel/gauge/Thresholds.tsx @@ -8,13 +8,19 @@ interface State { thresholds: Threshold[]; } +enum BasicGaugeColor { + Green = 'rgba(50, 172, 45, 0.97)', + Orange = 'rgba(237, 129, 40, 0.89)', + Red = 'rgb(212, 74, 58)', +} + export default class Thresholds extends PureComponent, State> { constructor(props) { super(props); this.state = { thresholds: this.props.options.thresholds || [ - { index: 0, label: 'Min', value: 0, canRemove: false, color: 'rgba(50, 172, 45, 0.97)' }, + { index: 0, label: 'Min', value: 0, canRemove: false, color: BasicGaugeColor.Green }, { index: 1, label: 'Max', value: 100, canRemove: false }, ], }; @@ -38,7 +44,7 @@ export default class Thresholds extends PureComponent this.updateGauge() @@ -114,7 +120,7 @@ export default class Thresholds extends PureComponent Date: Fri, 30 Nov 2018 13:36:53 +0100 Subject: [PATCH 240/432] react-panel: Move time range options to its own component and render it under the options button instead --- .../dashboard/dashgrid/QueriesTab.tsx | 107 ++--------------- .../dashboard/dashgrid/TimeRangeOptions.tsx | 111 ++++++++++++++++++ 2 files changed, 120 insertions(+), 98 deletions(-) create mode 100644 public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 016299f574e..8cef54272d0 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -8,11 +8,7 @@ import { DashboardModel } from '../dashboard_model'; import './../../panel/metrics_tab'; import config from 'app/core/config'; import { QueryInspector } from './QueryInspector'; -import { Switch } from 'app/core/components/Switch/Switch'; -import { Input } from 'app/core/components/Form'; -import { InputStatus, EventsWithValidation } from 'app/core/components/Form/Input'; -import { isValidTimeSpan } from 'app/core/utils/rangeutil'; -import { ValidationEvents } from 'app/types'; +import { TimeRangeOptions } from './TimeRangeOptions'; // Services import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; @@ -43,20 +39,6 @@ interface LoadingPlaceholderProps { const LoadingPlaceholder: SFC = ({ text }) =>

{text}

; -const timeRangeValidationEvents: ValidationEvents = { - [EventsWithValidation.onBlur]: [ - { - rule: value => { - if (!value) { - return true; - } - return isValidTimeSpan(value); - }, - errorMessage: 'Not a valid timespan', - }, - ], -}; - export class QueriesTab extends PureComponent { element: any; component: AngularComponent; @@ -220,10 +202,17 @@ export class QueriesTab extends PureComponent { }, }; - return Object.keys(queryOptions).map(key => { + const dsOptions = Object.keys(queryOptions).map(key => { const options = allOptions[key]; return ; }); + + return ( + <> + + {dsOptions} + + ); }; renderQueryInspector = () => { @@ -236,40 +225,8 @@ export class QueriesTab extends PureComponent { return isLoading ? : helpHtml; }; - emptyToNull = (value: string) => { - return value === '' ? null : value; - }; - - onOverrideTime = (evt, status: InputStatus) => { - const { value } = evt.target; - const { panel } = this.props; - const emptyToNullValue = this.emptyToNull(value); - if (status === InputStatus.Valid && panel.timeFrom !== emptyToNullValue) { - panel.timeFrom = emptyToNullValue; - panel.refresh(); - } - }; - - onTimeShift = (evt, status: InputStatus) => { - const { value } = evt.target; - const { panel } = this.props; - const emptyToNullValue = this.emptyToNull(value); - if (status === InputStatus.Valid && panel.timeShift !== emptyToNullValue) { - panel.timeShift = emptyToNullValue; - panel.refresh(); - } - }; - - onToggleTimeOverride = () => { - const { panel } = this.props; - panel.hideTimeOverride = !panel.hideTimeOverride; - panel.refresh(); - }; - render() { const { currentDatasource } = this.state; - const hideTimeOverride = this.props.panel.hideTimeOverride; - console.log('hideTimeOverride', hideTimeOverride); const { hasQueryHelp, queryOptions } = currentDatasource.meta; const hasQueryOptions = !!queryOptions; const dsInformation = { @@ -310,52 +267,6 @@ export class QueriesTab extends PureComponent { <>
(this.element = element)} style={{ width: '100%' }} /> - -
Time Range
- -
-
- - - - - Override relative time - Last - -
- -
- - - - Add time shift - Amount - -
- -
-
- - - -
- -
-
); diff --git a/public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx b/public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx new file mode 100644 index 00000000000..8c6830cf1db --- /dev/null +++ b/public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx @@ -0,0 +1,111 @@ +import React, { PureComponent } from 'react'; +import { Switch } from 'app/core/components/Switch/Switch'; +import { Input } from 'app/core/components/Form'; +import { isValidTimeSpan } from 'app/core/utils/rangeutil'; +import { ValidationEvents } from 'app/types'; +import { EventsWithValidation } from 'app/core/components/Form/Input'; +import { PanelModel } from '../panel_model'; +import { InputStatus } from 'app/core/components/Form/Input'; + +const timeRangeValidationEvents: ValidationEvents = { + [EventsWithValidation.onBlur]: [ + { + rule: value => { + if (!value) { + return true; + } + return isValidTimeSpan(value); + }, + errorMessage: 'Not a valid timespan', + }, + ], +}; + +const emptyToNull = (value: string) => { + return value === '' ? null : value; +}; + +interface Props { + panel: PanelModel; +} + +export class TimeRangeOptions extends PureComponent { + onOverrideTime = (evt, status: InputStatus) => { + const { value } = evt.target; + const { panel } = this.props; + const emptyToNullValue = emptyToNull(value); + if (status === InputStatus.Valid && panel.timeFrom !== emptyToNullValue) { + panel.timeFrom = emptyToNullValue; + panel.refresh(); + } + }; + + onTimeShift = (evt, status: InputStatus) => { + const { value } = evt.target; + const { panel } = this.props; + const emptyToNullValue = emptyToNull(value); + if (status === InputStatus.Valid && panel.timeShift !== emptyToNullValue) { + panel.timeShift = emptyToNullValue; + panel.refresh(); + } + }; + + onToggleTimeOverride = () => { + const { panel } = this.props; + panel.hideTimeOverride = !panel.hideTimeOverride; + panel.refresh(); + }; + + render = () => { + const hideTimeOverride = this.props.panel.hideTimeOverride; + return ( + <> +
Time Range
+ +
+
+ + + + + Override relative time + Last + +
+ +
+ + + + Add time shift + Amount + +
+ +
+
+ + + +
+ +
+
+ + ); + }; +} From 363425d55e1fa6faa8c6f83a290b689c871382cd Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Fri, 30 Nov 2018 13:39:06 +0100 Subject: [PATCH 241/432] react-panel: Options button should always be enabled now when Time Range-options are there --- public/app/features/dashboard/dashgrid/QueriesTab.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 8cef54272d0..7e909b06508 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -227,8 +227,7 @@ export class QueriesTab extends PureComponent { render() { const { currentDatasource } = this.state; - const { hasQueryHelp, queryOptions } = currentDatasource.meta; - const hasQueryOptions = !!queryOptions; + const { hasQueryHelp } = currentDatasource.meta; const dsInformation = { title: currentDatasource.name, imgSrc: currentDatasource.meta.info.logos.small, @@ -259,7 +258,7 @@ export class QueriesTab extends PureComponent { const options = { title: '', icon: 'fa fa-cog', - disabled: !hasQueryOptions, + disabled: false, render: this.renderOptions, }; From 0b65558c21abc9260c6f1617865a8eb3b122291a Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Fri, 30 Nov 2018 13:42:49 +0100 Subject: [PATCH 242/432] react-panel: Add nullcheck to prevent error on datasources without meta options --- public/app/features/dashboard/dashgrid/QueriesTab.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 7e909b06508..f202aca4277 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -202,10 +202,12 @@ export class QueriesTab extends PureComponent { }, }; - const dsOptions = Object.keys(queryOptions).map(key => { - const options = allOptions[key]; - return ; - }); + const dsOptions = queryOptions + ? Object.keys(queryOptions).map(key => { + const options = allOptions[key]; + return ; + }) + : null; return ( <> From 6c09373995330f8da72f13a4a56ffa9448f9964d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 30 Nov 2018 04:45:25 -0800 Subject: [PATCH 243/432] fix: minor style changes, removed hover scale increase --- public/app/features/dashboard/dashgrid/DataSourcePicker.tsx | 4 ---- public/sass/_variables.dark.scss | 2 +- public/sass/components/_panel_editor.scss | 4 ---- public/sass/pages/_dashboard.scss | 1 + 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx b/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx index 4ac128de39d..9a3923a09f2 100644 --- a/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx +++ b/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx @@ -79,10 +79,6 @@ export class DataSourcePicker extends PureComponent { /> -
- - -
); } diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index b57be74e6d7..e12522d0a10 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -79,7 +79,7 @@ $brand-gradient: linear-gradient( ); $page-gradient: linear-gradient(180deg, #222426 10px, rgb(22, 23, 25) 100px); -$edit-gradient: linear-gradient(180deg, rgb(22, 23, 25) 00px, #090909 60%); +$edit-gradient: linear-gradient(180deg, rgb(22, 23, 25) 50%, #090909); // Links // ------------------------- diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 21338f25282..333d41a0f35 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -159,8 +159,6 @@ transition: transform 1 ease; &:hover { - //background: $card-background-hover; - transform: scale(1.05); box-shadow: $panel-editor-viz-item-shadow-hover; background: $panel-editor-viz-item-bg-hover; border: $panel-editor-viz-item-border-hover; @@ -171,7 +169,6 @@ border: 1px solid $orange; &:hover { - transform: scale(1.05); box-shadow: 0 0 6px $orange; border: 1px solid $orange; background: $panel-editor-viz-item-bg-hover-active; @@ -269,7 +266,6 @@ &:hover { background: $card-background-hover; - transform: scaleY(1.1); box-shadow: $panel-editor-viz-item-shadow-hover; background: $panel-editor-viz-item-bg-hover; border: $panel-editor-viz-item-border-hover; diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index 353aee55a09..dbbfe48c828 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -25,6 +25,7 @@ div.flot-text { bottom: 0; right: 0; margin: 0; + .panel-container { border: none; z-index: $zindex-sidemenu + 1; From be85944d846d36758a240ff1b682f595b41dfe0d Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 30 Nov 2018 13:51:49 +0100 Subject: [PATCH 244/432] add an error alert component that will be displayed when there was an error loading ds in explore --- public/app/features/explore/Error.tsx | 21 +++++++++++++++++++++ public/app/features/explore/Explore.tsx | 11 ++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 public/app/features/explore/Error.tsx diff --git a/public/app/features/explore/Error.tsx b/public/app/features/explore/Error.tsx new file mode 100644 index 00000000000..2bfd366b3c9 --- /dev/null +++ b/public/app/features/explore/Error.tsx @@ -0,0 +1,21 @@ +import React, { SFC } from 'react'; + +interface Props { + message: any; +} + +export const Alert: SFC = props => { + const { message } = props; + return ( +
+
+
+ +
+
+
{message}
+
+
+
+ ); +}; diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 26884ec58dc..345ccd472ce 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -40,6 +40,7 @@ import Logs from './Logs'; import Table from './Table'; import ErrorBoundary from './ErrorBoundary'; import TimePicker from './TimePicker'; +import { Alert } from './Error'; interface ExploreProps { datasourceSrv: DatasourceSrv; @@ -851,16 +852,16 @@ export class Explore extends React.PureComponent {
- {datasourceLoading ?
Loading datasource...
: null} - {datasourceMissing ? (
Please add a datasource that supports Explore (e.g., Prometheus).
) : null} - {datasourceError ? ( -
Error connecting to datasource. [{datasourceError}]
- ) : null} + {datasourceError && ( +
+ +
+ )} {datasource && !datasourceError ? (
From 3c556c483302b1bb87bef6caa82634320f17a67f Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 30 Nov 2018 13:53:39 +0100 Subject: [PATCH 245/432] never load fallback query field. remove commented code --- public/app/features/explore/QueryRows.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/public/app/features/explore/QueryRows.tsx b/public/app/features/explore/QueryRows.tsx index 36d1db8f546..45a8c48ca22 100644 --- a/public/app/features/explore/QueryRows.tsx +++ b/public/app/features/explore/QueryRows.tsx @@ -90,9 +90,7 @@ class QueryRow extends PureComponent { const transactionWithError = transactions.find(t => t.error !== undefined); const hint = getFirstHintFromTransactions(transactions); const queryError = transactionWithError ? transactionWithError.error : null; - // const QueryField = datasource.pluginExports.ExploreQueryField || DefaultQueryField; const QueryField = datasource.pluginExports.ExploreQueryField; - // const QueryEditor = datasource.pluginExports.QueryCtrl; return (
From 1db3885283441c9928ddba0bb237bdc7fec78e22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 30 Nov 2018 05:14:23 -0800 Subject: [PATCH 246/432] style changes for panel placeholder (move and resize) effect --- public/sass/_variables.dark.scss | 3 +++ public/sass/_variables.light.scss | 3 +++ public/sass/components/_dashboard_grid.scss | 7 +++++++ public/sass/components/_panel_editor.scss | 3 +-- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index e12522d0a10..53a570fe1fa 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -383,3 +383,6 @@ $panel-editor-viz-item-bg: $black; $panel-editor-tabs-line-color: #e3e3e3; $panel-editor-viz-item-bg-hover: darken($blue, 47%); $panel-editor-viz-item-bg-hover-active: darken($orange, 45%); + +$panel-grid-placeholder-bg: darken($blue, 47%); +$panel-grid-placeholder-shadow: 0 0 4px $blue; diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 16cd96e3fc4..73f5c8c1252 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -393,3 +393,6 @@ $panel-editor-viz-item-bg: $white; $panel-editor-tabs-line-color: $dark-5; $panel-editor-viz-item-bg-hover: lighten($blue, 62%); $panel-editor-viz-item-bg-hover-active: lighten($orange, 34%); + +$panel-grid-placeholder-bg: lighten($blue, 62%); +$panel-grid-placeholder-shadow: 0 0 4px $blue-light; diff --git a/public/sass/components/_dashboard_grid.scss b/public/sass/components/_dashboard_grid.scss index 1370bd96709..95b1448d826 100644 --- a/public/sass/components/_dashboard_grid.scss +++ b/public/sass/components/_dashboard_grid.scss @@ -57,6 +57,13 @@ } } +.react-grid-item.react-grid-placeholder { + box-shadow: $panel-grid-placeholder-shadow; + background: $panel-grid-placeholder-bg; + z-index: 0; + opacity: unset; +} + .theme-dark { .react-grid-item > .react-resizable-handle::after { border-right: 2px solid $gray-1; diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 333d41a0f35..8d5e52fa60b 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -265,10 +265,9 @@ height: 44px; &:hover { - background: $card-background-hover; - box-shadow: $panel-editor-viz-item-shadow-hover; background: $panel-editor-viz-item-bg-hover; border: $panel-editor-viz-item-border-hover; + box-shadow: $panel-editor-viz-item-shadow-hover; } &--selected { From de9137a7c7044d70b355c9cdaaad4ece216ae525 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Fri, 30 Nov 2018 14:32:29 +0100 Subject: [PATCH 247/432] tidy import --- public/app/plugins/panel/gauge/Thresholds.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/panel/gauge/Thresholds.tsx b/public/app/plugins/panel/gauge/Thresholds.tsx index 36f633687c7..57ab4fe352e 100644 --- a/public/app/plugins/panel/gauge/Thresholds.tsx +++ b/public/app/plugins/panel/gauge/Thresholds.tsx @@ -1,8 +1,8 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames/bind'; +import { ColorPicker } from 'app/core/components/colorpicker/ColorPicker'; import { PanelOptionsProps, Threshold } from 'app/types'; import { OptionsProps } from './module'; -import { ColorPicker } from '../../../core/components/colorpicker/ColorPicker'; interface State { thresholds: Threshold[]; From fd8fe35e29cddec8effdf13802d15705d8bc0408 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 3 Dec 2018 09:37:45 +0100 Subject: [PATCH 248/432] redid props for gauge options --- public/app/plugins/panel/gauge/GaugeOptions.tsx | 5 ++--- public/app/plugins/panel/gauge/Thresholds.tsx | 6 +++--- public/app/plugins/panel/gauge/ValueOptions.tsx | 5 ++--- public/app/plugins/panel/gauge/module.tsx | 5 +++++ 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/public/app/plugins/panel/gauge/GaugeOptions.tsx b/public/app/plugins/panel/gauge/GaugeOptions.tsx index 2f0a1b598fb..ec3330ad62e 100644 --- a/public/app/plugins/panel/gauge/GaugeOptions.tsx +++ b/public/app/plugins/panel/gauge/GaugeOptions.tsx @@ -1,9 +1,8 @@ import React, { PureComponent } from 'react'; import { Switch } from 'app/core/components/Switch/Switch'; -import { PanelOptionsProps } from 'app/types'; -import { OptionsProps } from './module'; +import { OptionModuleProps } from './module'; -export default class GaugeOptions extends PureComponent> { +export default class GaugeOptions extends PureComponent { toggleThresholdLabels = () => this.props.onChange({ ...this.props.options, showThresholdLabels: !this.props.options.showThresholdLabels }); diff --git a/public/app/plugins/panel/gauge/Thresholds.tsx b/public/app/plugins/panel/gauge/Thresholds.tsx index 57ab4fe352e..909fe0858a3 100644 --- a/public/app/plugins/panel/gauge/Thresholds.tsx +++ b/public/app/plugins/panel/gauge/Thresholds.tsx @@ -1,8 +1,8 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames/bind'; import { ColorPicker } from 'app/core/components/colorpicker/ColorPicker'; -import { PanelOptionsProps, Threshold } from 'app/types'; -import { OptionsProps } from './module'; +import { OptionModuleProps } from './module'; +import { Threshold } from 'app/types'; interface State { thresholds: Threshold[]; @@ -14,7 +14,7 @@ enum BasicGaugeColor { Red = 'rgb(212, 74, 58)', } -export default class Thresholds extends PureComponent, State> { +export default class Thresholds extends PureComponent { constructor(props) { super(props); diff --git a/public/app/plugins/panel/gauge/ValueOptions.tsx b/public/app/plugins/panel/gauge/ValueOptions.tsx index afd0731652b..e3052f10861 100644 --- a/public/app/plugins/panel/gauge/ValueOptions.tsx +++ b/public/app/plugins/panel/gauge/ValueOptions.tsx @@ -2,8 +2,7 @@ import React, { PureComponent } from 'react'; import { Label } from 'app/core/components/Label/Label'; import SimplePicker from 'app/core/components/Picker/SimplePicker'; import UnitPicker from 'app/core/components/Picker/Unit/UnitPicker'; -import { PanelOptionsProps } from 'app/types'; -import { OptionsProps } from './module'; +import { OptionModuleProps } from './module'; const statOptions = [ { value: 'min', text: 'Min' }, @@ -21,7 +20,7 @@ const statOptions = [ const labelWidth = 6; -export default class ValueOptions extends PureComponent> { +export default class ValueOptions extends PureComponent { onUnitChange = unit => this.props.onChange({ ...this.props.options, unit: unit.value }); onStatChange = stat => this.props.onChange({ ...this.props.options, stat: stat.value }); diff --git a/public/app/plugins/panel/gauge/module.tsx b/public/app/plugins/panel/gauge/module.tsx index c4f054bf960..87e25612f61 100644 --- a/public/app/plugins/panel/gauge/module.tsx +++ b/public/app/plugins/panel/gauge/module.tsx @@ -17,6 +17,11 @@ export interface OptionsProps { thresholds: Threshold[]; } +export interface OptionModuleProps { + onChange: (item: any) => void; + options: OptionsProps; +} + export const defaultProps = { options: { minValue: 0, From c99861a7b3177f1581dfcb24f05080c8034bc98f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 3 Dec 2018 00:46:45 -0800 Subject: [PATCH 249/432] minor css fix --- public/sass/components/_dashboard_grid.scss | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/public/sass/components/_dashboard_grid.scss b/public/sass/components/_dashboard_grid.scss index 95b1448d826..6d3de55e96a 100644 --- a/public/sass/components/_dashboard_grid.scss +++ b/public/sass/components/_dashboard_grid.scss @@ -3,7 +3,13 @@ .panel-in-fullscreen { .react-grid-layout { - height: calc(100% - 20px) !important; + height: 100% !important; + } + + .dashboard-container--has-submenu { + .react-grid-layout { + height: calc(100% - 50px) !important; + } } .react-grid-item { From ab33cfd7c8b6357ea09a67d56f7d2b7d28f0d364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 3 Dec 2018 03:31:55 -0800 Subject: [PATCH 250/432] minor css fixes --- public/app/features/plugins/plugin_component.ts | 2 +- public/sass/components/_panel_editor.scss | 7 +++---- public/sass/pages/_dashboard.scss | 4 ---- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/public/app/features/plugins/plugin_component.ts b/public/app/features/plugins/plugin_component.ts index ff964f6ed4c..7092608085d 100644 --- a/public/app/features/plugins/plugin_component.ts +++ b/public/app/features/plugins/plugin_component.ts @@ -93,7 +93,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ PanelCtrl.templatePromise = getTemplate(PanelCtrl).then(template => { PanelCtrl.templateUrl = null; - PanelCtrl.template = `${template}`; + PanelCtrl.template = `${template}`; return componentInfo; }); diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 8d5e52fa60b..f2286991a9c 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -8,8 +8,7 @@ height: 100%; &--edit { - flex: 1 1 0; - height: unset; + height: 40%; margin: 0 $dashboard-padding; } @@ -25,7 +24,7 @@ margin-top: $panel-margin*2; display: flex; flex-direction: row; - height: 60%; + flex: 1 1 0; position: relative; } @@ -235,8 +234,8 @@ .gicon { height: 44px; width: 53px; - transition: transform 0.1s ease; margin-right: 5px; + transition: transform 0.1s ease 0.1s; &:hover { filter: $panel-editor-side-menu-shadow; diff --git a/public/sass/pages/_dashboard.scss b/public/sass/pages/_dashboard.scss index dbbfe48c828..a005b0386a3 100644 --- a/public/sass/pages/_dashboard.scss +++ b/public/sass/pages/_dashboard.scss @@ -3,10 +3,6 @@ width: 100%; height: 100%; box-sizing: border-box; - - &--has-submenu { - height: calc(100% - 50px); - } } .template-variable { From b4d5ddd6f4b83ea41a635bfb491b8ed0bad6c6e7 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 3 Dec 2018 13:33:17 +0100 Subject: [PATCH 251/432] fix for initial options --- .../dashboard/dashgrid/VisualizationTab.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 1a956b69a33..9e8fcb07548 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -19,14 +19,22 @@ export class VisualizationTab extends PureComponent { super(props); } + getPanelDefaultOptions() { + const { panel, plugin } = this.props; + + if (plugin.exports.PanelDefaults) { + return panel.getOptions(plugin.exports.PanelDefaults.options); + } + + return panel.getOptions(plugin.exports.PanelDefaults); + } + renderPanelOptions() { - const { plugin, panel } = this.props; + const { plugin } = this.props; const { PanelOptions } = plugin.exports; if (PanelOptions) { - return ( - - ); + return ; } else { return

Visualization has no options

; } From 2f78f46afa583abbfb527a3baceb8872fea6990e Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 3 Dec 2018 14:37:42 +0100 Subject: [PATCH 252/432] fix for add/remove labels --- public/app/viz/Gauge.tsx | 5 +++-- public/vendor/flot/jquery.flot.gauge.js | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/public/app/viz/Gauge.tsx b/public/app/viz/Gauge.tsx index 3ef9b599c36..2ccaec4d625 100644 --- a/public/app/viz/Gauge.tsx +++ b/public/app/viz/Gauge.tsx @@ -57,7 +57,8 @@ export class Gauge extends PureComponent { const fontColor = config.bootData.user.lightTheme ? 'rgb(38,38,38)' : 'rgb(230,230,230)'; const fontScale = parseInt('80', 10) / 100; const fontSize = Math.min(dimension / 5, 100) * fontScale; - const gaugeWidth = Math.min(dimension / 6, 60); + const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1; + const gaugeWidth = Math.min(dimension / 6, 60) / gaugeWidthReduceRatio; const thresholdMarkersWidth = gaugeWidth / 5; const thresholdLabelFontSize = fontSize / 2.5; @@ -115,7 +116,7 @@ export class Gauge extends PureComponent { let value: string | number = 'N/A'; if (timeSeries.length) { - value = timeSeries[0].stats.avg; + value = timeSeries[0].stats[stat]; } const plotSeries = { diff --git a/public/vendor/flot/jquery.flot.gauge.js b/public/vendor/flot/jquery.flot.gauge.js index db1cb1ffe24..d256a5db7ed 100644 --- a/public/vendor/flot/jquery.flot.gauge.js +++ b/public/vendor/flot/jquery.flot.gauge.js @@ -588,6 +588,7 @@ if (!exists) { span = $("") span.attr("id", id); + span.attr("class", "flot-temp-elem"); placeholder.append(span); } From b6ef10b8dbc42becf38a4094fd3175b32992b6a1 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 3 Dec 2018 15:02:41 +0100 Subject: [PATCH 253/432] arrow function --- .../app/features/dashboard/dashgrid/VisualizationTab.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 9e8fcb07548..d50386397f9 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -15,11 +15,7 @@ interface Props { } export class VisualizationTab extends PureComponent { - constructor(props) { - super(props); - } - - getPanelDefaultOptions() { + getPanelDefaultOptions = () => { const { panel, plugin } = this.props; if (plugin.exports.PanelDefaults) { @@ -27,7 +23,7 @@ export class VisualizationTab extends PureComponent { } return panel.getOptions(plugin.exports.PanelDefaults); - } + }; renderPanelOptions() { const { plugin } = this.props; From bc5a0d912055a0021a248cf2bb54bcec7a3eaa8c Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 4 Dec 2018 10:42:01 +0100 Subject: [PATCH 254/432] Add VizPicker search #14274 --- .../dashboard/dashgrid/VizTypePicker.tsx | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx index 2dd1b0284b6..fc5e19a9d5c 100644 --- a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx +++ b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx @@ -11,21 +11,22 @@ interface Props { } interface State { - pluginList: PanelPlugin[]; + searchQuery: string; } export class VizTypePicker extends PureComponent { searchInput: HTMLElement; + pluginList = this.getPanelPlugins(''); constructor(props) { super(props); this.state = { - pluginList: this.getPanelPlugins(''), + searchQuery: '', }; } - getPanelPlugins(filter) { + getPanelPlugins(filter): PanelPlugin[] { const panels = _.chain(config.panels) .filter({ hideFromList: false }) .map(item => item) @@ -35,7 +36,7 @@ export class VizTypePicker extends PureComponent { return _.sortBy(panels, 'sort'); } - renderVizPlugin = (plugin, index) => { + renderVizPlugin = (plugin: PanelPlugin, index: number) => { const cssClass = classNames({ 'viz-picker__item': true, 'viz-picker__item--selected': plugin.id === this.props.current.id, @@ -55,7 +56,27 @@ export class VizTypePicker extends PureComponent { }, 300); } - renderFilters() { + getFilteredPluginList = (): PanelPlugin[] => { + const { searchQuery } = this.state; + const regex = new RegExp(searchQuery, 'i'); + const pluginList = this.pluginList; + + const filtered = pluginList.filter(item => { + return regex.test(item.name); + }); + + return filtered; + }; + + onSearchQueryChange = evt => { + const value = evt.target.value; + this.setState(prevState => ({ + ...prevState, + searchQuery: value, + })); + }; + + renderFilters = () => { return ( <> ); - } + }; render() { - const { pluginList } = this.state; + const filteredPluginList = this.getFilteredPluginList(); return ( <> @@ -81,7 +103,7 @@ export class VizTypePicker extends PureComponent {
-
{pluginList.map(this.renderVizPlugin)}
+
{filteredPluginList.map(this.renderVizPlugin)}
); } From 5e1005f66c5360780511fed5794d31cf646459cd Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 4 Dec 2018 10:50:40 +0100 Subject: [PATCH 255/432] minor refactoring --- public/app/plugins/panel/gauge/Thresholds.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/public/app/plugins/panel/gauge/Thresholds.tsx b/public/app/plugins/panel/gauge/Thresholds.tsx index 909fe0858a3..9e9856b137b 100644 --- a/public/app/plugins/panel/gauge/Thresholds.tsx +++ b/public/app/plugins/panel/gauge/Thresholds.tsx @@ -113,7 +113,7 @@ export default class Thresholds extends PureComponent }); }; - getIndicatorColor(index) { + getIndicatorColor = index => { const { thresholds } = this.state; if (index === 0) { @@ -121,7 +121,7 @@ export default class Thresholds extends PureComponent } return index < thresholds.length ? thresholds[index].color : BasicGaugeColor.Red; - } + }; renderNoThresholds() { const { thresholds } = this.state; @@ -277,15 +277,13 @@ export default class Thresholds extends PureComponent renderIndicator() { const { thresholds } = this.state; - const indicators = thresholds.length - 1; + return thresholds.map((t, i) => { + if (i <= thresholds.length - 1) { + return this.renderIndicatorSection(i); + } - const sections = []; - - for (let i = 0; i < indicators; i++) { - sections.push(this.renderIndicatorSection(i)); - } - - return sections; + return null; + }); } render() { From 1ad4b44ec10be6d4da5454af8adf1e723a7eb163 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 4 Dec 2018 14:19:55 +0100 Subject: [PATCH 256/432] POC on how to save away settings from a viztype and restore when switching back to it #14274 --- public/app/features/dashboard/panel_model.ts | 41 ++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/public/app/features/dashboard/panel_model.ts b/public/app/features/dashboard/panel_model.ts index 9643c23dbc1..223226a8f26 100644 --- a/public/app/features/dashboard/panel_model.ts +++ b/public/app/features/dashboard/panel_model.ts @@ -15,6 +15,7 @@ const notPersistedProperties: { [str: string]: boolean } = { fullscreen: true, isEditing: true, hasRefreshed: true, + cachedPluginOptions: true, }; // For angular panels we need to clean up properties when changing type @@ -51,12 +52,14 @@ const mustKeepProps: { [str: string]: boolean } = { events: true, cacheTimeout: true, nullPointMode: true, + cachedPluginOptions: true, }; const defaults: any = { gridPos: { x: 0, y: 0, h: 3, w: 6 }, datasource: null, targets: [{}], + cachedPluginOptions: {}, }; export class PanelModel { @@ -96,6 +99,9 @@ export class PanelModel { events: Emitter; cacheTimeout?: any; + // cache props between plugins + cachedPluginOptions?: any; + constructor(model) { this.events = new Emitter(); @@ -184,7 +190,40 @@ export class PanelModel { this.events.emit('panel-initialized'); } + getPanelOptions() { + return Object.keys(this).reduce((acc, property) => { + if (notPersistedProperties[property]) { + return acc; + } + return { + ...acc, + [property]: this[property], + }; + }, {}); + } + + saveCurrentPanelOptions() { + const currentOptions = this.getPanelOptions(); + this.cachedPluginOptions[this.type] = currentOptions; + } + + restorePanelOptions(pluginId: string) { + const currentOptions = this.getPanelOptions(); + const prevOptions = this.cachedPluginOptions[pluginId]; + const newOptions = Object.keys(prevOptions).reduce((acc, currKey: string) => { + return { + ...acc, + [currKey]: currentOptions[currKey] || prevOptions[currKey], + }; + }, {}); + + Object.keys(newOptions).map(property => { + this[property] = newOptions[property]; + }); + } + changeType(pluginId: string, fromAngularPanel: boolean) { + this.saveCurrentPanelOptions(); this.type = pluginId; // for now we need to remove alert rules when changing type @@ -202,6 +241,8 @@ export class PanelModel { delete this[key]; } } + + this.restorePanelOptions(pluginId); } destroy() { From b8c6577626ad546101ff6f68c30f0794fc6bb359 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 4 Dec 2018 14:39:56 +0100 Subject: [PATCH 257/432] Let the cached props from previous visualization be the masters, unless specified in keepLatestProps const --- public/app/features/dashboard/panel_model.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/panel_model.ts b/public/app/features/dashboard/panel_model.ts index 223226a8f26..9892c33bc4f 100644 --- a/public/app/features/dashboard/panel_model.ts +++ b/public/app/features/dashboard/panel_model.ts @@ -55,6 +55,12 @@ const mustKeepProps: { [str: string]: boolean } = { cachedPluginOptions: true, }; +// Keep current option value when switching visualization +const keepLatestProps: { [str: string]: boolean } = { + title: true, + description: true, +}; + const defaults: any = { gridPos: { x: 0, y: 0, h: 3, w: 6 }, datasource: null, @@ -209,11 +215,11 @@ export class PanelModel { restorePanelOptions(pluginId: string) { const currentOptions = this.getPanelOptions(); - const prevOptions = this.cachedPluginOptions[pluginId]; + const prevOptions = this.cachedPluginOptions[pluginId] || {}; const newOptions = Object.keys(prevOptions).reduce((acc, currKey: string) => { return { ...acc, - [currKey]: currentOptions[currKey] || prevOptions[currKey], + [currKey]: keepLatestProps[currKey] ? currentOptions[currKey] : prevOptions[currKey], }; }, {}); From fe9e4b51f1a4a7d5cbb6040b8c9247e7271e7456 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 4 Dec 2018 15:00:37 +0100 Subject: [PATCH 258/432] Alert tab fails when datasource method targetContainsTemplate doesnt exist #14274 --- public/app/features/alerting/AlertTabCtrl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/alerting/AlertTabCtrl.ts b/public/app/features/alerting/AlertTabCtrl.ts index 5a8abee00d7..6d87c159d02 100644 --- a/public/app/features/alerting/AlertTabCtrl.ts +++ b/public/app/features/alerting/AlertTabCtrl.ts @@ -262,7 +262,7 @@ export class AlertTabCtrl { this.datasourceSrv.get(datasourceName).then(ds => { if (!ds.meta.alerting) { this.error = 'The datasource does not support alerting queries'; - } else if (ds.targetContainsTemplate(foundTarget)) { + } else if (ds.targetContainsTemplate && ds.targetContainsTemplate(foundTarget)) { this.error = 'Template variables are not supported in alert queries'; } else { this.error = ''; From e55659192af43763028739109885d305778cfdcf Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 4 Dec 2018 17:00:00 +0100 Subject: [PATCH 259/432] Small tooltip css-adjustments and add css for position "bottom-start" used by the panel header corner --- .../core/components/Tooltip/withPopper.tsx | 1 - .../PanelHeader/PanelHeaderCorner.tsx | 1 + public/sass/components/_popper.scss | 80 ++++++++++++------- 3 files changed, 50 insertions(+), 32 deletions(-) diff --git a/public/app/core/components/Tooltip/withPopper.tsx b/public/app/core/components/Tooltip/withPopper.tsx index fc4c352d0f5..4ba05937531 100644 --- a/public/app/core/components/Tooltip/withPopper.tsx +++ b/public/app/core/components/Tooltip/withPopper.tsx @@ -60,7 +60,6 @@ export default function withPopper(WrappedComponent) { }; renderContent(content) { - console.log('render content'); if (typeof content === 'function') { // If it's a function we assume it's a React component const ReactComponent = content; diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx index 60471cff19f..e8483ec467a 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx @@ -76,6 +76,7 @@ export class PanelHeaderCorner extends PureComponent { content={this.getInfoContent} className="popper__manager--block" refClassName={`panel-info-corner panel-info-corner--${infoMode.toLowerCase()}`} + placement="bottom-start" > diff --git a/public/sass/components/_popper.scss b/public/sass/components/_popper.scss index 79dc24f5e43..4a3c1cdc1b0 100644 --- a/public/sass/components/_popper.scss +++ b/public/sass/components/_popper.scss @@ -11,33 +11,13 @@ height: 0; border-style: solid; position: absolute; - margin: 5px; + margin: 0px; } .popper .popper__arrow { border-color: $tooltipBackground; } -.popper[data-placement^='top'] { - margin-bottom: 5px; -} - -.popper[data-placement^='top'] .popper__arrow { - border-width: 5px 5px 0 5px; - border-left-color: transparent; - border-right-color: transparent; - border-bottom-color: transparent; - bottom: -5px; - left: calc(50% - 5px); - margin-top: 0; - margin-bottom: 0; - padding-top: 5px; -} - -.popper[data-placement^='bottom'] { - padding-top: 5px; -} - .popper__background { background: $tooltipBackground; border-radius: 3px; @@ -45,16 +25,58 @@ padding: 10px; } +// Top +.popper[data-placement^='top'] { + padding-bottom: 5px; +} +.popper[data-placement^='top'] .popper__arrow { + border-width: 5px 5px 0 5px; + border-left-color: transparent; + border-right-color: transparent; + border-bottom-color: transparent; + bottom: -5px; + left: calc(50% - 5px); + padding-top: 5px; +} + +// Bottom +.popper[data-placement^='bottom'] { + padding-top: 5px; +} .popper[data-placement^='bottom'] .popper__arrow { border-width: 0 5px 5px 5px; border-left-color: transparent; border-right-color: transparent; border-top-color: transparent; top: 0; - left: calc(50% - 8px); - margin-top: 0; - margin-bottom: 0; + left: calc(50% - 5px); } + +.popper[data-placement^='bottom-start'] { + padding-top: 5px; +} +.popper[data-placement^='bottom-start'] .popper__arrow { + border-width: 0 5px 5px 5px; + border-left-color: transparent; + border-right-color: transparent; + border-top-color: transparent; + top: 0; + left: 5px; +} + +.popper[data-placement^='bottom-end'] { + padding-top: 5px; +} +.popper[data-placement^='bottom-end'] .popper__arrow { + border-width: 0 5px 5px 5px; + border-left-color: transparent; + border-right-color: transparent; + border-top-color: transparent; + top: 0; + left: calc(100% - 5px); +} + +// Right .popper[data-placement^='right'] { padding-left: 5px; } @@ -64,15 +86,13 @@ border-top-color: transparent; border-bottom-color: transparent; left: 0; - top: calc(50% - 8px); - margin-left: 0; - margin-right: 0; + top: calc(50% - 5px); } +// Left .popper[data-placement^='left'] { - margin-right: 5px; + padding-right: 5px; } - .popper[data-placement^='left'] .popper__arrow { border-width: 5px 0 5px 5px; border-top-color: transparent; @@ -80,8 +100,6 @@ border-bottom-color: transparent; right: -5px; top: calc(50% - 5px); - margin-left: 0; - margin-right: 0; } .popper__target, From 6d7374d982c170de34fa50c04cf0ab051e917a08 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 4 Dec 2018 17:16:08 +0100 Subject: [PATCH 260/432] Readme: We should write Node.js the same way in all places in the readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5882ea8a6a3..69f45fdb9fd 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ the latest master builds [here](https://grafana.com/grafana/download) ### Dependencies - Go (Latest Stable) -- NodeJS LTS +- Node.js LTS ### Building the backend ```bash @@ -37,7 +37,7 @@ go run build.go build ### Building frontend assets -For this you need nodejs (v.6+). +For this you need Node.js (version 6+). To build the assets, rebuild on file change, and serve them by Grafana's webserver (http://localhost:3000): ```bash From 4872cef808bf0149e42fab6efbe8b6a1a36ad890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 5 Dec 2018 07:33:21 +0100 Subject: [PATCH 261/432] refactored and added tests for panel model remember properties --- public/app/features/dashboard/panel_model.ts | 35 ++++-------- .../dashboard/specs/panel_model.test.ts | 54 +++++++++++++++++++ 2 files changed, 63 insertions(+), 26 deletions(-) create mode 100644 public/app/features/dashboard/specs/panel_model.test.ts diff --git a/public/app/features/dashboard/panel_model.ts b/public/app/features/dashboard/panel_model.ts index 9892c33bc4f..ab4ff02def4 100644 --- a/public/app/features/dashboard/panel_model.ts +++ b/public/app/features/dashboard/panel_model.ts @@ -55,12 +55,6 @@ const mustKeepProps: { [str: string]: boolean } = { cachedPluginOptions: true, }; -// Keep current option value when switching visualization -const keepLatestProps: { [str: string]: boolean } = { - title: true, - description: true, -}; - const defaults: any = { gridPos: { x: 0, y: 0, h: 3, w: 6 }, datasource: null, @@ -132,7 +126,7 @@ export class PanelModel { } private getOptionsKey() { - return 'options-' + this.type; + return PANEL_OPTIONS_KEY_PREFIX + this.type; } getSaveModel() { @@ -196,9 +190,9 @@ export class PanelModel { this.events.emit('panel-initialized'); } - getPanelOptions() { + private getOptionsToRemember() { return Object.keys(this).reduce((acc, property) => { - if (notPersistedProperties[property]) { + if (notPersistedProperties[property] || mustKeepProps[property]) { return acc; } return { @@ -208,23 +202,15 @@ export class PanelModel { }, {}); } - saveCurrentPanelOptions() { - const currentOptions = this.getPanelOptions(); - this.cachedPluginOptions[this.type] = currentOptions; + private saveCurrentPanelOptions() { + this.cachedPluginOptions[this.type] = this.getOptionsToRemember(); } - restorePanelOptions(pluginId: string) { - const currentOptions = this.getPanelOptions(); + private restorePanelOptions(pluginId: string) { const prevOptions = this.cachedPluginOptions[pluginId] || {}; - const newOptions = Object.keys(prevOptions).reduce((acc, currKey: string) => { - return { - ...acc, - [currKey]: keepLatestProps[currKey] ? currentOptions[currKey] : prevOptions[currKey], - }; - }, {}); - Object.keys(newOptions).map(property => { - this[property] = newOptions[property]; + Object.keys(prevOptions).map(property => { + this[property] = prevOptions[property]; }); } @@ -232,15 +218,12 @@ export class PanelModel { this.saveCurrentPanelOptions(); this.type = pluginId; - // for now we need to remove alert rules when changing type - delete this.alert; - // for angular panels only we need to remove all events and let angular panels do some cleanup if (fromAngularPanel) { this.destroy(); for (const key of _.keys(this)) { - if (mustKeepProps[key] || key.indexOf(PANEL_OPTIONS_KEY_PREFIX) === 0) { + if (mustKeepProps[key]) { continue; } diff --git a/public/app/features/dashboard/specs/panel_model.test.ts b/public/app/features/dashboard/specs/panel_model.test.ts new file mode 100644 index 00000000000..36bb8d6297e --- /dev/null +++ b/public/app/features/dashboard/specs/panel_model.test.ts @@ -0,0 +1,54 @@ +import _ from 'lodash'; +import { PanelModel } from '../panel_model'; + +describe('PanelModel', () => { + describe('when creating new panel model', () => { + let model; + + beforeEach(() => { + model = new PanelModel({ + type: 'table', + showColumns: true, + }); + }); + + it('should apply defaults', () => { + expect(model.gridPos.h).toBe(3); + }); + + it('should set model props on instance', () => { + expect(model.showColumns).toBe(true); + }); + + it('getSaveModel should remove defaults', () => { + const saveModel = model.getSaveModel(); + expect(saveModel.gridPos).toBe(undefined); + }); + + it('getSaveModel should remove nonPersistedProperties', () => { + const saveModel = model.getSaveModel(); + expect(saveModel.events).toBe(undefined); + }); + + describe('when changing panel type', () => { + beforeEach(() => { + model.changeType('graph', true); + model.alert = { id: 2 }; + }); + + it('should remove table properties but keep core props', () => { + expect(model.showColumns).toBe(undefined); + }); + + it('should restore table properties when changing back', () => { + model.changeType('table', true); + expect(model.showColumns).toBe(true); + }); + + it('should remove alert rule when changing type that does not support it', () => { + model.changeType('table', true); + expect(model.alert).toBe(undefined); + }); + }); + }); +}); From 735c28580d4c36bcdf9c630f2814c856f2b0380d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 5 Dec 2018 09:26:01 +0100 Subject: [PATCH 262/432] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 69f45fdb9fd..3961e86e3ac 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ go run build.go build ### Building frontend assets -For this you need Node.js (version 6+). +For this you need Node.js (LTS version). To build the assets, rebuild on file change, and serve them by Grafana's webserver (http://localhost:3000): ```bash From 0a8f23d8a6ae74701e04e81ab4d3f6a0d0b2599b Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Wed, 5 Dec 2018 09:39:03 +0100 Subject: [PATCH 263/432] Update css to use the border-radius variable and add a new variable for the popper's distance to its ref --- public/sass/components/_popper.scss | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/public/sass/components/_popper.scss b/public/sass/components/_popper.scss index 4a3c1cdc1b0..d869d52b92f 100644 --- a/public/sass/components/_popper.scss +++ b/public/sass/components/_popper.scss @@ -1,3 +1,5 @@ +$popper-margin-from-ref: 5px; + .popper { position: absolute; z-index: $zindex-tooltip; @@ -20,14 +22,14 @@ .popper__background { background: $tooltipBackground; - border-radius: 3px; + border-radius: $border-radius; box-shadow: 0 0 2px rgba(0, 0, 0, 0.5); padding: 10px; } // Top .popper[data-placement^='top'] { - padding-bottom: 5px; + padding-bottom: $popper-margin-from-ref; } .popper[data-placement^='top'] .popper__arrow { border-width: 5px 5px 0 5px; @@ -41,7 +43,7 @@ // Bottom .popper[data-placement^='bottom'] { - padding-top: 5px; + padding-top: $popper-margin-from-ref; } .popper[data-placement^='bottom'] .popper__arrow { border-width: 0 5px 5px 5px; @@ -53,7 +55,7 @@ } .popper[data-placement^='bottom-start'] { - padding-top: 5px; + padding-top: $popper-margin-from-ref; } .popper[data-placement^='bottom-start'] .popper__arrow { border-width: 0 5px 5px 5px; @@ -65,7 +67,7 @@ } .popper[data-placement^='bottom-end'] { - padding-top: 5px; + padding-top: $popper-margin-from-ref; } .popper[data-placement^='bottom-end'] .popper__arrow { border-width: 0 5px 5px 5px; @@ -78,7 +80,7 @@ // Right .popper[data-placement^='right'] { - padding-left: 5px; + padding-left: $popper-margin-from-ref; } .popper[data-placement^='right'] .popper__arrow { border-width: 5px 5px 5px 0; @@ -91,7 +93,7 @@ // Left .popper[data-placement^='left'] { - padding-right: 5px; + padding-right: $popper-margin-from-ref; } .popper[data-placement^='left'] .popper__arrow { border-width: 5px 0 5px 5px; From 5f8001689bdc9302608d0e6116622591607882d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 5 Dec 2018 10:22:26 +0100 Subject: [PATCH 264/432] explore data source selector fix --- public/app/core/components/Picker/SimplePicker.tsx | 1 + public/app/features/explore/Explore.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/public/app/core/components/Picker/SimplePicker.tsx b/public/app/core/components/Picker/SimplePicker.tsx index 817cd14150f..2ca8be43c75 100644 --- a/public/app/core/components/Picker/SimplePicker.tsx +++ b/public/app/core/components/Picker/SimplePicker.tsx @@ -37,6 +37,7 @@ const SimplePicker: SFC = ({ value={value} getOptionLabel={getOptionLabel} getOptionValue={getOptionValue} + menuShouldScrollIntoView={false} isSearchable={false} onChange={onSelected} options={options} diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index e18ddb25d79..386e8d46597 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -895,6 +895,7 @@ export class Explore extends React.PureComponent { diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index 3a584cd5664..6b0ae065eba 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -301,32 +301,28 @@ export default class Logs extends PureComponent {
- - - + + + this.onChangeDedup(LogsDedupStrategy.none)} - small /> this.onChangeDedup(LogsDedupStrategy.exact)} - small /> this.onChangeDedup(LogsDedupStrategy.numbers)} - small /> this.onChangeDedup(LogsDedupStrategy.signature)} - small /> {hasData && meta && ( From 72b7497d66a9b96394a29ea88e71e4c8ba57337f Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 5 Dec 2018 11:47:02 +0100 Subject: [PATCH 269/432] Added isDefault switch on settings --- .../settings/BasicSettings.test.tsx | 4 +- .../datasources/settings/BasicSettings.tsx | 44 ++++++---- .../settings/DataSourceSettings.test.tsx | 1 + .../settings/DataSourceSettings.tsx | 12 ++- .../app/features/datasources/state/actions.ts | 14 ++- .../features/datasources/state/reducers.ts | 3 + yarn.lock | 87 ++----------------- 7 files changed, 59 insertions(+), 106 deletions(-) diff --git a/public/app/features/datasources/settings/BasicSettings.test.tsx b/public/app/features/datasources/settings/BasicSettings.test.tsx index 6729830bb75..0194adc9007 100644 --- a/public/app/features/datasources/settings/BasicSettings.test.tsx +++ b/public/app/features/datasources/settings/BasicSettings.test.tsx @@ -5,7 +5,9 @@ import BasicSettings, { Props } from './BasicSettings'; const setup = () => { const props: Props = { dataSourceName: 'Graphite', - onChange: jest.fn(), + isDefault: false, + onDefaultChange: jest.fn(), + onNameChange: jest.fn(), }; return shallow(); diff --git a/public/app/features/datasources/settings/BasicSettings.tsx b/public/app/features/datasources/settings/BasicSettings.tsx index adaec898dbc..569e0909c4d 100644 --- a/public/app/features/datasources/settings/BasicSettings.tsx +++ b/public/app/features/datasources/settings/BasicSettings.tsx @@ -1,31 +1,37 @@ import React, { SFC } from 'react'; import { Label } from 'app/core/components/Label/Label'; +import { Switch } from '../../../core/components/Switch/Switch'; export interface Props { dataSourceName: string; - onChange: (name: string) => void; + isDefault: boolean; + onNameChange: (name: string) => void; + onDefaultChange: (value: boolean) => void; } -const BasicSettings: SFC = ({ dataSourceName, onChange }) => { +const BasicSettings: SFC = ({ dataSourceName, isDefault, onDefaultChange, onNameChange }) => { return (
-
- - onChange(event.target.value)} - required - /> +
+
+ + onNameChange(event.target.value)} + required + /> +
+ onDefaultChange(event.target.checked)} />
); diff --git a/public/app/features/datasources/settings/DataSourceSettings.test.tsx b/public/app/features/datasources/settings/DataSourceSettings.test.tsx index 73c05fdd518..d6c934aa6a4 100644 --- a/public/app/features/datasources/settings/DataSourceSettings.test.tsx +++ b/public/app/features/datasources/settings/DataSourceSettings.test.tsx @@ -15,6 +15,7 @@ const setup = (propOverrides?: object) => { loadDataSource: jest.fn(), setDataSourceName: jest.fn(), updateDataSource: jest.fn(), + setIsDefault: jest.fn(), }; Object.assign(props, propOverrides); diff --git a/public/app/features/datasources/settings/DataSourceSettings.tsx b/public/app/features/datasources/settings/DataSourceSettings.tsx index 0f07023b472..977d37e35ec 100644 --- a/public/app/features/datasources/settings/DataSourceSettings.tsx +++ b/public/app/features/datasources/settings/DataSourceSettings.tsx @@ -13,7 +13,7 @@ import { getBackendSrv } from 'app/core/services/backend_srv'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getDataSource, getDataSourceMeta } from '../state/selectors'; -import { deleteDataSource, loadDataSource, setDataSourceName, updateDataSource } from '../state/actions'; +import { deleteDataSource, loadDataSource, setDataSourceName, setIsDefault, updateDataSource } from '../state/actions'; import { getNavModel } from 'app/core/selectors/navModel'; import { getRouteParamsId } from 'app/core/selectors/location'; @@ -29,6 +29,7 @@ export interface Props { loadDataSource: typeof loadDataSource; setDataSourceName: typeof setDataSourceName; updateDataSource: typeof updateDataSource; + setIsDefault: typeof setIsDefault; } interface State { @@ -164,7 +165,7 @@ export class DataSourceSettings extends PureComponent { } render() { - const { dataSource, dataSourceMeta, navModel } = this.props; + const { dataSource, dataSourceMeta, navModel, setDataSourceName, setIsDefault } = this.props; const { testingMessage, testingStatus } = this.state; return ( @@ -177,8 +178,10 @@ export class DataSourceSettings extends PureComponent {
this.props.setDataSourceName(name)} + dataSourceName={dataSource.name} + isDefault={dataSource.isDefault} + onDefaultChange={state => setIsDefault(state)} + onNameChange={name => setDataSourceName(name)} /> {this.shouldRenderInfoBox() &&
{this.getInfoText()}
} @@ -240,6 +243,7 @@ const mapDispatchToProps = { loadDataSource, setDataSourceName, updateDataSource, + setIsDefault, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DataSourceSettings)); diff --git a/public/app/features/datasources/state/actions.ts b/public/app/features/datasources/state/actions.ts index c03860e1f01..2c6afed42f6 100644 --- a/public/app/features/datasources/state/actions.ts +++ b/public/app/features/datasources/state/actions.ts @@ -17,6 +17,7 @@ export enum ActionTypes { SetDataSourcesLayoutMode = 'SET_DATA_SOURCES_LAYOUT_MODE', SetDataSourceTypeSearchQuery = 'SET_DATA_SOURCE_TYPE_SEARCH_QUERY', SetDataSourceName = 'SET_DATA_SOURCE_NAME', + SetIsDefault = 'SET_IS_DEFAULT', } interface LoadDataSourcesAction { @@ -59,6 +60,11 @@ interface SetDataSourceNameAction { payload: string; } +interface SetIsDefaultAction { + type: ActionTypes.SetIsDefault; + payload: boolean; +} + const dataSourcesLoaded = (dataSources: DataSource[]): LoadDataSourcesAction => ({ type: ActionTypes.LoadDataSources, payload: dataSources, @@ -99,6 +105,11 @@ export const setDataSourceName = (name: string) => ({ payload: name, }); +export const setIsDefault = (state: boolean) => ({ + type: ActionTypes.SetIsDefault, + payload: state, +}); + export type Action = | LoadDataSourcesAction | SetDataSourcesSearchQueryAction @@ -109,7 +120,8 @@ export type Action = | LoadDataSourceAction | UpdateNavIndexAction | LoadDataSourceMetaAction - | SetDataSourceNameAction; + | SetDataSourceNameAction + | SetIsDefaultAction; type ThunkResult = ThunkAction; diff --git a/public/app/features/datasources/state/reducers.ts b/public/app/features/datasources/state/reducers.ts index 33feae6770a..e8625aac0d2 100644 --- a/public/app/features/datasources/state/reducers.ts +++ b/public/app/features/datasources/state/reducers.ts @@ -39,6 +39,9 @@ export const dataSourcesReducer = (state = initialState, action: Action): DataSo case ActionTypes.SetDataSourceName: return { ...state, dataSource: { ...state.dataSource, name: action.payload } }; + + case ActionTypes.SetIsDefault: + return { ...state, dataSource: { ...state.dataSource, isDefault: action.payload } }; } return state; diff --git a/yarn.lock b/yarn.lock index ed649a1b365..a0ef0a342d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -218,15 +218,6 @@ esutils "^2.0.2" js-tokens "^4.0.0" -<<<<<<< HEAD -"@babel/runtime@^7.1.2": - version "7.1.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.1.5.tgz#4170907641cf1f61508f563ece3725150cc6fe39" - dependencies: - regenerator-runtime "^0.12.0" - -||||||| merged common ancestors -======= "@babel/parser@^7.1.2", "@babel/parser@^7.1.3": version "7.1.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.3.tgz#2c92469bac2b7fbff810b67fca07bd138b48af77" @@ -659,6 +650,12 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-transform-typescript" "^7.1.0" +"@babel/runtime@^7.1.2": + version "7.1.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.1.5.tgz#4170907641cf1f61508f563ece3725150cc6fe39" + dependencies: + regenerator-runtime "^0.12.0" + "@babel/template@^7.1.0", "@babel/template@^7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644" @@ -683,7 +680,6 @@ globals "^11.1.0" lodash "^4.17.10" ->>>>>>> master "@babel/types@^7.0.0": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.2.tgz#183e7952cf6691628afdc2e2b90d03240bac80c0" @@ -1845,21 +1841,10 @@ babel-jest@^23.6.0: babel-plugin-istanbul "^4.1.6" babel-preset-jest "^23.2.0" -<<<<<<< HEAD -babel-loader@^7.1.4: - version "7.1.5" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.5.tgz#e3ee0cd7394aa557e013b02d3e492bfd07aa6d68" -||||||| merged common ancestors -babel-loader@^7.1.4: - version "7.1.5" - resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.5.tgz#e3ee0cd7394aa557e013b02d3e492bfd07aa6d68" - integrity sha512-iCHfbieL5d1LfOQeeVJEUyD9rTwBcP/fcEbRCfempxTDuqrKpu0AZjLAQHEQa3Yqyj9ORKe2iHfoj4rHLf7xpw== -======= babel-loader@^8.0.4: version "8.0.4" resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.4.tgz#7bbf20cbe4560629e2e41534147692d3fecbdce6" integrity sha512-fhBhNkUToJcW9nV46v8w87AJOwAJDz84c1CL57n3Stj73FANM/b9TbCUK4YhdOwEyZ+OxhYpdeZDNzSI29Firw== ->>>>>>> master dependencies: find-cache-dir "^1.0.0" loader-utils "^1.0.2" @@ -2225,30 +2210,7 @@ babel-plugin-transform-strict-mode@^6.24.1: babel-runtime "^6.22.0" babel-types "^6.24.1" -<<<<<<< HEAD -babel-polyfill@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153" - dependencies: - babel-runtime "^6.26.0" - core-js "^2.5.0" - regenerator-runtime "^0.10.5" - -babel-preset-es2015@^6.24.1, babel-preset-es2015@^6.9.0: -||||||| merged common ancestors -babel-polyfill@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153" - integrity sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM= - dependencies: - babel-runtime "^6.26.0" - core-js "^2.5.0" - regenerator-runtime "^0.10.5" - -babel-preset-es2015@^6.24.1, babel-preset-es2015@^6.9.0: -======= babel-preset-es2015@^6.9.0: ->>>>>>> master version "6.24.1" resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939" dependencies: @@ -6184,21 +6146,10 @@ header-case@^1.0.0: no-case "^2.2.0" upper-case "^1.1.3" -<<<<<<< HEAD -highlight-words-core@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/highlight-words-core/-/highlight-words-core-1.2.0.tgz#232bec301cbf2a4943d335dc748ce70e9024f3b1" -||||||| merged common ancestors -highlight-words-core@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/highlight-words-core/-/highlight-words-core-1.2.0.tgz#232bec301cbf2a4943d335dc748ce70e9024f3b1" - integrity sha512-nu5bMsWIgpsrlXEMNKSvbJMeUPhFxCOVT28DnI8UCVfhm3e98LC8oeyMNrc7E18+QQ4l/PvbeN7ojyN4XsmBdA== -======= highlight-words-core@^1.2.0: version "1.2.2" resolved "https://registry.yarnpkg.com/highlight-words-core/-/highlight-words-core-1.2.2.tgz#1eff6d7d9f0a22f155042a00791237791b1eeaaa" integrity sha512-BXUKIkUuh6cmmxzi5OIbUJxrG8OAk2MqoL1DtO3Wo9D2faJg2ph5ntyuQeLqaHJmzER6H5tllCDA9ZnNe9BVGg== ->>>>>>> master hmac-drbg@^1.0.0: version "1.0.1" @@ -10639,21 +10590,10 @@ react-grid-layout@0.16.6: react-draggable "3.x" react-resizable "1.x" -<<<<<<< HEAD -react-highlight-words@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/react-highlight-words/-/react-highlight-words-0.10.0.tgz#2e905c76c11635237f848ecad00600f1b6f6f4a8" -||||||| merged common ancestors -react-highlight-words@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/react-highlight-words/-/react-highlight-words-0.10.0.tgz#2e905c76c11635237f848ecad00600f1b6f6f4a8" - integrity sha512-/5jh6a8pir3baCOMC5j88MBmNciSwG5bXWNAAtbtDb3WYJoGn82e2zLCQFnghIBWod1h5y6/LRO8TS6ERbN5aQ== -======= react-highlight-words@0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/react-highlight-words/-/react-highlight-words-0.11.0.tgz#4f3c2039a8fd275f3ab795e59946b0324d8e6bee" integrity sha512-b+fgdQXNjX6RwHfiBYn6qH2D2mJEDNLuxdsqRseIiQffoCAoj7naMQ5EktUkmo9Bh1mXq/aMpJbdx7Lf2PytcQ== ->>>>>>> master dependencies: highlight-words-core "^1.2.0" prop-types "^15.5.8" @@ -11020,22 +10960,7 @@ regenerate@^1.2.1, regenerate@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" -<<<<<<< HEAD -regenerator-runtime@^0.10.5: - version "0.10.5" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" - -regenerator-runtime@^0.11.0: -||||||| merged common ancestors -regenerator-runtime@^0.10.5: - version "0.10.5" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" - integrity sha1-M2w+/BIgrc7dosn6tntaeVWjNlg= - -regenerator-runtime@^0.11.0: -======= regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1: ->>>>>>> master version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" From 690e701f6d06b354778fc5261f7a91fbd304a0ea Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 5 Dec 2018 12:50:14 +0100 Subject: [PATCH 270/432] fix snapshots --- .../__snapshots__/BasicSettings.test.tsx.snap | 36 +++++++++++++------ .../DataSourceSettings.test.tsx.snap | 16 ++++++--- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/public/app/features/datasources/settings/__snapshots__/BasicSettings.test.tsx.snap b/public/app/features/datasources/settings/__snapshots__/BasicSettings.test.tsx.snap index 0c3e8c86dd4..65d4b5ab953 100644 --- a/public/app/features/datasources/settings/__snapshots__/BasicSettings.test.tsx.snap +++ b/public/app/features/datasources/settings/__snapshots__/BasicSettings.test.tsx.snap @@ -5,20 +5,34 @@ exports[`Render should render component 1`] = ` className="gf-form-group" >
- - Name - - + Name + + +
+
diff --git a/public/app/features/datasources/settings/__snapshots__/DataSourceSettings.test.tsx.snap b/public/app/features/datasources/settings/__snapshots__/DataSourceSettings.test.tsx.snap index f95055c9095..dafff91a6f5 100644 --- a/public/app/features/datasources/settings/__snapshots__/DataSourceSettings.test.tsx.snap +++ b/public/app/features/datasources/settings/__snapshots__/DataSourceSettings.test.tsx.snap @@ -14,7 +14,9 @@ exports[`Render should render alpha info text 1`] = ` >
Date: Wed, 5 Dec 2018 14:56:29 +0100 Subject: [PATCH 271/432] Trigger panel.render on title, description, links change #14333 --- public/app/features/panel/GeneralTabCtrl.ts | 24 +++++++++++++++++++ .../features/panel/partials/general_tab.html | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/public/app/features/panel/GeneralTabCtrl.ts b/public/app/features/panel/GeneralTabCtrl.ts index c692106c92d..59d2d0e9cda 100644 --- a/public/app/features/panel/GeneralTabCtrl.ts +++ b/public/app/features/panel/GeneralTabCtrl.ts @@ -1,11 +1,35 @@ import coreModule from 'app/core/core_module'; +const obj2string = obj => { + return Object.keys(obj) + .reduce((acc, curr) => acc.concat(curr + '=' + obj[curr]), []) + .join(); +}; + export class GeneralTabCtrl { panelCtrl: any; /** @ngInject */ constructor($scope) { this.panelCtrl = $scope.ctrl; + + const updatePanel = () => { + console.log('panel.render()'); + this.panelCtrl.panel.render(); + }; + + const generateValueFromPanel = scope => { + const { panel } = scope.ctrl; + const panelPropsToTrack = ['title', 'description', 'transparent', 'repeat', 'repeatDirection', 'minSpan']; + const panelPropsString = panelPropsToTrack + .map(prop => (panel[prop] && panel[prop].toString ? panel[prop].toString() : panel[prop])) + .join(); + const panelLinks = panel.links; + const panelLinksString = panelLinks.map(obj2string).join(); + return panelPropsString + panelLinksString; + }; + + $scope.$watch(generateValueFromPanel, updatePanel, true); } } diff --git a/public/app/features/panel/partials/general_tab.html b/public/app/features/panel/partials/general_tab.html index 797c252331c..03aab78c407 100644 --- a/public/app/features/panel/partials/general_tab.html +++ b/public/app/features/panel/partials/general_tab.html @@ -3,11 +3,11 @@
Info
Title - +
Description - +
From 827ffaccd36b102545e0eb687492e7b423970e22 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Wed, 5 Dec 2018 14:57:57 +0100 Subject: [PATCH 272/432] Pass some panel props down as strings to trigger render #14333 --- .../features/dashboard/dashgrid/PanelChrome.tsx | 11 ++++++++++- .../dashgrid/PanelHeader/PanelHeader.tsx | 16 +++++++++++++--- .../dashgrid/PanelHeader/PanelHeaderCorner.tsx | 4 ++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index fe1d1ebf145..931f11d937e 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -95,7 +95,16 @@ export class PanelChrome extends PureComponent { return (
- + + { +export class PanelHeader extends Component { state = { panelMenuOpen: false, }; @@ -44,7 +48,13 @@ export class PanelHeader extends PureComponent { const { panel, dashboard, timeInfo } = this.props; return ( <> - +
{isLoading && ( diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx index e8483ec467a..270a4a38dc5 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx @@ -14,6 +14,10 @@ enum InfoModes { interface Props { panel: PanelModel; + title?: string; + description?: string; + scopedVars?: string; + links?: []; } export class PanelHeaderCorner extends PureComponent { From a516ff81c97259dd19b48ceb037a7896f4624c9e Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 6 Dec 2018 09:39:17 +0100 Subject: [PATCH 273/432] Add prop key to panelPropsString to avoid a bug when changing another value and the render doesnt trigger --- public/app/features/panel/GeneralTabCtrl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/panel/GeneralTabCtrl.ts b/public/app/features/panel/GeneralTabCtrl.ts index 59d2d0e9cda..75edd72027b 100644 --- a/public/app/features/panel/GeneralTabCtrl.ts +++ b/public/app/features/panel/GeneralTabCtrl.ts @@ -22,7 +22,7 @@ export class GeneralTabCtrl { const { panel } = scope.ctrl; const panelPropsToTrack = ['title', 'description', 'transparent', 'repeat', 'repeatDirection', 'minSpan']; const panelPropsString = panelPropsToTrack - .map(prop => (panel[prop] && panel[prop].toString ? panel[prop].toString() : panel[prop])) + .map(prop => prop + '=' + (panel[prop] && panel[prop].toString ? panel[prop].toString() : panel[prop])) .join(); const panelLinks = panel.links; const panelLinksString = panelLinks.map(obj2string).join(); From d62cd8a5c53ff6423eca8884ebbc2a5479be1337 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 6 Dec 2018 10:34:27 +0100 Subject: [PATCH 274/432] Fix transparent option #14333 --- public/app/features/dashboard/dashgrid/PanelChrome.tsx | 5 +++-- public/app/features/dashboard/panel_model.ts | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 931f11d937e..5df6f20fc23 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -83,8 +83,9 @@ export class PanelChrome extends PureComponent { const { panel, dashboard, plugin } = this.props; const { refreshCounter, timeRange, timeInfo, renderCounter } = this.state; - const { datasource, targets } = panel; + const { datasource, targets, transparent } = panel; const PanelComponent = plugin.exports.Panel; + const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`; return ( @@ -94,7 +95,7 @@ export class PanelChrome extends PureComponent { } return ( -
+
Date: Thu, 6 Dec 2018 10:54:31 +0100 Subject: [PATCH 275/432] Changing from PureComponent to Component to re-render on link updates made in Angular #14333 --- .../dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx index 270a4a38dc5..331e469a60d 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx @@ -1,4 +1,4 @@ -import React, { PureComponent } from 'react'; +import React, { Component } from 'react'; import { PanelModel } from 'app/features/dashboard/panel_model'; import Tooltip from 'app/core/components/Tooltip/Tooltip'; import templateSrv from 'app/features/templating/template_srv'; @@ -20,7 +20,7 @@ interface Props { links?: []; } -export class PanelHeaderCorner extends PureComponent { +export class PanelHeaderCorner extends Component { timeSrv: TimeSrv = getTimeSrv(); getInfoMode = () => { From 8460b48c24e845c2af311e7b5f670fecab679175 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 6 Dec 2018 11:19:41 +0100 Subject: [PATCH 276/432] Always open panel links in new window if user asked for it #14333 --- public/app/features/dashboard/panellinks/link_srv.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/public/app/features/dashboard/panellinks/link_srv.ts b/public/app/features/dashboard/panellinks/link_srv.ts index f9ad40c50da..d6b2cd3636b 100644 --- a/public/app/features/dashboard/panellinks/link_srv.ts +++ b/public/app/features/dashboard/panellinks/link_srv.ts @@ -73,6 +73,7 @@ export class LinkSrv { getPanelLinkAnchorInfo(link, scopedVars) { const info: any = {}; + info.target = link.targetBlank ? '_blank' : ''; if (link.type === 'absolute') { info.target = link.targetBlank ? '_blank' : '_self'; info.href = this.templateSrv.replace(link.url || '', scopedVars); @@ -80,11 +81,9 @@ export class LinkSrv { } else if (link.url) { info.href = link.url; info.title = this.templateSrv.replace(link.title || '', scopedVars); - info.target = link.targetBlank ? '_blank' : ''; } else if (link.dashUri) { info.href = 'dashboard/' + link.dashUri + '?'; info.title = this.templateSrv.replace(link.title || '', scopedVars); - info.target = link.targetBlank ? '_blank' : ''; } else { info.title = this.templateSrv.replace(link.title || '', scopedVars); const slug = kbn.slugifyForUrl(link.dashboard || ''); From fadabd997a21cd9528b477e4f26b427afc9967a4 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 6 Dec 2018 16:06:47 +0100 Subject: [PATCH 277/432] Restore PluginEditCtrl accidently removed --- public/app/features/plugins/all.ts | 1 + .../app/features/plugins/plugin_edit_ctrl.ts | 179 ++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 public/app/features/plugins/plugin_edit_ctrl.ts diff --git a/public/app/features/plugins/all.ts b/public/app/features/plugins/all.ts index cece501112f..757a9a2f491 100644 --- a/public/app/features/plugins/all.ts +++ b/public/app/features/plugins/all.ts @@ -1,3 +1,4 @@ +import './plugin_edit_ctrl'; import './plugin_page_ctrl'; import './import_list/import_list'; import './ds_edit_ctrl'; diff --git a/public/app/features/plugins/plugin_edit_ctrl.ts b/public/app/features/plugins/plugin_edit_ctrl.ts new file mode 100644 index 00000000000..44d3d31f996 --- /dev/null +++ b/public/app/features/plugins/plugin_edit_ctrl.ts @@ -0,0 +1,179 @@ +import angular from 'angular'; +import _ from 'lodash'; +import Remarkable from 'remarkable'; + +export class PluginEditCtrl { + model: any; + pluginIcon: string; + pluginId: any; + includes: any; + readmeHtml: any; + includedDatasources: any; + tab: string; + navModel: any; + hasDashboards: any; + preUpdateHook: () => any; + postUpdateHook: () => any; + + /** @ngInject */ + constructor(private $scope, private $rootScope, private backendSrv, private $sce, private $routeParams, navModelSrv) { + this.pluginId = $routeParams.pluginId; + this.preUpdateHook = () => Promise.resolve(); + this.postUpdateHook = () => Promise.resolve(); + + this.init(); + } + + setNavModel(model) { + let defaultTab = 'readme'; + + this.navModel = { + main: { + img: model.info.logos.large, + subTitle: model.info.author.name, + url: '', + text: model.name, + breadcrumbs: [{ title: 'Plugins', url: 'plugins' }], + children: [ + { + icon: 'fa fa-fw fa-file-text-o', + id: 'readme', + text: 'Readme', + url: `plugins/${this.model.id}/edit?tab=readme`, + }, + ], + }, + }; + + if (model.type === 'app') { + this.navModel.main.children.push({ + icon: 'gicon gicon-cog', + id: 'config', + text: 'Config', + url: `plugins/${this.model.id}/edit?tab=config`, + }); + + const hasDashboards = _.find(model.includes, { type: 'dashboard' }); + + if (hasDashboards) { + this.navModel.main.children.push({ + icon: 'gicon gicon-dashboard', + id: 'dashboards', + text: 'Dashboards', + url: `plugins/${this.model.id}/edit?tab=dashboards`, + }); + } + + defaultTab = 'config'; + } + + this.tab = this.$routeParams.tab || defaultTab; + + for (const tab of this.navModel.main.children) { + if (tab.id === this.tab) { + tab.active = true; + } + } + } + + init() { + return this.backendSrv.get(`/api/plugins/${this.pluginId}/settings`).then(result => { + this.model = result; + this.pluginIcon = this.getPluginIcon(this.model.type); + + this.model.dependencies.plugins.forEach(plug => { + plug.icon = this.getPluginIcon(plug.type); + }); + + this.includes = _.map(result.includes, plug => { + plug.icon = this.getPluginIcon(plug.type); + return plug; + }); + + this.setNavModel(this.model); + return this.initReadme(); + }); + } + + initReadme() { + return this.backendSrv.get(`/api/plugins/${this.pluginId}/markdown/readme`).then(res => { + const md = new Remarkable({ + linkify: true, + }); + this.readmeHtml = this.$sce.trustAsHtml(md.render(res)); + }); + } + + getPluginIcon(type) { + switch (type) { + case 'datasource': + return 'icon-gf icon-gf-datasources'; + case 'panel': + return 'icon-gf icon-gf-panel'; + case 'app': + return 'icon-gf icon-gf-apps'; + case 'page': + return 'icon-gf icon-gf-endpoint-tiny'; + case 'dashboard': + return 'icon-gf icon-gf-dashboard'; + default: + return 'icon-gf icon-gf-apps'; + } + } + + update() { + this.preUpdateHook() + .then(() => { + const updateCmd = _.extend( + { + enabled: this.model.enabled, + pinned: this.model.pinned, + jsonData: this.model.jsonData, + secureJsonData: this.model.secureJsonData, + }, + {} + ); + return this.backendSrv.post(`/api/plugins/${this.pluginId}/settings`, updateCmd); + }) + .then(this.postUpdateHook) + .then(res => { + window.location.href = window.location.href; + }); + } + + importDashboards() { + return Promise.resolve(); + } + + setPreUpdateHook(callback: () => any) { + this.preUpdateHook = callback; + } + + setPostUpdateHook(callback: () => any) { + this.postUpdateHook = callback; + } + + updateAvailable() { + const modalScope = this.$scope.$new(true); + modalScope.plugin = this.model; + + this.$rootScope.appEvent('show-modal', { + src: 'public/app/features/plugins/partials/update_instructions.html', + scope: modalScope, + }); + } + + enable() { + this.model.enabled = true; + this.model.pinned = true; + this.update(); + } + + disable() { + this.model.enabled = false; + this.model.pinned = false; + this.update(); + } +} + +angular.module('grafana.controllers').controller('PluginEditCtrl', PluginEditCtrl); From 6fff8e4a1caf78359b2210c0504b30ad5ff17a90 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Thu, 6 Dec 2018 17:09:32 +0100 Subject: [PATCH 278/432] initial stuff --- .../core/components/Picker/SimplePicker.tsx | 4 +- .../app/plugins/panel/gauge/ValueMappings.tsx | 96 +++++++++++++++++++ public/app/plugins/panel/gauge/module.tsx | 20 +++- public/app/types/index.ts | 4 +- public/app/types/panel.ts | 14 +++ 5 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 public/app/plugins/panel/gauge/ValueMappings.tsx diff --git a/public/app/core/components/Picker/SimplePicker.tsx b/public/app/core/components/Picker/SimplePicker.tsx index 2ca8be43c75..4d127b7d82f 100644 --- a/public/app/core/components/Picker/SimplePicker.tsx +++ b/public/app/core/components/Picker/SimplePicker.tsx @@ -6,8 +6,8 @@ import ResetStyles from './ResetStyles'; interface Props { className?: string; defaultValue?: any; - getOptionLabel: (item: any) => string; - getOptionValue: (item: any) => string; + getOptionLabel?: (item: any) => string; + getOptionValue?: (item: any) => string; onSelected: (item: any) => {} | void; options: any[]; placeholder?: string; diff --git a/public/app/plugins/panel/gauge/ValueMappings.tsx b/public/app/plugins/panel/gauge/ValueMappings.tsx new file mode 100644 index 00000000000..b7749889110 --- /dev/null +++ b/public/app/plugins/panel/gauge/ValueMappings.tsx @@ -0,0 +1,96 @@ +import React, { PureComponent } from 'react'; +import { Label } from 'app/core/components/Label/Label'; +import SimplePicker from 'app/core/components/Picker/SimplePicker'; +import { OptionModuleProps } from './module'; +import { RangeMap, ValueMap } from 'app/types'; + +interface State { + valueMaps: ValueMap[]; + rangeMaps: RangeMap[]; +} + +enum MappingType { + ValueToText = 1, + RangeToText = 2, +} + +const mappingOptions = [ + { name: 'Value to text', value: MappingType.ValueToText }, + { name: 'Range to text', value: MappingType.RangeToText }, +]; + +export default class ValueMappings extends PureComponent { + constructor(props) { + super(props); + + this.state = { + valueMaps: props.options.valueMaps, + rangeMaps: props.options.rangeMaps, + }; + } + onMappingTypeChange = option => this.props.onChange({ ...this.props.options, mappingType: option.value }); + + addValueMap = () => + this.setState(prevState => ({ + valueMaps: [...prevState.valueMaps, { op: '', value: '', text: '' }], + })); + + addRangeMap = () => { + this.setState = () => + this.setState(prevState => ({ + valueMaps: [...prevState.valueMaps, { op: '', value: '', text: '', from: '', to: '' }], + })); + }; + + updateGauge = () => {}; + + renderValueMapList() { + const { mappingType, rangeMaps, valueMaps } = this.props.options; + + if (mappingType === MappingType.RangeToText) { + return ( +
+ {rangeMaps.length > 0 + ? rangeMaps.map((range, index) => { + return
{`${range.from}-${range.to}`}
; + }) + : 'aint no ranges, add one?'} +
+ ); + } + + return ( +
+ {valueMaps.length > 0 + ? valueMaps.map((value, index) => { + return
{`${value}`}
; + }) + : 'aint no values, add one?'} +
+ ); + } + + render() { + const { mappingType } = this.props.options; + + return ( +
+
+ + i.name} + onSelected={option => this.onMappingTypeChange(option)} + width={5} + value={mappingType} + /> +
+
+
Set value mappings
+
{this.renderValueMapList()}
+
+
+ ); + } +} diff --git a/public/app/plugins/panel/gauge/module.tsx b/public/app/plugins/panel/gauge/module.tsx index 87e25612f61..81e047e6434 100644 --- a/public/app/plugins/panel/gauge/module.tsx +++ b/public/app/plugins/panel/gauge/module.tsx @@ -1,10 +1,11 @@ import React, { PureComponent } from 'react'; import Gauge from 'app/viz/Gauge'; -import { NullValueMode, PanelOptionsProps, PanelProps, Threshold } from 'app/types'; +import { NullValueMode, PanelOptionsProps, PanelProps, RangeMap, Threshold, ValueMap } from 'app/types'; import { getTimeSeriesVMs } from 'app/viz/state/timeSeries'; import ValueOptions from './ValueOptions'; import GaugeOptions from './GaugeOptions'; import Thresholds from './Thresholds'; +import ValueMappings from './ValueMappings'; export interface OptionsProps { decimals: number; @@ -15,6 +16,9 @@ export interface OptionsProps { suffix: string; unit: string; thresholds: Threshold[]; + valueMaps: ValueMap[]; + rangeMaps: RangeMap[]; + mappingType: number; } export interface OptionModuleProps { @@ -52,11 +56,19 @@ class Options extends PureComponent> { static defaultProps = defaultProps; render() { + const { onChange, options } = this.props; return (
- - - +
+
Options
+ + + +
+
+
Value mappings
+ +
); } diff --git a/public/app/types/index.ts b/public/app/types/index.ts index a13bf28c3ca..e66153ab723 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -20,7 +20,7 @@ import { DataQueryResponse, DataQueryOptions, } from './series'; -import { PanelProps, PanelOptionsProps, Threshold } from './panel'; +import { PanelProps, PanelOptionsProps, RangeMap, Threshold, ValueMap } from './panel'; import { PluginDashboard, PluginMeta, Plugin, PanelPlugin, PluginsState } from './plugins'; import { Organization, OrganizationState } from './organization'; import { @@ -92,6 +92,8 @@ export { Threshold, ValidationEvents, ValidationRule, + ValueMap, + RangeMap, }; export interface StoreState { diff --git a/public/app/types/panel.ts b/public/app/types/panel.ts index 049b07973de..0736ae47acb 100644 --- a/public/app/types/panel.ts +++ b/public/app/types/panel.ts @@ -36,3 +36,17 @@ export interface Threshold { color?: string; canRemove: boolean; } + +interface BaseMap { + op: string; + text: string; +} + +export interface ValueMap extends BaseMap { + value: string; +} + +export interface RangeMap extends BaseMap { + from: string; + to: string; +} From 97180d51065b3b5ee99391a0c8be62ec40c2cbf8 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 7 Dec 2018 10:28:51 +0100 Subject: [PATCH 279/432] add scoped vars to query options --- public/app/features/explore/Explore.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 386e8d46597..8e8d215e8b6 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -625,6 +625,10 @@ export class Explore extends React.PureComponent { raw: range, }, rangeRaw: range, + scopedVars: { + __interval: { text: interval, value: interval }, + __interval_ms: { text: intervalMs, value: intervalMs }, + }, }; } From 260d0189b64fc983341f18d01c44d18897aca7cf Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 7 Dec 2018 10:29:38 +0100 Subject: [PATCH 280/432] add table support flag in influx config --- public/app/plugins/datasource/influxdb/plugin.json | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/plugins/datasource/influxdb/plugin.json b/public/app/plugins/datasource/influxdb/plugin.json index 973c4ac52cd..ab8f36c29cb 100644 --- a/public/app/plugins/datasource/influxdb/plugin.json +++ b/public/app/plugins/datasource/influxdb/plugin.json @@ -7,6 +7,7 @@ "metrics": true, "annotations": true, "alerting": true, + "tables": true, "queryOptions": { "minInterval": true From 45fbbe8021e7c080e25e83a54a3f9947a46c62aa Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 7 Dec 2018 11:27:11 +0100 Subject: [PATCH 281/432] initialize empty variables array in constructor so that datasources can use the array in explore --- public/app/features/templating/template_srv.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/features/templating/template_srv.ts b/public/app/features/templating/template_srv.ts index 485d244e9ae..74da017bb93 100644 --- a/public/app/features/templating/template_srv.ts +++ b/public/app/features/templating/template_srv.ts @@ -17,6 +17,7 @@ export class TemplateSrv { constructor() { this.builtIns['__interval'] = { text: '1s', value: '1s' }; this.builtIns['__interval_ms'] = { text: '100', value: '100' }; + this.variables = []; } init(variables) { From 5d22cdab28f7e52786526e98573325a6ec583f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 7 Dec 2018 13:50:50 +0100 Subject: [PATCH 282/432] click on dashboard title moves you back to dashboard instead of search --- public/app/features/dashboard/dashgrid/GeneralTab.tsx | 2 +- public/app/features/dashboard/dashgrid/PanelEditor.tsx | 2 +- public/app/features/dashboard/dashnav/dashnav.ts | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/GeneralTab.tsx b/public/app/features/dashboard/dashgrid/GeneralTab.tsx index 96694e346df..91e236c8b31 100644 --- a/public/app/features/dashboard/dashgrid/GeneralTab.tsx +++ b/public/app/features/dashboard/dashgrid/GeneralTab.tsx @@ -44,7 +44,7 @@ export class GeneralTab extends PureComponent { render() { return ( - +
(this.element = element)} /> ); diff --git a/public/app/features/dashboard/dashgrid/PanelEditor.tsx b/public/app/features/dashboard/dashgrid/PanelEditor.tsx index 44857f989f3..fa00b51823f 100644 --- a/public/app/features/dashboard/dashgrid/PanelEditor.tsx +++ b/public/app/features/dashboard/dashgrid/PanelEditor.tsx @@ -77,7 +77,7 @@ export class PanelEditor extends PureComponent { const tabs = [ { id: 'queries', text: 'Queries' }, { id: 'visualization', text: 'Visualization' }, - { id: 'advanced', text: 'Advanced' }, + { id: 'advanced', text: 'Panel Options' }, ]; if (config.alertingEnabled && plugin.id === 'graph') { diff --git a/public/app/features/dashboard/dashnav/dashnav.ts b/public/app/features/dashboard/dashnav/dashnav.ts index 7312d6db784..81aeeed97d3 100644 --- a/public/app/features/dashboard/dashnav/dashnav.ts +++ b/public/app/features/dashboard/dashnav/dashnav.ts @@ -74,6 +74,11 @@ export class DashNavCtrl { } showSearch() { + if (this.dashboard.meta.fullscreen) { + this.close(); + return; + } + appEvents.emit('show-dash-search'); } From 68c2d2631e0b3312e2be6ec0aa3c511a1100c629 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 7 Dec 2018 14:11:40 +0100 Subject: [PATCH 283/432] filter out build in datasources. add unit test --- public/app/features/explore/Explore.tsx | 4 ++- public/app/features/plugins/datasource_srv.ts | 5 ++++ .../plugins/specs/datasource_srv.test.ts | 26 +++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 8e8d215e8b6..f7d4abf4f46 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -158,7 +158,7 @@ export class Explore extends React.PureComponent { if (!datasourceSrv) { throw new Error('No datasource service passed as props.'); } - const datasources = datasourceSrv.getAll(); + const datasources = datasourceSrv.getExternal(); const exploreDatasources = datasources.map(ds => ({ value: ds.name, label: ds.name, @@ -574,6 +574,7 @@ export class Explore extends React.PureComponent { 'Table', { format: 'table', + resultFormat: 'table', instant: true, valueWithRefId: true, }, @@ -585,6 +586,7 @@ export class Explore extends React.PureComponent { 'Graph', { format: 'time_series', + resultFormat: 'time_series', instant: false, }, makeTimeSeriesList diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index d5e4c247073..0d68cbc71ba 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -78,6 +78,11 @@ export class DatasourceSrv { return Object.keys(datasources).map(name => datasources[name]); } + getExternal() { + const datasources = this.getAll().filter(ds => !ds.meta.builtIn); + return _.sortBy(datasources, ['name']); + } + getAnnotationSources() { const sources = []; diff --git a/public/app/features/plugins/specs/datasource_srv.test.ts b/public/app/features/plugins/specs/datasource_srv.test.ts index b04e9fa10c2..51b83efb3f5 100644 --- a/public/app/features/plugins/specs/datasource_srv.test.ts +++ b/public/app/features/plugins/specs/datasource_srv.test.ts @@ -18,6 +18,32 @@ const templateSrv = { describe('datasource_srv', () => { const _datasourceSrv = new DatasourceSrv({}, {}, {}, templateSrv); + describe('when loading external datasources', () => { + beforeEach(() => { + config.datasources = { + buildInDs: { + name: 'buildIn', + meta: { builtIn: true }, + }, + nonBuildIn: { + name: 'external1', + meta: { builtIn: false }, + }, + nonExplore: { + name: 'external2', + meta: {}, + }, + }; + }); + + it('should return list of explore sources', () => { + const externalSources = _datasourceSrv.getExternal(); + expect(externalSources.length).toBe(2); + expect(externalSources[0].name).toBe('external1'); + expect(externalSources[1].name).toBe('external2'); + }); + }); + describe('when loading metric sources', () => { let metricSources; const unsortedDatasources = { From 569f5e8d5edc3993bf824832322c2d2187f6dc85 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 7 Dec 2018 14:13:05 +0100 Subject: [PATCH 284/432] remove result format. might add this later --- public/app/features/explore/Explore.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index f7d4abf4f46..d3c4a832a13 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -574,7 +574,6 @@ export class Explore extends React.PureComponent { 'Table', { format: 'table', - resultFormat: 'table', instant: true, valueWithRefId: true, }, @@ -586,7 +585,6 @@ export class Explore extends React.PureComponent { 'Graph', { format: 'time_series', - resultFormat: 'time_series', instant: false, }, makeTimeSeriesList From bd589b61bd6ad19ad06b98f5d317bf6323b58b44 Mon Sep 17 00:00:00 2001 From: ijin08 Date: Fri, 7 Dec 2018 15:18:43 +0100 Subject: [PATCH 285/432] removed side menu from display options, kept overrides in display options, moved thresholds and time regions to its own section in visualization --- public/app/features/panel/panel_editor_tab.ts | 7 ++- public/app/plugins/panel/graph/module.ts | 1 + .../app/plugins/panel/graph/tab_display.html | 52 ++++--------------- .../graph/tab_thresholds_time_regions.html | 2 + .../plugins/panel/graph/thresholds_form.html | 1 - .../panel/graph/time_regions_form.html | 3 +- 6 files changed, 19 insertions(+), 47 deletions(-) create mode 100644 public/app/plugins/panel/graph/tab_thresholds_time_regions.html diff --git a/public/app/features/panel/panel_editor_tab.ts b/public/app/features/panel/panel_editor_tab.ts index e9365cc1cfc..52d427c54bf 100644 --- a/public/app/features/panel/panel_editor_tab.ts +++ b/public/app/features/panel/panel_editor_tab.ts @@ -12,7 +12,12 @@ function panelEditorTab(dynamicDirectiveSrv) { }, directive: scope => { const pluginId = scope.ctrl.pluginId; - const tabName = scope.editorTab.title.toLowerCase().replace(' ', '-'); + const tabName = scope.editorTab.title + .toLowerCase() + .replace(' ', '-') + .replace('&', '') + .replace(' ', '') + .replace(' ', '-'); if (directiveCache[pluginId]) { if (directiveCache[pluginId][tabName]) { diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index 24c8825e883..75e82115ca1 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -138,6 +138,7 @@ class GraphCtrl extends MetricsPanelCtrl { this.addEditorTab('Display options', 'public/app/plugins/panel/graph/tab_display.html'); this.addEditorTab('Axes', axesEditorComponent); this.addEditorTab('Legend', 'public/app/plugins/panel/graph/tab_legend.html'); + this.addEditorTab('Thresholds & Time Regions', 'public/app/plugins/panel/graph/tab_thresholds_time_regions.html'); this.subTabIndex = 0; } diff --git a/public/app/plugins/panel/graph/tab_display.html b/public/app/plugins/panel/graph/tab_display.html index d407f30ffc8..2ed600aac70 100644 --- a/public/app/plugins/panel/graph/tab_display.html +++ b/public/app/plugins/panel/graph/tab_display.html @@ -1,28 +1,5 @@ -
- -
+
Draw Modes
@@ -89,9 +66,7 @@
-
-
Series specific overrides Regex match example: /server[0-3]/i
@@ -110,35 +85,26 @@
- +
- +
- +
+
+ +
- -
- -
- -
- -
- -
- -
diff --git a/public/app/plugins/panel/graph/tab_thresholds_time_regions.html b/public/app/plugins/panel/graph/tab_thresholds_time_regions.html new file mode 100644 index 00000000000..1ee603f5d36 --- /dev/null +++ b/public/app/plugins/panel/graph/tab_thresholds_time_regions.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/public/app/plugins/panel/graph/thresholds_form.html b/public/app/plugins/panel/graph/thresholds_form.html index 81877150a47..99e2f530c8d 100644 --- a/public/app/plugins/panel/graph/thresholds_form.html +++ b/public/app/plugins/panel/graph/thresholds_form.html @@ -1,5 +1,4 @@
-
Thresholds

Visual thresholds options disabled. Visit the Alert tab update your thresholds.
diff --git a/public/app/plugins/panel/graph/time_regions_form.html b/public/app/plugins/panel/graph/time_regions_form.html index 7292c53ec80..3330085e3a8 100644 --- a/public/app/plugins/panel/graph/time_regions_form.html +++ b/public/app/plugins/panel/graph/time_regions_form.html @@ -1,5 +1,4 @@

-
Time regions All configured time regions refers to UTC time
@@ -58,7 +57,7 @@
\ No newline at end of file From 368f1f67e4fb255fc21d3ceced3dc41b3d37938b Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Fri, 7 Dec 2018 16:55:58 +0100 Subject: [PATCH 286/432] render a value mapping row --- public/app/plugins/panel/gauge/MappingRow.tsx | 141 ++++++++++++++++++ .../app/plugins/panel/gauge/ValueMappings.tsx | 89 ++++------- public/app/plugins/panel/gauge/module.tsx | 2 + 3 files changed, 168 insertions(+), 64 deletions(-) create mode 100644 public/app/plugins/panel/gauge/MappingRow.tsx diff --git a/public/app/plugins/panel/gauge/MappingRow.tsx b/public/app/plugins/panel/gauge/MappingRow.tsx new file mode 100644 index 00000000000..63930d617cd --- /dev/null +++ b/public/app/plugins/panel/gauge/MappingRow.tsx @@ -0,0 +1,141 @@ +import React, { PureComponent } from 'react'; +import { Label } from 'app/core/components/Label/Label'; +import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup'; +import { RangeMap, ValueMap } from 'app/types'; + +enum MappingType { + ValueToText = 1, + RangeToText = 2, +} + +interface Props { + mapping: ValueMap | RangeMap; + updateMapping: (mapping) => void; +} + +interface State { + mapping: ValueMap | RangeMap; + mappingType: MappingType; +} + +export default class MappingRow extends PureComponent { + constructor(props) { + super(props); + + this.state = { + mappingType: MappingType.ValueToText, + mapping: props.mapping, + }; + } + + onMappingValueChange = event => { + const { mapping } = this.state; + + const updatedMapping = { ...mapping, value: event.target.value }; + + this.setState({ mapping: updatedMapping }); + }; + + onMappingFromChange = event => { + const { mapping } = this.state; + + const updatedMapping = { ...mapping, from: event.target.value }; + + this.setState({ mapping: updatedMapping }); + }; + + onMappingToChange = event => { + const { mapping } = this.state; + + const updatedMapping = { ...mapping, to: event.target.value }; + + this.setState({ mapping: updatedMapping }); + }; + + onMappingTextChange = event => { + const { mapping } = this.state; + + const updatedMapping = { ...mapping, text: event.target.value }; + this.setState({ mapping: updatedMapping }); + }; + + onMappingTypeChange = mappingType => this.setState({ mappingType }); + + renderRow() { + const { mapping, mappingType } = this.state; + + if (mappingType === MappingType.RangeToText) { + const rangeMap = mapping as RangeMap; + + return ( +
+
+ + +
+
+ + +
+
+ + +
+
+ ); + } + + const valueMap = mapping as ValueMap; + + return ( +
+
+ + +
+
+ + +
+
+ ); + } + + render() { + const { mappingType } = this.state; + + return ( +
+
+ this.onMappingTypeChange(mappingType)} + render={({ selectedValue, onChange }) => { + return [ + + Value + , + + Range + , + ]; + }} + /> +
{this.renderRow()}
+
+
+ ); + } +} diff --git a/public/app/plugins/panel/gauge/ValueMappings.tsx b/public/app/plugins/panel/gauge/ValueMappings.tsx index b7749889110..897fd462a78 100644 --- a/public/app/plugins/panel/gauge/ValueMappings.tsx +++ b/public/app/plugins/panel/gauge/ValueMappings.tsx @@ -1,94 +1,55 @@ import React, { PureComponent } from 'react'; -import { Label } from 'app/core/components/Label/Label'; -import SimplePicker from 'app/core/components/Picker/SimplePicker'; +import MappingRow from './MappingRow'; import { OptionModuleProps } from './module'; import { RangeMap, ValueMap } from 'app/types'; interface State { + combinedMappings: any[]; valueMaps: ValueMap[]; rangeMaps: RangeMap[]; } -enum MappingType { - ValueToText = 1, - RangeToText = 2, -} - -const mappingOptions = [ - { name: 'Value to text', value: MappingType.ValueToText }, - { name: 'Range to text', value: MappingType.RangeToText }, -]; - export default class ValueMappings extends PureComponent { constructor(props) { super(props); this.state = { - valueMaps: props.options.valueMaps, + combinedMappings: props.options.valueMaps.concat(props.options.rangeMaps), rangeMaps: props.options.rangeMaps, + valueMaps: props.options.valueMaps, }; } - onMappingTypeChange = option => this.props.onChange({ ...this.props.options, mappingType: option.value }); - addValueMap = () => + addMapping = () => this.setState(prevState => ({ - valueMaps: [...prevState.valueMaps, { op: '', value: '', text: '' }], + combinedMappings: [...prevState.combinedMappings, { op: '', value: '', text: '' }], })); - addRangeMap = () => { - this.setState = () => - this.setState(prevState => ({ - valueMaps: [...prevState.valueMaps, { op: '', value: '', text: '', from: '', to: '' }], - })); + updateGauge = mapping => { + this.setState(prevState => ({ + combinedMappings: prevState.combinedMappings.map(m => { + if (m === mapping) { + return { ...mapping }; + } + + return m; + }), + })); }; - updateGauge = () => {}; - - renderValueMapList() { - const { mappingType, rangeMaps, valueMaps } = this.props.options; - - if (mappingType === MappingType.RangeToText) { - return ( -
- {rangeMaps.length > 0 - ? rangeMaps.map((range, index) => { - return
{`${range.from}-${range.to}`}
; - }) - : 'aint no ranges, add one?'} -
- ); - } - - return ( -
- {valueMaps.length > 0 - ? valueMaps.map((value, index) => { - return
{`${value}`}
; - }) - : 'aint no values, add one?'} -
- ); - } - render() { - const { mappingType } = this.props.options; + const { combinedMappings } = this.state; return ( -
+
- - i.name} - onSelected={option => this.onMappingTypeChange(option)} - width={5} - value={mappingType} - /> -
-
-
Set value mappings
-
{this.renderValueMapList()}
+
+ {combinedMappings.length > 0 && + combinedMappings.map((mapping, index) => { + return ; + })} +
+
Add mapping
); diff --git a/public/app/plugins/panel/gauge/module.tsx b/public/app/plugins/panel/gauge/module.tsx index 81e047e6434..5a4b96b0647 100644 --- a/public/app/plugins/panel/gauge/module.tsx +++ b/public/app/plugins/panel/gauge/module.tsx @@ -34,6 +34,8 @@ export const defaultProps = { showThresholdMarkers: true, showThresholdLabels: false, suffix: '', + valueMaps: [], + rangeMaps: [], }, }; From a0a06911d81604d8eb3f05241c35fcc91b48e14f Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Fri, 7 Dec 2018 17:08:28 +0100 Subject: [PATCH 287/432] Toggle buttons --- public/app/plugins/panel/gauge/MappingRow.tsx | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/public/app/plugins/panel/gauge/MappingRow.tsx b/public/app/plugins/panel/gauge/MappingRow.tsx index 63930d617cd..93d17c24692 100644 --- a/public/app/plugins/panel/gauge/MappingRow.tsx +++ b/public/app/plugins/panel/gauge/MappingRow.tsx @@ -88,7 +88,7 @@ export default class MappingRow extends PureComponent { const valueMap = mapping as ValueMap; return ( -
+
@@ -105,36 +105,34 @@ export default class MappingRow extends PureComponent { const { mappingType } = this.state; return ( -
-
- this.onMappingTypeChange(mappingType)} - render={({ selectedValue, onChange }) => { - return [ - - Value - , - - Range - , - ]; - }} - /> -
{this.renderRow()}
-
+
+ this.onMappingTypeChange(mappingType)} + value={mappingType} + render={({ selectedValue, onChange }) => { + return [ + + Value + , + + Range + , + ]; + }} + /> +
{this.renderRow()}
); } From bfbb44af038200992efa269748f08830db380468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 7 Dec 2018 17:15:46 +0100 Subject: [PATCH 288/432] transparent toggle style and new button group style --- public/app/core/components/Switch/Switch.tsx | 7 +- .../ToggleButtonGroup/ToggleButtonGroup.tsx | 36 ++-------- public/app/features/explore/Logs.tsx | 32 +++------ .../app/plugins/datasource/loki/plugin.json | 2 +- public/sass/_variables.dark.scss | 5 ++ public/sass/_variables.light.scss | 5 ++ public/sass/base/_reboot.scss | 10 +-- public/sass/components/_buttons.scss | 2 +- public/sass/components/_gf-form.scss | 8 ++- public/sass/components/_panel_logs.scss | 14 +++- public/sass/components/_switch.scss | 20 ++++-- .../sass/components/_toggle_button_group.scss | 25 +++---- public/sass/mixins/_mixins.scss | 67 ++++--------------- 13 files changed, 93 insertions(+), 140 deletions(-) diff --git a/public/app/core/components/Switch/Switch.tsx b/public/app/core/components/Switch/Switch.tsx index 46040d2307b..5cb7617c89c 100644 --- a/public/app/core/components/Switch/Switch.tsx +++ b/public/app/core/components/Switch/Switch.tsx @@ -6,6 +6,7 @@ export interface Props { checked: boolean; labelClass?: string; switchClass?: string; + transparent?: boolean; onChange: (event) => any; } @@ -24,11 +25,11 @@ export class Switch extends PureComponent { }; render() { - const { labelClass = '', switchClass = '', label, checked } = this.props; + const { labelClass = '', switchClass = '', label, checked, transparent } = this.props; const labelId = `check-${this.state.id}`; - const labelClassName = `gf-form-label ${labelClass} pointer`; - const switchClassName = `gf-form-switch ${switchClass}`; + const labelClassName = `gf-form-label ${labelClass} ${transparent ? 'gf-form-label--transparent' : ''} pointer`; + const switchClassName = `gf-form-switch ${switchClass} ${transparent ? 'gf-form-switch--transparent' : ''}`; return (
- - - - - Object.keys(LogsDedupStrategy).map((dedupType, i) => ( - - {dedupType} - - )) - } - /> + + + + + {Object.keys(LogsDedupStrategy).map((dedupType, i) => ( + + {dedupType} + + ))} + + {hasData && meta && (
diff --git a/public/app/plugins/datasource/loki/plugin.json b/public/app/plugins/datasource/loki/plugin.json index 30997ca7632..64462a08415 100644 --- a/public/app/plugins/datasource/loki/plugin.json +++ b/public/app/plugins/datasource/loki/plugin.json @@ -7,7 +7,7 @@ "annotations": false, "logs": true, "explore": true, - "tables": true, + "tables": false, "info": { "description": "Loki Logging Data Source for Grafana", "author": { diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index ae410ac6cc0..ca9e1a38249 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -391,3 +391,8 @@ $panel-grid-placeholder-shadow: 0 0 4px $blue; // logs $logs-color-unkown: $gray-2; + +// toggle-group +$button-toggle-group-btn-active-bg: linear-gradient(90deg, $orange, $red); +$button-toggle-group-btn-active-shadow: inset 0 0 4px $black; +$button-toggle-group-btn-seperator-border: 1px solid $page-bg; diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 9c056294062..31f61a2c196 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -400,3 +400,8 @@ $panel-grid-placeholder-shadow: 0 0 4px $blue-light; // logs $logs-color-unkown: $gray-5; + +// toggle-group +$button-toggle-group-btn-active-bg: linear-gradient(90deg, $yellow, $red); +$button-toggle-group-btn-active-shadow: inset 0 0 4px $black; +$button-toggle-group-btn-seperator-border: 1px solid $gray-6; diff --git a/public/sass/base/_reboot.scss b/public/sass/base/_reboot.scss index e34bdfdb9ed..8aac8bc815e 100644 --- a/public/sass/base/_reboot.scss +++ b/public/sass/base/_reboot.scss @@ -87,7 +87,7 @@ body { // might still respond to pointer events. // // Credit: https://github.com/suitcss/base -[tabindex="-1"]:focus { +[tabindex='-1']:focus { outline: none !important; } @@ -171,7 +171,7 @@ a { } &:focus { - @include tab-focus(); + @include no-focus(); } } @@ -214,7 +214,7 @@ img { // for traditionally non-focusable elements with role="button" // see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile -[role="button"] { +[role='button'] { cursor: pointer; } @@ -231,7 +231,7 @@ img { a, area, button, -[role="button"], +[role='button'], input, label, select, @@ -320,7 +320,7 @@ legend { // border: 0; } -input[type="search"] { +input[type='search'] { // This overrides the extra rounded corners on search inputs in iOS so that our // `.form-control` class can properly style them. Note that this cannot simply // be added to `.form-control` as it's not specific enough. For details, see diff --git a/public/sass/components/_buttons.scss b/public/sass/components/_buttons.scss index 87947965fc3..27055551b4b 100644 --- a/public/sass/components/_buttons.scss +++ b/public/sass/components/_buttons.scss @@ -23,7 +23,7 @@ &.active { &:focus, &.focus { - @include tab-focus(); + @include no-focus(); } } diff --git a/public/sass/components/_gf-form.scss b/public/sass/components/_gf-form.scss index 74050e07abd..0c6ef7d1fa0 100644 --- a/public/sass/components/_gf-form.scss +++ b/public/sass/components/_gf-form.scss @@ -117,9 +117,11 @@ $input-border: 1px solid $input-border-color; color: $critical; } - &--small { - padding: ($input-padding-y / 2) ($input-padding-x / 2); - font-size: $font-size-xs; + &--transparent { + background-color: transparent; + border: 0; + text-align: right; + padding-left: 0px; } &:disabled { diff --git a/public/sass/components/_panel_logs.scss b/public/sass/components/_panel_logs.scss index 8220cfed878..bcf18025292 100644 --- a/public/sass/components/_panel_logs.scss +++ b/public/sass/components/_panel_logs.scss @@ -1,6 +1,6 @@ $column-horizontal-spacing: 10px; -.logs-panel-controls { +.logs-panel-options { display: flex; background-color: $page-bg; padding: $panel-padding; @@ -8,8 +8,14 @@ $column-horizontal-spacing: 10px; border-radius: $border-radius; margin: 2*$panel-margin 0; border: $panel-border; + flex-direction: column; +} + +.logs-panel-controls { + display: flex; justify-items: flex-start; - align-items: flex-start; + align-items: center; + flex-wrap: wrap; > * { margin-right: 1em; @@ -27,10 +33,14 @@ $column-horizontal-spacing: 10px; color: $text-color-weak; // Align first line with controls labels margin-top: -2px; + min-width: 30%; + display: flex; } .logs-panel-meta__item { margin-right: 1em; + display: flex; + flex-direction: column; } .logs-panel-meta__label { diff --git a/public/sass/components/_switch.scss b/public/sass/components/_switch.scss index bd512676912..88a959f5117 100644 --- a/public/sass/components/_switch.scss +++ b/public/sass/components/_switch.scss @@ -27,33 +27,39 @@ gf-form-switch[disabled] { border: 1px solid $input-border-color; border-left: none; border-radius: $input-border-radius; + display: flex; + align-items: center; + justify-content: center; input { opacity: 0; width: 0; height: 0; } + + &--transparent { + background: transparent; + border: 0; + width: 40px; + } } /* The slider */ .gf-form-switch__slider { - position: absolute; - top: 8px; - left: 16px; - right: 14px; - bottom: 10px; background: $switch-slider-off-bg; border-radius: 8px; height: 16px; width: 29px; + display: block; + position: relative; &::before { position: absolute; content: ''; height: 12px; width: 12px; - left: 2px; - bottom: 2px; + left: 1px; + top: 2px; background: $switch-slider-color; transition: 0.4s; border-radius: 50%; diff --git a/public/sass/components/_toggle_button_group.scss b/public/sass/components/_toggle_button_group.scss index ed701a489a9..73ebf61778e 100644 --- a/public/sass/components/_toggle_button_group.scss +++ b/public/sass/components/_toggle_button_group.scss @@ -1,24 +1,21 @@ .toggle-button-group { display: flex; - .gf-form-label { - background-color: $input-label-bg; - &:first-child { - border-radius: $border-radius 0 0 $border-radius; - margin: 0; - } - &.small { - padding: ($input-padding-y / 2) ($input-padding-x / 2); - font-size: $font-size-xs; - } - } - .btn { - background-color: $typeahead-selected-bg; + @include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl, $btn-inverse-text-color, $btn-inverse-text-shadow); + + padding: 7px 10px; + font-weight: $font-weight-semi-bold; + font-size: $font-size-sm; border-radius: 0; color: $text-color; + border-right: $button-toggle-group-btn-seperator-border; + &.active { - background-color: $input-bg; + background: $button-toggle-group-btn-active-bg; + box-shadow: $button-toggle-group-btn-active-shadow; + border-right: 0; + &:hover { cursor: default; } diff --git a/public/sass/mixins/_mixins.scss b/public/sass/mixins/_mixins.scss index f3be6af56ba..b6b028b7c87 100644 --- a/public/sass/mixins/_mixins.scss +++ b/public/sass/mixins/_mixins.scss @@ -1,6 +1,6 @@ @mixin clearfix() { &::after { - content: ""; + content: ''; display: table; clear: both; } @@ -19,6 +19,10 @@ outline-offset: -2px; } +@mixin no-focus() { + outline: none; +} + // Center-align a block level element // ---------------------------------- @mixin center-block() { @@ -265,20 +269,10 @@ // Add an alphatransparency value to any background or border color (via Elyse Holladay) #translucent { @mixin background($color: $white, $alpha: 1) { - background-color: hsla( - hue($color), - saturation($color), - lightness($color), - $alpha - ); + background-color: hsla(hue($color), saturation($color), lightness($color), $alpha); } @mixin border($color: $white, $alpha: 1) { - border-color: hsla( - hue($color), - saturation($color), - lightness($color), - $alpha - ); + border-color: hsla(hue($color), saturation($color), lightness($color), $alpha); @include background-clip(padding-box); } } @@ -294,66 +288,37 @@ // Gradients @mixin gradient-horizontal($startColor: #555, $endColor: #333) { background-color: $endColor; - background-image: linear-gradient( - to right, - $startColor, - $endColor - ); // Standard, IE10 + background-image: linear-gradient(to right, $startColor, $endColor); // Standard, IE10 background-repeat: repeat-x; } @mixin gradient-vertical($startColor: #555, $endColor: #333) { background-color: mix($startColor, $endColor, 60%); - background-image: linear-gradient( - to bottom, - $startColor, - $endColor - ); // Standard, IE10 + background-image: linear-gradient(to bottom, $startColor, $endColor); // Standard, IE10 background-repeat: repeat-x; } @mixin gradient-directional($startColor: #555, $endColor: #333, $deg: 45deg) { background-color: $endColor; background-repeat: repeat-x; - background-image: linear-gradient( - $deg, - $startColor, - $endColor - ); // Standard, IE10 + background-image: linear-gradient($deg, $startColor, $endColor); // Standard, IE10 } @mixin gradient-horizontal-three-colors($startColor: #00b3ee, $midColor: #7a43b6, $colorStop: 50%, $endColor: #c3325f) { background-color: mix($midColor, $endColor, 80%); - background-image: linear-gradient( - to right, - $startColor, - $midColor $colorStop, - $endColor - ); + background-image: linear-gradient(to right, $startColor, $midColor $colorStop, $endColor); background-repeat: no-repeat; } @mixin gradient-vertical-three-colors($startColor: #00b3ee, $midColor: #7a43b6, $colorStop: 50%, $endColor: #c3325f) { background-color: mix($midColor, $endColor, 80%); - background-image: linear-gradient( - $startColor, - $midColor $colorStop, - $endColor - ); + background-image: linear-gradient($startColor, $midColor $colorStop, $endColor); background-repeat: no-repeat; } @mixin gradient-radial($innerColor: #555, $outerColor: #333) { background-color: $outerColor; - background-image: -webkit-gradient( - radial, - center center, - 0, - center center, - 460, - from($innerColor), - to($outerColor) - ); + background-image: -webkit-gradient(radial, center center, 0, center center, 460, from($innerColor), to($outerColor)); background-image: -webkit-radial-gradient(circle, $innerColor, $outerColor); background-image: -moz-radial-gradient(circle, $innerColor, $outerColor); background-image: -o-radial-gradient(circle, $innerColor, $outerColor); @@ -380,11 +345,7 @@ @mixin left-brand-border-gradient() { border: none; - border-image: linear-gradient( - rgba(255, 213, 0, 1) 0%, - rgba(255, 68, 0, 1) 99%, - rgba(255, 68, 0, 1) 100% - ); + border-image: linear-gradient(rgba(255, 213, 0, 1) 0%, rgba(255, 68, 0, 1) 99%, rgba(255, 68, 0, 1) 100%); border-image-slice: 1; border-style: solid; border-top: 0; From cb59f388caff07bb3f2a2f67c8df6c70512fe29e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 7 Dec 2018 17:23:35 +0100 Subject: [PATCH 289/432] explore logs options styling --- public/app/features/explore/Logs.tsx | 24 ++++++++++++------------ public/sass/components/_panel_logs.scss | 6 ++---- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index bfd5f9fae99..1a7318a3dda 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -441,21 +441,21 @@ export default class Logs extends PureComponent { ))} - - {hasData && - meta && ( -
- {meta.map(item => ( -
- {item.label}: - {renderMetaItem(item.value, item.kind)} -
- ))} -
- )}
+ {hasData && + meta && ( +
+ {meta.map(item => ( +
+ {item.label}: + {renderMetaItem(item.value, item.kind)} +
+ ))} +
+ )} +
{hasData && !deferLogs && diff --git a/public/sass/components/_panel_logs.scss b/public/sass/components/_panel_logs.scss index bcf18025292..4782c730cdf 100644 --- a/public/sass/components/_panel_logs.scss +++ b/public/sass/components/_panel_logs.scss @@ -6,7 +6,7 @@ $column-horizontal-spacing: 10px; padding: $panel-padding; padding-top: 10px; border-radius: $border-radius; - margin: 2*$panel-margin 0; + margin: 2*$panel-margin 0 $panel-margin; border: $panel-border; flex-direction: column; } @@ -31,8 +31,7 @@ $column-horizontal-spacing: 10px; .logs-panel-meta { flex: 1; color: $text-color-weak; - // Align first line with controls labels - margin-top: -2px; + margin-bottom: 10px; min-width: 30%; display: flex; } @@ -40,7 +39,6 @@ $column-horizontal-spacing: 10px; .logs-panel-meta__item { margin-right: 1em; display: flex; - flex-direction: column; } .logs-panel-meta__label { From 3d0c54a946670eecf60c8bc6e101c2bcb464573c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 7 Dec 2018 12:13:23 -0800 Subject: [PATCH 290/432] logs style polish --- public/sass/_variables.light.scss | 6 +++--- public/sass/components/_toggle_button_group.scss | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 31f61a2c196..dd84b7af869 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -62,7 +62,7 @@ $critical: #ec2128; // ------------------------- $body-bg: $gray-7; -$page-bg: $gray-6; +$page-bg: $gray-7; $body-color: $gray-1; $text-color: $gray-1; $text-color-strong: $dark-2; @@ -402,6 +402,6 @@ $panel-grid-placeholder-shadow: 0 0 4px $blue-light; $logs-color-unkown: $gray-5; // toggle-group -$button-toggle-group-btn-active-bg: linear-gradient(90deg, $yellow, $red); -$button-toggle-group-btn-active-shadow: inset 0 0 4px $black; +$button-toggle-group-btn-active-bg: $brand-primary; +$button-toggle-group-btn-active-shadow: inset 0 0 4px $white; $button-toggle-group-btn-seperator-border: 1px solid $gray-6; diff --git a/public/sass/components/_toggle_button_group.scss b/public/sass/components/_toggle_button_group.scss index 73ebf61778e..ae1bb621d16 100644 --- a/public/sass/components/_toggle_button_group.scss +++ b/public/sass/components/_toggle_button_group.scss @@ -8,13 +8,13 @@ font-weight: $font-weight-semi-bold; font-size: $font-size-sm; border-radius: 0; - color: $text-color; border-right: $button-toggle-group-btn-seperator-border; &.active { background: $button-toggle-group-btn-active-bg; box-shadow: $button-toggle-group-btn-active-shadow; border-right: 0; + color: $white; &:hover { cursor: default; From 1bb535bbe34047ec9887677eb8f405cc3c69dee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Sat, 8 Dec 2018 12:54:36 +0100 Subject: [PATCH 291/432] minor style change --- .../dashboard/dashgrid/TimeRangeOptions.tsx | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx b/public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx index 8c6830cf1db..00371fa960b 100644 --- a/public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx +++ b/public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx @@ -1,4 +1,4 @@ -import React, { PureComponent } from 'react'; +import React, { PureComponent } from 'react'; import { Switch } from 'app/core/components/Switch/Switch'; import { Input } from 'app/core/components/Form'; import { isValidTimeSpan } from 'app/core/utils/rangeutil'; @@ -64,12 +64,7 @@ export class TimeRangeOptions extends PureComponent {
- - - - Override relative time - Last {
- - - Add time shift - Amount {
-
- - - -
From ee55a9c18be67bd3de8445183bdd160ed5f6803e Mon Sep 17 00:00:00 2001 From: ijin08 Date: Mon, 10 Dec 2018 11:59:02 +0100 Subject: [PATCH 292/432] removed side menu for column styles, added small header to column styles with a border --- .../plugins/panel/table/column_options.html | 332 +++++++++--------- public/sass/components/edit_sidemenu.scss | 4 + 2 files changed, 165 insertions(+), 171 deletions(-) diff --git a/public/app/plugins/panel/table/column_options.html b/public/app/plugins/panel/table/column_options.html index 6f9adb4ae0f..ffb6ea8e67f 100644 --- a/public/app/plugins/panel/table/column_options.html +++ b/public/app/plugins/panel/table/column_options.html @@ -1,200 +1,190 @@ -
- - -
- -
-
Options
-
-
- - +

{{style.pattern || 'New rule'}}

+
+
Options
+
+
+ + -
-
- - -
-
+
+ + +
+ +
-
-
Type
+
+
Type
-
- -
- -
+
+ +
+
-
- - +
+ + - -
+
+
-
- + -
-
- +
+ -
+
-
-
- -
-
-
- - +
+ +
+
+
+ + -
+
-
-
Value Mappings
-
-
-
- - Type - -
- -
-
-
-
- - - - - - -
-
- -
-
-
-
- - - - From - - To - - Text - -
-
- -
-
-
- -
-
Thresholds
-
- - -
-
- -
- -
-
-
- - - - - - - - - - -
- Invert -
-
-
- -
-
Link
-
-
-
-
+
+ +
+
Thresholds
+
+ + +
+
+ +
+ +
+
+
+ + + + + + + + + + +
+ Invert +
+
+
+ +
+
Link
+
+ + +
+
+ - + + + -
-
+ +
-
- +
+
-
-
-
+
+
+ +
diff --git a/public/sass/components/edit_sidemenu.scss b/public/sass/components/edit_sidemenu.scss index 87d90f18162..4fc6795c068 100644 --- a/public/sass/components/edit_sidemenu.scss +++ b/public/sass/components/edit_sidemenu.scss @@ -28,6 +28,10 @@ } } +.column-styles-heading { + border-bottom: 1px solid $gray-1; +} + @include media-breakpoint-down(sm) { .edit-tab-with-sidemenu { flex-direction: column; From 7e0bc8f30db34c92dcc9ba8cd525903b6504419b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 10 Dec 2018 12:02:27 +0100 Subject: [PATCH 293/432] fixed max height issue not being respected by react select dropdown --- public/app/core/components/Picker/ResetStyles.tsx | 6 ++++-- public/app/features/explore/Explore.tsx | 1 + public/sass/components/_form_select_box.scss | 5 +++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/public/app/core/components/Picker/ResetStyles.tsx b/public/app/core/components/Picker/ResetStyles.tsx index 2a235b2892a..c34abb544ab 100644 --- a/public/app/core/components/Picker/ResetStyles.tsx +++ b/public/app/core/components/Picker/ResetStyles.tsx @@ -1,4 +1,4 @@ -export default { +export default { clearIndicator: () => ({}), container: () => ({}), control: () => ({}), @@ -11,7 +11,9 @@ loadingIndicator: () => ({}), loadingMessage: () => ({}), menu: () => ({}), - menuList: () => ({}), + menuList: ({ maxHeight }: { maxHeight: number }) => ({ + maxHeight, + }), multiValue: () => ({}), multiValueLabel: () => ({}), multiValueRemove: () => ({}), diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index b395468313f..4cd8b8f91e0 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -913,6 +913,7 @@ export class Explore extends React.PureComponent { onChange={this.onChangeDatasource} options={exploreDatasources} styles={ResetStyles} + maxMenuHeight={500} placeholder="Select datasource" loadingMessage={() => 'Loading datasources...'} noOptionsMessage={() => 'No datasources found'} diff --git a/public/sass/components/_form_select_box.scss b/public/sass/components/_form_select_box.scss index 166baeb2f8f..47dcea3e96b 100644 --- a/public/sass/components/_form_select_box.scss +++ b/public/sass/components/_form_select_box.scss @@ -55,6 +55,11 @@ $select-input-bg-disabled: $input-bg-disabled; z-index: 2; } +.gf-form-select-box__menu-list { + overflow-y: auto; + max-height: 300px; +} + .tag-filter .gf-form-select-box__menu { width: 100%; } From 514c8a02106101e3a7f3a011b551a40c3d02d53f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 10 Dec 2018 12:20:14 +0100 Subject: [PATCH 294/432] fixed issue with grid responsive mode --- public/sass/components/_dashboard_grid.scss | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/public/sass/components/_dashboard_grid.scss b/public/sass/components/_dashboard_grid.scss index 6d3de55e96a..eeb01c86ab9 100644 --- a/public/sass/components/_dashboard_grid.scss +++ b/public/sass/components/_dashboard_grid.scss @@ -50,12 +50,8 @@ } .react-grid-item { - display: none; - transition-property: none !important; - } - - .panel { display: block !important; + transition-property: none !important; position: unset !important; width: 100% !important; transform: translate(0px, 0px) !important; From 0c5fd213275bc2acf75039212698ec20ac98fe21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 10 Dec 2018 14:07:20 +0100 Subject: [PATCH 295/432] fixed issue with colorpicker position above window, fixes #14412 --- public/app/core/components/colorpicker/SeriesColorPicker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/core/components/colorpicker/SeriesColorPicker.tsx b/public/app/core/components/colorpicker/SeriesColorPicker.tsx index d6feaa31965..32b7554e38d 100644 --- a/public/app/core/components/colorpicker/SeriesColorPicker.tsx +++ b/public/app/core/components/colorpicker/SeriesColorPicker.tsx @@ -44,7 +44,7 @@ export class SeriesColorPicker extends React.Component { const drop = new Drop({ target: this.pickerElem, content: dropContentElem, - position: 'top center', + position: 'bottom center', classes: 'drop-popover', openOn: 'hover', hoverCloseDelay: 200, From 8f26fe0fbb517b032510c9e7d115af152fac92f8 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Mon, 10 Dec 2018 14:25:03 +0100 Subject: [PATCH 296/432] fix time regions bugs --- .../graph/specs/time_region_manager.test.ts | 36 +++++++++++++++++++ .../panel/graph/time_region_manager.ts | 8 +++++ 2 files changed, 44 insertions(+) diff --git a/public/app/plugins/panel/graph/specs/time_region_manager.test.ts b/public/app/plugins/panel/graph/specs/time_region_manager.test.ts index 7ac9e658b83..fc0b86d1b68 100644 --- a/public/app/plugins/panel/graph/specs/time_region_manager.test.ts +++ b/public/app/plugins/panel/graph/specs/time_region_manager.test.ts @@ -238,6 +238,42 @@ describe('TimeRegionManager', () => { }); }); + plotOptionsScenario('for day of week from/to time region', ctx => { + const regions = [{ fromDayOfWeek: 7, from: '23:00', toDayOfWeek: 1, to: '01:40', fill: true, colorMode: 'red' }]; + const from = moment('2018-12-07T12:51:19+01:00'); + const to = moment('2018-12-10T13:51:29+01:00'); + ctx.setup(regions, from, to); + + it('should add 1 marking', () => { + expect(ctx.options.grid.markings.length).toBe(1); + }); + + it('should add one fill between sunday 23:00 and monday 01:40', () => { + const markings = ctx.options.grid.markings; + + expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-12-10T00:00:00+01:00').format()); + expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-12-10T02:40:00+01:00').format()); + }); + }); + + plotOptionsScenario('for day of week from/to time region', ctx => { + const regions = [{ fromDayOfWeek: 6, from: '03:00', toDayOfWeek: 7, to: '02:00', fill: true, colorMode: 'red' }]; + const from = moment('2018-12-07T12:51:19+01:00'); + const to = moment('2018-12-10T13:51:29+01:00'); + ctx.setup(regions, from, to); + + it('should add 1 marking', () => { + expect(ctx.options.grid.markings.length).toBe(1); + }); + + it('should add one fill between saturday 03:00 and sunday 02:00', () => { + const markings = ctx.options.grid.markings; + + expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-12-08T04:00:00+01:00').format()); + expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-12-09T03:00:00+01:00').format()); + }); + }); + plotOptionsScenario('for day of week from/to time region with daylight saving time', ctx => { const regions = [{ fromDayOfWeek: 7, from: '20:00', toDayOfWeek: 7, to: '23:00', fill: true, colorMode: 'red' }]; const from = moment('2018-03-17T06:00:00+01:00'); diff --git a/public/app/plugins/panel/graph/time_region_manager.ts b/public/app/plugins/panel/graph/time_region_manager.ts index 0c9b94c9013..e5ec27ee77a 100644 --- a/public/app/plugins/panel/graph/time_region_manager.ts +++ b/public/app/plugins/panel/graph/time_region_manager.ts @@ -169,8 +169,16 @@ export class TimeRegionManager { fromEnd.add(hRange.to.h - hRange.from.h, 'hours'); } else if (hRange.from.h + hRange.to.h < 23) { fromEnd.add(hRange.to.h, 'hours'); + + while (fromEnd.hour() !== hRange.to.h) { + fromEnd.add(-1, 'hours'); + } } else { fromEnd.add(24 - hRange.from.h, 'hours'); + + while (fromEnd.hour() !== hRange.to.h) { + fromEnd.add(1, 'hours'); + } } fromEnd.set('minute', hRange.to.m); From 49e756aa9331cafc58de9c2e737fdf7e2701039a Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 10 Dec 2018 14:25:29 +0100 Subject: [PATCH 297/432] use links instead of bridge network --- devenv/docker/blocks/elastic5/docker-compose.yaml | 11 +++++++---- devenv/docker/blocks/influxdb/docker-compose.yaml | 12 +++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/devenv/docker/blocks/elastic5/docker-compose.yaml b/devenv/docker/blocks/elastic5/docker-compose.yaml index 7148aa18c42..33a7d9855b0 100644 --- a/devenv/docker/blocks/elastic5/docker-compose.yaml +++ b/devenv/docker/blocks/elastic5/docker-compose.yaml @@ -1,15 +1,18 @@ # You need to run 'sysctl -w vm.max_map_count=262144' on the host machine - +version: '2' +services: elasticsearch5: image: elasticsearch:5 command: elasticsearch ports: - - "10200:9200" - - "10300:9300" + - '10200:9200' + - '10300:9300' fake-elastic5-data: image: grafana/fake-data-gen - network_mode: bridge + links: + - elasticsearch5 + # network_mode: bridge environment: FD_DATASOURCE: elasticsearch FD_PORT: 10200 diff --git a/devenv/docker/blocks/influxdb/docker-compose.yaml b/devenv/docker/blocks/influxdb/docker-compose.yaml index e1727807d41..e3ca81ced22 100644 --- a/devenv/docker/blocks/influxdb/docker-compose.yaml +++ b/devenv/docker/blocks/influxdb/docker-compose.yaml @@ -1,17 +1,19 @@ +version: '2' +services: influxdb: image: influxdb:latest container_name: influxdb ports: - - "2004:2004" - - "8083:8083" - - "8086:8086" + - '2004:2004' + - '8083:8083' + - '8086:8086' volumes: - ./docker/blocks/influxdb/influxdb.conf:/etc/influxdb/influxdb.conf fake-influxdb-data: image: grafana/fake-data-gen - network_mode: bridge + links: + - influxdb environment: FD_DATASOURCE: influxdb FD_PORT: 8086 - From ae5bc366c27bf6c6b788808a72fd0144a7215cda Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Mon, 10 Dec 2018 14:45:49 +0100 Subject: [PATCH 298/432] Start adding keyboard navigation to VizPicker --- .../dashboard/dashgrid/VizTypePicker.tsx | 76 ++++++++++++++----- .../dashgrid/VizTypePickerPlugin.tsx | 34 +++++++++ public/sass/components/_panel_editor.scss | 13 +++- 3 files changed, 105 insertions(+), 18 deletions(-) create mode 100644 public/app/features/dashboard/dashgrid/VizTypePickerPlugin.tsx diff --git a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx index fc5e19a9d5c..b185b9c7410 100644 --- a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx +++ b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx @@ -1,9 +1,9 @@ import React, { PureComponent } from 'react'; -import classNames from 'classnames'; import _ from 'lodash'; import config from 'app/core/config'; import { PanelPlugin } from 'app/types/plugins'; +import VizTypePickerPlugin from './VizTypePickerPlugin'; interface Props { current: PanelPlugin; @@ -12,6 +12,7 @@ interface Props { interface State { searchQuery: string; + selected: number; } export class VizTypePicker extends PureComponent { @@ -23,9 +24,56 @@ export class VizTypePicker extends PureComponent { this.state = { searchQuery: '', + selected: 0, }; } + get filteredPluginListCount() { + const filteredPluginList = this.getFilteredPluginList(); + return filteredPluginList.length; + } + + goRight = () => { + const maxArray = this.filteredPluginListCount - 1; + const nextIndex = this.state.selected >= maxArray ? 0 : this.state.selected + 1; + this.setState({ + selected: nextIndex, + }); + }; + + goLeft = () => { + const maxArray = this.filteredPluginListCount - 1; + const nextIndex = this.state.selected <= 0 ? maxArray : this.state.selected - 1; + this.setState({ + selected: nextIndex, + }); + }; + + onKeydown = (evt: KeyboardEvent) => { + if (evt.key === 'ArrowRight' || evt.key === 'ArrowDown') { + this.goRight(); + } + if (evt.key === 'ArrowLeft' || evt.key === 'ArrowUp') { + this.goLeft(); + } + if (evt.key === 'Enter') { + const filteredPluginList = this.getFilteredPluginList(); + this.props.onTypeChanged(filteredPluginList[this.state.selected]); + } + }; + + componentDidMount() { + setTimeout(() => { + this.searchInput.focus(); + }, 300); + + document.addEventListener('keydown', this.onKeydown); + } + + componentWillUnmount() { + document.removeEventListener('keydown', this.onKeydown); + } + getPanelPlugins(filter): PanelPlugin[] { const panels = _.chain(config.panels) .filter({ hideFromList: false }) @@ -37,25 +85,19 @@ export class VizTypePicker extends PureComponent { } renderVizPlugin = (plugin: PanelPlugin, index: number) => { - const cssClass = classNames({ - 'viz-picker__item': true, - 'viz-picker__item--selected': plugin.id === this.props.current.id, - }); - + const isSelected = this.state.selected === index; + const isCurrent = plugin.id === this.props.current.id; return ( -
this.props.onTypeChanged(plugin)} title={plugin.name}> -
{plugin.name}
- -
+ this.props.onTypeChanged(plugin)} + /> ); }; - componentDidMount() { - setTimeout(() => { - this.searchInput.focus(); - }, 300); - } - getFilteredPluginList = (): PanelPlugin[] => { const { searchQuery } = this.state; const regex = new RegExp(searchQuery, 'i'); @@ -73,6 +115,7 @@ export class VizTypePicker extends PureComponent { this.setState(prevState => ({ ...prevState, searchQuery: value, + selected: 0, })); }; @@ -102,7 +145,6 @@ export class VizTypePicker extends PureComponent { {this.renderFilters()}
-
{filteredPluginList.map(this.renderVizPlugin)}
); diff --git a/public/app/features/dashboard/dashgrid/VizTypePickerPlugin.tsx b/public/app/features/dashboard/dashgrid/VizTypePickerPlugin.tsx new file mode 100644 index 00000000000..534a0f3a756 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/VizTypePickerPlugin.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import classNames from 'classnames'; + +interface Props { + isSelected: boolean; + isCurrent: boolean; + plugin: any; + onClick: () => void; +} + +const VizTypePickerPlugin = React.memo( + ({ isSelected, isCurrent, plugin, onClick }: Props) => { + const cssClass = classNames({ + 'viz-picker__item': true, + 'viz-picker__item--selected': isSelected, + 'viz-picker__item--current': isCurrent, + }); + + return ( +
+
{plugin.name}
+ +
+ ); + }, + (prevProps, nextProps) => { + if (prevProps.isSelected === nextProps.isSelected && prevProps.isCurrent === nextProps.isCurrent) { + return true; + } + return false; + } +); + +export default VizTypePickerPlugin; diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index f2286991a9c..cd271133fc5 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -163,7 +163,7 @@ border: $panel-editor-viz-item-border-hover; } - &--selected { + &--current { box-shadow: 0 0 6px $orange; border: 1px solid $orange; @@ -173,6 +173,17 @@ background: $panel-editor-viz-item-bg-hover-active; } } + + &--selected { + box-shadow: 0 0 6px $purple; + border: 1px solid $purple; + + &:hover { + box-shadow: 0 0 6px $purple; + border: 1px solid $purple; + background: $panel-editor-viz-item-bg-hover-active; + } + } } .viz-picker__item-name { From 4a57d594e571ba3dbeec070c0df741b7a6ef37c5 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 10 Dec 2018 14:50:32 +0100 Subject: [PATCH 299/432] fixed styling --- .../ToggleButtonGroup/ToggleButtonGroup.tsx | 9 +- public/app/plugins/panel/gauge/MappingRow.tsx | 91 +++++++++++-------- .../app/plugins/panel/gauge/ValueMappings.tsx | 18 ++-- public/app/plugins/panel/gauge/module.tsx | 2 - public/sass/_grafana.scss | 1 + .../sass/components/_toggle_button_group.scss | 4 + public/sass/components/_value-mappings.scss | 30 ++++++ 7 files changed, 105 insertions(+), 50 deletions(-) create mode 100644 public/sass/components/_value-mappings.scss diff --git a/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx b/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx index 1e9ae4732df..b90e1d6a320 100644 --- a/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx +++ b/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx @@ -5,9 +5,14 @@ interface ToggleButtonGroupProps { value?: any; label?: string; render: (props) => void; + stackedButtons?: boolean; } export default class ToggleButtonGroup extends PureComponent { + static defaultProps = { + stackedButtons: false, + }; + getValues() { const { children } = this.props; return React.Children.toArray(children).map((c: ReactElement) => c.props.value); @@ -27,14 +32,14 @@ export default class ToggleButtonGroup extends PureComponent -
+
{label && } {this.props.render({ selectedValue, onChange: this.handleToggle.bind(this) })}
diff --git a/public/app/plugins/panel/gauge/MappingRow.tsx b/public/app/plugins/panel/gauge/MappingRow.tsx index 93d17c24692..507d0148379 100644 --- a/public/app/plugins/panel/gauge/MappingRow.tsx +++ b/public/app/plugins/panel/gauge/MappingRow.tsx @@ -68,18 +68,24 @@ export default class MappingRow extends PureComponent { const rangeMap = mapping as RangeMap; return ( -
-
- - +
+
+ +
+ +
-
- - +
+ +
+ +
-
- - +
+ +
+ +
); @@ -91,11 +97,15 @@ export default class MappingRow extends PureComponent {
- +
+ +
- +
+ +
); @@ -105,33 +115,36 @@ export default class MappingRow extends PureComponent { const { mappingType } = this.state; return ( -
- this.onMappingTypeChange(mappingType)} - value={mappingType} - render={({ selectedValue, onChange }) => { - return [ - - Value - , - - Range - , - ]; - }} - /> +
+
+ this.onMappingTypeChange(mappingType)} + value={mappingType} + stackedButtons={true} + render={({ selectedValue, onChange }) => { + return [ + + Value + , + + Range + , + ]; + }} + /> +
{this.renderRow()}
); diff --git a/public/app/plugins/panel/gauge/ValueMappings.tsx b/public/app/plugins/panel/gauge/ValueMappings.tsx index 897fd462a78..8ce41bdb83a 100644 --- a/public/app/plugins/panel/gauge/ValueMappings.tsx +++ b/public/app/plugins/panel/gauge/ValueMappings.tsx @@ -42,14 +42,18 @@ export default class ValueMappings extends PureComponent -
-
- {combinedMappings.length > 0 && - combinedMappings.map((mapping, index) => { - return ; - })} +
Value mappings
+
+ {combinedMappings.length > 0 && + combinedMappings.map((mapping, index) => { + return ; + })} +
+
+
+
-
Add mapping
+
Add mapping
); diff --git a/public/app/plugins/panel/gauge/module.tsx b/public/app/plugins/panel/gauge/module.tsx index 5a4b96b0647..ffe18cb9482 100644 --- a/public/app/plugins/panel/gauge/module.tsx +++ b/public/app/plugins/panel/gauge/module.tsx @@ -62,13 +62,11 @@ class Options extends PureComponent> { return (
-
Options
-
Value mappings
diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index 892a576fc87..1164d810099 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -106,6 +106,7 @@ @import 'components/unit-picker'; @import 'components/thresholds'; @import 'components/toggle_button_group'; +@import 'components/value-mappings'; // PAGES @import 'pages/login'; diff --git a/public/sass/components/_toggle_button_group.scss b/public/sass/components/_toggle_button_group.scss index ed701a489a9..2f657f91d7e 100644 --- a/public/sass/components/_toggle_button_group.scss +++ b/public/sass/components/_toggle_button_group.scss @@ -13,6 +13,10 @@ } } + &.stacked { + flex-direction: column; + } + .btn { background-color: $typeahead-selected-bg; border-radius: 0; diff --git a/public/sass/components/_value-mappings.scss b/public/sass/components/_value-mappings.scss new file mode 100644 index 00000000000..7f587672e5a --- /dev/null +++ b/public/sass/components/_value-mappings.scss @@ -0,0 +1,30 @@ +.mapping-row { + display: flex; + align-items: center; + margin-bottom: 15px; +} + +.mapping-row-type { + margin-right: 5px; +} + +.add-mapping-row { + display: flex; + overflow: hidden; + height: 37px; + cursor: pointer; +} + +.add-mapping-row-icon { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + background-color: $green; +} + +.add-mapping-row-label { + align-items: center; + display: flex; + padding: 5px 8px; +} From 856c0ee0526b89cd52629482b50e61702f8e8832 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Mon, 10 Dec 2018 15:19:14 +0100 Subject: [PATCH 300/432] Fix styling for vizPicker keyboard nav and change so only arrow up/down is OK to use --- .../dashboard/dashgrid/VizTypePicker.tsx | 15 ++++++++++-- .../dashgrid/VizTypePickerPlugin.tsx | 8 ++++--- public/sass/components/_panel_editor.scss | 23 +++---------------- 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx index b185b9c7410..abc810d2cd1 100644 --- a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx +++ b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx @@ -50,10 +50,12 @@ export class VizTypePicker extends PureComponent { }; onKeydown = (evt: KeyboardEvent) => { - if (evt.key === 'ArrowRight' || evt.key === 'ArrowDown') { + if (evt.key === 'ArrowDown') { + evt.preventDefault(); this.goRight(); } - if (evt.key === 'ArrowLeft' || evt.key === 'ArrowUp') { + if (evt.key === 'ArrowUp') { + evt.preventDefault(); this.goLeft(); } if (evt.key === 'Enter') { @@ -84,6 +86,12 @@ export class VizTypePicker extends PureComponent { return _.sortBy(panels, 'sort'); } + onMouseEnter = (mouseEnterIndex: number) => { + this.setState({ + selected: mouseEnterIndex, + }); + }; + renderVizPlugin = (plugin: PanelPlugin, index: number) => { const isSelected = this.state.selected === index; const isCurrent = plugin.id === this.props.current.id; @@ -93,6 +101,9 @@ export class VizTypePicker extends PureComponent { isSelected={isSelected} isCurrent={isCurrent} plugin={plugin} + onMouseEnter={() => { + this.onMouseEnter(index); + }} onClick={() => this.props.onTypeChanged(plugin)} /> ); diff --git a/public/app/features/dashboard/dashgrid/VizTypePickerPlugin.tsx b/public/app/features/dashboard/dashgrid/VizTypePickerPlugin.tsx index 534a0f3a756..d4ed96d1434 100644 --- a/public/app/features/dashboard/dashgrid/VizTypePickerPlugin.tsx +++ b/public/app/features/dashboard/dashgrid/VizTypePickerPlugin.tsx @@ -1,15 +1,17 @@ import React from 'react'; import classNames from 'classnames'; +import { PanelPlugin } from 'app/types/plugins'; interface Props { isSelected: boolean; isCurrent: boolean; - plugin: any; + plugin: PanelPlugin; onClick: () => void; + onMouseEnter: () => void; } const VizTypePickerPlugin = React.memo( - ({ isSelected, isCurrent, plugin, onClick }: Props) => { + ({ isSelected, isCurrent, plugin, onClick, onMouseEnter }: Props) => { const cssClass = classNames({ 'viz-picker__item': true, 'viz-picker__item--selected': isSelected, @@ -17,7 +19,7 @@ const VizTypePickerPlugin = React.memo( }); return ( -
+
{plugin.name}
diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index cd271133fc5..4824b3dd238 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -157,32 +157,15 @@ padding-bottom: 6px; transition: transform 1 ease; - &:hover { - box-shadow: $panel-editor-viz-item-shadow-hover; - background: $panel-editor-viz-item-bg-hover; - border: $panel-editor-viz-item-border-hover; - } - &--current { box-shadow: 0 0 6px $orange; border: 1px solid $orange; - - &:hover { - box-shadow: 0 0 6px $orange; - border: 1px solid $orange; - background: $panel-editor-viz-item-bg-hover-active; - } } &--selected { - box-shadow: 0 0 6px $purple; - border: 1px solid $purple; - - &:hover { - box-shadow: 0 0 6px $purple; - border: 1px solid $purple; - background: $panel-editor-viz-item-bg-hover-active; - } + box-shadow: $panel-editor-viz-item-shadow-hover; + background: $panel-editor-viz-item-bg-hover; + border: $panel-editor-viz-item-border-hover; } } From cdcc3163107e4a2fe1bd0b0727d0bfaf944b6972 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Mon, 10 Dec 2018 15:27:22 +0100 Subject: [PATCH 301/432] Variable rename. Did not make sense at all. --- .../app/features/dashboard/dashgrid/VizTypePicker.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx index abc810d2cd1..bc1643abbd7 100644 --- a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx +++ b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx @@ -28,22 +28,20 @@ export class VizTypePicker extends PureComponent { }; } - get filteredPluginListCount() { + get maxSelectedIndex() { const filteredPluginList = this.getFilteredPluginList(); - return filteredPluginList.length; + return filteredPluginList.length - 1; } goRight = () => { - const maxArray = this.filteredPluginListCount - 1; - const nextIndex = this.state.selected >= maxArray ? 0 : this.state.selected + 1; + const nextIndex = this.state.selected >= this.maxSelectedIndex ? 0 : this.state.selected + 1; this.setState({ selected: nextIndex, }); }; goLeft = () => { - const maxArray = this.filteredPluginListCount - 1; - const nextIndex = this.state.selected <= 0 ? maxArray : this.state.selected - 1; + const nextIndex = this.state.selected <= 0 ? this.maxSelectedIndex : this.state.selected - 1; this.setState({ selected: nextIndex, }); From 6c3f9effd4121a325f7f66490d552df0f1025a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 10 Dec 2018 15:40:43 +0100 Subject: [PATCH 302/432] Update CHANGELOG.md --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1299304ec6..294c5eb1834 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,24 @@ * **Dataproxy**: Override incoming Authorization header [#13815](https://github.com/grafana/grafana/issues/13815), thx [@kornholi](https://github.com/kornholi) * **Admin**: Fix prevent removing last grafana admin permissions [#11067](https://github.com/grafana/grafana/issues/11067), thx [@danielbh](https://github.com/danielbh) +# 5.4.1 (2018-12-10) + +* **Stackdriver**: Fixes issue with data proxy and Authorization header [#14262](https://github.com/grafana/grafana/issues/14262) +* **Units**: fixedUnit for Flow:l/min and mL/min [#14294](https://github.com/grafana/grafana/issues/14294), thx [@flopp999](https://github.com/flopp999). +* **Logging**: Fix for issue where data proxy logged a secret when debug logging was enabled, now redacted. [#14319](https://github.com/grafana/grafana/issues/14319) +* **InfluxDB**: Add support for alerting on InfluxDB queries that use the cumulative_sum function. [#14314](https://github.com/grafana/grafana/pull/14314), thx [@nitti](https://github.com/nitti) +* **Plugins**: Panel plugins should no receive the panel-initialized event again as usual. +* **Embedded Graphs**: Iframe graph panels should now work as usual. [#14284](https://github.com/grafana/grafana/issues/14284) +* **Postgres**: Improve PostgreSQL Query Editor if using different Schemas, [#14313]( +https://github.com/grafana/grafana/pull/14313) +* **Quotas**: Fixed for updating org & user quotas. [#14347](https://github.com/grafana/grafana/pull/14347), thx [#moznion](https://github.com/moznion) +* **Cloudwatch**: Add the AWS/SES Cloudwatch metrics of BounceRate and ComplaintRate to auto complete list. [#14401](https://github.com/grafana/grafana/pull/14401), thx [@sglajchEG](https://github.com/sglajchEG) +* **Dashboard Search**: Fixed filtering by tag issues. +* **Graph**: Fixed time region issues, [#14425](https://github.com/grafana/grafana/issues/14425), [#14280](https://github.com/grafana/grafana/issues/14280) +* **Graph**: Fixed issue with series color picker popover being placed outside window. + + + # 5.4.0 (2018-12-03) * **Cloudwatch**: Fix invalid time range causes segmentation fault [#14150](https://github.com/grafana/grafana/issues/14150) From 6ac8f5c7a5c414b2dfff154e9f002a1d51260aaa Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Mon, 10 Dec 2018 15:51:41 +0100 Subject: [PATCH 303/432] Unmount component when fading out to reset its state, such as search.. --- public/app/core/components/Animations/FadeIn.tsx | 3 ++- public/app/features/dashboard/dashgrid/EditorTabBody.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/public/app/core/components/Animations/FadeIn.tsx b/public/app/core/components/Animations/FadeIn.tsx index 98eb3b682c0..e12f22486f1 100644 --- a/public/app/core/components/Animations/FadeIn.tsx +++ b/public/app/core/components/Animations/FadeIn.tsx @@ -5,6 +5,7 @@ interface Props { duration: number; children: JSX.Element; in: boolean; + unmountOnExit?: boolean; } export const FadeIn: SFC = props => { @@ -21,7 +22,7 @@ export const FadeIn: SFC = props => { }; return ( - + {state => (
{
- +
{openView && this.renderOpenView(openView)}
From b380f7413147c517a46556d964ba626001cf1a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 10 Dec 2018 16:39:54 +0100 Subject: [PATCH 304/432] updated publish script --- packaging/publish/publish_both.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packaging/publish/publish_both.sh b/packaging/publish/publish_both.sh index cbe3918bf38..b1d480567e9 100755 --- a/packaging/publish/publish_both.sh +++ b/packaging/publish/publish_both.sh @@ -1,7 +1,7 @@ #! /usr/bin/env bash -version=5.0.2 +version=5.4.1 -wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_${version}_amd64.deb +wget https://dl.grafana.com/oss/release/grafana_${version}_amd64.deb package_cloud push grafana/stable/debian/jessie grafana_${version}_amd64.deb package_cloud push grafana/stable/debian/wheezy grafana_${version}_amd64.deb @@ -11,7 +11,7 @@ package_cloud push grafana/testing/debian/jessie grafana_${version}_amd64.deb package_cloud push grafana/testing/debian/wheezy grafana_${version}_amd64.deb --verbose package_cloud push grafana/testing/debian/stretch grafana_${version}_amd64.deb --verbose -wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-${version}-1.x86_64.rpm +wget https://dl.grafana.com/release/grafana-${version}-1.x86_64.rpm package_cloud push grafana/testing/el/6 grafana-${version}-1.x86_64.rpm --verbose package_cloud push grafana/testing/el/7 grafana-${version}-1.x86_64.rpm --verbose From 76f296e29948b562dee3fd9c50035e9dc2e42799 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 10 Dec 2018 16:57:34 +0100 Subject: [PATCH 305/432] fixed typings and remove --- public/app/plugins/panel/gauge/MappingRow.tsx | 69 ++++++++++++++----- .../app/plugins/panel/gauge/ValueMappings.tsx | 54 ++++++++++----- public/app/plugins/panel/gauge/module.tsx | 3 +- public/app/types/index.ts | 3 +- public/app/types/panel.ts | 6 ++ 5 files changed, 95 insertions(+), 40 deletions(-) diff --git a/public/app/plugins/panel/gauge/MappingRow.tsx b/public/app/plugins/panel/gauge/MappingRow.tsx index 507d0148379..4dee7f81be9 100644 --- a/public/app/plugins/panel/gauge/MappingRow.tsx +++ b/public/app/plugins/panel/gauge/MappingRow.tsx @@ -1,21 +1,16 @@ import React, { PureComponent } from 'react'; import { Label } from 'app/core/components/Label/Label'; import ToggleButtonGroup, { ToggleButton } from 'app/core/components/ToggleButtonGroup/ToggleButtonGroup'; -import { RangeMap, ValueMap } from 'app/types'; - -enum MappingType { - ValueToText = 1, - RangeToText = 2, -} +import { MappingType, RangeMap, ValueMap } from 'app/types'; interface Props { mapping: ValueMap | RangeMap; updateMapping: (mapping) => void; + removeMapping: () => void; } interface State { mapping: ValueMap | RangeMap; - mappingType: MappingType; } export default class MappingRow extends PureComponent { @@ -23,7 +18,6 @@ export default class MappingRow extends PureComponent { super(props); this.state = { - mappingType: MappingType.ValueToText, mapping: props.mapping, }; } @@ -59,12 +53,23 @@ export default class MappingRow extends PureComponent { this.setState({ mapping: updatedMapping }); }; - onMappingTypeChange = mappingType => this.setState({ mappingType }); + updateMapping = () => { + const { mapping } = this.state; + + this.props.updateMapping(mapping); + }; + + onMappingTypeChange = mappingType => { + const { mapping } = this.state; + + const updatedMapping = { ...mapping, type: mappingType }; + this.setState({ mapping: updatedMapping }); + }; renderRow() { - const { mapping, mappingType } = this.state; + const { mapping } = this.state; - if (mappingType === MappingType.RangeToText) { + if (mapping.type === MappingType.RangeToText) { const rangeMap = mapping as RangeMap; return ( @@ -72,19 +77,34 @@ export default class MappingRow extends PureComponent {
- +
- +
- +
@@ -98,13 +118,23 @@ export default class MappingRow extends PureComponent {
- +
- +
@@ -112,14 +142,14 @@ export default class MappingRow extends PureComponent { } render() { - const { mappingType } = this.state; + const { mapping } = this.state; return (
this.onMappingTypeChange(mappingType)} - value={mappingType} + value={mapping.type} stackedButtons={true} render={({ selectedValue, onChange }) => { return [ @@ -146,6 +176,9 @@ export default class MappingRow extends PureComponent { />
{this.renderRow()}
+
+ +
); } diff --git a/public/app/plugins/panel/gauge/ValueMappings.tsx b/public/app/plugins/panel/gauge/ValueMappings.tsx index 8ce41bdb83a..58b257e386a 100644 --- a/public/app/plugins/panel/gauge/ValueMappings.tsx +++ b/public/app/plugins/panel/gauge/ValueMappings.tsx @@ -1,12 +1,10 @@ import React, { PureComponent } from 'react'; import MappingRow from './MappingRow'; import { OptionModuleProps } from './module'; -import { RangeMap, ValueMap } from 'app/types'; +import { MappingType, RangeMap, ValueMap } from 'app/types'; interface State { - combinedMappings: any[]; - valueMaps: ValueMap[]; - rangeMaps: RangeMap[]; + mappings: Array; } export default class ValueMappings extends PureComponent { @@ -14,39 +12,57 @@ export default class ValueMappings extends PureComponent this.setState(prevState => ({ - combinedMappings: [...prevState.combinedMappings, { op: '', value: '', text: '' }], + mappings: [ + ...prevState.mappings, + { op: '', value: '', text: '', type: MappingType.ValueToText, from: '', to: '' }, + ], + })); + + onRemoveMapping = index => + this.setState(prevState => ({ + mappings: prevState.mappings.filter((m, i) => i !== index), })); updateGauge = mapping => { - this.setState(prevState => ({ - combinedMappings: prevState.combinedMappings.map(m => { - if (m === mapping) { - return { ...mapping }; - } + this.setState( + prevState => ({ + mappings: prevState.mappings.map(m => { + if (m === mapping) { + return { ...mapping }; + } - return m; + return m; + }), }), - })); + () => { + this.props.onChange({ ...this.props.options, mappings: this.state.mappings }); + } + ); }; render() { - const { combinedMappings } = this.state; + const { mappings } = this.state; return (
Value mappings
- {combinedMappings.length > 0 && - combinedMappings.map((mapping, index) => { - return ; + {mappings.length > 0 && + mappings.map((mapping, index) => { + return ( + this.onRemoveMapping(index)} + /> + ); })}
diff --git a/public/app/plugins/panel/gauge/module.tsx b/public/app/plugins/panel/gauge/module.tsx index ffe18cb9482..b04cc4d72f9 100644 --- a/public/app/plugins/panel/gauge/module.tsx +++ b/public/app/plugins/panel/gauge/module.tsx @@ -16,8 +16,7 @@ export interface OptionsProps { suffix: string; unit: string; thresholds: Threshold[]; - valueMaps: ValueMap[]; - rangeMaps: RangeMap[]; + mappings: Array; mappingType: number; } diff --git a/public/app/types/index.ts b/public/app/types/index.ts index 89d1f3a79a1..ed883bfd1c7 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -21,7 +21,7 @@ import { DataQueryOptions, IntervalValues, } from './series'; -import { PanelProps, PanelOptionsProps, RangeMap, Threshold, ValueMap } from './panel'; +import { MappingType, PanelProps, PanelOptionsProps, RangeMap, Threshold, ValueMap } from './panel'; import { PluginDashboard, PluginMeta, Plugin, PanelPlugin, PluginsState } from './plugins'; import { Organization, OrganizationState } from './organization'; import { @@ -96,6 +96,7 @@ export { ValueMap, RangeMap, IntervalValues, + MappingType, }; export interface StoreState { diff --git a/public/app/types/panel.ts b/public/app/types/panel.ts index 0736ae47acb..93e908db302 100644 --- a/public/app/types/panel.ts +++ b/public/app/types/panel.ts @@ -37,9 +37,15 @@ export interface Threshold { canRemove: boolean; } +export enum MappingType { + ValueToText = 1, + RangeToText = 2, +} + interface BaseMap { op: string; text: string; + type: MappingType; } export interface ValueMap extends BaseMap { From 71429d721059f1a317b8342c7412837570c33779 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Mon, 10 Dec 2018 10:05:57 -0800 Subject: [PATCH 306/432] Explore: Improved line parsing for logging - changed log parser API to be more semantic: getting fields, getting values - regexps become an implementation detail of parser and are no longer part of the API - improved JSON parser to not be tripped by nested quotation marks - improved JSON parser to support numbers - added more tests --- public/app/core/logs_model.ts | 52 ++++++++++++++++++++---- public/app/core/specs/logs_model.test.ts | 45 +++++++++++++++----- public/app/features/explore/Logs.tsx | 29 +++++-------- 3 files changed, 90 insertions(+), 36 deletions(-) diff --git a/public/app/core/logs_model.ts b/public/app/core/logs_model.ts index 934b950be72..650edfaca4f 100644 --- a/public/app/core/logs_model.ts +++ b/public/app/core/logs_model.ts @@ -108,11 +108,21 @@ export interface LogsParser { * Used to filter rows, and first capture group contains the value. */ buildMatcher: (label: string) => RegExp; + /** - * Regex to find a field in the log line. - * First capture group contains the label value, second capture group the value. + * Returns all parsable substrings from a line, used for highlighting */ - fieldRegex: RegExp; + getFields: (line: string) => string[]; + + /** + * Gets the label name from a parsable substring of a line + */ + getLabelFromField: (field: string) => string; + + /** + * Gets the label value from a parsable substring of a line + */ + getValueFromField: (field: string) => string; /** * Function to verify if this is a valid parser for the given line. * The parser accepts the line unless it returns undefined. @@ -120,20 +130,48 @@ export interface LogsParser { test: (line: string) => any; } +const LOGFMT_REGEXP = /(?:^|\s)(\w+)=("[^"]*"|\S+)/; + export const LogsParsers: { [name: string]: LogsParser } = { JSON: { - buildMatcher: label => new RegExp(`(?:{|,)\\s*"${label}"\\s*:\\s*"([^"]*)"`), - fieldRegex: /"(\w+)"\s*:\s*"([^"]*)"/, + buildMatcher: label => new RegExp(`(?:{|,)\\s*"${label}"\\s*:\\s*"?([\\d\\.]+|[^"]*)"?`), + getFields: line => { + const fields = []; + try { + const parsed = JSON.parse(line); + _.map(parsed, (value, key) => { + const fieldMatcher = new RegExp(`"${key}"\\s*:\\s*"?${_.escapeRegExp(JSON.stringify(value))}"?`); + + const match = line.match(fieldMatcher); + if (match) { + fields.push(match[0]); + } + }); + } catch {} + return fields; + }, + getLabelFromField: field => (field.match(/^"(\w+)"\s*:/) || [])[1], + getValueFromField: field => (field.match(/:\s*(.*)$/) || [])[1], test: line => { try { return JSON.parse(line); } catch (error) {} }, }, + logfmt: { buildMatcher: label => new RegExp(`(?:^|\\s)${label}=("[^"]*"|\\S+)`), - fieldRegex: /(?:^|\s)(\w+)=("[^"]*"|\S+)/, - test: line => LogsParsers.logfmt.fieldRegex.test(line), + getFields: line => { + const fields = []; + line.replace(new RegExp(LOGFMT_REGEXP, 'g'), substring => { + fields.push(substring.trim()); + return ''; + }); + return fields; + }, + getLabelFromField: field => (field.match(LOGFMT_REGEXP) || [])[1], + getValueFromField: field => (field.match(LOGFMT_REGEXP) || [])[2], + test: line => LOGFMT_REGEXP.test(line), }, }; diff --git a/public/app/core/specs/logs_model.test.ts b/public/app/core/specs/logs_model.test.ts index 85f75b50ed0..c59d18e8b7d 100644 --- a/public/app/core/specs/logs_model.test.ts +++ b/public/app/core/specs/logs_model.test.ts @@ -240,11 +240,16 @@ describe('LogsParsers', () => { expect(parser.test('foo=bar')).toBeTruthy(); }); - test('should have a valid fieldRegex', () => { - const match = 'foo=bar'.match(parser.fieldRegex); - expect(match).toBeDefined(); - expect(match[1]).toBe('foo'); - expect(match[2]).toBe('bar'); + test('should return parsed fields', () => { + expect(parser.getFields('foo=bar baz="42 + 1"')).toEqual(['foo=bar', 'baz="42 + 1"']); + }); + + test('should return label for field', () => { + expect(parser.getLabelFromField('foo=bar')).toBe('foo'); + }); + + test('should return value for field', () => { + expect(parser.getValueFromField('foo=bar')).toBe('bar'); }); test('should build a valid value matcher', () => { @@ -263,18 +268,36 @@ describe('LogsParsers', () => { expect(parser.test('{"foo":"bar"}')).toBeTruthy(); }); - test('should have a valid fieldRegex', () => { - const match = '{"foo":"bar"}'.match(parser.fieldRegex); - expect(match).toBeDefined(); - expect(match[1]).toBe('foo'); - expect(match[2]).toBe('bar'); + test('should return parsed fields', () => { + expect(parser.getFields('{ "foo" : "bar", "baz" : 42 }')).toEqual(['"foo" : "bar"', '"baz" : 42']); }); - test('should build a valid value matcher', () => { + test('should return parsed fields for nested quotes', () => { + expect(parser.getFields(`{"foo":"bar: '[value=\\"42\\"]'"}`)).toEqual([`"foo":"bar: '[value=\\"42\\"]'"`]); + }); + + test('should return label for field', () => { + expect(parser.getLabelFromField('"foo" : "bar"')).toBe('foo'); + }); + + test('should return value for field', () => { + expect(parser.getValueFromField('"foo" : "bar"')).toBe('"bar"'); + expect(parser.getValueFromField('"foo" : 42')).toBe('42'); + expect(parser.getValueFromField('"foo" : 42.1')).toBe('42.1'); + }); + + test('should build a valid value matcher for strings', () => { const matcher = parser.buildMatcher('foo'); const match = '{"foo":"bar"}'.match(matcher); expect(match).toBeDefined(); expect(match[1]).toBe('bar'); }); + + test('should build a valid value matcher for integers', () => { + const matcher = parser.buildMatcher('foo'); + const match = '{"foo":42.1}'.match(matcher); + expect(match).toBeDefined(); + expect(match[1]).toBe('42.1'); + }); }); }); diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index 96d49bf8da4..4d2962fa275 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -73,7 +73,7 @@ interface RowState { fieldStats: LogsLabelStat[]; fieldValue: string; parsed: boolean; - parser: LogsParser; + parser?: LogsParser; parsedFieldHighlights: string[]; showFieldStats: boolean; } @@ -94,7 +94,7 @@ class Row extends PureComponent { fieldStats: null, fieldValue: null, parsed: false, - parser: null, + parser: undefined, parsedFieldHighlights: [], showFieldStats: false, }; @@ -110,19 +110,16 @@ class Row extends PureComponent { onClickHighlight = (fieldText: string) => { const { getRows } = this.props; const { parser } = this.state; + const allRows = getRows(); - const fieldMatch = fieldText.match(parser.fieldRegex); - if (fieldMatch) { - const allRows = getRows(); - // Build value-agnostic row matcher based on the field label - const fieldLabel = fieldMatch[1]; - const fieldValue = fieldMatch[2]; - const matcher = parser.buildMatcher(fieldLabel); - const fieldStats = calculateFieldStats(allRows, matcher); - const fieldCount = fieldStats.reduce((sum, stat) => sum + stat.count, 0); + // Build value-agnostic row matcher based on the field label + const fieldLabel = parser.getLabelFromField(fieldText); + const fieldValue = parser.getValueFromField(fieldText); + const matcher = parser.buildMatcher(fieldLabel); + const fieldStats = calculateFieldStats(allRows, matcher); + const fieldCount = fieldStats.reduce((sum, stat) => sum + stat.count, 0); - this.setState({ fieldCount, fieldLabel, fieldStats, fieldValue, showFieldStats: true }); - } + this.setState({ fieldCount, fieldLabel, fieldStats, fieldValue, showFieldStats: true }); }; onMouseOverMessage = () => { @@ -141,11 +138,7 @@ class Row extends PureComponent { const parser = getParser(row.entry); if (parser) { // Use parser to highlight detected fields - const parsedFieldHighlights = []; - this.props.row.entry.replace(new RegExp(parser.fieldRegex, 'g'), substring => { - parsedFieldHighlights.push(substring.trim()); - return ''; - }); + const parsedFieldHighlights = parser.getFields(this.props.row.entry); this.setState({ parsedFieldHighlights, parsed: true, parser }); } } From 1ffac5a33dc8327d1588796411072b0ef0e98668 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Mon, 10 Dec 2018 21:38:07 +0100 Subject: [PATCH 307/432] Use react's onKeyDown event on the input instead of event listener on document --- public/app/features/dashboard/dashgrid/VizTypePicker.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx index bc1643abbd7..eff51ada020 100644 --- a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx +++ b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx @@ -47,7 +47,7 @@ export class VizTypePicker extends PureComponent { }); }; - onKeydown = (evt: KeyboardEvent) => { + onKeyDown = evt => { if (evt.key === 'ArrowDown') { evt.preventDefault(); this.goRight(); @@ -66,12 +66,6 @@ export class VizTypePicker extends PureComponent { setTimeout(() => { this.searchInput.focus(); }, 300); - - document.addEventListener('keydown', this.onKeydown); - } - - componentWillUnmount() { - document.removeEventListener('keydown', this.onKeydown); } getPanelPlugins(filter): PanelPlugin[] { @@ -138,6 +132,7 @@ export class VizTypePicker extends PureComponent { placeholder="" ref={elem => (this.searchInput = elem)} onChange={this.onSearchQueryChange} + onKeyDown={this.onKeyDown} /> From 20134c902b007b37ad96feb0b7a928a681eb0327 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Mon, 10 Dec 2018 21:42:53 +0100 Subject: [PATCH 308/432] Add keyboard navigation to datasource picker via a hoc. --- .../dashboard/dashgrid/DataSourcePicker.tsx | 179 ++++++++++-------- .../dashgrid/withKeyboardNavigation.tsx | 65 +++++++ public/sass/components/_panel_editor.scss | 4 +- 3 files changed, 170 insertions(+), 78 deletions(-) create mode 100644 public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx diff --git a/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx b/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx index 9a3923a09f2..2c33474ee73 100644 --- a/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx +++ b/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx @@ -1,97 +1,124 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; import _ from 'lodash'; - +import withKeyboardNavigation from './withKeyboardNavigation'; import { DataSourceSelectItem } from 'app/types'; -interface Props { +export interface Props { onChangeDataSource: (ds: any) => void; datasources: DataSourceSelectItem[]; + selected?: number; + onKeyDown?: (evt: any, maxSelectedIndex: number, onEnterAction: () => void) => void; + onMouseEnter?: (select: number) => void; } interface State { searchQuery: string; } -export class DataSourcePicker extends PureComponent { - searchInput: HTMLElement; +export const DataSourcePicker = withKeyboardNavigation( + class DataSourcePicker extends PureComponent { + searchInput: HTMLElement; - constructor(props) { - super(props); - this.state = { - searchQuery: '', - }; - } + constructor(props) { + super(props); + this.state = { + searchQuery: '', + }; + } - getDataSources() { - const { searchQuery } = this.state; - const regex = new RegExp(searchQuery, 'i'); - const { datasources } = this.props; + getDataSources() { + const { searchQuery } = this.state; + const regex = new RegExp(searchQuery, 'i'); + const { datasources } = this.props; - const filtered = datasources.filter(item => { - return regex.test(item.name) || regex.test(item.meta.name); - }); + const filtered = datasources.filter(item => { + return regex.test(item.name) || regex.test(item.meta.name); + }); - return filtered; - } + return filtered; + } - renderDataSource = (ds: DataSourceSelectItem, index: number) => { - const { onChangeDataSource } = this.props; - const onClick = () => onChangeDataSource(ds); - const cssClass = classNames({ - 'ds-picker-list__item': true, - }); + get maxSelectedIndex() { + const filtered = this.getDataSources(); + return filtered.length - 1; + } - return ( -
- -
{ds.name}
-
- ); - }; - - componentDidMount() { - setTimeout(() => { - this.searchInput.focus(); - }, 300); - } - - onSearchQueryChange = evt => { - const value = evt.target.value; - this.setState(prevState => ({ - ...prevState, - searchQuery: value, - })); - }; - - renderFilters() { - const { searchQuery } = this.state; - return ( - <> - - - ); - } - - render() { - return ( - <> -
- {this.renderFilters()} -
+ renderDataSource = (ds: DataSourceSelectItem, index: number) => { + const { onChangeDataSource, selected, onMouseEnter } = this.props; + const onClick = () => onChangeDataSource(ds); + const isSelected = selected === index; + const cssClass = classNames({ + 'ds-picker-list__item': true, + 'ds-picker-list__item--selected': isSelected, + }); + return ( +
onMouseEnter(index)} + > + +
{ds.name}
-
{this.getDataSources().map(this.renderDataSource)}
- - ); + ); + }; + + componentDidMount() { + setTimeout(() => { + this.searchInput.focus(); + }, 300); + } + + onSearchQueryChange = evt => { + const value = evt.target.value; + this.setState(prevState => ({ + ...prevState, + searchQuery: value, + })); + }; + + renderFilters() { + const { searchQuery } = this.state; + const { onKeyDown } = this.props; + return ( + <> + + + ); + } + + render() { + return ( + <> +
+ {this.renderFilters()} +
+
+
{this.getDataSources().map(this.renderDataSource)}
+ + ); + } } -} +); + +export default DataSourcePicker; diff --git a/public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx b/public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx new file mode 100644 index 00000000000..58affdf0471 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { Props } from './DataSourcePicker'; + +interface State { + selected: number; +} + +const withKeyboardNavigation = WrappedComponent => { + return class extends React.Component { + constructor(props) { + super(props); + + this.state = { + selected: 0, + }; + } + + goToNext = (maxSelectedIndex: number) => { + const nextIndex = this.state.selected >= maxSelectedIndex ? 0 : this.state.selected + 1; + this.setState({ + selected: nextIndex, + }); + }; + + goToPrev = (maxSelectedIndex: number) => { + const nextIndex = this.state.selected <= 0 ? maxSelectedIndex : this.state.selected - 1; + this.setState({ + selected: nextIndex, + }); + }; + + onKeyDown = (evt: KeyboardEvent, maxSelectedIndex: number, onEnterAction: any) => { + if (evt.key === 'ArrowDown') { + evt.preventDefault(); + this.goToNext(maxSelectedIndex); + } + if (evt.key === 'ArrowUp') { + evt.preventDefault(); + this.goToPrev(maxSelectedIndex); + } + if (evt.key === 'Enter' && onEnterAction) { + onEnterAction(); + } + }; + + onMouseEnter = (mouseEnterIndex: number) => { + this.setState({ + selected: mouseEnterIndex, + }); + }; + + render() { + return ( + + ); + } + }; +}; + +export default withKeyboardNavigation; diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 4824b3dd238..ae480c5a3d3 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -257,13 +257,13 @@ align-items: center; height: 44px; - &:hover { + &--selected { background: $panel-editor-viz-item-bg-hover; border: $panel-editor-viz-item-border-hover; box-shadow: $panel-editor-viz-item-shadow-hover; } - &--selected { + &--active { box-shadow: 0 0 6px $orange; border: 1px solid $orange; From a2f4775b8d96b9d9f10bc769a0da483223a32c55 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 5 Dec 2018 09:31:17 +0100 Subject: [PATCH 309/432] docs: explore Documentation for the new Explore feature. Describes both the Prometheus-specific features and the Loki (logging) features. --- docs/sources/features/explore/index.md | 160 +++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 docs/sources/features/explore/index.md diff --git a/docs/sources/features/explore/index.md b/docs/sources/features/explore/index.md new file mode 100644 index 00000000000..636e513a2f8 --- /dev/null +++ b/docs/sources/features/explore/index.md @@ -0,0 +1,160 @@ ++++ +title = "Explore" +type = "docs" +[menu.docs] +name = "Explore" +identifier = "explore" +parent = "features" +weight = 5 ++++ + +# Introduction + +One of the major new features of Grafana 6.0 is the new query-focused Explore workflow for troubleshooting and/or for data exploration. + +Grafana's dashboard UI is all about building dashboards for visualization. Explore strips away all the dashboard and panel options so that you can focus on the query. Iterate until you have a working query and then think about building a dashboard. + +For infrastructure monitoring and incident response, you no longer need to switch to other tools to debug what went wrong. Explore allows you to dig deeper into your metrics and logs to find the cause. Grafana's new logging datasource, [Loki](https://github.com/grafana/loki) is tightly integrated into Explore and allows you to correlate metrics and logs by viewing them side-by-side. This creates a new debugging workflow where you can: + +1. Receive an alert +2. Drill down and examine metrics +3. Drill down again and search logs related to the metric and time interval (and in the future, distributed traces). + +If you just want to explore your data and do not want to create a dashboard then Explore makes this much easier. Explore will show the results as both a graph and a table enabling you to see trends in the data and more detail at the same time (if the datasource supports both graph and table data). + +## Turning the Explore Feature On + +Explore will be officially released in Grafana 6.0. It is however already in the latest nightly builds of Grafana and can be turned using a feature flag in the config file. Restart Grafana after making the config file change. + +```ini +[explore] +# Enable the Explore section +enabled = true +``` + +Or if using docker: + +```bash +docker pull grafana/grafana:master +docker run --name grafana -p 3000:3000 -e "GF_EXPLORE_ENABLED=true" grafana/grafana:master +``` + +## How to Start Exploring + +There is a new Explore icon on the menu bar to the left. This opens a new empty Explore tab. + +{{< docs-imagebox img="/img/docs/v60/explore_menu.png" class="docs-image--no-shadow" caption="Screenshot of the new Explore Icon" >}} + +If you want to start with an existing query in a panel then choose the Explore option from the Panel menu. This opens an Explore tab with the query from the panel and allows you to tweak or iterate in the query outside of your dashboard. + +{{< docs-imagebox img="/img/docs/v60/explore_panel_menu.png" class="docs-image--no-shadow" caption="Screenshot of the new Explore option in the panel menu" >}} + +Choose your datasource in the dropdown in the top left. Prometheus has a custom Explore implementation, the other datasources (for now) use their standard query editor. + +The query field is where you can write your query and explore your data. There are three buttons beside the query field, a clear button (X), an add query button (+) and the remove query button (-). Just like the normal query editor, you can add and remove multiple queries. + +## Split and Compare + +The Split feature is an easy way to compare graphs and tables side-by-side or to look at related data together on one page. Click the split button to duplicate the current query and split the page into two side-by-side queries. It is possible to select another datasource for the new query which for example, allows you to compare the same query for two different servers or to compare the staging environment to the production environment. + +{{< docs-imagebox img="/img/docs/v60/explore_split.png" class="docs-image--no-shadow" caption="Screenshot of the new Explore option in the panel menu" >}} + +You can close the newly created query by clicking on the Close Split button. + +## Prometheus-specific Features + +The first version of Explore features a custom querying experience for Prometheus. When a query is executed, it actually executes two queries, a normal Prometheus query for the graph and an Instant Query for the table. An Instant Query returns the last value for each time series which shows a good summary of the data shown in the graph. + +### Metrics Explorer + +On the left-hand side of the query field is a `Metrics` button, clicking on this opens the Metric Explorer. This shows a hierarchical menu with metrics grouped by their prefix. For example, all the Alert Manager metrics will be grouped under the `alertmanager` prefix. This is a good starting point if you just want to explore which metrics are available. + +{{< docs-imagebox img="/img/docs/v60/explore_metric_explorer.png" class="docs-image--no-shadow" caption="Screenshot of the new Explore option in the panel menu" >}} + +### Query Field + +The Query field supports autocomplete for metric names, function and works mostly the same way as the standard Prometheus query editor. Press the enter key to execute a query. + +The autocomplete menu can be trigger by pressing Ctrl + Space. The Autocomplete menu contains a new History section with a list of recently executed queries. + +Suggestions can appear under the query field - click on them to update your query with the suggested change. + +- For counters (monotonously increasing metrics), a rate function will be suggested. +- For buckets, a histogram function will be suggested. +- For recording rules, possible to expand the rules. + +### Table Filters + +Click on the filter button in a labels column in the Table panel to add filters to the query expression. This works with multiple queries too - the filter will be added for all the queries. + +## Logs Integration - Loki-specific Features + +For Grafana 6.0, the first log integration is for the new open source log aggregation system from Grafana Labs - [Loki](https://github.com/grafana/loki). Loki is designed to be very cost effective, as it does not index the contents of the logs, but rather a set of labels for each log stream. The logs from Loki are queried in a similar way to querying with label selectors in Prometheus. It uses labels to group log streams which can be made to match up with your Prometheus labels. Read more about Loki [here]((https://github.com/grafana/loki)) or the Grafana Labs hosted variant: [Grafana Cloud Logs](https://grafana.com/loki). + +If you switch from a Prometheus query to a logs query (you can do a split first to have your metrics and logs side by side) then it will keep the labels from your query that exist in the logs and use those to query the log streams. For example, the following Prometheus query: + +`grafana_alerting_active_alerts{job="grafana"}` + +after switching to the Logs datasource, the query changes to: + +`{job="grafana"}` + +This will return a chunk of logs in the selected time range that can be grepped/text searched. + +### Log Queries + +A log query consists of two parts: **log stream selector**, and a **search expression**. For performance reasons you need to start by choosing a log stream by selecting a log label. + +The Logs Explorer (the `Log labels` button) next to the query field shows a list of labels of available log streams. An alternative way to write a query is to use the query field's autocomplete - you start by typing a left curly brace `{` and the autocomplete menu will suggest a list of labels. Press the `enter` key to execute the query. + +Once the result is returned, the log panel shows a list of log rows and a bar chart where the x-axis shows the time and the y-axis shows the frequency/count. + +{{< docs-imagebox img="/img/docs/v60/explore_loki.png" class="docs-image--no-shadow" caption="Explore Loki Log Streams" >}} + +#### Log Stream Selector + +For the label part of the query expression, wrap it in curly braces `{}` and then use the key value syntax for selecting labels. Multiple label expressions are separated by a comma: + +`{app="mysql",name="mysql-backup"}` + +The following label matching operators are currently supported: + +- `=` exactly equal. +- `!=` not equal. +- `=~` regex-match. +- `!~` do not regex-match. + +Examples: + +- `{name=~"mysql.+"}` +- `{name!~"mysql.+"}` + +The [same rules that apply for Prometheus Label Selectors](https://prometheus.io/docs/prometheus/latest/querying/basics/#instant-vector-selectors) apply for Loki Log Stream Selectors. + +Another way to add a label selector, is in the table section, clicking on the **Filter** button beside a label will add the label to the query expression. This even works for multiple queries and will the label selector to each query. + +#### Search Expression + +After writing the Log Stream Selector, you can filter the results further by writing a search expression. The search expression can be just text or a regex expression. + +Example queries: + +- `{job="mysql"} error` +- `{name="kafka"} tsdb-ops.*io:2003` +- `{instance=~"kafka-[23]",name="kafka"} kafka.server:type=ReplicaManager` + +### Deduping + +Log data can be very repetitive and Explore can help by hiding duplicate log lines. There are a few different deduplication algorithms that you can use: + +- `exact` Exact matches are done on the whole line, except for date fields. +- `numbers` Matches on the line after stripping out numbers (durations, IP addresses etc.). +- `signature` The most aggressive deduping - strips all letters and numbers, and matches on the remaining whitespace and punctuation. + +### Timestamp, Local time and Labels + +There are some other check boxes under the logging graph apart from the Deduping options. + +- Timestamp: shows/hides the Timestamp column +- Local time: shows/hides the Local time column +- Labels: shows/hides the label filters column From d6c6ba642e62cb03e2707216117d23c38d942f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 11 Dec 2018 10:33:09 +0100 Subject: [PATCH 310/432] Don't show heading for first tab --- .../app/features/dashboard/dashgrid/VisualizationTab.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 97e4e3aa556..19026754f83 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -87,10 +87,11 @@ export class VisualizationTab extends PureComponent { let template = ''; for (let i = 0; i < panelCtrl.editorTabs.length; i++) { - template += ` -
-
{{ctrl.editorTabs[${i}].title}}
-
+ template += + ` +
` + + (i > 0 ? `
{{ctrl.editorTabs[${i}].title}}
` : '') + + `
From 4591c3555caa47b925fc0dc1aa3b0e598c165629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 11 Dec 2018 13:36:44 +0100 Subject: [PATCH 311/432] Moved more metrics tab to react --- .../features/dashboard/dashboard_migration.ts | 2 +- .../app/features/dashboard/dashboard_model.ts | 10 - .../dashboard/dashgrid/QueriesTab.tsx | 111 ++++++++--- public/app/features/dashboard/panel_model.ts | 18 ++ public/app/features/panel/metrics_tab.ts | 176 +----------------- .../features/panel/partials/metrics_tab.html | 37 ++-- public/app/features/panel/query_editor_row.ts | 2 +- public/sass/components/_gf-form.scss | 1 - 8 files changed, 136 insertions(+), 221 deletions(-) diff --git a/public/app/features/dashboard/dashboard_migration.ts b/public/app/features/dashboard/dashboard_migration.ts index 5f1deae6e14..c5a960304fd 100644 --- a/public/app/features/dashboard/dashboard_migration.ts +++ b/public/app/features/dashboard/dashboard_migration.ts @@ -143,7 +143,7 @@ export class DashboardMigrator { panelUpgrades.push(panel => { _.each(panel.targets, target => { if (!target.refId) { - target.refId = this.dashboard.getNextQueryLetter(panel); + target.refId = panel.getNextQueryLetter(); } }); }); diff --git a/public/app/features/dashboard/dashboard_model.ts b/public/app/features/dashboard/dashboard_model.ts index 18a16d5c1d4..6f98bc5a17a 100644 --- a/public/app/features/dashboard/dashboard_model.ts +++ b/public/app/features/dashboard/dashboard_model.ts @@ -806,16 +806,6 @@ export class DashboardModel { return this.timezone === 'browser' ? moment(date).fromNow() : moment.utc(date).fromNow(); } - getNextQueryLetter(panel) { - const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - - return _.find(letters, refId => { - return _.every(panel.targets, other => { - return other.refId !== refId; - }); - }); - } - isTimezoneUtc() { return this.getTimezone() === 'utc'; } diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 4c341f684f7..1e00e193456 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -1,21 +1,27 @@ +// Libraries import React, { SFC, PureComponent } from 'react'; +import Remarkable from 'remarkable'; +import _ from 'lodash'; + +// Components import DataSourceOption from './DataSourceOption'; -import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; import { EditorTabBody } from './EditorTabBody'; import { DataSourcePicker } from './DataSourcePicker'; -import { PanelModel } from '../panel_model'; -import { DashboardModel } from '../dashboard_model'; -import './../../panel/metrics_tab'; -import config from 'app/core/config'; import { QueryInspector } from './QueryInspector'; import { TimeRangeOptions } from './TimeRangeOptions'; +import './../../panel/metrics_tab'; +import { AngularQueryComponentScope } from 'app/features/panel/metrics_tab'; // Services import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv'; -import { DataSourceSelectItem } from 'app/types'; +import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; +import config from 'app/core/config'; -import Remarkable from 'remarkable'; +// Types +import { PanelModel } from '../panel_model'; +import { DashboardModel } from '../dashboard_model'; +import { DataSourceSelectItem, DataQuery } from 'app/types'; interface Props { panel: PanelModel; @@ -40,7 +46,7 @@ interface LoadingPlaceholderProps { const LoadingPlaceholder: SFC = ({ text }) =>

{text}

; export class QueriesTab extends PureComponent { - element: any; + element: HTMLElement; component: AngularComponent; datasources: DataSourceSelectItem[] = getDatasourceSrv().getMetricSources(); backendSrv: BackendSrv = getBackendSrv(); @@ -59,20 +65,30 @@ export class QueriesTab extends PureComponent { }; } + getAngularQueryComponentScope(): AngularQueryComponentScope { + const { panel, dashboard } = this.props; + + return { + panel: panel, + dashboard: dashboard, + refresh: () => panel.refresh(), + render: () => panel.render, + addQuery: this.onAddQuery, + moveQuery: this.onMoveQuery, + removeQuery: this.onRemoveQuery, + events: panel.events, + }; + } + componentDidMount() { if (!this.element) { return; } - const { panel, dashboard } = this.props; const loader = getAngularLoader(); const template = ''; const scopeProps = { - ctrl: { - panel: panel, - dashboard: dashboard, - refresh: () => panel.refresh(), - }, + ctrl: this.getAngularQueryComponentScope(), }; this.component = loader.load(this.element, scopeProps, template); @@ -87,6 +103,7 @@ export class QueriesTab extends PureComponent { onChangeDataSource = datasource => { const { panel } = this.props; const { currentDatasource } = this.state; + // switching to mixed if (datasource.meta.mixed) { panel.targets.forEach(target => { @@ -95,10 +112,16 @@ export class QueriesTab extends PureComponent { target.datasource = config.defaultDatasource; } }); - } else if (currentDatasource && currentDatasource.meta.mixed) { - panel.targets.forEach(target => { - delete target.datasource; - }); + } else if (currentDatasource) { + // if switching from mixed + if (currentDatasource.meta.mixed) { + for (const target of panel.targets) { + delete target.datasource; + } + } else if (currentDatasource.meta.id !== datasource.meta.id) { + // we are changing data source type, clear queries + panel.targets = [{ refId: 'A' }]; + } } panel.datasource = datasource.value; @@ -227,9 +250,41 @@ export class QueriesTab extends PureComponent { return isLoading ? : helpHtml; }; + onAddQuery = (query?: DataQuery) => { + this.props.panel.addQuery(query); + this.forceUpdate(); + }; + + onAddQueryClick = () => { + this.props.panel.addQuery(); + this.component.digest(); + this.forceUpdate(); + }; + + onRemoveQuery = (query: DataQuery) => { + const { panel } = this.props; + + const index = _.indexOf(panel.targets, query); + panel.targets.splice(index, 1); + panel.refresh(); + + this.forceUpdate(); + }; + + onMoveQuery = (query: DataQuery, direction: number) => { + const { panel } = this.props; + + const index = _.indexOf(panel.targets, query); + _.move(panel.targets, index, index + direction); + + this.forceUpdate(); + }; + render() { + const { panel } = this.props; const { currentDatasource } = this.state; const { hasQueryHelp } = currentDatasource.meta; + const dsInformation = { title: currentDatasource.name, imgSrc: currentDatasource.meta.info.logos.small, @@ -266,9 +321,23 @@ export class QueriesTab extends PureComponent { return ( - <> -
(this.element = element)} style={{ width: '100%' }} /> - +
+
(this.element = element)} /> + +
+
+ + +
+
+
); } diff --git a/public/app/features/dashboard/panel_model.ts b/public/app/features/dashboard/panel_model.ts index a664e3d8ea1..e0a3e6707cb 100644 --- a/public/app/features/dashboard/panel_model.ts +++ b/public/app/features/dashboard/panel_model.ts @@ -1,6 +1,7 @@ import { Emitter } from 'app/core/utils/emitter'; import _ from 'lodash'; import { PANEL_OPTIONS_KEY_PREFIX } from 'app/core/constants'; +import { DataQuery } from 'app/types'; export interface GridPos { x: number; @@ -237,6 +238,23 @@ export class PanelModel { this.restorePanelOptions(pluginId); } + addQuery(query?: DataQuery) { + query = query || { refId: 'A' }; + (query.refId = this.getNextQueryLetter()), (query.isNew = true); + + this.targets.push(query); + } + + getNextQueryLetter(): string { + const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + return _.find(letters, refId => { + return _.every(this.targets, other => { + return other.refId !== refId; + }); + }); + } + destroy() { this.events.emit('panel-teardown'); this.events.removeAllListeners(); diff --git a/public/app/features/panel/metrics_tab.ts b/public/app/features/panel/metrics_tab.ts index 2ceb39e8a82..70b3571e30d 100644 --- a/public/app/features/panel/metrics_tab.ts +++ b/public/app/features/panel/metrics_tab.ts @@ -1,181 +1,24 @@ // Libraries import _ from 'lodash'; -import Remarkable from 'remarkable'; // Services & utils import coreModule from 'app/core/core_module'; -import config from 'app/core/config'; import { Emitter } from 'app/core/utils/emitter'; // Types import { DashboardModel } from '../dashboard/dashboard_model'; +import { PanelModel } from '../dashboard/panel_model'; +import { DataQuery } from 'app/types'; -export class MetricsTabCtrl { - dsName: string; - panel: any; - panelCtrl: any; - datasources: any[]; - datasourceInstance: any; - nextRefId: string; +export interface AngularQueryComponentScope { + panel: PanelModel; dashboard: DashboardModel; - panelDsValue: any; - addQueryDropdown: any; - queryTroubleshooterOpen: boolean; - helpOpen: boolean; - optionsOpen: boolean; - hasQueryHelp: boolean; - helpHtml: string; - queryOptions: any; events: Emitter; - - /** @ngInject */ - constructor($scope, private $sce, datasourceSrv, private backendSrv) { - this.panelCtrl = $scope.ctrl; - $scope.ctrl = this; - - this.panel = this.panelCtrl.panel; - this.panel.datasource = this.panel.datasource || null; - this.panel.targets = this.panel.targets || [{}]; - - this.dashboard = this.panelCtrl.dashboard; - this.datasources = datasourceSrv.getMetricSources(); - this.panelDsValue = this.panelCtrl.panel.datasource; - - // added here as old query controller expects this on panelCtrl but - // they are getting MetricsTabCtrl instead - this.events = this.panel.events; - - for (const ds of this.datasources) { - if (ds.value === this.panelDsValue) { - this.datasourceInstance = ds; - } - } - - this.addQueryDropdown = { text: 'Add Query', value: null, fake: true }; - - // update next ref id - this.nextRefId = this.dashboard.getNextQueryLetter(this.panel); - this.updateDatasourceOptions(); - } - - updateDatasourceOptions() { - if (this.datasourceInstance) { - this.hasQueryHelp = this.datasourceInstance.meta.hasQueryHelp; - this.queryOptions = this.datasourceInstance.meta.queryOptions; - } - } - - getOptions(includeBuiltin) { - return Promise.resolve( - this.datasources - .filter(value => { - return includeBuiltin || !value.meta.builtIn; - }) - .map(ds => { - return { value: ds.value, text: ds.name, datasource: ds }; - }) - ); - } - - datasourceChanged(option) { - if (!option) { - return; - } - - this.setDatasource(option.datasource); - this.updateDatasourceOptions(); - } - - setDatasource(datasource) { - // switching to mixed - if (datasource.meta.mixed) { - _.each(this.panel.targets, target => { - target.datasource = this.panel.datasource; - if (!target.datasource) { - target.datasource = config.defaultDatasource; - } - }); - } else if (this.datasourceInstance) { - // if switching from mixed - if (this.datasourceInstance.meta.mixed) { - _.each(this.panel.targets, target => { - delete target.datasource; - }); - } else if (this.datasourceInstance.meta.id !== datasource.meta.id) { - // we are changing data source type, clear queries - this.panel.targets = [{ refId: 'A' }]; - this.panelCtrl.nextRefId = this.dashboard.getNextQueryLetter(this.panel); - } - } - - this.datasourceInstance = datasource; - this.panel.datasource = datasource.value; - this.panel.refresh(); - } - - addMixedQuery(option) { - if (!option) { - return; - } - - this.panelCtrl.addQuery({ - isNew: true, - datasource: option.datasource.name, - }); - this.addQueryDropdown = { text: 'Add Query', value: null, fake: true }; - } - - toggleHelp() { - this.optionsOpen = false; - this.queryTroubleshooterOpen = false; - this.helpOpen = !this.helpOpen; - - this.backendSrv.get(`/api/plugins/${this.datasourceInstance.meta.id}/markdown/query_help`).then(res => { - const md = new Remarkable(); - this.helpHtml = this.$sce.trustAsHtml(md.render(res)); - }); - } - - toggleOptions() { - this.helpOpen = false; - this.queryTroubleshooterOpen = false; - this.optionsOpen = !this.optionsOpen; - } - - toggleQueryTroubleshooter() { - this.helpOpen = false; - this.optionsOpen = false; - this.queryTroubleshooterOpen = !this.queryTroubleshooterOpen; - } - - addQuery(query?) { - query = query || {}; - query.refId = this.dashboard.getNextQueryLetter(this.panel); - query.isNew = true; - - this.panel.targets.push(query); - this.nextRefId = this.dashboard.getNextQueryLetter(this.panel); - } - - refresh() { - this.panel.refresh(); - } - - render() { - this.panel.render(); - } - - removeQuery(target) { - const index = _.indexOf(this.panel.targets, target); - this.panel.targets.splice(index, 1); - this.nextRefId = this.dashboard.getNextQueryLetter(this.panel); - this.panel.refresh(); - } - - moveQuery(target, direction) { - const index = _.indexOf(this.panel.targets, target); - _.move(this.panel.targets, index, index + direction); - } + refresh: () => void; + render: () => void; + removeQuery: (query: DataQuery) => void; + addQuery: (query?: DataQuery) => void; + moveQuery: (query: DataQuery, direction: number) => void; } /** @ngInject */ @@ -185,7 +28,6 @@ export function metricsTabDirective() { restrict: 'E', scope: true, templateUrl: 'public/app/features/panel/partials/metrics_tab.html', - controller: MetricsTabCtrl, }; } diff --git a/public/app/features/panel/partials/metrics_tab.html b/public/app/features/panel/partials/metrics_tab.html index ecee1f76ea9..5e9f23ba2ef 100644 --- a/public/app/features/panel/partials/metrics_tab.html +++ b/public/app/features/panel/partials/metrics_tab.html @@ -1,5 +1,3 @@ - -
@@ -7,21 +5,20 @@
-
-
- - - -
-
-
+ + + + + + + + + + + + + + + + + diff --git a/public/app/features/panel/query_editor_row.ts b/public/app/features/panel/query_editor_row.ts index 39bd6008873..a44c1e8be6d 100644 --- a/public/app/features/panel/query_editor_row.ts +++ b/public/app/features/panel/query_editor_row.ts @@ -20,7 +20,7 @@ export class QueryRowCtrl { this.hideEditorRowActions = this.panelCtrl.hideEditorRowActions; if (!this.target.refId) { - this.target.refId = this.panelCtrl.dashboard.getNextQueryLetter(this.panel); + this.target.refId = this.panel.getNextQueryLetter(); } this.toggleCollapse(true); diff --git a/public/sass/components/_gf-form.scss b/public/sass/components/_gf-form.scss index 0c6ef7d1fa0..f0855ead897 100644 --- a/public/sass/components/_gf-form.scss +++ b/public/sass/components/_gf-form.scss @@ -110,7 +110,6 @@ $input-border: 1px solid $input-border-color; &--grow { flex-grow: 1; - min-height: 2.6rem; } &--error { From 645812f6435d96728074c93a827406e12affa907 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 11 Dec 2018 09:57:58 +0100 Subject: [PATCH 312/432] Let VizTypePicker use the keyboard navigation hoc --- .../dashboard/dashgrid/DataSourcePicker.tsx | 9 +- .../dashboard/dashgrid/VizTypePicker.tsx | 244 ++++++++---------- .../dashgrid/withKeyboardNavigation.tsx | 11 +- 3 files changed, 122 insertions(+), 142 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx b/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx index 2c33474ee73..5c343d31353 100644 --- a/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx +++ b/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx @@ -1,15 +1,12 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; import _ from 'lodash'; -import withKeyboardNavigation from './withKeyboardNavigation'; +import withKeyboardNavigation, { KeyboardNavigationProps } from './withKeyboardNavigation'; import { DataSourceSelectItem } from 'app/types'; export interface Props { - onChangeDataSource: (ds: any) => void; + onChangeDataSource: (ds: DataSourceSelectItem) => void; datasources: DataSourceSelectItem[]; - selected?: number; - onKeyDown?: (evt: any, maxSelectedIndex: number, onEnterAction: () => void) => void; - onMouseEnter?: (select: number) => void; } interface State { @@ -17,7 +14,7 @@ interface State { } export const DataSourcePicker = withKeyboardNavigation( - class DataSourcePicker extends PureComponent { + class DataSourcePicker extends PureComponent { searchInput: HTMLElement; constructor(props) { diff --git a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx index eff51ada020..94c0ef79ad0 100644 --- a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx +++ b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx @@ -4,153 +4,129 @@ import _ from 'lodash'; import config from 'app/core/config'; import { PanelPlugin } from 'app/types/plugins'; import VizTypePickerPlugin from './VizTypePickerPlugin'; +import withKeyboardNavigation, { KeyboardNavigationProps } from './withKeyboardNavigation'; -interface Props { +export interface Props { current: PanelPlugin; onTypeChanged: (newType: PanelPlugin) => void; } interface State { searchQuery: string; - selected: number; } -export class VizTypePicker extends PureComponent { - searchInput: HTMLElement; - pluginList = this.getPanelPlugins(''); +export const VizTypePicker = withKeyboardNavigation( + class VizTypePicker extends PureComponent { + searchInput: HTMLElement; + pluginList = this.getPanelPlugins(''); - constructor(props) { - super(props); + constructor(props) { + super(props); - this.state = { - searchQuery: '', - selected: 0, - }; - } - - get maxSelectedIndex() { - const filteredPluginList = this.getFilteredPluginList(); - return filteredPluginList.length - 1; - } - - goRight = () => { - const nextIndex = this.state.selected >= this.maxSelectedIndex ? 0 : this.state.selected + 1; - this.setState({ - selected: nextIndex, - }); - }; - - goLeft = () => { - const nextIndex = this.state.selected <= 0 ? this.maxSelectedIndex : this.state.selected - 1; - this.setState({ - selected: nextIndex, - }); - }; - - onKeyDown = evt => { - if (evt.key === 'ArrowDown') { - evt.preventDefault(); - this.goRight(); + this.state = { + searchQuery: '', + }; } - if (evt.key === 'ArrowUp') { - evt.preventDefault(); - this.goLeft(); - } - if (evt.key === 'Enter') { + + get maxSelectedIndex() { const filteredPluginList = this.getFilteredPluginList(); - this.props.onTypeChanged(filteredPluginList[this.state.selected]); + return filteredPluginList.length - 1; } - }; - componentDidMount() { - setTimeout(() => { - this.searchInput.focus(); - }, 300); + componentDidMount() { + setTimeout(() => { + this.searchInput.focus(); + }, 300); + } + + getPanelPlugins(filter): PanelPlugin[] { + const panels = _.chain(config.panels) + .filter({ hideFromList: false }) + .map(item => item) + .value(); + + // add sort by sort property + return _.sortBy(panels, 'sort'); + } + + renderVizPlugin = (plugin: PanelPlugin, index: number) => { + const { onTypeChanged, selected, onMouseEnter } = this.props; + const isSelected = selected === index; + const isCurrent = plugin.id === this.props.current.id; + return ( + { + onMouseEnter(index); + }} + onClick={() => onTypeChanged(plugin)} + /> + ); + }; + + getFilteredPluginList = (): PanelPlugin[] => { + const { searchQuery } = this.state; + const regex = new RegExp(searchQuery, 'i'); + const pluginList = this.pluginList; + + const filtered = pluginList.filter(item => { + return regex.test(item.name); + }); + + return filtered; + }; + + onSearchQueryChange = evt => { + const value = evt.target.value; + this.setState(prevState => ({ + ...prevState, + searchQuery: value, + })); + }; + + renderFilters = () => { + const { searchQuery } = this.state; + const { onKeyDown } = this.props; + return ( + <> + + + ); + }; + + render() { + const filteredPluginList = this.getFilteredPluginList(); + + return ( + <> +
+ {this.renderFilters()} +
+
+
{filteredPluginList.map(this.renderVizPlugin)}
+ + ); + } } - - getPanelPlugins(filter): PanelPlugin[] { - const panels = _.chain(config.panels) - .filter({ hideFromList: false }) - .map(item => item) - .value(); - - // add sort by sort property - return _.sortBy(panels, 'sort'); - } - - onMouseEnter = (mouseEnterIndex: number) => { - this.setState({ - selected: mouseEnterIndex, - }); - }; - - renderVizPlugin = (plugin: PanelPlugin, index: number) => { - const isSelected = this.state.selected === index; - const isCurrent = plugin.id === this.props.current.id; - return ( - { - this.onMouseEnter(index); - }} - onClick={() => this.props.onTypeChanged(plugin)} - /> - ); - }; - - getFilteredPluginList = (): PanelPlugin[] => { - const { searchQuery } = this.state; - const regex = new RegExp(searchQuery, 'i'); - const pluginList = this.pluginList; - - const filtered = pluginList.filter(item => { - return regex.test(item.name); - }); - - return filtered; - }; - - onSearchQueryChange = evt => { - const value = evt.target.value; - this.setState(prevState => ({ - ...prevState, - searchQuery: value, - selected: 0, - })); - }; - - renderFilters = () => { - return ( - <> - - - ); - }; - - render() { - const filteredPluginList = this.getFilteredPluginList(); - - return ( - <> -
- {this.renderFilters()} -
-
-
{filteredPluginList.map(this.renderVizPlugin)}
- - ); - } -} +); diff --git a/public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx b/public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx index 58affdf0471..ef20fe96a29 100644 --- a/public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx +++ b/public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx @@ -1,12 +1,19 @@ import React from 'react'; -import { Props } from './DataSourcePicker'; +import { Props as DataSourceProps } from './DataSourcePicker'; +import { Props as VizTypeProps } from './VizTypePicker'; interface State { selected: number; } +export interface KeyboardNavigationProps { + selected?: number; + onKeyDown?: (evt: React.KeyboardEvent, maxSelectedIndex: number, onEnterAction: () => void) => void; + onMouseEnter?: (select: number) => void; +} + const withKeyboardNavigation = WrappedComponent => { - return class extends React.Component { + return class extends React.Component { constructor(props) { super(props); From 07ce88f685c113ec134ca3c8eca095f921cc7dc5 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 11 Dec 2018 11:46:59 +0100 Subject: [PATCH 313/432] Clean up hoc and extend component props automatically --- .../dashgrid/withKeyboardNavigation.tsx | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx b/public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx index ef20fe96a29..f0b84b6299b 100644 --- a/public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx +++ b/public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx @@ -1,19 +1,17 @@ -import React from 'react'; -import { Props as DataSourceProps } from './DataSourcePicker'; -import { Props as VizTypeProps } from './VizTypePicker'; +import React, { KeyboardEvent, ComponentType, Component } from 'react'; interface State { selected: number; } export interface KeyboardNavigationProps { - selected?: number; - onKeyDown?: (evt: React.KeyboardEvent, maxSelectedIndex: number, onEnterAction: () => void) => void; - onMouseEnter?: (select: number) => void; + onKeyDown: (evt: KeyboardEvent, maxSelectedIndex: number, onEnterAction: () => void) => void; + onMouseEnter: (select: number) => void; + selected: number; } -const withKeyboardNavigation = WrappedComponent => { - return class extends React.Component { +const withKeyboardNavigation =

(WrappedComponent: ComponentType

) => { + return class WithKeyboardNavigation extends Component { constructor(props) { super(props); @@ -58,12 +56,7 @@ const withKeyboardNavigation = WrappedComponent => { render() { return ( - + ); } }; From a0da303f80b6fc624c90fe2be3929de324098663 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Tue, 11 Dec 2018 13:46:17 +0100 Subject: [PATCH 314/432] Change KeyboardNavigation from hoc to render prop component --- .../dashboard/dashgrid/DataSourcePicker.tsx | 200 ++++++++------- .../dashboard/dashgrid/KeyboardNavigation.tsx | 71 ++++++ .../dashboard/dashgrid/VizTypePicker.tsx | 227 +++++++++--------- .../dashgrid/withKeyboardNavigation.tsx | 65 ----- 4 files changed, 284 insertions(+), 279 deletions(-) create mode 100644 public/app/features/dashboard/dashgrid/KeyboardNavigation.tsx delete mode 100644 public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx diff --git a/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx b/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx index 5c343d31353..21eaac3e027 100644 --- a/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx +++ b/public/app/features/dashboard/dashgrid/DataSourcePicker.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; import classNames from 'classnames'; import _ from 'lodash'; -import withKeyboardNavigation, { KeyboardNavigationProps } from './withKeyboardNavigation'; +import KeyboardNavigation, { KeyboardNavigationProps } from './KeyboardNavigation'; import { DataSourceSelectItem } from 'app/types'; export interface Props { @@ -13,109 +13,105 @@ interface State { searchQuery: string; } -export const DataSourcePicker = withKeyboardNavigation( - class DataSourcePicker extends PureComponent { - searchInput: HTMLElement; +export class DataSourcePicker extends PureComponent { + searchInput: HTMLElement; - constructor(props) { - super(props); - this.state = { - searchQuery: '', - }; - } - - getDataSources() { - const { searchQuery } = this.state; - const regex = new RegExp(searchQuery, 'i'); - const { datasources } = this.props; - - const filtered = datasources.filter(item => { - return regex.test(item.name) || regex.test(item.meta.name); - }); - - return filtered; - } - - get maxSelectedIndex() { - const filtered = this.getDataSources(); - return filtered.length - 1; - } - - renderDataSource = (ds: DataSourceSelectItem, index: number) => { - const { onChangeDataSource, selected, onMouseEnter } = this.props; - const onClick = () => onChangeDataSource(ds); - const isSelected = selected === index; - const cssClass = classNames({ - 'ds-picker-list__item': true, - 'ds-picker-list__item--selected': isSelected, - }); - return ( -

onMouseEnter(index)} - > - -
{ds.name}
-
- ); + constructor(props) { + super(props); + this.state = { + searchQuery: '', }; - - componentDidMount() { - setTimeout(() => { - this.searchInput.focus(); - }, 300); - } - - onSearchQueryChange = evt => { - const value = evt.target.value; - this.setState(prevState => ({ - ...prevState, - searchQuery: value, - })); - }; - - renderFilters() { - const { searchQuery } = this.state; - const { onKeyDown } = this.props; - return ( - <> - - - ); - } - - render() { - return ( - <> -
- {this.renderFilters()} -
-
-
{this.getDataSources().map(this.renderDataSource)}
- - ); - } } -); + + getDataSources() { + const { searchQuery } = this.state; + const regex = new RegExp(searchQuery, 'i'); + const { datasources } = this.props; + + const filtered = datasources.filter(item => { + return regex.test(item.name) || regex.test(item.meta.name); + }); + + return filtered; + } + + get maxSelectedIndex() { + const filtered = this.getDataSources(); + return filtered.length - 1; + } + + renderDataSource = (ds: DataSourceSelectItem, index: number, keyNavProps: KeyboardNavigationProps) => { + const { onChangeDataSource } = this.props; + const { selected, onMouseEnter } = keyNavProps; + const onClick = () => onChangeDataSource(ds); + const isSelected = selected === index; + const cssClass = classNames({ + 'ds-picker-list__item': true, + 'ds-picker-list__item--selected': isSelected, + }); + return ( +
onMouseEnter(index)}> + +
{ds.name}
+
+ ); + }; + + componentDidMount() { + setTimeout(() => { + this.searchInput.focus(); + }, 300); + } + + onSearchQueryChange = evt => { + const value = evt.target.value; + this.setState(prevState => ({ + ...prevState, + searchQuery: value, + })); + }; + + renderFilters({ onKeyDown, selected }: KeyboardNavigationProps) { + const { searchQuery } = this.state; + return ( + + ); + } + + render() { + return ( + ( + <> +
+ {this.renderFilters(keyNavProps)} +
+
+
+ {this.getDataSources().map((ds, index) => this.renderDataSource(ds, index, keyNavProps))} +
+ + )} + /> + ); + } +} export default DataSourcePicker; diff --git a/public/app/features/dashboard/dashgrid/KeyboardNavigation.tsx b/public/app/features/dashboard/dashgrid/KeyboardNavigation.tsx new file mode 100644 index 00000000000..dab8371c925 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/KeyboardNavigation.tsx @@ -0,0 +1,71 @@ +import React, { KeyboardEvent, Component } from 'react'; + +interface State { + selected: number; +} + +export interface KeyboardNavigationProps { + onKeyDown: (evt: KeyboardEvent, maxSelectedIndex: number, onEnterAction: () => void) => void; + onMouseEnter: (select: number) => void; + selected: number; +} + +interface Props { + render: (injectProps: any) => void; +} + +class KeyboardNavigation extends Component { + constructor(props) { + super(props); + + this.state = { + selected: 0, + }; + } + + goToNext = (maxSelectedIndex: number) => { + const nextIndex = this.state.selected >= maxSelectedIndex ? 0 : this.state.selected + 1; + this.setState({ + selected: nextIndex, + }); + }; + + goToPrev = (maxSelectedIndex: number) => { + const nextIndex = this.state.selected <= 0 ? maxSelectedIndex : this.state.selected - 1; + this.setState({ + selected: nextIndex, + }); + }; + + onKeyDown = (evt: KeyboardEvent, maxSelectedIndex: number, onEnterAction: any) => { + if (evt.key === 'ArrowDown') { + evt.preventDefault(); + this.goToNext(maxSelectedIndex); + } + if (evt.key === 'ArrowUp') { + evt.preventDefault(); + this.goToPrev(maxSelectedIndex); + } + if (evt.key === 'Enter' && onEnterAction) { + onEnterAction(); + } + }; + + onMouseEnter = (mouseEnterIndex: number) => { + this.setState({ + selected: mouseEnterIndex, + }); + }; + + render() { + const injectProps = { + onKeyDown: this.onKeyDown, + onMouseEnter: this.onMouseEnter, + selected: this.state.selected, + }; + + return <>{this.props.render({ ...injectProps })}; + } +} + +export default KeyboardNavigation; diff --git a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx index 94c0ef79ad0..939fa79c289 100644 --- a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx +++ b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx @@ -4,7 +4,7 @@ import _ from 'lodash'; import config from 'app/core/config'; import { PanelPlugin } from 'app/types/plugins'; import VizTypePickerPlugin from './VizTypePickerPlugin'; -import withKeyboardNavigation, { KeyboardNavigationProps } from './withKeyboardNavigation'; +import KeyboardNavigation, { KeyboardNavigationProps } from './KeyboardNavigation'; export interface Props { current: PanelPlugin; @@ -15,118 +15,121 @@ interface State { searchQuery: string; } -export const VizTypePicker = withKeyboardNavigation( - class VizTypePicker extends PureComponent { - searchInput: HTMLElement; - pluginList = this.getPanelPlugins(''); +export class VizTypePicker extends PureComponent { + searchInput: HTMLElement; + pluginList = this.getPanelPlugins(''); - constructor(props) { - super(props); + constructor(props) { + super(props); - this.state = { - searchQuery: '', - }; - } - - get maxSelectedIndex() { - const filteredPluginList = this.getFilteredPluginList(); - return filteredPluginList.length - 1; - } - - componentDidMount() { - setTimeout(() => { - this.searchInput.focus(); - }, 300); - } - - getPanelPlugins(filter): PanelPlugin[] { - const panels = _.chain(config.panels) - .filter({ hideFromList: false }) - .map(item => item) - .value(); - - // add sort by sort property - return _.sortBy(panels, 'sort'); - } - - renderVizPlugin = (plugin: PanelPlugin, index: number) => { - const { onTypeChanged, selected, onMouseEnter } = this.props; - const isSelected = selected === index; - const isCurrent = plugin.id === this.props.current.id; - return ( - { - onMouseEnter(index); - }} - onClick={() => onTypeChanged(plugin)} - /> - ); + this.state = { + searchQuery: '', }; - - getFilteredPluginList = (): PanelPlugin[] => { - const { searchQuery } = this.state; - const regex = new RegExp(searchQuery, 'i'); - const pluginList = this.pluginList; - - const filtered = pluginList.filter(item => { - return regex.test(item.name); - }); - - return filtered; - }; - - onSearchQueryChange = evt => { - const value = evt.target.value; - this.setState(prevState => ({ - ...prevState, - searchQuery: value, - })); - }; - - renderFilters = () => { - const { searchQuery } = this.state; - const { onKeyDown } = this.props; - return ( - <> - - - ); - }; - - render() { - const filteredPluginList = this.getFilteredPluginList(); - - return ( - <> -
- {this.renderFilters()} -
-
-
{filteredPluginList.map(this.renderVizPlugin)}
- - ); - } } -); + + get maxSelectedIndex() { + const filteredPluginList = this.getFilteredPluginList(); + return filteredPluginList.length - 1; + } + + componentDidMount() { + setTimeout(() => { + this.searchInput.focus(); + }, 300); + } + + getPanelPlugins(filter): PanelPlugin[] { + const panels = _.chain(config.panels) + .filter({ hideFromList: false }) + .map(item => item) + .value(); + + // add sort by sort property + return _.sortBy(panels, 'sort'); + } + + renderVizPlugin = (plugin: PanelPlugin, index: number, keyNavProps: KeyboardNavigationProps) => { + const { onTypeChanged } = this.props; + const { selected, onMouseEnter } = keyNavProps; + const isSelected = selected === index; + const isCurrent = plugin.id === this.props.current.id; + return ( + { + onMouseEnter(index); + }} + onClick={() => onTypeChanged(plugin)} + /> + ); + }; + + getFilteredPluginList = (): PanelPlugin[] => { + const { searchQuery } = this.state; + const regex = new RegExp(searchQuery, 'i'); + const pluginList = this.pluginList; + + const filtered = pluginList.filter(item => { + return regex.test(item.name); + }); + + return filtered; + }; + + onSearchQueryChange = evt => { + const value = evt.target.value; + this.setState(prevState => ({ + ...prevState, + searchQuery: value, + })); + }; + + renderFilters = ({ onKeyDown, selected }: KeyboardNavigationProps) => { + const { searchQuery } = this.state; + return ( + <> + + + ); + }; + + render() { + const filteredPluginList = this.getFilteredPluginList(); + + return ( + ( + <> +
+ {this.renderFilters(keyNavProps)} +
+
+
+ {filteredPluginList.map((plugin, index) => this.renderVizPlugin(plugin, index, keyNavProps))} +
+ + )} + /> + ); + } +} diff --git a/public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx b/public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx deleted file mode 100644 index f0b84b6299b..00000000000 --- a/public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { KeyboardEvent, ComponentType, Component } from 'react'; - -interface State { - selected: number; -} - -export interface KeyboardNavigationProps { - onKeyDown: (evt: KeyboardEvent, maxSelectedIndex: number, onEnterAction: () => void) => void; - onMouseEnter: (select: number) => void; - selected: number; -} - -const withKeyboardNavigation =

(WrappedComponent: ComponentType

) => { - return class WithKeyboardNavigation extends Component { - constructor(props) { - super(props); - - this.state = { - selected: 0, - }; - } - - goToNext = (maxSelectedIndex: number) => { - const nextIndex = this.state.selected >= maxSelectedIndex ? 0 : this.state.selected + 1; - this.setState({ - selected: nextIndex, - }); - }; - - goToPrev = (maxSelectedIndex: number) => { - const nextIndex = this.state.selected <= 0 ? maxSelectedIndex : this.state.selected - 1; - this.setState({ - selected: nextIndex, - }); - }; - - onKeyDown = (evt: KeyboardEvent, maxSelectedIndex: number, onEnterAction: any) => { - if (evt.key === 'ArrowDown') { - evt.preventDefault(); - this.goToNext(maxSelectedIndex); - } - if (evt.key === 'ArrowUp') { - evt.preventDefault(); - this.goToPrev(maxSelectedIndex); - } - if (evt.key === 'Enter' && onEnterAction) { - onEnterAction(); - } - }; - - onMouseEnter = (mouseEnterIndex: number) => { - this.setState({ - selected: mouseEnterIndex, - }); - }; - - render() { - return ( - - ); - } - }; -}; - -export default withKeyboardNavigation; From ddf080dab6808ea526ff129ae653328f847da2ef Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 11 Dec 2018 14:17:58 +0100 Subject: [PATCH 315/432] Using an id to identify mappings --- .../ToggleButtonGroup/ToggleButtonGroup.tsx | 14 ++++- public/app/plugins/panel/gauge/MappingRow.tsx | 26 +++++---- public/app/plugins/panel/gauge/Thresholds.tsx | 13 +---- .../panel/gauge/ValueMappings.test.tsx | 54 ++++++++++++++++++ .../app/plugins/panel/gauge/ValueMappings.tsx | 57 +++++++++++++------ public/app/plugins/panel/gauge/module.tsx | 23 ++++++-- public/app/types/index.ts | 3 +- public/app/types/panel.ts | 9 ++- .../sass/components/_toggle_button_group.scss | 4 ++ public/sass/components/_value-mappings.scss | 8 +++ 10 files changed, 161 insertions(+), 50 deletions(-) create mode 100644 public/app/plugins/panel/gauge/ValueMappings.test.tsx diff --git a/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx b/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx index b90e1d6a320..2a59e5a5662 100644 --- a/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx +++ b/public/app/core/components/ToggleButtonGroup/ToggleButtonGroup.tsx @@ -41,7 +41,7 @@ export default class ToggleButtonGroup extends PureComponent

{label && } - {this.props.render({ selectedValue, onChange: this.handleToggle.bind(this) })} + {this.props.render({ selectedValue, onChange: this.handleToggle.bind(this), stackedButtons: stackedButtons })}
); @@ -54,9 +54,17 @@ interface ToggleButtonProps { value: any; className?: string; children: ReactNode; + stackedButtons?: boolean; } -export const ToggleButton: SFC = ({ children, selected, className = '', value, onChange }) => { +export const ToggleButton: SFC = ({ + children, + selected, + className = '', + value, + onChange, + stackedButtons, +}) => { const handleChange = event => { event.stopPropagation(); if (onChange) { @@ -64,7 +72,7 @@ export const ToggleButton: SFC = ({ children, selected, class } }; - const btnClassName = `btn ${className} ${selected ? 'active' : ''}`; + const btnClassName = `btn ${className} ${selected ? 'active' : ''} ${stackedButtons ? 'stacked' : ''}`; return ( +
+
+
+ {filteredPluginList.map((plugin, index) => this.renderVizPlugin(plugin, index))} +
+
+
); } } diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index ca9e1a38249..7e76fc841d5 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -271,7 +271,7 @@ $menu-dropdown-shadow: 5px 5px 20px -5px $black; $tab-border-color: $dark-4; // Toolbar -$toolbar-bg: $black; +$toolbar-bg: $input-bg; // Pagination // ------------------------- @@ -375,6 +375,7 @@ $checkbox-color: $dark-1; //Panel Edit // ------------------------- $panel-editor-shadow: 0 0 20px black; +$panel-editor-border: 1px solid $dark-3; $panel-editor-side-menu-shadow: drop-shadow(0 0 10px $black); $panel-editor-toolbar-view-bg: $black; $panel-editor-viz-item-shadow: 0 0 8px $dark-5; diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index dd84b7af869..657e9eb3509 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -384,6 +384,7 @@ $checkbox-color: $gray-7; //Panel Edit // ------------------------- $panel-editor-shadow: 2px 2px 8px $gray-3; +$panel-editor-border: 1px solid $dark-4; $panel-editor-side-menu-shadow: drop-shadow(0 0 2px $gray-3); $panel-editor-toolbar-view-bg: $white; $panel-editor-viz-item-shadow: 0 0 4px $gray-3; diff --git a/public/sass/components/_buttons.scss b/public/sass/components/_buttons.scss index 27055551b4b..4e032d7b9d1 100644 --- a/public/sass/components/_buttons.scss +++ b/public/sass/components/_buttons.scss @@ -78,6 +78,7 @@ .btn-link { color: $btn-link-color; + background: transparent; } // Set the backgrounds diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 898139afd3a..e2dbbc6f532 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -34,8 +34,8 @@ flex-grow: 1; background: $page-bg; margin: 0 20px 0 84px; - border-left: 2px solid $orange; border-radius: 3px; + border: $panel-editor-border; box-shadow: $panel-editor-shadow; } @@ -133,17 +133,14 @@ } .viz-picker { - background: $panel-editor-toolbar-view-bg; display: flex; flex-wrap: wrap; - margin: -40px -20px; - margin-bottom: 13px; - padding: 20px; + margin-bottom: 40px; } .viz-picker__item { - background: $panel-editor-viz-item-bg; - border: $panel-editor-viz-item-border; + background: $panel-bg; + border: $panel-border; border-radius: 3px; height: 100px; width: 150px; @@ -323,6 +320,13 @@ margin-bottom: 20px; background: $input-label-bg; border-radius: 3px; + position: relative; + + .btn { + position: absolute; + right: 0; + top: 2px; + } } .form-section__body { From 5adb0f79dfb7f0451815e1ff16dd034f8065f3d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Dec 2018 14:25:07 +0100 Subject: [PATCH 341/432] wip --- .../dashboard/dashgrid/VisualizationTab.tsx | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 42023d9de35..793baf15586 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -106,7 +106,7 @@ export class VisualizationTab extends PureComponent { template += `
` + - (i > -1 ? `
{{ctrl.editorTabs[${i}].title}}
` : '') + + (i > 0 ? `
{{ctrl.editorTabs[${i}].title}}
` : '') + `
@@ -155,23 +155,23 @@ export class VisualizationTab extends PureComponent { const { plugin } = this.props; const { searchQuery } = this.state; - if (this.state.isVizPickerOpen) { - return ( - <> - - - ); - } else { + // if (this.state.isVizPickerOpen) { + // return ( + // <> + // + // + // ); + // } else { return (
@@ -179,7 +179,7 @@ export class VisualizationTab extends PureComponent {
); - } + // } }; onTypeChanged = (plugin: PanelPlugin) => { @@ -201,12 +201,7 @@ export class VisualizationTab extends PureComponent { <> - + {this.renderPanelOptions()} From 1751a5108893c0fe8cb7885e3daab501675cd187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Dec 2018 14:44:40 +0100 Subject: [PATCH 342/432] wip --- .../dashboard/dashgrid/VisualizationTab.tsx | 60 +++++++++++-------- .../dashboard/dashgrid/VizTypePicker.tsx | 14 +---- public/sass/components/_panel_editor.scss | 12 +++- 3 files changed, 46 insertions(+), 40 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 793baf15586..ae976ddfd54 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -5,7 +5,7 @@ import React, { PureComponent } from 'react'; import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; // Components -import { EditorTabBody } from './EditorTabBody'; +import { EditorTabBody, EditorToolBarView } from './EditorTabBody'; import { VizTypePicker } from './VizTypePicker'; import { FadeIn } from 'app/core/components/Animations/FadeIn'; @@ -155,23 +155,23 @@ export class VisualizationTab extends PureComponent { const { plugin } = this.props; const { searchQuery } = this.state; - // if (this.state.isVizPickerOpen) { - // return ( - // <> - // - // - // ); - // } else { + if (this.state.isVizPickerOpen) { + return ( + <> + + + ); + } else { return (
@@ -179,7 +179,7 @@ export class VisualizationTab extends PureComponent {
); - // } + } }; onTypeChanged = (plugin: PanelPlugin) => { @@ -190,18 +190,26 @@ export class VisualizationTab extends PureComponent { render() { const { plugin } = this.props; const { isVizPickerOpen, searchQuery } = this.state; + const toolbarItems: EditorToolBarView = []; - const panelHelp = { - title: '', - icon: 'fa fa-question', - render: () =>

Help

, - }; + if (!isVizPickerOpen) { + toolbarItems.push({ + title: '', + icon: 'fa fa-question', + render: () =>

Help

, + }); + } return ( - + <> - + {this.renderPanelOptions()} diff --git a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx index e9f8d895e40..76c6c3af9ce 100644 --- a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx +++ b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx @@ -66,17 +66,9 @@ export class VizTypePicker extends PureComponent { const filteredPluginList = this.getFilteredPluginList(); return ( -
-
- Type selection - -
-
-
- {filteredPluginList.map((plugin, index) => this.renderVizPlugin(plugin, index))} -
+
+
+ {filteredPluginList.map((plugin, index) => this.renderVizPlugin(plugin, index))}
); diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index e2dbbc6f532..34a19ceb72f 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -133,14 +133,20 @@ } .viz-picker { + background: $toolbar-bg; + margin: -40px -20px 40px 106px; + padding: 20px; + position: relative; +} + +.viz-picker-list { display: flex; flex-wrap: wrap; - margin-bottom: 40px; } .viz-picker__item { - background: $panel-bg; - border: $panel-border; + background: $panel-editor-viz-item-bg; + border: $panel-editor-viz-item-border; border-radius: 3px; height: 100px; width: 150px; From 2d2254293b041b475939003bcd6ac110a9c9eee3 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 12 Dec 2018 20:34:33 +0100 Subject: [PATCH 343/432] loki: updates the logo --- .../plugins/datasource/loki/img/loki_icon.svg | 254 ++++++------------ 1 file changed, 77 insertions(+), 177 deletions(-) diff --git a/public/app/plugins/datasource/loki/img/loki_icon.svg b/public/app/plugins/datasource/loki/img/loki_icon.svg index 50199611c06..de7a0d5b5f5 100644 --- a/public/app/plugins/datasource/loki/img/loki_icon.svg +++ b/public/app/plugins/datasource/loki/img/loki_icon.svg @@ -2,7 +2,8 @@ + width="264.2px" height="318px" viewBox="-181.1 -67 264.2 318" style="enable-background:new -181.1 -67 264.2 318;" + xml:space="preserve"> - - - + + + + - - - - + + + + + - - - - + + + + + - - - - + + + + + - - - - + + + + + - - - - + + + + + - - - - + + + + + - - - - + + + + + - - - - + + + + + - - - - + + + + + - - - - + + + + + - - - - + + + + + - - - - + + + + + - - - - + + + + + - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + From 90e21757166d9c36166c668b73ee2d58bc23b158 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 11 Dec 2018 20:08:59 +0100 Subject: [PATCH 344/432] migration: renames logging ds to loki ds in data_source table --- pkg/services/sqlstore/migrations/datasource_mig.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/services/sqlstore/migrations/datasource_mig.go b/pkg/services/sqlstore/migrations/datasource_mig.go index 9011429e398..fc617be72a1 100644 --- a/pkg/services/sqlstore/migrations/datasource_mig.go +++ b/pkg/services/sqlstore/migrations/datasource_mig.go @@ -127,4 +127,7 @@ func addDataSourceMigration(mg *Migrator) { mg.AddMigration("Add read_only data column", NewAddColumnMigration(tableV2, &Column{ Name: "read_only", Type: DB_Bool, Nullable: true, })) + + const migrateLoggingToLoki = `UPDATE data_source SET type = 'loki' WHERE type = 'logging'` + mg.AddMigration("Migrate logging ds to loki ds", NewRawSqlMigration(migrateLoggingToLoki)) } From 666e8e8330cb5205dc16148e5470f505bba06bd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 13 Dec 2018 07:44:58 +0100 Subject: [PATCH 345/432] wip: changes --- .../features/dashboard/dashgrid/EditorTabBody.tsx | 10 +++++++--- .../dashboard/dashgrid/VisualizationTab.tsx | 13 ++++++++++--- .../features/dashboard/dashgrid/VizTypePicker.tsx | 1 - public/sass/_variables.dark.scss | 6 +++--- public/sass/components/_panel_editor.scss | 3 +-- public/sass/utils/_utils.scss | 4 ++++ 6 files changed, 25 insertions(+), 12 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index e76edb1840a..42c6b33764b 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -5,7 +5,7 @@ import { FadeIn } from 'app/core/components/Animations/FadeIn'; interface Props { children: JSX.Element; heading: string; - renderToolbar?: () => JSX.Element | JSX.Element[]; + renderToolbar?: () => JSX.Element; toolbarItems?: EditorToolBarView[]; } @@ -106,8 +106,12 @@ export class EditorTabBody extends PureComponent {
{heading}
{renderToolbar && renderToolbar()} -
- {toolbarItems.map(item => this.renderButton(item))} + {toolbarItems.length > 0 && ( + <> +
+ {toolbarItems.map(item => this.renderButton(item))} + + )}
diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index ae976ddfd54..e52475149be 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -169,6 +169,10 @@ export class VisualizationTab extends PureComponent { /> +
+ ); } else { @@ -183,14 +187,17 @@ export class VisualizationTab extends PureComponent { }; onTypeChanged = (plugin: PanelPlugin) => { - // this.setState({ isVizPickerOpen: false }); - this.props.onTypeChanged(plugin); + if (plugin.id === this.props.plugin.id) { + this.setState({ isVizPickerOpen: false }); + } else { + this.props.onTypeChanged(plugin); + } }; render() { const { plugin } = this.props; const { isVizPickerOpen, searchQuery } = this.state; - const toolbarItems: EditorToolBarView = []; + const toolbarItems: EditorToolBarView[] = []; if (!isVizPickerOpen) { toolbarItems.push({ diff --git a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx index 76c6c3af9ce..f0dd7d0f2c7 100644 --- a/public/app/features/dashboard/dashgrid/VizTypePicker.tsx +++ b/public/app/features/dashboard/dashgrid/VizTypePicker.tsx @@ -62,7 +62,6 @@ export class VizTypePicker extends PureComponent { }; render() { - const { onClose } = this.props; const filteredPluginList = this.getFilteredPluginList(); return ( diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 7e76fc841d5..4e756be7f59 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -271,7 +271,7 @@ $menu-dropdown-shadow: 5px 5px 20px -5px $black; $tab-border-color: $dark-4; // Toolbar -$toolbar-bg: $input-bg; +$toolbar-bg: $input-black; // Pagination // ------------------------- @@ -377,12 +377,12 @@ $checkbox-color: $dark-1; $panel-editor-shadow: 0 0 20px black; $panel-editor-border: 1px solid $dark-3; $panel-editor-side-menu-shadow: drop-shadow(0 0 10px $black); -$panel-editor-toolbar-view-bg: $black; +$panel-editor-toolbar-view-bg: $input-black; $panel-editor-viz-item-shadow: 0 0 8px $dark-5; $panel-editor-viz-item-border: 1px solid $dark-5; $panel-editor-viz-item-shadow-hover: 0 0 4px $blue; $panel-editor-viz-item-border-hover: 1px solid $blue; -$panel-editor-viz-item-bg: $black; +$panel-editor-viz-item-bg: $input-black; $panel-editor-tabs-line-color: #e3e3e3; $panel-editor-viz-item-bg-hover: darken($blue, 47%); $panel-editor-viz-item-bg-hover-active: darken($orange, 45%); diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 34a19ceb72f..d43a44d615c 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -35,7 +35,6 @@ background: $page-bg; margin: 0 20px 0 84px; border-radius: 3px; - border: $panel-editor-border; box-shadow: $panel-editor-shadow; } @@ -134,7 +133,7 @@ .viz-picker { background: $toolbar-bg; - margin: -40px -20px 40px 106px; + margin: -40px -20px 40px -20px; padding: 20px; position: relative; } diff --git a/public/sass/utils/_utils.scss b/public/sass/utils/_utils.scss index 811731660eb..ce59c68a23f 100644 --- a/public/sass/utils/_utils.scss +++ b/public/sass/utils/_utils.scss @@ -83,6 +83,10 @@ button.close { position: absolute; } +.flex-grow { + flex-grow: 1; +} + .center-vh { display: flex; align-items: center; From f39fd7655ef296d820e6d63a43e7b80e4bdfb022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 13 Dec 2018 08:56:20 +0100 Subject: [PATCH 346/432] wip: minor style changes --- .../dashboard/dashgrid/VisualizationTab.tsx | 13 ++----------- public/sass/components/_json_explorer.scss | 8 +++++--- public/sass/components/_panel_editor.scss | 7 +++---- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index e52475149be..f598ae5f7a0 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -5,7 +5,7 @@ import React, { PureComponent } from 'react'; import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; // Components -import { EditorTabBody, EditorToolBarView } from './EditorTabBody'; +import { EditorTabBody } from './EditorTabBody'; import { VizTypePicker } from './VizTypePicker'; import { FadeIn } from 'app/core/components/Animations/FadeIn'; @@ -197,18 +197,9 @@ export class VisualizationTab extends PureComponent { render() { const { plugin } = this.props; const { isVizPickerOpen, searchQuery } = this.state; - const toolbarItems: EditorToolBarView[] = []; - - if (!isVizPickerOpen) { - toolbarItems.push({ - title: '', - icon: 'fa fa-question', - render: () =>

Help

, - }); - } return ( - + <> Date: Thu, 13 Dec 2018 02:10:29 -0800 Subject: [PATCH 347/432] log error when resolvePath --- pkg/services/provisioning/dashboards/file_reader.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/services/provisioning/dashboards/file_reader.go b/pkg/services/provisioning/dashboards/file_reader.go index ea093860f3e..f9590b01bf1 100644 --- a/pkg/services/provisioning/dashboards/file_reader.go +++ b/pkg/services/provisioning/dashboards/file_reader.go @@ -333,12 +333,12 @@ func (fr *fileReader) resolvePath(path string) string { copy := path path, err := filepath.Abs(path) if err != nil { - fr.log.Error("Could not create absolute path ", "path", path) + fr.log.Error("Could not create absolute path", "path", copy, "error", err) } path, err = filepath.EvalSymlinks(path) if err != nil { - fr.log.Error("Failed to read content of symlinked path: %s", path) + fr.log.Error("Failed to read content of symlinked path", "path", copy, "error", err) } if path == "" { From 226e55b18888a90e4129f12d7230f1f82f6b76c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 13 Dec 2018 11:35:07 +0100 Subject: [PATCH 348/432] fixed ordering changing panel types, fixes issues with loading panel options --- .../dashboard/dashgrid/DashboardPanel.tsx | 39 ++++++++++--------- .../dashboard/dashgrid/VisualizationTab.tsx | 2 +- .../plugins/panel/table/column_options.html | 5 ++- public/sass/_variables.dark.scss | 2 +- public/sass/components/_form_select_box.scss | 2 +- 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index 130a4f99bb0..4ae12fc24b4 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -43,8 +43,8 @@ export class DashboardPanel extends PureComponent { this.specialPanels['add-panel'] = this.renderAddPanel.bind(this); } - isSpecial() { - return this.specialPanels[this.props.panel.type]; + isSpecial(pluginId: string) { + return this.specialPanels[pluginId]; } renderRow() { @@ -56,38 +56,41 @@ export class DashboardPanel extends PureComponent { } onPluginTypeChanged = (plugin: PanelPlugin) => { - this.props.panel.changeType(plugin.id, this.state.angularPanel !== null); - this.loadPlugin(); + this.loadPlugin(plugin.id); }; - loadPlugin() { - if (this.isSpecial()) { + async loadPlugin(pluginId: string) { + if (this.isSpecial(pluginId)) { return; } const { panel } = this.props; // handle plugin loading & changing of plugin type - if (!this.state.plugin || this.state.plugin.id !== panel.type) { - const plugin = config.panels[panel.type] || getPanelPluginNotFound(panel.type); + if (!this.state.plugin || this.state.plugin.id !== pluginId) { + const plugin = config.panels[pluginId] || getPanelPluginNotFound(pluginId); + + // remember if this is from an angular panel + const fromAngularPanel = this.state.angularPanel != null; + + // unmount angular panel + this.cleanUpAngularPanel(); if (plugin.exports) { - this.cleanUpAngularPanel(); this.setState({ plugin: plugin }); } else { - importPluginModule(plugin.module).then(pluginExports => { - this.cleanUpAngularPanel(); - // cache plugin exports (saves a promise async cycle next time) - plugin.exports = pluginExports; - // update panel state - this.setState({ plugin: plugin }); - }); + plugin.exports = await importPluginModule(plugin.module); + this.setState({ plugin: plugin }); + } + + if (panel.type !== pluginId) { + this.props.panel.changeType(pluginId, fromAngularPanel); } } } componentDidMount() { - this.loadPlugin(); + this.loadPlugin(this.props.panel.type); } componentDidUpdate() { @@ -140,7 +143,7 @@ export class DashboardPanel extends PureComponent { const { panel, dashboard, isFullscreen, isEditing } = this.props; const { plugin, angularPanel } = this.state; - if (this.isSpecial()) { + if (this.isSpecial(panel.type)) { return this.specialPanels[panel.type](); } diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index f598ae5f7a0..cad204ce712 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -106,7 +106,7 @@ export class VisualizationTab extends PureComponent { template += `
` + - (i > 0 ? `
{{ctrl.editorTabs[${i}].title}}
` : '') + + (i > -1 ? `
{{ctrl.editorTabs[${i}].title}}
` : '') + `
diff --git a/public/app/plugins/panel/table/column_options.html b/public/app/plugins/panel/table/column_options.html index ffb6ea8e67f..d0dbd0c1e98 100644 --- a/public/app/plugins/panel/table/column_options.html +++ b/public/app/plugins/panel/table/column_options.html @@ -1,5 +1,4 @@
-

{{style.pattern || 'New rule'}}

Options
@@ -182,9 +181,13 @@ Remove Rule
+ +
+
+ diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index 4e756be7f59..e2c350ff220 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -95,7 +95,7 @@ $headings-color: darken($white, 11%); $abbr-border-color: $gray-3 !default; $text-muted: $text-color-weak; -$hr-border-color: rgba(0, 0, 0, 0.1) !default; +$hr-border-color: $dark-4; // Panel // ------------------------- diff --git a/public/sass/components/_form_select_box.scss b/public/sass/components/_form_select_box.scss index 54488ad3fff..aecc21d8176 100644 --- a/public/sass/components/_form_select_box.scss +++ b/public/sass/components/_form_select_box.scss @@ -56,7 +56,7 @@ $select-input-bg-disabled: $input-bg-disabled; background: $input-bg; box-shadow: $menu-dropdown-shadow; position: absolute; - z-index: 2; + z-index: $zindex-dropdown; min-width: 100%; } From 5fde9771851205a6ba2b792f2a5a77a7439e4f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 13 Dec 2018 13:05:43 +0100 Subject: [PATCH 349/432] wip: style changes --- .../dashboard/dashgrid/EditorTabBody.tsx | 19 +++++----- .../dashboard/dashgrid/QueriesTab.tsx | 36 ++++++++++--------- .../dashboard/dashgrid/VisualizationTab.tsx | 6 ++-- public/sass/components/_panel_editor.scss | 32 +++-------------- public/sass/components/_query_editor.scss | 4 +++ 5 files changed, 43 insertions(+), 54 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index 42c6b33764b..cf85102338c 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -88,11 +88,14 @@ export class EditorTabBody extends PureComponent { renderOpenView(view: EditorToolBarView) { return ( -
- - {view.render(this.onCloseOpenView)} +
+
+ {view.title} + +
+
{view.render(this.onCloseOpenView)}
); } @@ -115,10 +118,10 @@ export class EditorTabBody extends PureComponent {
- -
{openView && this.renderOpenView(openView)}
-
+ + {openView && this.renderOpenView(openView)} + {children} diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 31480403743..34490d9a434 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -336,23 +336,27 @@ export class QueriesTab extends PureComponent { renderToolbar={this.renderToolbar} toolbarItems={[options, queryInspector, dsHelp]} > -
-
(this.element = element)} /> +
+
+
+
(this.element = element)} /> -
-
- - {!isAddingMixed && ( - - )} - {isAddingMixed && this.renderMixedPicker()} +
+
+ + {!isAddingMixed && ( + + )} + {isAddingMixed && this.renderMixedPicker()} +
+
diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index cad204ce712..9c5b865b9a2 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -105,9 +105,9 @@ export class VisualizationTab extends PureComponent { for (let i = 0; i < panelCtrl.editorTabs.length; i++) { template += ` -
` + - (i > -1 ? `
{{ctrl.editorTabs[${i}].title}}
` : '') + - `
+
` + + (i > -1 ? `
{{ctrl.editorTabs[${i}].title}}
` : '') + + `
diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index a836bf3f8cd..fbb5fdeec1d 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -63,7 +63,7 @@ } .panel-editor__content { - padding: 40px 20px; + padding: 0; } .panel-editor__toolbar-view { @@ -132,7 +132,6 @@ } .viz-picker { - margin-top: -40px; padding: 20px; position: relative; } @@ -303,27 +302,10 @@ width: 30px; } -.form-option-box { - margin-bottom: 20px; -} - -.form-option-box__header { - border-bottom: 2px solid $dark-4; - padding: 5px 0px; - font-size: $font-size-md; - margin-bottom: 20px; -} - -.form-section { - margin-bottom: 10px; -} - -.form-section__header { - padding: 5px 10px; +.panel-option-section__header { + padding: 5px 20px; font-size: $font-size-h5; - margin-bottom: 20px; background: $input-label-bg; - border-radius: 3px; position: relative; .btn { @@ -333,10 +315,6 @@ } } -.form-section__body { - padding: 0 10px; -} - -.panel-editor-tabs__item-popover { - background: $orange; +.panel-option-section__body { + padding: 20px; } diff --git a/public/sass/components/_query_editor.scss b/public/sass/components/_query_editor.scss index 40cee199062..17280a99cc6 100644 --- a/public/sass/components/_query_editor.scss +++ b/public/sass/components/_query_editor.scss @@ -13,6 +13,10 @@ color: $orange; } +.query-editor-rows { + margin-top: 20px; +} + .gf-form-query { display: flex; flex-direction: row; From fe0f66b3f2e22e9468f8b2d622609300e588184a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 13 Dec 2018 13:14:39 +0100 Subject: [PATCH 350/432] wip: testing new styles --- .../dashboard/dashgrid/QueriesTab.tsx | 7 +- public/sass/components/_panel_editor.scss | 69 ++----------------- 2 files changed, 9 insertions(+), 67 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 34490d9a434..53e066ef43d 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -331,12 +331,9 @@ export class QueriesTab extends PureComponent { }; return ( - +
+
Queries
(this.element = element)} /> diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index fbb5fdeec1d..194c359277c 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -32,7 +32,7 @@ display: flex; flex-direction: column; flex-grow: 1; - background: $page-bg; + background: $input-bg; margin: 0 20px 0 84px; border-radius: 3px; box-shadow: $panel-editor-shadow; @@ -63,12 +63,7 @@ } .panel-editor__content { - padding: 0; -} - -.panel-editor__toolbar-view { - background: $panel-editor-toolbar-view-bg; - padding: 20px; + padding: 10px; } .panel-in-fullscreen { @@ -142,8 +137,8 @@ } .viz-picker__item { - background: $panel-bg; - border: $panel-border; + background: $panel-editor-viz-item-bg; + border: $panel-editor-viz-item-border; border-radius: 3px; height: 100px; width: 150px; @@ -242,64 +237,13 @@ } } -.ds-picker-list { - display: flex; - flex-wrap: wrap; - margin-bottom: 13px; - flex-direction: column; -} - -.ds-picker-list__item { - background: $panel-editor-viz-item-bg; - border: $panel-editor-viz-item-border; - border-radius: 3px; - display: flex; - cursor: pointer; - margin-bottom: 3px; - padding: 5px 15px; - align-items: center; - height: 44px; - - &--selected { - background: $panel-editor-viz-item-bg-hover; - border: $panel-editor-viz-item-border-hover; - box-shadow: $panel-editor-viz-item-shadow-hover; - } - - &--active { - box-shadow: 0 0 6px $orange; - border: 1px solid $orange; - - .ds-picker-list__name { - color: $text-color; - } - } -} - .ds-picker { position: relative; min-width: 200px; } -.ds-picker-menu { - min-width: 400px; - max-width: 500px; - position: absolute; - background: $panel-editor-toolbar-view-bg; - padding: 5px; - overflow: auto; -} - -.ds-picker-list__name { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - font-size: $font-size-md; - padding-left: 15px; -} - -.ds-picker-list__img { - width: 30px; +.panel-option-section { + margin-bottom: 10px; } .panel-option-section__header { @@ -317,4 +261,5 @@ .panel-option-section__body { padding: 20px; + background: $page-bg; } From 889518e8e1312398db690c036272c5f5105be290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 13 Dec 2018 13:27:33 +0100 Subject: [PATCH 351/432] wip: styles --- public/sass/components/_panel_editor.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 194c359277c..daa4382a690 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -63,7 +63,7 @@ } .panel-editor__content { - padding: 10px; + padding: 15px; } .panel-in-fullscreen { From 3f1adf1390688d5f90b5147fecba22fcd2df47d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 13 Dec 2018 14:28:44 +0100 Subject: [PATCH 352/432] wip: styles are starting to come together --- public/app/core/components/Form/Input.tsx | 4 +- .../dashboard/dashgrid/QueriesTab.tsx | 131 ++++---------- .../dashboard/dashgrid/QueryOptions.tsx | 167 ++++++++++++++++++ .../dashboard/dashgrid/TimeRangeOptions.tsx | 97 ---------- .../features/dashboard/panellinks/module.html | 4 - .../features/panel/partials/general_tab.html | 82 +++++---- public/sass/components/_panel_editor.scss | 4 + 7 files changed, 251 insertions(+), 238 deletions(-) create mode 100644 public/app/features/dashboard/dashgrid/QueryOptions.tsx delete mode 100644 public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx diff --git a/public/app/core/components/Form/Input.tsx b/public/app/core/components/Form/Input.tsx index 6ba58b45e91..f69aac9759f 100644 --- a/public/app/core/components/Form/Input.tsx +++ b/public/app/core/components/Form/Input.tsx @@ -1,4 +1,4 @@ -import React, { PureComponent } from 'react'; +import React, { PureComponent } from 'react'; import { ValidationEvents, ValidationRule } from 'app/types'; import { validate, hasValidationEvent } from 'app/core/utils/validate'; @@ -76,7 +76,7 @@ export class Input extends PureComponent { render() { const { validationEvents, className, hideErrorMessage, ...restProps } = this.props; const { error } = this.state; - const inputClassName = 'gf-form-input' + (this.isInvalid ? ' invalid' : ''); + const inputClassName = 'gf-form-input' + (this.isInvalid ? ' invalid' : '') + ' ' + className; const inputElementProps = this.populateEventPropsWithStatus(restProps, validationEvents); return ( diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 53e066ef43d..e2c64da9b29 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -4,12 +4,11 @@ import Remarkable from 'remarkable'; import _ from 'lodash'; // Components -import DataSourceOption from './DataSourceOption'; +import './../../panel/metrics_tab'; import { EditorTabBody } from './EditorTabBody'; import { DataSourcePicker } from './DataSourcePicker'; import { QueryInspector } from './QueryInspector'; -import { TimeRangeOptions } from './TimeRangeOptions'; -import './../../panel/metrics_tab'; +import { QueryOptions } from './QueryOptions'; import { AngularQueryComponentScope } from 'app/features/panel/metrics_tab'; // Services @@ -157,75 +156,6 @@ export class QueriesTab extends PureComponent { } }; - renderOptions = close => { - const { currentDS } = this.state; - const { queryOptions } = currentDS.meta; - const { panel } = this.props; - - const onChangeFn = (panelKey: string) => { - return (value: string | number) => { - panel[panelKey] = value; - panel.refresh(); - }; - }; - - const allOptions = { - cacheTimeout: { - label: 'Cache timeout', - placeholder: '60', - name: 'cacheTimeout', - value: panel.cacheTimeout, - tooltipInfo: ( - <> - If your time series store has a query cache this option can override the default cache timeout. Specify a - numeric value in seconds. - - ), - }, - maxDataPoints: { - label: 'Max data points', - placeholder: 'auto', - name: 'maxDataPoints', - value: panel.maxDataPoints, - tooltipInfo: ( - <> - The maximum data points the query should return. For graphs this is automatically set to one data point per - pixel. - - ), - }, - minInterval: { - label: 'Min time interval', - placeholder: '0', - name: 'minInterval', - value: panel.interval, - panelKey: 'interval', - tooltipInfo: ( - <> - A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example{' '} - 1m if your data is written every minute. Access auto interval via variable{' '} - $__interval for time range string and $__interval_ms for numeric variable that can - be used in math expressions. - - ), - }, - }; - - const dsOptions = queryOptions - ? Object.keys(queryOptions).map(key => { - const options = allOptions[key]; - return ; - }) - : null; - - return ( - <> - - {dsOptions} - - ); - }; - renderQueryInspector = () => { const { panel } = this.props; return ; @@ -323,40 +253,41 @@ export class QueriesTab extends PureComponent { render: this.renderHelp, }; - const options = { - title: 'Time Range', - icon: '', - disabled: false, - render: this.renderOptions, - }; - return ( - -
-
Queries
-
-
-
(this.element = element)} /> + + <> +
+
Queries
+
+
+
(this.element = element)} /> -
-
- - {!isAddingMixed && ( - - )} - {isAddingMixed && this.renderMixedPicker()} +
+
+ + {!isAddingMixed && ( + + )} + {isAddingMixed && this.renderMixedPicker()} +
-
+
+
Options
+
+ +
+
+ ); } diff --git a/public/app/features/dashboard/dashgrid/QueryOptions.tsx b/public/app/features/dashboard/dashgrid/QueryOptions.tsx new file mode 100644 index 00000000000..c6d5fecb6d4 --- /dev/null +++ b/public/app/features/dashboard/dashgrid/QueryOptions.tsx @@ -0,0 +1,167 @@ +// Libraries +import React, { PureComponent } from 'react'; + +// Utils +import { isValidTimeSpan } from 'app/core/utils/rangeutil'; + +// Components +import { Switch } from 'app/core/components/Switch/Switch'; +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'; + +// Types +import { PanelModel } from '../panel_model'; +import { ValidationEvents, DataSourceSelectItem } from 'app/types'; + +const timeRangeValidationEvents: ValidationEvents = { + [EventsWithValidation.onBlur]: [ + { + rule: value => { + if (!value) { + return true; + } + return isValidTimeSpan(value); + }, + errorMessage: 'Not a valid timespan', + }, + ], +}; + +const emptyToNull = (value: string) => { + return value === '' ? null : value; +}; + +interface Props { + panel: PanelModel; + datasource: DataSourceSelectItem; +} + +export class QueryOptions extends PureComponent { + onOverrideTime = (evt, status: InputStatus) => { + const { value } = evt.target; + const { panel } = this.props; + const emptyToNullValue = emptyToNull(value); + if (status === InputStatus.Valid && panel.timeFrom !== emptyToNullValue) { + panel.timeFrom = emptyToNullValue; + panel.refresh(); + } + }; + + onTimeShift = (evt, status: InputStatus) => { + const { value } = evt.target; + const { panel } = this.props; + const emptyToNullValue = emptyToNull(value); + if (status === InputStatus.Valid && panel.timeShift !== emptyToNullValue) { + panel.timeShift = emptyToNullValue; + panel.refresh(); + } + }; + + onToggleTimeOverride = () => { + const { panel } = this.props; + panel.hideTimeOverride = !panel.hideTimeOverride; + panel.refresh(); + }; + + renderOptions() { + const { datasource, panel } = this.props; + const { queryOptions } = datasource.meta; + + if (!queryOptions) { + return null; + } + + const onChangeFn = (panelKey: string) => { + return (value: string | number) => { + panel[panelKey] = value; + panel.refresh(); + }; + }; + + const allOptions = { + cacheTimeout: { + label: 'Cache timeout', + placeholder: '60', + name: 'cacheTimeout', + value: panel.cacheTimeout, + tooltipInfo: ( + <> + If your time series store has a query cache this option can override the default cache timeout. Specify a + numeric value in seconds. + + ), + }, + maxDataPoints: { + label: 'Max data points', + placeholder: 'auto', + name: 'maxDataPoints', + value: panel.maxDataPoints, + tooltipInfo: ( + <> + The maximum data points the query should return. For graphs this is automatically set to one data point per + pixel. + + ), + }, + minInterval: { + label: 'Min time interval', + placeholder: '0', + name: 'minInterval', + value: panel.interval, + panelKey: 'interval', + tooltipInfo: ( + <> + A lower limit for the auto group by time interval. Recommended to be set to write frequency, for example{' '} + 1m if your data is written every minute. Access auto interval via variable{' '} + $__interval for time range string and $__interval_ms for numeric variable that can + be used in math expressions. + + ), + }, + }; + + return Object.keys(queryOptions).map(key => { + const options = allOptions[key]; + return ; + }); + } + + render = () => { + const hideTimeOverride = this.props.panel.hideTimeOverride; + return ( +
+ {this.renderOptions()} + +
+ Relative time + +
+ +
+ Time shift + +
+ +
+ +
+
+ ); + }; +} diff --git a/public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx b/public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx deleted file mode 100644 index 00371fa960b..00000000000 --- a/public/app/features/dashboard/dashgrid/TimeRangeOptions.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React, { PureComponent } from 'react'; -import { Switch } from 'app/core/components/Switch/Switch'; -import { Input } from 'app/core/components/Form'; -import { isValidTimeSpan } from 'app/core/utils/rangeutil'; -import { ValidationEvents } from 'app/types'; -import { EventsWithValidation } from 'app/core/components/Form/Input'; -import { PanelModel } from '../panel_model'; -import { InputStatus } from 'app/core/components/Form/Input'; - -const timeRangeValidationEvents: ValidationEvents = { - [EventsWithValidation.onBlur]: [ - { - rule: value => { - if (!value) { - return true; - } - return isValidTimeSpan(value); - }, - errorMessage: 'Not a valid timespan', - }, - ], -}; - -const emptyToNull = (value: string) => { - return value === '' ? null : value; -}; - -interface Props { - panel: PanelModel; -} - -export class TimeRangeOptions extends PureComponent { - onOverrideTime = (evt, status: InputStatus) => { - const { value } = evt.target; - const { panel } = this.props; - const emptyToNullValue = emptyToNull(value); - if (status === InputStatus.Valid && panel.timeFrom !== emptyToNullValue) { - panel.timeFrom = emptyToNullValue; - panel.refresh(); - } - }; - - onTimeShift = (evt, status: InputStatus) => { - const { value } = evt.target; - const { panel } = this.props; - const emptyToNullValue = emptyToNull(value); - if (status === InputStatus.Valid && panel.timeShift !== emptyToNullValue) { - panel.timeShift = emptyToNullValue; - panel.refresh(); - } - }; - - onToggleTimeOverride = () => { - const { panel } = this.props; - panel.hideTimeOverride = !panel.hideTimeOverride; - panel.refresh(); - }; - - render = () => { - const hideTimeOverride = this.props.panel.hideTimeOverride; - return ( - <> -
Time Range
- -
-
- Override relative time - -
- -
- Add time shift - -
- -
- -
-
- - ); - }; -} diff --git a/public/app/features/dashboard/panellinks/module.html b/public/app/features/dashboard/panellinks/module.html index 96d9b785a1b..0dcb0ef3ed6 100644 --- a/public/app/features/dashboard/panellinks/module.html +++ b/public/app/features/dashboard/panellinks/module.html @@ -1,8 +1,4 @@
-
- Drilldown / detail linkThese links appear in the dropdown menu in the panel menu.
- -
diff --git a/public/app/features/panel/partials/general_tab.html b/public/app/features/panel/partials/general_tab.html index 03aab78c407..13ca0380dbc 100644 --- a/public/app/features/panel/partials/general_tab.html +++ b/public/app/features/panel/partials/general_tab.html @@ -1,37 +1,49 @@ -
-
-
Info
-
- Title - -
-
- Description - -
- -
- -
-
Repeat
-
- For each value of - -
-
- Direction - -
-
- Min width - -
-
- - +
+
Information
+
+
+
+ Title + +
+ +
+
+
+ Description + +
+
+
+
+
+
Repeating
+
+
+
+ Repat + +
+
+ Direction + +
+
+ Min width + +
+
+
+
+
+
Drildown Links
+
+ +
+
diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index daa4382a690..7195d0c181a 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -262,4 +262,8 @@ .panel-option-section__body { padding: 20px; background: $page-bg; + + &--queries { + min-height: 300px; + } } From 8a6ed4814cf18136e4d83d15896b4bc9316768bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 13 Dec 2018 14:31:05 +0100 Subject: [PATCH 353/432] Update latest.json --- latest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/latest.json b/latest.json index d36158608bf..21a3f38e1af 100644 --- a/latest.json +++ b/latest.json @@ -1,4 +1,4 @@ { - "stable": "5.4.0", - "testing": "5.4.0" + "stable": "5.4.2", + "testing": "5.4.2" } From 3517029ede1d6e37162fed6b8bc05159489b264b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 13 Dec 2018 14:34:30 +0100 Subject: [PATCH 354/432] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eabf91f897..69b78a30f68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ * **Admin**: Fix prevent removing last grafana admin permissions [#11067](https://github.com/grafana/grafana/issues/11067), thx [@danielbh](https://github.com/danielbh) * **Templating**: Escaping "Custom" template variables [#13754](https://github.com/grafana/grafana/issues/13754), thx [@IntegersOfK](https://github.com/IntegersOfK) +# 5.4.2 (2018-12-13) + +* **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. + # 5.4.1 (2018-12-10) * **Stackdriver**: Fixes issue with data proxy and Authorization header [#14262](https://github.com/grafana/grafana/issues/14262) From dc22beadcad91eb85175e79a61181f99a5eecc19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 13 Dec 2018 16:02:14 +0100 Subject: [PATCH 355/432] wip: style change progress --- packaging/publish/publish_both.sh | 22 +++++++++++----------- public/sass/_variables.dark.scss | 3 +++ public/sass/_variables.light.scss | 3 +++ public/sass/components/_panel_editor.scss | 8 +++++--- public/sass/components/_tabbed_view.scss | 2 +- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/packaging/publish/publish_both.sh b/packaging/publish/publish_both.sh index b1d480567e9..b86c20011e0 100755 --- a/packaging/publish/publish_both.sh +++ b/packaging/publish/publish_both.sh @@ -1,17 +1,17 @@ #! /usr/bin/env bash -version=5.4.1 +version=5.4.2 -wget https://dl.grafana.com/oss/release/grafana_${version}_amd64.deb +# wget https://dl.grafana.com/oss/release/grafana_${version}_amd64.deb +# +# package_cloud push grafana/stable/debian/jessie grafana_${version}_amd64.deb +# package_cloud push grafana/stable/debian/wheezy grafana_${version}_amd64.deb +# package_cloud push grafana/stable/debian/stretch grafana_${version}_amd64.deb +# +# package_cloud push grafana/testing/debian/jessie grafana_${version}_amd64.deb +# package_cloud push grafana/testing/debian/wheezy grafana_${version}_amd64.deb --verbose +# package_cloud push grafana/testing/debian/stretch grafana_${version}_amd64.deb --verbose -package_cloud push grafana/stable/debian/jessie grafana_${version}_amd64.deb -package_cloud push grafana/stable/debian/wheezy grafana_${version}_amd64.deb -package_cloud push grafana/stable/debian/stretch grafana_${version}_amd64.deb - -package_cloud push grafana/testing/debian/jessie grafana_${version}_amd64.deb -package_cloud push grafana/testing/debian/wheezy grafana_${version}_amd64.deb --verbose -package_cloud push grafana/testing/debian/stretch grafana_${version}_amd64.deb --verbose - -wget https://dl.grafana.com/release/grafana-${version}-1.x86_64.rpm +wget https://dl.grafana.com/oss/release/grafana-${version}-1.x86_64.rpm package_cloud push grafana/testing/el/6 grafana-${version}-1.x86_64.rpm --verbose package_cloud push grafana/testing/el/7 grafana-${version}-1.x86_64.rpm --verbose diff --git a/public/sass/_variables.dark.scss b/public/sass/_variables.dark.scss index e2c350ff220..cab0eb76dde 100644 --- a/public/sass/_variables.dark.scss +++ b/public/sass/_variables.dark.scss @@ -387,6 +387,9 @@ $panel-editor-tabs-line-color: #e3e3e3; $panel-editor-viz-item-bg-hover: darken($blue, 47%); $panel-editor-viz-item-bg-hover-active: darken($orange, 45%); +$panel-option-section-border: 1px solid $dark-3; +$panel-option-section-header-bg: linear-gradient(0deg, $gray-blue, $dark-1); + $panel-grid-placeholder-bg: darken($blue, 47%); $panel-grid-placeholder-shadow: 0 0 4px $blue; diff --git a/public/sass/_variables.light.scss b/public/sass/_variables.light.scss index 657e9eb3509..336c35ab13d 100644 --- a/public/sass/_variables.light.scss +++ b/public/sass/_variables.light.scss @@ -396,6 +396,9 @@ $panel-editor-tabs-line-color: $dark-5; $panel-editor-viz-item-bg-hover: lighten($blue, 62%); $panel-editor-viz-item-bg-hover-active: lighten($orange, 34%); +$panel-option-section-border: 1px solid $gray-6; +$panel-option-section-header-bg: linear-gradient(0deg, $gray-6, $gray-7); + $panel-grid-placeholder-bg: lighten($blue, 62%); $panel-grid-placeholder-shadow: 0 0 4px $blue-light; diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 7195d0c181a..f07597b9400 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -244,12 +244,14 @@ .panel-option-section { margin-bottom: 10px; + border: $panel-option-section-border; + border-radius: $border-radius; } .panel-option-section__header { - padding: 5px 20px; - font-size: $font-size-h5; - background: $input-label-bg; + padding: 4px 20px; + font-size: 1.1rem; + background: $panel-option-section-header-bg; position: relative; .btn { diff --git a/public/sass/components/_tabbed_view.scss b/public/sass/components/_tabbed_view.scss index c1cf78716bb..ef21d54f71c 100644 --- a/public/sass/components/_tabbed_view.scss +++ b/public/sass/components/_tabbed_view.scss @@ -58,6 +58,6 @@ } .section-heading { - font-size: 1.1rem; + font-size: $font-size-md; margin-bottom: 0.6rem; } From 9c13520e973250f7c7ebcebf9e6ca1ad45242a86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 13 Dec 2018 21:24:41 +0100 Subject: [PATCH 356/432] updated styles --- .../features/alerting/partials/alert_tab.html | 346 +++++++++--------- .../dashboard/dashgrid/DashboardPanel.tsx | 8 +- .../dashboard/dashgrid/EditorTabBody.tsx | 5 +- .../dashboard/dashgrid/QueriesTab.tsx | 10 +- .../dashboard/dashgrid/QueryInspector.tsx | 10 +- .../dashboard/dashgrid/VisualizationTab.tsx | 2 +- .../settings/DataSourceSettings.tsx | 6 +- .../features/panel/partials/general_tab.html | 2 +- .../app/plugins/panel/graph/tab_display.html | 12 +- public/sass/components/_panel_editor.scss | 6 +- 10 files changed, 202 insertions(+), 205 deletions(-) diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index 2ebe2b53a76..2c9b64cf6d2 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -1,187 +1,189 @@ -
- +
+
+ -
-
-
- {{ctrl.error}} -
+
+
+
+ {{ctrl.error}} +
-
-
Alert Config
-
- Name - -
-
-
- Evaluate every - -
-
- - - - If an alert rule has a configured For and the query violates the configured threshold it will first go from OK to Pending. - Going from OK to Pending Grafana will not send any notifications. Once the alert rule has been firing for more than For duration, it will change to Alerting and send alert notifications. - -
-
-
+
+
Alert Config
+
+ Name + +
+
+
+ Evaluate every + +
+
+ + + + If an alert rule has a configured For and the query violates the configured threshold it will first go from OK to Pending. + Going from OK to Pending Grafana will not send any notifications. Once the alert rule has been firing for more than For duration, it will change to Alerting and send alert notifications. + +
+
+
-
-
Conditions
-
-
- - WHEN -
-
- - - OF -
-
- - -
-
- - - - -
-
- -
-
+
+
Conditions
+
+
+ + WHEN +
+
+ + + OF +
+
+ + +
+
+ + + + +
+
+ +
+
-
- -
-
+
+ +
+
-
-
- If no data or all values are null - SET STATE TO -
- -
-
+
+
+ If no data or all values are null + SET STATE TO +
+ +
+
-
- If execution error or timeout - SET STATE TO -
- -
-
+
+ If execution error or timeout + SET STATE TO +
+ +
+
-
- -
-
+
+ +
+
-
- Evaluating rule -
+
+ Evaluating rule +
-
- -
-
+
+ +
+
-
-
Notifications
-
-
- Send to - -  {{nc.name}}  - - - -
-
-
- Message - -
-
+
+
Notifications
+
+
+ Send to + +  {{nc.name}}  + + + +
+
+
+ Message + +
+
-
- -
- State history (last 50 state changes) -
+
+ +
+ State history (last 50 state changes) +
-
-
- No state changes recorded -
+
+
+ No state changes recorded +
-
    -
  1. -
    - -
    -
    -
    -
    - {{al.stateModel.text}} -
    -
    - {{al.info}} -
    -
    - {{al.time}} -
    -
  2. -
-
-
-
+
    +
  1. +
    + +
    +
    +
    +
    + {{al.stateModel.text}} +
    +
    + {{al.info}} +
    +
    + {{al.time}} +
    +
  2. +
+
+
+
-
-
- -
+
+
+ +
+
diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index 4ae12fc24b4..5744031b719 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -76,16 +76,16 @@ export class DashboardPanel extends PureComponent { // unmount angular panel this.cleanUpAngularPanel(); + if (panel.type !== pluginId) { + this.props.panel.changeType(pluginId, fromAngularPanel); + } + if (plugin.exports) { this.setState({ plugin: plugin }); } else { plugin.exports = await importPluginModule(plugin.module); this.setState({ plugin: plugin }); } - - if (panel.type !== pluginId) { - this.props.panel.changeType(pluginId, fromAngularPanel); - } } } diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index cf85102338c..8dab06dba43 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -10,7 +10,8 @@ interface Props { } export interface EditorToolBarView { - title: string; + title?: string; + heading?: string; imgSrc?: string; icon?: string; disabled?: boolean; @@ -90,7 +91,7 @@ export class EditorTabBody extends PureComponent { return (
- {view.title} + {view.title || view.heading} diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index e2c64da9b29..03ae9a97777 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -246,7 +246,7 @@ export class QueriesTab extends PureComponent { }; const dsHelp = { - title: '', + heading: 'Help', icon: 'fa fa-question', disabled: !hasQueryHelp, onClick: this.loadHelp, @@ -254,11 +254,11 @@ export class QueriesTab extends PureComponent { }; return ( - + <>
-
Queries
-
+ {/*
Queries
*/} +
(this.element = element)} /> @@ -282,7 +282,7 @@ export class QueriesTab extends PureComponent {
-
Options
+ {/*
Options
*/}
diff --git a/public/app/features/dashboard/dashgrid/QueryInspector.tsx b/public/app/features/dashboard/dashgrid/QueryInspector.tsx index ec618404651..090bc220bc0 100644 --- a/public/app/features/dashboard/dashgrid/QueryInspector.tsx +++ b/public/app/features/dashboard/dashgrid/QueryInspector.tsx @@ -1,4 +1,4 @@ -import React, { PureComponent } from 'react'; +import React, { PureComponent } from 'react'; import { JSONFormatter } from 'app/core/components/JSONFormatter/JSONFormatter'; import appEvents from 'app/core/app_events'; import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard'; @@ -187,16 +187,10 @@ export class QueryInspector extends PureComponent { return ( <> -
- {/* - - */} +
- { template += `
` + - (i > -1 ? `
{{ctrl.editorTabs[${i}].title}}
` : '') + + (i > 0 ? `
{{ctrl.editorTabs[${i}].title}}
` : '') + `
diff --git a/public/app/features/datasources/settings/DataSourceSettings.tsx b/public/app/features/datasources/settings/DataSourceSettings.tsx index 977d37e35ec..5786bd1db57 100644 --- a/public/app/features/datasources/settings/DataSourceSettings.tsx +++ b/public/app/features/datasources/settings/DataSourceSettings.tsx @@ -177,6 +177,9 @@ export class DataSourceSettings extends PureComponent {
+ {this.isReadOnly() && this.renderIsReadOnlyMessage()} + {this.shouldRenderInfoBox() &&
{this.getInfoText()}
} + { onNameChange={name => setDataSourceName(name)} /> - {this.shouldRenderInfoBox() &&
{this.getInfoText()}
} - - {this.isReadOnly() && this.renderIsReadOnlyMessage()} {dataSourceMeta.module && ( -
Information
+
diff --git a/public/app/plugins/panel/graph/tab_display.html b/public/app/plugins/panel/graph/tab_display.html index 2ed600aac70..976b9bcc940 100644 --- a/public/app/plugins/panel/graph/tab_display.html +++ b/public/app/plugins/panel/graph/tab_display.html @@ -22,10 +22,10 @@
-
+
- +
@@ -66,7 +66,7 @@
-
+
@@ -85,16 +85,16 @@
- +
- +
- +