mirror of
https://github.com/grafana/grafana.git
synced 2025-01-17 12:03:26 -06:00
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
This commit is contained in:
parent
7a4c32d703
commit
99acad4448
@ -0,0 +1,37 @@
|
||||
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';
|
@ -1,19 +1,18 @@
|
||||
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';
|
||||
|
@ -1,8 +1,17 @@
|
||||
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 defaultProps: DismissableFeatureInfoBoxProps = {
|
||||
title: 'A title',
|
||||
severity: 'info',
|
||||
url: 'http://www.grafana.com',
|
||||
persistenceId: 'storybook-feature-info-box-persist',
|
||||
featureState: FeatureState.beta,
|
||||
|
||||
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 };
|
||||
children: (
|
||||
<p>
|
||||
The database user should only be granted SELECT permissions on the specified database & 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.
|
||||
</p>
|
||||
),
|
||||
};
|
||||
|
||||
export const basic = () => {
|
||||
const { containerWidth, severity, title, url } = 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 }}>
|
||||
<InfoBox
|
||||
title={title}
|
||||
url={url}
|
||||
severity={severity}
|
||||
onDismiss={() => {
|
||||
alert('onDismiss clicked');
|
||||
}}
|
||||
<div>
|
||||
<div>
|
||||
<DismissableFeatureInfoBox {...args} />
|
||||
</div>
|
||||
<div
|
||||
className={css`
|
||||
margin-top: 24px;
|
||||
`}
|
||||
>
|
||||
<p>
|
||||
The database user should only be granted SELECT permissions on the specified database & 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.
|
||||
</p>
|
||||
</InfoBox>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const featureInfoBox = () => {
|
||||
const { containerWidth } = getKnobs();
|
||||
|
||||
return (
|
||||
<div style={{ width: containerWidth }}>
|
||||
<FeatureInfoBox
|
||||
title="Transformations"
|
||||
url={'http://www.grafana.com'}
|
||||
featureState={FeatureState.beta}
|
||||
onDismiss={() => {
|
||||
alert('onDismiss clicked');
|
||||
}}
|
||||
>
|
||||
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'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);
|
||||
|
Loading…
Reference in New Issue
Block a user