Skip to content

Commit

Permalink
feat: Add link to stats for add-on developer
Browse files Browse the repository at this point in the history
fixes #2751
  • Loading branch information
tofumatt committed Sep 26, 2017
1 parent 01f241e commit b1fe89b
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 43 deletions.
37 changes: 29 additions & 8 deletions src/amo/components/AddonMeta.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
/* @flow */
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose } from 'redux';

import Link from 'amo/components/Link';
import translate from 'core/i18n/translate';
import { isAddonAuthor } from 'core/utils';
import type { AddonType } from 'core/types/addons';
import LoadingText from 'ui/components/LoadingText';
import Rating from 'ui/components/Rating';

import 'amo/css/AddonMeta.scss';


type PropTypes = {
addon: AddonType | null,
i18n: Object,
userId: number | null,
}

export class AddonMetaBase extends React.Component {
static propTypes = {
addon: PropTypes.object.isRequired,
i18n: PropTypes.object.isRequired,
}
props: PropTypes;

render() {
const { addon, i18n } = this.props;
const { addon, i18n, userId } = this.props;
const averageRating = addon ? addon.ratings.average : null;
const addonRatingCount = addon ? addon.ratings.count : null;

Expand Down Expand Up @@ -49,7 +56,14 @@ export class AddonMetaBase extends React.Component {
<div className="AddonMeta-item AddonMeta-users">
<h3 className="visually-hidden">{i18n.gettext('Used by')}</h3>
<p className="AddonMeta-text AddonMeta-user-count">
{userCount}
{addon && isAddonAuthor({ addon, userId }) ? (
<Link
href={`/addon/${addon.slug}/statistics/`}
title={i18n.gettext('Click to view statistics')}
>
{userCount}
</Link>
) : userCount}
</p>
<p className="AddonMeta-text AddonMeta-review-count">
{reviewCount}
Expand All @@ -66,6 +80,13 @@ export class AddonMetaBase extends React.Component {
}
}

export const mapStateToProps = (state: Object) => {
return {
userId: state.user.id,
};
};

export default compose(
translate({ withRef: true }),
connect(mapStateToProps),
translate(),
)(AddonMetaBase);
43 changes: 35 additions & 8 deletions src/amo/components/AddonMoreInfo/index.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
/* @flow */
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose } from 'redux';

import Link from 'amo/components/Link';
import ReportAbuseButton from 'amo/components/ReportAbuseButton';
import translate from 'core/i18n/translate';
import { trimAndAddProtocolToUrl } from 'core/utils';
import type { AddonType } from 'core/types/addons';
import { isAddonAuthor, trimAndAddProtocolToUrl } from 'core/utils';
import Card from 'ui/components/Card';
import LoadingText from 'ui/components/LoadingText';

import './styles.scss';


type PropTypes = {
addon: AddonType | null,
i18n: Object,
userId: number | null,
}

export class AddonMoreInfoBase extends React.Component {
static propTypes = {
addon: PropTypes.object.isRequired,
i18n: PropTypes.object.isRequired,
}
props: PropTypes;

listContent() {
const { addon, i18n } = this.props;
const { addon, i18n, userId } = this.props;

if (!addon) {
return this.renderDefinitions({
Expand Down Expand Up @@ -52,6 +57,14 @@ export class AddonMoreInfoBase extends React.Component {
return this.renderDefinitions({
homepage,
supportUrl,
statsLink: addon && isAddonAuthor({ addon, userId }) ? (
<a
className="AddonMoreInfo-stats-link"
href={`/addon/${addon.slug}/statistics/`}
>
{i18n.gettext('Visit stats dashboard')}
</a>
) : null,
version: addon.current_version.version,
versionLastUpdated: i18n.sprintf(
// translators: This will output, in English:
Expand Down Expand Up @@ -110,6 +123,7 @@ export class AddonMoreInfoBase extends React.Component {
renderDefinitions({
homepage = null,
supportUrl = null,
statsLink = null,
privacyPolicyLink = null,
eulaLink = null,
version,
Expand All @@ -118,7 +132,7 @@ export class AddonMoreInfoBase extends React.Component {
versionHistoryLink,
betaVersionsLink = null,
addonId,
}) {
}: Object) {
const { i18n } = this.props;
return (
<dl className="AddonMoreInfo-contents">
Expand Down Expand Up @@ -171,6 +185,12 @@ export class AddonMoreInfoBase extends React.Component {
</dt>
) : null}
{betaVersionsLink ? <dd>{betaVersionsLink}</dd> : null}
{statsLink ? (
<dt className="AddonMoreInfo-stats-title">
{i18n.gettext('Usage Statistics')}
</dt>
) : null}
{statsLink ? <dd>{statsLink}</dd> : null}
<dt
className="AddonMoreInfo-database-id-title"
title={i18n.gettext(`This ID is useful for debugging and
Expand Down Expand Up @@ -201,6 +221,13 @@ export class AddonMoreInfoBase extends React.Component {
}
}

export const mapStateToProps = (state: Object) => {
return {
userId: state.user.id,
};
};

export default compose(
connect(mapStateToProps),
translate(),
)(AddonMoreInfoBase);
10 changes: 10 additions & 0 deletions src/core/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,16 @@ export function loadAddonIfNeeded(
return _refreshAddon({ addonSlug: slug, apiState: state.api, dispatch });
}

export function isAddonAuthor({ addon, userId }) {
if (!addon || !addon.authors || !addon.authors.length || !userId) {
return false;
}

return addon.authors.some((author) => {
return author.id === userId;
});
}

export function isAllowedOrigin(urlString, {
allowedOrigins = [config.get('amoCDN')],
} = {}) {
Expand Down
104 changes: 86 additions & 18 deletions tests/unit/amo/components/TestAddonMeta.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
import { shallow } from 'enzyme';
import React from 'react';

import { AddonMetaBase } from 'amo/components/AddonMeta';
import { fakeAddon } from 'tests/unit/amo/helpers';
import { getFakeI18nInst } from 'tests/unit/helpers';
import AddonMeta, { AddonMetaBase } from 'amo/components/AddonMeta';
import Link from 'amo/components/Link';
import { createInternalAddon } from 'core/reducers/addons';
import {
dispatchClientMetadata,
dispatchSignInActions,
fakeAddon,
} from 'tests/unit/amo/helpers';
import { getFakeI18nInst, shallowUntilTarget } from 'tests/unit/helpers';
import LoadingText from 'ui/components/LoadingText';
import Rating from 'ui/components/Rating';

function render({ ...customProps } = {}) {
const props = {
addon: fakeAddon,
i18n: getFakeI18nInst(),
...customProps,
};
return shallow(<AddonMetaBase {...props} />);
}

describe('<AddonMeta>', () => {
describe(__filename, () => {
function render({
addon = createInternalAddon(fakeAddon),
store = dispatchClientMetadata().store,
...props
} = {}) {
return shallowUntilTarget(
<AddonMeta
addon={addon}
i18n={getFakeI18nInst()}
store={store}
{...props}
/>,
AddonMetaBase
);
}

it('can render without an addon', () => {
const root = render({ addon: null });
expect(root.find('.AddonMeta-user-count').find(LoadingText))
Expand All @@ -33,38 +46,93 @@ describe('<AddonMeta>', () => {

it('renders the user count', () => {
const root = render({
addon: { ...fakeAddon, average_daily_users: 2 },
addon: createInternalAddon({ ...fakeAddon, average_daily_users: 2 }),
});
expect(getUserCount(root)).toEqual('2 users');
});

it('renders one user', () => {
const root = render({
addon: { ...fakeAddon, average_daily_users: 1 },
addon: createInternalAddon({ ...fakeAddon, average_daily_users: 1 }),
});
expect(getUserCount(root)).toEqual('1 user');
});

it('localizes the user count', () => {
const i18n = getFakeI18nInst({ lang: 'de' });
const root = render({
addon: { ...fakeAddon, average_daily_users: 1000 },
addon: createInternalAddon({
...fakeAddon,
average_daily_users: 1000,
}),
i18n,
});
expect(getUserCount(root)).toMatch(/^1\.000/);
});

it('does not link to stats if user is not author of the add-on', () => {
const authorUserId = 11;
const addon = createInternalAddon({
...fakeAddon,
slug: 'coolio',
authors: [
{
...fakeAddon.authors[0],
id: authorUserId,
name: 'tofumatt',
picture_url: 'http://cdn.a.m.o/myphoto.jpg',
url: 'http://a.m.o/en-GB/firefox/user/tofumatt/',
username: 'tofumatt',
},
],
});
const root = render({
addon,
store: dispatchSignInActions({ userId: 5 }).store,
});

const statsLink = root.find('.AddonMeta-user-count').find(Link);
expect(statsLink).toHaveLength(0);
});

it('links to stats if add-on author is viewing the page', () => {
const authorUserId = 11;
const addon = createInternalAddon({
...fakeAddon,
slug: 'coolio',
authors: [
{
...fakeAddon.authors[0],
id: authorUserId,
name: 'tofumatt',
picture_url: 'http://cdn.a.m.o/myphoto.jpg',
url: 'http://a.m.o/en-GB/firefox/user/tofumatt/',
username: 'tofumatt',
},
],
});
const root = render({
addon,
store: dispatchSignInActions({ userId: authorUserId }).store,
});

const statsLink = root.find('.AddonMeta-user-count').find(Link);
expect(statsLink).toHaveLength(1);
expect(statsLink).toHaveProp('title', 'Click to view statistics');
expect(statsLink).toHaveProp('href', '/addon/coolio/statistics/');
});
});

describe('ratings', () => {
function renderRatings(ratings = {}, otherProps = {}) {
return render({
addon: {
addon: createInternalAddon({
...fakeAddon,
ratings: {
...fakeAddon.ratings,
...ratings,
},
},
}),
...otherProps,
});
}
Expand Down

0 comments on commit b1fe89b

Please sign in to comment.