Refactor: EmptyListCTA (#18516)

* Rewrite EmptyListCTA props and start removing css classes

* Add watchDepth onClick

* EmptyListCTA with React in annotaitons/editor

* Begin conversion of DashLinks editor EmptyListCTA

* Use React component in DashLinks, Variables and TeamGroupSync

* Remove scss file and add emotion styles

* Update snapshot

* Remove style import

* Fix feedback

* Update snapshot
This commit is contained in:
Tobias Skarhed 2019-08-20 17:19:21 +02:00 committed by GitHub
parent 299a0e20f4
commit ec492e55dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 196 additions and 205 deletions

View File

@ -18,7 +18,19 @@ export function registerAngularDirectives() {
react2AngularDirective('functionEditor', FunctionEditor, ['func', 'onRemove', 'onMoveLeft', 'onMoveRight']);
react2AngularDirective('appNotificationsList', AppNotificationList, []);
react2AngularDirective('pageHeader', PageHeader, ['model', 'noTabs']);
react2AngularDirective('emptyListCta', EmptyListCTA, ['model']);
react2AngularDirective('emptyListCta', EmptyListCTA, [
'title',
'buttonIcon',
'buttonLink',
'buttonTitle',
['onClick', { watchDepth: 'reference', wrapApply: true }],
'proTip',
'proTipLink',
'proTipLinkTitle',
'proTipTarget',
'infoBox',
'infoBoxTitle',
]);
react2AngularDirective('searchField', SearchField, [
'query',
'autoFocus',

View File

@ -1,38 +1,71 @@
import React, { useContext } from 'react';
import React, { useContext, MouseEvent } from 'react';
import { CallToActionCard, LinkButton, ThemeContext } from '@grafana/ui';
import { css } from 'emotion';
export interface Props {
model: any;
title: string;
buttonIcon: string;
buttonLink?: string;
buttonTitle: string;
onClick?: (event: MouseEvent) => void;
proTip?: string;
proTipLink?: string;
proTipLinkTitle?: string;
proTipTarget?: string;
infoBox?: { __html: string };
infoBoxTitle?: string;
}
const EmptyListCTA: React.FunctionComponent<Props> = props => {
const ctaStyle = css`
text-align: center;
`;
const infoBoxStyles = css`
max-width: 700px;
margin: 0 auto;
`;
const EmptyListCTA: React.FunctionComponent<Props> = ({
title,
buttonIcon,
buttonLink,
buttonTitle,
onClick,
proTip,
proTipLink,
proTipLinkTitle,
proTipTarget,
infoBox,
infoBoxTitle,
}) => {
const theme = useContext(ThemeContext);
const {
title,
buttonIcon,
buttonLink,
buttonTitle,
onClick,
proTip,
proTipLink,
proTipLinkTitle,
proTipTarget,
} = props.model;
const footer = () => {
return (
<>
{proTip ? (
<span key="proTipFooter">
<i className="fa fa-rocket" />
<> ProTip: {proTip} </>
<a href={proTipLink} target={proTipTarget} className="text-link">
{proTipLinkTitle}
</a>
</span>
) : (
''
)}
{infoBox ? (
<div key="infoBoxHtml" className={`grafana-info-box ${infoBoxStyles}`}>
{infoBoxTitle && <h5>{infoBoxTitle}</h5>}
<div dangerouslySetInnerHTML={infoBox} />
</div>
) : (
''
)}
</>
);
};
const footer = proTip ? (
<span>
<i className="fa fa-rocket" />
<> ProTip: {proTip} </>
<a href={proTipLink} target={proTipTarget} className="text-link">
{proTipLinkTitle}
</a>
</span>
) : (
''
);
const ctaElementClassName = !footer
const ctaElementClassName = !footer()
? css`
margin-bottom: 20px;
`
@ -44,7 +77,15 @@ const EmptyListCTA: React.FunctionComponent<Props> = props => {
</LinkButton>
);
return <CallToActionCard message={title} footer={footer} callToActionElement={ctaElement} theme={theme} />;
return (
<CallToActionCard
className={ctaStyle}
message={title}
footer={footer()}
callToActionElement={ctaElement}
theme={theme}
/>
);
};
export default EmptyListCTA;

View File

@ -142,7 +142,7 @@ export class AlertTab extends PureComponent<Props> {
<EditorTabBody heading="Alert" toolbarItems={toolbarItems}>
<>
<div ref={element => (this.element = element)} />
{!alert && <EmptyListCTA model={model} />}
{!alert && <EmptyListCTA {...model} />}
</>
</EditorTabBody>
);

View File

@ -23,6 +23,25 @@ export class AnnotationsEditorCtrl {
hide: false,
};
emptyListCta = {
title: 'There are no custom annotation queries added yet',
buttonIcon: 'gicon gicon-annotation',
buttonTitle: 'Add Annotation Query',
infoBox: {
__html: `<p>Annotations provide a way to integrate event data into your graphs. They are visualized as vertical lines
and icons on all graph panels. When you hover over an annotation icon you can get event text &amp; tags for
the event. You can add annotation events directly from grafana by holding CTRL or CMD + click on graph (or
drag region). These will be stored in Grafana's annotation database.
</p>
Checkout the
<a class='external-link' target='_blank' href='http://docs.grafana.org/reference/annotations/'
>Annotations documentation</a
>
for more information.`,
},
infoBoxTitle: 'What are annotations?',
};
showOptions: any = [{ text: 'All Panels', value: 0 }, { text: 'Specific Panels', value: 1 }];
/** @ngInject */
@ -63,10 +82,10 @@ export class AnnotationsEditorCtrl {
this.mode = 'list';
}
setupNew() {
setupNew = () => {
this.mode = 'new';
this.reset();
}
};
backToList() {
this.mode = 'list';

View File

@ -13,10 +13,10 @@
class="btn btn-primary"
ng-click="ctrl.setupNew();"
ng-if="ctrl.annotations.length > 1"
ng-hide="ctrl.mode === 'edit' || ctrl.mode === 'new'">
New
</a
ng-hide="ctrl.mode === 'edit' || ctrl.mode === 'new'"
>
New
</a>
</div>
<div ng-if="ctrl.mode === 'list'">
@ -62,27 +62,7 @@
<!-- empty list cta, there is always one built in query -->
<div ng-if="ctrl.annotations.length === 1" class="p-t-2">
<div class="empty-list-cta">
<div class="empty-list-cta__title">There are no custom annotation queries added yet</div>
<a ng-click="ctrl.setupNew()" class="empty-list-cta__button btn btn-large btn-primary">
<i class="gicon gicon-annotation"></i>
Add Annotation Query
</a>
<div class="grafana-info-box">
<h5>What are Annotations?</h5>
<p>
Annotations provide a way to integrate event data into your graphs. They are visualized as vertical lines
and icons on all graph panels. When you hover over an annotation icon you can get event text &amp; tags for
the event. You can add annotation events directly from grafana by holding CTRL or CMD + click on graph (or
drag region). These will be stored in Grafana's annotation database.
</p>
Checkout the
<a class="external-link" target="_blank" href="http://docs.grafana.org/reference/annotations/"
>Annotations documentation</a
>
for more information.
</div>
</div>
<empty-list-cta title="ctrl.emptyListCta.title" buttonIcon="ctrl.emptyListCta.buttonIcon" buttonTitle="ctrl.emptyListCta.buttonTitle" infoBox="ctrl.emptyListCta.infoBox" infoBoxTitle="ctrl.emptyListCta.infoBoxTitle" on-click="ctrl.setupNew"/>
</div>
</div>

View File

@ -145,17 +145,12 @@ export class ApiKeysPage extends PureComponent<Props, any> {
<>
{!isAdding && (
<EmptyListCTA
model={{
title: "You haven't added any API Keys yet.",
buttonIcon: 'gicon gicon-apikeys',
buttonLink: '#',
onClick: this.onToggleAdding,
buttonTitle: ' New API Key',
proTip: 'Remember you can provide view-only API access to other applications.',
proTipLink: '',
proTipLinkTitle: '',
proTipTarget: '_blank',
}}
title="You haven't added any API Keys yet."
buttonIcon="gicon gicon-apikeys"
buttonLink="#"
onClick={this.onToggleAdding}
buttonTitle=" New API Key"
proTip="Remember you can provide view-only API access to other applications."
/>
)}
{this.renderAddApiKeyForm()}

View File

@ -36,19 +36,12 @@ exports[`Render should render CTA if there are no API keys 1`] = `
isLoading={false}
>
<EmptyListCTA
model={
Object {
"buttonIcon": "gicon gicon-apikeys",
"buttonLink": "#",
"buttonTitle": " New API Key",
"onClick": [Function],
"proTip": "Remember you can provide view-only API access to other applications.",
"proTipLink": "",
"proTipLinkTitle": "",
"proTipTarget": "_blank",
"title": "You haven't added any API Keys yet.",
}
}
buttonIcon="gicon gicon-apikeys"
buttonLink="#"
buttonTitle=" New API Key"
onClick={[Function]}
proTip="Remember you can provide view-only API access to other applications."
title="You haven't added any API Keys yet."
/>
<Component
in={false}

View File

@ -18,6 +18,19 @@ export class DashLinksEditorCtrl {
mode: any;
link: any;
emptyListCta = {
title: 'There are no dashboard links added yet',
buttonIcon: 'gicon gicon-link',
buttonTitle: 'Add Dashboard Link',
infoBox: {
__html: `<p>
Dashboard Links allow you to place links to other dashboards and web sites directly in below the dashboard
header.
</p>`,
},
infoBoxTitle: 'What are Dashboard Links?',
};
/** @ngInject */
constructor($scope: any, $rootScope: any) {
this.iconMap = iconMap;
@ -33,10 +46,10 @@ export class DashLinksEditorCtrl {
this.mode = 'list';
}
setupNew() {
setupNew = () => {
this.mode = 'new';
this.link = { type: 'dashboards', icon: 'external link' };
}
};
addLink() {
this.dashboard.links.push(this.link);

View File

@ -19,22 +19,7 @@
<div ng-if="ctrl.mode == 'list'">
<div ng-if="ctrl.dashboard.links.length === 0">
<div class="empty-list-cta">
<div class="empty-list-cta__title">
There are no dashboard links added yet
</div>
<a ng-click="ctrl.setupNew()" class="empty-list-cta__button btn btn-large btn-primary">
<i class="gicon gicon-link"></i>
Add Dashboard Link
</a>
<div class="grafana-info-box">
<h5>What are Dashboard Links?</h5>
<p>
Dashboard Links allow you to place links to other dashboards and web sites directly in below the dashboard
header.
</p>
</div>
</div>
<empty-list-cta on-click="ctrl.setupNew" title="ctrl.emptyListCta.title" buttonIcon="ctrl.emptyListCta.buttonIcon" buttonTitle="ctrl.emptyListCta.buttonTitle" infoBox="ctrl.emptyListCta.infoBox" infoBoxTitle="ctrl.emptyListCta.infoBoxTitle"/>
</div>
<div ng-if="ctrl.dashboard.links.length > 0">

View File

@ -79,7 +79,7 @@ export class DataSourcesListPage extends PureComponent<Props> {
<Page navModel={navModel}>
<Page.Contents isLoading={!hasFetched}>
<>
{hasFetched && dataSourcesCount === 0 && <EmptyListCTA model={emptyListModel} />}
{hasFetched && dataSourcesCount === 0 && <EmptyListCTA {...emptyListModel} />}
{hasFetched &&
dataSourcesCount > 0 && [
<OrgActionBar

View File

@ -7,6 +7,7 @@ import { Input, Tooltip } from '@grafana/ui';
import { TeamGroup } from '../../types';
import { addTeamGroup, loadTeamGroups, removeTeamGroup } from './state/actions';
import { getTeamGroups } from './state/selectors';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
export interface Props {
groups: TeamGroup[];
@ -119,23 +120,16 @@ export class TeamGroupSync extends PureComponent<Props, State> {
</SlideDown>
{groups.length === 0 && !isAdding && (
<div className="empty-list-cta">
<div className="empty-list-cta__title">There are no external groups to sync with</div>
<button onClick={this.onToggleAdding} className="empty-list-cta__button btn btn-large btn-primary">
<i className="gicon gicon-team" />
Add Group
</button>
<div className="empty-list-cta__pro-tip">
<i className="fa fa-rocket" /> {headerTooltip}
<a
className="text-link empty-list-cta__pro-tip-link"
href="http://docs.grafana.org/auth/enhanced_ldap/"
target="_blank"
>
Learn more
</a>
</div>
</div>
<EmptyListCTA
onClick={this.onToggleAdding}
buttonIcon="gicon gicon-team"
title="There are no external groups to sync with"
buttonTitle="Add Group"
proTip={headerTooltip}
proTipLinkTitle="Learn more"
proTipLink="http://docs.grafana.org/auth/enhanced_ldap/"
proTipTarget="_blank"
/>
)}
{groups.length > 0 && (

View File

@ -75,16 +75,14 @@ export class TeamList extends PureComponent<Props, any> {
renderEmptyList() {
return (
<EmptyListCTA
model={{
title: "You haven't created any teams yet.",
buttonIcon: 'gicon gicon-team',
buttonLink: 'org/teams/new',
buttonTitle: ' New team',
proTip: 'Assign folder and dashboard permissions to teams instead of users to ease administration.',
proTipLink: '',
proTipLinkTitle: '',
proTipTarget: '_blank',
}}
title="You haven't created any teams yet."
buttonIcon="gicon gicon-team"
buttonLink="org/teams/new"
buttonTitle=" New team"
proTip="Assign folder and dashboard permissions to teams instead of users to ease administration."
proTipLink=""
proTipLinkTitle=""
proTipTarget="_blank"
/>
);
}

View File

@ -72,40 +72,16 @@ exports[`Render should render component 1`] = `
</form>
</div>
</Component>
<div
className="empty-list-cta"
>
<div
className="empty-list-cta__title"
>
There are no external groups to sync with
</div>
<button
className="empty-list-cta__button btn btn-large btn-primary"
onClick={[Function]}
>
<i
className="gicon gicon-team"
/>
Add Group
</button>
<div
className="empty-list-cta__pro-tip"
>
<i
className="fa fa-rocket"
/>
Sync LDAP or OAuth groups with your Grafana teams.
<a
className="text-link empty-list-cta__pro-tip-link"
href="http://docs.grafana.org/auth/enhanced_ldap/"
target="_blank"
>
Learn more
</a>
</div>
</div>
<EmptyListCTA
buttonIcon="gicon gicon-team"
buttonTitle="Add Group"
onClick={[Function]}
proTip="Sync LDAP or OAuth groups with your Grafana teams."
proTipLink="http://docs.grafana.org/auth/enhanced_ldap/"
proTipLinkTitle="Learn more"
proTipTarget="_blank"
title="There are no external groups to sync with"
/>
</div>
`;

View File

@ -14,6 +14,24 @@ export class VariableEditorCtrl {
$scope.namePattern = /^(?!__).*$/;
$scope._ = _;
$scope.optionsLimit = 20;
$scope.emptyListCta = {
title: 'There are no variables yet',
buttonTitle: 'Add variable',
buttonIcon: 'gicon gicon-variable',
infoBox: {
__html: ` <p>
Variables enable more interactive and dynamic dashboards. Instead of hard-coding things like server or
sensor names in your metric queries you can use variables in their place. Variables are shown as dropdown
select boxes at the top of the dashboard. These dropdowns make it easy to change the data being displayed in
your dashboard. Check out the
<a class="external-link" href="http://docs.grafana.org/reference/templating/" target="_blank">
Templating documentation
</a>
for more information.
</p>`,
infoBoxTitle: 'What do variables do?',
},
};
$scope.refreshOptions = [
{ value: 0, text: 'Never' },
@ -50,6 +68,10 @@ export class VariableEditorCtrl {
$scope.mode = mode;
};
$scope.setNewMode = () => {
$scope.setMode('new');
};
$scope.add = () => {
if ($scope.isValid()) {
variableSrv.addVariable($scope.current);

View File

@ -19,26 +19,14 @@
<div ng-if="mode === 'list'">
<div ng-if="variables.length === 0">
<div class="empty-list-cta">
<div class="empty-list-cta__title">There are no variables added yet</div>
<a ng-click="setMode('new')" class="empty-list-cta__button btn btn-large btn-primary">
<i class="gicon gicon-variable"></i>
Add variable
</a>
<div class="grafana-info-box">
<h5>What do variables do?</h5>
<p>
Variables enable more interactive and dynamic dashboards. Instead of hard-coding things like server or
sensor names in your metric queries you can use variables in their place. Variables are shown as dropdown
select boxes at the top of the dashboard. These dropdowns make it easy to change the data being displayed in
your dashboard. Check out the
<a class="external-link" href="http://docs.grafana.org/reference/templating/" target="_blank">
Templating documentation
</a>
for more information.
</p>
</div>
</div>
<empty-list-cta
on-click="setNewMode"
title="emptyListCta.title"
infoBox="emptyListCta.infoBox"
infoBoxTitle="emptyListCta.infoBoxTitle"
buttonTitle="emptyListCta.buttonTitle"
buttonIcon="emptyListCta.buttonIcon"
/>
</div>
<div ng-if="variables.length">

View File

@ -90,7 +90,6 @@
@import 'components/dashboard_list';
@import 'components/page_header';
@import 'components/dashboard_settings';
@import 'components/empty_list_cta';
@import 'components/panel_editor';
@import 'components/toolbar';
@import 'components/add_data_source.scss';

View File

@ -1,24 +0,0 @@
.empty-list-cta {
background-color: $empty-list-cta-bg;
text-align: center;
padding: $spacer * 2;
border-radius: $border-radius;
.grafana-info-box {
max-width: 700px;
margin: 0 auto;
}
}
.empty-list-cta__title {
padding-bottom: $spacer * 3;
font-style: italic;
}
.empty-list-cta__button {
margin-bottom: $spacer * 3;
}
.empty-list-cta__pro-tip-link {
margin-left: 5px;
}