diff --git a/services/github/github-discussions-custom-search.service.js b/services/github/github-discussions-custom-search.service.js new file mode 100644 index 0000000000000..ece506cd2c39e --- /dev/null +++ b/services/github/github-discussions-custom-search.service.js @@ -0,0 +1,114 @@ +import gql from 'graphql-tag' +import Joi from 'joi' +import { metric } from '../text-formatters.js' +import { nonNegativeInteger } from '../validators.js' +import { GithubAuthV4Service } from './github-auth-service.js' +import { documentation, transformErrors } from './github-helpers.js' + +const discussionsSearchDocs = ` +For a full list of available filters and allowed values that can be used in the query, +see GitHub's documentation on +[Searching discussions](https://docs.github.com/en/search-github/searching-on-github/searching-discussions). +${documentation} +` + +const discussionCountSchema = Joi.object({ + data: Joi.object({ + search: Joi.object({ + discussionCount: nonNegativeInteger, + }).required(), + }).required(), +}).required() + +const queryParamSchema = Joi.object({ + query: Joi.string().required(), +}).required() + +class BaseGithubDiscussionsSearch extends GithubAuthV4Service { + static category = 'other' + static defaultBadgeData = { label: 'query', color: 'informational' } + + static render({ discussionCount }) { + return { message: metric(discussionCount) } + } + + async fetch({ query }) { + const data = await this._requestGraphql({ + query: gql` + query ($query: String!) { + search(query: $query, type: DISCUSSION) { + discussionCount + } + } + `, + variables: { query }, + schema: discussionCountSchema, + transformErrors, + }) + return data.data.search.discussionCount + } +} + +class GithubDiscussionsSearch extends BaseGithubDiscussionsSearch { + static route = { + base: 'github', + pattern: 'discussions-search', + queryParamSchema, + } + + static examples = [ + { + title: 'GitHub discussions custom search', + namedParams: {}, + queryParams: { + query: 'repo:badges/shields is:answered answered-by:chris48s', + }, + staticPreview: { + label: 'query', + message: '2', + color: 'blue', + }, + documentation: discussionsSearchDocs, + }, + ] + + async handle(namedParams, { query }) { + const discussionCount = await this.fetch({ query }) + return this.constructor.render({ discussionCount }) + } +} + +class GithubRepoDiscussionsSearch extends BaseGithubDiscussionsSearch { + static route = { + base: 'github', + pattern: 'discussions-search/:user/:repo', + queryParamSchema, + } + + static examples = [ + { + title: 'GitHub discussions custom search in repo', + namedParams: { + user: 'badges', + repo: 'shields', + }, + queryParams: { + query: 'is:answered answered-by:chris48s', + }, + staticPreview: { + label: 'query', + message: '2', + color: 'blue', + }, + documentation: discussionsSearchDocs, + }, + ] + + async handle({ user, repo }, { query }) { + query = `repo:${user}/${repo} ${query}` + const discussionCount = await this.fetch({ query }) + return this.constructor.render({ discussionCount }) + } +} + +export { GithubDiscussionsSearch, GithubRepoDiscussionsSearch } diff --git a/services/github/github-discussions-custom-search.tester.js b/services/github/github-discussions-custom-search.tester.js new file mode 100644 index 0000000000000..c48b7fe4c0196 --- /dev/null +++ b/services/github/github-discussions-custom-search.tester.js @@ -0,0 +1,48 @@ +import { isMetric } from '../test-validators.js' +import { ServiceTester } from '../tester.js' +export const t = new ServiceTester({ + id: 'GithubDiscussionsSearch', + title: 'Github Discussions Search', + pathPrefix: '/github', +}) + +t.create('GitHub discussions search (valid query string)') + .get( + '/discussions-search.json?query=repo%3Abadges%2Fshields%20is%3Aanswered%20author%3Achris48s' + ) + .expectBadge({ + label: 'query', + message: isMetric, + }) + +t.create('GitHub discussions search (invalid query string)') + .get('/discussions-search.json?query=') + .expectBadge({ + label: 'query', + message: 'invalid query parameter: query', + }) + +t.create('GitHub Repo discussions search (valid query string)') + .get( + '/discussions-search/badges/shields.json?query=is%3Aanswered%20author%3Achris48s' + ) + .expectBadge({ + label: 'query', + message: isMetric, + }) + +t.create('GitHub Repo discussions search (invalid query string)') + .get('/discussions-search/badges/shields.json?query=') + .expectBadge({ + label: 'query', + message: 'invalid query parameter: query', + }) + +t.create('GitHub Repo discussions search (invalid repo)') + .get( + '/discussions-search/badges/helmets.json?query=is%3Aanswered%20author%3Achris48s' + ) + .expectBadge({ + label: 'query', + message: '0', + })