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
pull/18649/head
Tobias Skarhed 6 years ago committed by GitHub
parent 299a0e20f4
commit ec492e55dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      public/app/core/angular_wrappers.ts
  2. 97
      public/app/core/components/EmptyListCTA/EmptyListCTA.tsx
  3. 2
      public/app/features/alerting/AlertTab.tsx
  4. 23
      public/app/features/annotations/editor_ctrl.ts
  5. 28
      public/app/features/annotations/partials/editor.html
  6. 17
      public/app/features/api-keys/ApiKeysPage.tsx
  7. 19
      public/app/features/api-keys/__snapshots__/ApiKeysPage.test.tsx.snap
  8. 17
      public/app/features/dashboard/components/DashLinks/DashLinksEditorCtrl.ts
  9. 17
      public/app/features/dashboard/components/DashLinks/editor.html
  10. 2
      public/app/features/datasources/DataSourcesListPage.tsx
  11. 28
      public/app/features/teams/TeamGroupSync.tsx
  12. 18
      public/app/features/teams/TeamList.tsx
  13. 44
      public/app/features/teams/__snapshots__/TeamGroupSync.test.tsx.snap
  14. 22
      public/app/features/templating/editor_ctrl.ts
  15. 28
      public/app/features/templating/partials/editor.html
  16. 1
      public/sass/_grafana.scss
  17. 24
      public/sass/components/_empty_list_cta.scss

@ -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',

@ -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 = proTip ? (
<span>
<i className="fa fa-rocket" />
<> ProTip: {proTip} </>
<a href={proTipLink} target={proTipTarget} className="text-link">
{proTipLinkTitle}
</a>
</span>
) : (
''
);
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 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;

@ -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>
);

@ -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';

@ -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>

@ -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()}

@ -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}

@ -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);

@ -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">

@ -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

@ -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 && (

@ -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"
/>
);
}

@ -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>
`;

@ -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);

@ -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">

@ -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';

@ -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;
}
Loading…
Cancel
Save