diff --git a/e2e/suite1/specs/inspect-drawer.spec.ts b/e2e/suite1/specs/inspect-drawer.spec.ts index 97e69c76a14..1d48b66377e 100644 --- a/e2e/suite1/specs/inspect-drawer.spec.ts +++ b/e2e/suite1/specs/inspect-drawer.spec.ts @@ -27,7 +27,7 @@ e2e.scenario({ e2e.flows.openPanelMenuItem(e2e.flows.PanelMenuItems.Edit, PANEL_UNDER_TEST); - e2e.components.QueryEditorToolbarItem.button('Query inspector') + e2e.components.QueryTab.queryInspectorButton() .should('be.visible') .click(); diff --git a/packages/grafana-e2e/src/components/index.ts b/packages/grafana-e2e/src/components/index.ts index 3a5b09462aa..5332e57f9b7 100644 --- a/packages/grafana-e2e/src/components/index.ts +++ b/packages/grafana-e2e/src/components/index.ts @@ -77,6 +77,7 @@ export const Components = { QueryTab: componentFactory({ selectors: { content: 'Query editor tab content', + queryInspectorButton: 'Query inspector button', }, }), AlertTab: componentFactory({ diff --git a/packages/grafana-ui/src/components/Drawer/Drawer.tsx b/packages/grafana-ui/src/components/Drawer/Drawer.tsx index b028e17a046..4fe13860697 100644 --- a/packages/grafana-ui/src/components/Drawer/Drawer.tsx +++ b/packages/grafana-ui/src/components/Drawer/Drawer.tsx @@ -13,7 +13,7 @@ export interface Props { title?: ReactNode; /** Subtitle shown below the title */ subtitle?: ReactNode; - /** Should the Drawer be closable by clicking on the mask */ + /** Should the Drawer be closable by clicking on the mask, defaults to true */ closeOnMaskClick?: boolean; /** Render the drawer inside a container on the page */ inline?: boolean; @@ -70,7 +70,7 @@ export const Drawer: FC = ({ children, inline = false, onClose, - closeOnMaskClick = false, + closeOnMaskClick = true, scrollableContent = false, title, subtitle, diff --git a/packages/grafana-ui/src/components/Modal/getModalStyles.ts b/packages/grafana-ui/src/components/Modal/getModalStyles.ts index d64b73b8f03..8efc592cba7 100644 --- a/packages/grafana-ui/src/components/Modal/getModalStyles.ts +++ b/packages/grafana-ui/src/components/Modal/getModalStyles.ts @@ -3,7 +3,7 @@ import { GrafanaTheme } from '@grafana/data'; import { stylesFactory } from '../../themes'; export const getModalStyles = stylesFactory((theme: GrafanaTheme) => { - const backdropBackground = theme.colors.bg1; + const backdropBackground = theme.colors.bg3; return { modal: css` diff --git a/packages/grafana-ui/src/components/Select/SelectBase.tsx b/packages/grafana-ui/src/components/Select/SelectBase.tsx index 49e665f4e28..366b358e7ba 100644 --- a/packages/grafana-ui/src/components/Select/SelectBase.tsx +++ b/packages/grafana-ui/src/components/Select/SelectBase.tsx @@ -329,12 +329,12 @@ export function SelectBase({ zIndex: theme.zIndex.dropdown, }), //These are required for the menu positioning to function - menu: ({ top, bottom, width, position }: any) => ({ + menu: ({ top, bottom, position }: any) => ({ top, bottom, - width, position, marginBottom: !!bottom ? '10px' : '0', + 'min-width': '100%', zIndex: theme.zIndex.dropdown, }), container: () => ({ diff --git a/packages/grafana-ui/src/themes/_variables.dark.scss.tmpl.ts b/packages/grafana-ui/src/themes/_variables.dark.scss.tmpl.ts index d707659b6b0..4af146405ab 100644 --- a/packages/grafana-ui/src/themes/_variables.dark.scss.tmpl.ts +++ b/packages/grafana-ui/src/themes/_variables.dark.scss.tmpl.ts @@ -136,7 +136,7 @@ $divider-border-color: $gray-1; $tight-form-func-bg: $dark-9; $tight-form-func-highlight-bg: $dark-10; -$modal-backdrop-bg: ${theme.colors.bg1}; +$modal-backdrop-bg: ${theme.colors.bg3}; $code-tag-bg: $dark-1; $code-tag-border: $dark-9; diff --git a/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx b/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx index 20e38e28840..7e62b8637ef 100644 --- a/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx +++ b/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx @@ -6,6 +6,7 @@ import { useUpdateEffect } from 'react-use'; interface QueryOperationRowProps { title?: ((props: { isOpen: boolean }) => React.ReactNode) | React.ReactNode; + headerElement?: React.ReactNode; actions?: | ((props: { isOpen: boolean; openRow: () => void; closeRow: () => void }) => React.ReactNode) | React.ReactNode; @@ -19,6 +20,7 @@ export const QueryOperationRow: React.FC = ({ children, actions, title, + headerElement, onClose, onOpen, isOpen, @@ -64,6 +66,7 @@ export const QueryOperationRow: React.FC = ({ > {title && {titleElement}} + {headerElement} {actions && actionsElement} @@ -76,7 +79,7 @@ export const QueryOperationRow: React.FC = ({ const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => { return { wrapper: css` - margin-bottom: ${theme.spacing.formSpacingBase * 2}px; + margin-bottom: ${theme.spacing.md}; `, header: css` padding: 0 ${theme.spacing.sm}; @@ -90,6 +93,9 @@ const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => { `, collapseIcon: css` color: ${theme.colors.textWeak}; + &:hover { + color: ${theme.colors.text}; + } `, titleWrapper: css` display: flex; diff --git a/public/app/core/components/Select/DataSourcePicker.tsx b/public/app/core/components/Select/DataSourcePicker.tsx index 257332fe68d..3086d4fb1f3 100644 --- a/public/app/core/components/Select/DataSourcePicker.tsx +++ b/public/app/core/components/Select/DataSourcePicker.tsx @@ -2,9 +2,8 @@ import React, { PureComponent } from 'react'; // Components -import { LegacyForms } from '@grafana/ui'; +import { Select } from '@grafana/ui'; import { SelectableValue, DataSourceSelectItem } from '@grafana/data'; -const { Select } = LegacyForms; export interface Props { onChange: (ds: DataSourceSelectItem) => void; @@ -66,23 +65,22 @@ export class DataSourcePicker extends PureComponent { }; return ( -
- ); } } diff --git a/public/app/features/alerting/AlertTab.tsx b/public/app/features/alerting/AlertTab.tsx index 07583a69624..4e5d074e97f 100644 --- a/public/app/features/alerting/AlertTab.tsx +++ b/public/app/features/alerting/AlertTab.tsx @@ -205,7 +205,7 @@ class UnConnectedAlertTab extends PureComponent { }; return ( - +
{alert && hasTransformations && ( {{ctrl.error}}
-
-
-
-

Rule

-
-
- 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. - -
-
-
- + +
+

Rule

+
+
+ 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 +
+
+ + +
+
+ + + + +
+
+ +
+
+ +
+ +
+
+ +
+

No Data & Error Handling

+
+
+ If no data or all values are null +
+
+ SET STATE TO +
+
+
-
-

Conditions

-
-
- - WHEN -
-
- - - OF -
-
- - -
-
- - - - -
-
- -
-
- -
- -
+
+
+ If execution error or timeout
- -
-

No Data & Error Handling

-
-
- If no data or all values are null -
-
- SET STATE TO -
- -
-
-
- -
-
- If execution error or timeout -
-
- SET STATE TO -
- -
-
+
+ SET STATE TO +
+
-
-
Notifications
-
+

Notifications

+
+
+ Send to +
+
+ + +  {{nc.name}} (default) + + +
+
+ +
+
+
+ Message + +
+
+ Tags +
+
+ + + +
- Send to + +
-
- - -  {{nc.name}} (default) - - -
-
- -
-
-
- Message -
- Tags -
-
- - - -
-
-
-
- - -
-
- -
-
+
diff --git a/public/app/features/dashboard/panel_editor/DataSourceOption.tsx b/public/app/features/dashboard/panel_editor/DataSourceOption.tsx index e01645501d5..d6a726360c1 100644 --- a/public/app/features/dashboard/panel_editor/DataSourceOption.tsx +++ b/public/app/features/dashboard/panel_editor/DataSourceOption.tsx @@ -15,7 +15,9 @@ interface Props { export const DataSourceOption: FC = ({ label, placeholder, name, value, onBlur, onChange, tooltipInfo }) => { return (
- {label} + + {label} + JSX.Element; toolbarItems?: EditorToolbarView[]; scrollTop?: number; @@ -110,16 +109,13 @@ export class EditorTabBody extends PureComponent { } render() { - const { children, renderToolbar, heading, toolbarItems, scrollTop, setScrollTop } = this.props; + const { children, renderToolbar, toolbarItems, scrollTop, setScrollTop } = this.props; const { openView, fadeIn, isOpen } = this.state; return ( <>
-
-
{heading}
- {renderToolbar && renderToolbar()} -
+ {renderToolbar && renderToolbar()} {toolbarItems.map(item => this.renderButton(item))}
diff --git a/public/app/features/dashboard/panel_editor/QueriesTab.tsx b/public/app/features/dashboard/panel_editor/QueriesTab.tsx index 58874b8ec5e..b5675a16caf 100644 --- a/public/app/features/dashboard/panel_editor/QueriesTab.tsx +++ b/public/app/features/dashboard/panel_editor/QueriesTab.tsx @@ -1,10 +1,9 @@ // Libraries import React, { PureComponent } from 'react'; // Components -import { EditorTabBody, EditorToolbarView } from './EditorTabBody'; import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; import { QueryOptions } from './QueryOptions'; -import { PanelOptionsGroup } from '@grafana/ui'; +import { CustomScrollbar, stylesFactory, Button, HorizontalGroup, Modal } from '@grafana/ui'; import { getLocationSrv } from '@grafana/runtime'; import { QueryEditorRows } from './QueryEditorRows'; // Services @@ -20,6 +19,7 @@ import { addQuery } from 'app/core/utils/query'; import { Unsubscribable } from 'rxjs'; import { DashboardQueryEditor, isSharedDashboardQuery } from 'app/plugins/datasource/dashboard'; import { expressionDatasource, ExpressionDatasourceID } from 'app/features/expressions/ExpressionDatasource'; +import { css } from 'emotion'; import { e2e } from '@grafana/e2e'; interface Props { @@ -35,6 +35,7 @@ interface State { isAddingMixed: boolean; scrollTop: number; data: PanelData; + isHelpOpen: boolean; } export class QueriesTab extends PureComponent { @@ -48,6 +49,7 @@ export class QueriesTab extends PureComponent { helpContent: null, isPickerOpen: false, isAddingMixed: false, + isHelpOpen: false, scrollTop: 0, data: { state: LoadingState.NotStarted, @@ -121,6 +123,7 @@ export class QueriesTab extends PureComponent { openQueryInspector = () => { const { panel } = this.props; + getLocationSrv().update({ query: { inspect: panel.id, inspectTab: 'query' }, partial: true, @@ -128,7 +131,7 @@ export class QueriesTab extends PureComponent { }; renderHelp = () => { - return ; + return; }; /** @@ -155,30 +158,45 @@ export class QueriesTab extends PureComponent { }; onScrollBottom = () => { - this.setState({ scrollTop: this.state.scrollTop + 10000 }); + this.setState({ scrollTop: 1000 }); }; - renderToolbar = () => { - const { currentDS, isAddingMixed } = this.state; - const showAddButton = !(isAddingMixed || isSharedDashboardQuery(currentDS.name)); + renderTopSection(styles: QueriesTabStyls) { + const { panel } = this.props; + const { currentDS, data } = this.state; return ( - <> - -
- {showAddButton && ( - - )} - {isAddingMixed && this.renderMixedPicker()} - {config.featureToggles.expressions && ( - - )} - +
+
+
+ +
+
+
+
+ +
+
+ +
+
+
); + } + + onOpenHelp = () => { + this.setState({ isHelpOpen: true }); + }; + + onCloseHelp = () => { + this.setState({ isHelpOpen: false }); }; renderMixedPicker = () => { @@ -218,7 +236,7 @@ export class QueriesTab extends PureComponent { this.setState({ scrollTop: target.scrollTop }); }; - renderQueryBody = () => { + renderQueries() { const { panel, dashboard } = this.props; const { currentDS, data } = this.state; @@ -237,36 +255,83 @@ export class QueriesTab extends PureComponent { dashboard={dashboard} data={data} /> - - -
); - }; + } - render() { - const { scrollTop } = this.state; - const queryInspector: EditorToolbarView = { - title: 'Query inspector', - onClick: this.openQueryInspector, - }; - - const dsHelp: EditorToolbarView = { - heading: 'Help', - icon: 'question-circle', - render: this.renderHelp, - }; + renderAddQueryRow(styles: QueriesTabStyls) { + const { currentDS, isAddingMixed } = this.state; + const showAddButton = !(isAddingMixed || isSharedDashboardQuery(currentDS.name)); return ( - + {showAddButton && ( + + )} + {isAddingMixed && this.renderMixedPicker()} + {config.featureToggles.expressions && ( + + )} + + ); + } + + render() { + const { scrollTop, isHelpOpen } = this.state; + const styles = getStyles(); + + return ( + - <>{this.renderQueryBody()} - +
+ {this.renderTopSection(styles)} +
{this.renderQueries()}
+ {this.renderAddQueryRow(styles)} + + {isHelpOpen && ( + + + + )} +
+ ); } } + +const getStyles = stylesFactory(() => { + const { theme } = config; + + return { + innerWrapper: css` + display: flex; + flex-direction: column; + height: 100%; + padding: ${theme.spacing.md}; + `, + dataSourceRow: css` + display: flex; + margin-bottom: ${theme.spacing.md}; + `, + dataSourceRowItem: css` + margin-right: ${theme.spacing.inlineFormMargin}; + `, + dataSourceRowItemOptions: css` + flex-grow: 1; + `, + queriesWrapper: css` + padding-bottom: 16px; + `, + }; +}); + +type QueriesTabStyls = ReturnType; diff --git a/public/app/features/dashboard/panel_editor/QueryEditorRows.tsx b/public/app/features/dashboard/panel_editor/QueryEditorRows.tsx index c773a39750d..8dcc3963ec1 100644 --- a/public/app/features/dashboard/panel_editor/QueryEditorRows.tsx +++ b/public/app/features/dashboard/panel_editor/QueryEditorRows.tsx @@ -73,24 +73,20 @@ export class QueryEditorRows extends PureComponent { render() { const { props } = this; - return ( -
- {props.queries.map((query, index) => ( - this.onChangeQuery(query, index)} - onRemoveQuery={this.onRemoveQuery} - onAddQuery={this.onAddQuery} - onMoveQuery={this.onMoveQuery} - inMixedMode={props.datasource.meta.mixed} - /> - ))} -
- ); + return props.queries.map((query, index) => ( + this.onChangeQuery(query, index)} + onRemoveQuery={this.onRemoveQuery} + onAddQuery={this.onAddQuery} + onMoveQuery={this.onMoveQuery} + inMixedMode={props.datasource.meta.mixed} + /> + )); } } diff --git a/public/app/features/dashboard/panel_editor/QueryOptions.tsx b/public/app/features/dashboard/panel_editor/QueryOptions.tsx index e16e8b71a13..8f82b5a28eb 100644 --- a/public/app/features/dashboard/panel_editor/QueryOptions.tsx +++ b/public/app/features/dashboard/panel_editor/QueryOptions.tsx @@ -2,15 +2,25 @@ import React, { PureComponent, ChangeEvent, FocusEvent, ReactText } from 'react'; // Utils -import { rangeUtil, DataSourceSelectItem } from '@grafana/data'; +import { rangeUtil, DataSourceSelectItem, PanelData } from '@grafana/data'; // Components -import { EventsWithValidation, LegacyInputStatus, LegacyForms, ValidationEvents, InlineFormLabel } from '@grafana/ui'; +import { + EventsWithValidation, + LegacyInputStatus, + LegacyForms, + ValidationEvents, + InlineFormLabel, + stylesFactory, +} from '@grafana/ui'; import { DataSourceOption } from './DataSourceOption'; const { Input, Switch } = LegacyForms; // Types import { PanelModel } from '../state'; +import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; +import { config } from 'app/core/config'; +import { css } from 'emotion'; const timeRangeValidationEvents: ValidationEvents = { [EventsWithValidation.onBlur]: [ @@ -33,6 +43,7 @@ const emptyToNull = (value: string) => { interface Props { panel: PanelModel; datasource: DataSourceSelectItem; + data: PanelData; } interface State { @@ -42,6 +53,7 @@ interface State { maxDataPoints: string | ReactText; interval: string; hideTimeOverride: boolean; + isOpen: boolean; } export class QueryOptions extends PureComponent { @@ -95,6 +107,7 @@ export class QueryOptions extends PureComponent { maxDataPoints: props.panel.maxDataPoints || '', interval: props.panel.interval || '', hideTimeOverride: props.panel.hideTimeOverride || false, + isOpen: false, }; } @@ -180,15 +193,57 @@ export class QueryOptions extends PureComponent { }); }; + onOpenOptions = () => { + this.setState({ isOpen: true }); + }; + + onCloseOptions = () => { + this.setState({ isOpen: false }); + }; + + renderCollapsedText(styles: StylesType): React.ReactNode | undefined { + const { data } = this.props; + const { isOpen, maxDataPoints, interval } = this.state; + + if (isOpen) { + return undefined; + } + + let mdDesc = maxDataPoints; + if (maxDataPoints === '' && data.request) { + mdDesc = `auto = ${data.request.maxDataPoints}`; + } + + let intervalDesc = interval; + if (intervalDesc === '' && data.request) { + intervalDesc = `auto = ${data.request.interval}`; + } + + return ( + <> + {
MD = {mdDesc}
} + {
Interval = {intervalDesc}
} + + ); + } + render() { const { hideTimeOverride } = this.state; - const { relativeTime, timeShift } = this.state; + const { relativeTime, timeShift, isOpen } = this.state; + const styles = getStyles(); + return ( -
+ {this.renderOptions()}
- Relative time + Relative time {
- Time shift + Time shift {
{(timeShift || relativeTime) && (
- +
)} -
+ ); } } + +const getStyles = stylesFactory(() => { + const { theme } = config; + + return { + collapsedText: css` + margin-left: ${theme.spacing.md}; + font-size: ${theme.typography.size.sm}; + color: ${theme.colors.textWeak}; + `, + }; +}); + +type StylesType = ReturnType; diff --git a/public/sass/_variables.dark.generated.scss b/public/sass/_variables.dark.generated.scss index 92154fc400a..079572540a6 100644 --- a/public/sass/_variables.dark.generated.scss +++ b/public/sass/_variables.dark.generated.scss @@ -138,7 +138,7 @@ $divider-border-color: $gray-1; $tight-form-func-bg: $dark-9; $tight-form-func-highlight-bg: $dark-10; -$modal-backdrop-bg: #141619; +$modal-backdrop-bg: #2c3235; $code-tag-bg: $dark-1; $code-tag-border: $dark-9; diff --git a/public/sass/components/_gf-form.scss b/public/sass/components/_gf-form.scss index f7c3ece0882..35282a8891c 100644 --- a/public/sass/components/_gf-form.scss +++ b/public/sass/components/_gf-form.scss @@ -109,6 +109,7 @@ $input-border: 1px solid $input-border-color; font-size: $font-size-sm; background-color: $input-label-bg; height: $input-height; + line-height: $input-height; margin-right: $space-xs; border-radius: $input-border-radius; justify-content: space-between; diff --git a/public/sass/components/_panel_editor.scss b/public/sass/components/_panel_editor.scss index 2e006b6eac5..2cb71928c7b 100644 --- a/public/sass/components/_panel_editor.scss +++ b/public/sass/components/_panel_editor.scss @@ -1,9 +1,3 @@ -.panel-editor-container { - display: flex; - flex-direction: column; - height: 100%; -} - .panel-wrapper { height: 100%; position: relative; @@ -16,45 +10,6 @@ } } -.panel-editor-container__editor { - margin-top: $space-lg; - display: flex; - flex-direction: row; - flex: 1 1 0; - position: relative; - min-height: 0; -} - -.panel-editor__right { - display: flex; - flex-direction: column; - flex-grow: 1; - background: $input-bg; - margin: 0 20px 0 84px; - width: calc(100% - 84px); - border-radius: 3px; - box-shadow: $panel-editor-shadow; - min-height: 0; -} - -.panel-editor__close { - @include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl); - position: absolute; - left: 11px; - top: 5px; - width: 40px; - height: 40px; - border-radius: 50%; - display: flex; - align-items: center; - - i { - flex-grow: 1; - text-align: center; - font-size: 20px; - } -} - .panel-editor__scroll { flex-grow: 1; min-width: 0; @@ -65,7 +20,7 @@ } .panel-editor__content { - padding: 16px; + padding: 0 16px 16px 16px; } .panel-in-fullscreen { @@ -86,88 +41,6 @@ } } -.panel-editor-container__resizer { - position: relative; - margin-top: -3px; -} - -.panel-editor-resizer__handle { - position: relative; - display: block; - background: $vertical-resize-handle-bg; - width: 150px; - margin-left: -75px; - height: 6px; - cursor: ns-resize; - border-radius: 3px; - margin: 0 auto; - - &::before { - content: ' '; - position: absolute; - left: 10px; - right: 10px; - top: 2px; - border-top: 2px dotted $vertical-resize-handle-dots; - } - - &:hover::before { - border-color: $vertical-resize-handle-dots-hover; - } -} - -.panel-editor-tabs { - z-index: 2; - display: flex; - flex-direction: column; - position: absolute; - top: 44px; - left: 20px; - align-items: flex-start; - - &::before { - content: ''; - display: block; - position: absolute; - top: 10px; - bottom: 10px; - left: 21px; - width: 2px; - background: $panel-editor-tabs-line-color; - } -} - -.panel-editor-tabs__item { - margin-bottom: 25px; - position: relative; - z-index: 1; - text-align: center; - - &:last-child { - margin-bottom: 0; - } -} - -.panel-editor-tabs__link { - display: inline-block; - - &.active { - position: relative; - } - - .gicon { - height: 44px; - width: 53px; - margin-right: 5px; - transition: transform 0.1s ease 0.1s; - - &:hover { - filter: $panel-editor-side-menu-shadow; - transform: scale(1.1); - } - } -} - .ds-picker { position: relative; min-width: 200px; diff --git a/public/sass/components/_query_editor.scss b/public/sass/components/_query_editor.scss index 1ee15d90746..52fc173414c 100644 --- a/public/sass/components/_query_editor.scss +++ b/public/sass/components/_query_editor.scss @@ -11,10 +11,6 @@ color: $gray-2; } -.query-editor-rows { - margin: 20px 0; -} - .tight-form-func { background: $tight-form-func-bg; diff --git a/public/sass/components/_toolbar.scss b/public/sass/components/_toolbar.scss index 54ab616e5d4..b4b9dd5d273 100644 --- a/public/sass/components/_toolbar.scss +++ b/public/sass/components/_toolbar.scss @@ -2,12 +2,9 @@ display: flex; align-content: center; align-items: center; - padding: 3px 20px 3px 20px; + padding: 16px; position: relative; flex: 0 0 auto; - background: $toolbar-bg; - border-radius: 3px; - height: 44px; } .toolbar__heading {