From 4175b517f37b75a8f828b5cd368b8cbe1437331f Mon Sep 17 00:00:00 2001 From: Nigel George Date: Mon, 26 Feb 2024 18:50:21 +0900 Subject: [PATCH 01/10] wip(youtrack): wip youtrack issues --- core/server/server.js | 2 + doc/server-secrets.md | 9 ++ services/youtrack/youtrack-base.js | 22 +++++ services/youtrack/youtrack-base.spec.js | 48 ++++++++++ services/youtrack/youtrack-helper.js | 6 ++ services/youtrack/youtrack-issues.service.js | 93 ++++++++++++++++++++ services/youtrack/youtrack-issues.tester.js | 9 ++ 7 files changed, 189 insertions(+) create mode 100644 services/youtrack/youtrack-base.js create mode 100644 services/youtrack/youtrack-base.spec.js create mode 100644 services/youtrack/youtrack-helper.js create mode 100644 services/youtrack/youtrack-issues.service.js create mode 100644 services/youtrack/youtrack-issues.tester.js diff --git a/core/server/server.js b/core/server/server.js index d6c6256a1165e..99484ef92f6cc 100644 --- a/core/server/server.js +++ b/core/server/server.js @@ -142,6 +142,7 @@ const publicConfigSchema = Joi.object({ sonar: defaultService, teamcity: defaultService, weblate: defaultService, + youtrack: defaultService, trace: Joi.boolean().required(), }).required(), cacheHeaders: { defaultCacheLengthSeconds: nonNegativeInteger }, @@ -205,6 +206,7 @@ const privateConfigSchema = Joi.object({ influx_password: Joi.string(), weblate_api_key: Joi.string(), youtube_api_key: Joi.string(), + youtrack_token: Joi.string(), }).required() const privateMetricsInfluxConfigSchema = privateConfigSchema.append({ influx_username: Joi.string().required(), diff --git a/doc/server-secrets.md b/doc/server-secrets.md index f7406b6d8bb89..6efadc0183805 100644 --- a/doc/server-secrets.md +++ b/doc/server-secrets.md @@ -364,6 +364,15 @@ and create an API key for the YouTube Data API v3. [youtube credentials]: https://console.developers.google.com/apis/credentials +### YouTrack + +- `YOUTRACK_ORIGINS` (yml: `public.services.youtrack.authorizedOrigins`) +- `YOUTRACK_TOKEN` (yml: `private.youtrack_token`) + +A Youtrack [Permanent Access Token][youtrack-pat] is required for accessing private content. If you need a Youtrack token for your self-hosted Shields server then we recommend limiting the scopes to the minimal set necessary for the badges you are using. + +[youtrack-pat]: https://www.jetbrains.com/help/youtrack/devportal/Manage-Permanent-Token.html + ## Error reporting - `SENTRY_DSN` (yml: `private.sentry_dsn`) diff --git a/services/youtrack/youtrack-base.js b/services/youtrack/youtrack-base.js new file mode 100644 index 0000000000000..771828a54a383 --- /dev/null +++ b/services/youtrack/youtrack-base.js @@ -0,0 +1,22 @@ +import { BaseJsonService } from '../index.js' + +export default class YoutrackBase extends BaseJsonService { + static auth = { + passKey: 'youtrack_token', + serviceKey: 'youtrack', + } + + async fetch({ url, options, schema, httpErrors }) { + console.log(url) + console.log(options) + + return this._requestJson( + this.authHelper.withBearerAuthHeader({ + schema, + url, + options, + httpErrors: { 500: 'invalid query' }, + }), + ) + } +} diff --git a/services/youtrack/youtrack-base.spec.js b/services/youtrack/youtrack-base.spec.js new file mode 100644 index 0000000000000..c06e5555d8171 --- /dev/null +++ b/services/youtrack/youtrack-base.spec.js @@ -0,0 +1,48 @@ +import Joi from 'joi' +import { expect } from 'chai' +import nock from 'nock' +import { cleanUpNockAfterEach, defaultContext } from '../test-helpers.js' +import YoutrackBase from './youtrack-base.js' + +class DummyYoutrackService extends YoutrackBase { + static route = { base: 'fake-base' } + + async handle() { + const data = await this.fetch({ + schema: Joi.any(), + url: 'https://youtrack.jetbrains.com//api/issuesGetter/count?fields=count', + }) + return { message: data.message } + } +} + +describe('YoutrackBase', function () { + describe('auth', function () { + cleanUpNockAfterEach() + + const config = { + public: { + services: { + youtrack: { + authorizedOrigins: ['https://youtrack.jetbrains.com'], + }, + }, + }, + private: { + youtrack_token: 'fake-key', + }, + } + + it('sends the auth information as configured', async function () { + const scope = nock('https://youtrack.jetbrains.com') + .get('/api/issuesGetter/count?fields=count') + .matchHeader('Authorization', 'Bearer fake-key') + .reply(200, { message: 'fake message' }) + expect( + await DummyYoutrackService.invoke(defaultContext, config, {}), + ).to.not.have.property('isError') + + scope.done() + }) + }) +}) diff --git a/services/youtrack/youtrack-helper.js b/services/youtrack/youtrack-helper.js new file mode 100644 index 0000000000000..84108f3b0d3c1 --- /dev/null +++ b/services/youtrack/youtrack-helper.js @@ -0,0 +1,6 @@ +const description = ` +By default this badge looks for projects on [youtrack.jetbrains.com](https://youtrack.jetbrains.com). +To specify a self-hosted instance, use the \`youtrack_url\` query param. +` + +export { description } diff --git a/services/youtrack/youtrack-issues.service.js b/services/youtrack/youtrack-issues.service.js new file mode 100644 index 0000000000000..9661741960175 --- /dev/null +++ b/services/youtrack/youtrack-issues.service.js @@ -0,0 +1,93 @@ +import Joi from 'joi' +import { pathParam, queryParam } from '../index.js' +import { optionalUrl } from '../validators.js' +import { metric } from '../text-formatters.js' +import { description } from './youtrack-helper.js' +import YoutrackBase from './youtrack-base.js' + +const schema = Joi.array().items( + Joi.object({ + count: Joi.number().required(), + type: Joi.string().required(), + }), +) + +const queryParamSchema = Joi.object({ + query: Joi.string(), + youtrack_url: optionalUrl, +}).required() + +export default class YoutrackIssues extends YoutrackBase { + static category = 'issue-tracking' + + static route = { + base: 'youtrack/issues', + pattern: ':project+', + queryParamSchema, + } + + static openApi = { + '/youtrack/issues/{project}': { + get: { + summary: 'Youtrack Issues', + description, + parameters: [ + pathParam({ + name: 'project', + example: 'RIDER', + }), + queryParam({ + name: 'youtrack_url', + example: 'https://youtrack.jetbrains.com', + }), + queryParam({ + name: 'query', + example: 'bug #Unresolved', + description: 'A valid YouTrack search query.', + }), + ], + }, + }, + } + + static defaultBadgeData = { label: 'issues', color: 'informational' } + + static render({ count }) { + return { + label: 'issues', + message: metric(count), + color: count > 0 ? 'yellow' : 'brightgreen', + } + } + + async fetch({ baseUrl, query }) { + // https://www.jetbrains.com.cn/en-us/help/youtrack/devportal/resource-api-issuesGetter-count.html + return super.fetch({ + schema, + options: { + method: 'POST', + searchParams: { + query, + }, + }, + url: `${baseUrl}/api/issuesGetter/count?fields=count`, + }) + } + + async handle( + { project }, + { youtrack_url: baseUrl = 'https://youtrack.jetbrains.com', query }, + ) { + const { res } = await this.fetch({ + baseUrl, + query: `project: ${project} ${query}`, + }) + + console.log(res) + + const data = this.constructor._validate(res.headers, schema) + + const count = data.count() + return this.constructor.render({ count }) + } +} diff --git a/services/youtrack/youtrack-issues.tester.js b/services/youtrack/youtrack-issues.tester.js new file mode 100644 index 0000000000000..7514ae809d08c --- /dev/null +++ b/services/youtrack/youtrack-issues.tester.js @@ -0,0 +1,9 @@ +import { createServiceTester } from '../tester.js' +import { isMetric } from '../test-validators.js' + +export const t = await createServiceTester() + +t.create('Issues (RIDER)').get('/RIDER?query=#Unresolved').expectBadge({ + label: 'issues', + message: isMetric, +}) From a47ae40b2194061fadb3925dfca22ad02c4019a3 Mon Sep 17 00:00:00 2001 From: Nigel George Date: Mon, 26 Feb 2024 19:06:31 +0900 Subject: [PATCH 02/10] fix(youtrack): fix url encoded # --- services/youtrack/youtrack-issues.tester.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/youtrack/youtrack-issues.tester.js b/services/youtrack/youtrack-issues.tester.js index 7514ae809d08c..c4ae5cf6b2111 100644 --- a/services/youtrack/youtrack-issues.tester.js +++ b/services/youtrack/youtrack-issues.tester.js @@ -3,7 +3,7 @@ import { isMetric } from '../test-validators.js' export const t = await createServiceTester() -t.create('Issues (RIDER)').get('/RIDER?query=#Unresolved').expectBadge({ +t.create('Issues (RIDER)').get('/RIDER?query=%23Unresolved').expectBadge({ label: 'issues', message: isMetric, }) From 6fed1206de4dbb455dfed2367cc2c4d0be18baf0 Mon Sep 17 00:00:00 2001 From: Nigel George Date: Tue, 27 Feb 2024 19:38:41 +0900 Subject: [PATCH 03/10] fix(youtrack): fix request and handling, add tests --- services/youtrack/youtrack-base.js | 5 +---- services/youtrack/youtrack-issues.service.js | 23 ++++++-------------- services/youtrack/youtrack-issues.tester.js | 20 ++++++++++++++--- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/services/youtrack/youtrack-base.js b/services/youtrack/youtrack-base.js index 771828a54a383..2f399a953560a 100644 --- a/services/youtrack/youtrack-base.js +++ b/services/youtrack/youtrack-base.js @@ -7,15 +7,12 @@ export default class YoutrackBase extends BaseJsonService { } async fetch({ url, options, schema, httpErrors }) { - console.log(url) - console.log(options) - return this._requestJson( this.authHelper.withBearerAuthHeader({ schema, url, options, - httpErrors: { 500: 'invalid query' }, + httpErrors: { 500: 'invalid query', ...httpErrors }, }), ) } diff --git a/services/youtrack/youtrack-issues.service.js b/services/youtrack/youtrack-issues.service.js index 9661741960175..f7a51dc2523ab 100644 --- a/services/youtrack/youtrack-issues.service.js +++ b/services/youtrack/youtrack-issues.service.js @@ -5,12 +5,10 @@ import { metric } from '../text-formatters.js' import { description } from './youtrack-helper.js' import YoutrackBase from './youtrack-base.js' -const schema = Joi.array().items( - Joi.object({ - count: Joi.number().required(), - type: Joi.string().required(), - }), -) +const schema = Joi.object({ + count: Joi.number().required(), + $type: Joi.equal('IssueCountResponse'), +}) const queryParamSchema = Joi.object({ query: Joi.string(), @@ -66,9 +64,7 @@ export default class YoutrackIssues extends YoutrackBase { schema, options: { method: 'POST', - searchParams: { - query, - }, + json: { query }, }, url: `${baseUrl}/api/issuesGetter/count?fields=count`, }) @@ -78,16 +74,11 @@ export default class YoutrackIssues extends YoutrackBase { { project }, { youtrack_url: baseUrl = 'https://youtrack.jetbrains.com', query }, ) { - const { res } = await this.fetch({ + const data = await this.fetch({ baseUrl, query: `project: ${project} ${query}`, }) - console.log(res) - - const data = this.constructor._validate(res.headers, schema) - - const count = data.count() - return this.constructor.render({ count }) + return this.constructor.render({ count: data.count }) } } diff --git a/services/youtrack/youtrack-issues.tester.js b/services/youtrack/youtrack-issues.tester.js index c4ae5cf6b2111..584cc105a5f13 100644 --- a/services/youtrack/youtrack-issues.tester.js +++ b/services/youtrack/youtrack-issues.tester.js @@ -1,9 +1,23 @@ import { createServiceTester } from '../tester.js' -import { isMetric } from '../test-validators.js' +import { isMetricAllowNegative } from '../test-validators.js' export const t = await createServiceTester() -t.create('Issues (RIDER)').get('/RIDER?query=%23Unresolved').expectBadge({ +t.create('Issues (IDEA)').get('/IDEA.json?query=%23Unresolved').expectBadge({ label: 'issues', - message: isMetric, + message: isMetricAllowNegative, }) + +t.create('Issues (IDEA) (Invalid State)') + .get('/IDEA.json?query=%23ABCDEFG') + .expectBadge({ + label: 'issues', + message: 'invalid', + }) + +t.create('Issues (DOESNOTEXIST) (Invalid Project)') + .get('/DOESNOTEXIST.json?query=%23Unresolved') + .expectBadge({ + label: 'issues', + message: 'invalid', + }) From aedd31d71a14786b10195cb3f76a5189536e159c Mon Sep 17 00:00:00 2001 From: Nigel George Date: Wed, 28 Feb 2024 22:05:43 +0900 Subject: [PATCH 04/10] ci(url): update to use dedicated cloud hosted instance --- services/youtrack/youtrack-issues.tester.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/services/youtrack/youtrack-issues.tester.js b/services/youtrack/youtrack-issues.tester.js index 584cc105a5f13..6a2df7edb2e92 100644 --- a/services/youtrack/youtrack-issues.tester.js +++ b/services/youtrack/youtrack-issues.tester.js @@ -3,20 +3,22 @@ import { isMetricAllowNegative } from '../test-validators.js' export const t = await createServiceTester() -t.create('Issues (IDEA)').get('/IDEA.json?query=%23Unresolved').expectBadge({ - label: 'issues', - message: isMetricAllowNegative, -}) +t.create('Issues (DEMO) (Cloud)') + .get('/DEMO.json?youtrack_url=https://shields.youtrack.cloud&query=%23Fixed') + .expectBadge({ + label: 'issues', + message: isMetricAllowNegative, + }) -t.create('Issues (IDEA) (Invalid State)') - .get('/IDEA.json?query=%23ABCDEFG') +t.create('Issues (DEMO) (Invalid State) (Cloud Hosted)') + .get('/DEMO.json?https://shields.youtrack.cloud&query=%23ABCDEFG') .expectBadge({ label: 'issues', message: 'invalid', }) -t.create('Issues (DOESNOTEXIST) (Invalid Project)') - .get('/DOESNOTEXIST.json?query=%23Unresolved') +t.create('Issues (DOESNOTEXIST) (Invalid Project) (Cloud Hosted)') + .get('/DOESNOTEXIST.json?https://shields.youtrack.cloud&query=%23Unresolved') .expectBadge({ label: 'issues', message: 'invalid', From 4a698af99ebca41516002f78586c35d1a37b55e9 Mon Sep 17 00:00:00 2001 From: Nigel George Date: Mon, 4 Mar 2024 11:29:47 +0900 Subject: [PATCH 05/10] fix(params): add required to url param, set red for -1 --- services/youtrack/youtrack-helper.js | 5 +++-- services/youtrack/youtrack-issues.service.js | 5 +++-- services/youtrack/youtrack-issues.tester.js | 7 +++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/services/youtrack/youtrack-helper.js b/services/youtrack/youtrack-helper.js index 84108f3b0d3c1..deaab538c4488 100644 --- a/services/youtrack/youtrack-helper.js +++ b/services/youtrack/youtrack-helper.js @@ -1,6 +1,7 @@ const description = ` -By default this badge looks for projects on [youtrack.jetbrains.com](https://youtrack.jetbrains.com). -To specify a self-hosted instance, use the \`youtrack_url\` query param. +Returns the number of issues for the specified project based on the \`query\` parameter defined. + +NOTE: The \`youtrack_url\` query param is required. ` export { description } diff --git a/services/youtrack/youtrack-issues.service.js b/services/youtrack/youtrack-issues.service.js index f7a51dc2523ab..216d23817b4bf 100644 --- a/services/youtrack/youtrack-issues.service.js +++ b/services/youtrack/youtrack-issues.service.js @@ -36,7 +36,8 @@ export default class YoutrackIssues extends YoutrackBase { }), queryParam({ name: 'youtrack_url', - example: 'https://youtrack.jetbrains.com', + example: 'https://shields.youtrack.cloud', + required: true, }), queryParam({ name: 'query', @@ -54,7 +55,7 @@ export default class YoutrackIssues extends YoutrackBase { return { label: 'issues', message: metric(count), - color: count > 0 ? 'yellow' : 'brightgreen', + color: count < 0 ? 'red' : count > 0 ? 'yellow' : 'brightgreen', } } diff --git a/services/youtrack/youtrack-issues.tester.js b/services/youtrack/youtrack-issues.tester.js index 6a2df7edb2e92..2a1f65692655d 100644 --- a/services/youtrack/youtrack-issues.tester.js +++ b/services/youtrack/youtrack-issues.tester.js @@ -10,6 +10,13 @@ t.create('Issues (DEMO) (Cloud)') message: isMetricAllowNegative, }) +t.create('Issues (DEMO) (Empty Query) (Cloud)') + .get('/DEMO.json?youtrack_url=https://shields.youtrack.cloud') + .expectBadge({ + label: 'issues', + message: isMetricAllowNegative, + }) + t.create('Issues (DEMO) (Invalid State) (Cloud Hosted)') .get('/DEMO.json?https://shields.youtrack.cloud&query=%23ABCDEFG') .expectBadge({ From 514d0996514d54ce4ca54a06c08e508dfc192983 Mon Sep 17 00:00:00 2001 From: Nigel George Date: Tue, 5 Mar 2024 17:56:39 +0900 Subject: [PATCH 06/10] fix(-1): invoke error on -1 --- services/youtrack/youtrack-base.js | 3 +++ services/youtrack/youtrack-issues.service.js | 10 ++++++++-- services/youtrack/youtrack-issues.tester.js | 7 ++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/services/youtrack/youtrack-base.js b/services/youtrack/youtrack-base.js index 2f399a953560a..c99d5321093df 100644 --- a/services/youtrack/youtrack-base.js +++ b/services/youtrack/youtrack-base.js @@ -13,6 +13,9 @@ export default class YoutrackBase extends BaseJsonService { url, options, httpErrors: { 500: 'invalid query', ...httpErrors }, + systemErrors: { + ETIMEOUT: { prettyMessage: 'timeout', cacheSeconds: 10 }, + }, }), ) } diff --git a/services/youtrack/youtrack-issues.service.js b/services/youtrack/youtrack-issues.service.js index 216d23817b4bf..5ab9367fc62b3 100644 --- a/services/youtrack/youtrack-issues.service.js +++ b/services/youtrack/youtrack-issues.service.js @@ -1,5 +1,5 @@ import Joi from 'joi' -import { pathParam, queryParam } from '../index.js' +import { InvalidResponse, pathParam, queryParam } from '../index.js' import { optionalUrl } from '../validators.js' import { metric } from '../text-formatters.js' import { description } from './youtrack-helper.js' @@ -55,7 +55,7 @@ export default class YoutrackIssues extends YoutrackBase { return { label: 'issues', message: metric(count), - color: count < 0 ? 'red' : count > 0 ? 'yellow' : 'brightgreen', + color: count > 0 ? 'yellow' : 'brightgreen', } } @@ -80,6 +80,12 @@ export default class YoutrackIssues extends YoutrackBase { query: `project: ${project} ${query}`, }) + if (data.count === -1) { + throw new InvalidResponse({ + prettyMessage: 'processing', + cacheSeconds: 10, + }) + } return this.constructor.render({ count: data.count }) } } diff --git a/services/youtrack/youtrack-issues.tester.js b/services/youtrack/youtrack-issues.tester.js index 2a1f65692655d..51cc8f5854f5e 100644 --- a/services/youtrack/youtrack-issues.tester.js +++ b/services/youtrack/youtrack-issues.tester.js @@ -1,5 +1,6 @@ +import Joi from 'joi' import { createServiceTester } from '../tester.js' -import { isMetricAllowNegative } from '../test-validators.js' +import { isMetric } from '../test-validators.js' export const t = await createServiceTester() @@ -7,14 +8,14 @@ t.create('Issues (DEMO) (Cloud)') .get('/DEMO.json?youtrack_url=https://shields.youtrack.cloud&query=%23Fixed') .expectBadge({ label: 'issues', - message: isMetricAllowNegative, + message: Joi.alternatives().try(isMetric, 'processing', 'timeout'), }) t.create('Issues (DEMO) (Empty Query) (Cloud)') .get('/DEMO.json?youtrack_url=https://shields.youtrack.cloud') .expectBadge({ label: 'issues', - message: isMetricAllowNegative, + message: Joi.alternatives().try(isMetric, 'processing', 'timeout'), }) t.create('Issues (DEMO) (Invalid State) (Cloud Hosted)') From 6d0e43ba86b2c88153ba3afbd6a2b7c80b20ee43 Mon Sep 17 00:00:00 2001 From: Nigel George Date: Tue, 5 Mar 2024 18:02:21 +0900 Subject: [PATCH 07/10] fix(test): last minute fixes --- services/youtrack/youtrack-base.spec.js | 6 +++--- services/youtrack/youtrack-issues.service.js | 5 +---- services/youtrack/youtrack-issues.tester.js | 8 ++++++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/services/youtrack/youtrack-base.spec.js b/services/youtrack/youtrack-base.spec.js index c06e5555d8171..33361cf08a38a 100644 --- a/services/youtrack/youtrack-base.spec.js +++ b/services/youtrack/youtrack-base.spec.js @@ -10,7 +10,7 @@ class DummyYoutrackService extends YoutrackBase { async handle() { const data = await this.fetch({ schema: Joi.any(), - url: 'https://youtrack.jetbrains.com//api/issuesGetter/count?fields=count', + url: 'https://shields.youtrack.cloud/api/issuesGetter/count?fields=count', }) return { message: data.message } } @@ -24,7 +24,7 @@ describe('YoutrackBase', function () { public: { services: { youtrack: { - authorizedOrigins: ['https://youtrack.jetbrains.com'], + authorizedOrigins: ['https://shields.youtrack.cloud'], }, }, }, @@ -34,7 +34,7 @@ describe('YoutrackBase', function () { } it('sends the auth information as configured', async function () { - const scope = nock('https://youtrack.jetbrains.com') + const scope = nock('https://shields.youtrack.cloud') .get('/api/issuesGetter/count?fields=count') .matchHeader('Authorization', 'Bearer fake-key') .reply(200, { message: 'fake message' }) diff --git a/services/youtrack/youtrack-issues.service.js b/services/youtrack/youtrack-issues.service.js index 5ab9367fc62b3..ce02f4d480de8 100644 --- a/services/youtrack/youtrack-issues.service.js +++ b/services/youtrack/youtrack-issues.service.js @@ -71,10 +71,7 @@ export default class YoutrackIssues extends YoutrackBase { }) } - async handle( - { project }, - { youtrack_url: baseUrl = 'https://youtrack.jetbrains.com', query }, - ) { + async handle({ project }, { youtrack_url: baseUrl, query }) { const data = await this.fetch({ baseUrl, query: `project: ${project} ${query}`, diff --git a/services/youtrack/youtrack-issues.tester.js b/services/youtrack/youtrack-issues.tester.js index 51cc8f5854f5e..48a0fa72e4b5a 100644 --- a/services/youtrack/youtrack-issues.tester.js +++ b/services/youtrack/youtrack-issues.tester.js @@ -19,14 +19,18 @@ t.create('Issues (DEMO) (Empty Query) (Cloud)') }) t.create('Issues (DEMO) (Invalid State) (Cloud Hosted)') - .get('/DEMO.json?https://shields.youtrack.cloud&query=%23ABCDEFG') + .get( + '/DEMO.json?youtrack_url=https://shields.youtrack.cloud&query=%23ABCDEFG', + ) .expectBadge({ label: 'issues', message: 'invalid', }) t.create('Issues (DOESNOTEXIST) (Invalid Project) (Cloud Hosted)') - .get('/DOESNOTEXIST.json?https://shields.youtrack.cloud&query=%23Unresolved') + .get( + '/DOESNOTEXIST.json?youtrack_url=https://shields.youtrack.cloud&query=%23Unresolved', + ) .expectBadge({ label: 'issues', message: 'invalid', From ad2ddcb9f67b0c941e77df23b371826aefc080bb Mon Sep 17 00:00:00 2001 From: Nigel George Date: Mon, 11 Mar 2024 09:43:46 +0900 Subject: [PATCH 08/10] fix(example): update example to be a more stable example --- services/youtrack/youtrack-issues.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/youtrack/youtrack-issues.service.js b/services/youtrack/youtrack-issues.service.js index ce02f4d480de8..bf201e3a11c01 100644 --- a/services/youtrack/youtrack-issues.service.js +++ b/services/youtrack/youtrack-issues.service.js @@ -32,7 +32,7 @@ export default class YoutrackIssues extends YoutrackBase { parameters: [ pathParam({ name: 'project', - example: 'RIDER', + example: 'DEMO', }), queryParam({ name: 'youtrack_url', @@ -41,7 +41,7 @@ export default class YoutrackIssues extends YoutrackBase { }), queryParam({ name: 'query', - example: 'bug #Unresolved', + example: 'manage state: Unresolved', description: 'A valid YouTrack search query.', }), ], From 5e98b19bcf93637bfa57e643ab5d560e8d0a8da3 Mon Sep 17 00:00:00 2001 From: Nigel George Date: Mon, 11 Mar 2024 09:56:17 +0900 Subject: [PATCH 09/10] ci(query): update tests to use example style query --- services/youtrack/youtrack-issues.tester.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/youtrack/youtrack-issues.tester.js b/services/youtrack/youtrack-issues.tester.js index 48a0fa72e4b5a..58176d021a4bb 100644 --- a/services/youtrack/youtrack-issues.tester.js +++ b/services/youtrack/youtrack-issues.tester.js @@ -5,7 +5,9 @@ import { isMetric } from '../test-validators.js' export const t = await createServiceTester() t.create('Issues (DEMO) (Cloud)') - .get('/DEMO.json?youtrack_url=https://shields.youtrack.cloud&query=%23Fixed') + .get( + '/DEMO.json?youtrack_url=https://shields.youtrack.cloud&query=manage%20state%3A%20Unresolved', + ) .expectBadge({ label: 'issues', message: Joi.alternatives().try(isMetric, 'processing', 'timeout'), @@ -29,7 +31,7 @@ t.create('Issues (DEMO) (Invalid State) (Cloud Hosted)') t.create('Issues (DOESNOTEXIST) (Invalid Project) (Cloud Hosted)') .get( - '/DOESNOTEXIST.json?youtrack_url=https://shields.youtrack.cloud&query=%23Unresolved', + '/DOESNOTEXIST.json?youtrack_url=https://shields.youtrack.cloud&query=state%3A%20Unresolved', ) .expectBadge({ label: 'issues', From 8bcc3da49ef8fdb55005d4475fac060a18d973e6 Mon Sep 17 00:00:00 2001 From: Nigel George Date: Fri, 15 Mar 2024 14:39:58 +0900 Subject: [PATCH 10/10] feat(retry): add 6x 500ms spaced retries --- services/youtrack/youtrack-issues.service.js | 33 +++++++++++++------- services/youtrack/youtrack-issues.tester.js | 5 ++- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/services/youtrack/youtrack-issues.service.js b/services/youtrack/youtrack-issues.service.js index bf201e3a11c01..2c6d7e4a58082 100644 --- a/services/youtrack/youtrack-issues.service.js +++ b/services/youtrack/youtrack-issues.service.js @@ -5,6 +5,10 @@ import { metric } from '../text-formatters.js' import { description } from './youtrack-helper.js' import YoutrackBase from './youtrack-base.js' +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)) +} + const schema = Joi.object({ count: Joi.number().required(), $type: Joi.equal('IssueCountResponse'), @@ -71,18 +75,25 @@ export default class YoutrackIssues extends YoutrackBase { }) } - async handle({ project }, { youtrack_url: baseUrl, query }) { - const data = await this.fetch({ - baseUrl, - query: `project: ${project} ${query}`, - }) - - if (data.count === -1) { - throw new InvalidResponse({ - prettyMessage: 'processing', - cacheSeconds: 10, + async handle({ project }, { youtrack_url: baseUrl, query = '' }) { + for (let i = 0; i < 6; i++) { + // 6 trials + const data = await this.fetch({ + baseUrl, + query: `project: ${project} ${query}`, }) + + if (data.count === -1) { + await sleep(500) + continue + } + + return this.constructor.render({ count: data.count }) } - return this.constructor.render({ count: data.count }) + + throw new InvalidResponse({ + prettyMessage: 'invalid', + cacheSeconds: 10, + }) } } diff --git a/services/youtrack/youtrack-issues.tester.js b/services/youtrack/youtrack-issues.tester.js index 58176d021a4bb..60aeecf3e7f6b 100644 --- a/services/youtrack/youtrack-issues.tester.js +++ b/services/youtrack/youtrack-issues.tester.js @@ -1,4 +1,3 @@ -import Joi from 'joi' import { createServiceTester } from '../tester.js' import { isMetric } from '../test-validators.js' @@ -10,14 +9,14 @@ t.create('Issues (DEMO) (Cloud)') ) .expectBadge({ label: 'issues', - message: Joi.alternatives().try(isMetric, 'processing', 'timeout'), + message: isMetric, }) t.create('Issues (DEMO) (Empty Query) (Cloud)') .get('/DEMO.json?youtrack_url=https://shields.youtrack.cloud') .expectBadge({ label: 'issues', - message: Joi.alternatives().try(isMetric, 'processing', 'timeout'), + message: isMetric, }) t.create('Issues (DEMO) (Invalid State) (Cloud Hosted)')