From e642fce4a5070fb0b4db61ac2e8a13995e1fa91a Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Fri, 12 Oct 2018 16:16:06 +0200 Subject: [PATCH 001/368] 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 228/368] 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 229/368] 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 230/368] 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 231/368] 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 232/368] 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 233/368] 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 234/368] 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 235/368] 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 236/368] 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 237/368] 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 238/368] 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 239/368] 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 240/368] 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 241/368] 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 242/368] 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 243/368] 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 244/368] 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 245/368] 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 246/368] 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 247/368] 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 248/368] 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 249/368] 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 250/368] 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 251/368] 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 252/368] 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 253/368] 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 254/368] 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 255/368] 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 256/368] 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 257/368] 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 258/368] 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 259/368] 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 260/368] 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 261/368] 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 262/368] 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 267/368] 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 268/368] 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 269/368] 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 270/368] 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 271/368] 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 272/368] 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 273/368] 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 274/368] 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 275/368] 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 276/368] 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 277/368] 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 278/368] 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 279/368] 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 280/368] 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 281/368] 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 282/368] 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 283/368] 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 284/368] 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 285/368] 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 286/368] 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 287/368] 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 288/368] 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 289/368] 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 290/368] 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 291/368] 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 292/368] 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 ae5bc366c27bf6c6b788808a72fd0144a7215cda Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Mon, 10 Dec 2018 14:45:49 +0100 Subject: [PATCH 293/368] 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 294/368] 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 295/368] 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 296/368] 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 6ac8f5c7a5c414b2dfff154e9f002a1d51260aaa Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Mon, 10 Dec 2018 15:51:41 +0100 Subject: [PATCH 297/368] 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 76f296e29948b562dee3fd9c50035e9dc2e42799 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 10 Dec 2018 16:57:34 +0100 Subject: [PATCH 298/368] 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 1ffac5a33dc8327d1588796411072b0ef0e98668 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Mon, 10 Dec 2018 21:38:07 +0100 Subject: [PATCH 299/368] 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 300/368] 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 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 301/368] 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 302/368] 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 303/368] 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 304/368] 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 305/368] 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 306/368] 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 326/368] 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 327/368] 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 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 328/368] 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 329/368] 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 11:35:07 +0100 Subject: [PATCH 330/368] 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 331/368] 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 332/368] 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 333/368] 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 334/368] 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 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 335/368] 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 336/368] updated styles --- .../features/alerting/partials/alert_tab.html | 348 +++++++++--------- .../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, 203 insertions(+), 206 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 @@
- +
- +
- +