mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 06:32:28 +08:00
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>
This commit is contained in:
@ -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}`}
|
||||
|
Reference in New Issue
Block a user