Skip to content

Commit ddb8d35

Browse files
committed
Hide ADU for new add-ons (#13962)
1 parent c396a03 commit ddb8d35

8 files changed

Lines changed: 127 additions & 26 deletions

File tree

config/default.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ module.exports = {
110110
'langs',
111111
'loggingLevel',
112112
'mozillaUserId',
113+
'recentAddonCutOffDays',
113114
'rtlLangs',
114115
'staticPath',
115116
'trackingEnabled',
@@ -408,6 +409,8 @@ module.exports = {
408409

409410
extensionWorkshopUrl: 'https://extensionworkshop.com',
410411

412+
recentAddonCutOffDays: 30,
413+
411414
// The withExperiment HOC relies on this config to enable/disable A/B
412415
// experiments on AMO.
413416
experiments: {

src/amo/components/AddonBadges/index.js

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import './styles.scss';
1919

2020
type Props = {|
2121
addon: AddonType,
22+
hideUsers?: boolean,
2223
|};
2324

2425
type DefaultProps = {|
@@ -49,8 +50,9 @@ export class AddonBadgesBase extends React.Component<InternalProps> {
4950
renderAndroidCompatibleBadge(): React.Node {
5051
const { addon, clientApp, i18n } = this.props;
5152

52-
if (clientApp !== CLIENT_APP_FIREFOX || !addon.isAndroidCompatible)
53+
if (clientApp !== CLIENT_APP_FIREFOX || !addon.isAndroidCompatible) {
5354
return null;
55+
}
5456

5557
return (
5658
<Badge
@@ -64,7 +66,9 @@ export class AddonBadgesBase extends React.Component<InternalProps> {
6466
renderExperimentalBadge(): React.Node {
6567
const { addon, i18n } = this.props;
6668

67-
if (!addon.is_experimental) return null;
69+
if (!addon.is_experimental) {
70+
return null;
71+
}
6872

6973
return (
7074
<Badge
@@ -78,7 +82,9 @@ export class AddonBadgesBase extends React.Component<InternalProps> {
7882
renderRequiresPaymentBadge(): React.Node {
7983
const { addon, i18n } = this.props;
8084

81-
if (!addon.requires_payment) return null;
85+
if (!addon.requires_payment) {
86+
return null;
87+
}
8288

8389
return (
8490
<Badge
@@ -98,7 +104,9 @@ export class AddonBadgesBase extends React.Component<InternalProps> {
98104
forBadging: true,
99105
});
100106

101-
if (!promotedCategory) return null;
107+
if (!promotedCategory) {
108+
return null;
109+
}
102110

103111
const props = getPromotedProps(i18n, promotedCategory);
104112

@@ -116,7 +124,9 @@ export class AddonBadgesBase extends React.Component<InternalProps> {
116124
renderRatingMeta(): React.Node {
117125
const { addon, i18n, location } = this.props;
118126

119-
if (!addon?.ratings) return null;
127+
if (!addon?.ratings) {
128+
return null;
129+
}
120130

121131
const addonRatingCount: number = addon.ratings.count;
122132
const averageRating: number = addon.ratings.average;
@@ -145,9 +155,11 @@ export class AddonBadgesBase extends React.Component<InternalProps> {
145155
}
146156

147157
renderUserCount(): React.Node {
148-
const { addon, i18n } = this.props;
158+
const { addon, hideUsers, i18n } = this.props;
149159

150-
if (!addon) return null;
160+
if (!addon || hideUsers) {
161+
return null;
162+
}
151163

152164
const averageDailyUsers = addon.average_daily_users;
153165

src/amo/components/SearchResult/index.js

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from 'amo/constants';
1616
import translate from 'amo/i18n/translate';
1717
import { getAddonIconUrl, getPreviewImage } from 'amo/imageUtils';
18+
import { isRecentAddon } from 'amo/reducers/addons';
1819
import { getPromotedCategory } from 'amo/utils/addons';
1920
import { addQueryParams } from 'amo/utils/url';
2021
import Icon from 'amo/components/Icon';
@@ -248,23 +249,26 @@ export class SearchResultBase extends React.Component<InternalProps> {
248249
)}
249250
</div>
250251

251-
<h3 className="SearchResult-users SearchResult--meta-section">
252-
<Icon className="SearchResult-users-icon" name="user-fill" />
253-
<span className="SearchResult-users-text">
254-
{averageDailyUsers !== null && averageDailyUsers !== undefined ? (
255-
i18n.sprintf(
256-
i18n.ngettext(
257-
'%(total)s user',
258-
'%(total)s users',
259-
averageDailyUsers,
260-
),
261-
{ total: i18n.formatNumber(averageDailyUsers) },
262-
)
263-
) : (
264-
<LoadingText width={80} />
265-
)}
266-
</span>
267-
</h3>
252+
{!isRecentAddon(addon) && (
253+
<h3 className="SearchResult-users SearchResult--meta-section">
254+
<Icon className="SearchResult-users-icon" name="user-fill" />
255+
<span className="SearchResult-users-text">
256+
{averageDailyUsers !== null &&
257+
averageDailyUsers !== undefined ? (
258+
i18n.sprintf(
259+
i18n.ngettext(
260+
'%(total)s user',
261+
'%(total)s users',
262+
averageDailyUsers,
263+
),
264+
{ total: i18n.formatNumber(averageDailyUsers) },
265+
)
266+
) : (
267+
<LoadingText width={80} />
268+
)}
269+
</span>
270+
</h3>
271+
)}
268272
</div>
269273
</div>
270274
);

src/amo/pages/Addon/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
fetchAddon,
3737
getAddonByIdInURL,
3838
isAddonLoading,
39+
isRecentAddon,
3940
} from 'amo/reducers/addons';
4041
import { sendServerRedirect } from 'amo/reducers/redirectTo';
4142
import { withFixedErrorHandler } from 'amo/errorHandler';
@@ -70,6 +71,7 @@ export class AddonBase extends React.Component {
7071
currentVersion: PropTypes.object,
7172
dispatch: PropTypes.func.isRequired,
7273
errorHandler: PropTypes.object.isRequired,
74+
hideMetadata: PropTypes.bool.isRequired,
7375
i18n: PropTypes.object.isRequired,
7476
installError: PropTypes.string,
7577
lang: PropTypes.string.isRequired,
@@ -372,6 +374,7 @@ export class AddonBase extends React.Component {
372374
clientApp,
373375
currentVersion,
374376
errorHandler,
377+
hideMetadata,
375378
i18n,
376379
} = this.props;
377380

@@ -451,7 +454,7 @@ export class AddonBase extends React.Component {
451454
{showSummary ? summary : null}
452455
</div>
453456

454-
<AddonBadges addon={addon} />
457+
<AddonBadges addon={addon} hideUsers={hideMetadata} />
455458

456459
<div className="Addon-install">
457460
<InstallButtonWrapper addon={addon} />
@@ -528,6 +531,7 @@ function mapStateToProps(state, ownProps) {
528531
addonsByAuthors,
529532
clientApp: state.api.clientApp,
530533
currentVersion,
534+
hideMetadata: isRecentAddon(addon),
531535
installError: installedAddon.error,
532536
lang: state.api.lang,
533537
// The `withInstallHelpers` HOC requires an `addon` prop too:

src/amo/reducers/addons.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* @flow */
22
import invariant from 'invariant';
3+
import config from 'config';
34

45
import { CLIENT_APP_ANDROID, ADDON_TYPE_EXTENSION } from 'amo/constants';
56
import {
@@ -20,6 +21,7 @@ import type { ExternalAddonInfoType } from 'amo/api/addonInfo';
2021
import type { AppState } from 'amo/store';
2122
import type {
2223
AddonType,
24+
CollectionAddonType,
2325
ExternalAddonType,
2426
ExternalPreviewType,
2527
GroupedRatingsType,
@@ -336,6 +338,20 @@ export const isAddonInfoLoading = ({
336338
return Boolean(infoForSlug && infoForSlug.loading);
337339
};
338340

341+
export const isRecentAddon = (
342+
addon: ?AddonType | CollectionAddonType | null,
343+
{ _config = config }: { _config: typeof config } = {},
344+
): boolean => {
345+
if (!addon) {
346+
return false;
347+
}
348+
349+
const created = new Date(addon.created);
350+
created.setDate(created.getDate() + _config.get('recentAddonCutOffDays'));
351+
352+
return created >= new Date();
353+
};
354+
339355
export const createInternalAddonInfo = (
340356
addonInfo: ExternalAddonInfoType,
341357
lang: string,

tests/unit/amo/components/TestSearchResults.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ describe(__filename, () => {
100100
'src',
101101
headerImageFull,
102102
);
103+
expect(screen.getByClassName('SearchResult-users')).toBeInTheDocument();
103104
});
104105

105106
it('sets add-on install source to search by default', () => {
@@ -148,4 +149,19 @@ describe(__filename, () => {
148149

149150
expect(screen.getByText('Page 1 of 2')).toBeInTheDocument();
150151
});
152+
153+
it('does not render the user count for recent add-ons', () => {
154+
const results = [
155+
createInternalAddonWithLang({ ...fakeAddon, created: new Date() }),
156+
];
157+
render({
158+
filters: { query: 'test' },
159+
loading: false,
160+
results,
161+
});
162+
163+
expect(
164+
screen.queryByClassName('SearchResult-users'),
165+
).not.toBeInTheDocument();
166+
});
151167
});

tests/unit/amo/pages/TestAddon.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,17 @@ describe(__filename, () => {
10511051
expect(content).toHaveTextContent('No Users');
10521052
});
10531053

1054+
it('does not render the user count badge when the add-on is too recent', () => {
1055+
addon = {
1056+
...addon,
1057+
created: new Date(),
1058+
};
1059+
renderWithAddon();
1060+
1061+
const badge = screen.queryByTestId(`badge-user-fill`);
1062+
expect(badge).not.toBeInTheDocument();
1063+
});
1064+
10541065
it('renders user count badge with singular user', () => {
10551066
addon = {
10561067
...addon,

tests/unit/amo/reducers/test_addons.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import addons, {
1818
initialState,
1919
isAddonInfoLoading,
2020
isAddonLoading,
21+
isRecentAddon,
2122
loadAddon,
2223
loadAddonInfo,
2324
selectLocalizedUrlWithOutgoing,
@@ -26,14 +27,15 @@ import { setLang } from 'amo/reducers/api';
2627
import {
2728
DEFAULT_LANG_IN_TESTS,
2829
createFakeAddon,
30+
createFakeAddonInfo,
2931
createInternalAddonWithLang,
3032
createLocalizedString,
3133
createStubErrorHandler,
3234
dispatchClientMetadata,
3335
fakeAddon,
3436
fakePreview,
35-
createFakeAddonInfo,
3637
fakeReview,
38+
getMockConfig,
3739
} from 'tests/unit/helpers';
3840

3941
describe(__filename, () => {
@@ -1063,4 +1065,37 @@ describe(__filename, () => {
10631065
).toEqual(null);
10641066
});
10651067
});
1068+
1069+
describe('isRecentAddon', () => {
1070+
it('should return false when there is no add-on', () => {
1071+
expect(isRecentAddon(null)).toEqual(false);
1072+
});
1073+
1074+
it('should return true when the add-on is recent', () => {
1075+
// Cut-off is a day ago.
1076+
const _config = getMockConfig({ recentAddonCutOffDays: 1 });
1077+
_config.get = (key) => _config[key];
1078+
1079+
// Add-on was just created.
1080+
const created = new Date();
1081+
1082+
expect(isRecentAddon({ ...fakeAddon, created }, { _config })).toEqual(
1083+
true,
1084+
);
1085+
});
1086+
1087+
it('should return false when the add-on is no longer recent', () => {
1088+
// Cut-off is a day ago.
1089+
const _config = getMockConfig({ recentAddonCutOffDays: 1 });
1090+
_config.get = (key) => _config[key];
1091+
1092+
// Add-on created 2 days ago.
1093+
const created = new Date();
1094+
created.setDate(created.getDate() - 2);
1095+
1096+
expect(isRecentAddon({ ...fakeAddon, created }, { _config })).toEqual(
1097+
false,
1098+
);
1099+
});
1100+
});
10661101
});

0 commit comments

Comments
 (0)