migrate some services from examples to openApi part 31; affects [packagecontrol discourse] (#9858)

* migrate some services from examples to openApi

* update e2e test assertion
This commit is contained in:
chris48s
2024-01-01 10:32:38 +00:00
committed by GitHub
parent 09c83d9d46
commit a7f2396202
3 changed files with 143 additions and 149 deletions

View File

@@ -25,7 +25,7 @@ describe('Frontend', function () {
cy.contains('Build') cy.contains('Build')
cy.contains('Chat').click() cy.contains('Chat').click()
cy.contains('Discourse status') cy.contains('Discourse Status')
cy.contains('Stack Exchange questions') cy.contains('Stack Exchange questions')
}) })

View File

@@ -1,8 +1,7 @@
import camelcase from 'camelcase'
import Joi from 'joi' import Joi from 'joi'
import { metric } from '../text-formatters.js' import { metric } from '../text-formatters.js'
import { nonNegativeInteger, optionalUrl } from '../validators.js' import { nonNegativeInteger, optionalUrl } from '../validators.js'
import { BaseJsonService } from '../index.js' import { BaseJsonService, queryParams } from '../index.js'
const schemaSingular = Joi.object({ const schemaSingular = Joi.object({
topic_count: nonNegativeInteger, topic_count: nonNegativeInteger,
@@ -24,17 +23,19 @@ const queryParamSchema = Joi.object({
server: optionalUrl.required(), server: optionalUrl.required(),
}).required() }).required()
function singular(variant) {
return variant.slice(0, -1)
}
const params = queryParams({
name: 'server',
example: 'https://meta.discourse.org',
required: true,
})
class DiscourseBase extends BaseJsonService { class DiscourseBase extends BaseJsonService {
static category = 'chat' static category = 'chat'
static buildRoute(metric) {
return {
base: 'discourse',
pattern: metric,
queryParamSchema,
}
}
static defaultBadgeData = { label: 'discourse' } static defaultBadgeData = { label: 'discourse' }
async fetch({ server }) { async fetch({ server }) {
@@ -45,58 +46,61 @@ class DiscourseBase extends BaseJsonService {
} }
} }
function DiscourseMetricIntegrationFactory({ metricType }) { class DiscourseMetric extends DiscourseBase {
// We supply the singular form to more easily check against both schemas. static route = {
// But, we use the plural form as the metric name for grammatical reasons. base: 'discourse',
const metricName = `${metricType}s` pattern: ':variant(topics|users|posts|likes)',
return class DiscourseMetric extends DiscourseBase { queryParamSchema,
// The space is needed so we get 'DiscourseTopics' rather than }
// 'Discoursetopics'. `camelcase()` removes it.
static name = camelcase(`Discourse ${metricName}`, { pascalCase: true })
static route = this.buildRoute(metricName)
static examples = [ static openApi = {
{ '/discourse/topics': {
title: `Discourse ${metricName}`, get: { summary: 'Discourse Topics', parameters: params },
namedParams: {}, },
queryParams: { '/discourse/users': {
server: 'https://meta.discourse.org', get: { summary: 'Discourse Users', parameters: params },
}, },
staticPreview: this.render({ stat: 100 }), '/discourse/posts': {
}, get: { summary: 'Discourse Posts', parameters: params },
] },
'/discourse/likes': {
get: { summary: 'Discourse Likes', parameters: params },
},
}
static render({ stat }) { static render({ variant, stat }) {
return { return {
message: `${metric(stat)} ${metricName}`, message: `${metric(stat)} ${variant}`,
color: 'brightgreen', color: 'brightgreen',
}
} }
}
async handle(_routeParams, { server }) { async handle({ variant }, { server }) {
const data = await this.fetch({ server }) const data = await this.fetch({ server })
// e.g. metricType == 'topic' --> try 'topic_count' then 'topics_count' // e.g. variant == 'topics' --> try 'topic_count' then 'topics_count'
let stat = data[`${metricType}_count`] let stat = data[`${singular(variant)}_count`]
if (stat === undefined) { if (stat === undefined) {
stat = data[`${metricType}s_count`] stat = data[`${variant}_count`]
}
return this.constructor.render({ stat })
} }
return this.constructor.render({ variant, stat })
} }
} }
class DiscourseStatus extends DiscourseBase { class DiscourseStatus extends DiscourseBase {
static route = this.buildRoute('status') static route = {
static examples = [ base: 'discourse',
{ pattern: 'status',
title: 'Discourse status', queryParamSchema,
namedParams: {}, }
queryParams: {
server: 'https://meta.discourse.org', static openApi = {
'/discourse/status': {
get: {
summary: 'Discourse Status',
parameters: params,
}, },
staticPreview: this.render(),
}, },
] }
static render() { static render() {
return { return {
@@ -113,11 +117,4 @@ class DiscourseStatus extends DiscourseBase {
} }
} }
const metricIntegrations = [ export default [DiscourseMetric, DiscourseStatus]
{ metricType: 'topic' },
{ metricType: 'user' },
{ metricType: 'post' },
{ metricType: 'like' },
].map(DiscourseMetricIntegrationFactory)
export default [...metricIntegrations, DiscourseStatus]

View File

@@ -1,9 +1,7 @@
import Joi from 'joi' import Joi from 'joi'
import { renderDownloadsBadge } from '../downloads.js' import { renderDownloadsBadge } from '../downloads.js'
import { nonNegativeInteger } from '../validators.js' import { nonNegativeInteger } from '../validators.js'
import { BaseJsonService } from '../index.js' import { BaseJsonService, pathParams } from '../index.js'
const keywords = ['sublime', 'sublimetext', 'packagecontrol']
const schema = Joi.object({ const schema = Joi.object({
installs: Joi.object({ installs: Joi.object({
@@ -20,92 +18,91 @@ const schema = Joi.object({
}).required(), }).required(),
}) })
function DownloadsForInterval(downloadInterval) { const intervalMap = {
const { base, interval, transform, name } = { dd: {
day: { label: 'day',
base: 'packagecontrol/dd', transform: resp => {
interval: 'day', const platforms = resp.installs.daily.data
transform: resp => { let downloads = 0
const platforms = resp.installs.daily.data platforms.forEach(platform => {
let downloads = 0 // use the downloads from yesterday
platforms.forEach(platform => { downloads += platform.totals[1]
// use the downloads from yesterday
downloads += platform.totals[1]
})
return downloads
},
name: 'PackageControlDownloadsDay',
},
week: {
base: 'packagecontrol/dw',
interval: 'week',
transform: resp => {
const platforms = resp.installs.daily.data
let downloads = 0
platforms.forEach(platform => {
// total for the first 7 days
for (let i = 0; i < 7; i++) {
downloads += platform.totals[i]
}
})
return downloads
},
name: 'PackageControlDownloadsWeek',
},
month: {
base: 'packagecontrol/dm',
interval: 'month',
transform: resp => {
const platforms = resp.installs.daily.data
let downloads = 0
platforms.forEach(platform => {
// total for the first 30 days
for (let i = 0; i < 30; i++) {
downloads += platform.totals[i]
}
})
return downloads
},
name: 'PackageControlDownloadsMonth',
},
total: {
base: 'packagecontrol/dt',
transform: resp => resp.installs.total,
name: 'PackageControlDownloadsTotal',
},
}[downloadInterval]
return class PackageControlDownloads extends BaseJsonService {
static name = name
static category = 'downloads'
static route = { base, pattern: ':packageName' }
static examples = [
{
title: 'Package Control',
namedParams: { packageName: 'GitGutter' },
staticPreview: renderDownloadsBadge({ downloads: 12000 }),
keywords,
},
]
static defaultBadgeData = { label: 'downloads' }
async fetch({ packageName }) {
const url = `https://packagecontrol.io/packages/${packageName}.json`
return this._requestJson({ schema, url })
}
async handle({ packageName }) {
const data = await this.fetch({ packageName })
return renderDownloadsBadge({
downloads: transform(data),
interval,
}) })
} return downloads
} },
},
dw: {
label: 'week',
transform: resp => {
const platforms = resp.installs.daily.data
let downloads = 0
platforms.forEach(platform => {
// total for the first 7 days
for (let i = 0; i < 7; i++) {
downloads += platform.totals[i]
}
})
return downloads
},
},
dm: {
label: 'month',
transform: resp => {
const platforms = resp.installs.daily.data
let downloads = 0
platforms.forEach(platform => {
// total for the first 30 days
for (let i = 0; i < 30; i++) {
downloads += platform.totals[i]
}
})
return downloads
},
},
dt: {
transform: resp => resp.installs.total,
},
} }
export default ['day', 'week', 'month', 'total'].map(DownloadsForInterval) export default class PackageControlDownloads extends BaseJsonService {
static category = 'downloads'
static route = {
base: 'packagecontrol',
pattern: ':interval(dd|dw|dm|dt)/:packageName',
}
static openApi = {
'/packagecontrol/{interval}/{packageName}': {
get: {
summary: 'Package Control Downloads',
description:
'Package Control is a package registry for Sublime Text packages',
parameters: pathParams(
{
name: 'interval',
example: 'dt',
schema: { type: 'string', enum: this.getEnum('interval') },
description: 'Daily, Weekly, Monthly, or Total downloads',
},
{ name: 'packageName', example: 'GitGutter' },
),
},
},
}
static defaultBadgeData = { label: 'downloads' }
async fetch({ packageName }) {
const url = `https://packagecontrol.io/packages/${packageName}.json`
return this._requestJson({ schema, url })
}
async handle({ interval, packageName }) {
const data = await this.fetch({ packageName })
return renderDownloadsBadge({
downloads: intervalMap[interval].transform(data),
interval: intervalMap[interval].label,
})
}
}