Merge pull request #13294 from RoadieHQ/stack-overflow-api

add stackoverflow api implementation
This commit is contained in:
Emma Indal
2022-08-31 09:55:49 +02:00
committed by GitHub
7 changed files with 209 additions and 10 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-stack-overflow': patch
---
Create a front end API.
@@ -0,0 +1,31 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createApiRef } from '@backstage/core-plugin-api';
import {
StackOverflowQuestion,
StackOverflowQuestionsRequestParams,
} from '../types';
export const stackOverflowApiRef = createApiRef<StackOverflowApi>({
id: 'plugin.stackoverflow.service',
});
export type StackOverflowApi = {
listQuestions(options?: {
requestParams: StackOverflowQuestionsRequestParams;
}): Promise<StackOverflowQuestion[]>;
};
@@ -0,0 +1,79 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { setupRequestMockHandlers } from '@backstage/test-utils';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { StackOverflowClient } from './index';
import { StackOverflowQuestion } from '../types';
import { ConfigReader } from '@backstage/config';
const server = setupServer();
const backstageQuestions: StackOverflowQuestion[] = [
{
title: 'What is it?',
link: 'https://example.com:9191/questions/1',
tags: ['asdf'],
owner: { who: 'me' },
answer_count: 3,
},
{
title: 'Is it?',
link: 'https://example.com:9191/questions/2',
tags: ['asdf'],
owner: { who: 'me' },
answer_count: 4,
},
];
describe('StackOverflowClient', () => {
setupRequestMockHandlers(server);
const mockBaseUrl = 'https://example.com:9191';
const setupHandlers = () => {
server.use(
rest.get(`${mockBaseUrl}/questions`, (req, res, ctx) => {
return res(
ctx.json({
items:
req.url.searchParams.get('tagged') === 'backstage'
? backstageQuestions
: [],
}),
);
}),
);
};
it('list questions should return all questions', async () => {
setupHandlers();
const client = StackOverflowClient.fromConfig(
new ConfigReader({
stackoverflow: {
baseUrl: 'https://example.com:9191',
},
}),
);
const responseQuestions = await client.listQuestions({
requestParams: { tagged: 'backstage' },
});
expect(responseQuestions.length).toEqual(2);
expect(responseQuestions).toEqual(backstageQuestions);
});
});
@@ -0,0 +1,55 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fetch from 'cross-fetch';
import qs from 'qs';
import { StackOverflowApi } from './StackOverflowApi';
import {
StackOverflowQuestion,
StackOverflowQuestionsRequestParams,
} from '../types';
import { Config } from '@backstage/config';
export class StackOverflowClient implements StackOverflowApi {
private baseUrl: string;
private constructor({ baseUrl }: { baseUrl: string }) {
this.baseUrl = baseUrl;
}
static fromConfig(config: Config) {
return new StackOverflowClient({
baseUrl:
config.getOptionalString('stackoverflow.baseUrl') ||
'https://api.stackexchange.com/2.2',
});
}
/**
* List Questions in the StackOverflow instance
*
* */
async listQuestions(options: {
requestParams: StackOverflowQuestionsRequestParams;
}): Promise<StackOverflowQuestion[]> {
const params = qs.stringify(options.requestParams, {
addQueryPrefix: true,
});
const response = await fetch(`${this.baseUrl}/questions${params}`);
const data = await response.json();
return data.items;
}
}
+25
View File
@@ -0,0 +1,25 @@
/*
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* An API definition and client for integrating with the StackOverflow Backend
* API
*
* @packageDocumentation
*/
export * from './StackOverflowApi';
export * from './StackOverflowClient';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { useApi, configApiRef } from '@backstage/core-plugin-api';
import { useApi } from '@backstage/core-plugin-api';
import { Link } from '@backstage/core-components';
import {
IconButton,
@@ -27,12 +27,12 @@ import {
import OpenInNewIcon from '@material-ui/icons/OpenInNew';
import useAsync from 'react-use/lib/useAsync';
import _unescape from 'lodash/unescape';
import qs from 'qs';
import React from 'react';
import {
StackOverflowQuestion,
StackOverflowQuestionsContentProps,
} from '../../types';
import { stackOverflowApiRef } from '../../api';
/**
* A component to display a list of stack overflow questions on the homepage.
@@ -42,18 +42,12 @@ import {
export const Content = (props: StackOverflowQuestionsContentProps) => {
const { requestParams } = props;
const configApi = useApi(configApiRef);
const baseUrl =
configApi.getOptionalString('stackoverflow.baseUrl') ||
'https://api.stackexchange.com/2.2';
const stackOverflowApi = useApi(stackOverflowApiRef);
const { value, loading, error } = useAsync(async (): Promise<
StackOverflowQuestion[]
> => {
const params = qs.stringify(requestParams, { addQueryPrefix: true });
const response = await fetch(`${baseUrl}/questions${params}`);
const data = await response.json();
return data.items;
return await stackOverflowApi.listQuestions({ requestParams });
}, []);
if (loading) {
+10
View File
@@ -17,9 +17,12 @@
import {
createPlugin,
createComponentExtension,
createApiFactory,
configApiRef,
} from '@backstage/core-plugin-api';
import { createCardExtension } from '@backstage/plugin-home';
import { StackOverflowQuestionsContentProps } from './types';
import { stackOverflowApiRef, StackOverflowClient } from './api';
/**
* The Backstage plugin that holds stack overflow specific components
@@ -28,6 +31,13 @@ import { StackOverflowQuestionsContentProps } from './types';
*/
export const stackOverflowPlugin = createPlugin({
id: 'stack-overflow',
apis: [
createApiFactory({
api: stackOverflowApiRef,
deps: { configApi: configApiRef },
factory: ({ configApi }) => StackOverflowClient.fromConfig(configApi),
}),
],
});
/**