Commit 99acad44 by Oscar Kilhed Committed by GitHub

GrafanaUI: Add a way to persistently close InfoBox (#30716)

* GrafanaUI: Add a way to persistently close InfoBox

InfoBox and FeatureInfoBox can take up a lot of screen realestate. This makes it easy to let the user close the boxes.

* Migrate InfoBox story to controls
parent 7a4c32d7
import React from 'react';
import { useLocalStorage } from 'react-use';
import { FeatureInfoBox, FeatureInfoBoxProps } from './FeatureInfoBox';
export const FEATUREINFOBOX_PERSISTENCE_ID_PREFIX = 'grafana-ui.components.InfoBox.FeatureInfoBox';
export interface DismissableFeatureInfoBoxProps extends FeatureInfoBoxProps {
/** Unique id under which this instance will be persisted. */
persistenceId: string;
}
/**
@internal
Wraps FeatureInfoBox and perists if a user has dismissed the box in local storage.
*/
export const DismissableFeatureInfoBox = React.memo(
React.forwardRef<HTMLDivElement, DismissableFeatureInfoBoxProps>(
({ persistenceId, onDismiss, ...otherProps }, ref) => {
const localStorageKey = FEATUREINFOBOX_PERSISTENCE_ID_PREFIX.concat(persistenceId);
const [dismissed, setDismissed] = useLocalStorage(localStorageKey, { isDismissed: false });
const dismiss = () => {
setDismissed({ isDismissed: true });
if (onDismiss) {
onDismiss();
}
};
if (dismissed.isDismissed) {
return null;
}
return <FeatureInfoBox onDismiss={dismiss} ref={ref} {...otherProps}></FeatureInfoBox>;
}
)
);
DismissableFeatureInfoBox.displayName = 'DismissableFeatureInfoBox';
import React from 'react';
import { InfoBox, InfoBoxProps } from './InfoBox';
import { FeatureState, GrafanaTheme } from '@grafana/data';
import { stylesFactory, useTheme } from '../../themes';
import { stylesFactory, useStyles } from '../../themes';
import { Badge, BadgeProps } from '../Badge/Badge';
import { css } from 'emotion';
interface FeatureInfoBoxProps extends Omit<InfoBoxProps, 'branded' | 'title' | 'urlTitle'> {
export interface FeatureInfoBoxProps extends Omit<InfoBoxProps, 'branded' | 'title' | 'urlTitle'> {
title: string;
featureState?: FeatureState;
}
export const FeatureInfoBox = React.memo(
React.forwardRef<HTMLDivElement, FeatureInfoBoxProps>(({ title, featureState, ...otherProps }, ref) => {
const theme = useTheme();
const styles = getFeatureInfoBoxStyles(theme);
const styles = useStyles(getFeatureInfoBoxStyles);
const titleEl = featureState ? (
<>
......@@ -25,7 +24,7 @@ export const FeatureInfoBox = React.memo(
) : (
<h3>{title}</h3>
);
return <InfoBox branded title={titleEl} urlTitle="Read documentation" {...otherProps} />;
return <InfoBox branded title={titleEl} urlTitle="Read documentation" ref={ref} {...otherProps} />;
})
);
FeatureInfoBox.displayName = 'FeatureInfoBox';
......
import React from 'react';
import { number, select, text } from '@storybook/addon-knobs';
import { FeatureState } from '@grafana/data';
import { InfoBox, FeatureInfoBox } from '@grafana/ui';
import mdx from './InfoBox.mdx';
import {
DismissableFeatureInfoBox,
DismissableFeatureInfoBoxProps,
FEATUREINFOBOX_PERSISTENCE_ID_PREFIX,
} from './DismissableFeatureInfoBox';
import { Button } from '../Button';
import { css } from 'emotion';
import { Story } from '@storybook/react';
import { FeatureInfoBoxProps } from './FeatureInfoBox';
import { InfoBoxProps } from './InfoBox';
export default {
title: 'Layout/InfoBox',
......@@ -13,67 +22,64 @@ export default {
page: mdx,
},
},
argTypes: {
onDismiss: { action: 'Dismissed' },
featureState: {
control: { type: 'select', options: ['alpha', 'beta', undefined] },
},
children: {
table: {
disable: true,
},
},
},
};
const getKnobs = () => {
const containerWidth = number('Container width', 800, {
range: true,
min: 100,
max: 1500,
step: 100,
});
const title = text('Title', 'User permission');
const url = text('Url', 'http://docs.grafana.org/features/datasources/mysql/');
const severity = select('Severity', ['success', 'warning', 'error', 'info'], 'info');
return { containerWidth, severity, title, url };
};
export const basic = () => {
const { containerWidth, severity, title, url } = getKnobs();
const defaultProps: DismissableFeatureInfoBoxProps = {
title: 'A title',
severity: 'info',
url: 'http://www.grafana.com',
persistenceId: 'storybook-feature-info-box-persist',
featureState: FeatureState.beta,
return (
<div style={{ width: containerWidth }}>
<InfoBox
title={title}
url={url}
severity={severity}
onDismiss={() => {
alert('onDismiss clicked');
}}
>
children: (
<p>
The database user should only be granted SELECT permissions on the specified database &amp; tables you want to
query. Grafana does not validate that queries are safe so queries can contain any SQL statement. For example,
statements like <code>USE otherdb;</code> and <code>DROP TABLE user;</code> would be executed. To protect
against this we <strong>Highly</strong> recommend you create a specific MySQL user with restricted
permissions.
statements like <code>USE otherdb;</code> and <code>DROP TABLE user;</code> would be executed. To protect against
this we <strong>Highly</strong> recommend you create a specific MySQL user with restricted permissions.
</p>
</InfoBox>
</div>
);
),
};
export const featureInfoBox = () => {
const { containerWidth } = getKnobs();
const InfoBoxTemplate: Story<InfoBoxProps> = (args) => <InfoBox {...args} />;
export const infoBox = InfoBoxTemplate.bind({});
infoBox.args = defaultProps;
const FeatureInfoBoxTemplate: Story<FeatureInfoBoxProps> = (args) => <FeatureInfoBox {...args}></FeatureInfoBox>;
export const featureInfoBox = FeatureInfoBoxTemplate.bind({});
featureInfoBox.args = defaultProps;
const DismissableTemplate: Story<DismissableFeatureInfoBoxProps> = (args) => {
const onResetClick = () => {
localStorage.removeItem(FEATUREINFOBOX_PERSISTENCE_ID_PREFIX.concat(args.persistenceId));
location.reload();
};
return (
<div style={{ width: containerWidth }}>
<FeatureInfoBox
title="Transformations"
url={'http://www.grafana.com'}
featureState={FeatureState.beta}
onDismiss={() => {
alert('onDismiss clicked');
}}
<div>
<div>
<DismissableFeatureInfoBox {...args} />
</div>
<div
className={css`
margin-top: 24px;
`}
>
Transformations allow you to join, calculate, re-order, hide and rename your query results before being
visualized. <br />
Many transforms are not suitable if you&apos;re using the Graph visualisation as it currently only supports time
series. <br />
It can help to switch to Table visualisation to understand what a transformation is doing.
</FeatureInfoBox>
<Button onClick={onResetClick}>Reset DismissableFeatureInfoBox</Button>
</div>
</div>
);
};
export const dismissableFeatureInfoBox = DismissableTemplate.bind({});
dismissableFeatureInfoBox.args = defaultProps;
......@@ -34,7 +34,7 @@ export const InfoBox = React.memo(
({ title, className, children, branded, url, urlTitle, onDismiss, severity = 'info', ...otherProps }, ref) => {
const theme = useTheme();
const styles = getInfoBoxStyles(theme, severity);
const wrapperClassName = branded ? cx(styles.wrapperBranded, className) : cx(styles.wrapper, className);
const wrapperClassName = cx(branded ? styles.wrapperBranded : styles.wrapper, className);
return (
<div className={wrapperClassName} {...otherProps} ref={ref}>
......
......@@ -103,6 +103,7 @@ export { DataLinksContextMenu } from './DataLinks/DataLinksContextMenu';
export { SeriesIcon } from './VizLegend/SeriesIcon';
export { InfoBox } from './InfoBox/InfoBox';
export { FeatureBadge, FeatureInfoBox } from './InfoBox/FeatureInfoBox';
export { DismissableFeatureInfoBox } from './InfoBox/DismissableFeatureInfoBox';
export { JSONFormatter } from './JSONFormatter/JSONFormatter';
export { JsonExplorer } from './JSONFormatter/json_explorer/json_explorer';
......
......@@ -4,11 +4,13 @@ import {
Button,
Container,
CustomScrollbar,
FeatureInfoBox,
stylesFactory,
Themeable,
DismissableFeatureInfoBox,
useTheme,
ValuePicker,
VerticalGroup,
withTheme,
} from '@grafana/ui';
import {
DataFrame,
......@@ -31,7 +33,7 @@ import { TransformationsEditorTransformation } from './types';
import { PanelNotSupported } from '../PanelEditor/PanelNotSupported';
import { AppNotificationSeverity } from '../../../../types';
interface TransformationsEditorProps {
interface TransformationsEditorProps extends Themeable {
panel: PanelModel;
}
......@@ -40,7 +42,7 @@ interface State {
transformations: TransformationsEditorTransformation[];
}
export class TransformationsEditor extends React.PureComponent<TransformationsEditorProps, State> {
class UnThemedTransformationsEditor extends React.PureComponent<TransformationsEditorProps, State> {
subscription?: Unsubscribable;
constructor(props: TransformationsEditorProps) {
......@@ -208,9 +210,16 @@ export class TransformationsEditor extends React.PureComponent<TransformationsEd
renderNoAddedTransformsState() {
return (
<VerticalGroup spacing={'lg'}>
<>
<Container grow={1}>
<FeatureInfoBox title="Transformations" url={getDocsLink(DocsId.Transformations)}>
<DismissableFeatureInfoBox
title="Transformations"
className={css`
margin-bottom: ${this.props.theme.spacing.lg};
`}
persistenceId="transformationsFeaturesInfoBox"
url={getDocsLink(DocsId.Transformations)}
>
<p>
Transformations allow you to join, calculate, re-order, hide and rename your query results before being
visualized. <br />
......@@ -218,7 +227,7 @@ export class TransformationsEditor extends React.PureComponent<TransformationsEd
supports time series. <br />
It can help to switch to Table visualization to understand what a transformation is doing. <br />
</p>
</FeatureInfoBox>
</DismissableFeatureInfoBox>
</Container>
<VerticalGroup>
{standardTransformersRegistry.list().map((t) => {
......@@ -236,7 +245,7 @@ export class TransformationsEditor extends React.PureComponent<TransformationsEd
);
})}
</VerticalGroup>
</VerticalGroup>
</>
);
}
......@@ -299,3 +308,5 @@ const getTransformationCardStyles = stylesFactory((theme: GrafanaTheme) => {
`,
};
});
export const TransformationsEditor = withTheme(UnThemedTransformationsEditor);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment