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:
@@ -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')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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]
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user