From a35b6459d50b3979509365c532a51932f7c53ca9 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Thu, 21 Oct 2021 21:53:51 +0100 Subject: [PATCH 01/25] feat: Created `AzurePullRequestsIcon` component. Signed-off-by: Marley Powell --- .../AzurePullRequestsIcon.tsx | 28 +++++++++++++++++++ .../components/AzurePullRequestsIcon/index.ts | 17 +++++++++++ 2 files changed, 45 insertions(+) create mode 100644 plugins/azure-devops/src/components/AzurePullRequestsIcon/AzurePullRequestsIcon.tsx create mode 100644 plugins/azure-devops/src/components/AzurePullRequestsIcon/index.ts diff --git a/plugins/azure-devops/src/components/AzurePullRequestsIcon/AzurePullRequestsIcon.tsx b/plugins/azure-devops/src/components/AzurePullRequestsIcon/AzurePullRequestsIcon.tsx new file mode 100644 index 0000000000..3159556788 --- /dev/null +++ b/plugins/azure-devops/src/components/AzurePullRequestsIcon/AzurePullRequestsIcon.tsx @@ -0,0 +1,28 @@ +/* + * 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 { SvgIcon, SvgIconProps } from '@material-ui/core'; + +import React from 'react'; + +export const AzurePullRequestsIcon = (props: SvgIconProps) => ( + + + +); diff --git a/plugins/azure-devops/src/components/AzurePullRequestsIcon/index.ts b/plugins/azure-devops/src/components/AzurePullRequestsIcon/index.ts new file mode 100644 index 0000000000..1f49119b4b --- /dev/null +++ b/plugins/azure-devops/src/components/AzurePullRequestsIcon/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { AzurePullRequestsIcon } from './AzurePullRequestsIcon'; From 94d942a14720cdbb3faad13e9ec26e236b61d691 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Mon, 8 Nov 2021 09:52:13 +0000 Subject: [PATCH 02/25] feat: Added Azure Pull Requests route and sidebar link. Signed-off-by: Marley Powell --- packages/app/src/App.tsx | 2 ++ packages/app/src/components/Root/Root.tsx | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 109ae7922e..c152b5c982 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -33,6 +33,7 @@ import { SignInPage, } from '@backstage/core-components'; import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs'; +import { AzurePullRequestsPage } from '@backstage/plugin-azure-devops'; import { CatalogEntityPage, CatalogIndexPage, @@ -203,6 +204,7 @@ const routes = ( element={} /> } /> + } /> ); diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 7a666a020a..43c057e553 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -41,6 +41,7 @@ import { SidebarSpace, SidebarScrollWrapper, } from '@backstage/core-components'; +import { AzurePullRequestsIcon } from '@backstage/plugin-azure-devops'; const useSidebarLogoStyles = makeStyles({ root: { @@ -94,6 +95,11 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( + From 0c97cb0eb4edf1f58ffcb911535ad01dbdebad73 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Mon, 8 Nov 2021 10:04:20 +0000 Subject: [PATCH 03/25] feat: Created initial `PullRequestsPage` component. Signed-off-by: Marley Powell --- .../PullRequestsPage/PullRequestsPage.tsx | 28 +++++++++++++++++++ .../src/components/PullRequestsPage/index.ts | 17 +++++++++++ plugins/azure-devops/src/plugin.ts | 19 +++++++++++-- plugins/azure-devops/src/routes.ts | 10 +++++-- 4 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/PullRequestsPage.tsx create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/index.ts diff --git a/plugins/azure-devops/src/components/PullRequestsPage/PullRequestsPage.tsx b/plugins/azure-devops/src/components/PullRequestsPage/PullRequestsPage.tsx new file mode 100644 index 0000000000..e22476f742 --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/PullRequestsPage.tsx @@ -0,0 +1,28 @@ +/* + * 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 { Content, Header, Page } from '@backstage/core-components'; + +import React from 'react'; + +export const PullRequestsPage = () => { + return ( + +
+ + + ); +}; diff --git a/plugins/azure-devops/src/components/PullRequestsPage/index.ts b/plugins/azure-devops/src/components/PullRequestsPage/index.ts new file mode 100644 index 0000000000..e8b6cfa6bb --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { PullRequestsPage } from './PullRequestsPage'; diff --git a/plugins/azure-devops/src/plugin.ts b/plugins/azure-devops/src/plugin.ts index ab6d816460..7553f02da1 100644 --- a/plugins/azure-devops/src/plugin.ts +++ b/plugins/azure-devops/src/plugin.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + azurePipelinesEntityContentRouteRef, + azurePrsRootRouteRef, +} from './routes'; import { createApiFactory, createPlugin, @@ -26,7 +30,6 @@ import { AZURE_DEVOPS_ANNOTATION } from './constants'; import { AzureDevOpsClient } from './api/AzureDevOpsClient'; import { Entity } from '@backstage/catalog-model'; import { azureDevOpsApiRef } from './api/AzureDevOpsApi'; -import { azureDevOpsRouteRef } from './routes'; export const isAzureDevOpsAvailable = (entity: Entity) => Boolean(entity.metadata.annotations?.[AZURE_DEVOPS_ANNOTATION]); @@ -42,10 +45,20 @@ export const azureDevOpsPlugin = createPlugin({ }), ], routes: { - entityContent: azureDevOpsRouteRef, + azurePrsRoot: azurePrsRootRouteRef, + azurePipelinesEntityContent: azurePipelinesEntityContentRouteRef, }, }); +export const AzurePullRequestsPage = azureDevOpsPlugin.provide( + createRoutableExtension({ + name: 'AzurePullRequestsPage', + component: () => + import('./components/PullRequestsPage').then(m => m.PullRequestsPage), + mountPoint: azurePrsRootRouteRef, + }), +); + export const EntityAzurePipelinesContent = azureDevOpsPlugin.provide( createRoutableExtension({ name: 'EntityAzurePipelinesContent', @@ -53,6 +66,6 @@ export const EntityAzurePipelinesContent = azureDevOpsPlugin.provide( import('./components/EntityPageAzurePipelines').then( m => m.EntityPageAzurePipelines, ), - mountPoint: azureDevOpsRouteRef, + mountPoint: azurePipelinesEntityContentRouteRef, }), ); diff --git a/plugins/azure-devops/src/routes.ts b/plugins/azure-devops/src/routes.ts index 81f4b48fda..6884c3d2b2 100644 --- a/plugins/azure-devops/src/routes.ts +++ b/plugins/azure-devops/src/routes.ts @@ -13,8 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { createRouteRef } from '@backstage/core-plugin-api'; -export const azureDevOpsRouteRef = createRouteRef({ - title: 'azure-devops', +export const azurePrsRootRouteRef = createRouteRef({ + path: '', + title: 'Azure Pull Requests', +}); + +export const azurePipelinesEntityContentRouteRef = createRouteRef({ + title: 'Azure Pipelines Entity Content', }); From cf9d60ff60c21602ef978b6161382f1df6c053b9 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Mon, 8 Nov 2021 12:02:40 +0000 Subject: [PATCH 04/25] feat: Created intial display components for `PullRequestsPage`. Signed-off-by: Marley Powell --- .../lib/PullRequestCard/PullRequestCard.tsx | 86 +++++++++++++++++++ .../lib/PullRequestCard/index.ts | 17 ++++ .../lib/PullRequestGrid/PullRequestGrid.tsx | 48 +++++++++++ .../lib/PullRequestGrid/index.ts | 17 ++++ .../PullRequestGridColumn.tsx | 86 +++++++++++++++++++ .../lib/PullRequestGridColumn/index.ts | 17 ++++ .../components/PullRequestsPage/lib/types.ts | 23 +++++ plugins/azure-devops/src/index.ts | 4 + 8 files changed, 298 insertions(+) create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/index.ts create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGrid/PullRequestGrid.tsx create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGrid/index.ts create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGridColumn/PullRequestGridColumn.tsx create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGridColumn/index.ts create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/lib/types.ts diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx new file mode 100644 index 0000000000..849d3fce96 --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx @@ -0,0 +1,86 @@ +/* + * 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 { Card, CardContent, CardHeader, Link } from '@material-ui/core'; + +import { Avatar } from '@backstage/core-components'; +import { PullRequest } from '../../../../api/types'; +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; + +const useStyles = makeStyles( + theme => ({ + card: { + backgroundColor: + theme.palette.type === 'dark' + ? theme.palette.grey[700] + : theme.palette.common.white, + }, + cardHeaderSimplified: { + paddingBottom: theme.spacing(2), + }, + content: { + display: 'flex', + flexDirection: 'row', + }, + }), + { name: 'PullRequestCard' }, +); + +type PullRequestCardProps = { + pullRequest: PullRequest; + simplified?: boolean; +}; + +export const PullRequestCard = ({ + pullRequest, + simplified, +}: PullRequestCardProps) => { + const title = ( + + {pullRequest.title} + + ); + + const avatar = ( + + ); + + const classes = useStyles(); + + return ( + + + + {!simplified && } + + ); +}; diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/index.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/index.ts new file mode 100644 index 0000000000..5d6a49d09e --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { PullRequestCard } from './PullRequestCard'; diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGrid/PullRequestGrid.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGrid/PullRequestGrid.tsx new file mode 100644 index 0000000000..68c6ad8d51 --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGrid/PullRequestGrid.tsx @@ -0,0 +1,48 @@ +/* + * 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 { PullRequestGridColumn } from '../PullRequestGridColumn'; +import { PullRequestGroup } from '../types'; +import React from 'react'; +import { styled } from '@material-ui/core'; + +const GridDiv = styled('div')(({ theme }) => ({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + '& > *': { + marginRight: theme.spacing(2), + }, + '& > :last-of-type': { + marginRight: 0, + }, +})); + +type PullRequestGridProps = { + pullRequestGroups: PullRequestGroup[]; +}; + +export const PullRequestGrid = ({ + pullRequestGroups, +}: PullRequestGridProps) => { + return ( + + {pullRequestGroups.map(pullRequestGroup => ( + + ))} + + ); +}; diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGrid/index.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGrid/index.ts new file mode 100644 index 0000000000..902680f80d --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGrid/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { PullRequestGrid } from './PullRequestGrid'; diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGridColumn/PullRequestGridColumn.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGridColumn/PullRequestGridColumn.tsx new file mode 100644 index 0000000000..2db77c4a49 --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGridColumn/PullRequestGridColumn.tsx @@ -0,0 +1,86 @@ +/* + * 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 { Paper, Typography, styled, withStyles } from '@material-ui/core'; + +import { PullRequestCard } from '../PullRequestCard'; +import { PullRequestGroup } from '../types'; +import React from 'react'; + +const ColumnPaper = withStyles(theme => ({ + root: { + display: 'flex', + flexDirection: 'column', + flex: 1, + padding: theme.spacing(2), + backgroundColor: + theme.palette.type === 'dark' + ? theme.palette.grey[800] + : theme.palette.grey[300], + height: '100%', + }, +}))(Paper); + +const ColumnTitleDiv = styled('div')(({ theme }) => ({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + marginBottom: theme.spacing(2), +})); + +export const PullRequestCardContainer = styled('div')(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + '& > *': { + marginBottom: theme.spacing(2), + }, + '& > :last-of-type': { + marginBottom: 0, + }, +})); + +type PullRequestGridColumnProps = { + pullRequestGroup: PullRequestGroup; +}; + +export const PullRequestGridColumn = ({ + pullRequestGroup, +}: PullRequestGridColumnProps) => { + const columnTitle = ( + + {pullRequestGroup.title} + + ); + + const pullRequests = ( + + {pullRequestGroup.pullRequests.map(pullRequest => ( + + ))} + + ); + + return ( + + {columnTitle} + {pullRequests} + + ); +}; diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGridColumn/index.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGridColumn/index.ts new file mode 100644 index 0000000000..aa3cf82554 --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGridColumn/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { PullRequestGridColumn } from './PullRequestGridColumn'; diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/types.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/types.ts new file mode 100644 index 0000000000..f05e036a58 --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/types.ts @@ -0,0 +1,23 @@ +/* + * 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 { PullRequest } from '../../../api/types'; + +export interface PullRequestGroup { + title: string; + pullRequests: PullRequest[]; + simplified?: boolean; +} diff --git a/plugins/azure-devops/src/index.ts b/plugins/azure-devops/src/index.ts index 71d3b654fe..63e0d4923d 100644 --- a/plugins/azure-devops/src/index.ts +++ b/plugins/azure-devops/src/index.ts @@ -13,8 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { azureDevOpsPlugin, EntityAzurePipelinesContent, isAzureDevOpsAvailable, + AzurePullRequestsPage, } from './plugin'; + +export * from './components/AzurePullRequestsIcon'; From fdb2eb0b963c5c289b82b93b3e9c9a59d1dd4860 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Mon, 8 Nov 2021 12:22:09 +0000 Subject: [PATCH 05/25] feat: Created `AutoCompleteIcon` component for `PullRequestCard`. Signed-off-by: Marley Powell --- .../lib/AutoCompleteIcon/AutoCompleteIcon.tsx | 29 +++++++++++++++++++ .../lib/AutoCompleteIcon/index.ts | 17 +++++++++++ .../lib/PullRequestCard/PullRequestCard.tsx | 10 +++++++ 3 files changed, 56 insertions(+) create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/lib/AutoCompleteIcon/AutoCompleteIcon.tsx create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/lib/AutoCompleteIcon/index.ts diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/AutoCompleteIcon/AutoCompleteIcon.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/AutoCompleteIcon/AutoCompleteIcon.tsx new file mode 100644 index 0000000000..aea64ff23b --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/AutoCompleteIcon/AutoCompleteIcon.tsx @@ -0,0 +1,29 @@ +/* + * 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 DoneAllIcon from '@material-ui/icons/DoneAll'; +import { withStyles } from '@material-ui/core/styles'; + +export const AutoCompleteIcon = withStyles( + theme => ({ + root: (props: { hasAutoComplete: boolean }) => ({ + color: props.hasAutoComplete + ? theme.palette.success.main + : theme.palette.grey[400], + }), + }), + { name: 'AutoCompleteIcon' }, +)(DoneAllIcon); diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/AutoCompleteIcon/index.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/AutoCompleteIcon/index.ts new file mode 100644 index 0000000000..d83b0ffad0 --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/AutoCompleteIcon/index.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export { AutoCompleteIcon } from './AutoCompleteIcon'; diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx index 849d3fce96..85fc337aae 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx @@ -16,6 +16,7 @@ import { Card, CardContent, CardHeader, Link } from '@material-ui/core'; +import { AutoCompleteIcon } from '../AutoCompleteIcon'; import { Avatar } from '@backstage/core-components'; import { PullRequest } from '../../../../api/types'; import React from 'react'; @@ -36,6 +37,11 @@ const useStyles = makeStyles( display: 'flex', flexDirection: 'row', }, + cardHeaderAction: { + display: 'flex', + alignSelf: 'center', + margin: 0, + }, }), { name: 'PullRequestCard' }, ); @@ -75,8 +81,12 @@ export const PullRequestCard = ({ + } classes={{ ...(simplified && { root: classes.cardHeaderSimplified }), + action: classes.cardHeaderAction, }} /> From 504a600cae7b81b7e52c392ea362b3e25d82ead2 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Mon, 8 Nov 2021 12:40:02 +0000 Subject: [PATCH 06/25] feat: Created `PullRequestCardPolicies` and `PullRequestCardReviewers` components. Signed-off-by: Marley Powell --- .../lib/PullRequestCard/PullRequestCard.tsx | 24 ++++-- .../PullRequestCardPolicies.tsx | 76 +++++++++++++++++++ .../PullRequestCard/PullRequestCardPolicy.tsx | 70 +++++++++++++++++ .../PullRequestCardReviewer.tsx | 41 ++++++++++ .../PullRequestCardReviewers.tsx | 41 ++++++++++ 5 files changed, 247 insertions(+), 5 deletions(-) create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicies.tsx create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicy.tsx create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewer.tsx create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewers.tsx diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx index 85fc337aae..c7a472c082 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx @@ -19,6 +19,8 @@ import { Card, CardContent, CardHeader, Link } from '@material-ui/core'; import { AutoCompleteIcon } from '../AutoCompleteIcon'; import { Avatar } from '@backstage/core-components'; import { PullRequest } from '../../../../api/types'; +import { PullRequestCardPolicies } from './PullRequestCardPolicies'; +import { PullRequestCardReviewers } from './PullRequestCardReviewers'; import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; @@ -33,15 +35,18 @@ const useStyles = makeStyles( cardHeaderSimplified: { paddingBottom: theme.spacing(2), }, - content: { - display: 'flex', - flexDirection: 'row', - }, cardHeaderAction: { display: 'flex', alignSelf: 'center', margin: 0, }, + content: { + display: 'flex', + flexDirection: 'row', + }, + policies: { + flex: 1, + }, }), { name: 'PullRequestCard' }, ); @@ -90,7 +95,16 @@ export const PullRequestCard = ({ }} /> - {!simplified && } + {!simplified && ( + + + + + + )} ); }; diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicies.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicies.tsx new file mode 100644 index 0000000000..eeb78b32f8 --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicies.tsx @@ -0,0 +1,76 @@ +/* + * 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 { + PolicyInProgressIcon, + PolicyIssueIcon, + PolicyRequiredIcon, + PullRequestCardPolicy, +} from './PullRequestCardPolicy'; + +import { Policy } from '../../../../api/types'; +import React from 'react'; + +function getPullRequestCardPolicy(policy: Policy): JSX.Element | null { + switch (policy.type) { + case 'Build': + switch (policy.status) { + case 'Running': + return ( + } + /> + ); + case 'Rejected': + return ( + } /> + ); + case 'Queued': + return ( + } + /> + ); + default: + return null; + } + case 'MinimumReviewers': + return ( + } /> + ); + case 'Status': + case 'Comments': + return ( + } /> + ); + default: + return null; + } +} + +type PullRequestCardProps = { + policies: Policy[]; + className: string; +}; + +export const PullRequestCardPolicies = ({ + policies, + className, +}: PullRequestCardProps) => ( +
{policies.map(getPullRequestCardPolicy)}
+); diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicy.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicy.tsx new file mode 100644 index 0000000000..479c31f8c1 --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicy.tsx @@ -0,0 +1,70 @@ +/* + * 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 { styled, withStyles } from '@material-ui/core/styles'; + +import CancelIcon from '@material-ui/icons/Cancel'; +import GroupWorkIcon from '@material-ui/icons/GroupWork'; +import { Policy } from '../../../../api/types'; +import React from 'react'; +import WatchLaterIcon from '@material-ui/icons/WatchLater'; + +export const PolicyRequiredIcon = withStyles( + theme => ({ + root: { + color: theme.palette.info.main, + }, + }), + { name: 'PolicyRequiredIcon' }, +)(WatchLaterIcon); + +export const PolicyIssueIcon = withStyles( + theme => ({ + root: { + color: theme.palette.error.main, + }, + }), + { name: 'PolicyIssueIcon' }, +)(CancelIcon); + +export const PolicyInProgressIcon = withStyles( + theme => ({ + root: { + color: theme.palette.info.main, + }, + }), + { name: 'PolicyInProgressIcon' }, +)(GroupWorkIcon); + +const PullRequestCardPolicyContainer = styled('div')({ + display: 'flex', + alignItems: 'center', + flexWrap: 'wrap', +}); + +type PullRequestCardPolicyProps = { + policy: Policy; + icon: React.ReactNode; +}; + +export const PullRequestCardPolicy = ({ + policy, + icon, +}: PullRequestCardPolicyProps) => ( + + {icon} {policy.text} + +); diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewer.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewer.tsx new file mode 100644 index 0000000000..87dbb6118b --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewer.tsx @@ -0,0 +1,41 @@ +/* + * 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 { Avatar } from '@backstage/core-components'; +import React from 'react'; +import { Reviewer } from '../../../../api/types'; +import { Tooltip } from '@material-ui/core'; + +type PullRequestCardReviewerProps = { + reviewer: Reviewer; +}; + +export const PullRequestCardReviewer = ({ + reviewer, +}: PullRequestCardReviewerProps) => ( + + + +); diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewers.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewers.tsx new file mode 100644 index 0000000000..b46223e0cb --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewers.tsx @@ -0,0 +1,41 @@ +/* + * 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 { PullRequestCardReviewer } from './PullRequestCardReviewer'; +import React from 'react'; +import { Reviewer } from '../../../../api/types'; +import { styled } from '@material-ui/core/styles'; + +const PullRequestCardReviewersContainer = styled('div')({ + '& > *': { + marginTop: 4, + marginBottom: 4, + }, +}); + +type PullRequestCardReviewersProps = { + reviewers: Reviewer[]; +}; + +export const PullRequestCardReviewers = ({ + reviewers, +}: PullRequestCardReviewersProps) => ( + + {reviewers.map(reviewer => ( + + ))} + +); From 60cafe772736805f9f1be5532f224aa7b3dff078 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Mon, 8 Nov 2021 12:52:09 +0000 Subject: [PATCH 07/25] feat: Added subheader to `PullRequestCard` component. Signed-off-by: Marley Powell --- .../lib/PullRequestCard/PullRequestCard.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx index c7a472c082..1d30504f44 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx @@ -18,6 +18,7 @@ import { Card, CardContent, CardHeader, Link } from '@material-ui/core'; import { AutoCompleteIcon } from '../AutoCompleteIcon'; import { Avatar } from '@backstage/core-components'; +import { DateTime } from 'luxon'; import { PullRequest } from '../../../../api/types'; import { PullRequestCardPolicies } from './PullRequestCardPolicies'; import { PullRequestCardReviewers } from './PullRequestCardReviewers'; @@ -71,6 +72,20 @@ export const PullRequestCard = ({ ); + const subheader = ( + + + {pullRequest.repository.name} + {' '} + 路 {DateTime.fromISO(pullRequest.creationDate.toString()).toRelative()} + + ); + const avatar = ( } From b280d08a070ca2c6feb795709a1ae4c544160a89 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Mon, 8 Nov 2021 13:00:40 +0000 Subject: [PATCH 08/25] docs: Created initial stories for `PullRequestCard` component. Signed-off-by: Marley Powell --- .../PullRequestCard.stories.tsx | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.stories.tsx diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.stories.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.stories.tsx new file mode 100644 index 0000000000..e918d37282 --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.stories.tsx @@ -0,0 +1,86 @@ +/* + * 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 { MemoryRouter } from 'react-router'; +import { PullRequest } from '../../../../api/types'; +import { PullRequestCard } from './PullRequestCard'; +import { PullRequestStatus } from 'azure-devops-node-api/interfaces/GitInterfaces'; +import React from 'react'; + +export default { + title: 'Plugins/Azure Devops/Pull Request Card', + component: PullRequestCard, +}; + +const pullRequest: PullRequest = { + pullRequestId: 1, + title: + "feat(EXUX-4091): 馃泜 Added the admin role authorization to the backend API's", + description: + 'This PR contains the following updates:\n\n| Package | Type | Update | Change |\n|---|---|---|---|\n| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint) | devDependencies | major | [`4.33.0` -> `5.0.0`](https://renovatebot.com/diffs/npm/@typescript-eslint%2feslint-plugin/4.33.0/5.0.0) |\n| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescrip', + link: '', + repository: { + name: 'backstage', + url: '', + }, + createdBy: { + id: '', + displayName: 'Marley', + uniqueName: 'marley@test.com', + imageUrl: + 'https://dev.azure.com/exclaimerltd/_api/_common/identityImage?id=e6c0634b-68d2-6e6f-aa7d-adccada23216', + }, + reviewers: [ + { displayName: 'Marley', imageUrl: '' }, + { displayName: 'User 1', imageUrl: '' }, + { displayName: 'User 2', imageUrl: '' }, + ], + policies: [ + { + type: 'Build', + status: 'Running', + text: 'Build: UI (running)', + }, + { + type: 'MinimumReviewers', + text: 'Minimum number of reviewers (2)', + status: '', + }, + { + type: 'Comments', + text: 'Comment requirements', + status: '', + }, + ], + hasAutoComplete: true, + creationDate: new Date(Date.now() - 10000000).toISOString(), + sourceRefName: '', + targetRefName: '', + status: PullRequestStatus.Active, + isDraft: false, +}; + +export const Default = () => ( + + + +); + +export const Simplified = () => ( + + + +); From 96c562bb22f32ac3a8ae1336e250071430128752 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Wed, 10 Nov 2021 14:53:40 +0000 Subject: [PATCH 09/25] refactored: Improved types and small code improvements. Signed-off-by: Marley Powell --- plugins/azure-devops-common/src/types.ts | 125 ++++++++++++++++++ .../PullRequestCard.stories.tsx | 64 ++++++--- .../lib/PullRequestCard/PullRequestCard.tsx | 35 +++-- .../PullRequestCardPolicies.tsx | 20 +-- .../PullRequestCard/PullRequestCardPolicy.tsx | 4 +- .../PullRequestCardReviewer.tsx | 25 ++-- .../PullRequestCardReviewers.tsx | 14 +- .../components/PullRequestsPage/lib/types.ts | 17 ++- 8 files changed, 247 insertions(+), 57 deletions(-) diff --git a/plugins/azure-devops-common/src/types.ts b/plugins/azure-devops-common/src/types.ts index eacc2af30d..458eaa76d4 100644 --- a/plugins/azure-devops-common/src/types.ts +++ b/plugins/azure-devops-common/src/types.ts @@ -126,3 +126,128 @@ export type PullRequestOptions = { top: number; status: PullRequestStatus; }; + +export interface DashboardPullRequest { + pullRequestId: number | undefined; + title: string | undefined; + description: string | undefined; + repository: Repository | undefined; + createdBy: CreatedBy | undefined; + hasAutoComplete: boolean; + policies: Policy[] | undefined; + reviewers: Reviewer[] | undefined; + creationDate: string | undefined; + status: PullRequestStatus | undefined; + isDraft: boolean | undefined; + link: string | undefined; +} + +export interface Reviewer { + id: string | undefined; + displayName: string | undefined; + imageUrl: string | undefined; + isRequired: boolean | undefined; + isContainer: boolean | undefined; + voteStatus: PullRequestVoteStatus; +} + +export interface Policy { + id: number | undefined; + type: PolicyType; + status: PolicyEvaluationStatus | undefined; + text: string | undefined; + link: string | undefined; +} + +export interface CreatedBy { + id: string | undefined; + displayName: string | undefined; + uniqueName: string | undefined; + imageUrl: string | undefined; +} + +export interface Repository { + id: string | undefined; + name: string | undefined; + url: string | undefined; +} + +export interface Team { + id: string | undefined; + name: string | undefined; + memberIds: string[] | undefined; +} + +/** + * Status of a policy which is running against a specific pull request. + */ +export enum PolicyEvaluationStatus { + /** + * The policy is either queued to run, or is waiting for some event before progressing. + */ + Queued = 0, + /** + * The policy is currently running. + */ + Running = 1, + /** + * The policy has been fulfilled for this pull request. + */ + Approved = 2, + /** + * The policy has rejected this pull request. + */ + Rejected = 3, + /** + * The policy does not apply to this pull request. + */ + NotApplicable = 4, + /** + * The policy has encountered an unexpected error. + */ + Broken = 5, +} + +export enum PolicyType { + Build = 'Build', + Status = 'Status', + MinimumReviewers = 'MinimumReviewers', + Comments = 'Comments', + RequiredReviewers = 'RequiredReviewers', + MergeStrategy = 'MergeStrategy', +} + +export const PolicyTypeId = { + /** + * This policy will require a successful build has been performed before updating protected refs. + */ + Build: '0609b952-1397-4640-95ec-e00a01b2c241', + /** + * This policy will require a successful status to be posted before updating protected refs. + */ + Status: 'cbdc66da-9728-4af8-aada-9a5a32e4a226', + /** + * This policy will ensure that a minimum number of reviewers have approved a pull request before completion. + */ + MinimumReviewers: 'fa4e907d-c16b-4a4c-9dfa-4906e5d171dd', + /** + * Check if the pull request has any active comments. + */ + Comments: 'c6a1889d-b943-4856-b76f-9e46bb6b0df2', + /** + * This policy will ensure that required reviewers are added for modified files matching specified patterns. + */ + RequiredReviewers: 'fd2167ab-b0be-447a-8ec8-39368250530e', + /** + * This policy ensures that pull requests use a consistent merge strategy. + */ + MergeStrategy: 'fa4e907d-c16b-4a4c-9dfa-4916e5d171ab', +}; + +export enum PullRequestVoteStatus { + Approved = 10, + ApprovedWithSuggestions = 5, + NoVote = 0, + WaitingForAuthor = -5, + Rejected = -10, +} diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.stories.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.stories.tsx index e918d37282..b948b616bd 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.stories.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.stories.tsx @@ -14,10 +14,16 @@ * limitations under the License. */ +import { + DashboardPullRequest, + PolicyEvaluationStatus, + PolicyType, + PullRequestStatus, + PullRequestVoteStatus, +} from '@backstage/plugin-azure-devops-common'; + import { MemoryRouter } from 'react-router'; -import { PullRequest } from '../../../../api/types'; import { PullRequestCard } from './PullRequestCard'; -import { PullRequestStatus } from 'azure-devops-node-api/interfaces/GitInterfaces'; import React from 'react'; export default { @@ -25,16 +31,17 @@ export default { component: PullRequestCard, }; -const pullRequest: PullRequest = { +const pullRequest: DashboardPullRequest = { pullRequestId: 1, title: "feat(EXUX-4091): 馃泜 Added the admin role authorization to the backend API's", description: 'This PR contains the following updates:\n\n| Package | Type | Update | Change |\n|---|---|---|---|\n| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint) | devDependencies | major | [`4.33.0` -> `5.0.0`](https://renovatebot.com/diffs/npm/@typescript-eslint%2feslint-plugin/4.33.0/5.0.0) |\n| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescrip', - link: '', + link: undefined, repository: { + id: undefined, name: 'backstage', - url: '', + url: undefined, }, createdBy: { id: '', @@ -44,31 +51,56 @@ const pullRequest: PullRequest = { 'https://dev.azure.com/exclaimerltd/_api/_common/identityImage?id=e6c0634b-68d2-6e6f-aa7d-adccada23216', }, reviewers: [ - { displayName: 'Marley', imageUrl: '' }, - { displayName: 'User 1', imageUrl: '' }, - { displayName: 'User 2', imageUrl: '' }, + { + id: undefined, + displayName: 'Marley', + imageUrl: '', + isRequired: true, + isContainer: false, + voteStatus: PullRequestVoteStatus.Approved, + }, + { + id: undefined, + displayName: 'User 1', + imageUrl: '', + isRequired: false, + isContainer: false, + voteStatus: PullRequestVoteStatus.WaitingForAuthor, + }, + { + id: undefined, + displayName: 'User 2', + imageUrl: '', + isRequired: true, + isContainer: false, + voteStatus: PullRequestVoteStatus.NoVote, + }, ], policies: [ { - type: 'Build', - status: 'Running', + id: undefined, + type: PolicyType.Build, + status: PolicyEvaluationStatus.Running, text: 'Build: UI (running)', + link: undefined, }, { - type: 'MinimumReviewers', + id: undefined, + type: PolicyType.MinimumReviewers, text: 'Minimum number of reviewers (2)', - status: '', + status: undefined, + link: undefined, }, { - type: 'Comments', + id: undefined, + type: PolicyType.Comments, text: 'Comment requirements', - status: '', + status: undefined, + link: undefined, }, ], hasAutoComplete: true, creationDate: new Date(Date.now() - 10000000).toISOString(), - sourceRefName: '', - targetRefName: '', status: PullRequestStatus.Active, isDraft: false, }; diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx index 1d30504f44..cfb0a121db 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx @@ -18,8 +18,8 @@ import { Card, CardContent, CardHeader, Link } from '@material-ui/core'; import { AutoCompleteIcon } from '../AutoCompleteIcon'; import { Avatar } from '@backstage/core-components'; +import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common'; import { DateTime } from 'luxon'; -import { PullRequest } from '../../../../api/types'; import { PullRequestCardPolicies } from './PullRequestCardPolicies'; import { PullRequestCardReviewers } from './PullRequestCardReviewers'; import React from 'react'; @@ -53,7 +53,7 @@ const useStyles = makeStyles( ); type PullRequestCardProps = { - pullRequest: PullRequest; + pullRequest: DashboardPullRequest; simplified?: boolean; }; @@ -75,21 +75,23 @@ export const PullRequestCard = ({ const subheader = ( - {pullRequest.repository.name} + {pullRequest.repository?.name} {' '} - 路 {DateTime.fromISO(pullRequest.creationDate.toString()).toRelative()} + 路{' '} + {pullRequest.creationDate && + DateTime.fromISO(pullRequest.creationDate).toRelative()} ); const avatar = ( ); @@ -97,7 +99,10 @@ export const PullRequestCard = ({ const classes = useStyles(); return ( - + - + {pullRequest.policies && ( + + )} - + {pullRequest.reviewers && ( + + )}
)} diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicies.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicies.tsx index eeb78b32f8..21fe7ba034 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicies.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicies.tsx @@ -14,6 +14,11 @@ * limitations under the License. */ +import { + Policy, + PolicyEvaluationStatus, + PolicyType, +} from '@backstage/plugin-azure-devops-common'; import { PolicyInProgressIcon, PolicyIssueIcon, @@ -21,25 +26,24 @@ import { PullRequestCardPolicy, } from './PullRequestCardPolicy'; -import { Policy } from '../../../../api/types'; import React from 'react'; function getPullRequestCardPolicy(policy: Policy): JSX.Element | null { switch (policy.type) { - case 'Build': + case PolicyType.Build: switch (policy.status) { - case 'Running': + case PolicyEvaluationStatus.Running: return ( } /> ); - case 'Rejected': + case PolicyEvaluationStatus.Rejected: return ( } /> ); - case 'Queued': + case PolicyEvaluationStatus.Queued: return ( } /> ); - case 'Status': - case 'Comments': + case PolicyType.Status: + case PolicyType.Comments: return ( } /> ); diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicy.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicy.tsx index 479c31f8c1..22f9bca39b 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicy.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicy.tsx @@ -18,7 +18,7 @@ import { styled, withStyles } from '@material-ui/core/styles'; import CancelIcon from '@material-ui/icons/Cancel'; import GroupWorkIcon from '@material-ui/icons/GroupWork'; -import { Policy } from '../../../../api/types'; +import { Policy } from '@backstage/plugin-azure-devops-common'; import React from 'react'; import WatchLaterIcon from '@material-ui/icons/WatchLater'; @@ -57,7 +57,7 @@ const PullRequestCardPolicyContainer = styled('div')({ type PullRequestCardPolicyProps = { policy: Policy; - icon: React.ReactNode; + icon: JSX.Element; }; export const PullRequestCardPolicy = ({ diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewer.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewer.tsx index 87dbb6118b..1d2049728b 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewer.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewer.tsx @@ -16,8 +16,7 @@ import { Avatar } from '@backstage/core-components'; import React from 'react'; -import { Reviewer } from '../../../../api/types'; -import { Tooltip } from '@material-ui/core'; +import { Reviewer } from '@backstage/plugin-azure-devops-common'; type PullRequestCardReviewerProps = { reviewer: Reviewer; @@ -26,16 +25,14 @@ type PullRequestCardReviewerProps = { export const PullRequestCardReviewer = ({ reviewer, }: PullRequestCardReviewerProps) => ( - - - + ); diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewers.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewers.tsx index b46223e0cb..a3b87e0439 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewers.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewers.tsx @@ -14,9 +14,13 @@ * limitations under the License. */ +import { + PullRequestVoteStatus, + Reviewer, +} from '@backstage/plugin-azure-devops-common'; + import { PullRequestCardReviewer } from './PullRequestCardReviewer'; import React from 'react'; -import { Reviewer } from '../../../../api/types'; import { styled } from '@material-ui/core/styles'; const PullRequestCardReviewersContainer = styled('div')({ @@ -34,8 +38,14 @@ export const PullRequestCardReviewers = ({ reviewers, }: PullRequestCardReviewersProps) => ( - {reviewers.map(reviewer => ( + {reviewers.filter(reviewerFilter).map(reviewer => ( ))} ); + +function reviewerFilter(reviewer: Reviewer): boolean { + return reviewer.voteStatus === PullRequestVoteStatus.NoVote + ? !!reviewer.isRequired + : !reviewer.isContainer; +} diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/types.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/types.ts index f05e036a58..2d936f7b64 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/types.ts +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/types.ts @@ -14,10 +14,23 @@ * limitations under the License. */ -import { PullRequest } from '../../../api/types'; +import { + DashboardPullRequest, + Team, +} from '@backstage/plugin-azure-devops-common'; export interface PullRequestGroup { title: string; - pullRequests: PullRequest[]; + pullRequests: DashboardPullRequest[]; + simplified?: boolean; +} + +export type PullRequestFilter = (pullRequest: DashboardPullRequest) => boolean; + +export type TeamFilter = (team: Team) => boolean; + +export interface PullRequestGroupConfig { + title: string; + filter: PullRequestFilter; simplified?: boolean; } From 135d2abcf2b3e75e20558baf638383627c7b9b30 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Wed, 10 Nov 2021 15:16:16 +0000 Subject: [PATCH 10/25] feat: Created `getDashboardPullRequests` and `getAllTeams` methods in `AzureDevOpsClient`. Signed-off-by: Marley Powell --- .../azure-devops/src/api/AzureDevOpsApi.ts | 8 ++++++++ .../azure-devops/src/api/AzureDevOpsClient.ts | 20 ++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/plugins/azure-devops/src/api/AzureDevOpsApi.ts b/plugins/azure-devops/src/api/AzureDevOpsApi.ts index 05377b0b7b..a6967d01e4 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsApi.ts @@ -15,8 +15,10 @@ */ import { + DashboardPullRequest, RepoBuild, RepoBuildOptions, + Team, } from '@backstage/plugin-azure-devops-common'; import { createApiRef } from '@backstage/core-plugin-api'; @@ -33,4 +35,10 @@ export interface AzureDevOpsApi { repoName: string, options?: RepoBuildOptions, ): Promise<{ items: RepoBuild[] }>; + + getDashboardPullRequests( + projectName: string, + ): Promise; + + getAllTeams(): Promise; } diff --git a/plugins/azure-devops/src/api/AzureDevOpsClient.ts b/plugins/azure-devops/src/api/AzureDevOpsClient.ts index 4fd4e781c4..08da6305e5 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsClient.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsClient.ts @@ -14,11 +14,13 @@ * limitations under the License. */ -import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; import { + DashboardPullRequest, RepoBuild, RepoBuildOptions, + Team, } from '@backstage/plugin-azure-devops-common'; +import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; import { AzureDevOpsApi } from './AzureDevOpsApi'; import { ResponseError } from '@backstage/errors'; @@ -27,7 +29,7 @@ export class AzureDevOpsClient implements AzureDevOpsApi { private readonly discoveryApi: DiscoveryApi; private readonly identityApi: IdentityApi; - constructor(options: { + public constructor(options: { discoveryApi: DiscoveryApi; identityApi: IdentityApi; }) { @@ -35,7 +37,7 @@ export class AzureDevOpsClient implements AzureDevOpsApi { this.identityApi = options.identityApi; } - async getRepoBuilds( + public async getRepoBuilds( projectName: string, repoName: string, options?: RepoBuildOptions, @@ -46,6 +48,18 @@ export class AzureDevOpsClient implements AzureDevOpsApi { return { items }; } + public getDashboardPullRequests( + projectName: string, + ): Promise { + return this.get( + `dashboard-pull-requests/${projectName}?top=100`, + ); + } + + public getAllTeams(): Promise { + return this.get('all-teams'); + } + private async get(path: string): Promise { const baseUrl = `${await this.discoveryApi.getBaseUrl('azure-devops')}/`; const url = new URL(path, baseUrl); From 7e7e98f8f2de6396e5bbb9505c19a3cf5552d5a7 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Wed, 10 Nov 2021 16:37:03 +0000 Subject: [PATCH 11/25] feat: Created new hooks for loading API data. Signed-off-by: Marley Powell --- plugins/azure-devops/src/hooks/index.ts | 21 ++++++++++ plugins/azure-devops/src/hooks/useAllTeams.ts | 42 +++++++++++++++++++ .../src/hooks/useDashboardPullRequests.ts | 42 +++++++++++++++++++ .../azure-devops/src/hooks/useUserEmail.ts | 22 ++++++++++ 4 files changed, 127 insertions(+) create mode 100644 plugins/azure-devops/src/hooks/index.ts create mode 100644 plugins/azure-devops/src/hooks/useAllTeams.ts create mode 100644 plugins/azure-devops/src/hooks/useDashboardPullRequests.ts create mode 100644 plugins/azure-devops/src/hooks/useUserEmail.ts diff --git a/plugins/azure-devops/src/hooks/index.ts b/plugins/azure-devops/src/hooks/index.ts new file mode 100644 index 0000000000..29a2a3b568 --- /dev/null +++ b/plugins/azure-devops/src/hooks/index.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ + +export * from './useAllTeams'; +export * from './useDashboardPullRequests'; +export * from './useProjectRepoFromEntity'; +export * from './useRepoBuilds'; +export * from './useUserEmail'; diff --git a/plugins/azure-devops/src/hooks/useAllTeams.ts b/plugins/azure-devops/src/hooks/useAllTeams.ts new file mode 100644 index 0000000000..d32aa5e136 --- /dev/null +++ b/plugins/azure-devops/src/hooks/useAllTeams.ts @@ -0,0 +1,42 @@ +/* + * 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 { Team } from '@backstage/plugin-azure-devops-common'; +import { azureDevOpsApiRef } from '../api'; +import { useApi } from '@backstage/core-plugin-api'; +import { useAsync } from 'react-use'; + +export function useAllTeams(): { + teams?: Team[]; + loading: boolean; + error?: Error; +} { + const api = useApi(azureDevOpsApiRef); + + const { + value: teams, + loading, + error, + } = useAsync(() => { + return api.getAllTeams(); + }, [api]); + + return { + teams, + loading, + error, + }; +} diff --git a/plugins/azure-devops/src/hooks/useDashboardPullRequests.ts b/plugins/azure-devops/src/hooks/useDashboardPullRequests.ts new file mode 100644 index 0000000000..977f3d4c0c --- /dev/null +++ b/plugins/azure-devops/src/hooks/useDashboardPullRequests.ts @@ -0,0 +1,42 @@ +/* + * 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 { DashboardPullRequest } from '@backstage/plugin-azure-devops-common'; +import { azureDevOpsApiRef } from '../api'; +import { useApi } from '@backstage/core-plugin-api'; +import { useAsync } from 'react-use'; + +export function useDashboardPullRequests(project: string): { + pullRequests?: DashboardPullRequest[]; + loading: boolean; + error?: Error; +} { + const api = useApi(azureDevOpsApiRef); + + const { + value: pullRequests, + loading, + error, + } = useAsync(() => { + return api.getDashboardPullRequests(project); + }, [api, project]); + + return { + pullRequests, + loading, + error, + }; +} diff --git a/plugins/azure-devops/src/hooks/useUserEmail.ts b/plugins/azure-devops/src/hooks/useUserEmail.ts new file mode 100644 index 0000000000..4655815297 --- /dev/null +++ b/plugins/azure-devops/src/hooks/useUserEmail.ts @@ -0,0 +1,22 @@ +/* + * 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 { identityApiRef, useApi } from '@backstage/core-plugin-api'; + +export function useUserEmail(): string | undefined { + const identityApi = useApi(identityApiRef); + return identityApi.getProfile().email; +} From 6ba700165964b79ceb2ea7ccca4a805046bdc610 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Thu, 11 Nov 2021 12:19:01 +0000 Subject: [PATCH 12/25] feat: Created `utils` file for util functions. Signed-off-by: Marley Powell --- .../PullRequestCardReviewers.tsx | 13 +- .../components/PullRequestsPage/lib/utils.ts | 117 ++++++++++++++++++ 2 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/lib/utils.ts diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewers.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewers.tsx index a3b87e0439..2a9a265756 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewers.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewers.tsx @@ -14,13 +14,10 @@ * limitations under the License. */ -import { - PullRequestVoteStatus, - Reviewer, -} from '@backstage/plugin-azure-devops-common'; - import { PullRequestCardReviewer } from './PullRequestCardReviewer'; import React from 'react'; +import { Reviewer } from '@backstage/plugin-azure-devops-common'; +import { reviewerFilter } from '../utils'; import { styled } from '@material-ui/core/styles'; const PullRequestCardReviewersContainer = styled('div')({ @@ -43,9 +40,3 @@ export const PullRequestCardReviewers = ({ ))} ); - -function reviewerFilter(reviewer: Reviewer): boolean { - return reviewer.voteStatus === PullRequestVoteStatus.NoVote - ? !!reviewer.isRequired - : !reviewer.isContainer; -} diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/utils.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/utils.ts new file mode 100644 index 0000000000..c9cf866010 --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/utils.ts @@ -0,0 +1,117 @@ +/* + * 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 { + DashboardPullRequest, + PullRequestVoteStatus, + Reviewer, +} from '@backstage/plugin-azure-devops-common'; +import { + PullRequestFilter, + PullRequestGroup, + PullRequestGroupConfig, +} from './types'; + +/** + * Creates a filter that matches pull requests created by `userEmail`. + * @param userEmail an email to filter by. + * @returns a filter for pull requests created by `userEmail`. + */ +export function getCreatedByUserFilter( + userEmail: string | undefined, +): PullRequestFilter { + return (pullRequest: DashboardPullRequest): boolean => + pullRequest.createdBy?.uniqueName?.toLocaleLowerCase() === + userEmail?.toLocaleLowerCase(); +} + +/** + * Filters a reviewer based on vote status and if the reviewer is required. + * @param reviewer a reviewer to filter. + * @returns whether or not to filter the `reviewer`. + */ +export function reviewerFilter(reviewer: Reviewer): boolean { + return reviewer.voteStatus === PullRequestVoteStatus.NoVote + ? !!reviewer.isRequired + : !reviewer.isContainer; +} + +/** + * Removes values from the provided array and returns them. + * @param arr the array to extract values from. + * @param filter a filter used to extract values from the provided array. + * @returns the values that were extracted from the array. + * + * @example + * ```ts + * const numbers = [1, 2, 3, 4, 5, 6]; + * const numberFilter = (num: number): boolean => num > 3; + * const extractedNumbers = arrayExtract(numbers, numberFilter); + * console.log(numbers); // [1, 2, 3] + * console.log(extractedNumbers); // [4, 5, 6] + * ``` + * + * @example + * ```ts + * const numbers = [1, 2, 3, 4, 5, 6]; + * const numberFilter = (num: number): boolean => num % 2 === 0; + * const extractedNumbers = arrayExtract(numbers, numberFilter); + * console.log(numbers); // [1, 3, 5] + * console.log(extractedNumbers); // [2, 4, 6] + * ``` + */ +export function arrayExtract(arr: T[], filter: (value: T) => unknown): T[] { + const extractedValues: T[] = []; + + for (let i = 0; i - extractedValues.length < arr.length; i++) { + const offsetIndex = i - extractedValues.length; + + const value = arr[offsetIndex]; + + if (filter(value)) { + arr.splice(offsetIndex, 1); + extractedValues.push(value); + } + } + + return extractedValues; +} + +/** + * Creates groups of pull requests based on a list of `PullRequestGroupConfig`. + * @param pullRequests all pull requests to be split up into groups. + * @param configs the config used for splitting up the pull request groups. + * @returns a list of pull request groups. + */ +export function getPullRequestGroups( + pullRequests: DashboardPullRequest[], + configs: PullRequestGroupConfig[], +): PullRequestGroup[] { + const remainingPullRequests: DashboardPullRequest[] = [...pullRequests]; + const pullRequestGroups: PullRequestGroup[] = []; + + configs.forEach(({ title, filter: configFilter, simplified }) => { + const groupPullRequests = arrayExtract(remainingPullRequests, configFilter); + + pullRequestGroups.push({ + title, + pullRequests: groupPullRequests, + simplified, + }); + }); + + return pullRequestGroups; +} From 4e0fc8f8a4f292fc41e1c6fd8990be18c493e671 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Thu, 11 Nov 2021 12:20:17 +0000 Subject: [PATCH 13/25] test: Added unit tests for `utils`. Signed-off-by: Marley Powell --- .../PullRequestsPage/lib/utils.test.ts | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 plugins/azure-devops/src/components/PullRequestsPage/lib/utils.test.ts diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/utils.test.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/utils.test.ts new file mode 100644 index 0000000000..da27b2e06e --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/utils.test.ts @@ -0,0 +1,161 @@ +/* + * 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 { + DashboardPullRequest, + PullRequestVoteStatus, + Reviewer, +} from '@backstage/plugin-azure-devops-common'; +import { + arrayExtract, + getCreatedByUserFilter, + getPullRequestGroups, + reviewerFilter, +} from './utils'; + +describe('getCreatedByUserFilter', () => { + it('should filter if pull request is created by user', () => { + const userEmail = 'user1@backstage.com'; + const pr = { + createdBy: { uniqueName: userEmail }, + } as DashboardPullRequest; + const result = getCreatedByUserFilter(userEmail)(pr); + expect(result).toBe(true); + }); + + it('should not filter if pull request is not created by user', () => { + const userEmail1 = 'user1@backstage.com'; + const userEmail2 = 'user2@backstage.com'; + const pr = { + createdBy: { uniqueName: userEmail1 }, + } as DashboardPullRequest; + const result = getCreatedByUserFilter(userEmail2)(pr); + expect(result).toBe(false); + }); +}); + +describe('reviewerFilter', () => { + it('should return false if reviewer has no vote and is not required', () => { + const reviewer = { + voteStatus: PullRequestVoteStatus.NoVote, + isRequired: false, + } as Reviewer; + const result = reviewerFilter(reviewer); + expect(result).toBe(false); + }); + + it('should return true if reviewer has no vote and is required', () => { + const reviewer = { + voteStatus: PullRequestVoteStatus.NoVote, + isRequired: true, + } as Reviewer; + const result = reviewerFilter(reviewer); + expect(result).toBe(true); + }); + + it('should return true if reviewer has a vote and is not a container', () => { + const reviewer = { + voteStatus: PullRequestVoteStatus.Approved, + isContainer: false, + } as Reviewer; + const result = reviewerFilter(reviewer); + expect(result).toBe(true); + }); + + it('should return true if reviewer has a vote and is a container', () => { + const reviewer = { + voteStatus: PullRequestVoteStatus.Approved, + isContainer: true, + } as Reviewer; + const result = reviewerFilter(reviewer); + expect(result).toBe(false); + }); +}); + +describe('arrayExtract', () => { + it('should extract numbers greater than 3', () => { + const numbers = [1, 2, 3, 4, 5, 6]; + const numberFilter = (num: number): boolean => num > 3; + const extractedNumbers = arrayExtract(numbers, numberFilter); + expect(numbers).toEqual([1, 2, 3]); + expect(extractedNumbers).toEqual([4, 5, 6]); + }); + + it('should extract even numbers', () => { + const numbers = [1, 2, 3, 4, 5, 6]; + const numberFilter = (num: number): boolean => num % 2 === 0; + const extractedNumbers = arrayExtract(numbers, numberFilter); + expect(numbers).toEqual([1, 3, 5]); + expect(extractedNumbers).toEqual([2, 4, 6]); + }); +}); + +describe('getPullRequestGroups', () => { + it('should create groups of pull requests based on the provided configs', () => { + const userEmail = 'user1@backstage.com'; + const userEmail2 = 'user2@backstage.com'; + + const pullRequests = [ + { + pullRequestId: 1, + createdBy: { uniqueName: userEmail }, + } as DashboardPullRequest, + { + pullRequestId: 2, + createdBy: { uniqueName: userEmail }, + } as DashboardPullRequest, + { + pullRequestId: 3, + createdBy: { uniqueName: userEmail2 }, + } as DashboardPullRequest, + { + pullRequestId: 4, + createdBy: { uniqueName: userEmail2 }, + } as DashboardPullRequest, + ]; + + const configs = [ + { title: 'Created by me', filter: getCreatedByUserFilter(userEmail) }, + { title: 'Other PRs', filter: (_: unknown) => true, simplified: true }, + ]; + + const result = getPullRequestGroups(pullRequests, configs); + + expect(result.length).toBe(2); + + const group1 = result[0]; + expect(group1.title).toBe('Created by me'); + expect(group1.simplified).toBeFalsy(); + expect(group1.pullRequests.length).toBe(2); + expect(group1.pullRequests).toContainEqual( + expect.objectContaining({ pullRequestId: 1 }), + ); + expect(group1.pullRequests).toContainEqual( + expect.objectContaining({ pullRequestId: 2 }), + ); + + const group2 = result[1]; + expect(group2.title).toBe('Other PRs'); + expect(group2.simplified).toBe(true); + expect(group2.pullRequests.length).toBe(2); + expect(group2.pullRequests).toContainEqual( + expect.objectContaining({ pullRequestId: 3 }), + ); + expect(group2.pullRequests).toContainEqual( + expect.objectContaining({ pullRequestId: 4 }), + ); + }); +}); From 367cd7a2ac562957749b82c273f9365fb10e465d Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Thu, 11 Nov 2021 12:22:59 +0000 Subject: [PATCH 14/25] refactor: Updated type properties to be optional instead of undefined union. Signed-off-by: Marley Powell --- plugins/azure-devops-common/src/types.ts | 60 ++++++++++++------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/plugins/azure-devops-common/src/types.ts b/plugins/azure-devops-common/src/types.ts index 458eaa76d4..61488d4e98 100644 --- a/plugins/azure-devops-common/src/types.ts +++ b/plugins/azure-devops-common/src/types.ts @@ -128,54 +128,54 @@ export type PullRequestOptions = { }; export interface DashboardPullRequest { - pullRequestId: number | undefined; - title: string | undefined; - description: string | undefined; - repository: Repository | undefined; - createdBy: CreatedBy | undefined; + pullRequestId?: number; + title?: string; + description?: string; + repository?: Repository; + createdBy?: CreatedBy; hasAutoComplete: boolean; - policies: Policy[] | undefined; - reviewers: Reviewer[] | undefined; - creationDate: string | undefined; - status: PullRequestStatus | undefined; - isDraft: boolean | undefined; - link: string | undefined; + policies?: Policy[]; + reviewers?: Reviewer[]; + creationDate?: string; + status?: PullRequestStatus; + isDraft?: boolean; + link?: string; } export interface Reviewer { - id: string | undefined; - displayName: string | undefined; - imageUrl: string | undefined; - isRequired: boolean | undefined; - isContainer: boolean | undefined; + id?: string; + displayName?: string; + imageUrl?: string; + isRequired?: boolean; + isContainer?: boolean; voteStatus: PullRequestVoteStatus; } export interface Policy { - id: number | undefined; + id?: number; type: PolicyType; - status: PolicyEvaluationStatus | undefined; - text: string | undefined; - link: string | undefined; + status?: PolicyEvaluationStatus; + text?: string; + link?: string; } export interface CreatedBy { - id: string | undefined; - displayName: string | undefined; - uniqueName: string | undefined; - imageUrl: string | undefined; + id?: string; + displayName?: string; + uniqueName?: string; + imageUrl?: string; } export interface Repository { - id: string | undefined; - name: string | undefined; - url: string | undefined; + id?: string; + name?: string; + url?: string; } export interface Team { - id: string | undefined; - name: string | undefined; - memberIds: string[] | undefined; + id?: string; + name?: string; + memberIds?: string[]; } /** From 051561b635b7bee85a1af15202269cf486be2527 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Thu, 11 Nov 2021 13:41:55 +0000 Subject: [PATCH 15/25] feat: Added initial logic to `PullRequestsPage` for displaying pull request groups. Signed-off-by: Marley Powell --- .../PullRequestsPage/PullRequestsPage.tsx | 75 ++++++++++++++++++- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/plugins/azure-devops/src/components/PullRequestsPage/PullRequestsPage.tsx b/plugins/azure-devops/src/components/PullRequestsPage/PullRequestsPage.tsx index e22476f742..c3f0536749 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/PullRequestsPage.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/PullRequestsPage.tsx @@ -14,15 +14,84 @@ * limitations under the License. */ -import { Content, Header, Page } from '@backstage/core-components'; +import { + Content, + Header, + Page, + ResponseErrorPanel, +} from '@backstage/core-components'; +import { PullRequestGroup, PullRequestGroupConfig } from './lib/types'; +import React, { useEffect, useState } from 'react'; +import { getCreatedByUserFilter, getPullRequestGroups } from './lib/utils'; +import { useDashboardPullRequests, useUserEmail } from '../../hooks'; -import React from 'react'; +import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common'; +import { PullRequestGrid } from './lib/PullRequestGrid'; + +/** + * @deprecated TEMPORARY - This will be configurable in a follow up PR. + */ +const PROJECT_NAME = 'projectName'; + +function usePullRequestGroupConfigs( + userEmail: string | undefined, +): PullRequestGroupConfig[] { + const [pullRequestGroupConfigs, setPullRequestGroupConfigs] = useState< + PullRequestGroupConfig[] + >([]); + + useEffect(() => { + const prGroupConfigs: PullRequestGroupConfig[] = [ + { title: 'Created by me', filter: getCreatedByUserFilter(userEmail) }, + { title: 'Other PRs', filter: _ => true, simplified: false }, + ]; + + setPullRequestGroupConfigs(prGroupConfigs); + }, [userEmail]); + + return pullRequestGroupConfigs; +} + +function usePullRequestGroups( + pullRequests: DashboardPullRequest[] | undefined, + pullRequestGroupConfigs: PullRequestGroupConfig[], +): PullRequestGroup[] { + const [pullRequestGroups, setPullRequestGroups] = useState< + PullRequestGroup[] + >([]); + + useEffect(() => { + if (pullRequests) { + const groups = getPullRequestGroups( + pullRequests, + pullRequestGroupConfigs, + ); + setPullRequestGroups(groups); + } + }, [pullRequests, pullRequestGroupConfigs]); + + return pullRequestGroups; +} export const PullRequestsPage = () => { + const { pullRequests, error } = useDashboardPullRequests(PROJECT_NAME); + const userEmail = useUserEmail(); + const pullRequestGroupConfigs = usePullRequestGroupConfigs(userEmail); + const pullRequestGroups = usePullRequestGroups( + pullRequests, + pullRequestGroupConfigs, + ); + + const pullRequestsContent = error ? ( + + ) : ( + + ); + return (
- + {pullRequestsContent} ); }; From b585da59e1361fc215a4ef8db92e900b7b282e39 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Thu, 11 Nov 2021 13:56:14 +0000 Subject: [PATCH 16/25] refactor: Reworked policy icon logic. Signed-off-by: Marley Powell --- .../PullRequestCardPolicies.tsx | 59 +++---------------- .../PullRequestCard/PullRequestCardPolicy.tsx | 39 +++++++++--- 2 files changed, 39 insertions(+), 59 deletions(-) diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicies.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicies.tsx index 21fe7ba034..e520b480d6 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicies.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicies.tsx @@ -14,59 +14,10 @@ * limitations under the License. */ -import { - Policy, - PolicyEvaluationStatus, - PolicyType, -} from '@backstage/plugin-azure-devops-common'; -import { - PolicyInProgressIcon, - PolicyIssueIcon, - PolicyRequiredIcon, - PullRequestCardPolicy, -} from './PullRequestCardPolicy'; - +import { Policy } from '@backstage/plugin-azure-devops-common'; +import { PullRequestCardPolicy } from './PullRequestCardPolicy'; import React from 'react'; -function getPullRequestCardPolicy(policy: Policy): JSX.Element | null { - switch (policy.type) { - case PolicyType.Build: - switch (policy.status) { - case PolicyEvaluationStatus.Running: - return ( - } - /> - ); - case PolicyEvaluationStatus.Rejected: - return ( - } /> - ); - case PolicyEvaluationStatus.Queued: - return ( - } - /> - ); - default: - return null; - } - case PolicyType.MinimumReviewers: - return ( - } /> - ); - case PolicyType.Status: - case PolicyType.Comments: - return ( - } /> - ); - default: - return null; - } -} - type PullRequestCardProps = { policies: Policy[]; className: string; @@ -76,5 +27,9 @@ export const PullRequestCardPolicies = ({ policies, className, }: PullRequestCardProps) => ( -
{policies.map(getPullRequestCardPolicy)}
+
+ {policies.map(policy => ( + + ))} +
); diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicy.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicy.tsx index 22f9bca39b..42343a8b27 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicy.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicy.tsx @@ -14,15 +14,19 @@ * limitations under the License. */ +import { + Policy, + PolicyEvaluationStatus, + PolicyType, +} from '@backstage/plugin-azure-devops-common'; import { styled, withStyles } from '@material-ui/core/styles'; import CancelIcon from '@material-ui/icons/Cancel'; import GroupWorkIcon from '@material-ui/icons/GroupWork'; -import { Policy } from '@backstage/plugin-azure-devops-common'; import React from 'react'; import WatchLaterIcon from '@material-ui/icons/WatchLater'; -export const PolicyRequiredIcon = withStyles( +const PolicyRequiredIcon = withStyles( theme => ({ root: { color: theme.palette.info.main, @@ -31,7 +35,7 @@ export const PolicyRequiredIcon = withStyles( { name: 'PolicyRequiredIcon' }, )(WatchLaterIcon); -export const PolicyIssueIcon = withStyles( +const PolicyIssueIcon = withStyles( theme => ({ root: { color: theme.palette.error.main, @@ -40,7 +44,7 @@ export const PolicyIssueIcon = withStyles( { name: 'PolicyIssueIcon' }, )(CancelIcon); -export const PolicyInProgressIcon = withStyles( +const PolicyInProgressIcon = withStyles( theme => ({ root: { color: theme.palette.info.main, @@ -49,6 +53,29 @@ export const PolicyInProgressIcon = withStyles( { name: 'PolicyInProgressIcon' }, )(GroupWorkIcon); +function getPolicyIcon(policy: Policy): JSX.Element | null { + switch (policy.type) { + case PolicyType.Build: + switch (policy.status) { + case PolicyEvaluationStatus.Running: + return ; + case PolicyEvaluationStatus.Rejected: + return ; + case PolicyEvaluationStatus.Queued: + return ; + default: + return null; + } + case PolicyType.MinimumReviewers: + return ; + case PolicyType.Status: + case PolicyType.Comments: + return ; + default: + return null; + } +} + const PullRequestCardPolicyContainer = styled('div')({ display: 'flex', alignItems: 'center', @@ -57,14 +84,12 @@ const PullRequestCardPolicyContainer = styled('div')({ type PullRequestCardPolicyProps = { policy: Policy; - icon: JSX.Element; }; export const PullRequestCardPolicy = ({ policy, - icon, }: PullRequestCardPolicyProps) => ( - {icon} {policy.text} + {getPolicyIcon(policy)} {policy.text} ); From cfa98e33f93f30504446d13a77d432e8b1f9ceeb Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Thu, 11 Nov 2021 13:58:00 +0000 Subject: [PATCH 17/25] fix: Added `key` properties to list items. Signed-off-by: Marley Powell --- .../lib/PullRequestCard/PullRequestCardPolicies.tsx | 2 +- .../lib/PullRequestCard/PullRequestCardReviewers.tsx | 2 +- .../lib/PullRequestGrid/PullRequestGrid.tsx | 7 +++++-- .../lib/PullRequestGridColumn/PullRequestGridColumn.tsx | 1 + 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicies.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicies.tsx index e520b480d6..ec6389ab1e 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicies.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardPolicies.tsx @@ -29,7 +29,7 @@ export const PullRequestCardPolicies = ({ }: PullRequestCardProps) => (
{policies.map(policy => ( - + ))}
); diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewers.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewers.tsx index 2a9a265756..e3bf0c8a03 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewers.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCardReviewers.tsx @@ -36,7 +36,7 @@ export const PullRequestCardReviewers = ({ }: PullRequestCardReviewersProps) => ( {reviewers.filter(reviewerFilter).map(reviewer => ( - + ))} ); diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGrid/PullRequestGrid.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGrid/PullRequestGrid.tsx index 68c6ad8d51..8b9c49beb4 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGrid/PullRequestGrid.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGrid/PullRequestGrid.tsx @@ -40,8 +40,11 @@ export const PullRequestGrid = ({ }: PullRequestGridProps) => { return ( - {pullRequestGroups.map(pullRequestGroup => ( - + {pullRequestGroups.map((pullRequestGroup, index) => ( + ))} ); diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGridColumn/PullRequestGridColumn.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGridColumn/PullRequestGridColumn.tsx index 2db77c4a49..94a648188d 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGridColumn/PullRequestGridColumn.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGridColumn/PullRequestGridColumn.tsx @@ -70,6 +70,7 @@ export const PullRequestGridColumn = ({ {pullRequestGroup.pullRequests.map(pullRequest => ( From d3f3e3dc7bc26a540eae9df7d77ecbe38db9f0c7 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Thu, 11 Nov 2021 14:04:50 +0000 Subject: [PATCH 18/25] fix: Fixed issue with `AutoCompleteIcon` component props. Signed-off-by: Marley Powell --- .../lib/AutoCompleteIcon/AutoCompleteIcon.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/AutoCompleteIcon/AutoCompleteIcon.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/AutoCompleteIcon/AutoCompleteIcon.tsx index aea64ff23b..ca43b8e27b 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/AutoCompleteIcon/AutoCompleteIcon.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/AutoCompleteIcon/AutoCompleteIcon.tsx @@ -15,15 +15,18 @@ */ import DoneAllIcon from '@material-ui/icons/DoneAll'; -import { withStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; -export const AutoCompleteIcon = withStyles( - theme => ({ - root: (props: { hasAutoComplete: boolean }) => ({ - color: props.hasAutoComplete - ? theme.palette.success.main - : theme.palette.grey[400], - }), +const useStyles = makeStyles(theme => ({ + root: (props: { hasAutoComplete: boolean }) => ({ + color: props.hasAutoComplete + ? theme.palette.success.main + : theme.palette.grey[400], }), - { name: 'AutoCompleteIcon' }, -)(DoneAllIcon); +})); + +export const AutoCompleteIcon = (props: { hasAutoComplete: boolean }) => { + const classes = useStyles(props); + return ; +}; From d75972b3525b465afb28cf65538599784bd6a093 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Thu, 11 Nov 2021 14:09:01 +0000 Subject: [PATCH 19/25] chore: Updated `api-report`. Signed-off-by: Marley Powell --- plugins/azure-devops/api-report.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/plugins/azure-devops/api-report.md b/plugins/azure-devops/api-report.md index fbe28c179c..6b304938b6 100644 --- a/plugins/azure-devops/api-report.md +++ b/plugins/azure-devops/api-report.md @@ -8,17 +8,29 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { RouteRef } from '@backstage/core-plugin-api'; +import { SvgIconProps } from '@material-ui/core'; // Warning: (ae-missing-release-tag) "azureDevOpsPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const azureDevOpsPlugin: BackstagePlugin< { - entityContent: RouteRef; + azurePrsRoot: RouteRef; + azurePipelinesEntityContent: RouteRef; }, {} >; +// Warning: (ae-missing-release-tag) "AzurePullRequestsIcon" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const AzurePullRequestsIcon: (props: SvgIconProps) => JSX.Element; + +// Warning: (ae-missing-release-tag) "AzurePullRequestsPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const AzurePullRequestsPage: () => JSX.Element; + // Warning: (ae-missing-release-tag) "EntityAzurePipelinesContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) From 9a84e7f4e967fd880c20b51ed85c6da4102fb176 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Thu, 11 Nov 2021 14:19:26 +0000 Subject: [PATCH 20/25] chore: Updated `api-report`. Signed-off-by: Marley Powell --- plugins/azure-devops-common/api-report.md | 160 ++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/plugins/azure-devops-common/api-report.md b/plugins/azure-devops-common/api-report.md index f03c0b4059..650ab97157 100644 --- a/plugins/azure-devops-common/api-report.md +++ b/plugins/azure-devops-common/api-report.md @@ -27,6 +27,108 @@ export enum BuildStatus { Postponed = 8, } +// Warning: (ae-missing-release-tag) "CreatedBy" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface CreatedBy { + // (undocumented) + displayName?: string; + // (undocumented) + id?: string; + // (undocumented) + imageUrl?: string; + // (undocumented) + uniqueName?: string; +} + +// Warning: (ae-missing-release-tag) "DashboardPullRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface DashboardPullRequest { + // (undocumented) + createdBy?: CreatedBy; + // (undocumented) + creationDate?: string; + // (undocumented) + description?: string; + // (undocumented) + hasAutoComplete: boolean; + // (undocumented) + isDraft?: boolean; + // (undocumented) + link?: string; + // (undocumented) + policies?: Policy[]; + // (undocumented) + pullRequestId?: number; + // (undocumented) + repository?: Repository; + // (undocumented) + reviewers?: Reviewer[]; + // (undocumented) + status?: PullRequestStatus; + // (undocumented) + title?: string; +} + +// Warning: (ae-missing-release-tag) "Policy" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface Policy { + // (undocumented) + id?: number; + // (undocumented) + link?: string; + // (undocumented) + status?: PolicyEvaluationStatus; + // (undocumented) + text?: string; + // (undocumented) + type: PolicyType; +} + +// Warning: (ae-missing-release-tag) "PolicyEvaluationStatus" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export enum PolicyEvaluationStatus { + Approved = 2, + Broken = 5, + NotApplicable = 4, + Queued = 0, + Rejected = 3, + Running = 1, +} + +// Warning: (ae-missing-release-tag) "PolicyType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export enum PolicyType { + // (undocumented) + Build = 'Build', + // (undocumented) + Comments = 'Comments', + // (undocumented) + MergeStrategy = 'MergeStrategy', + // (undocumented) + MinimumReviewers = 'MinimumReviewers', + // (undocumented) + RequiredReviewers = 'RequiredReviewers', + // (undocumented) + Status = 'Status', +} + +// Warning: (ae-missing-release-tag) "PolicyTypeId" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const PolicyTypeId: { + Build: string; + Status: string; + MinimumReviewers: string; + Comments: string; + RequiredReviewers: string; + MergeStrategy: string; +}; + // Warning: (ae-missing-release-tag) "PullRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -63,6 +165,22 @@ export enum PullRequestStatus { NotSet = 0, } +// Warning: (ae-missing-release-tag) "PullRequestVoteStatus" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export enum PullRequestVoteStatus { + // (undocumented) + Approved = 10, + // (undocumented) + ApprovedWithSuggestions = 5, + // (undocumented) + NoVote = 0, + // (undocumented) + Rejected = -10, + // (undocumented) + WaitingForAuthor = -5, +} + // Warning: (ae-missing-release-tag) "RepoBuild" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -86,5 +204,47 @@ export type RepoBuildOptions = { top?: number; }; +// Warning: (ae-missing-release-tag) "Repository" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface Repository { + // (undocumented) + id?: string; + // (undocumented) + name?: string; + // (undocumented) + url?: string; +} + +// Warning: (ae-missing-release-tag) "Reviewer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface Reviewer { + // (undocumented) + displayName?: string; + // (undocumented) + id?: string; + // (undocumented) + imageUrl?: string; + // (undocumented) + isContainer?: boolean; + // (undocumented) + isRequired?: boolean; + // (undocumented) + voteStatus: PullRequestVoteStatus; +} + +// Warning: (ae-missing-release-tag) "Team" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface Team { + // (undocumented) + id?: string; + // (undocumented) + memberIds?: string[]; + // (undocumented) + name?: string; +} + // (No @packageDocumentation comment for this package) ``` From caa2bc31f152d2d23681f426ec2aff0692b011a7 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Fri, 12 Nov 2021 11:19:06 +0000 Subject: [PATCH 21/25] refactor: Updated `PullRequestCard` to use `Link` from `@backstage/core-components`. Signed-off-by: Marley Powell --- .../lib/PullRequestCard/PullRequestCard.tsx | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx index cfb0a121db..181e016e93 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx @@ -14,10 +14,10 @@ * limitations under the License. */ -import { Card, CardContent, CardHeader, Link } from '@material-ui/core'; +import { Avatar, Link } from '@backstage/core-components'; +import { Card, CardContent, CardHeader } from '@material-ui/core'; import { AutoCompleteIcon } from '../AutoCompleteIcon'; -import { Avatar } from '@backstage/core-components'; import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common'; import { DateTime } from 'luxon'; import { PullRequestCardPolicies } from './PullRequestCardPolicies'; @@ -62,24 +62,14 @@ export const PullRequestCard = ({ simplified, }: PullRequestCardProps) => { const title = ( - + {pullRequest.title} ); const subheader = ( - + {pullRequest.repository?.name} {' '} 路{' '} From fa6c26f68d010ce6c5acecf155162145e92baadc Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Fri, 12 Nov 2021 11:23:45 +0000 Subject: [PATCH 22/25] refactor: Renamed `azurePrsRootRouteRef` to `azurePullRequestDashboardRouteRef`. Signed-off-by: Marley Powell --- plugins/azure-devops/api-report.md | 2 +- plugins/azure-devops/src/plugin.ts | 6 +++--- plugins/azure-devops/src/routes.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/azure-devops/api-report.md b/plugins/azure-devops/api-report.md index 6b304938b6..96ea95e00e 100644 --- a/plugins/azure-devops/api-report.md +++ b/plugins/azure-devops/api-report.md @@ -15,7 +15,7 @@ import { SvgIconProps } from '@material-ui/core'; // @public (undocumented) export const azureDevOpsPlugin: BackstagePlugin< { - azurePrsRoot: RouteRef; + azurePullRequestDashboard: RouteRef; azurePipelinesEntityContent: RouteRef; }, {} diff --git a/plugins/azure-devops/src/plugin.ts b/plugins/azure-devops/src/plugin.ts index 7553f02da1..06c79b1742 100644 --- a/plugins/azure-devops/src/plugin.ts +++ b/plugins/azure-devops/src/plugin.ts @@ -16,7 +16,7 @@ import { azurePipelinesEntityContentRouteRef, - azurePrsRootRouteRef, + azurePullRequestDashboardRouteRef, } from './routes'; import { createApiFactory, @@ -45,7 +45,7 @@ export const azureDevOpsPlugin = createPlugin({ }), ], routes: { - azurePrsRoot: azurePrsRootRouteRef, + azurePullRequestDashboard: azurePullRequestDashboardRouteRef, azurePipelinesEntityContent: azurePipelinesEntityContentRouteRef, }, }); @@ -55,7 +55,7 @@ export const AzurePullRequestsPage = azureDevOpsPlugin.provide( name: 'AzurePullRequestsPage', component: () => import('./components/PullRequestsPage').then(m => m.PullRequestsPage), - mountPoint: azurePrsRootRouteRef, + mountPoint: azurePullRequestDashboardRouteRef, }), ); diff --git a/plugins/azure-devops/src/routes.ts b/plugins/azure-devops/src/routes.ts index 6884c3d2b2..9573ff5c69 100644 --- a/plugins/azure-devops/src/routes.ts +++ b/plugins/azure-devops/src/routes.ts @@ -16,7 +16,7 @@ import { createRouteRef } from '@backstage/core-plugin-api'; -export const azurePrsRootRouteRef = createRouteRef({ +export const azurePullRequestDashboardRouteRef = createRouteRef({ path: '', title: 'Azure Pull Requests', }); From 0749dd0307384a1501609d0eaf20145e27aff562 Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Thu, 25 Nov 2021 09:34:35 +0000 Subject: [PATCH 23/25] chore: Generated changeset. Signed-off-by: Marley Powell --- .changeset/chilly-queens-bow.md | 6 ++++++ plugins/azure-devops/src/hooks/index.ts | 1 + 2 files changed, 7 insertions(+) create mode 100644 .changeset/chilly-queens-bow.md diff --git a/.changeset/chilly-queens-bow.md b/.changeset/chilly-queens-bow.md new file mode 100644 index 0000000000..00e8628776 --- /dev/null +++ b/.changeset/chilly-queens-bow.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-azure-devops': patch +'@backstage/plugin-azure-devops-common': patch +--- + +feat: Created pull request card component and initial pull request dashboard page. diff --git a/plugins/azure-devops/src/hooks/index.ts b/plugins/azure-devops/src/hooks/index.ts index 29a2a3b568..44b94f3f4b 100644 --- a/plugins/azure-devops/src/hooks/index.ts +++ b/plugins/azure-devops/src/hooks/index.ts @@ -17,5 +17,6 @@ export * from './useAllTeams'; export * from './useDashboardPullRequests'; export * from './useProjectRepoFromEntity'; +export * from './usePullRequests'; export * from './useRepoBuilds'; export * from './useUserEmail'; From 2439a1acbdf2c55ba61d3c69fde4d2e8361d0bbe Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Fri, 26 Nov 2021 13:22:02 +0000 Subject: [PATCH 24/25] refactor: Small change to `PullRequestCard` subheader. Signed-off-by: Marley Powell --- .../lib/PullRequestCard/PullRequestCard.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx index 181e016e93..b8b052425c 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx @@ -67,14 +67,19 @@ export const PullRequestCard = ({ ); + const repoLink = ( + + {pullRequest.repository?.name} + + ); + + const creationDate = pullRequest.creationDate + ? DateTime.fromISO(pullRequest.creationDate).toRelative() + : undefined; + const subheader = ( - - {pullRequest.repository?.name} - {' '} - 路{' '} - {pullRequest.creationDate && - DateTime.fromISO(pullRequest.creationDate).toRelative()} + {repoLink}路{creationDate} ); From e66d33c1e8d4dc3d1b4285f2681e10302271b79c Mon Sep 17 00:00:00 2001 From: Marley Powell Date: Tue, 30 Nov 2021 13:19:42 +0000 Subject: [PATCH 25/25] refactor: Use named export for `AzurePullRequestsIcon`. Signed-off-by: Marley Powell --- plugins/azure-devops/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/azure-devops/src/index.ts b/plugins/azure-devops/src/index.ts index 622f5560b9..c9b804bc8d 100644 --- a/plugins/azure-devops/src/index.ts +++ b/plugins/azure-devops/src/index.ts @@ -22,4 +22,4 @@ export { AzurePullRequestsPage, } from './plugin'; -export * from './components/AzurePullRequestsIcon'; +export { AzurePullRequestsIcon } from './components/AzurePullRequestsIcon';