diff --git a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.story.tsx b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.story.tsx
new file mode 100644
index 00000000000..6d7c9ab97b5
--- /dev/null
+++ b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.story.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import { storiesOf } from '@storybook/react';
+import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
+import { CallToActionCard } from './CallToActionCard';
+import { select, text } from '@storybook/addon-knobs';
+import { LargeButton } from '../Button/Button';
+import { action } from '@storybook/addon-actions';
+
+const CallToActionCardStories = storiesOf('UI/CallToActionCard', module);
+
+CallToActionCardStories.add('default', () => {
+ const ctaElements: { [key: string]: JSX.Element } = {
+ custom:
This is just H1 tag, you can any component as CTA element
,
+ button: (
+
+ Add datasource
+
+ ),
+ };
+ const ctaElement = select(
+ 'Call to action element',
+ {
+ Custom: 'custom',
+ Button: 'button',
+ },
+ 'custom'
+ );
+
+ return renderComponentWithTheme(CallToActionCard, {
+ message: text('Call to action message', 'Renders message prop content'),
+ callToActionElement: ctaElements[ctaElement],
+ footer: text('Call to action footer', 'Renders footer prop content'),
+ });
+});
diff --git a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.test.tsx b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.test.tsx
new file mode 100644
index 00000000000..afd3d6c87e5
--- /dev/null
+++ b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.test.tsx
@@ -0,0 +1,38 @@
+import React, { useContext } from 'react';
+import { render } from 'enzyme';
+import { CallToActionCard, CallToActionCardProps } from './CallToActionCard';
+import { ThemeContext } from '../../themes';
+
+type Omit = Pick>;
+
+const TestRenderer = (props: Omit) => {
+ const theme = useContext(ThemeContext);
+ return ;
+};
+
+describe('CallToActionCard', () => {
+ describe('rendering', () => {
+ it('when no message and footer provided', () => {
+ const tree = render(Click me} />);
+ expect(tree).toMatchSnapshot();
+ });
+
+ it('when message and no footer provided', () => {
+ const tree = render(
+ Click me} />
+ );
+ expect(tree).toMatchSnapshot();
+ });
+
+ it('when message and footer provided', () => {
+ const tree = render(
+ Click me}
+ />
+ );
+ expect(tree).toMatchSnapshot();
+ });
+ });
+});
diff --git a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.tsx b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.tsx
new file mode 100644
index 00000000000..002382762cc
--- /dev/null
+++ b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.tsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import { Themeable, GrafanaTheme } from '../../types/theme';
+import { selectThemeVariant } from '../../themes/selectThemeVariant';
+import { css, cx } from 'emotion';
+
+export interface CallToActionCardProps extends Themeable {
+ message?: string | JSX.Element;
+ callToActionElement: JSX.Element;
+ footer?: string | JSX.Element;
+ className?: string;
+}
+
+const getCallToActionCardStyles = (theme: GrafanaTheme) => ({
+ wrapper: css`
+ label: call-to-action-card;
+ padding: ${theme.spacing.lg};
+ background: ${selectThemeVariant({ light: theme.colors.gray6, dark: theme.colors.grayBlue }, theme.type)};
+ border-radius: ${theme.border.radius.md};
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ `,
+ message: css`
+ margin-bottom: ${theme.spacing.lg};
+ font-style: italic;
+ `,
+ footer: css`
+ margin-top: ${theme.spacing.lg};
+ `,
+});
+
+export const CallToActionCard: React.FunctionComponent = ({
+ message,
+ callToActionElement,
+ footer,
+ theme,
+ className,
+}) => {
+ const css = getCallToActionCardStyles(theme);
+
+ return (
+
+ {message &&
{message}
}
+ {callToActionElement}
+ {footer &&
{footer}
}
+
+ );
+};
diff --git a/packages/grafana-ui/src/components/CallToActionCard/__snapshots__/CallToActionCard.test.tsx.snap b/packages/grafana-ui/src/components/CallToActionCard/__snapshots__/CallToActionCard.test.tsx.snap
new file mode 100644
index 00000000000..4c677f7bffd
--- /dev/null
+++ b/packages/grafana-ui/src/components/CallToActionCard/__snapshots__/CallToActionCard.test.tsx.snap
@@ -0,0 +1,52 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`CallToActionCard rendering when message and footer provided 1`] = `
+
+
+ Click button bellow
+
+
+ Click me
+
+
+ footer content
+
+
+`;
+
+exports[`CallToActionCard rendering when message and no footer provided 1`] = `
+
+`;
+
+exports[`CallToActionCard rendering when no message and footer provided 1`] = `
+
+`;
diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts
index a79e0ede6b8..2cc86c83db2 100644
--- a/packages/grafana-ui/src/components/index.ts
+++ b/packages/grafana-ui/src/components/index.ts
@@ -37,3 +37,5 @@ export { Gauge } from './Gauge/Gauge';
export { Graph } from './Graph/Graph';
export { BarGauge } from './BarGauge/BarGauge';
export { VizRepeater } from './VizRepeater/VizRepeater';
+
+export { CallToActionCard } from './CallToActionCard/CallToActionCard';
diff --git a/public/app/core/components/EmptyListCTA/EmptyListCTA.test.tsx b/public/app/core/components/EmptyListCTA/EmptyListCTA.test.tsx
deleted file mode 100644
index ac07b671c31..00000000000
--- a/public/app/core/components/EmptyListCTA/EmptyListCTA.test.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import EmptyListCTA from './EmptyListCTA';
-
-const model = {
- title: 'Title',
- buttonIcon: 'ga css class',
- buttonLink: 'http://url/to/destination',
- buttonTitle: 'Click me',
- onClick: jest.fn(),
- proTip: 'This is a tip',
- proTipLink: 'http://url/to/tip/destination',
- proTipLinkTitle: 'Learn more',
- proTipTarget: '_blank',
-};
-
-describe('EmptyListCTA', () => {
- it('renders correctly', () => {
- const tree = shallow();
- expect(tree).toMatchSnapshot();
- });
-});
diff --git a/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx b/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx
index 6b5c6ebb7ca..d36eeaaf6ff 100644
--- a/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx
+++ b/public/app/core/components/EmptyListCTA/EmptyListCTA.tsx
@@ -1,40 +1,48 @@
-import React, { Component } from 'react';
-
+import React, { useContext } from 'react';
+import { CallToActionCard, ExtraLargeLinkButton, ThemeContext } from '@grafana/ui';
+import { css } from 'emotion';
export interface Props {
model: any;
}
-class EmptyListCTA extends Component {
- render() {
- const {
- title,
- buttonIcon,
- buttonLink,
- buttonTitle,
- onClick,
- proTip,
- proTipLink,
- proTipLinkTitle,
- proTipTarget,
- } = this.props.model;
- return (
-
- );
- }
-}
+const EmptyListCTA: React.FunctionComponent = props => {
+ const theme = useContext(ThemeContext);
+
+ const {
+ title,
+ buttonIcon,
+ buttonLink,
+ buttonTitle,
+ onClick,
+ proTip,
+ proTipLink,
+ proTipLinkTitle,
+ proTipTarget,
+ } = props.model;
+
+ const footer = proTip ? (
+
+
+ <> ProTip: {proTip} >
+
+ {proTipLinkTitle}
+
+
+ ) : null;
+
+ const ctaElementClassName = !footer
+ ? css`
+ margin-bottom: 20px;
+ `
+ : '';
+
+ const ctaElement = (
+
+ {buttonTitle}
+
+ );
+
+ return ;
+};
export default EmptyListCTA;
diff --git a/public/app/core/components/EmptyListCTA/__snapshots__/EmptyListCTA.test.tsx.snap b/public/app/core/components/EmptyListCTA/__snapshots__/EmptyListCTA.test.tsx.snap
deleted file mode 100644
index 21c2ed294b4..00000000000
--- a/public/app/core/components/EmptyListCTA/__snapshots__/EmptyListCTA.test.tsx.snap
+++ /dev/null
@@ -1,39 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`EmptyListCTA renders correctly 1`] = `
-
-`;
diff --git a/public/app/features/alerting/AlertTab.tsx b/public/app/features/alerting/AlertTab.tsx
index 6343b4ca2c9..9e114e559f5 100644
--- a/public/app/features/alerting/AlertTab.tsx
+++ b/public/app/features/alerting/AlertTab.tsx
@@ -133,7 +133,7 @@ export class AlertTab extends PureComponent {
const model = {
title: 'Panel has no alert rule defined',
- icon: 'icon-gf icon-gf-alert',
+ buttonIcon: 'icon-gf icon-gf-alert',
onClick: this.onAddAlert,
buttonTitle: 'Create Alert',
};