Compare commits
49 Commits
server-202
...
server-202
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55d711c39d | ||
|
|
a190bb718f | ||
|
|
81c5c25d59 | ||
|
|
d78f9f8233 | ||
|
|
3528e842d7 | ||
|
|
f413650fe4 | ||
|
|
d1b3145e48 | ||
|
|
882f06824c | ||
|
|
42be044927 | ||
|
|
07a803228c | ||
|
|
33b5a2d5f1 | ||
|
|
91a6b6d913 | ||
|
|
252a0046d1 | ||
|
|
d52ab7867a | ||
|
|
ae98c7d10c | ||
|
|
9294e5d0d2 | ||
|
|
64145d736c | ||
|
|
410bb4d2bd | ||
|
|
f9f6ce52db | ||
|
|
1e21dc1ad0 | ||
|
|
33362d399c | ||
|
|
ae6d073179 | ||
|
|
638d096921 | ||
|
|
5e2e1656d1 | ||
|
|
8a4fd194e7 | ||
|
|
111a609e14 | ||
|
|
934f2443e4 | ||
|
|
9e32586c71 | ||
|
|
c0087cae67 | ||
|
|
a251357881 | ||
|
|
eea9bbd447 | ||
|
|
523e31d389 | ||
|
|
73142977e4 | ||
|
|
46a241c7d3 | ||
|
|
6925946bc1 | ||
|
|
8a3b2dbbc0 | ||
|
|
0db492d188 | ||
|
|
c21116ca85 | ||
|
|
523f9de33e | ||
|
|
781bde7750 | ||
|
|
bd6a04ba03 | ||
|
|
cab87aa19d | ||
|
|
65a921da24 | ||
|
|
7c562900ed | ||
|
|
a96c25a405 | ||
|
|
21881d6b9c | ||
|
|
fd49e60410 | ||
|
|
f43675109b | ||
|
|
125d815a1b |
@@ -1,27 +1,5 @@
|
||||
version: 2
|
||||
|
||||
integration_steps: &integration_steps
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: |
|
||||
npm ci
|
||||
environment:
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
|
||||
- run:
|
||||
name: Integration tests
|
||||
when: always
|
||||
environment:
|
||||
mocha_reporter: mocha-junit-reporter
|
||||
MOCHA_FILE: junit/integration/results.xml
|
||||
command: npm run test:integration
|
||||
|
||||
- store_test_results:
|
||||
path: junit
|
||||
|
||||
services_steps: &services_steps
|
||||
steps:
|
||||
- checkout
|
||||
@@ -48,22 +26,6 @@ services_steps: &services_steps
|
||||
path: junit
|
||||
|
||||
jobs:
|
||||
integration:
|
||||
docker:
|
||||
- image: cimg/node:16.15
|
||||
- image: redis
|
||||
|
||||
<<: *integration_steps
|
||||
|
||||
integration@node-17:
|
||||
docker:
|
||||
- image: cimg/node:17.9
|
||||
- image: redis
|
||||
environment:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
<<: *integration_steps
|
||||
|
||||
danger:
|
||||
docker:
|
||||
- image: cimg/node:16.15
|
||||
@@ -185,10 +147,6 @@ workflows:
|
||||
|
||||
on-commit:
|
||||
jobs:
|
||||
- integration@node-17:
|
||||
filters:
|
||||
branches:
|
||||
ignore: gh-pages
|
||||
- frontend:
|
||||
filters:
|
||||
branches:
|
||||
|
||||
20
.github/actions/integration-tests/action.yml
vendored
Normal file
20
.github/actions/integration-tests/action.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: 'Integration tests'
|
||||
description: 'Run integration tests'
|
||||
inputs:
|
||||
github-token:
|
||||
description: 'The GITHUB_TOKEN secret'
|
||||
required: true
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Integration Tests
|
||||
if: always()
|
||||
run: npm run test:integration -- --reporter json --reporter-option 'output=reports/integration-tests.json'
|
||||
env:
|
||||
GH_TOKEN: '${{ inputs.github-token }}'
|
||||
shell: bash
|
||||
|
||||
- name: Write Markdown Summary
|
||||
if: always()
|
||||
run: node scripts/mocha2md.js Integration reports/integration-tests.json >> $GITHUB_STEP_SUMMARY
|
||||
shell: bash
|
||||
4
.github/workflows/auto-close.yml
vendored
4
.github/workflows/auto-close.yml
vendored
@@ -1,5 +1,7 @@
|
||||
name: Auto close
|
||||
on: pull_request_target
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
enforce-dependency-review:
|
||||
|
||||
48
.github/workflows/test-integration-17.yml
vendored
Normal file
48
.github/workflows/test-integration-17.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: Integration@node 17
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-integration-17:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PAT_EXISTS: ${{ secrets.GH_PAT != '' }}
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 17
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
- name: Integration Tests (with PAT)
|
||||
if: ${{ env.PAT_EXISTS == 'true' }}
|
||||
uses: ./.github/actions/integration-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GH_PAT }}'
|
||||
|
||||
- name: Integration Tests (with workflow token)
|
||||
if: ${{ env.PAT_EXISTS == 'false' }}
|
||||
uses: ./.github/actions/integration-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
46
.github/workflows/test-integration.yml
vendored
Normal file
46
.github/workflows/test-integration.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Integration
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-integration:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PAT_EXISTS: ${{ secrets.GH_PAT != '' }}
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Integration Tests (with PAT)
|
||||
if: ${{ env.PAT_EXISTS == 'true' }}
|
||||
uses: ./.github/actions/integration-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GH_PAT }}'
|
||||
|
||||
- name: Integration Tests (with workflow token)
|
||||
if: ${{ env.PAT_EXISTS == 'false' }}
|
||||
uses: ./.github/actions/integration-tests
|
||||
with:
|
||||
github-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
2
.github/workflows/test-lint.yml
vendored
2
.github/workflows/test-lint.yml
vendored
@@ -1,9 +1,11 @@
|
||||
name: Lint
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-lint:
|
||||
|
||||
2
.github/workflows/test-main-17.yml
vendored
2
.github/workflows/test-main-17.yml
vendored
@@ -1,9 +1,11 @@
|
||||
name: Main@node 17
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-main-17:
|
||||
|
||||
2
.github/workflows/test-main.yml
vendored
2
.github/workflows/test-main.yml
vendored
@@ -1,9 +1,11 @@
|
||||
name: Main
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-main:
|
||||
|
||||
2
.github/workflows/test-package-cli.yml
vendored
2
.github/workflows/test-package-cli.yml
vendored
@@ -1,9 +1,11 @@
|
||||
name: Package CLI
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
# Smoke test (render a badge with the CLI) with only the package
|
||||
# dependencies installed.
|
||||
|
||||
2
.github/workflows/test-package-lib.yml
vendored
2
.github/workflows/test-package-lib.yml
vendored
@@ -1,9 +1,11 @@
|
||||
name: Package Library
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-package-lib:
|
||||
|
||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -4,6 +4,21 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
|
||||
|
||||
---
|
||||
|
||||
## server-2022-11-01
|
||||
|
||||
- [Ansible] Add collection badge [#8578](https://github.com/badges/shields/issues/8578)
|
||||
- [VisualStudioMarketplace] Add support to prerelease extensions version (Issue #8207) [#8561](https://github.com/badges/shields/issues/8561)
|
||||
- feat: add [GitlabLastCommit] service [#8508](https://github.com/badges/shields/issues/8508)
|
||||
- fix [swagger] service tests (allow 0 items in array) [#8564](https://github.com/badges/shields/issues/8564)
|
||||
- fix codecov badge for non-default branch [#8565](https://github.com/badges/shields/issues/8565)
|
||||
- Add [GitHubLastCommit] by committer badge [#8537](https://github.com/badges/shields/issues/8537)
|
||||
- [GitHubReleaseDate] - published_at field [#8543](https://github.com/badges/shields/issues/8543)
|
||||
- Fix [Testspace] with new "untested" value in case_counts array [#8544](https://github.com/badges/shields/issues/8544)
|
||||
- fix: Support WAITING status for GitHub deployments [#8521](https://github.com/badges/shields/issues/8521)
|
||||
- [Whatpulse] badge for a user and for a team [#8466](https://github.com/badges/shields/issues/8466)
|
||||
- deprecate [pkgreview] service [#8499](https://github.com/badges/shields/issues/8499)
|
||||
- Dependency updates
|
||||
|
||||
## server-2022-10-08
|
||||
|
||||
- deprecate [criterion] service [#8501](https://github.com/badges/shields/issues/8501)
|
||||
|
||||
1053
package-lock.json
generated
1053
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
38
package.json
38
package.json
@@ -25,28 +25,28 @@
|
||||
"@fontsource/lekton": "^4.5.11",
|
||||
"@renovate/pep440": "^1.0.0",
|
||||
"@renovatebot/ruby-semver": "^1.1.6",
|
||||
"@sentry/node": "^7.14.2",
|
||||
"@sentry/node": "^7.17.2",
|
||||
"@shields_io/camp": "^18.1.1",
|
||||
"badge-maker": "file:badge-maker",
|
||||
"bytes": "^3.1.2",
|
||||
"camelcase": "^7.0.0",
|
||||
"chalk": "^5.1.0",
|
||||
"chalk": "^5.1.2",
|
||||
"check-node-version": "^4.2.1",
|
||||
"cloudflare-middleware": "^1.0.4",
|
||||
"config": "^3.3.8",
|
||||
"cross-env": "^7.0.3",
|
||||
"dayjs": "^1.11.5",
|
||||
"dayjs": "^1.11.6",
|
||||
"decamelize": "^3.2.0",
|
||||
"emojic": "^1.1.17",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"fast-xml-parser": "^4.0.11",
|
||||
"glob": "^8.0.3",
|
||||
"global-agent": "^3.0.0",
|
||||
"got": "^12.5.1",
|
||||
"got": "^12.5.2",
|
||||
"graphql": "^15.6.1",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"ioredis": "5.2.3",
|
||||
"joi": "17.6.2",
|
||||
"joi": "17.6.4",
|
||||
"joi-extension-semver": "5.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonpath": "~1.1.1",
|
||||
@@ -62,7 +62,7 @@
|
||||
"qs": "^6.11.0",
|
||||
"query-string": "^7.1.1",
|
||||
"semver": "~7.3.8",
|
||||
"simple-icons": "7.14.0",
|
||||
"simple-icons": "7.17.0",
|
||||
"webextension-store-meta": "^1.0.5",
|
||||
"xmldom": "~0.6.0",
|
||||
"xpath": "~0.0.32"
|
||||
@@ -142,7 +142,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19.3",
|
||||
"@babel/core": "^7.19.6",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"@babel/register": "7.18.9",
|
||||
"@istanbuljs/schema": "^0.1.3",
|
||||
@@ -150,13 +150,13 @@
|
||||
"@types/chai": "^4.3.3",
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"@types/lodash.groupby": "^4.6.7",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"@types/mocha": "^10.0.0",
|
||||
"@types/node": "^16.7.10",
|
||||
"@types/react-helmet": "^6.1.5",
|
||||
"@types/react-modal": "^3.13.1",
|
||||
"@types/react-select": "^4.0.17",
|
||||
"@types/styled-components": "5.1.26",
|
||||
"@typescript-eslint/eslint-plugin": "^5.39.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.41.0",
|
||||
"@typescript-eslint/parser": "^5.30.7",
|
||||
"babel-plugin-inline-react-svg": "^2.0.1",
|
||||
"babel-preset-gatsby": "^2.22.0",
|
||||
@@ -168,9 +168,9 @@
|
||||
"chai-string": "^1.4.0",
|
||||
"child-process-promise": "^2.2.1",
|
||||
"clipboard-copy": "^4.0.1",
|
||||
"concurrently": "^7.4.0",
|
||||
"cypress": "^10.9.0",
|
||||
"cypress-wait-for-stable-dom": "^0.0.4",
|
||||
"concurrently": "^7.5.0",
|
||||
"cypress": "^10.11.0",
|
||||
"cypress-wait-for-stable-dom": "^0.1.0",
|
||||
"danger": "^11.1.4",
|
||||
"danger-plugin-no-test-shortcuts": "^2.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
@@ -182,12 +182,12 @@
|
||||
"eslint-plugin-chai-friendly": "^0.7.2",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsdoc": "^39.3.6",
|
||||
"eslint-plugin-jsdoc": "^39.3.25",
|
||||
"eslint-plugin-mocha": "^10.1.0",
|
||||
"eslint-plugin-no-extension-in-require": "^0.2.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.2.0",
|
||||
"eslint-plugin-react": "^7.31.8",
|
||||
"eslint-plugin-react": "^7.31.10",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-sort-class-members": "^1.15.2",
|
||||
"fetch-ponyfill": "^7.1.0",
|
||||
@@ -208,10 +208,10 @@
|
||||
"lint-staged": "^13.0.3",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.difference": "^4.5.0",
|
||||
"minimist": "^1.2.6",
|
||||
"mocha": "^9.2.2",
|
||||
"minimist": "^1.2.7",
|
||||
"mocha": "^10.1.0",
|
||||
"mocha-env-reporter": "^4.0.0",
|
||||
"mocha-junit-reporter": "^2.1.0",
|
||||
"mocha-junit-reporter": "^2.1.1",
|
||||
"mocha-yaml-loader": "^1.0.3",
|
||||
"nock": "13.2.9",
|
||||
"node-mocks-http": "^1.11.0",
|
||||
@@ -224,14 +224,14 @@
|
||||
"react-dom": "^17.0.2",
|
||||
"react-error-overlay": "^6.0.11",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-modal": "^3.15.1",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-pose": "^4.0.10",
|
||||
"react-select": "^4.3.1",
|
||||
"read-all-stdin-sync": "^1.0.5",
|
||||
"redis-server": "^1.2.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"sazerac": "^2.0.0",
|
||||
"simple-git-hooks": "^2.8.0",
|
||||
"simple-git-hooks": "^2.8.1",
|
||||
"sinon": "^14.0.1",
|
||||
"sinon-chai": "^3.7.0",
|
||||
"snap-shot-it": "^7.9.6",
|
||||
|
||||
46
services/ansible/ansible-collection.service.js
Normal file
46
services/ansible/ansible-collection.service.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import Joi from 'joi'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
|
||||
const ansibleCollectionSchema = Joi.object({
|
||||
name: Joi.string().required(),
|
||||
namespace: Joi.object({
|
||||
name: Joi.string().required(),
|
||||
}),
|
||||
}).required()
|
||||
|
||||
class AnsibleGalaxyCollectionName extends BaseJsonService {
|
||||
static category = 'other'
|
||||
static route = { base: 'ansible/collection', pattern: ':collectionId' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Ansible Collection',
|
||||
namedParams: { collectionId: '278' },
|
||||
staticPreview: this.render({
|
||||
name: 'community.general',
|
||||
}),
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'collection' }
|
||||
|
||||
static render({ name }) {
|
||||
return { message: name, color: 'blue' }
|
||||
}
|
||||
|
||||
async fetch({ collectionId }) {
|
||||
const url = `https://galaxy.ansible.com/api/v2/collections/${collectionId}/`
|
||||
return this._requestJson({
|
||||
url,
|
||||
schema: ansibleCollectionSchema,
|
||||
})
|
||||
}
|
||||
|
||||
async handle({ collectionId }) {
|
||||
const json = await this.fetch({ collectionId })
|
||||
const name = `${json.namespace.name}.${json.name}`
|
||||
return this.constructor.render({ name })
|
||||
}
|
||||
}
|
||||
|
||||
export { AnsibleGalaxyCollectionName }
|
||||
14
services/ansible/ansible-collection.tester.js
Normal file
14
services/ansible/ansible-collection.tester.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { ServiceTester } from '../tester.js'
|
||||
export const t = new ServiceTester({
|
||||
id: 'AnsibleCollection',
|
||||
title: 'AnsibleCollection',
|
||||
pathPrefix: '/ansible/collection',
|
||||
})
|
||||
|
||||
t.create('collection name (valid)')
|
||||
.get('/278.json')
|
||||
.expectBadge({ label: 'collection', message: 'community.general' })
|
||||
|
||||
t.create('collection name (not found)')
|
||||
.get('/000.json')
|
||||
.expectBadge({ label: 'collection', message: 'not found' })
|
||||
@@ -20,7 +20,7 @@ t.create('pr-raw (not found)')
|
||||
|
||||
t.create('pr-raw (private repo)')
|
||||
.get('/pr-raw/chris48s/example-private-repo.json')
|
||||
.expectBadge({ label: 'pull requests', message: 'private repo' })
|
||||
.expectBadge({ label: 'pull requests', message: 'not found' })
|
||||
|
||||
t.create('pr (valid)').get('/pr/atlassian/python-bitbucket.json').expectBadge({
|
||||
label: 'pull requests',
|
||||
@@ -33,7 +33,7 @@ t.create('pr (not found)')
|
||||
|
||||
t.create('pr (private repo)')
|
||||
.get('/pr/chris48s/example-private-repo.json')
|
||||
.expectBadge({ label: 'pull requests', message: 'private repo' })
|
||||
.expectBadge({ label: 'pull requests', message: 'not found' })
|
||||
|
||||
t.create('pr (server)')
|
||||
.get('/pr/project/repo.json?server=https://bitbucket.mydomain.net')
|
||||
|
||||
@@ -107,7 +107,7 @@ export default class Codecov extends BaseSvgScrapingService {
|
||||
async legacyFetch({ vcsName, user, repo, branch, token }) {
|
||||
// Codecov Docs: https://docs.codecov.io/reference#section-get-a-single-repository
|
||||
const url = `https://codecov.io/api/${vcsName}/${user}/${repo}${
|
||||
branch ? `/branches/${branch}` : ''
|
||||
branch ? `/branch/${branch}` : ''
|
||||
}`
|
||||
const { buffer } = await this._request({
|
||||
url,
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import Joi from 'joi'
|
||||
import { createServiceTester } from '../tester.js'
|
||||
import { isOrdinalNumber, isOrdinalNumberDaily } from '../test-validators.js'
|
||||
export const t = await createServiceTester()
|
||||
|
||||
const isOrdinalNumber = Joi.string().regex(/^[1-9][0-9]+(ᵗʰ|ˢᵗ|ⁿᵈ|ʳᵈ)$/)
|
||||
const isOrdinalNumberDaily = Joi.string().regex(
|
||||
/^[1-9][0-9]*(ᵗʰ|ˢᵗ|ⁿᵈ|ʳᵈ) daily$/
|
||||
)
|
||||
|
||||
t.create('total rank (valid)').get('/rt/rspec-puppet-facts.json').expectBadge({
|
||||
label: 'rank',
|
||||
message: isOrdinalNumber,
|
||||
|
||||
@@ -7,7 +7,7 @@ import { documentation, transformErrors } from './github-helpers.js'
|
||||
const greenStates = ['SUCCESS']
|
||||
const redStates = ['ERROR', 'FAILURE']
|
||||
const blueStates = ['INACTIVE']
|
||||
const otherStates = ['IN_PROGRESS', 'QUEUED', 'PENDING', 'NO_STATUS']
|
||||
const otherStates = ['IN_PROGRESS', 'QUEUED', 'PENDING', 'NO_STATUS', 'WAITING']
|
||||
|
||||
const stateToMessageMappings = {
|
||||
IN_PROGRESS: 'in progress',
|
||||
|
||||
@@ -21,6 +21,12 @@ describe('GithubDeployments', function () {
|
||||
message: 'in progress',
|
||||
color: undefined,
|
||||
})
|
||||
given({
|
||||
state: 'WAITING',
|
||||
}).expect({
|
||||
message: 'waiting',
|
||||
color: undefined,
|
||||
})
|
||||
given({
|
||||
state: 'NO_STATUS',
|
||||
}).expect({
|
||||
|
||||
@@ -15,14 +15,29 @@ const schema = Joi.array()
|
||||
author: Joi.object({
|
||||
date: Joi.string().required(),
|
||||
}).required(),
|
||||
committer: Joi.object({
|
||||
date: Joi.string().required(),
|
||||
}).required(),
|
||||
}).required(),
|
||||
}).required()
|
||||
)
|
||||
.required()
|
||||
.min(1)
|
||||
|
||||
const queryParamSchema = Joi.object({
|
||||
display_timestamp: Joi.string()
|
||||
.valid('author', 'committer')
|
||||
.default('author'),
|
||||
}).required()
|
||||
|
||||
export default class GithubLastCommit extends GithubAuthV3Service {
|
||||
static category = 'activity'
|
||||
static route = { base: 'github/last-commit', pattern: ':user/:repo/:branch*' }
|
||||
static route = {
|
||||
base: 'github/last-commit',
|
||||
pattern: ':user/:repo/:branch*',
|
||||
queryParamSchema,
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'GitHub last commit',
|
||||
@@ -45,6 +60,17 @@ export default class GithubLastCommit extends GithubAuthV3Service {
|
||||
staticPreview: this.render({ commitDate: '2013-07-31T20:01:41Z' }),
|
||||
...commonExampleAttrs,
|
||||
},
|
||||
{
|
||||
title: 'GitHub last commit (by committer)',
|
||||
pattern: ':user/:repo',
|
||||
namedParams: {
|
||||
user: 'google',
|
||||
repo: 'skia',
|
||||
},
|
||||
queryParams: { display_timestamp: 'committer' },
|
||||
staticPreview: this.render({ commitDate: '2022-10-15T20:01:41Z' }),
|
||||
...commonExampleAttrs,
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'last commit' }
|
||||
@@ -65,8 +91,11 @@ export default class GithubLastCommit extends GithubAuthV3Service {
|
||||
})
|
||||
}
|
||||
|
||||
async handle({ user, repo, branch }) {
|
||||
async handle({ user, repo, branch }, queryParams) {
|
||||
const body = await this.fetch({ user, repo, branch })
|
||||
return this.constructor.render({ commitDate: body[0].commit.author.date })
|
||||
|
||||
return this.constructor.render({
|
||||
commitDate: body[0].commit[queryParams.display_timestamp].date,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ t.create('last commit (on branch)')
|
||||
.get('/badges/badgr.co/shielded.json')
|
||||
.expectBadge({ label: 'last commit', message: 'july 2013' })
|
||||
|
||||
t.create('last commit (by committer)')
|
||||
.get('/badges/badgr.co/shielded.json?display_timestamp=committer')
|
||||
.expectBadge({ label: 'last commit', message: 'july 2013' })
|
||||
|
||||
t.create('last commit (repo not found)')
|
||||
.get('/badges/helmets.json')
|
||||
.expectBadge({ label: 'last commit', message: 'repo not found' })
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createServiceTester } from '../tester.js'
|
||||
export const t = await createServiceTester()
|
||||
|
||||
t.create('github pull request check state')
|
||||
.get('/s/pulls/badges/shields/1110.json')
|
||||
.get('/s/pulls/badges/shields/8486.json')
|
||||
.expectBadge({ label: 'checks', message: 'failure' })
|
||||
|
||||
t.create('github pull request check state (pull request not found)')
|
||||
@@ -10,7 +10,7 @@ t.create('github pull request check state (pull request not found)')
|
||||
.expectBadge({ label: 'checks', message: 'pull request or repo not found' })
|
||||
|
||||
t.create(
|
||||
"github pull request check state (ref returned by github doesn't exist"
|
||||
"github pull request check state (ref returned by github doesn't exist)"
|
||||
)
|
||||
.get('/s/pulls/badges/shields/1110.json')
|
||||
.intercept(
|
||||
@@ -26,5 +26,5 @@ t.create(
|
||||
})
|
||||
|
||||
t.create('github pull request check contexts')
|
||||
.get('/contexts/pulls/badges/shields/1110.json')
|
||||
.expectBadge({ label: 'checks', message: '1 failure' })
|
||||
.get('/contexts/pulls/badges/shields/8486.json')
|
||||
.expectBadge({ label: 'checks', message: '2 success, 4 failure' })
|
||||
|
||||
@@ -8,21 +8,30 @@ import { documentation, errorMessagesFor } from './github-helpers.js'
|
||||
const schema = Joi.alternatives(
|
||||
Joi.object({
|
||||
created_at: Joi.date().required(),
|
||||
published_at: Joi.date().required(),
|
||||
}).required(),
|
||||
Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
created_at: Joi.date().required(),
|
||||
published_at: Joi.date().required(),
|
||||
}).required()
|
||||
)
|
||||
.min(1)
|
||||
)
|
||||
|
||||
const queryParamSchema = Joi.object({
|
||||
display_date: Joi.string()
|
||||
.valid('created_at', 'published_at')
|
||||
.default('created_at'),
|
||||
}).required()
|
||||
|
||||
export default class GithubReleaseDate extends GithubAuthV3Service {
|
||||
static category = 'activity'
|
||||
static route = {
|
||||
base: 'github',
|
||||
pattern: ':variant(release-date|release-date-pre)/:user/:repo',
|
||||
queryParamSchema,
|
||||
}
|
||||
|
||||
static examples = [
|
||||
@@ -46,6 +55,17 @@ export default class GithubReleaseDate extends GithubAuthV3Service {
|
||||
staticPreview: this.render({ date: '2017-04-13T07:50:27.000Z' }),
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'GitHub Release Date - Published_At',
|
||||
pattern: 'release-date/:user/:repo',
|
||||
namedParams: {
|
||||
user: 'microsoft',
|
||||
repo: 'vscode',
|
||||
},
|
||||
queryParams: { display_date: 'published_at' },
|
||||
staticPreview: this.render({ date: '2022-10-17T07:50:27.000Z' }),
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'release date' }
|
||||
@@ -70,11 +90,13 @@ export default class GithubReleaseDate extends GithubAuthV3Service {
|
||||
})
|
||||
}
|
||||
|
||||
async handle({ variant, user, repo }) {
|
||||
async handle({ variant, user, repo }, queryParams) {
|
||||
const body = await this.fetch({ variant, user, repo })
|
||||
if (Array.isArray(body)) {
|
||||
return this.constructor.render({ date: body[0].created_at })
|
||||
return this.constructor.render({
|
||||
date: body[0][queryParams.display_date],
|
||||
})
|
||||
}
|
||||
return this.constructor.render({ date: body.created_at })
|
||||
return this.constructor.render({ date: body[queryParams.display_date] })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,27 @@ t.create('Release Date. e.g release date|today')
|
||||
message: isFormattedDate,
|
||||
})
|
||||
|
||||
t.create('Release Date - display_date by `created_at` (default)')
|
||||
.get('/release-date/microsoft/vscode.json?display_date=created_at')
|
||||
.expectBadge({
|
||||
label: 'release date',
|
||||
message: isFormattedDate,
|
||||
})
|
||||
|
||||
t.create('Release Date - display_date by `published_at`')
|
||||
.get('/release-date/microsoft/vscode.json?display_date=published_at')
|
||||
.expectBadge({
|
||||
label: 'release date',
|
||||
message: isFormattedDate,
|
||||
})
|
||||
|
||||
t.create('Release Date - display_date by `published_at`, incorrect query param')
|
||||
.get('/release-date/microsoft/vscode.json?display_date=published_attttttttt')
|
||||
.expectBadge({
|
||||
label: 'release date',
|
||||
message: 'invalid query parameter: display_date',
|
||||
})
|
||||
|
||||
t.create(
|
||||
'Release Date - Should return `no releases or repo not found` for invalid repo'
|
||||
)
|
||||
|
||||
79
services/gitlab/gitlab-last-commit.service.js
Normal file
79
services/gitlab/gitlab-last-commit.service.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import Joi from 'joi'
|
||||
import { optionalUrl } from '../validators.js'
|
||||
import { formatDate } from '../text-formatters.js'
|
||||
import { age as ageColor } from '../color-formatters.js'
|
||||
import { documentation, errorMessagesFor } from './gitlab-helper.js'
|
||||
import GitLabBase from './gitlab-base.js'
|
||||
|
||||
const schema = Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
committed_date: Joi.string().required(),
|
||||
}).required()
|
||||
)
|
||||
.required()
|
||||
.min(1)
|
||||
|
||||
const queryParamSchema = Joi.object({
|
||||
ref: Joi.string(),
|
||||
gitlab_url: optionalUrl,
|
||||
}).required()
|
||||
|
||||
const refText = `
|
||||
<p>
|
||||
ref can be filled with the name of a branch, tag or revision range of the repository.
|
||||
</p>
|
||||
`
|
||||
|
||||
const defaultDocumentation = documentation + refText
|
||||
|
||||
export default class GitlabLastCommit extends GitLabBase {
|
||||
static category = 'activity'
|
||||
|
||||
static route = {
|
||||
base: 'gitlab/last-commit',
|
||||
pattern: ':project+',
|
||||
queryParamSchema,
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'GitLab last commit',
|
||||
namedParams: {
|
||||
project: 'gitlab-org/gitlab',
|
||||
},
|
||||
queryParams: { gitlab_url: 'https://gitlab.com' },
|
||||
staticPreview: this.render({ commitDate: '2013-07-31T20:01:41Z' }),
|
||||
documentation: defaultDocumentation,
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'last commit' }
|
||||
|
||||
static render({ commitDate }) {
|
||||
return {
|
||||
message: formatDate(commitDate),
|
||||
color: ageColor(Date.parse(commitDate)),
|
||||
}
|
||||
}
|
||||
|
||||
async fetch({ project, baseUrl, ref }) {
|
||||
// https://docs.gitlab.com/ee/api/commits.html#list-repository-commits
|
||||
return super.fetch({
|
||||
url: `${baseUrl}/api/v4/projects/${encodeURIComponent(
|
||||
project
|
||||
)}/repository/commits`,
|
||||
options: { searchParams: { ref_name: ref } },
|
||||
schema,
|
||||
errorMessages: errorMessagesFor('project not found'),
|
||||
})
|
||||
}
|
||||
|
||||
async handle(
|
||||
{ project },
|
||||
{ gitlab_url: baseUrl = 'https://gitlab.com', ref }
|
||||
) {
|
||||
const data = await this.fetch({ project, baseUrl, ref })
|
||||
return this.constructor.render({ commitDate: data[0].committed_date })
|
||||
}
|
||||
}
|
||||
30
services/gitlab/gitlab-last-commit.tester.js
Normal file
30
services/gitlab/gitlab-last-commit.tester.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { isFormattedDate } from '../test-validators.js'
|
||||
import { createServiceTester } from '../tester.js'
|
||||
|
||||
export const t = await createServiceTester()
|
||||
|
||||
t.create('last commit (recent)').get('/gitlab-org/gitlab.json').expectBadge({
|
||||
label: 'last commit',
|
||||
message: isFormattedDate,
|
||||
})
|
||||
|
||||
t.create('last commit (on ref and ancient)')
|
||||
.get('/gitlab-org/gitlab.json?ref=v13.8.6-ee')
|
||||
.expectBadge({
|
||||
label: 'last commit',
|
||||
message: 'march 2021',
|
||||
})
|
||||
|
||||
t.create('last commit (self-managed)')
|
||||
.get('/gitlab-cn/gitlab.json?gitlab_url=https://jihulab.com')
|
||||
.expectBadge({
|
||||
label: 'last commit',
|
||||
message: isFormattedDate,
|
||||
})
|
||||
|
||||
t.create('last commit (project not found)')
|
||||
.get('/open/guoxudong.io/shields-test/do-not-exist.json')
|
||||
.expectBadge({
|
||||
label: 'last commit',
|
||||
message: 'project not found',
|
||||
})
|
||||
@@ -23,9 +23,9 @@ t.create('version installs | valid: numeric version')
|
||||
})
|
||||
|
||||
t.create('version installs | valid: alphanumeric version')
|
||||
.get('/build-failure-analyzer/1.17.2-DRE3.14.json')
|
||||
.get('/build-failure-analyzer/1.17.2-DRE3.21.json')
|
||||
.expectBadge({
|
||||
label: 'installs@1.17.2-DRE3.14',
|
||||
label: 'installs@1.17.2-DRE3.21',
|
||||
message: isMetric,
|
||||
})
|
||||
|
||||
|
||||
@@ -59,10 +59,3 @@ t.create('alerts: total alerts for a project with a github mapped host')
|
||||
label: 'lgtm alerts',
|
||||
message: Joi.string().regex(/^[0-9kM.]+$/),
|
||||
})
|
||||
|
||||
t.create('alerts: total alerts for a project with a bitbucket mapped host')
|
||||
.get('/bitbucket/atlassian/confluence-business-blueprints.json')
|
||||
.expectBadge({
|
||||
label: 'lgtm alerts',
|
||||
message: Joi.string().regex(/^[0-9kM.]+$/),
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ t.create('total downloads (valid)')
|
||||
})
|
||||
|
||||
t.create('total downloads (tenant)')
|
||||
.get('/cefsharp.myget/cefsharp/dt/CefSharp.Common.json')
|
||||
.get('/vs-devcore.myget/vs-devcore/dt/MicroBuild.json')
|
||||
.expectBadge({
|
||||
label: 'downloads',
|
||||
message: isMetric,
|
||||
|
||||
@@ -1,83 +1,11 @@
|
||||
import Joi from 'joi'
|
||||
import { starRating, metric } from '../text-formatters.js'
|
||||
import { colorScale } from '../color-formatters.js'
|
||||
import { nonNegativeInteger } from '../validators.js'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
import { deprecatedService } from '../index.js'
|
||||
|
||||
const pkgReviewColor = colorScale([2, 3, 4])
|
||||
|
||||
const schema = Joi.object({
|
||||
rating: Joi.number().min(0).max(1).precision(1).required().allow(null),
|
||||
reviewsCount: nonNegativeInteger,
|
||||
}).required()
|
||||
|
||||
// Repository for this service is: https://github.com/iqubex-technologies/pkgreview.dev
|
||||
// Internally the service leverages the npms.io API (https://api.npms.io/v2)
|
||||
export default class PkgreviewRating extends BaseJsonService {
|
||||
static category = 'rating'
|
||||
|
||||
static route = {
|
||||
export default deprecatedService({
|
||||
category: 'rating',
|
||||
route: {
|
||||
base: 'pkgreview',
|
||||
pattern: ':format(rating|stars)/:pkgManager(npm)/:pkgSlug+',
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'pkgreview.dev Package Ratings',
|
||||
pattern: 'rating/:pkgManager/:pkgSlug+',
|
||||
namedParams: { pkgManager: 'npm', pkgSlug: 'react' },
|
||||
staticPreview: this.render({
|
||||
format: 'rating',
|
||||
rating: 3.5,
|
||||
reviewsCount: 237,
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: 'pkgreview.dev Star Ratings',
|
||||
pattern: 'stars/:pkgManager/:pkgSlug+',
|
||||
namedParams: { pkgManager: 'npm', pkgSlug: 'react' },
|
||||
staticPreview: this.render({
|
||||
format: 'stars',
|
||||
rating: 1.5,
|
||||
reviewsCount: 200,
|
||||
}),
|
||||
},
|
||||
]
|
||||
|
||||
static render({ rating, reviewsCount, format }) {
|
||||
const message =
|
||||
format === 'rating'
|
||||
? `${+parseFloat(rating).toFixed(1)}/5 (${metric(reviewsCount)})`
|
||||
: starRating(rating)
|
||||
|
||||
return {
|
||||
message,
|
||||
label: format,
|
||||
color: pkgReviewColor(rating),
|
||||
}
|
||||
}
|
||||
|
||||
async fetch({ pkgManager, pkgSlug }) {
|
||||
return this._requestJson({
|
||||
schema,
|
||||
url: `https://pkgreview.now.sh/api/v1/${pkgManager}/${encodeURIComponent(
|
||||
pkgSlug
|
||||
)}`,
|
||||
errorMessages: {
|
||||
404: 'package not found',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async handle({ format, pkgManager, pkgSlug }) {
|
||||
const { reviewsCount, rating } = await this.fetch({
|
||||
pkgManager,
|
||||
pkgSlug,
|
||||
})
|
||||
return this.constructor.render({
|
||||
reviewsCount,
|
||||
format,
|
||||
rating: rating * 5,
|
||||
})
|
||||
}
|
||||
}
|
||||
pattern: ':various*',
|
||||
},
|
||||
label: 'pkgreview',
|
||||
dateAdded: new Date('2022-10-07'),
|
||||
})
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
import { withRegex, isStarRating } from '../test-validators.js'
|
||||
import { createServiceTester } from '../tester.js'
|
||||
export const t = await createServiceTester()
|
||||
import { ServiceTester } from '../tester.js'
|
||||
|
||||
const isRatingWithReviews = withRegex(
|
||||
/^(([0-4](.?([0-9]))?)|5)\/5?\s*\([0-9]*\)$/
|
||||
)
|
||||
export const t = new ServiceTester({
|
||||
id: 'pkgreview',
|
||||
title: 'PkgReview',
|
||||
pathPrefix: '/pkgreview',
|
||||
})
|
||||
|
||||
t.create('Stars Badge renders')
|
||||
t.create('Stars Badge')
|
||||
.get('/stars/npm/react.json')
|
||||
.expectBadge({ label: 'stars', message: isStarRating })
|
||||
.expectBadge({ label: 'pkgreview', message: 'no longer available' })
|
||||
|
||||
t.create('Rating Badge renders')
|
||||
t.create('Rating Badge')
|
||||
.get('/rating/npm/react.json')
|
||||
.expectBadge({ label: 'rating', message: isRatingWithReviews })
|
||||
|
||||
t.create('nonexistent package')
|
||||
.get('/rating/npm/ohlolweallknowthispackagewontexist.json')
|
||||
.expectBadge({
|
||||
label: 'rating',
|
||||
message: 'package not found',
|
||||
color: 'red',
|
||||
})
|
||||
.expectBadge({ label: 'pkgreview', message: 'no longer available' })
|
||||
|
||||
@@ -35,7 +35,7 @@ class PowershellGalleryPlatformSupport extends BaseXmlService {
|
||||
static examples = [
|
||||
{
|
||||
title: 'PowerShell Gallery',
|
||||
namedParams: { packageName: 'DNS.1.1.1.1' },
|
||||
namedParams: { packageName: 'PackageManagement' },
|
||||
staticPreview: this.render({
|
||||
platforms: ['windows', 'macos', 'linux'],
|
||||
}),
|
||||
|
||||
@@ -47,7 +47,7 @@ t.create('version (legacy redirect: vpre)')
|
||||
.get('/vpre/ACMESharp.svg')
|
||||
.expectRedirect('/powershellgallery/v/ACMESharp.svg?include_prereleases')
|
||||
|
||||
t.create('platform (valid').get('/p/DNS.1.1.1.1.json').expectBadge({
|
||||
t.create('platform (valid)').get('/p/PackageManagement.json').expectBadge({
|
||||
label: 'platform',
|
||||
message: isPlatform,
|
||||
})
|
||||
|
||||
@@ -62,7 +62,10 @@ t.create('valid repo -- unregistered')
|
||||
color: COLOR_MAP.unregistered,
|
||||
})
|
||||
|
||||
t.create('invalid repo').get('/github.com/repo/invalid-repo.json').expectBadge({
|
||||
label: 'reuse',
|
||||
message: 'Not a Git repository',
|
||||
})
|
||||
t.create('invalid repo')
|
||||
.timeout(15000)
|
||||
.get('/github.com/repo/invalid-repo.json')
|
||||
.expectBadge({
|
||||
label: 'reuse',
|
||||
message: 'Not a Git repository',
|
||||
})
|
||||
|
||||
@@ -8,7 +8,7 @@ const schema = Joi.object()
|
||||
Joi.object({
|
||||
level: Joi.string().required(),
|
||||
message: Joi.string().required(),
|
||||
}).required()
|
||||
})
|
||||
),
|
||||
})
|
||||
.required()
|
||||
|
||||
@@ -155,6 +155,15 @@ const isCustomCompactTestTotals = makeCompactTestTotalsValidator({
|
||||
skipped: '🤷',
|
||||
})
|
||||
|
||||
const isOrdinalNumber = Joi.string().regex(/^[1-9][0-9]*(ᵗʰ|ˢᵗ|ⁿᵈ|ʳᵈ)$/)
|
||||
const isOrdinalNumberDaily = Joi.string().regex(
|
||||
/^[1-9][0-9]*(ᵗʰ|ˢᵗ|ⁿᵈ|ʳᵈ) daily$/
|
||||
)
|
||||
|
||||
const isHumanized = Joi.string().regex(
|
||||
/[0-9a-z]+ (second|seconds|minute|minutes|hour|hours|day|days|month|months|year|years)/
|
||||
)
|
||||
|
||||
export {
|
||||
isSemver,
|
||||
isVPlusTripleDottedVersion,
|
||||
@@ -187,4 +196,7 @@ export {
|
||||
isCustomCompactTestTotals,
|
||||
makeTestTotalsValidator,
|
||||
makeCompactTestTotalsValidator,
|
||||
isOrdinalNumber,
|
||||
isOrdinalNumberDaily,
|
||||
isHumanized,
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import Joi from 'joi'
|
||||
import { nonNegativeInteger } from '../validators.js'
|
||||
import { BaseJsonService, NotFound } from '../index.js'
|
||||
|
||||
// https://help.testspace.com/docs/reference/web-api#list-results
|
||||
// case_counts|array|The contained cases [passed, failed, na, errored]|counters of result
|
||||
// https://help.testspace.com/reference/web-api#list-results
|
||||
// case_counts|array|The contained cases [passed, failed, na, errored, untested]|counters of result
|
||||
// session_* fields are for manual runs
|
||||
// There are instances where the api returns a 200 status code with an empty array
|
||||
// notably in cases where a space id is used
|
||||
@@ -12,14 +12,14 @@ const schema = Joi.array()
|
||||
Joi.object({
|
||||
case_counts: Joi.array()
|
||||
.items(nonNegativeInteger)
|
||||
.min(4)
|
||||
.max(4)
|
||||
.min(5)
|
||||
.max(5)
|
||||
.required(),
|
||||
})
|
||||
)
|
||||
.required()
|
||||
|
||||
// https://help.testspace.com/docs/dashboard/overview-navigate
|
||||
// https://help.testspace.com/dashboard/overview#navigate
|
||||
// Org is owner/account
|
||||
// Project is generally a repository
|
||||
// Space is a container, often a branch
|
||||
@@ -49,11 +49,11 @@ export default class TestspaceBase extends BaseJsonService {
|
||||
|
||||
const [
|
||||
{
|
||||
case_counts: [passed, failed, skipped, errored],
|
||||
case_counts: [passed, failed, skipped, errored, untested],
|
||||
},
|
||||
] = json
|
||||
const total = passed + failed + skipped + errored
|
||||
const total = passed + failed + skipped + errored + untested
|
||||
|
||||
return { passed, failed, skipped, errored, total }
|
||||
return { passed, failed, skipped, errored, untested, total }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ export default class TestspaceTestCount extends TestspaceBase {
|
||||
static route = {
|
||||
base: 'testspace',
|
||||
pattern:
|
||||
':metric(total|passed|failed|skipped|errored)/:org/:project/:space+',
|
||||
':metric(total|passed|failed|skipped|errored|untested)/:org/:project/:space+',
|
||||
}
|
||||
|
||||
static examples = [
|
||||
@@ -39,7 +39,7 @@ export default class TestspaceTestCount extends TestspaceBase {
|
||||
}
|
||||
|
||||
transform({ json, metric }) {
|
||||
const { passed, failed, skipped, errored, total } =
|
||||
const { passed, failed, skipped, errored, untested, total } =
|
||||
this.transformCaseCounts(json)
|
||||
if (metric === 'total') {
|
||||
return { value: total }
|
||||
@@ -49,6 +49,8 @@ export default class TestspaceTestCount extends TestspaceBase {
|
||||
return { value: failed }
|
||||
} else if (metric === 'skipped') {
|
||||
return { value: skipped }
|
||||
} else if (metric === 'untested') {
|
||||
return { value: untested }
|
||||
} else {
|
||||
return { value: errored }
|
||||
}
|
||||
|
||||
@@ -38,5 +38,10 @@ describe('TestspaceTestCount', function () {
|
||||
message: '0',
|
||||
color: 'informational',
|
||||
})
|
||||
given({ metric: 'untested', value: 0 }).expect({
|
||||
label: 'untested tests',
|
||||
message: '0',
|
||||
color: 'informational',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -41,3 +41,10 @@ t.create('Errored')
|
||||
label: 'errored tests',
|
||||
message: isMetricAllowZero,
|
||||
})
|
||||
|
||||
t.create('Untested')
|
||||
.get('/untested/swellaby/swellaby:testspace-sample/main.json')
|
||||
.expectBadge({
|
||||
label: 'untested tests',
|
||||
message: isMetricAllowZero,
|
||||
})
|
||||
|
||||
@@ -21,6 +21,12 @@ const extensionQuerySchema = Joi.object({
|
||||
.items(
|
||||
Joi.object({
|
||||
version: Joi.string().required(),
|
||||
properties: Joi.array().items(
|
||||
Joi.object({
|
||||
key: Joi.string().required(),
|
||||
value: Joi.any().required(),
|
||||
})
|
||||
),
|
||||
})
|
||||
)
|
||||
.min(1)
|
||||
@@ -67,7 +73,12 @@ export default class VisualStudioMarketplaceBase extends BaseJsonService {
|
||||
criteria: [{ filterType: 7, value: extensionId }],
|
||||
},
|
||||
],
|
||||
flags: 914,
|
||||
// Microsoft does not provide a clear API doc. It seems that the flag value is calculated
|
||||
// as the combined hex values of the requested flags, converted to base 10.
|
||||
// This was found using the vscode repo at:
|
||||
// https://github.com/microsoft/vscode/blob/main/src/vs/platform/extensionManagement/common/extensionGalleryService.ts
|
||||
// This flag value is 0x192.
|
||||
flags: 402,
|
||||
}
|
||||
const options = {
|
||||
method: 'POST',
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import Joi from 'joi'
|
||||
import { renderVersionBadge } from '../version.js'
|
||||
import VisualStudioMarketplaceBase from './visual-studio-marketplace-base.js'
|
||||
|
||||
const queryParamSchema = Joi.object({
|
||||
include_prereleases: Joi.equal(''),
|
||||
}).required()
|
||||
|
||||
export default class VisualStudioMarketplaceVersion extends VisualStudioMarketplaceBase {
|
||||
static category = 'version'
|
||||
|
||||
static route = {
|
||||
base: '',
|
||||
pattern: '(visual-studio-marketplace|vscode-marketplace)/v/:extensionId',
|
||||
queryParamSchema,
|
||||
}
|
||||
|
||||
static examples = [
|
||||
@@ -17,6 +23,14 @@ export default class VisualStudioMarketplaceVersion extends VisualStudioMarketpl
|
||||
staticPreview: this.render({ version: '0.2.7' }),
|
||||
keywords: this.keywords,
|
||||
},
|
||||
{
|
||||
title: 'Visual Studio Marketplace Version (including pre-releases)',
|
||||
pattern: 'visual-studio-marketplace/v/:extensionId',
|
||||
namedParams: { extensionId: 'swellaby.rust-pack' },
|
||||
queryParams: { include_prereleases: null },
|
||||
staticPreview: this.render({ version: '0.2.9-dev' }),
|
||||
keywords: this.keywords,
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = {
|
||||
@@ -27,15 +41,33 @@ export default class VisualStudioMarketplaceVersion extends VisualStudioMarketpl
|
||||
return renderVersionBadge({ version })
|
||||
}
|
||||
|
||||
transform({ json }) {
|
||||
transform({ json }, includePrereleases) {
|
||||
const { extension } = this.transformExtension({ json })
|
||||
const version = extension.versions[0].version
|
||||
const preReleaseKey = 'Microsoft.VisualStudio.Code.PreRelease'
|
||||
let version
|
||||
|
||||
if (!includePrereleases) {
|
||||
version = extension.versions.find(
|
||||
obj =>
|
||||
!obj.properties.find(
|
||||
({ key, value }) => key === preReleaseKey && value === 'true'
|
||||
)
|
||||
)?.version
|
||||
}
|
||||
|
||||
// this condition acts as the 'else' clause AND as a fallback,
|
||||
// in case all versions are pre-release
|
||||
if (!version) {
|
||||
version = extension.versions[0].version
|
||||
}
|
||||
|
||||
return { version }
|
||||
}
|
||||
|
||||
async handle({ extensionId }) {
|
||||
async handle({ extensionId }, queryParams) {
|
||||
const json = await this.fetch({ extensionId })
|
||||
const { version } = this.transform({ json })
|
||||
const includePrereleases = queryParams.include_prereleases !== undefined
|
||||
const { version } = this.transform({ json }, includePrereleases)
|
||||
|
||||
return this.constructor.render({ version })
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ export const t = await createServiceTester()
|
||||
const isMarketplaceVersion = withRegex(/^v(\d+\.\d+\.\d+)(\.\d+)?$/)
|
||||
|
||||
t.create('rating')
|
||||
.get('/visual-studio-marketplace/v/ritwickdey.LiveServer.json')
|
||||
.get('/visual-studio-marketplace/v/lextudio.restructuredtext.json')
|
||||
.expectBadge({
|
||||
label: 'version',
|
||||
message: isMarketplaceVersion,
|
||||
})
|
||||
|
||||
t.create('version')
|
||||
.get('/visual-studio-marketplace/v/ritwickdey.LiveServer.json')
|
||||
.get('/visual-studio-marketplace/v/lextudio.restructuredtext.json')
|
||||
.intercept(nock =>
|
||||
nock('https://marketplace.visualstudio.com/_apis/public/gallery/')
|
||||
.post('/extensionquery/')
|
||||
@@ -23,8 +23,27 @@ t.create('version')
|
||||
{
|
||||
statistics: [],
|
||||
versions: [
|
||||
{
|
||||
version: '1.3.8-alpha',
|
||||
properties: [
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Services.Branding.Theme',
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Code.PreRelease',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '1.0.0',
|
||||
properties: [
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Services.Branding.Theme',
|
||||
value: 'light',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
releaseDate: '2019-04-13T07:50:27.000Z',
|
||||
@@ -41,8 +60,10 @@ t.create('version')
|
||||
color: 'blue',
|
||||
})
|
||||
|
||||
t.create('pre-release version')
|
||||
.get('/visual-studio-marketplace/v/swellaby.vscode-rust-test-adapter.json')
|
||||
t.create(
|
||||
'version - includePrereleases flag is false and response has pre-release only'
|
||||
)
|
||||
.get('/visual-studio-marketplace/v/lextudio.restructuredtext.json')
|
||||
.intercept(nock =>
|
||||
nock('https://marketplace.visualstudio.com/_apis/public/gallery/')
|
||||
.post('/extensionquery/')
|
||||
@@ -54,7 +75,43 @@ t.create('pre-release version')
|
||||
statistics: [],
|
||||
versions: [
|
||||
{
|
||||
version: '0.3.8',
|
||||
version: '1.3.8',
|
||||
properties: [
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Services.Branding.Theme',
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Code.PreRelease',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '1.3.7',
|
||||
properties: [
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Services.Branding.Theme',
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Code.PreRelease',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '1.3.6',
|
||||
properties: [
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Services.Branding.Theme',
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Code.PreRelease',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
releaseDate: '2019-04-13T07:50:27.000Z',
|
||||
@@ -67,7 +124,124 @@ t.create('pre-release version')
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'version',
|
||||
message: 'v0.3.8',
|
||||
message: 'v1.3.8',
|
||||
color: 'blue',
|
||||
})
|
||||
|
||||
t.create('version - prerelease key has false value')
|
||||
.get('/visual-studio-marketplace/v/lextudio.restructuredtext.json')
|
||||
.intercept(nock =>
|
||||
nock('https://marketplace.visualstudio.com/_apis/public/gallery/')
|
||||
.post('/extensionquery/')
|
||||
.reply(200, {
|
||||
results: [
|
||||
{
|
||||
extensions: [
|
||||
{
|
||||
statistics: [],
|
||||
versions: [
|
||||
{
|
||||
version: '1.3.8',
|
||||
properties: [
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Services.Branding.Theme',
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Code.PreRelease',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '1.3.7',
|
||||
properties: [
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Services.Branding.Theme',
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Code.PreRelease',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '1.3.6',
|
||||
properties: [
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Services.Branding.Theme',
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Code.PreRelease',
|
||||
value: 'false',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
releaseDate: '2019-04-13T07:50:27.000Z',
|
||||
lastUpdated: '2019-04-13T07:50:27.000Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'version',
|
||||
message: 'v1.3.6',
|
||||
color: 'blue',
|
||||
})
|
||||
|
||||
t.create('pre-release version')
|
||||
.get(
|
||||
'/visual-studio-marketplace/v/swellaby.vscode-rust-test-adapter.json?include_prereleases'
|
||||
)
|
||||
.intercept(nock =>
|
||||
nock('https://marketplace.visualstudio.com/_apis/public/gallery/')
|
||||
.post('/extensionquery/')
|
||||
.reply(200, {
|
||||
results: [
|
||||
{
|
||||
extensions: [
|
||||
{
|
||||
statistics: [],
|
||||
versions: [
|
||||
{
|
||||
version: '1.3.8-alpha',
|
||||
properties: [
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Services.Branding.Theme',
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Code.PreRelease',
|
||||
value: 'true',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
version: '1.0.0',
|
||||
properties: [
|
||||
{
|
||||
key: 'Microsoft.VisualStudio.Services.Branding.Theme',
|
||||
value: 'light',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
releaseDate: '2019-04-13T07:50:27.000Z',
|
||||
lastUpdated: '2019-04-13T07:50:27.000Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
.expectBadge({
|
||||
label: 'version',
|
||||
message: 'v1.3.8-alpha',
|
||||
color: 'orange',
|
||||
})
|
||||
|
||||
|
||||
126
services/whatpulse/whatpulse.service.js
Normal file
126
services/whatpulse/whatpulse.service.js
Normal file
@@ -0,0 +1,126 @@
|
||||
import Joi from 'joi'
|
||||
import dayjs from 'dayjs'
|
||||
import calendar from 'dayjs/plugin/calendar.js'
|
||||
import duration from 'dayjs/plugin/duration.js'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime.js'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
import { metric as formatMetric, ordinalNumber } from '../text-formatters.js'
|
||||
dayjs.extend(calendar)
|
||||
dayjs.extend(duration)
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const schema = Joi.object({
|
||||
Keys: Joi.alternatives(Joi.string(), Joi.number()).required(),
|
||||
Clicks: Joi.alternatives(Joi.string(), Joi.number()).required(),
|
||||
UptimeSeconds: Joi.alternatives(Joi.string(), Joi.number()).required(),
|
||||
Download: Joi.string().required(),
|
||||
Upload: Joi.string().required(),
|
||||
Ranks: Joi.object({
|
||||
Keys: Joi.string().required(),
|
||||
Clicks: Joi.string().required(),
|
||||
Download: Joi.string().required(),
|
||||
Upload: Joi.string().required(),
|
||||
Uptime: Joi.string().required(),
|
||||
}),
|
||||
}).required()
|
||||
|
||||
const queryParamSchema = Joi.object({
|
||||
rank: Joi.equal(''),
|
||||
}).required()
|
||||
|
||||
export default class WhatPulse extends BaseJsonService {
|
||||
static category = 'activity'
|
||||
static route = {
|
||||
base: 'whatpulse',
|
||||
pattern:
|
||||
':metric(keys|clicks|uptime|download|upload)/:userType(user|team)/:id',
|
||||
queryParamSchema,
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'WhatPulse user metric',
|
||||
namedParams: { metric: 'keys', userType: 'user', id: '179734' },
|
||||
staticPreview: this.render({
|
||||
metric: 'keys',
|
||||
metricValue: '21G',
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: 'WhatPulse team metric - rank',
|
||||
namedParams: {
|
||||
metric: 'upload',
|
||||
userType: 'team',
|
||||
id: 'dutch power cows',
|
||||
},
|
||||
queryParams: { rank: null },
|
||||
staticPreview: this.render({
|
||||
metric: 'upload',
|
||||
metricValue: '1ˢᵗ',
|
||||
}),
|
||||
},
|
||||
]
|
||||
|
||||
static defaultBadgeData = { label: 'whatpulse' }
|
||||
|
||||
static render({ metric, metricValue }) {
|
||||
return {
|
||||
label: metric,
|
||||
message: metricValue,
|
||||
color: 'informational',
|
||||
}
|
||||
}
|
||||
|
||||
async fetch({ userType, id }) {
|
||||
return await this._requestJson({
|
||||
schema,
|
||||
url: `https://api.whatpulse.org/${userType}.php?${userType}=${id}&format=json`,
|
||||
})
|
||||
}
|
||||
|
||||
toLowerKeys(obj) {
|
||||
return Object.keys(obj).reduce((accumulator, key) => {
|
||||
accumulator[key.toLowerCase()] = obj[key]
|
||||
return accumulator
|
||||
}, {})
|
||||
}
|
||||
|
||||
transform({ json, metric }, { rank }) {
|
||||
// We want to compare with lowercase keys from the WhatPulse's API.
|
||||
const jsonLowercase = this.toLowerKeys(json)
|
||||
jsonLowercase.ranks = this.toLowerKeys(json.Ranks)
|
||||
|
||||
// Just metric, no rank.
|
||||
if (rank === undefined) {
|
||||
if (metric === 'uptime') {
|
||||
return dayjs.duration(jsonLowercase.uptimeseconds, 'seconds').humanize()
|
||||
}
|
||||
|
||||
let metricValue
|
||||
|
||||
metricValue = jsonLowercase[metric]
|
||||
|
||||
if (metric === 'keys' || metric === 'clicks') {
|
||||
metricValue = formatMetric(metricValue)
|
||||
}
|
||||
|
||||
if (metric === 'upload' || metric === 'download') {
|
||||
metricValue = metricValue.replace(/([A-Za-z]+)/, ' $1')
|
||||
}
|
||||
|
||||
return metricValue
|
||||
}
|
||||
|
||||
// Rank achieved by the user/team with the given metric.
|
||||
const rankFromResp = jsonLowercase.ranks[metric]
|
||||
|
||||
return ordinalNumber(rankFromResp)
|
||||
}
|
||||
|
||||
async handle({ metric, userType, id }, { rank }) {
|
||||
const json = await this.fetch({ userType, id, metric })
|
||||
const metricValue = this.transform({ json, metric }, { rank })
|
||||
|
||||
return this.constructor.render({ metric, metricValue })
|
||||
}
|
||||
}
|
||||
42
services/whatpulse/whatpulse.tester.js
Normal file
42
services/whatpulse/whatpulse.tester.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import { createServiceTester } from '../tester.js'
|
||||
import {
|
||||
isFileSize,
|
||||
isHumanized,
|
||||
isMetric,
|
||||
isOrdinalNumber,
|
||||
} from '../test-validators.js'
|
||||
export const t = await createServiceTester()
|
||||
|
||||
t.create('WhatPulse user as user id, uptime')
|
||||
.get('/uptime/user/179734.json')
|
||||
.expectBadge({ label: 'uptime', message: isHumanized })
|
||||
|
||||
t.create('WhatPulse user as user name, keys')
|
||||
.get('/keys/user/jerone.json')
|
||||
.expectBadge({ label: 'keys', message: isMetric })
|
||||
|
||||
t.create('WhatPulse team as team id, clicks')
|
||||
.get('/clicks/team/1295.json')
|
||||
.expectBadge({ label: 'clicks', message: isMetric })
|
||||
|
||||
t.create('WhatPulse team as team id, download')
|
||||
.get('/download/team/1295.json')
|
||||
.expectBadge({ label: 'download', message: isFileSize })
|
||||
|
||||
t.create('WhatPulse team as team id, upload')
|
||||
.get('/upload/team/1295.json')
|
||||
.expectBadge({ label: 'upload', message: isFileSize })
|
||||
|
||||
t.create('WhatPulse team as team name, keys - from Ranks')
|
||||
.get('/keys/team/dutch power cows.json?rank')
|
||||
.expectBadge({ label: 'keys', message: isOrdinalNumber })
|
||||
|
||||
t.create(
|
||||
'WhatPulse invalid metric name (not one of the options from the modal`s dropdown)'
|
||||
)
|
||||
.get('/UpTIMe/user/jerone.json')
|
||||
.expectBadge({ label: '404', message: 'badge not found' })
|
||||
|
||||
t.create('WhatPulse incorrect user name')
|
||||
.get('/uptime/user/NonExistentUsername.json')
|
||||
.expectBadge({ label: 'whatpulse', message: 'invalid response data' })
|
||||
Reference in New Issue
Block a user