diff --git a/services/jetbrains/jetbrains-base.js b/services/jetbrains/jetbrains-base.js
index 877fb413fd..cba934dd42 100644
--- a/services/jetbrains/jetbrains-base.js
+++ b/services/jetbrains/jetbrains-base.js
@@ -1,11 +1,32 @@
'use strict'
const { BaseXmlService, NotFound } = require('..')
+const { parseJson } = require('../../core/base-service/json')
+/*
+JetBrains is a bit awkward. Sometimes we want to call an XML API
+and sometimes we want to call a JSON API so we need a mongrel base class.
+When the legacy IntelliJ (XML) API is retired we can simplify all this and
+switch JetbrainsDownloads, JetbrainsRating and JetbrainsVersion to just
+inherit from BaseJsonService directly.
+*/
module.exports = class JetbrainsBase extends BaseXmlService {
+ static _isLegacyPluginId(pluginId) {
+ return !pluginId.match(/^([0-9])+/)
+ }
+
+ static _cleanPluginId(pluginId) {
+ const match = pluginId.match(/^([0-9])+/)
+ if (match) {
+ return match[0]
+ }
+ return pluginId
+ }
+
+ // xml
static _validate(data, schema) {
if (data['plugin-repository'] === '') {
- // Note the 'not found' response from JetBrains Plugins Repository is:
+ // Note the 'not found' response from JetBrains IntelliJ API is:
// status code = 200,
// body =
// which is parsed to object = { 'plugin-repository': '' }
@@ -14,7 +35,7 @@ module.exports = class JetbrainsBase extends BaseXmlService {
return super._validate(data, schema)
}
- async fetchPackageData({ pluginId, schema }) {
+ async fetchIntelliJPluginData({ pluginId, schema }) {
const parserOptions = {
parseNodeValue: false,
ignoreAttributes: false,
@@ -25,4 +46,27 @@ module.exports = class JetbrainsBase extends BaseXmlService {
parserOptions,
})
}
+
+ // json
+ _parseJson(buffer) {
+ return parseJson(buffer)
+ }
+
+ static _validateJson(data, schema) {
+ return super._validate(data, schema)
+ }
+
+ async _requestJson({ schema, url, options = {}, errorMessages = {} }) {
+ const mergedOptions = {
+ ...{ headers: { Accept: 'application/json' } },
+ ...options,
+ }
+ const { buffer } = await this._request({
+ url,
+ options: mergedOptions,
+ errorMessages,
+ })
+ const json = this._parseJson(buffer)
+ return this.constructor._validateJson(json, schema)
+ }
}
diff --git a/services/jetbrains/jetbrains-downloads.service.js b/services/jetbrains/jetbrains-downloads.service.js
index 3a5019072c..c2d7f87904 100644
--- a/services/jetbrains/jetbrains-downloads.service.js
+++ b/services/jetbrains/jetbrains-downloads.service.js
@@ -6,7 +6,7 @@ const { downloadCount: downloadCountColor } = require('../color-formatters')
const { nonNegativeInteger } = require('../validators')
const JetbrainsBase = require('./jetbrains-base')
-const schema = Joi.object({
+const intelliJschema = Joi.object({
'plugin-repository': Joi.object({
category: Joi.object({
'idea-plugin': Joi.array()
@@ -22,6 +22,8 @@ const schema = Joi.object({
}).required(),
}).required()
+const jetbrainsSchema = Joi.object({ downloads: nonNegativeInteger }).required()
+
module.exports = class JetbrainsDownloads extends JetbrainsBase {
static category = 'downloads'
@@ -32,9 +34,9 @@ module.exports = class JetbrainsDownloads extends JetbrainsBase {
static examples = [
{
- title: 'JetBrains IntelliJ plugins',
+ title: 'JetBrains plugins',
namedParams: {
- pluginId: '1347-scala',
+ pluginId: '1347',
},
staticPreview: this.render({ downloads: 10200000 }),
},
@@ -48,9 +50,27 @@ module.exports = class JetbrainsDownloads extends JetbrainsBase {
}
async handle({ pluginId }) {
- const pluginData = await this.fetchPackageData({ pluginId, schema })
- const downloads =
- pluginData['plugin-repository'].category['idea-plugin'][0]['@_downloads']
+ let downloads
+ if (this.constructor._isLegacyPluginId(pluginId)) {
+ const intelliJPluginData = await this.fetchIntelliJPluginData({
+ pluginId,
+ schema: intelliJschema,
+ })
+ downloads =
+ intelliJPluginData['plugin-repository'].category['idea-plugin'][0][
+ '@_downloads'
+ ]
+ } else {
+ const jetbrainsPluginData = await this._requestJson({
+ schema: jetbrainsSchema,
+ url: `https://plugins.jetbrains.com/api/plugins/${this.constructor._cleanPluginId(
+ pluginId
+ )}`,
+ errorMessages: { 400: 'not found' },
+ })
+ downloads = jetbrainsPluginData.downloads
+ }
+
return this.constructor.render({ downloads })
}
}
diff --git a/services/jetbrains/jetbrains-downloads.tester.js b/services/jetbrains/jetbrains-downloads.tester.js
index 1a95d5dae5..946f43e517 100644
--- a/services/jetbrains/jetbrains-downloads.tester.js
+++ b/services/jetbrains/jetbrains-downloads.tester.js
@@ -15,12 +15,21 @@ t.create('downloads (user friendly plugin id)')
.get('/1347-scala.json')
.expectBadge({ label: 'downloads', message: isMetric })
-t.create('downloads')
+t.create('downloads (numeric id)')
.get('/9435.json')
+ .intercept(nock =>
+ nock('https://plugins.jetbrains.com')
+ .get('/api/plugins/9435')
+ .reply(200, { downloads: 2 })
+ )
+ .expectBadge({ label: 'downloads', message: '2' })
+
+t.create('downloads (string id)')
+ .get('/io.harply.plugin.json')
.intercept(
nock =>
nock('https://plugins.jetbrains.com')
- .get('/plugins/list?pluginId=9435')
+ .get('/plugins/list?pluginId=io.harply.plugin')
.reply(
200,
`
@@ -36,6 +45,14 @@ t.create('downloads')
)
.expectBadge({ label: 'downloads', message: '2' })
-t.create('unknown plugin')
+t.create('unknown plugin (string id)')
.get('/unknown-plugin.json')
.expectBadge({ label: 'downloads', message: 'not found' })
+
+t.create('unknown plugin (numeric id)')
+ .get('/9999999999999.json')
+ .expectBadge({ label: 'downloads', message: 'not found' })
+
+t.create('unknown plugin (mixed id)')
+ .get('/9999999999999-abc.json')
+ .expectBadge({ label: 'downloads', message: 'not found' })
diff --git a/services/jetbrains/jetbrains-rating.service.js b/services/jetbrains/jetbrains-rating.service.js
index e90fbcfcd5..480d276b6b 100644
--- a/services/jetbrains/jetbrains-rating.service.js
+++ b/services/jetbrains/jetbrains-rating.service.js
@@ -7,7 +7,7 @@ const JetbrainsBase = require('./jetbrains-base')
const pluginRatingColor = colorScale([2, 3, 4])
-const schema = Joi.object({
+const intelliJschema = Joi.object({
'plugin-repository': Joi.object({
category: Joi.object({
'idea-plugin': Joi.array()
@@ -23,6 +23,10 @@ const schema = Joi.object({
}).required(),
}).required()
+const jetbrainsSchema = Joi.object({
+ meanRating: Joi.number().min(0).required(),
+}).required()
+
module.exports = class JetbrainsRating extends JetbrainsBase {
static category = 'rating'
@@ -33,10 +37,10 @@ module.exports = class JetbrainsRating extends JetbrainsBase {
static examples = [
{
- title: 'JetBrains IntelliJ Plugins',
+ title: 'JetBrains Plugins',
pattern: 'rating/:pluginId',
namedParams: {
- pluginId: '11941-automatic-power-saver',
+ pluginId: '11941',
},
staticPreview: this.render({
rating: '4.5',
@@ -44,10 +48,10 @@ module.exports = class JetbrainsRating extends JetbrainsBase {
}),
},
{
- title: 'JetBrains IntelliJ Plugins',
+ title: 'JetBrains Plugins',
pattern: 'stars/:pluginId',
namedParams: {
- pluginId: '11941-automatic-power-saver',
+ pluginId: '11941',
},
staticPreview: this.render({
rating: '4.5',
@@ -70,9 +74,26 @@ module.exports = class JetbrainsRating extends JetbrainsBase {
}
async handle({ format, pluginId }) {
- const pluginData = await this.fetchPackageData({ pluginId, schema })
- const pluginRating =
- pluginData['plugin-repository'].category['idea-plugin'][0].rating
- return this.constructor.render({ rating: pluginRating, format })
+ let rating
+ if (this.constructor._isLegacyPluginId(pluginId)) {
+ const intelliJPluginData = await this.fetchIntelliJPluginData({
+ pluginId,
+ schema: intelliJschema,
+ })
+ rating =
+ intelliJPluginData['plugin-repository'].category['idea-plugin'][0]
+ .rating
+ } else {
+ const jetbrainsPluginData = await this._requestJson({
+ schema: jetbrainsSchema,
+ url: `https://plugins.jetbrains.com/api/plugins/${this.constructor._cleanPluginId(
+ pluginId
+ )}/rating`,
+ errorMessages: { 400: 'not found' },
+ })
+ rating = jetbrainsPluginData.meanRating
+ }
+
+ return this.constructor.render({ rating, format })
}
}
diff --git a/services/jetbrains/jetbrains-rating.tester.js b/services/jetbrains/jetbrains-rating.tester.js
index 1388905f5e..be721b1c32 100644
--- a/services/jetbrains/jetbrains-rating.tester.js
+++ b/services/jetbrains/jetbrains-rating.tester.js
@@ -17,10 +17,18 @@ t.create('rating number (number as a plugin id)')
.get('/rating/11941.json')
.expectBadge({ label: 'rating', message: isRating })
-t.create('rating number for unknown plugin')
+t.create('rating number for unknown plugin (string)')
.get('/rating/unknown-plugin.json')
.expectBadge({ label: 'rating', message: 'not found' })
+t.create('rating stars for unknown plugin (numeric)')
+ .get('/stars/9999999999999.json')
+ .expectBadge({ label: 'rating', message: 'not found' })
+
+t.create('rating stars for unknown plugin (mixed)')
+ .get('/stars/9999999999999-abc.json')
+ .expectBadge({ label: 'rating', message: 'not found' })
+
t.create('rating stars (user friendly plugin id)')
.get('/stars/11941-automatic-power-saver.json')
.expectBadge({ label: 'rating', message: isStarRating })
@@ -33,23 +41,42 @@ t.create('rating stars (number as a plugin id)')
.get('/stars/11941.json')
.expectBadge({ label: 'rating', message: isStarRating })
-t.create('rating stars for unknown plugin')
+t.create('rating stars for unknown plugin (string id)')
.get('/stars/unknown-plugin.json')
.expectBadge({ label: 'rating', message: 'not found' })
-t.create('rating number')
+t.create('rating stars for unknown plugin (numeric id)')
+ .get('/stars/9999999999999.json')
+ .expectBadge({ label: 'rating', message: 'not found' })
+
+t.create('rating stars for unknown plugin (mixed id)')
+ .get('/stars/9999999999999-abc.json')
+ .expectBadge({ label: 'rating', message: 'not found' })
+
+t.create('rating number (numeric id)')
.get('/rating/11941.json')
+ .intercept(nock =>
+ nock('https://plugins.jetbrains.com')
+ .get('/api/plugins/11941/rating')
+ .reply(200, { meanRating: 4.4848 })
+ )
+ .expectBadge({ label: 'rating', message: '4.5/5' })
+
+t.create('rating number (string id)')
+ .get('/rating/com.chriscarini.jetbrains.jetbrains-auto-power-saver.json')
.intercept(
nock =>
nock('https://plugins.jetbrains.com')
- .get('/plugins/list?pluginId=11941')
+ .get(
+ '/plugins/list?pluginId=com.chriscarini.jetbrains.jetbrains-auto-power-saver'
+ )
.reply(
200,
`
- 4.5
+ 4.4848
`
@@ -60,19 +87,30 @@ t.create('rating number')
)
.expectBadge({ label: 'rating', message: '4.5/5' })
-t.create('rating stars')
+t.create('rating stars (numeric id)')
.get('/stars/11941.json')
+ .intercept(nock =>
+ nock('https://plugins.jetbrains.com')
+ .get('/api/plugins/11941/rating')
+ .reply(200, { meanRating: 4.4848 })
+ )
+ .expectBadge({ label: 'rating', message: '★★★★½' })
+
+t.create('rating stars (string id)')
+ .get('/stars/com.chriscarini.jetbrains.jetbrains-auto-power-saver.json')
.intercept(
nock =>
nock('https://plugins.jetbrains.com')
- .get('/plugins/list?pluginId=11941')
+ .get(
+ '/plugins/list?pluginId=com.chriscarini.jetbrains.jetbrains-auto-power-saver'
+ )
.reply(
200,
`
- 4.5
+ 4.4848
`
diff --git a/services/jetbrains/jetbrains-version.service.js b/services/jetbrains/jetbrains-version.service.js
index fa081664a8..09aa91bf04 100644
--- a/services/jetbrains/jetbrains-version.service.js
+++ b/services/jetbrains/jetbrains-version.service.js
@@ -4,7 +4,7 @@ const Joi = require('joi')
const { renderVersionBadge } = require('../version')
const JetbrainsBase = require('./jetbrains-base')
-const schema = Joi.object({
+const intelliJschema = Joi.object({
'plugin-repository': Joi.object({
category: Joi.object({
'idea-plugin': Joi.array()
@@ -20,6 +20,15 @@ const schema = Joi.object({
}).required(),
}).required()
+const jetbrainsSchema = Joi.array()
+ .min(1)
+ .items(
+ Joi.object({
+ version: Joi.string().required(),
+ }).required()
+ )
+ .required()
+
module.exports = class JetbrainsVersion extends JetbrainsBase {
static category = 'version'
@@ -30,9 +39,9 @@ module.exports = class JetbrainsVersion extends JetbrainsBase {
static examples = [
{
- title: 'JetBrains IntelliJ Plugins',
+ title: 'JetBrains Plugins',
namedParams: {
- pluginId: '9630-a8translate',
+ pluginId: '9630',
},
staticPreview: this.render({ version: 'v1.7' }),
},
@@ -45,9 +54,26 @@ module.exports = class JetbrainsVersion extends JetbrainsBase {
}
async handle({ pluginId }) {
- const pluginData = await this.fetchPackageData({ pluginId, schema })
- const version =
- pluginData['plugin-repository'].category['idea-plugin'][0].version
+ let version
+ if (this.constructor._isLegacyPluginId(pluginId)) {
+ const intelliJPluginData = await this.fetchIntelliJPluginData({
+ pluginId,
+ schema: intelliJschema,
+ })
+ version =
+ intelliJPluginData['plugin-repository'].category['idea-plugin'][0]
+ .version
+ } else {
+ const jetbrainsPluginData = await this._requestJson({
+ schema: jetbrainsSchema,
+ url: `https://plugins.jetbrains.com/api/plugins/${this.constructor._cleanPluginId(
+ pluginId
+ )}/updates`,
+ errorMessages: { 400: 'not found' },
+ })
+ version = jetbrainsPluginData[0].version
+ }
+
return this.constructor.render({ version })
}
}
diff --git a/services/jetbrains/jetbrains-version.tester.js b/services/jetbrains/jetbrains-version.tester.js
index cf472ddafe..0d5861c176 100644
--- a/services/jetbrains/jetbrains-version.tester.js
+++ b/services/jetbrains/jetbrains-version.tester.js
@@ -22,12 +22,21 @@ t.create('version (number as a plugin id)').get('/7495.json').expectBadge({
message: isVPlusDottedVersionNClauses,
})
-t.create('version')
+t.create('version (numeric id)')
.get('/9435.json')
+ .intercept(nock =>
+ nock('https://plugins.jetbrains.com')
+ .get('/api/plugins/9435/updates')
+ .reply(200, [{ version: '1.0' }])
+ )
+ .expectBadge({ label: 'jetbrains plugin', message: 'v1.0' })
+
+t.create('version (strong id)')
+ .get('/io.harply.plugin.json')
.intercept(
nock =>
nock('https://plugins.jetbrains.com')
- .get('/plugins/list?pluginId=9435')
+ .get('/plugins/list?pluginId=io.harply.plugin')
.reply(
200,
`
@@ -45,6 +54,14 @@ t.create('version')
)
.expectBadge({ label: 'jetbrains plugin', message: 'v1.0' })
-t.create('version for unknown plugin')
+t.create('version for unknown plugin (string id)')
.get('/unknown-plugin.json')
.expectBadge({ label: 'jetbrains plugin', message: 'not found' })
+
+t.create('version for unknown plugin (numeric id)')
+ .get('/9999999999999.json')
+ .expectBadge({ label: 'jetbrains plugin', message: 'not found' })
+
+t.create('unknown plugin (mixed id)')
+ .get('/9999999999999-abc.json')
+ .expectBadge({ label: 'jetbrains plugin', message: 'not found' })