QueryTab: Design updates (#23906)

* WIP: first stage

* Another take

* argghhh

* Updated

* My brain is mush

* Minor progress

* Progres

* Starting to work

* Fixes

* fixed e2e
This commit is contained in:
Torkel Ödegaard 2020-04-26 21:59:14 +02:00 committed by GitHub
parent b9a40fc346
commit 49276f2c12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 465 additions and 469 deletions

View File

@ -27,7 +27,7 @@ e2e.scenario({
e2e.flows.openPanelMenuItem(e2e.flows.PanelMenuItems.Edit, PANEL_UNDER_TEST); e2e.flows.openPanelMenuItem(e2e.flows.PanelMenuItems.Edit, PANEL_UNDER_TEST);
e2e.components.QueryEditorToolbarItem.button('Query inspector') e2e.components.QueryTab.queryInspectorButton()
.should('be.visible') .should('be.visible')
.click(); .click();

View File

@ -77,6 +77,7 @@ export const Components = {
QueryTab: componentFactory({ QueryTab: componentFactory({
selectors: { selectors: {
content: 'Query editor tab content', content: 'Query editor tab content',
queryInspectorButton: 'Query inspector button',
}, },
}), }),
AlertTab: componentFactory({ AlertTab: componentFactory({

View File

@ -13,7 +13,7 @@ export interface Props {
title?: ReactNode; title?: ReactNode;
/** Subtitle shown below the title */ /** Subtitle shown below the title */
subtitle?: ReactNode; 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; closeOnMaskClick?: boolean;
/** Render the drawer inside a container on the page */ /** Render the drawer inside a container on the page */
inline?: boolean; inline?: boolean;
@ -70,7 +70,7 @@ export const Drawer: FC<Props> = ({
children, children,
inline = false, inline = false,
onClose, onClose,
closeOnMaskClick = false, closeOnMaskClick = true,
scrollableContent = false, scrollableContent = false,
title, title,
subtitle, subtitle,

View File

@ -3,7 +3,7 @@ import { GrafanaTheme } from '@grafana/data';
import { stylesFactory } from '../../themes'; import { stylesFactory } from '../../themes';
export const getModalStyles = stylesFactory((theme: GrafanaTheme) => { export const getModalStyles = stylesFactory((theme: GrafanaTheme) => {
const backdropBackground = theme.colors.bg1; const backdropBackground = theme.colors.bg3;
return { return {
modal: css` modal: css`

View File

@ -329,12 +329,12 @@ export function SelectBase<T>({
zIndex: theme.zIndex.dropdown, zIndex: theme.zIndex.dropdown,
}), }),
//These are required for the menu positioning to function //These are required for the menu positioning to function
menu: ({ top, bottom, width, position }: any) => ({ menu: ({ top, bottom, position }: any) => ({
top, top,
bottom, bottom,
width,
position, position,
marginBottom: !!bottom ? '10px' : '0', marginBottom: !!bottom ? '10px' : '0',
'min-width': '100%',
zIndex: theme.zIndex.dropdown, zIndex: theme.zIndex.dropdown,
}), }),
container: () => ({ container: () => ({

View File

@ -136,7 +136,7 @@ $divider-border-color: $gray-1;
$tight-form-func-bg: $dark-9; $tight-form-func-bg: $dark-9;
$tight-form-func-highlight-bg: $dark-10; $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-bg: $dark-1;
$code-tag-border: $dark-9; $code-tag-border: $dark-9;

View File

@ -6,6 +6,7 @@ import { useUpdateEffect } from 'react-use';
interface QueryOperationRowProps { interface QueryOperationRowProps {
title?: ((props: { isOpen: boolean }) => React.ReactNode) | React.ReactNode; title?: ((props: { isOpen: boolean }) => React.ReactNode) | React.ReactNode;
headerElement?: React.ReactNode;
actions?: actions?:
| ((props: { isOpen: boolean; openRow: () => void; closeRow: () => void }) => React.ReactNode) | ((props: { isOpen: boolean; openRow: () => void; closeRow: () => void }) => React.ReactNode)
| React.ReactNode; | React.ReactNode;
@ -19,6 +20,7 @@ export const QueryOperationRow: React.FC<QueryOperationRowProps> = ({
children, children,
actions, actions,
title, title,
headerElement,
onClose, onClose,
onOpen, onOpen,
isOpen, isOpen,
@ -64,6 +66,7 @@ export const QueryOperationRow: React.FC<QueryOperationRowProps> = ({
> >
<Icon name={isContentVisible ? 'angle-down' : 'angle-right'} className={styles.collapseIcon} /> <Icon name={isContentVisible ? 'angle-down' : 'angle-right'} className={styles.collapseIcon} />
{title && <span className={styles.title}>{titleElement}</span>} {title && <span className={styles.title}>{titleElement}</span>}
{headerElement}
</div> </div>
{actions && actionsElement} {actions && actionsElement}
</HorizontalGroup> </HorizontalGroup>
@ -76,7 +79,7 @@ export const QueryOperationRow: React.FC<QueryOperationRowProps> = ({
const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => { const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => {
return { return {
wrapper: css` wrapper: css`
margin-bottom: ${theme.spacing.formSpacingBase * 2}px; margin-bottom: ${theme.spacing.md};
`, `,
header: css` header: css`
padding: 0 ${theme.spacing.sm}; padding: 0 ${theme.spacing.sm};
@ -90,6 +93,9 @@ const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => {
`, `,
collapseIcon: css` collapseIcon: css`
color: ${theme.colors.textWeak}; color: ${theme.colors.textWeak};
&:hover {
color: ${theme.colors.text};
}
`, `,
titleWrapper: css` titleWrapper: css`
display: flex; display: flex;

View File

@ -2,9 +2,8 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
// Components // Components
import { LegacyForms } from '@grafana/ui'; import { Select } from '@grafana/ui';
import { SelectableValue, DataSourceSelectItem } from '@grafana/data'; import { SelectableValue, DataSourceSelectItem } from '@grafana/data';
const { Select } = LegacyForms;
export interface Props { export interface Props {
onChange: (ds: DataSourceSelectItem) => void; onChange: (ds: DataSourceSelectItem) => void;
@ -66,9 +65,8 @@ export class DataSourcePicker extends PureComponent<Props> {
}; };
return ( return (
<div className="gf-form-inline">
<Select <Select
className="ds-picker" className="ds-picker select-container"
isMulti={false} isMulti={false}
isClearable={false} isClearable={false}
backspaceRemovesValue={false} backspaceRemovesValue={false}
@ -78,11 +76,11 @@ export class DataSourcePicker extends PureComponent<Props> {
onBlur={onBlur} onBlur={onBlur}
openMenuOnFocus={openMenuOnFocus} openMenuOnFocus={openMenuOnFocus}
maxMenuHeight={500} maxMenuHeight={500}
menuPlacement="bottom"
placeholder={placeholder} placeholder={placeholder}
noOptionsMessage={() => 'No datasources found'} noOptionsMessage="No datasources found"
value={value} value={value}
/> />
</div>
); );
} }
} }

View File

@ -205,7 +205,7 @@ class UnConnectedAlertTab extends PureComponent<Props, State> {
}; };
return ( return (
<EditorTabBody heading="Alert" toolbarItems={toolbarItems}> <EditorTabBody toolbarItems={toolbarItems}>
<div aria-label={e2e.components.AlertTab.selectors.content}> <div aria-label={e2e.components.AlertTab.selectors.content}>
{alert && hasTransformations && ( {alert && hasTransformations && (
<Alert <Alert

View File

@ -2,8 +2,7 @@
<div class="alert alert-error m-b-2" ng-show="ctrl.error"> <div class="alert alert-error m-b-2" ng-show="ctrl.error">
<icon name="'exclamation-triangle'"></icon> {{ctrl.error}} <icon name="'exclamation-triangle'"></icon> {{ctrl.error}}
</div> </div>
<div class="panel-options-group">
<div class="panel-options-group__body">
<div class="gf-form-group"> <div class="gf-form-group">
<h4 class="section-heading">Rule</h4> <h4 class="section-heading">Rule</h4>
<div class="gf-form-inline"> <div class="gf-form-inline">
@ -30,9 +29,9 @@
placeholder="5m" placeholder="5m"
/> />
<info-popover mode="right-absolute"> <info-popover mode="right-absolute">
If an alert rule has a configured For and the query violates the configured threshold it will first go If an alert rule has a configured For and the query violates the configured threshold it will first go from OK
from OK to Pending. Going from OK to Pending Grafana will not send any notifications. Once the alert rule to Pending. Going from OK to Pending Grafana will not send any notifications. Once the alert rule has been
has been firing for more than For duration, it will change to Alerting and send alert notifications. firing for more than For duration, it will change to Alerting and send alert notifications.
</info-popover> </info-popover>
</div> </div>
</div> </div>
@ -158,12 +157,8 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<div class="panel-options-group"> <h4 class="section-heading">Notifications</h4>
<div class="panel-options-group__header">Notifications</div>
<div class="panel-options-group__body">
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-8">Send to</span> <span class="gf-form-label width-8">Send to</span>
@ -214,7 +209,6 @@
</a> </a>
</label> </label>
</div> </div>
<div class="gf-form-group">
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form"> <div class="gf-form">
<input <input
@ -241,6 +235,3 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
</div>

View File

@ -15,7 +15,9 @@ interface Props {
export const DataSourceOption: FC<Props> = ({ label, placeholder, name, value, onBlur, onChange, tooltipInfo }) => { export const DataSourceOption: FC<Props> = ({ label, placeholder, name, value, onBlur, onChange, tooltipInfo }) => {
return ( return (
<div className="gf-form gf-form--flex-end"> <div className="gf-form gf-form--flex-end">
<InlineFormLabel tooltip={tooltipInfo}>{label}</InlineFormLabel> <InlineFormLabel width={9} tooltip={tooltipInfo}>
{label}
</InlineFormLabel>
<Input <Input
type="text" type="text"
className="gf-form-input width-6" className="gf-form-input width-6"

View File

@ -7,7 +7,6 @@ import { e2e } from '@grafana/e2e';
interface Props { interface Props {
children: JSX.Element; children: JSX.Element;
heading: string;
renderToolbar?: () => JSX.Element; renderToolbar?: () => JSX.Element;
toolbarItems?: EditorToolbarView[]; toolbarItems?: EditorToolbarView[];
scrollTop?: number; scrollTop?: number;
@ -110,16 +109,13 @@ export class EditorTabBody extends PureComponent<Props, State> {
} }
render() { render() {
const { children, renderToolbar, heading, toolbarItems, scrollTop, setScrollTop } = this.props; const { children, renderToolbar, toolbarItems, scrollTop, setScrollTop } = this.props;
const { openView, fadeIn, isOpen } = this.state; const { openView, fadeIn, isOpen } = this.state;
return ( return (
<> <>
<div className="toolbar"> <div className="toolbar">
<div className="toolbar__left">
<div className="toolbar__heading">{heading}</div>
{renderToolbar && renderToolbar()} {renderToolbar && renderToolbar()}
</div>
{toolbarItems.map(item => this.renderButton(item))} {toolbarItems.map(item => this.renderButton(item))}
</div> </div>
<div className="panel-editor__scroll"> <div className="panel-editor__scroll">

View File

@ -1,10 +1,9 @@
// Libraries // Libraries
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
// Components // Components
import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
import { QueryOptions } from './QueryOptions'; import { QueryOptions } from './QueryOptions';
import { PanelOptionsGroup } from '@grafana/ui'; import { CustomScrollbar, stylesFactory, Button, HorizontalGroup, Modal } from '@grafana/ui';
import { getLocationSrv } from '@grafana/runtime'; import { getLocationSrv } from '@grafana/runtime';
import { QueryEditorRows } from './QueryEditorRows'; import { QueryEditorRows } from './QueryEditorRows';
// Services // Services
@ -20,6 +19,7 @@ import { addQuery } from 'app/core/utils/query';
import { Unsubscribable } from 'rxjs'; import { Unsubscribable } from 'rxjs';
import { DashboardQueryEditor, isSharedDashboardQuery } from 'app/plugins/datasource/dashboard'; import { DashboardQueryEditor, isSharedDashboardQuery } from 'app/plugins/datasource/dashboard';
import { expressionDatasource, ExpressionDatasourceID } from 'app/features/expressions/ExpressionDatasource'; import { expressionDatasource, ExpressionDatasourceID } from 'app/features/expressions/ExpressionDatasource';
import { css } from 'emotion';
import { e2e } from '@grafana/e2e'; import { e2e } from '@grafana/e2e';
interface Props { interface Props {
@ -35,6 +35,7 @@ interface State {
isAddingMixed: boolean; isAddingMixed: boolean;
scrollTop: number; scrollTop: number;
data: PanelData; data: PanelData;
isHelpOpen: boolean;
} }
export class QueriesTab extends PureComponent<Props, State> { export class QueriesTab extends PureComponent<Props, State> {
@ -48,6 +49,7 @@ export class QueriesTab extends PureComponent<Props, State> {
helpContent: null, helpContent: null,
isPickerOpen: false, isPickerOpen: false,
isAddingMixed: false, isAddingMixed: false,
isHelpOpen: false,
scrollTop: 0, scrollTop: 0,
data: { data: {
state: LoadingState.NotStarted, state: LoadingState.NotStarted,
@ -121,6 +123,7 @@ export class QueriesTab extends PureComponent<Props, State> {
openQueryInspector = () => { openQueryInspector = () => {
const { panel } = this.props; const { panel } = this.props;
getLocationSrv().update({ getLocationSrv().update({
query: { inspect: panel.id, inspectTab: 'query' }, query: { inspect: panel.id, inspectTab: 'query' },
partial: true, partial: true,
@ -128,7 +131,7 @@ export class QueriesTab extends PureComponent<Props, State> {
}; };
renderHelp = () => { renderHelp = () => {
return <PluginHelp plugin={this.state.currentDS.meta} type="query_help" />; return;
}; };
/** /**
@ -155,30 +158,45 @@ export class QueriesTab extends PureComponent<Props, State> {
}; };
onScrollBottom = () => { onScrollBottom = () => {
this.setState({ scrollTop: this.state.scrollTop + 10000 }); this.setState({ scrollTop: 1000 });
}; };
renderToolbar = () => { renderTopSection(styles: QueriesTabStyls) {
const { currentDS, isAddingMixed } = this.state; const { panel } = this.props;
const showAddButton = !(isAddingMixed || isSharedDashboardQuery(currentDS.name)); const { currentDS, data } = this.state;
return ( return (
<> <div>
<div className={styles.dataSourceRow}>
<div className={styles.dataSourceRowItem}>
<DataSourcePicker datasources={this.datasources} onChange={this.onChangeDataSource} current={currentDS} /> <DataSourcePicker datasources={this.datasources} onChange={this.onChangeDataSource} current={currentDS} />
<div className="flex-grow-1" /> </div>
{showAddButton && ( <div className={styles.dataSourceRowItem}>
<button className="btn navbar-button" onClick={this.onAddQueryClick}> <Button variant="secondary" icon="info-circle" title="Open data source help" onClick={this.onOpenHelp} />
Add query </div>
</button> <div className={styles.dataSourceRowItem}>
)} <Button
{isAddingMixed && this.renderMixedPicker()} variant="secondary"
{config.featureToggles.expressions && ( onClick={this.openQueryInspector}
<button className="btn navbar-button" onClick={this.onAddExpressionClick}> aria-label={e2e.components.QueryTab.selectors.queryInspectorButton}
Add Expression >
</button> Query inspector
)} </Button>
</> </div>
<div className={styles.dataSourceRowItemOptions}>
<QueryOptions panel={panel} datasource={currentDS} data={data} />
</div>
</div>
</div>
); );
}
onOpenHelp = () => {
this.setState({ isHelpOpen: true });
};
onCloseHelp = () => {
this.setState({ isHelpOpen: false });
}; };
renderMixedPicker = () => { renderMixedPicker = () => {
@ -218,7 +236,7 @@ export class QueriesTab extends PureComponent<Props, State> {
this.setState({ scrollTop: target.scrollTop }); this.setState({ scrollTop: target.scrollTop });
}; };
renderQueryBody = () => { renderQueries() {
const { panel, dashboard } = this.props; const { panel, dashboard } = this.props;
const { currentDS, data } = this.state; const { currentDS, data } = this.state;
@ -237,36 +255,83 @@ export class QueriesTab extends PureComponent<Props, State> {
dashboard={dashboard} dashboard={dashboard}
data={data} data={data}
/> />
<PanelOptionsGroup>
<QueryOptions panel={panel} datasource={currentDS} />
</PanelOptionsGroup>
</div> </div>
); );
}; }
render() { renderAddQueryRow(styles: QueriesTabStyls) {
const { scrollTop } = this.state; const { currentDS, isAddingMixed } = this.state;
const queryInspector: EditorToolbarView = { const showAddButton = !(isAddingMixed || isSharedDashboardQuery(currentDS.name));
title: 'Query inspector',
onClick: this.openQueryInspector,
};
const dsHelp: EditorToolbarView = {
heading: 'Help',
icon: 'question-circle',
render: this.renderHelp,
};
return ( return (
<EditorTabBody <HorizontalGroup spacing="md" align="flex-start">
heading="Data source" {showAddButton && (
renderToolbar={this.renderToolbar} <Button icon="plus" onClick={this.onAddQueryClick} variant="secondary">
toolbarItems={[queryInspector, dsHelp]} Query
setScrollTop={this.setScrollTop} </Button>
)}
{isAddingMixed && this.renderMixedPicker()}
{config.featureToggles.expressions && (
<Button icon="plus" onClick={this.onAddExpressionClick} variant="secondary">
Expression
</Button>
)}
</HorizontalGroup>
);
}
render() {
const { scrollTop, isHelpOpen } = this.state;
const styles = getStyles();
return (
<CustomScrollbar
autoHeightMin="100%"
autoHide={true}
updateAfterMountMs={300}
scrollTop={scrollTop} scrollTop={scrollTop}
setScrollTop={this.setScrollTop}
> >
<>{this.renderQueryBody()}</> <div className={styles.innerWrapper}>
</EditorTabBody> {this.renderTopSection(styles)}
<div className={styles.queriesWrapper}>{this.renderQueries()}</div>
{this.renderAddQueryRow(styles)}
{isHelpOpen && (
<Modal title="Data source help" isOpen={true} onDismiss={this.onCloseHelp}>
<PluginHelp plugin={this.state.currentDS.meta} type="query_help" />
</Modal>
)}
</div>
</CustomScrollbar>
); );
} }
} }
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<typeof getStyles>;

View File

@ -73,9 +73,7 @@ export class QueryEditorRows extends PureComponent<Props> {
render() { render() {
const { props } = this; const { props } = this;
return ( return props.queries.map((query, index) => (
<div className="query-editor-rows">
{props.queries.map((query, index) => (
<QueryEditorRow <QueryEditorRow
dataSourceValue={query.datasource || props.datasource.value} dataSourceValue={query.datasource || props.datasource.value}
key={query.refId} key={query.refId}
@ -89,8 +87,6 @@ export class QueryEditorRows extends PureComponent<Props> {
onMoveQuery={this.onMoveQuery} onMoveQuery={this.onMoveQuery}
inMixedMode={props.datasource.meta.mixed} inMixedMode={props.datasource.meta.mixed}
/> />
))} ));
</div>
);
} }
} }

View File

@ -2,15 +2,25 @@
import React, { PureComponent, ChangeEvent, FocusEvent, ReactText } from 'react'; import React, { PureComponent, ChangeEvent, FocusEvent, ReactText } from 'react';
// Utils // Utils
import { rangeUtil, DataSourceSelectItem } from '@grafana/data'; import { rangeUtil, DataSourceSelectItem, PanelData } from '@grafana/data';
// Components // Components
import { EventsWithValidation, LegacyInputStatus, LegacyForms, ValidationEvents, InlineFormLabel } from '@grafana/ui'; import {
EventsWithValidation,
LegacyInputStatus,
LegacyForms,
ValidationEvents,
InlineFormLabel,
stylesFactory,
} from '@grafana/ui';
import { DataSourceOption } from './DataSourceOption'; import { DataSourceOption } from './DataSourceOption';
const { Input, Switch } = LegacyForms; const { Input, Switch } = LegacyForms;
// Types // Types
import { PanelModel } from '../state'; 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 = { const timeRangeValidationEvents: ValidationEvents = {
[EventsWithValidation.onBlur]: [ [EventsWithValidation.onBlur]: [
@ -33,6 +43,7 @@ const emptyToNull = (value: string) => {
interface Props { interface Props {
panel: PanelModel; panel: PanelModel;
datasource: DataSourceSelectItem; datasource: DataSourceSelectItem;
data: PanelData;
} }
interface State { interface State {
@ -42,6 +53,7 @@ interface State {
maxDataPoints: string | ReactText; maxDataPoints: string | ReactText;
interval: string; interval: string;
hideTimeOverride: boolean; hideTimeOverride: boolean;
isOpen: boolean;
} }
export class QueryOptions extends PureComponent<Props, State> { export class QueryOptions extends PureComponent<Props, State> {
@ -95,6 +107,7 @@ export class QueryOptions extends PureComponent<Props, State> {
maxDataPoints: props.panel.maxDataPoints || '', maxDataPoints: props.panel.maxDataPoints || '',
interval: props.panel.interval || '', interval: props.panel.interval || '',
hideTimeOverride: props.panel.hideTimeOverride || false, hideTimeOverride: props.panel.hideTimeOverride || false,
isOpen: false,
}; };
} }
@ -180,15 +193,57 @@ export class QueryOptions extends PureComponent<Props, State> {
}); });
}; };
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 (
<>
{<div className={styles.collapsedText}>MD = {mdDesc}</div>}
{<div className={styles.collapsedText}>Interval = {intervalDesc}</div>}
</>
);
}
render() { render() {
const { hideTimeOverride } = this.state; const { hideTimeOverride } = this.state;
const { relativeTime, timeShift } = this.state; const { relativeTime, timeShift, isOpen } = this.state;
const styles = getStyles();
return ( return (
<div className="gf-form-inline"> <QueryOperationRow
title="Options"
headerElement={this.renderCollapsedText(styles)}
isOpen={isOpen}
onOpen={this.onOpenOptions}
onClose={this.onCloseOptions}
>
{this.renderOptions()} {this.renderOptions()}
<div className="gf-form"> <div className="gf-form">
<InlineFormLabel>Relative time</InlineFormLabel> <InlineFormLabel width={9}>Relative time</InlineFormLabel>
<Input <Input
type="text" type="text"
className="width-6" className="width-6"
@ -202,7 +257,7 @@ export class QueryOptions extends PureComponent<Props, State> {
</div> </div>
<div className="gf-form"> <div className="gf-form">
<span className="gf-form-label">Time shift</span> <span className="gf-form-label width-9">Time shift</span>
<Input <Input
type="text" type="text"
className="width-6" className="width-6"
@ -216,10 +271,29 @@ export class QueryOptions extends PureComponent<Props, State> {
</div> </div>
{(timeShift || relativeTime) && ( {(timeShift || relativeTime) && (
<div className="gf-form-inline"> <div className="gf-form-inline">
<Switch label="Hide time info" checked={hideTimeOverride} onChange={this.onToggleTimeOverride} /> <Switch
label="Hide time info"
labelClass="width-9"
checked={hideTimeOverride}
onChange={this.onToggleTimeOverride}
/>
</div> </div>
)} )}
</div> </QueryOperationRow>
); );
} }
} }
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<typeof getStyles>;

View File

@ -138,7 +138,7 @@ $divider-border-color: $gray-1;
$tight-form-func-bg: $dark-9; $tight-form-func-bg: $dark-9;
$tight-form-func-highlight-bg: $dark-10; $tight-form-func-highlight-bg: $dark-10;
$modal-backdrop-bg: #141619; $modal-backdrop-bg: #2c3235;
$code-tag-bg: $dark-1; $code-tag-bg: $dark-1;
$code-tag-border: $dark-9; $code-tag-border: $dark-9;

View File

@ -109,6 +109,7 @@ $input-border: 1px solid $input-border-color;
font-size: $font-size-sm; font-size: $font-size-sm;
background-color: $input-label-bg; background-color: $input-label-bg;
height: $input-height; height: $input-height;
line-height: $input-height;
margin-right: $space-xs; margin-right: $space-xs;
border-radius: $input-border-radius; border-radius: $input-border-radius;
justify-content: space-between; justify-content: space-between;

View File

@ -1,9 +1,3 @@
.panel-editor-container {
display: flex;
flex-direction: column;
height: 100%;
}
.panel-wrapper { .panel-wrapper {
height: 100%; height: 100%;
position: relative; 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 { .panel-editor__scroll {
flex-grow: 1; flex-grow: 1;
min-width: 0; min-width: 0;
@ -65,7 +20,7 @@
} }
.panel-editor__content { .panel-editor__content {
padding: 16px; padding: 0 16px 16px 16px;
} }
.panel-in-fullscreen { .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 { .ds-picker {
position: relative; position: relative;
min-width: 200px; min-width: 200px;

View File

@ -11,10 +11,6 @@
color: $gray-2; color: $gray-2;
} }
.query-editor-rows {
margin: 20px 0;
}
.tight-form-func { .tight-form-func {
background: $tight-form-func-bg; background: $tight-form-func-bg;

View File

@ -2,12 +2,9 @@
display: flex; display: flex;
align-content: center; align-content: center;
align-items: center; align-items: center;
padding: 3px 20px 3px 20px; padding: 16px;
position: relative; position: relative;
flex: 0 0 auto; flex: 0 0 auto;
background: $toolbar-bg;
border-radius: 3px;
height: 44px;
} }
.toolbar__heading { .toolbar__heading {