From 384c57eb475bfb93a999ef130bcf60aa2ed394cc Mon Sep 17 00:00:00 2001 From: Seth Falco Date: Fri, 2 Jul 2021 04:58:00 +0200 Subject: [PATCH] Add [Weblate] badges (#6677) * feat: add weblate badges * fix: use color-formatter for translated-percentage * fix: use metric formatter * rm: removed units badge * test: use createservicetester Co-authored-by: Caleb Cartwright * fix: refactor weblate badges Co-authored-by: Caleb Cartwright Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com> --- .../weblate-component-license.service.js | 58 ++++++++++++ .../weblate-component-license.tester.js | 11 +++ .../weblate/weblate-entity-count.service.js | 74 +++++++++++++++ .../weblate/weblate-entity-count.tester.js | 22 +++++ ...e-project-translated-percentage.service.js | 68 ++++++++++++++ ...te-project-translated-percentage.tester.js | 13 +++ .../weblate-user-statistics.service.js | 90 +++++++++++++++++++ .../weblate/weblate-user-statistics.tester.js | 22 +++++ 8 files changed, 358 insertions(+) create mode 100644 services/weblate/weblate-component-license.service.js create mode 100644 services/weblate/weblate-component-license.tester.js create mode 100644 services/weblate/weblate-entity-count.service.js create mode 100644 services/weblate/weblate-entity-count.tester.js create mode 100644 services/weblate/weblate-project-translated-percentage.service.js create mode 100644 services/weblate/weblate-project-translated-percentage.tester.js create mode 100644 services/weblate/weblate-user-statistics.service.js create mode 100644 services/weblate/weblate-user-statistics.tester.js diff --git a/services/weblate/weblate-component-license.service.js b/services/weblate/weblate-component-license.service.js new file mode 100644 index 0000000000..22e1038979 --- /dev/null +++ b/services/weblate/weblate-component-license.service.js @@ -0,0 +1,58 @@ +'use strict' + +const Joi = require('joi') +const { BaseJsonService } = require('..') +const { optionalUrl } = require('../validators') + +const schema = Joi.object({ + license: Joi.string().required(), +}).required() + +const queryParamSchema = Joi.object({ + server: optionalUrl.required(), +}).required() + +/** + * This badge displays the license of a component on a Weblate instance. + */ +module.exports = class WeblateComponentLicense extends BaseJsonService { + static category = 'license' + static route = { + base: 'weblate/license', + pattern: ':project/:component', + queryParamSchema, + } + + static examples = [ + { + title: 'Weblate component license', + namedParams: { project: 'godot-engine', component: 'godot' }, + queryParams: { server: 'https://hosted.weblate.org' }, + staticPreview: this.render({ license: 'MIT' }), + keywords: ['i18n', 'translation', 'internationalization'], + }, + ] + + static defaultBadgeData = { label: 'license', color: 'informational' } + + static render({ license }) { + return { message: `${license}` } + } + + async fetch({ project, component, server }) { + return this._requestJson({ + schema, + url: `${server}/api/components/${project}/${component}/`, + errorMessages: { + 403: 'access denied by remote server', + 404: 'component not found', + 429: 'rate limited by remote server', + }, + }) + } + + async handle({ project, component }, { server }) { + const { license } = await this.fetch({ project, component, server }) + return this.constructor.render({ license }) + } +} diff --git a/services/weblate/weblate-component-license.tester.js b/services/weblate/weblate-component-license.tester.js new file mode 100644 index 0000000000..60ec6c726f --- /dev/null +++ b/services/weblate/weblate-component-license.tester.js @@ -0,0 +1,11 @@ +'use strict' + +const t = (module.exports = require('../tester').createServiceTester()) + +t.create('License') + .get('/godot-engine/godot.json?server=https://hosted.weblate.org') + .expectBadge({ label: 'license', message: 'MIT' }) + +t.create("Component Doesn't Exist") + .get('/fake-project/fake-component.json?server=https://hosted.weblate.org') + .expectBadge({ label: 'license', message: 'component not found' }) diff --git a/services/weblate/weblate-entity-count.service.js b/services/weblate/weblate-entity-count.service.js new file mode 100644 index 0000000000..3b04ea3f04 --- /dev/null +++ b/services/weblate/weblate-entity-count.service.js @@ -0,0 +1,74 @@ +'use strict' + +const Joi = require('joi') +const camelcase = require('camelcase') +const { BaseJsonService } = require('..') +const { nonNegativeInteger, optionalUrl } = require('../validators') +const { metric } = require('../text-formatters') + +const schema = Joi.object({ + count: nonNegativeInteger, +}).required() + +const queryParamSchema = Joi.object({ + server: optionalUrl.required(), +}).required() + +class WeblateEntityCountBase extends BaseJsonService { + static category = 'other' + + static buildRoute(entityName) { + return { + base: 'weblate', + pattern: entityName, + queryParamSchema, + } + } + + static defaultBadgeData = { color: 'informational' } + + async fetch({ entityName, server }) { + return this._requestJson({ + schema, + url: `${server}/api/${entityName}/`, + errorMessages: { + 403: 'access denied by remote server', + 429: 'rate limited by remote server', + }, + }) + } +} + +function WeblateEntityCountFactory({ entityName, exampleValue }) { + return class WeblateEntityCountService extends WeblateEntityCountBase { + static name = camelcase(`Weblate ${entityName}`, { pascalCase: true }) + static route = this.buildRoute(entityName) + + static examples = [ + { + title: `Weblate ${entityName}`, + namedParams: {}, + queryParams: { server: 'https://hosted.weblate.org' }, + staticPreview: this.render({ count: exampleValue }), + keywords: ['i18n', 'internationalization'], + }, + ] + + static render({ count }) { + return { label: entityName, message: metric(count) } + } + + async handle(routeParams, { server }) { + const { count } = await this.fetch({ entityName, server }) + return this.constructor.render({ count }) + } + } +} + +const entityCounts = [ + { entityName: 'components', exampleValue: 2799 }, + { entityName: 'projects', exampleValue: 533 }, + { entityName: 'users', exampleValue: 33058 }, +].map(WeblateEntityCountFactory) + +module.exports = [...entityCounts] diff --git a/services/weblate/weblate-entity-count.tester.js b/services/weblate/weblate-entity-count.tester.js new file mode 100644 index 0000000000..a78a5bfc8d --- /dev/null +++ b/services/weblate/weblate-entity-count.tester.js @@ -0,0 +1,22 @@ +'use strict' + +const { ServiceTester } = require('../tester') +const { isMetric } = require('../test-validators') + +const t = (module.exports = new ServiceTester({ + id: 'WeblateEntity', + title: 'Weblate Entity', + pathPrefix: '/weblate', +})) + +t.create('Components') + .get('/components.json?server=https://hosted.weblate.org') + .expectBadge({ label: 'components', message: isMetric }) + +t.create('Projects') + .get('/projects.json?server=https://hosted.weblate.org') + .expectBadge({ label: 'projects', message: isMetric }) + +t.create('Users') + .get('/users.json?server=https://hosted.weblate.org') + .expectBadge({ label: 'users', message: isMetric }) diff --git a/services/weblate/weblate-project-translated-percentage.service.js b/services/weblate/weblate-project-translated-percentage.service.js new file mode 100644 index 0000000000..d5bf6bbbfb --- /dev/null +++ b/services/weblate/weblate-project-translated-percentage.service.js @@ -0,0 +1,68 @@ +'use strict' + +const Joi = require('joi') +const { BaseJsonService } = require('..') +const { optionalUrl } = require('../validators') +const { colorScale } = require('../color-formatters') + +const schema = Joi.object({ + translated_percent: Joi.number().required(), +}).required() + +const queryParamSchema = Joi.object({ + server: optionalUrl.required(), +}).required() + +/** + * This badge displays the percentage of strings translated on a project on a + * Weblate instance. + */ +module.exports = class WeblateProjectTranslatedPercentage extends ( + BaseJsonService +) { + static category = 'other' + static route = { base: 'weblate', pattern: ':project', queryParamSchema } + + static examples = [ + { + title: 'Weblate project translated', + namedParams: { project: 'godot-engine' }, + queryParams: { server: 'https://hosted.weblate.org' }, + staticPreview: this.render({ translatedPercent: 20.5 }), + keywords: ['i18n', 'translation', 'internationalization'], + }, + ] + + static defaultBadgeData = { label: 'translated' } + + /** + * Takes a percentage and maps it to a message and color. + * + * The colors are determined based on how Weblate does it internally. + * {@link https://github.com/WeblateOrg/weblate/blob/main/weblate/trans/widgets.py Weblate on GitHub} + * + * @param {*} translatedPercent The percentage of translations translated. + * @returns {object} Format for the badge. + */ + static render({ translatedPercent }) { + const color = colorScale([75, 90])(translatedPercent) + return { message: `${translatedPercent.toFixed(0)}%`, color } + } + + async fetch({ project, server }) { + return this._requestJson({ + schema, + url: `${server}/api/projects/${project}/statistics/`, + errorMessages: { + 403: 'access denied by remote server', + 404: 'project not found', + 429: 'rate limited by remote server', + }, + }) + } + + async handle({ project }, { server }) { + const { translated_percent } = await this.fetch({ project, server }) + return this.constructor.render({ translatedPercent: translated_percent }) + } +} diff --git a/services/weblate/weblate-project-translated-percentage.tester.js b/services/weblate/weblate-project-translated-percentage.tester.js new file mode 100644 index 0000000000..52a8ba67d1 --- /dev/null +++ b/services/weblate/weblate-project-translated-percentage.tester.js @@ -0,0 +1,13 @@ +'use strict' + +const t = (module.exports = require('../tester').createServiceTester()) + +const { isPercentage } = require('../test-validators') + +t.create('License') + .get('/godot-engine.json?server=https://hosted.weblate.org') + .expectBadge({ label: 'translated', message: isPercentage }) + +t.create('Not Valid') + .get('/fake-project.json?server=https://hosted.weblate.org') + .expectBadge({ label: 'translated', message: 'project not found' }) diff --git a/services/weblate/weblate-user-statistics.service.js b/services/weblate/weblate-user-statistics.service.js new file mode 100644 index 0000000000..e61cdd8814 --- /dev/null +++ b/services/weblate/weblate-user-statistics.service.js @@ -0,0 +1,90 @@ +'use strict' + +const Joi = require('joi') +const camelcase = require('camelcase') +const { BaseJsonService } = require('..') +const { nonNegativeInteger, optionalUrl } = require('../validators') +const { metric } = require('../text-formatters') + +const schema = Joi.object({ + translated: nonNegativeInteger, + suggested: nonNegativeInteger, + uploaded: nonNegativeInteger, + commented: nonNegativeInteger, + languages: nonNegativeInteger, +}).required() + +const queryParamSchema = Joi.object({ + server: optionalUrl.required(), +}).required() + +class WeblateUserStatisticBase extends BaseJsonService { + static category = 'other' + + static buildRoute(statistic) { + return { + base: 'weblate/user', + pattern: `:user/${statistic}`, + queryParamSchema, + } + } + + static defaultBadgeData = { color: 'informational' } + + async fetch({ user, server }) { + return this._requestJson({ + schema, + url: `${server}/api/users/${user}/statistics/`, + errorMessages: { + 403: 'access denied by remote server', + 404: 'user not found', + 429: 'rate limited by remote server', + }, + }) + } +} + +function WeblateUserStatisticFactory({ + statisticName, + property, + exampleValue, +}) { + return class WeblateUserStatistic extends WeblateUserStatisticBase { + static name = camelcase(`Weblate user ${statisticName}`, { + pascalCase: true, + }) + + static route = this.buildRoute(statisticName) + + static examples = [ + { + title: `Weblate user ${statisticName}`, + namedParams: { user: 'nijel' }, + queryParams: { server: 'https://hosted.weblate.org' }, + staticPreview: this.render({ count: exampleValue }), + keywords: ['i18n', 'internationalization'], + }, + ] + + static render({ count }) { + return { label: statisticName, message: metric(count) } + } + + async handle({ user }, { server }) { + const data = await this.fetch({ user, server }) + return this.constructor.render({ count: data[property] }) + } + } +} + +const userStatistics = [ + { + statisticName: 'translations', + property: 'translated', + exampleValue: 30585, + }, + { statisticName: 'suggestions', property: 'suggested', exampleValue: 7 }, + { statisticName: 'languages', property: 'languages', exampleValue: 1 }, +].map(WeblateUserStatisticFactory) + +module.exports = [...userStatistics] diff --git a/services/weblate/weblate-user-statistics.tester.js b/services/weblate/weblate-user-statistics.tester.js new file mode 100644 index 0000000000..42934b072f --- /dev/null +++ b/services/weblate/weblate-user-statistics.tester.js @@ -0,0 +1,22 @@ +'use strict' + +const { ServiceTester } = require('../tester') +const { isMetric } = require('../test-validators') + +const t = (module.exports = new ServiceTester({ + id: 'WeblateUserStatistic', + title: 'Weblate User Statistic', + pathPrefix: '/weblate', +})) + +t.create('Translations') + .get('/user/nijel/translations.json?server=https://hosted.weblate.org') + .expectBadge({ label: 'translations', message: isMetric }) + +t.create('Suggestions') + .get('/user/nijel/suggestions.json?server=https://hosted.weblate.org') + .expectBadge({ label: 'suggestions', message: isMetric }) + +t.create('Languages') + .get('/user/nijel/languages.json?server=https://hosted.weblate.org') + .expectBadge({ label: 'languages', message: isMetric })