add stackoverflow api implementation

Signed-off-by: Brian Fletcher <brian@roadie.io>
This commit is contained in:
Brian Fletcher
2022-08-22 20:55:16 +01:00
parent dd9025fe0f
commit 7107d2907c
6 changed files with 195 additions and 10 deletions
@@ -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,74 @@
/*
* 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';
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 = new StackOverflowClient({
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,46 @@
/*
* 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 { StackOverflowApi } from './StackOverflowApi';
import {
StackOverflowQuestion,
StackOverflowQuestionsRequestParams,
} from '../types';
import qs from 'qs';
export class StackOverflowClient implements StackOverflowApi {
private baseUrl: string;
constructor({ baseUrl }: { baseUrl: string }) {
this.baseUrl = baseUrl;
}
/**
* 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,
@@ -26,12 +26,12 @@ import {
} from '@material-ui/core';
import OpenInNewIcon from '@material-ui/icons/OpenInNew';
import useAsync from 'react-use/lib/useAsync';
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.
@@ -41,18 +41,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) {
+15
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,18 @@ import { StackOverflowQuestionsContentProps } from './types';
*/
export const stackOverflowPlugin = createPlugin({
id: 'stack-overflow',
apis: [
createApiFactory({
api: stackOverflowApiRef,
deps: { configApi: configApiRef },
factory: ({ configApi }) =>
new StackOverflowClient({
baseUrl:
configApi.getOptionalString('stackoverflow.baseUrl') ||
'https://api.stackexchange.com/2.2',
}),
}),
],
});
/**