From a0ac7496ad6f15de0be262c28a18d354c95e1815 Mon Sep 17 00:00:00 2001 From: Chris Hawkins Date: Wed, 29 Mar 2023 16:59:30 +0100 Subject: [PATCH 1/7] Support for 2.3 teamName Signed-off-by: Chris Hawkins --- ...ckOverflowQuestionsCollatorFactory.test.ts | 44 +++++++++++++++++++ .../StackOverflowQuestionsCollatorFactory.ts | 27 ++++++++++-- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.test.ts b/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.test.ts index bdc5be125c..5d2f65923c 100644 --- a/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.test.ts +++ b/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.test.ts @@ -139,5 +139,49 @@ describe('StackOverflowQuestionsCollatorFactory', () => { expect(documents).toHaveLength(mockOverrideQuestion.items.length); }); + + it('uses API key when provided', async () => { + worker.use( + rest.get('http://stack.overflow.override/questions', (req, res, ctx) => + req.url.searchParams.get('key') === 'abcdefg' + ? res(ctx.status(200), ctx.json(mockOverrideQuestion)) + : res(ctx.status(401), ctx.json({})), + ), + ); + const factory = StackOverflowQuestionsCollatorFactory.fromConfig(config, { + logger, + baseUrl: 'http://stack.overflow.override', + apiKey: 'abcdefg', + requestParams: defaultOptions.requestParams, + }); + const collator = await factory.getCollator(); + + const pipeline = TestPipeline.fromCollator(collator); + const { documents } = await pipeline.execute(); + + expect(documents).toHaveLength(mockOverrideQuestion.items.length); + }); + + it('uses teamName when provided', async () => { + worker.use( + rest.get('http://stack.overflow.override/questions', (req, res, ctx) => + req.url.searchParams.get('team') === 'abcdefg' + ? res(ctx.status(200), ctx.json(mockOverrideQuestion)) + : res(ctx.status(401), ctx.json({})), + ), + ); + const factory = StackOverflowQuestionsCollatorFactory.fromConfig(config, { + logger, + baseUrl: 'http://stack.overflow.override', + teamName: 'abcdefg', + requestParams: defaultOptions.requestParams, + }); + const collator = await factory.getCollator(); + + const pipeline = TestPipeline.fromCollator(collator); + const { documents } = await pipeline.execute(); + + expect(documents).toHaveLength(mockOverrideQuestion.items.length); + }); }); }); diff --git a/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts b/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts index 807d6637c1..ccded6b266 100644 --- a/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts +++ b/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts @@ -53,6 +53,7 @@ export type StackOverflowQuestionsCollatorFactoryOptions = { maxPage?: number; apiKey?: string; apiAccessToken?: string; + teamName?: string; requestParams: StackOverflowQuestionsRequestParams; logger: Logger; }; @@ -69,6 +70,7 @@ export class StackOverflowQuestionsCollatorFactory private readonly baseUrl: string | undefined; private readonly apiKey: string | undefined; private readonly apiAccessToken: string | undefined; + private readonly teamName: string | undefined; private readonly maxPage: number | undefined; private readonly logger: Logger; public readonly type: string = 'stack-overflow'; @@ -77,6 +79,7 @@ export class StackOverflowQuestionsCollatorFactory this.baseUrl = options.baseUrl; this.apiKey = options.apiKey; this.apiAccessToken = options.apiAccessToken; + this.teamName = options.teamName; this.maxPage = options.maxPage; this.requestParams = options.requestParams; this.logger = options.logger.child({ documentType: this.type }); @@ -90,15 +93,18 @@ export class StackOverflowQuestionsCollatorFactory const apiAccessToken = config.getOptionalString( 'stackoverflow.apiAccessToken', ); + const pat = config.getOptionalString('stackoverflow.pat'); + const teamName = config.getOptionalString('stackoverflow.teamName'); const baseUrl = config.getOptionalString('stackoverflow.baseUrl') || - 'https://api.stackexchange.com/2.2'; + 'https://api.stackexchange.com/2.3'; const maxPage = options.maxPage || 100; return new StackOverflowQuestionsCollatorFactory({ baseUrl, maxPage, apiKey, apiAccessToken, + teamName, ...options, }); } @@ -114,10 +120,16 @@ export class StackOverflowQuestionsCollatorFactory ); } + if (this.apiKey && this.teamName) { + this.logger.debug( + 'Both stackoverflow.apiKey and stackoverflow.teamName configured in your app-config.yaml, apiKey must be removed before teamName will be used', + ); + } + try { if (Object.keys(this.requestParams).indexOf('key') >= 0) { this.logger.warn( - 'The API Key should be passed as a seperate param to bypass encoding', + 'The API Key should be passed as a separate param to bypass encoding', ); delete this.requestParams.key; } @@ -134,6 +146,15 @@ export class StackOverflowQuestionsCollatorFactory ? `${params ? '&' : '?'}key=${this.apiKey}` : ''; + const teamParam = this.teamName + ? `${params ? '&' : '?'}team=${this.teamName}` + : ''; + + // PAT change requires team name as a parameter + const requestUrl = this.apiKey + ? `${this.baseUrl}/questions${params}${apiKeyParam}` + : `${this.baseUrl}/questions${params}${teamParam}`; + let hasMorePages = true; let page = 1; while (hasMorePages) { @@ -144,7 +165,7 @@ export class StackOverflowQuestionsCollatorFactory break; } const res = await fetch( - `${this.baseUrl}/questions${params}${apiKeyParam}&page=${page}`, + `${requestUrl}&page=${page}`, this.apiAccessToken ? { headers: { From 3cd651234b0af7e4444fc387f465a38516f76869 Mon Sep 17 00:00:00 2001 From: Chris Hawkins Date: Wed, 29 Mar 2023 17:08:29 +0100 Subject: [PATCH 2/7] Updating README Signed-off-by: Chris Hawkins --- plugins/stack-overflow-backend/README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/plugins/stack-overflow-backend/README.md b/plugins/stack-overflow-backend/README.md index 4bcc755404..e6d9d50a74 100644 --- a/plugins/stack-overflow-backend/README.md +++ b/plugins/stack-overflow-backend/README.md @@ -17,9 +17,9 @@ stackoverflow: ### Stack Overflow for Teams -If you have a private Stack Overflow instance and/or a private Stack Overflow Team you will need to supply an API key. You can read more about how to set this up by going to [Stack Overflow's Help Page](https://stackoverflow.help/en/articles/4385859-stack-overflow-for-teams-api). +If you have a private Stack Overflow instance and/or a private Stack Overflow Team you will need to supply an API key or Personal Access Token. You can read more about how to set this up by going to [Stack Overflow's Help Page](https://stackoverflow.help/en/articles/4385859-stack-overflow-for-teams-api). -A private Stack Overflow Team requires both an API key and an API Access Token. Step 3 ("Generate an Access Token via OAuth") from the previously mentioned Help Page explains the process for generating an API Access Token with no expiration. +The existing API key approach remains the default, to support the new v2.3 API and PAT authentication model you need to pass the team name and the new PAT into the existing apiAccessToken parameter. See [15770](https://github.com/backstage/backstage/issues/15770) for more details. ```yaml stackoverflow: @@ -28,6 +28,13 @@ stackoverflow: apiAccessToken: $STACK_OVERFLOW_API_ACCESS_TOKEN ``` +```yaml +stackoverflow: + baseUrl: https://api.stackexchange.com/2.3 # alternative: your internal stack overflow instance + teamName: $STACK_OVERFLOW_TEAM_NAME + apiAccessToken: $STACK_OVERFLOW_API_ACCESS_TOKEN +``` + ## Areas of Responsibility This stack overflow backend plugin is primarily responsible for the following: From 51ff6f9edff773deb04a104a21ff6b7784be0b4d Mon Sep 17 00:00:00 2001 From: Chris Hawkins Date: Wed, 29 Mar 2023 17:12:43 +0100 Subject: [PATCH 3/7] Changeset Signed-off-by: Chris Hawkins --- .changeset/tricky-wombats-explode.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tricky-wombats-explode.md diff --git a/.changeset/tricky-wombats-explode.md b/.changeset/tricky-wombats-explode.md new file mode 100644 index 0000000000..aeb5b7849f --- /dev/null +++ b/.changeset/tricky-wombats-explode.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-stack-overflow-backend': minor +--- + +Adding support for v2.3 API and PAT authentication From a812b65c2ee7e182738dcd903a407f3071f56299 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 29 Mar 2023 19:28:08 +0100 Subject: [PATCH 4/7] Removing reference to PAT Signed-off-by: Chris --- .../src/search/StackOverflowQuestionsCollatorFactory.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts b/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts index ccded6b266..c5f8a8c16b 100644 --- a/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts +++ b/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts @@ -93,7 +93,6 @@ export class StackOverflowQuestionsCollatorFactory const apiAccessToken = config.getOptionalString( 'stackoverflow.apiAccessToken', ); - const pat = config.getOptionalString('stackoverflow.pat'); const teamName = config.getOptionalString('stackoverflow.teamName'); const baseUrl = config.getOptionalString('stackoverflow.baseUrl') || From 24fce3f515ce0a89203ffdc68c432f6a57a05d1b Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 29 Mar 2023 19:58:39 +0100 Subject: [PATCH 5/7] Update api-report.md Signed-off-by: Chris --- plugins/stack-overflow-backend/api-report.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/stack-overflow-backend/api-report.md b/plugins/stack-overflow-backend/api-report.md index f6c7530de9..8c601399c1 100644 --- a/plugins/stack-overflow-backend/api-report.md +++ b/plugins/stack-overflow-backend/api-report.md @@ -44,6 +44,7 @@ export type StackOverflowQuestionsCollatorFactoryOptions = { maxPage?: number; apiKey?: string; apiAccessToken?: string; + teamName?: string; requestParams: StackOverflowQuestionsRequestParams; logger: Logger; }; From 11dfa214196ea5ab5f9c33bb02bbc4c2b8ffbe61 Mon Sep 17 00:00:00 2001 From: Chris Hawkins Date: Thu, 13 Apr 2023 18:22:09 +0100 Subject: [PATCH 6/7] Adding teamName to config.d.ts Signed-off-by: Chris Hawkins --- plugins/stack-overflow-backend/config.d.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/stack-overflow-backend/config.d.ts b/plugins/stack-overflow-backend/config.d.ts index 54e517c5d8..b97f237c19 100644 --- a/plugins/stack-overflow-backend/config.d.ts +++ b/plugins/stack-overflow-backend/config.d.ts @@ -30,6 +30,11 @@ export interface Config { */ apiKey?: string; + /** + * The name of the team for a Stack Overflow for Teams account + */ + teamName?: string; + /** * The API Access Token to authenticate to Stack Overflow API * @visibility secret From 29a4f6e1c26e7df1f161448fd197e43780bf7506 Mon Sep 17 00:00:00 2001 From: Chris Hawkins Date: Thu, 13 Apr 2023 18:56:23 +0100 Subject: [PATCH 7/7] Fixing new Stack Overflow for Teams URL Signed-off-by: Chris Hawkins --- plugins/stack-overflow-backend/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/stack-overflow-backend/README.md b/plugins/stack-overflow-backend/README.md index e6d9d50a74..4575ab58c0 100644 --- a/plugins/stack-overflow-backend/README.md +++ b/plugins/stack-overflow-backend/README.md @@ -19,7 +19,7 @@ stackoverflow: If you have a private Stack Overflow instance and/or a private Stack Overflow Team you will need to supply an API key or Personal Access Token. You can read more about how to set this up by going to [Stack Overflow's Help Page](https://stackoverflow.help/en/articles/4385859-stack-overflow-for-teams-api). -The existing API key approach remains the default, to support the new v2.3 API and PAT authentication model you need to pass the team name and the new PAT into the existing apiAccessToken parameter. See [15770](https://github.com/backstage/backstage/issues/15770) for more details. +The existing API key approach remains the default, to support the new v2.3 API and PAT authentication model you need to pass the team name and the new PAT into the existing apiAccessToken parameter to the new URL. See [15770](https://github.com/backstage/backstage/issues/15770) for more details. ```yaml stackoverflow: @@ -30,7 +30,7 @@ stackoverflow: ```yaml stackoverflow: - baseUrl: https://api.stackexchange.com/2.3 # alternative: your internal stack overflow instance + baseUrl: https://api.stackoverflowteams.com/2.3 # alternative: your internal stack overflow instance teamName: $STACK_OVERFLOW_TEAM_NAME apiAccessToken: $STACK_OVERFLOW_API_ACCESS_TOKEN ```