Add [NpmStatDownloads] Badge (#9783)

* feat: add npm downloads (by author) badge

* Update services/npm-stat/npm-stat-downloads.service.js

Co-authored-by: chris48s <chris48s@users.noreply.github.com>

* test: add test cases for NpmStatDownloads helper getTotalDownloads

* refactor: using dayjs to get from && until date string

* feat: remove support of dt

---------

Co-authored-by: chris48s <chris48s@users.noreply.github.com>
This commit is contained in:
Huan
2023-12-13 03:19:22 +08:00
committed by GitHub
parent 407fe39e6e
commit 57ba623fd6
3 changed files with 131 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
import Joi from 'joi'
import dayjs from 'dayjs'
import { nonNegativeInteger } from '../validators.js'
import { BaseJsonService } from '../index.js'
import { renderDownloadsBadge } from '../downloads.js'
const schema = Joi.object()
.pattern(Joi.string(), Joi.object().pattern(Joi.string(), nonNegativeInteger))
.required()
const intervalMap = {
dw: { interval: 'week' },
dm: { interval: 'month' },
dy: { interval: 'year' },
}
export default class NpmStatDownloads extends BaseJsonService {
static category = 'downloads'
static route = {
base: 'npm-stat',
pattern: ':interval(dw|dm|dy)/:author',
}
static examples = [
{
title: 'npm (by author)',
documentation:
'The total number of downloads of npm packages published by the specified author from [npm-stat](https://npm-stat.com).',
namedParams: { interval: 'dy', author: 'dukeluo' },
staticPreview: this.render({ interval: 'dy', downloadCount: 30000 }),
keywords: ['node'],
},
]
static _cacheLength = 21600
static defaultBadgeData = { label: 'downloads' }
static getTotalDownloads(data) {
const add = (x, y) => x + y
const sum = nums => nums.reduce(add, 0)
return Object.values(data).reduce(
(count, packageDownloads) => count + sum(Object.values(packageDownloads)),
0,
)
}
static render({ interval, downloads }) {
return renderDownloadsBadge({
downloads,
interval: intervalMap[interval].interval,
colorOverride: downloads > 0 ? 'brightgreen' : 'red',
})
}
async handle({ interval, author }) {
const unit = intervalMap[interval].interval
const today = dayjs()
const until = today.format('YYYY-MM-DD')
const from = today.subtract(1, unit).format('YYYY-MM-DD')
const data = await this._requestJson({
url: `https://npm-stat.com/api/download-counts?author=${author}&from=${from}&until=${until}`,
schema,
})
const downloads = this.constructor.getTotalDownloads(data)
return this.constructor.render({ interval, downloads })
}
}

View File

@@ -0,0 +1,25 @@
import { test, given } from 'sazerac'
import NpmStatDownloads from './npm-stat-downloads.service.js'
describe('NpmStatDownloads helpers', function () {
test(NpmStatDownloads.getTotalDownloads, () => {
given({
'hexo-theme-candelas': {
'2022-12-01': 1,
'2022-12-02': 2,
'2022-12-03': 3,
},
'@dukeluo/fanjs': {
'2022-12-01': 10,
'2022-12-02': 20,
'2022-12-03': 30,
},
'eslint-plugin-check-file': {
'2022-12-01': 100,
'2022-12-02': 200,
'2022-12-03': 300,
},
}).expect(666)
given({}).expect(0)
})
})

View File

@@ -0,0 +1,35 @@
import { isMetricOverTimePeriod } from '../test-validators.js'
import { createServiceTester } from '../tester.js'
export const t = await createServiceTester()
t.create('weekly downloads of npm author dukeluo')
.get('/dw/dukeluo.json')
.expectBadge({
label: 'downloads',
message: isMetricOverTimePeriod,
color: 'brightgreen',
})
t.create('monthly downloads of npm author dukeluo')
.get('/dm/dukeluo.json')
.expectBadge({
label: 'downloads',
message: isMetricOverTimePeriod,
color: 'brightgreen',
})
t.create('yearly downloads of npm author dukeluo')
.get('/dy/dukeluo.json')
.expectBadge({
label: 'downloads',
message: isMetricOverTimePeriod,
color: 'brightgreen',
})
t.create('downloads of unknown npm package author')
.get('/dy/npm-api-does-not-have-this-package-author.json')
.expectBadge({
label: 'downloads',
message: '0/year',
color: 'red',
})