diff --git a/services/wordpress/wordpress-base.js b/services/wordpress/wordpress-base.js index 7980c47176..3a7c67588f 100644 --- a/services/wordpress/wordpress-base.js +++ b/services/wordpress/wordpress-base.js @@ -21,20 +21,14 @@ const notFoundSchema = Joi.string().allow(null, false) const schemas = Joi.alternatives(foundSchema, notFoundSchema) module.exports = class BaseWordpress extends BaseJsonService { - static get extensionType() { - throw new Error(`extensionType() function not implemented for ${this.name}`) - } - - async fetch({ slug }) { - const url = `https://api.wordpress.org/${ - this.constructor.extensionType - }s/info/1.1/` - return this._requestJson({ + async fetch({ extensionType, slug }) { + const url = `https://api.wordpress.org/${extensionType}s/info/1.1/` + const json = await this._requestJson({ url, schema: schemas, options: { qs: { - action: `${this.constructor.extensionType}_information`, + action: `${extensionType}_information`, request: { slug, fields: { @@ -51,15 +45,9 @@ module.exports = class BaseWordpress extends BaseJsonService { }, }, }) - } - - async handle({ slug }) { - const json = await this.fetch({ slug }) - if (!json) { throw new NotFound() } - - return this.constructor.render({ response: json }) + return json } } diff --git a/services/wordpress/wordpress-downloads.service.js b/services/wordpress/wordpress-downloads.service.js index c0151152d0..48655b7358 100644 --- a/services/wordpress/wordpress-downloads.service.js +++ b/services/wordpress/wordpress-downloads.service.js @@ -29,13 +29,21 @@ function DownloadsForExtensionType(extensionType) { return `Wordpress${capt}Downloads` } - static render({ response }) { + static render({ downloads }) { return { - message: metric(response.downloaded), - color: downloadCount(response.downloaded), + message: metric(downloads), + color: downloadCount(downloads), } } + async handle({ slug }) { + const { downloaded: downloads } = await this.fetch({ + extensionType, + slug, + }) + return this.constructor.render({ downloads }) + } + static get category() { return 'downloads' } @@ -50,16 +58,13 @@ function DownloadsForExtensionType(extensionType) { pattern: ':slug', } } - static get extensionType() { - return extensionType - } static get examples() { return [ { title: `Wordpress ${capt} Downloads`, namedParams: { slug: exampleSlug }, - staticPreview: this.render({ response: { downloaded: 200000 } }), + staticPreview: this.render({ downloads: 200000 }), }, ] } @@ -74,21 +79,25 @@ function InstallsForExtensionType(extensionType) { return `Wordpress${capt}Installs` } - static get extensionType() { - return extensionType - } - static get category() { return 'downloads' } - static render({ response }) { + static render({ installCount }) { return { - message: `${metric(response.active_installs)}+`, - color: downloadCount(response.active_installs), + message: metric(installCount), + color: downloadCount(installCount), } } + async handle({ slug }) { + const { active_installs: installCount } = await this.fetch({ + extensionType, + slug, + }) + return this.constructor.render({ installCount }) + } + static get defaultBadgeData() { return { label: 'active installs' } } @@ -105,7 +114,7 @@ function InstallsForExtensionType(extensionType) { { title: `Wordpress ${capt} Active Installs`, namedParams: { slug: exampleSlug }, - staticPreview: this.render({ response: { active_installs: 300000 } }), + staticPreview: this.render({ installCount: 300000 }), }, ] } diff --git a/services/wordpress/wordpress-platform-redirect.service.js b/services/wordpress/wordpress-platform-redirect.service.js new file mode 100644 index 0000000000..d91355d834 --- /dev/null +++ b/services/wordpress/wordpress-platform-redirect.service.js @@ -0,0 +1,13 @@ +'use strict' + +const { redirector } = require('..') + +module.exports = redirector({ + category: 'platform-support', + route: { + base: 'wordpress/v', + pattern: ':slug', + }, + transformPath: ({ slug }) => `/wordpress/plugin/wp-version/${slug}`, + dateAdded: new Date('2019-04-17'), +}) diff --git a/services/wordpress/wordpress-platform-redirect.tester.js b/services/wordpress/wordpress-platform-redirect.tester.js new file mode 100644 index 0000000000..3106da6d93 --- /dev/null +++ b/services/wordpress/wordpress-platform-redirect.tester.js @@ -0,0 +1,10 @@ +'use strict' + +const t = (module.exports = require('../tester').createServiceTester()) + +t.create('Plugin Tested WP Version (Alias)') + .get('/akismet.svg', { + followRedirect: false, + }) + .expectStatus(301) + .expectHeader('Location', '/wordpress/plugin/wp-version/akismet.svg') diff --git a/services/wordpress/wordpress-platform.service.js b/services/wordpress/wordpress-platform.service.js index 18611d8f67..66cc59ba66 100644 --- a/services/wordpress/wordpress-platform.service.js +++ b/services/wordpress/wordpress-platform.service.js @@ -1,49 +1,15 @@ 'use strict' -const semver = require('semver') -const Joi = require('joi') const { addv } = require('../text-formatters') const { version: versionColor } = require('../color-formatters') -const { NotFound } = require('..') const BaseWordpress = require('./wordpress-base') +const { versionColorForWordpressVersion } = require('./wordpress-version-color') -const coreSchema = Joi.object() - .keys({ - offers: Joi.array() - .items( - Joi.object() - .keys({ - version: Joi.string() - .regex(/^\d+(\.\d+)?(\.\d+)?$/) - .required(), - }) - .required() - ) - .required(), - }) - .required() - -class BaseWordpressPlatform extends BaseWordpress { - static get defaultBadgeData() { - return { label: 'wordpress' } - } - static get extensionType() { - return 'plugin' - } - - static render({ response }) { - return { - message: addv(response.requires), - color: versionColor(response.requires), - } - } - +class WordpressPluginRequiresVersion extends BaseWordpress { static get category() { return 'platform-support' } -} -class WordpressPluginRequiresVersion extends BaseWordpressPlatform { static get route() { return { base: `wordpress/plugin/wp-version`, @@ -56,59 +22,38 @@ class WordpressPluginRequiresVersion extends BaseWordpressPlatform { { title: 'Wordpress Plugin: Required WP Version', namedParams: { slug: 'bbpress' }, - staticPreview: this.render({ response: { requires: '4.8' } }), + staticPreview: this.render({ wordpressVersion: '4.8' }), }, ] } -} -class WordpressPluginTestedVersion extends BaseWordpressPlatform { - static render({ version, color }) { - return { - message: `${addv(version)} tested`, - color, - } + static get defaultBadgeData() { + return { label: 'wordpress' } } - async fetchCore() { - const coreURL = 'https://api.wordpress.org/core/version-check/1.7/' - return this._requestJson({ - url: coreURL, - schema: coreSchema, - }) + static render({ wordpressVersion }) { + return { + message: addv(wordpressVersion), + color: versionColor(wordpressVersion), + } } async handle({ slug }) { - const json = await this.fetch({ slug }) - const core = await this.fetchCore() + const { requires: wordpressVersion } = await this.fetch({ + extensionType: 'plugin', + slug, + }) + return this.constructor.render({ wordpressVersion }) + } +} - if (!json || !core) { - throw new NotFound() - } +class WordpressPluginTestedVersion extends BaseWordpress { + static get category() { + return 'platform-support' + } - //Copy & Paste old color formatting code. - const versions = core.offers.map(v => v.version) - let testedVersion = json.tested - let color = '' - const svTestedVersion = - testedVersion.split('.').length === 2 - ? (testedVersion += '.0') - : testedVersion - const svVersion = - versions[0].split('.').length === 2 ? (versions[0] += '.0') : versions[0] - - if ( - testedVersion === versions[0] || - semver.gtr(svTestedVersion, svVersion) - ) { - color = 'brightgreen' - } else if (versions.indexOf(testedVersion) !== -1) { - color = 'orange' - } else { - color = 'yellow' - } - - return this.constructor.render({ version: testedVersion, color }) + static get defaultBadgeData() { + return { label: 'wordpress' } } static get route() { @@ -123,29 +68,42 @@ class WordpressPluginTestedVersion extends BaseWordpressPlatform { { title: 'Wordpress Plugin: Tested WP Version', namedParams: { slug: 'bbpress' }, - staticPreview: this.render({ version: '4.9.8', color: 'brightgreen' }), - documentation: `

There is an alias for this badge. wordpress/v/:slug.svg

`, + staticPreview: this.renderStaticPreview({ + testedVersion: '4.9.8', + }), }, ] } -} -class WordpressPluginTestedVersionAlias extends WordpressPluginTestedVersion { - static get route() { + static renderStaticPreview({ testedVersion }) { + // Since this badge has an async `render()` function, but `get examples()` has to + // be synchronous, this method exists. It should return the same value as the + // real `render()`. return { - base: `wordpress/v`, - pattern: ':slug', + message: `${addv(testedVersion)} tested`, + color: 'brightgreen', } } - //The alias is documented in the above class. - static get examples() { - return [] + static async render({ testedVersion }) { + // Atypically, the `render()` function of this badge is `async` because it needs to pull + // data from the server. + return { + message: `${addv(testedVersion)} tested`, + color: await versionColorForWordpressVersion(testedVersion), + } + } + + async handle({ slug }) { + const { tested: testedVersion } = await this.fetch({ + extensionType: 'plugin', + slug, + }) + return this.constructor.render({ testedVersion }) } } module.exports = { WordpressPluginRequiresVersion, WordpressPluginTestedVersion, - WordpressPluginTestedVersionAlias, } diff --git a/services/wordpress/wordpress-platform.tester.js b/services/wordpress/wordpress-platform.tester.js index 447af95637..9907eac4ab 100644 --- a/services/wordpress/wordpress-platform.tester.js +++ b/services/wordpress/wordpress-platform.tester.js @@ -5,8 +5,9 @@ const { ServiceTester } = require('../tester') const { isVPlusDottedVersionAtLeastOne } = require('../test-validators') const t = (module.exports = new ServiceTester({ - id: 'wordpress', + id: 'WordpressPlatform', title: 'Wordpress Platform Tests', + pathPrefix: '/wordpress', })) t.create('Plugin Required WP Version') @@ -23,13 +24,6 @@ t.create('Plugin Tested WP Version') message: Joi.string().regex(/^v\d+(\.\d+)?(\.\d+)? tested$/), }) -t.create('Plugin Tested WP Version (Alias)') - .get('/v/akismet.json') - .expectBadge({ - label: 'wordpress', - message: Joi.string().regex(/^v\d+(\.\d+)?(\.\d+)? tested$/), - }) - const mockedQuerySelector = { action: 'plugin_information', request: { diff --git a/services/wordpress/wordpress-rating.service.js b/services/wordpress/wordpress-rating.service.js index ceb5219cc9..f43c6844dd 100644 --- a/services/wordpress/wordpress-rating.service.js +++ b/services/wordpress/wordpress-rating.service.js @@ -16,15 +16,6 @@ const extensionData = { } class WordpressRatingBase extends BaseWordpress { - static render({ response }) { - const total = response.num_ratings - const rating = ((response.rating / 100) * 5).toFixed(1) - return { - message: `${rating}/5 (${metric(total)})`, - color: floorCount(rating, 2, 3, 4), - } - } - static get category() { return 'rating' } @@ -49,10 +40,6 @@ function RatingForExtensionType(extensionType) { } } - static get extensionType() { - return extensionType - } - static get examples() { return [ { @@ -67,6 +54,22 @@ function RatingForExtensionType(extensionType) { }, ] } + + static render({ rating, numRatings }) { + const scaledAndRounded = ((rating / 100) * 5).toFixed(1) + return { + message: `${scaledAndRounded}/5 (${metric(numRatings)})`, + color: floorCount(scaledAndRounded, 2, 3, 4), + } + } + + async handle({ slug }) { + const { rating, num_ratings: numRatings } = await this.fetch({ + extensionType, + slug, + }) + return this.constructor.render({ rating, numRatings }) + } } } @@ -78,23 +81,13 @@ function StarsForExtensionType(extensionType) { return `Wordpress${capt}Stars` } - static render({ response }) { - const rating = (response.rating / 100) * 5 - return { message: starRating(rating), color: floorCount(rating, 2, 3, 4) } - } - static get route() { return { base: `wordpress/${extensionType}`, - format: '(?:stars|r)/(.+)', - capture: ['slug'], + pattern: '(stars|r)/:slug', } } - static get extensionType() { - return extensionType - } - static get examples() { return [ { @@ -111,6 +104,19 @@ function StarsForExtensionType(extensionType) { }, ] } + + static render({ rating }) { + const scaled = (rating / 100) * 5 + return { message: starRating(scaled), color: floorCount(scaled, 2, 3, 4) } + } + + async handle({ slug }) { + const { rating } = await this.fetch({ + extensionType, + slug, + }) + return this.constructor.render({ rating }) + } } } diff --git a/services/wordpress/wordpress-version-color.js b/services/wordpress/wordpress-version-color.js new file mode 100644 index 0000000000..6b678d0658 --- /dev/null +++ b/services/wordpress/wordpress-version-color.js @@ -0,0 +1,55 @@ +'use strict' + +const { promisify } = require('util') +const semver = require('semver') +const { regularUpdate } = require('../../core/legacy/regular-update') + +// TODO: Incorporate this schema. +// const schema = Joi.object() +// .keys({ +// offers: Joi.array() +// .items( +// Joi.object() +// .keys({ +// version: Joi.string() +// .regex(/^\d+(\.\d+)?(\.\d+)?$/) +// .required(), +// }) +// .required() +// ) +// .required(), +// }) +// .required() + +function getOfferedVersions() { + return promisify(regularUpdate)({ + url: 'https://api.wordpress.org/core/version-check/1.7/', + intervalMillis: 24 * 3600 * 1000, + json: true, + scraper: json => json.offers.map(v => v.version), + }) +} + +async function versionColorForWordpressVersion(version) { + const offeredVersions = await getOfferedVersions() + + // What is this? + let latestVersion = offeredVersions[0] + const svVersion = + latestVersion.split('.').length === 2 + ? (latestVersion += '.0') + : latestVersion + + if (version === latestVersion || semver.gtr(version, svVersion)) { + return 'brightgreen' + } else if (offeredVersions.includes(version)) { + return 'orange' + } else { + return 'yellow' + } +} + +module.exports = { + getOfferedVersions, + versionColorForWordpressVersion, +} diff --git a/services/wordpress/wordpress-version.service.js b/services/wordpress/wordpress-version.service.js index 1c8ae711df..6cdc942536 100644 --- a/services/wordpress/wordpress-version.service.js +++ b/services/wordpress/wordpress-version.service.js @@ -21,25 +21,10 @@ function VersionForExtensionType(extensionType) { return `Wordpress${capt}Version` } - static get extensionType() { - return extensionType - } - - static render({ response }) { - return { - message: addv(response.version), - color: versionColor(response.version), - } - } - static get category() { return 'version' } - static get defaultBadgeData() { - return { label: extensionType } - } - static get route() { return { base: `wordpress/${extensionType}/v`, @@ -52,10 +37,29 @@ function VersionForExtensionType(extensionType) { { title: `Wordpress ${capt} Version`, namedParams: { slug: exampleSlug }, - staticPreview: this.render({ response: { version: 2.5 } }), + staticPreview: this.render({ version: 2.5 }), }, ] } + + static get defaultBadgeData() { + return { label: extensionType } + } + + static render({ version }) { + return { + message: addv(version), + color: versionColor(version), + } + } + + async handle({ slug }) { + const { version } = await this.fetch({ + extensionType, + slug, + }) + return this.constructor.render({ version }) + } } }