diff --git a/.changeset/sweet-fishes-taste.md b/.changeset/sweet-fishes-taste.md new file mode 100644 index 0000000000..8d1fcfc790 --- /dev/null +++ b/.changeset/sweet-fishes-taste.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-github-pull-requests-board': patch +--- + +Add optional `pullRequestLimit` prop to `EntityTeamPullRequestsCard` and `EntityTeamPullRequestsContent` to limit the number of PRs shown per repository. Excluding this prop will default the number of pull requests shown to 10 per repository (the existing functionality). diff --git a/plugins/github-pull-requests-board/api-report.md b/plugins/github-pull-requests-board/api-report.md index 1657d455b5..81097118af 100644 --- a/plugins/github-pull-requests-board/api-report.md +++ b/plugins/github-pull-requests-board/api-report.md @@ -5,13 +5,27 @@ ```ts /// -import { FunctionComponent } from 'react'; +// @public (undocumented) +export const EntityTeamPullRequestsCard: ( + props: EntityTeamPullRequestsCardProps, +) => JSX.Element; // @public (undocumented) -export const EntityTeamPullRequestsCard: FunctionComponent<{}>; +export interface EntityTeamPullRequestsCardProps { + // (undocumented) + pullRequestLimit?: number; +} // @public (undocumented) -export const EntityTeamPullRequestsContent: FunctionComponent<{}>; +export const EntityTeamPullRequestsContent: ( + props: EntityTeamPullRequestsContentProps, +) => JSX.Element; + +// @public (undocumented) +export interface EntityTeamPullRequestsContentProps { + // (undocumented) + pullRequestLimit?: number; +} // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/github-pull-requests-board/package.json b/plugins/github-pull-requests-board/package.json index 1b57aaddcf..b5bcc182cd 100644 --- a/plugins/github-pull-requests-board/package.json +++ b/plugins/github-pull-requests-board/package.json @@ -57,11 +57,11 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", + "@types/react": "^16.13.1 || ^17.0.0", "cross-fetch": "^3.1.5", "msw": "^0.45.0" }, "peerDependencies": { - "@types/react": "^16.13.1 || ^17.0.0", "react": "^16.13.1 || ^17.0.0", "react-dom": "^16.13.1 || ^17.0.0" }, diff --git a/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts index bd5df89833..2295a01442 100644 --- a/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts +++ b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts @@ -18,37 +18,86 @@ import React from 'react'; import { GraphQlPullRequests, PullRequestsNumber } from '../utils/types'; import { useOctokitGraphQl } from './useOctokitGraphQl'; +const PULL_REQUEST_LIMIT = 10; +const GITHUB_GRAPHQL_MAX_ITEMS = 100; + export const useGetPullRequestsFromRepository = () => { const graphql = useOctokitGraphQl>(); const fn = React.useRef( - async (repo: string): Promise => { + async ( + repo: string, + pullRequestLimit?: number, + ): Promise => { + const limit = pullRequestLimit ?? PULL_REQUEST_LIMIT; const [organisation, repositoryName] = repo.split('/'); - const { repository } = await graphql( - ` - query ($name: String!, $owner: String!) { - repository(name: $name, owner: $owner) { - pullRequests(states: OPEN, first: 10) { - edges { - node { - number - } - } - } - } - } - `, - { - name: repositoryName, - owner: organisation, - }, + return await getPullRequestEdges( + graphql, + repositoryName, + organisation, + limit, ); - - return repository.pullRequests.edges; }, ); return fn.current; }; + +async function getPullRequestEdges( + graphql: ( + path: string, + options?: any, + ) => Promise>, + repositoryName: string, + organisation: string, + pullRequestLimit: number, +): Promise { + const pullRequestEdges: PullRequestsNumber[] = []; + let result: GraphQlPullRequests | undefined = undefined; + + do { + result = await graphql( + ` + query ( + $name: String! + $owner: String! + $first: Int + $endCursor: String + ) { + repository(name: $name, owner: $owner) { + pullRequests(states: OPEN, first: $first, after: $endCursor) { + edges { + node { + number + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + } + `, + { + name: repositoryName, + owner: organisation, + first: + pullRequestLimit > GITHUB_GRAPHQL_MAX_ITEMS + ? GITHUB_GRAPHQL_MAX_ITEMS + : pullRequestLimit, + endCursor: result + ? result.repository.pullRequests.pageInfo.endCursor + : undefined, + }, + ); + + pullRequestEdges.push(...result.repository.pullRequests.edges); + + if (pullRequestEdges.length >= pullRequestLimit) return pullRequestEdges; + } while (result.repository.pullRequests.pageInfo.hasNextPage); + + return pullRequestEdges; +} diff --git a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx index b19651536a..2ae33c6b07 100644 --- a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx +++ b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { FunctionComponent, useState } from 'react'; +import React, { useState } from 'react'; import { Grid, Typography } from '@material-ui/core'; import FullscreenIcon from '@material-ui/icons/Fullscreen'; @@ -28,11 +28,19 @@ import { PRCardFormating } from '../../utils/types'; import { DraftPrIcon } from '../icons/DraftPr'; import { useUserRepositories } from '../../hooks/useUserRepositories'; -const EntityTeamPullRequestsCard: FunctionComponent = () => { +/** @public */ +export interface EntityTeamPullRequestsCardProps { + pullRequestLimit?: number; +} + +const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => { + const { pullRequestLimit } = props; const [infoCardFormat, setInfoCardFormat] = useState([]); const { repositories } = useUserRepositories(); - const { loading, pullRequests, refreshPullRequests } = - usePullRequestsByTeam(repositories); + const { loading, pullRequests, refreshPullRequests } = usePullRequestsByTeam( + repositories, + pullRequestLimit, + ); const header = ( diff --git a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/index.ts b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/index.ts index bac16f1424..5c82402f7f 100644 --- a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/index.ts +++ b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export { default as EntityTeamPullRequestsCard } from './EntityTeamPullRequestsCard'; +export type { EntityTeamPullRequestsCardProps } from './EntityTeamPullRequestsCard'; diff --git a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/EntityTeamPullRequestsContent.tsx b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/EntityTeamPullRequestsContent.tsx index 33980e2364..de1dda2fb8 100644 --- a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/EntityTeamPullRequestsContent.tsx +++ b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/EntityTeamPullRequestsContent.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { FunctionComponent, useState } from 'react'; +import React, { useState } from 'react'; import { Grid, Typography } from '@material-ui/core'; import { Progress, InfoCard } from '@backstage/core-components'; @@ -26,11 +26,21 @@ import { PRCardFormating } from '../../utils/types'; import { DraftPrIcon } from '../icons/DraftPr'; import { useUserRepositories } from '../../hooks/useUserRepositories'; -const EntityTeamPullRequestsContent: FunctionComponent = () => { +/** @public */ +export interface EntityTeamPullRequestsContentProps { + pullRequestLimit?: number; +} + +const EntityTeamPullRequestsContent = ( + props: EntityTeamPullRequestsContentProps, +) => { + const { pullRequestLimit } = props; const [infoCardFormat, setInfoCardFormat] = useState([]); const { repositories } = useUserRepositories(); - const { loading, pullRequests, refreshPullRequests } = - usePullRequestsByTeam(repositories); + const { loading, pullRequests, refreshPullRequests } = usePullRequestsByTeam( + repositories, + pullRequestLimit, + ); const header = ( diff --git a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/index.ts b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/index.ts index c2be57464c..032e698a21 100644 --- a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/index.ts +++ b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export { default as EntityTeamPullRequestsContent } from './EntityTeamPullRequestsContent'; +export type { EntityTeamPullRequestsContentProps } from './EntityTeamPullRequestsContent'; diff --git a/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx b/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx index 234a0baefd..2e6553f778 100644 --- a/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx +++ b/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx @@ -19,7 +19,10 @@ import { PullRequests, PullRequestsColumn } from '../utils/types'; import { useGetPullRequestsFromRepository } from '../api/useGetPullRequestsFromRepository'; import { useGetPullRequestDetails } from '../api/useGetPullRequestDetails'; -export function usePullRequestsByTeam(repositories: string[]) { +export function usePullRequestsByTeam( + repositories: string[], + pullRequestLimit?: number, +) { const [pullRequests, setPullRequests] = useState([]); const [loading, setLoading] = useState(true); const getPullRequests = useGetPullRequestsFromRepository(); @@ -27,7 +30,10 @@ export function usePullRequestsByTeam(repositories: string[]) { const getPRsPerRepository = useCallback( async (repository: string): Promise => { - const pullRequestsNumbers = await getPullRequests(repository); + const pullRequestsNumbers = await getPullRequests( + repository, + pullRequestLimit, + ); const pullRequestsWithDetails = await Promise.all( pullRequestsNumbers.map(({ node }) => @@ -37,7 +43,7 @@ export function usePullRequestsByTeam(repositories: string[]) { return pullRequestsWithDetails; }, - [getPullRequests, getPullRequestDetails], + [getPullRequests, getPullRequestDetails, pullRequestLimit], ); const getPRsFromTeam = useCallback( diff --git a/plugins/github-pull-requests-board/src/index.ts b/plugins/github-pull-requests-board/src/index.ts index d9cffaa468..c93c1732b8 100644 --- a/plugins/github-pull-requests-board/src/index.ts +++ b/plugins/github-pull-requests-board/src/index.ts @@ -17,3 +17,5 @@ export { EntityTeamPullRequestsCard, EntityTeamPullRequestsContent, } from './plugin'; +export type { EntityTeamPullRequestsCardProps } from './components/EntityTeamPullRequestsCard'; +export type { EntityTeamPullRequestsContentProps } from './components/EntityTeamPullRequestsContent'; diff --git a/plugins/github-pull-requests-board/src/utils/types.tsx b/plugins/github-pull-requests-board/src/utils/types.tsx index a2b121a1b8..a1e2dbfda3 100644 --- a/plugins/github-pull-requests-board/src/utils/types.tsx +++ b/plugins/github-pull-requests-board/src/utils/types.tsx @@ -23,6 +23,10 @@ export type GraphQlPullRequests = { repository: { pullRequests: { edges: T; + pageInfo: { + hasNextPage: boolean; + endCursor?: string; + }; }; }; };