From cc8bfc56c36bfad880109924792efa77e49aa664 Mon Sep 17 00:00:00 2001 From: Simon Ninon Date: Sun, 9 Oct 2022 20:08:14 -0700 Subject: [PATCH] plugin-github-pull-requests-board: support team members pull requests Signed-off-by: Simon Ninon --- .changeset/nasty-crabs-share.md | 8 ++ .../github-pull-requests-board/package.json | 1 + .../src/api/useGetPullRequestDetails.ts | 3 + .../api/useGetPullRequestsFromRepository.ts | 18 ++- .../src/api/useGetPullRequestsFromUser.ts | 108 ++++++++++++++++++ .../EntityTeamPullRequestsCard.tsx | 37 +++++- .../EntityTeamPullRequestsContent.tsx | 35 +++++- .../src/components/icons/DraftPr/DraftPr.tsx | 12 +- .../src/hooks/usePullRequestsByTeam.tsx | 79 ++++++++++--- .../src/hooks/useUserRepositories.tsx | 47 -------- .../src/hooks/useUserRepositoriesAndTeam.tsx | 81 +++++++++++++ .../src/utils/functions.ts | 49 +++++++- .../src/utils/types.tsx | 46 +++++--- yarn.lock | 1 + 14 files changed, 426 insertions(+), 99 deletions(-) create mode 100644 .changeset/nasty-crabs-share.md create mode 100644 plugins/github-pull-requests-board/src/api/useGetPullRequestsFromUser.ts delete mode 100644 plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx create mode 100644 plugins/github-pull-requests-board/src/hooks/useUserRepositoriesAndTeam.tsx diff --git a/.changeset/nasty-crabs-share.md b/.changeset/nasty-crabs-share.md new file mode 100644 index 0000000000..d09eb09044 --- /dev/null +++ b/.changeset/nasty-crabs-share.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-github-pull-requests-board': patch +--- + +Add a new "Team" Filter Options to the Github Pull Requests Dashboard. + +When toggling this option on, the dashboard will displays all of the PRs opened +by the members of that team on any repositories of the organization. diff --git a/plugins/github-pull-requests-board/package.json b/plugins/github-pull-requests-board/package.json index e4e9d0827a..e68e6b1377 100644 --- a/plugins/github-pull-requests-board/package.json +++ b/plugins/github-pull-requests-board/package.json @@ -45,6 +45,7 @@ "@material-ui/lab": "4.0.0-alpha.57", "@octokit/rest": "^19.0.3", "luxon": "^3.0.0", + "p-limit": "^4.0.0", "react-use": "^17.2.4" }, "devDependencies": { diff --git a/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts b/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts index d44c36b54d..3f818c63fc 100644 --- a/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts +++ b/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts @@ -33,6 +33,9 @@ export const useGetPullRequestDetails = () => { id repository { name + owner { + login + } } title url diff --git a/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts index 2295a01442..e1daf491cc 100644 --- a/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts +++ b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts @@ -33,7 +33,7 @@ export const useGetPullRequestsFromRepository = () => { const limit = pullRequestLimit ?? PULL_REQUEST_LIMIT; const [organisation, repositoryName] = repo.split('/'); - return await getPullRequestEdges( + return await getPullRequestNodes( graphql, repositoryName, organisation, @@ -45,7 +45,7 @@ export const useGetPullRequestsFromRepository = () => { return fn.current; }; -async function getPullRequestEdges( +async function getPullRequestNodes( graphql: ( path: string, options?: any, @@ -54,7 +54,7 @@ async function getPullRequestEdges( organisation: string, pullRequestLimit: number, ): Promise { - const pullRequestEdges: PullRequestsNumber[] = []; + const pullRequestNodes: PullRequestsNumber[] = []; let result: GraphQlPullRequests | undefined = undefined; do { @@ -68,10 +68,8 @@ async function getPullRequestEdges( ) { repository(name: $name, owner: $owner) { pullRequests(states: OPEN, first: $first, after: $endCursor) { - edges { - node { - number - } + nodes { + number } pageInfo { hasNextPage @@ -94,10 +92,10 @@ async function getPullRequestEdges( }, ); - pullRequestEdges.push(...result.repository.pullRequests.edges); + pullRequestNodes.push(...result.repository.pullRequests.nodes); - if (pullRequestEdges.length >= pullRequestLimit) return pullRequestEdges; + if (pullRequestNodes.length >= pullRequestLimit) return pullRequestNodes; } while (result.repository.pullRequests.pageInfo.hasNextPage); - return pullRequestEdges; + return pullRequestNodes; } diff --git a/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromUser.ts b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromUser.ts new file mode 100644 index 0000000000..fb4fb66bd3 --- /dev/null +++ b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromUser.ts @@ -0,0 +1,108 @@ +/* + * 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 React from 'react'; + +import { + GraphQlUserPullRequests, + PullRequestsNumberAndOwner, +} from '../utils/types'; +import { useOctokitGraphQl } from './useOctokitGraphQl'; + +const PULL_REQUEST_LIMIT = 10; +const GITHUB_GRAPHQL_MAX_ITEMS = 100; + +export const useGetPullRequestsFromUser = () => { + const graphql = + useOctokitGraphQl>(); + + const fn = React.useRef( + async ( + userLogin: string, + organization?: string, + pullRequestLimit?: number, + ): Promise => { + const limit = pullRequestLimit ?? PULL_REQUEST_LIMIT; + + return await getPullRequestNodes(graphql, userLogin, limit, organization); + }, + ); + + return fn.current; +}; + +async function getPullRequestNodes( + graphql: ( + path: string, + options?: any, + ) => Promise>, + userLogin: string, + pullRequestLimit: number, + organization?: string, +): Promise { + const pullRequestNodes: PullRequestsNumberAndOwner[] = []; + let result: + | GraphQlUserPullRequests + | undefined = undefined; + + do { + result = await graphql( + ` + query ($login: String!, $first: Int, $endCursor: String) { + user(login: $login) { + pullRequests(states: OPEN, first: $first, after: $endCursor) { + nodes { + number + repository { + name + owner { + login + } + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + } + `, + { + login: userLogin, + first: + pullRequestLimit > GITHUB_GRAPHQL_MAX_ITEMS + ? GITHUB_GRAPHQL_MAX_ITEMS + : pullRequestLimit, + endCursor: result + ? result.user.pullRequests.pageInfo.endCursor + : undefined, + }, + ); + + pullRequestNodes.push( + ...result.user.pullRequests.nodes.filter( + edge => + !organization || + edge?.repository?.owner?.login?.toLocaleLowerCase('en-US') === + organization.toLocaleLowerCase('en-US'), + ), + ); + + if (pullRequestNodes.length >= pullRequestLimit) return pullRequestNodes; + } while (result.user.pullRequests.pageInfo.hasNextPage); + + return pullRequestNodes; +} 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 2ae33c6b07..95d7745c62 100644 --- a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx +++ b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx @@ -16,6 +16,7 @@ import React, { useState } from 'react'; import { Grid, Typography } from '@material-ui/core'; import FullscreenIcon from '@material-ui/icons/Fullscreen'; +import PeopleIcon from '@material-ui/icons/People'; import { Progress, InfoCard } from '@backstage/core-components'; @@ -25,8 +26,9 @@ import { Wrapper } from '../Wrapper'; import { PullRequestCard } from '../PullRequestCard'; import { usePullRequestsByTeam } from '../../hooks/usePullRequestsByTeam'; import { PRCardFormating } from '../../utils/types'; +import { shouldDisplayCard } from '../../utils/functions'; import { DraftPrIcon } from '../icons/DraftPr'; -import { useUserRepositories } from '../../hooks/useUserRepositories'; +import { useUserRepositoriesAndTeam } from '../../hooks/useUserRepositoriesAndTeam'; /** @public */ export interface EntityTeamPullRequestsCardProps { @@ -36,9 +38,20 @@ export interface EntityTeamPullRequestsCardProps { const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => { const { pullRequestLimit } = props; const [infoCardFormat, setInfoCardFormat] = useState([]); - const { repositories } = useUserRepositories(); - const { loading, pullRequests, refreshPullRequests } = usePullRequestsByTeam( + const { + loading: loadingReposAndTeam, repositories, + teamMembers, + teamMembersOrganization, + } = useUserRepositoriesAndTeam(); + const { + loading: loadingPRs, + pullRequests, + refreshPullRequests, + } = usePullRequestsByTeam( + repositories, + teamMembers, + teamMembersOrganization, pullRequestLimit, ); @@ -48,6 +61,11 @@ const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => { onClickOption={newFormats => setInfoCardFormat(newFormats)} value={infoCardFormat} options={[ + { + icon: , + value: 'team', + ariaLabel: 'Show PRs from your team', + }, { icon: , value: 'draft', @@ -56,7 +74,7 @@ const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => { { icon: , value: 'fullscreen', - ariaLabel: 'Info card is set to fullscreen', + ariaLabel: 'Set card to fullscreen', }, ]} /> @@ -64,7 +82,7 @@ const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => { ); const getContent = () => { - if (loading) { + if (loadingReposAndTeam || loadingPRs) { return ; } @@ -92,7 +110,14 @@ const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => { }, index, ) => - infoCardFormat.includes('draft') === isDraft && ( + shouldDisplayCard( + repository, + author, + repositories, + teamMembers, + infoCardFormat, + isDraft, + ) && ( { const { pullRequestLimit } = props; const [infoCardFormat, setInfoCardFormat] = useState([]); - const { repositories } = useUserRepositories(); - const { loading, pullRequests, refreshPullRequests } = usePullRequestsByTeam( + const { + loading: loadingReposAndTeam, repositories, + teamMembers, + teamMembersOrganization, + } = useUserRepositoriesAndTeam(); + const { + loading: loadingPRs, + pullRequests, + refreshPullRequests, + } = usePullRequestsByTeam( + repositories, + teamMembers, + teamMembersOrganization, pullRequestLimit, ); @@ -48,6 +61,11 @@ const EntityTeamPullRequestsContent = ( onClickOption={newFormats => setInfoCardFormat(newFormats)} value={infoCardFormat} options={[ + { + icon: , + value: 'team', + ariaLabel: 'Show PRs from your team', + }, { icon: , value: 'draft', @@ -59,7 +77,7 @@ const EntityTeamPullRequestsContent = ( ); const getContent = () => { - if (loading) { + if (loadingReposAndTeam || loadingPRs) { return ; } @@ -84,7 +102,14 @@ const EntityTeamPullRequestsContent = ( }, index, ) => - infoCardFormat.includes('draft') === isDraft && ( + shouldDisplayCard( + repository, + author, + repositories, + teamMembers, + infoCardFormat, + isDraft, + ) && ( ( width="16" data-view-component="true" > - - + + + + ); diff --git a/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx b/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx index 2e6553f778..de36d806ff 100644 --- a/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx +++ b/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx @@ -13,48 +13,93 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import pLimit from 'p-limit'; import { useCallback, useEffect, useState } from 'react'; import { formatPRsByReviewDecision } from '../utils/functions'; import { PullRequests, PullRequestsColumn } from '../utils/types'; import { useGetPullRequestsFromRepository } from '../api/useGetPullRequestsFromRepository'; +import { useGetPullRequestsFromUser } from '../api/useGetPullRequestsFromUser'; import { useGetPullRequestDetails } from '../api/useGetPullRequestDetails'; export function usePullRequestsByTeam( repositories: string[], + members: string[], + organization?: string, pullRequestLimit?: number, ) { const [pullRequests, setPullRequests] = useState([]); const [loading, setLoading] = useState(true); - const getPullRequests = useGetPullRequestsFromRepository(); + const getPullRequestsFromRepository = useGetPullRequestsFromRepository(); + const getPullRequestsFromUser = useGetPullRequestsFromUser(); const getPullRequestDetails = useGetPullRequestDetails(); const getPRsPerRepository = useCallback( async (repository: string): Promise => { - const pullRequestsNumbers = await getPullRequests( + const concurrencyLimit = pLimit(5); + const pullRequestsNumbers = await getPullRequestsFromRepository( repository, pullRequestLimit, ); const pullRequestsWithDetails = await Promise.all( - pullRequestsNumbers.map(({ node }) => - getPullRequestDetails(repository, node.number), + pullRequestsNumbers.map(node => + concurrencyLimit(() => + getPullRequestDetails(repository, node.number), + ), ), ); return pullRequestsWithDetails; }, - [getPullRequests, getPullRequestDetails, pullRequestLimit], + [getPullRequestsFromRepository, getPullRequestDetails, pullRequestLimit], + ); + + const getPRsPerTeamMember = useCallback( + async ( + teamMember: string, + teamOrganization?: string, + ): Promise => { + const concurrencyLimit = pLimit(3); + const pullRequestsNumbers = await getPullRequestsFromUser( + teamMember, + teamOrganization, + pullRequestLimit, + ); + + const pullRequestsWithDetails = await Promise.all( + pullRequestsNumbers.map(node => + concurrencyLimit(() => + getPullRequestDetails( + `${node.repository.owner.login}/${node.repository.name}`, + node.number, + ), + ), + ), + ); + + return pullRequestsWithDetails; + }, + [getPullRequestsFromUser, getPullRequestDetails, pullRequestLimit], ); const getPRsFromTeam = useCallback( - async (teamRepositories: string[]): Promise => { + async ( + teamRepositories: string[], + teamMembers: string[], + teamOrganization?: string, + ): Promise => { const teamRepositoriesPromises = teamRepositories.map(repository => getPRsPerRepository(repository), ); - const teamPullRequests = await Promise.allSettled( - teamRepositoriesPromises, - ).then(promises => + const teamMembersPromises = teamMembers.map(teamMember => + getPRsPerTeamMember(teamMember, teamOrganization), + ); + + const teamPullRequests = await Promise.allSettled([ + ...teamRepositoriesPromises, + ...teamMembersPromises, + ]).then(promises => promises.reduce((acc, curr) => { if (curr.status === 'fulfilled') { return [...acc, ...curr.value]; @@ -63,18 +108,26 @@ export function usePullRequestsByTeam( }, [] as PullRequests), ); - return teamPullRequests; + const uniqueTeamPullRequests = teamPullRequests.filter( + (lhs, i) => teamPullRequests.findIndex(rhs => lhs.id === rhs.id) === i, + ); + + return uniqueTeamPullRequests; }, - [getPRsPerRepository], + [getPRsPerRepository, getPRsPerTeamMember], ); const getAllPullRequests = useCallback(async () => { setLoading(true); - const teamPullRequests = await getPRsFromTeam(repositories); + const teamPullRequests = await getPRsFromTeam( + repositories, + members, + organization, + ); setPullRequests(formatPRsByReviewDecision(teamPullRequests)); setLoading(false); - }, [getPRsFromTeam, repositories]); + }, [getPRsFromTeam, repositories, members, organization]); useEffect(() => { getAllPullRequests(); diff --git a/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx b/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx deleted file mode 100644 index ed0d3580f0..0000000000 --- a/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx +++ /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 { stringifyEntityRef } from '@backstage/catalog-model'; -import { useApi } from '@backstage/core-plugin-api'; -import { catalogApiRef, useEntity } from '@backstage/plugin-catalog-react'; -import { useCallback, useEffect, useState } from 'react'; -import { getProjectNameFromEntity } from '../utils/functions'; - -export function useUserRepositories() { - const { entity: teamEntity } = useEntity(); - const catalogApi = useApi(catalogApiRef); - const [repositories, setRepositories] = useState([]); - - const getRepositoriesNames = useCallback(async () => { - const entitiesList = await catalogApi.getEntities({ - filter: { 'relations.ownedBy': stringifyEntityRef(teamEntity) }, - }); - - const entitiesNames: string[] = entitiesList.items.map(componentEntity => - getProjectNameFromEntity(componentEntity), - ); - - setRepositories([...new Set(entitiesNames)]); - }, [catalogApi, teamEntity]); - - useEffect(() => { - getRepositoriesNames(); - }, [getRepositoriesNames]); - - return { - repositories, - }; -} diff --git a/plugins/github-pull-requests-board/src/hooks/useUserRepositoriesAndTeam.tsx b/plugins/github-pull-requests-board/src/hooks/useUserRepositoriesAndTeam.tsx new file mode 100644 index 0000000000..162dadcfbf --- /dev/null +++ b/plugins/github-pull-requests-board/src/hooks/useUserRepositoriesAndTeam.tsx @@ -0,0 +1,81 @@ +/* + * 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 { stringifyEntityRef } from '@backstage/catalog-model'; +import { useApi } from '@backstage/core-plugin-api'; +import { catalogApiRef, useEntity } from '@backstage/plugin-catalog-react'; +import { useCallback, useEffect, useState } from 'react'; +import { + getProjectNameFromEntity, + getGithubOrganizationFromEntity, + getUserNameFromEntity, +} from '../utils/functions'; + +export function useUserRepositoriesAndTeam() { + const { entity: teamEntity } = useEntity(); + const catalogApi = useApi(catalogApiRef); + const [loading, setLoading] = useState(true); + const [teamData, setTeamData] = useState<{ + repositories: string[]; + teamMembers: string[]; + }>({ + repositories: [], + teamMembers: [], + }); + + const getTeamData = useCallback(async () => { + setLoading(true); + + // get team repositories and members + const entitiesList = await catalogApi.getEntities({ + filter: [ + { 'relations.ownedBy': stringifyEntityRef(teamEntity) }, + { 'relations.memberOf': stringifyEntityRef(teamEntity) }, + ], + }); + + const repositories = entitiesList.items.filter( + entity => entity.kind === 'Component', + ); + const repositoriesNames: string[] = repositories + .map(componentEntity => getProjectNameFromEntity(componentEntity) ?? '') + .filter(projectName => !!projectName); + + const teamMembers = entitiesList.items.filter( + entity => entity.kind === 'User', + ); + const teamMembersNames: string[] = teamMembers + .map(componentEntity => getUserNameFromEntity(componentEntity) ?? '') + .filter(userName => !!userName); + + setTeamData({ + repositories: [...new Set(repositoriesNames)], + teamMembers: [...new Set(teamMembersNames)], + }); + setLoading(false); + }, [catalogApi, teamEntity]); + + useEffect(() => { + getTeamData(); + }, [getTeamData]); + + return { + loading, + repositories: teamData.repositories, + teamMembers: teamData.teamMembers, + teamMembersOrganization: getGithubOrganizationFromEntity(teamEntity), + }; +} diff --git a/plugins/github-pull-requests-board/src/utils/functions.ts b/plugins/github-pull-requests-board/src/utils/functions.ts index 5644530689..227cad459c 100644 --- a/plugins/github-pull-requests-board/src/utils/functions.ts +++ b/plugins/github-pull-requests-board/src/utils/functions.ts @@ -21,13 +21,29 @@ import { ReviewDecision, PullRequestsColumn, Author, + PRCardFormating, + Repository, } from './types'; import { COLUMNS } from './constants'; const GITHUB_PULL_REQUESTS_ANNOTATION = 'github.com/project-slug'; +const GITHUB_USER_LOGIN_ANNOTATION = 'github.com/user-login'; -export const getProjectNameFromEntity = (entity: Entity): string => { - return entity?.metadata.annotations?.[GITHUB_PULL_REQUESTS_ANNOTATION] ?? ''; +export const getProjectNameFromEntity = ( + entity: Entity, +): string | undefined => { + return entity?.metadata.annotations?.[GITHUB_PULL_REQUESTS_ANNOTATION]; +}; + +export const getUserNameFromEntity = (entity: Entity): string | undefined => { + return entity?.metadata.annotations?.[GITHUB_USER_LOGIN_ANNOTATION]; +}; + +export const getGithubOrganizationFromEntity = (entity: Entity): string => { + return ( + entity?.metadata?.annotations?.['github.com/team-slug']?.split('/')?.[0] ?? + '' + ); }; export const getApprovedReviews = (reviews: Reviews = []): Reviews => { @@ -107,3 +123,32 @@ export const formatPRsByReviewDecision = ( { title: COLUMNS.APPROVED, content: reviewDecisions.APPROVED }, ]; }; + +export const shouldDisplayCard = ( + repository: Repository, + author: Author, + teamRepositories: string[], + teamMembers: string[], + infoCardFormat: PRCardFormating[], + isDraft: boolean, +) => { + // hide draft PRs unless "draft" filter is toggled + if (infoCardFormat.includes('draft') !== isDraft) { + return false; + } + + // when "team" filter is toggled on, only shows PR from team members + if (infoCardFormat.includes('team')) { + return teamMembers.includes(author.login); + } + + const fullRepoName = + `${repository.owner.login}/${repository.name}`.toLocaleLowerCase('en-US'); + + const repositories = teamRepositories.map(repo => + repo.toLocaleLowerCase('en-US'), + ); + + // when "team" filter is toggled off, only shows PR on team repos + return repositories.includes(fullRepoName); +}; diff --git a/plugins/github-pull-requests-board/src/utils/types.tsx b/plugins/github-pull-requests-board/src/utils/types.tsx index a1e2dbfda3..8a9f15b967 100644 --- a/plugins/github-pull-requests-board/src/utils/types.tsx +++ b/plugins/github-pull-requests-board/src/utils/types.tsx @@ -19,22 +19,33 @@ export type GraphQlPullRequest = { }; }; +export type Connection = { + nodes: T; + pageInfo: { + hasNextPage: boolean; + endCursor?: string; + }; +}; + export type GraphQlPullRequests = { repository: { - pullRequests: { - edges: T; - pageInfo: { - hasNextPage: boolean; - endCursor?: string; - }; - }; + pullRequests: Connection; + }; +}; + +export type GraphQlUserPullRequests = { + user: { + pullRequests: Connection; }; }; export type PullRequestsNumber = { - node: { - number: number; - }; + number: number; +}; + +export type PullRequestsNumberAndOwner = { + number: number; + repository: Repository; }; export type Review = { @@ -57,11 +68,16 @@ export type Author = { name: string; }; +export type Repository = { + name: string; + owner: { + login: string; + }; +}; + export type PullRequest = { id: string; - repository: { - name: string; - }; + repository: Repository; title: string; url: string; lastEditedAt: string; @@ -83,6 +99,8 @@ export type PullRequestsColumn = { content: PullRequests; }; -export type PRCardFormating = 'compacted' | 'fullscreen' | 'draft'; +export type PRCardFormating = 'compacted' | 'fullscreen' | 'draft' | 'team'; export type ReviewDecision = 'IN_PROGRESS' | 'APPROVED' | 'REVIEW_REQUIRED'; + +export type PullRequestsSourceType = 'TeamRepositories' | 'TeamMembers'; diff --git a/yarn.lock b/yarn.lock index 00cd26f161..d0335aa767 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5665,6 +5665,7 @@ __metadata: cross-fetch: ^3.1.5 luxon: ^3.0.0 msw: ^0.47.0 + p-limit: ^4.0.0 react-use: ^17.2.4 peerDependencies: react: ^16.13.1 || ^17.0.0