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('functionEditor', FunctionEditor, ['func', 'onRemove', 'onMoveLeft', 'onMoveRight']);
react2AngularDirective('appNotificationsList', AppNotificationList, []); react2AngularDirective('appNotificationsList', AppNotificationList, []);
react2AngularDirective('pageHeader', PageHeader, ['model', 'noTabs']); 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, [ react2AngularDirective('searchField', SearchField, [
'query', 'query',
'autoFocus', '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 { CallToActionCard, LinkButton, ThemeContext } from '@grafana/ui';
import { css } from 'emotion'; import { css } from 'emotion';
export interface Props { 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 theme = useContext(ThemeContext);
const { const footer = () => {
title, return (
buttonIcon, <>
buttonLink, {proTip ? (
buttonTitle, <span key="proTipFooter">
onClick, <i className="fa fa-rocket" />
proTip, <> ProTip: {proTip} </>
proTipLink, <a href={proTipLink} target={proTipTarget} className="text-link">
proTipLinkTitle, {proTipLinkTitle}
proTipTarget, </a>
} = props.model; </span>
) : (
''
)}
{infoBox ? (
<div key="infoBoxHtml" className={`grafana-info-box ${infoBoxStyles}`}>
{infoBoxTitle && <h5>{infoBoxTitle}</h5>}
<div dangerouslySetInnerHTML={infoBox} />
</div>
) : (
''
)}
</>
);
};
const footer = proTip ? ( const ctaElementClassName = !footer()
<span>
<i className="fa fa-rocket" />
<> ProTip: {proTip} </>
<a href={proTipLink} target={proTipTarget} className="text-link">
{proTipLinkTitle}
</a>
</span>
) : (
''
);
const ctaElementClassName = !footer
? css` ? css`
margin-bottom: 20px; margin-bottom: 20px;
` `
@ -44,7 +77,15 @@ const EmptyListCTA: React.FunctionComponent<Props> = props => {
</LinkButton> </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; export default EmptyListCTA;

View File

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

View File

@ -23,6 +23,25 @@ export class AnnotationsEditorCtrl {
hide: false, 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 }]; showOptions: any = [{ text: 'All Panels', value: 0 }, { text: 'Specific Panels', value: 1 }];
/** @ngInject */ /** @ngInject */
@ -63,10 +82,10 @@ export class AnnotationsEditorCtrl {
this.mode = 'list'; this.mode = 'list';
} }
setupNew() { setupNew = () => {
this.mode = 'new'; this.mode = 'new';
this.reset(); this.reset();
} };
backToList() { backToList() {
this.mode = 'list'; this.mode = 'list';

View File

@ -13,10 +13,10 @@
class="btn btn-primary" class="btn btn-primary"
ng-click="ctrl.setupNew();" ng-click="ctrl.setupNew();"
ng-if="ctrl.annotations.length > 1" ng-if="ctrl.annotations.length > 1"
ng-hide="ctrl.mode === 'edit' || ctrl.mode === 'new'"> ng-hide="ctrl.mode === 'edit' || ctrl.mode === 'new'"
New
</a
> >
New
</a>
</div> </div>
<div ng-if="ctrl.mode === 'list'"> <div ng-if="ctrl.mode === 'list'">
@ -62,27 +62,7 @@
<!-- empty list cta, there is always one built in query --> <!-- empty list cta, there is always one built in query -->
<div ng-if="ctrl.annotations.length === 1" class="p-t-2"> <div ng-if="ctrl.annotations.length === 1" class="p-t-2">
<div class="empty-list-cta"> <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 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>
</div> </div>
</div> </div>

View File

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

View File

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

View File

@ -18,6 +18,19 @@ export class DashLinksEditorCtrl {
mode: any; mode: any;
link: 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 */ /** @ngInject */
constructor($scope: any, $rootScope: any) { constructor($scope: any, $rootScope: any) {
this.iconMap = iconMap; this.iconMap = iconMap;
@ -33,10 +46,10 @@ export class DashLinksEditorCtrl {
this.mode = 'list'; this.mode = 'list';
} }
setupNew() { setupNew = () => {
this.mode = 'new'; this.mode = 'new';
this.link = { type: 'dashboards', icon: 'external link' }; this.link = { type: 'dashboards', icon: 'external link' };
} };
addLink() { addLink() {
this.dashboard.links.push(this.link); this.dashboard.links.push(this.link);

View File

@ -19,22 +19,7 @@
<div ng-if="ctrl.mode == 'list'"> <div ng-if="ctrl.mode == 'list'">
<div ng-if="ctrl.dashboard.links.length === 0"> <div ng-if="ctrl.dashboard.links.length === 0">
<div class="empty-list-cta"> <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 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>
</div> </div>
<div ng-if="ctrl.dashboard.links.length > 0"> <div ng-if="ctrl.dashboard.links.length > 0">

View File

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

View File

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

View File

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

View File

@ -72,40 +72,16 @@ exports[`Render should render component 1`] = `
</form> </form>
</div> </div>
</Component> </Component>
<div <EmptyListCTA
className="empty-list-cta" buttonIcon="gicon gicon-team"
> buttonTitle="Add Group"
<div onClick={[Function]}
className="empty-list-cta__title" proTip="Sync LDAP or OAuth groups with your Grafana teams."
> proTipLink="http://docs.grafana.org/auth/enhanced_ldap/"
There are no external groups to sync with proTipLinkTitle="Learn more"
</div> proTipTarget="_blank"
<button title="There are no external groups to sync with"
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>
</div> </div>
`; `;

View File

@ -14,6 +14,24 @@ export class VariableEditorCtrl {
$scope.namePattern = /^(?!__).*$/; $scope.namePattern = /^(?!__).*$/;
$scope._ = _; $scope._ = _;
$scope.optionsLimit = 20; $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 = [ $scope.refreshOptions = [
{ value: 0, text: 'Never' }, { value: 0, text: 'Never' },
@ -50,6 +68,10 @@ export class VariableEditorCtrl {
$scope.mode = mode; $scope.mode = mode;
}; };
$scope.setNewMode = () => {
$scope.setMode('new');
};
$scope.add = () => { $scope.add = () => {
if ($scope.isValid()) { if ($scope.isValid()) {
variableSrv.addVariable($scope.current); variableSrv.addVariable($scope.current);

View File

@ -19,26 +19,14 @@
<div ng-if="mode === 'list'"> <div ng-if="mode === 'list'">
<div ng-if="variables.length === 0"> <div ng-if="variables.length === 0">
<div class="empty-list-cta"> <empty-list-cta
<div class="empty-list-cta__title">There are no variables added yet</div> on-click="setNewMode"
<a ng-click="setMode('new')" class="empty-list-cta__button btn btn-large btn-primary"> title="emptyListCta.title"
<i class="gicon gicon-variable"></i> infoBox="emptyListCta.infoBox"
Add variable infoBoxTitle="emptyListCta.infoBoxTitle"
</a> buttonTitle="emptyListCta.buttonTitle"
<div class="grafana-info-box"> buttonIcon="emptyListCta.buttonIcon"
<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>
</div> </div>
<div ng-if="variables.length"> <div ng-if="variables.length">

View File

@ -90,7 +90,6 @@
@import 'components/dashboard_list'; @import 'components/dashboard_list';
@import 'components/page_header'; @import 'components/page_header';
@import 'components/dashboard_settings'; @import 'components/dashboard_settings';
@import 'components/empty_list_cta';
@import 'components/panel_editor'; @import 'components/panel_editor';
@import 'components/toolbar'; @import 'components/toolbar';
@import 'components/add_data_source.scss'; @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;
}