Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions services/amo/amo-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,19 @@ import Joi from 'joi'
import { nonNegativeInteger } from '../validators.js'
import { BaseJsonService } from '../index.js'

const registryMap = {
firefox: 'https://addons.mozilla.org/api/v4',
thunderbird: 'https://addons.thunderbird.net/api/v5',
}

const description =
'[addons.mozilla.org](https://addons.mozilla.org) (AMO) publishes extensions for Mozilla Firefox'
'Supports [addons.mozilla.org](https://addons.mozilla.org) (Mozilla Firefox) and ' +
'[addons.thunderbird.net](https://addons.thunderbird.net) (Mozilla Thunderbird). ' +
'Use `?registry=thunderbird` for Thunderbird add-ons.'

const queryParamSchema = Joi.object({
registry: Joi.string().valid('firefox', 'thunderbird').default('firefox'),
}).required()

const schema = Joi.object({
average_daily_users: nonNegativeInteger,
Expand All @@ -19,12 +30,13 @@ const schema = Joi.object({
class BaseAmoService extends BaseJsonService {
static defaultBadgeData = { label: 'mozilla add-on' }

async fetch({ addonId }) {
async fetch({ addonId, registry = 'firefox' }) {
const apiUrl = registryMap[registry]
return this._requestJson({
schema,
url: `https://addons.mozilla.org/api/v4/addons/addon/${addonId}/`,
url: `${apiUrl}/addons/addon/${addonId}/`,
})
}
}

export { BaseAmoService, description }
export { BaseAmoService, description, queryParamSchema }
25 changes: 19 additions & 6 deletions services/amo/amo-downloads.service.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { renderDownloadsBadge } from '../downloads.js'
import { deprecatedService, pathParams } from '../index.js'
import { BaseAmoService, description as baseDescription } from './amo-base.js'
import { deprecatedService, pathParam, queryParam } from '../index.js'
import {
BaseAmoService,
description as baseDescription,
queryParamSchema,
} from './amo-base.js'

const description = `${baseDescription}

Expand All @@ -11,14 +15,23 @@ only give us weekly downloads. The route \`amo/d\` redirects to \`amo/dw\`.

class AmoWeeklyDownloads extends BaseAmoService {
static category = 'downloads'
static route = { base: 'amo/dw', pattern: ':addonId' }
static route = { base: 'amo/dw', pattern: ':addonId', queryParamSchema }

static openApi = {
'/amo/dw/{addonId}': {
get: {
summary: 'Mozilla Add-on Downloads',
description,
parameters: pathParams({ name: 'addonId', example: 'dustman' }),
parameters: [
pathParam({ name: 'addonId', example: 'dustman' }),
queryParam({
name: 'registry',
example: 'thunderbird',
schema: { type: 'string', enum: ['firefox', 'thunderbird'] },
description:
'Registry to use. Can be `firefox` (default) or `thunderbird`.',
}),
],
},
},
}
Expand All @@ -31,8 +44,8 @@ class AmoWeeklyDownloads extends BaseAmoService {
return renderDownloadsBadge({ downloads, interval: 'week' })
}

async handle({ addonId }) {
const data = await this.fetch({ addonId })
async handle({ addonId }, { registry }) {
const data = await this.fetch({ addonId, registry })
return this.constructor.render({
downloads: data.weekly_downloads,
})
Expand Down
14 changes: 14 additions & 0 deletions services/amo/amo-downloads.tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,17 @@ t.create('/d URL should return deprecated badge')
label: 'mozilla-add-on',
message: 'https://github.com/badges/shields/pull/11583',
})

t.create('Weekly Downloads (thunderbird)')

@PyvesB PyvesB Apr 26, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please switch to non-mocked tests, i.e. using real data and API calls? See https://github.com/badges/shields/blob/master/doc/service-tests.md for more information. :)

.get('/dw/tbkeys-lite.json?registry=thunderbird')
.intercept(nock =>
nock('https://addons.thunderbird.net')
.get('/api/v5/addons/addon/tbkeys-lite/')
.reply(200, {
average_daily_users: 1000,
current_version: { version: '4.0.0' },
ratings: { average: 4.5 },
weekly_downloads: 200,
}),
)
.expectBadge({ label: 'downloads', message: isMetricOverTimePeriod })
36 changes: 29 additions & 7 deletions services/amo/amo-rating.service.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,47 @@
import { starRating } from '../text-formatters.js'
import { floorCount as floorCountColor } from '../color-formatters.js'
import { pathParams } from '../index.js'
import { BaseAmoService, description } from './amo-base.js'
import { pathParam, queryParam } from '../index.js'
import { BaseAmoService, description, queryParamSchema } from './amo-base.js'

export default class AmoRating extends BaseAmoService {
static category = 'rating'
static route = { base: 'amo', pattern: ':format(stars|rating)/:addonId' }
static route = {
base: 'amo',
pattern: ':format(stars|rating)/:addonId',
queryParamSchema,
}

static openApi = {
'/amo/rating/{addonId}': {
get: {
summary: 'Mozilla Add-on Rating',
description,
parameters: pathParams({ name: 'addonId', example: 'dustman' }),
parameters: [
pathParam({ name: 'addonId', example: 'dustman' }),
queryParam({
name: 'registry',
example: 'thunderbird',
schema: { type: 'string', enum: ['firefox', 'thunderbird'] },
description:
'Registry to use. Can be `firefox` (default) or `thunderbird`.',
}),
],
},
},
'/amo/stars/{addonId}': {
get: {
summary: 'Mozilla Add-on Stars',
description,
parameters: pathParams({ name: 'addonId', example: 'dustman' }),
parameters: [
pathParam({ name: 'addonId', example: 'dustman' }),
queryParam({
name: 'registry',
example: 'thunderbird',
schema: { type: 'string', enum: ['firefox', 'thunderbird'] },
description:
'Registry to use. Can be `firefox` (default) or `thunderbird`.',
}),
],
},
},
}
Expand All @@ -37,8 +59,8 @@ export default class AmoRating extends BaseAmoService {
}
}

async handle({ format, addonId }) {
const data = await this.fetch({ addonId })
async handle({ format, addonId }, { registry }) {
const data = await this.fetch({ addonId, registry })
return this.constructor.render({ format, rating: data.ratings.average })
}
}
17 changes: 17 additions & 0 deletions services/amo/amo-rating.tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,20 @@ t.create('Stars')
t.create('Rating (not found)')
.get('/rating/not-a-real-plugin.json')
.expectBadge({ label: 'mozilla add-on', message: 'not found' })

t.create('Rating (thunderbird)')
.get('/rating/tbkeys-lite.json?registry=thunderbird')
.intercept(nock =>
nock('https://addons.thunderbird.net')
.get('/api/v5/addons/addon/tbkeys-lite/')
.reply(200, {
average_daily_users: 1000,
current_version: { version: '4.0.0' },
ratings: { average: 4.5 },
weekly_downloads: 200,
}),
)
.expectBadge({
label: 'rating',
message: Joi.string().regex(/^\d(\.\d)?\/\d$/),
})
21 changes: 15 additions & 6 deletions services/amo/amo-users.service.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
import { renderDownloadsBadge } from '../downloads.js'
import { pathParams } from '../index.js'
import { BaseAmoService, description } from './amo-base.js'
import { pathParam, queryParam } from '../index.js'
import { BaseAmoService, description, queryParamSchema } from './amo-base.js'

export default class AmoUsers extends BaseAmoService {
static category = 'downloads'
static route = { base: 'amo/users', pattern: ':addonId' }
static route = { base: 'amo/users', pattern: ':addonId', queryParamSchema }

static openApi = {
'/amo/users/{addonId}': {
get: {
summary: 'Mozilla Add-on Users',
description,
parameters: pathParams({ name: 'addonId', example: 'dustman' }),
parameters: [
pathParam({ name: 'addonId', example: 'dustman' }),
queryParam({
name: 'registry',
example: 'thunderbird',
schema: { type: 'string', enum: ['firefox', 'thunderbird'] },
description:
'Registry to use. Can be `firefox` (default) or `thunderbird`.',
}),
],
},
},
}
Expand All @@ -24,8 +33,8 @@ export default class AmoUsers extends BaseAmoService {
return renderDownloadsBadge({ downloads, colorOverride: 'blue' })
}

async handle({ addonId }) {
const data = await this.fetch({ addonId })
async handle({ addonId }, { registry }) {
const data = await this.fetch({ addonId, registry })
return this.constructor.render({ users: data.average_daily_users })
}
}
14 changes: 14 additions & 0 deletions services/amo/amo-users.tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,17 @@ t.create('Users')
t.create('Users (not found)')
.get('/not-a-real-plugin.json')
.expectBadge({ label: 'users', message: 'not found' })

t.create('Users (thunderbird)')
.get('/tbkeys-lite.json?registry=thunderbird')
.intercept(nock =>
nock('https://addons.thunderbird.net')
.get('/api/v5/addons/addon/tbkeys-lite/')
.reply(200, {
average_daily_users: 1000,
current_version: { version: '4.0.0' },
ratings: { average: 4.5 },
weekly_downloads: 200,
}),
)
.expectBadge({ label: 'users', message: isMetric })
21 changes: 15 additions & 6 deletions services/amo/amo-version.service.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
import { renderVersionBadge } from '../version.js'
import { pathParams } from '../index.js'
import { BaseAmoService, description } from './amo-base.js'
import { pathParam, queryParam } from '../index.js'
import { BaseAmoService, description, queryParamSchema } from './amo-base.js'

export default class AmoVersion extends BaseAmoService {
static category = 'version'
static route = { base: 'amo/v', pattern: ':addonId' }
static route = { base: 'amo/v', pattern: ':addonId', queryParamSchema }

static openApi = {
'/amo/v/{addonId}': {
get: {
summary: 'Mozilla Add-on Version',
description,
parameters: pathParams({ name: 'addonId', example: 'dustman' }),
parameters: [
pathParam({ name: 'addonId', example: 'dustman' }),
queryParam({
name: 'registry',
example: 'thunderbird',
schema: { type: 'string', enum: ['firefox', 'thunderbird'] },
description:
'Registry to use. Can be `firefox` (default) or `thunderbird`.',
}),
],
},
},
}

async handle({ addonId }) {
const data = await this.fetch({ addonId })
async handle({ addonId }, { registry }) {
const data = await this.fetch({ addonId, registry })
return renderVersionBadge({ version: data.current_version.version })
}
}
26 changes: 26 additions & 0 deletions services/amo/amo-version.tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,29 @@ t.create('Version').get('/night-video-tuner.json').expectBadge({
t.create('Version (not found)')
.get('/not-a-real-plugin.json')
.expectBadge({ label: 'mozilla add-on', message: 'not found' })

t.create('Version (thunderbird)')
.get('/tbkeys-lite.json?registry=thunderbird')
.intercept(nock =>
nock('https://addons.thunderbird.net')
.get('/api/v5/addons/addon/tbkeys-lite/')
.reply(200, {
average_daily_users: 1000,
current_version: { version: '4.0.0' },
ratings: { average: 4.5 },
weekly_downloads: 200,
}),
)
.expectBadge({
label: 'mozilla add-on',
message: isVPlusDottedVersionAtLeastOne,
})

t.create('Version (thunderbird, not found)')
.get('/not-a-real-plugin.json?registry=thunderbird')
.intercept(nock =>
nock('https://addons.thunderbird.net')
.get('/api/v5/addons/addon/not-a-real-plugin/')
.reply(404),
)
.expectBadge({ label: 'mozilla add-on', message: 'not found' })
Loading