From 19eb6499999e1129e943042effa38b1f844fe414 Mon Sep 17 00:00:00 2001 From: Kamil Wolny Date: Tue, 9 Aug 2022 23:39:14 +0100 Subject: [PATCH] feat: hooks refactor to use new plugin api Signed-off-by: Kamil Wolny --- .../useGetIssuesBeRepoFromGitHub.test.tsx | 100 ----------- .../src/hooks/useGetIssuesByRepoFromGitHub.ts | 170 +++--------------- .../src/hooks/useOctokitGraphQL.ts | 47 ----- 3 files changed, 20 insertions(+), 297 deletions(-) delete mode 100644 plugins/github-issues/src/hooks/useGetIssuesBeRepoFromGitHub.test.tsx delete mode 100644 plugins/github-issues/src/hooks/useOctokitGraphQL.ts diff --git a/plugins/github-issues/src/hooks/useGetIssuesBeRepoFromGitHub.test.tsx b/plugins/github-issues/src/hooks/useGetIssuesBeRepoFromGitHub.test.tsx deleted file mode 100644 index 547e32db22..0000000000 --- a/plugins/github-issues/src/hooks/useGetIssuesBeRepoFromGitHub.test.tsx +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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. - */ -const mockGraphQLQuery = jest.fn(() => ({})); -jest.mock('./useOctokitGraphQL', () => ({ - useOctokitGraphQL: jest.fn(() => mockGraphQLQuery), -})); - -import React from 'react'; -import { render } from '@testing-library/react'; -import { useGetIssuesByRepoFromGitHub } from './useGetIssuesByRepoFromGitHub'; - -describe('useGetIssuesBeRepoFromGitHub', () => { - it('should call GitHub API with correct query with fragment for each repo', async () => { - const Helper = () => { - const getIssues = useGetIssuesByRepoFromGitHub(); - - getIssues(['mrwolny/yo-yo', 'mrwolny/yoyo', 'mrwolny/yo.yo'], 10); - - return
; - }; - - render(); - - expect(mockGraphQLQuery).toHaveBeenCalledTimes(1); - expect(mockGraphQLQuery).toHaveBeenCalledWith( - '\n' + - ' \n' + - ' fragment issues on Repository {\n' + - ' issues(\n' + - ' states: OPEN\n' + - ' first: 10\n' + - ' orderBy: { field: UPDATED_AT, direction: DESC }\n' + - ' ) {\n' + - ' totalCount\n' + - ' edges {\n' + - ' node {\n' + - ' assignees(first: 10) {\n' + - ' edges {\n' + - ' node {\n' + - ' avatarUrl\n' + - ' login\n' + - ' }\n' + - ' }\n' + - ' }\n' + - ' author {\n' + - ' login\n' + - ' avatarUrl\n' + - ' url\n' + - ' }\n' + - ' repository {\n' + - ' nameWithOwner\n' + - ' }\n' + - ' title\n' + - ' url\n' + - ' participants {\n' + - ' totalCount\n' + - ' }\n' + - ' updatedAt\n' + - ' createdAt\n' + - ' comments(last: 1) {\n' + - ' totalCount\n' + - ' }\n' + - ' }\n' + - ' }\n' + - ' }\n' + - ' }\n' + - ' \n' + - '\n' + - ' query {\n' + - ' \n' + - ' yoyo: repository(name: "yo-yo", owner: "mrwolny") {\n' + - ' ...issues\n' + - ' }\n' + - ' ,\n' + - ' yoyox: repository(name: "yoyo", owner: "mrwolny") {\n' + - ' ...issues\n' + - ' }\n' + - ' ,\n' + - ' yoyoxx: repository(name: "yo.yo", owner: "mrwolny") {\n' + - ' ...issues\n' + - ' }\n' + - ' \n' + - ' } \n' + - ' ', - ); - }); -}); diff --git a/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts b/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts index 271cd6c741..29e89213dc 100644 --- a/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts +++ b/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts @@ -13,161 +13,31 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { useOctokitGraphQL } from './useOctokitGraphQL'; +import { useApi } from '@backstage/core-plugin-api'; -type Assignee = { - avatarUrl: string; - login: string; -}; +import { useAsyncRetry } from 'react-use'; +import { gitHubIssuesApiRef } from '../api'; -export type EdgesWithNodes = { - edges: Array<{ - node: T; - }>; -}; - -export type Node = { - node: T; -}; - -type IssueAuthor = { - login: string; -}; - -export type Issue = { - assignees: EdgesWithNodes; - author: IssueAuthor; - repository: { - nameWithOwner: string; - }; - title: string; - url: string; - participants: { - totalCount: number; - }; - createdAt: string; - updatedAt: string; - comments: { - totalCount: number; - }; -}; - -export type RepoIssues = { - issues: { - totalCount: number; - } & EdgesWithNodes; -}; - -export type RepoIssuesQueryResults = Record; - -const createQuery = ( - repositories: Array<{ - safeName: string; - name: string; - owner: string; - }>, +export const useGetIssuesByRepoFromGitHub = ( + repos: Array, itemsPerRepo: number, -): string => { - const fragment = ` - fragment issues on Repository { - issues( - states: OPEN - first: ${itemsPerRepo} - orderBy: { field: UPDATED_AT, direction: DESC } - ) { - totalCount - edges { - node { - assignees(first: 10) { - edges { - node { - avatarUrl - login - } - } - } - author { - login - avatarUrl - url - } - repository { - nameWithOwner - } - title - url - participants { - totalCount - } - updatedAt - createdAt - comments(last: 1) { - totalCount - } - } - } - } - } - `; +) => { + const gitHubIssuesApi = useApi(gitHubIssuesApiRef); - const query = ` - ${fragment} - - query { - ${repositories.map( - ({ safeName, name, owner }) => ` - ${safeName}: repository(name: "${name}", owner: "${owner}") { - ...issues - } - `, - )} - } - `; - - return query; -}; - -export const useGetIssuesByRepoFromGitHub = () => { - const graphql = useOctokitGraphQL(); - - const fn = React.useRef( - async ( - repos: Array, - itemsPerRepo: number, - ): Promise> => { - const safeNames: Array = []; - - const repositories = repos.map(repo => { - const [owner, name] = repo.split('/'); - - const safeNameRegex = /-|\./gi; - let safeName = name.replace(safeNameRegex, ''); - - while (safeNames.includes(safeName)) { - safeName += 'x'; - } - - safeNames.push(safeName); - - return { - safeName, - name, - owner, - }; - }); - - const issuesByRepo: RepoIssuesQueryResults = await graphql( - createQuery(repositories, itemsPerRepo), + const { + value: issues, + loading: isLoading, + retry, + } = useAsyncRetry(async () => { + if (repos.length > 0) { + return await gitHubIssuesApi.fetchIssuesByRepoFromGitHub( + repos, + itemsPerRepo, ); + } - return repositories.reduce((acc, { safeName, name, owner }) => { - acc[`${owner}/${name}`] = issuesByRepo[safeName]; + return {}; + }, [repos]); - return acc; - }, {} as Record); - }, - ); - - return fn.current; + return { isLoading, gitHubIssuesByRepo: issues, retry }; }; diff --git a/plugins/github-issues/src/hooks/useOctokitGraphQL.ts b/plugins/github-issues/src/hooks/useOctokitGraphQL.ts deleted file mode 100644 index 6b783efd6f..0000000000 --- a/plugins/github-issues/src/hooks/useOctokitGraphQL.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 { Octokit } from 'octokit'; -import { - useApi, - githubAuthApiRef, - configApiRef, -} from '@backstage/core-plugin-api'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; - -let octokit: Octokit; - -export const useOctokitGraphQL = () => { - const auth = useApi(githubAuthApiRef); - const config = useApi(configApiRef); - - const baseUrl = readGitHubIntegrationConfigs( - config.getOptionalConfigArray('integrations.github') ?? [], - )[0].apiBaseUrl; - - return (path: string, options?: any): Promise => - auth - .getAccessToken(['repo']) - .then((token: string) => { - if (!octokit) { - octokit = new Octokit({ auth: token, ...(baseUrl && { baseUrl }) }); - } - - return octokit; - }) - .then(octokitInstance => { - return octokitInstance.graphql(path, options); - }); -};