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;
}
const readOnlyVariables = dashboard.meta.isSnapshot ?? false;
return (
<div className="submenu-controls">
<form aria-label="Template variables" className={styles}>
<SubMenuItems variables={variables} />
<SubMenuItems variables={variables} readOnly={readOnlyVariables} />
</form>
<Annotations
annotations={annotations}

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

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

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

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

@ -11,15 +11,24 @@ interface Props {
filterValue?: string;
onChange: (item: SelectableValue<string>) => void;
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);
return (
<div className="gf-form" data-testid="AdHocFilterValue-value-wrapper">
<SegmentAsync
className="query-segment-value"
disabled={disabled}
placeholder={placeHolder}
value={filterValue}
onChange={onChange}

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

@ -6,6 +6,7 @@ import { Segment } from '@grafana/ui';
interface Props {
value: string;
onChange: (item: SelectableValue<string>) => void;
disabled?: boolean;
}
const options = ['=', '!=', '<', '>', '=~', '!~'].map<SelectableValue<string>>((value) => ({
@ -13,6 +14,14 @@ const options = ['=', '!=', '<', '>', '=~', '!~'].map<SelectableValue<string>>((
value,
}));
export const OperatorSegment: FC<Props> = ({ value, onChange }) => {
return <Segment className="query-segment-operator" value={value} options={options} onChange={onChange} />;
export const OperatorSegment: FC<Props> = ({ value, disabled, 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> = {
variable: v,
onVariableChange,
readOnly: false,
};
const Picker = optionPickerFactory();
const optionsPicker: OptionsPickerState = { ...initialOptionPickerState, ...pickerState };

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

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

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

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

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

Loading…
Cancel
Save