Dashboards: Disable variable pickers for snapshots (#52827)

* user essentials mob! 🔱

lastFile:public/app/features/variables/textbox/TextBoxVariablePicker.tsx

* user essentials mob! 🔱

* user essentials mob! 🔱

lastFile:public/app/features/variables/adhoc/picker/AdHocFilter.tsx

* finish up disabling variables in snapshots

* remove accident

* use theme.spacing instead of the v1 shim

Co-authored-by: Joao Silva <joao.silva@grafana.com>
Co-authored-by: Leodegario Pasakdal <leodegario.pasakdal@grafana.com>
pull/53007/head
Josh Hunt 3 years ago committed by GitHub
parent d3323f870e
commit 06d78ea904
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      public/app/features/dashboard/components/SubMenu/SubMenu.tsx
  2. 5
      public/app/features/dashboard/components/SubMenu/SubMenuItems.tsx
  3. 32
      public/app/features/variables/adhoc/picker/AdHocFilter.tsx
  4. 5
      public/app/features/variables/adhoc/picker/AdHocFilterKey.tsx
  5. 6
      public/app/features/variables/adhoc/picker/AdHocFilterRenderer.tsx
  6. 11
      public/app/features/variables/adhoc/picker/AdHocFilterValue.tsx
  7. 1
      public/app/features/variables/adhoc/picker/AdHocPicker.tsx
  8. 13
      public/app/features/variables/adhoc/picker/OperatorSegment.tsx
  9. 1
      public/app/features/variables/pickers/OptionsPicker/OptionPicker.test.tsx
  10. 1
      public/app/features/variables/pickers/OptionsPicker/OptionsPicker.tsx
  11. 3
      public/app/features/variables/pickers/PickerRenderer.tsx
  12. 32
      public/app/features/variables/pickers/shared/VariableLink.tsx
  13. 1
      public/app/features/variables/pickers/types.ts
  14. 3
      public/app/features/variables/textbox/TextBoxVariablePicker.tsx

@ -49,10 +49,12 @@ class SubMenuUnConnected extends PureComponent<Props> {
return null; return null;
} }
const readOnlyVariables = dashboard.meta.isSnapshot ?? false;
return ( return (
<div className="submenu-controls"> <div className="submenu-controls">
<form aria-label="Template variables" className={styles}> <form aria-label="Template variables" className={styles}>
<SubMenuItems variables={variables} /> <SubMenuItems variables={variables} readOnly={readOnlyVariables} />
</form> </form>
<Annotations <Annotations
annotations={annotations} annotations={annotations}

@ -7,9 +7,10 @@ import { VariableHide, VariableModel } from '../../../variables/types';
interface Props { interface Props {
variables: VariableModel[]; variables: VariableModel[];
readOnly?: boolean;
} }
export const SubMenuItems: FunctionComponent<Props> = ({ variables }) => { export const SubMenuItems: FunctionComponent<Props> = ({ variables, readOnly }) => {
const [visibleVariables, setVisibleVariables] = useState<VariableModel[]>([]); const [visibleVariables, setVisibleVariables] = useState<VariableModel[]>([]);
useEffect(() => { useEffect(() => {
@ -29,7 +30,7 @@ export const SubMenuItems: FunctionComponent<Props> = ({ variables }) => {
className="submenu-item gf-form-inline" className="submenu-item gf-form-inline"
data-testid={selectors.pages.Dashboard.SubMenu.submenuItem} data-testid={selectors.pages.Dashboard.SubMenu.submenuItem}
> >
<PickerRenderer variable={variable} /> <PickerRenderer variable={variable} readOnly={readOnly} />
</div> </div>
); );
})} })}

@ -1,6 +1,7 @@
import React, { PureComponent, ReactNode } from 'react'; import React, { PureComponent, ReactNode } from 'react';
import { DataSourceRef, SelectableValue } from '@grafana/data'; import { DataSourceRef, SelectableValue } from '@grafana/data';
import { Segment } from '@grafana/ui';
import { AdHocVariableFilter } from 'app/features/variables/types'; import { AdHocVariableFilter } from 'app/features/variables/types';
import { AdHocFilterBuilder } from './AdHocFilterBuilder'; import { AdHocFilterBuilder } from './AdHocFilterBuilder';
@ -17,6 +18,7 @@ interface Props {
// Passes options to the datasources getTagKeys(options?: any) method // Passes options to the datasources getTagKeys(options?: any) method
// which is called to fetch the available filter key options in AdHocFilterKey.tsx // which is called to fetch the available filter key options in AdHocFilterKey.tsx
getTagKeysOptions?: any; getTagKeysOptions?: any;
disabled?: boolean;
} }
/** /**
@ -47,35 +49,43 @@ export class AdHocFilter extends PureComponent<Props> {
}; };
render() { render() {
const { filters } = this.props; const { filters, disabled } = this.props;
return ( return (
<div className="gf-form-inline"> <div className="gf-form-inline">
{this.renderFilters(filters)} {this.renderFilters(filters, disabled)}
<AdHocFilterBuilder
datasource={this.props.datasource!} {!disabled && (
appendBefore={filters.length > 0 ? <ConditionSegment label="AND" /> : null} <AdHocFilterBuilder
onCompleted={this.appendFilterToVariable} datasource={this.props.datasource!}
getTagKeysOptions={this.props.getTagKeysOptions} appendBefore={filters.length > 0 ? <ConditionSegment label="AND" /> : null}
/> onCompleted={this.appendFilterToVariable}
getTagKeysOptions={this.props.getTagKeysOptions}
/>
)}
</div> </div>
); );
} }
renderFilters(filters: AdHocVariableFilter[]) { renderFilters(filters: AdHocVariableFilter[], disabled?: boolean) {
if (filters.length === 0 && disabled) {
return <Segment disabled={disabled} value="No filters" options={[]} onChange={() => {}} />;
}
return filters.reduce((segments: ReactNode[], filter, index) => { return filters.reduce((segments: ReactNode[], filter, index) => {
if (segments.length > 0) { if (segments.length > 0) {
segments.push(<ConditionSegment label="AND" key={`condition-${index}`} />); segments.push(<ConditionSegment label="AND" key={`condition-${index}`} />);
} }
segments.push(this.renderFilterSegments(filter, index)); segments.push(this.renderFilterSegments(filter, index, disabled));
return segments; return segments;
}, []); }, []);
} }
renderFilterSegments(filter: AdHocVariableFilter, index: number) { renderFilterSegments(filter: AdHocVariableFilter, index: number, disabled?: boolean) {
return ( return (
<React.Fragment key={`filter-${index}`}> <React.Fragment key={`filter-${index}`}>
<AdHocFilterRenderer <AdHocFilterRenderer
disabled={disabled}
datasource={this.props.datasource!} datasource={this.props.datasource!}
filter={filter} filter={filter}
onKeyChange={this.onChange(index, 'key')} onKeyChange={this.onChange(index, 'key')}

@ -10,10 +10,11 @@ interface Props {
filterKey: string | null; filterKey: string | null;
onChange: (item: SelectableValue<string | null>) => void; onChange: (item: SelectableValue<string | null>) => void;
getTagKeysOptions?: any; getTagKeysOptions?: any;
disabled?: boolean;
} }
const MIN_WIDTH = 90; const MIN_WIDTH = 90;
export const AdHocFilterKey: FC<Props> = ({ datasource, onChange, filterKey, getTagKeysOptions }) => { export const AdHocFilterKey: FC<Props> = ({ datasource, onChange, disabled, filterKey, getTagKeysOptions }) => {
const loadKeys = () => fetchFilterKeys(datasource, getTagKeysOptions); const loadKeys = () => fetchFilterKeys(datasource, getTagKeysOptions);
const loadKeysWithRemove = () => fetchFilterKeysWithRemove(datasource, getTagKeysOptions); const loadKeysWithRemove = () => fetchFilterKeysWithRemove(datasource, getTagKeysOptions);
@ -21,6 +22,7 @@ export const AdHocFilterKey: FC<Props> = ({ datasource, onChange, filterKey, get
return ( return (
<div className="gf-form" data-testid="AdHocFilterKey-add-key-wrapper"> <div className="gf-form" data-testid="AdHocFilterKey-add-key-wrapper">
<SegmentAsync <SegmentAsync
disabled={disabled}
className="query-segment-key" className="query-segment-key"
Component={plusSegment} Component={plusSegment}
value={filterKey} value={filterKey}
@ -35,6 +37,7 @@ export const AdHocFilterKey: FC<Props> = ({ datasource, onChange, filterKey, get
return ( return (
<div className="gf-form" data-testid="AdHocFilterKey-key-wrapper"> <div className="gf-form" data-testid="AdHocFilterKey-key-wrapper">
<SegmentAsync <SegmentAsync
disabled={disabled}
className="query-segment-key" className="query-segment-key"
value={filterKey} value={filterKey}
onChange={onChange} onChange={onChange}

@ -15,6 +15,7 @@ interface Props {
onValueChange: (item: SelectableValue<string>) => void; onValueChange: (item: SelectableValue<string>) => void;
placeHolder?: string; placeHolder?: string;
getTagKeysOptions?: any; getTagKeysOptions?: any;
disabled?: boolean;
} }
export const AdHocFilterRenderer: FC<Props> = ({ export const AdHocFilterRenderer: FC<Props> = ({
@ -25,19 +26,22 @@ export const AdHocFilterRenderer: FC<Props> = ({
onValueChange, onValueChange,
placeHolder, placeHolder,
getTagKeysOptions, getTagKeysOptions,
disabled,
}) => { }) => {
return ( return (
<> <>
<AdHocFilterKey <AdHocFilterKey
disabled={disabled}
datasource={datasource} datasource={datasource}
filterKey={key} filterKey={key}
onChange={onKeyChange} onChange={onKeyChange}
getTagKeysOptions={getTagKeysOptions} getTagKeysOptions={getTagKeysOptions}
/> />
<div className="gf-form"> <div className="gf-form">
<OperatorSegment value={operator} onChange={onOperatorChange} /> <OperatorSegment disabled={disabled} value={operator} onChange={onOperatorChange} />
</div> </div>
<AdHocFilterValue <AdHocFilterValue
disabled={disabled}
datasource={datasource} datasource={datasource}
filterKey={key} filterKey={key}
filterValue={value} filterValue={value}

@ -11,15 +11,24 @@ interface Props {
filterValue?: string; filterValue?: string;
onChange: (item: SelectableValue<string>) => void; onChange: (item: SelectableValue<string>) => void;
placeHolder?: string; placeHolder?: string;
disabled?: boolean;
} }
export const AdHocFilterValue: FC<Props> = ({ datasource, onChange, filterKey, filterValue, placeHolder }) => { export const AdHocFilterValue: FC<Props> = ({
datasource,
disabled,
onChange,
filterKey,
filterValue,
placeHolder,
}) => {
const loadValues = () => fetchFilterValues(datasource, filterKey); const loadValues = () => fetchFilterValues(datasource, filterKey);
return ( return (
<div className="gf-form" data-testid="AdHocFilterValue-value-wrapper"> <div className="gf-form" data-testid="AdHocFilterValue-value-wrapper">
<SegmentAsync <SegmentAsync
className="query-segment-value" className="query-segment-value"
disabled={disabled}
placeholder={placeHolder} placeholder={placeHolder}
value={filterValue} value={filterValue}
onChange={onChange} onChange={onChange}

@ -48,6 +48,7 @@ export class AdHocPickerUnconnected extends PureComponent<Props> {
<AdHocFilter <AdHocFilter
datasource={datasource} datasource={datasource}
filters={filters} filters={filters}
disabled={this.props.readOnly}
addFilter={this.addFilter} addFilter={this.addFilter}
removeFilter={this.removeFilter} removeFilter={this.removeFilter}
changeFilter={this.changeFilter} changeFilter={this.changeFilter}

@ -6,6 +6,7 @@ import { Segment } from '@grafana/ui';
interface Props { interface Props {
value: string; value: string;
onChange: (item: SelectableValue<string>) => void; onChange: (item: SelectableValue<string>) => void;
disabled?: boolean;
} }
const options = ['=', '!=', '<', '>', '=~', '!~'].map<SelectableValue<string>>((value) => ({ const options = ['=', '!=', '<', '>', '=~', '!~'].map<SelectableValue<string>>((value) => ({
@ -13,6 +14,14 @@ const options = ['=', '!=', '<', '>', '=~', '!~'].map<SelectableValue<string>>((
value, value,
})); }));
export const OperatorSegment: FC<Props> = ({ value, onChange }) => { export const OperatorSegment: FC<Props> = ({ value, disabled, onChange }) => {
return <Segment className="query-segment-operator" value={value} options={options} onChange={onChange} />; return (
<Segment
className="query-segment-operator"
value={value}
disabled={disabled}
options={options}
onChange={onChange}
/>
);
}; };

@ -37,6 +37,7 @@ function setupTestContext({ pickerState = {}, variable = {} }: Args = {}) {
const props: VariablePickerProps<VariableWithMultiSupport | VariableWithOptions> = { const props: VariablePickerProps<VariableWithMultiSupport | VariableWithOptions> = {
variable: v, variable: v,
onVariableChange, onVariableChange,
readOnly: false,
}; };
const Picker = optionPickerFactory(); const Picker = optionPickerFactory();
const optionsPicker: OptionsPickerState = { ...initialOptionPickerState, ...pickerState }; const optionsPicker: OptionsPickerState = { ...initialOptionPickerState, ...pickerState };

@ -130,6 +130,7 @@ export const optionPickerFactory = <Model extends VariableWithOptions | Variable
onClick={this.onShowOptions} onClick={this.onShowOptions}
loading={loading} loading={loading}
onCancel={this.onCancel} onCancel={this.onCancel}
disabled={this.props.readOnly}
/> />
); );
} }

@ -8,6 +8,7 @@ import { VariableHide, VariableModel } from '../types';
interface Props { interface Props {
variable: VariableModel; variable: VariableModel;
readOnly?: boolean;
} }
export const PickerRenderer: FunctionComponent<Props> = (props) => { export const PickerRenderer: FunctionComponent<Props> = (props) => {
@ -21,7 +22,7 @@ export const PickerRenderer: FunctionComponent<Props> = (props) => {
<div className="gf-form"> <div className="gf-form">
<PickerLabel variable={props.variable} /> <PickerLabel variable={props.variable} />
{props.variable.hide !== VariableHide.hideVariable && PickerToRender && ( {props.variable.hide !== VariableHide.hideVariable && PickerToRender && (
<PickerToRender variable={props.variable} /> <PickerToRender variable={props.variable} readOnly={props.readOnly ?? false} />
)} )}
</div> </div>
); );

@ -1,23 +1,24 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import React, { FC, MouseEvent, useCallback } from 'react'; import React, { FC, MouseEvent, useCallback } from 'react';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { Icon, Tooltip, useStyles } from '@grafana/ui'; import { Icon, Tooltip, useStyles2 } from '@grafana/ui';
interface Props { interface Props {
onClick: () => void; onClick: () => void;
text: string; text: string;
loading: boolean; loading: boolean;
onCancel: () => void; onCancel: () => void;
disabled: boolean; // todo: optional?
/** /**
* htmlFor, needed for the label * htmlFor, needed for the label
*/ */
id: string; id: string;
} }
export const VariableLink: FC<Props> = ({ loading, onClick: propsOnClick, text, onCancel, id }) => { export const VariableLink: FC<Props> = ({ loading, disabled, onClick: propsOnClick, text, onCancel, id }) => {
const styles = useStyles(getStyles); const styles = useStyles2(getStyles);
const onClick = useCallback( const onClick = useCallback(
(event: MouseEvent<HTMLButtonElement>) => { (event: MouseEvent<HTMLButtonElement>) => {
event.stopPropagation(); event.stopPropagation();
@ -50,6 +51,7 @@ export const VariableLink: FC<Props> = ({ loading, onClick: propsOnClick, text,
aria-controls={`options-${id}`} aria-controls={`options-${id}`}
id={id} id={id}
title={text} title={text}
disabled={disabled}
> >
<VariableLinkText text={text} /> <VariableLinkText text={text} />
<Icon aria-hidden name="angle-down" size="sm" /> <Icon aria-hidden name="angle-down" size="sm" />
@ -62,7 +64,7 @@ interface VariableLinkTextProps {
} }
const VariableLinkText: FC<VariableLinkTextProps> = ({ text }) => { const VariableLinkText: FC<VariableLinkTextProps> = ({ text }) => {
const styles = useStyles(getStyles); const styles = useStyles2(getStyles);
return <span className={styles.textAndTags}>{text}</span>; return <span className={styles.textAndTags}>{text}</span>;
}; };
@ -88,28 +90,34 @@ const LoadingIndicator: FC<Pick<Props, 'onCancel'>> = ({ onCancel }) => {
); );
}; };
const getStyles = (theme: GrafanaTheme) => ({ const getStyles = (theme: GrafanaTheme2) => ({
container: css` container: css`
max-width: 500px; max-width: 500px;
padding-right: 10px; padding-right: 10px;
padding: 0 ${theme.spacing.sm}; padding: 0 ${theme.spacing(1)};
background-color: ${theme.colors.formInputBg}; background-color: ${theme.components.input.background};
border: 1px solid ${theme.colors.formInputBorder}; border: 1px solid ${theme.components.input.borderColor};
border-radius: ${theme.border.radius.sm}; border-radius: ${theme.shape.borderRadius(1)};
display: flex; display: flex;
align-items: center; align-items: center;
color: ${theme.colors.text}; color: ${theme.colors.text};
height: ${theme.height.md}px; height: ${theme.spacing(theme.components.height.md)};
.label-tag { .label-tag {
margin: 0 5px; margin: 0 5px;
} }
&:disabled {
background-color: ${theme.colors.action.disabledBackground};
color: ${theme.colors.action.disabledText};
border: 1px solid ${theme.colors.action.disabledBackground};
}
`, `,
textAndTags: css` textAndTags: css`
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
margin-right: ${theme.spacing.xxs}; margin-right: ${theme.spacing(0.25)};
user-select: none; user-select: none;
`, `,
}); });

@ -2,6 +2,7 @@ import { VariableModel } from '../types';
export interface VariablePickerProps<Model extends VariableModel = VariableModel> { export interface VariablePickerProps<Model extends VariableModel = VariableModel> {
variable: Model; variable: Model;
readOnly: boolean;
onVariableChange?: (variable: Model) => void; onVariableChange?: (variable: Model) => void;
} }

@ -12,7 +12,7 @@ import { toVariablePayload } from '../utils';
export interface Props extends VariablePickerProps<TextBoxVariableModel> {} export interface Props extends VariablePickerProps<TextBoxVariableModel> {}
export function TextBoxVariablePicker({ variable, onVariableChange }: Props): ReactElement { export function TextBoxVariablePicker({ variable, onVariableChange, readOnly }: Props): ReactElement {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [updatedValue, setUpdatedValue] = useState(variable.current.value); const [updatedValue, setUpdatedValue] = useState(variable.current.value);
useEffect(() => { useEffect(() => {
@ -68,6 +68,7 @@ export function TextBoxVariablePicker({ variable, onVariableChange }: Props): Re
value={updatedValue} value={updatedValue}
onChange={onChange} onChange={onChange}
onBlur={onBlur} onBlur={onBlur}
disabled={readOnly}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
placeholder="Enter variable value" placeholder="Enter variable value"
id={`var-${variable.id}`} id={`var-${variable.id}`}

Loading…
Cancel
Save