diff --git a/catalog-info.yaml b/catalog-info.yaml index 6de6fd3426..ff894befbc 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -18,6 +18,6 @@ metadata: backstage.io/techdocs-ref: dir:. lighthouse.com/website-url: https://backstage.io spec: - type: library + type: service owner: CNCF lifecycle: experimental diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 3fe027ddad..4f95e89636 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -35,6 +35,7 @@ import { } from '@backstage/plugin-api-docs'; import { EntityAzurePipelinesContent, + EntityAzureReposContent, isAzureDevOpsAvailable, } from '@backstage/plugin-azure-devops'; import { EntityBadgesDialog } from '@backstage/plugin-badges'; @@ -264,6 +265,18 @@ const errorsContent = ( ); +const pullRequestsContent = ( + + + + + + + + + +); + const overviewContent = ( {entityWarningContent} @@ -368,7 +381,7 @@ const serviceEntityPage = ( - + {pullRequestsContent} @@ -427,7 +440,7 @@ const websiteEntityPage = ( - + {pullRequestsContent} diff --git a/plugins/azure-devops/README.md b/plugins/azure-devops/README.md index 47a0c4debd..f33bd3bdb6 100644 --- a/plugins/azure-devops/README.md +++ b/plugins/azure-devops/README.md @@ -6,10 +6,16 @@ Website: [https://dev.azure.com/](https://dev.azure.com/) ### Azure Pipelines -Lists the top _n_ builds for a given repository where _n_ is a configurable value +Lists the top _n_ builds for a given Azure Repo where _n_ is a configurable value ![Azure Pipelines Builds Example](./docs/azure-devops-builds.png) +### Azure Repos + +Lists the top _n_ Active, Completed, or Abandoned Pull Requests for a given repository where _n_ is a configurable value + +![Azure Repos Pull Requests Example](./docs/azure-devops-pull-requests.png) + ## Setup The following sections will help you get the Azure DevOps plugin setup and running @@ -69,14 +75,55 @@ To get the Azure Pipelines component working you'll need to do the following two // ... - // Set defaultLimit to the max number of builds you would like to be able to see - // the default if not set is 10 // ... ``` +**Notes:** + +- The `if` prop is optional on the `EntitySwitch.Case`, you can remove it if you always want to see the tab even if the entity being viewed does not have the needed annotation +- The `defaultLimit` proper on the `EntityAzurePipelinesContent` will set the max number of Builds you would like to see, if not set this will default to 10 + +### Azure Repos Component + +To get the Azure Repos component working you'll need to do the following two steps: + +1. First we need to add the @backstage/plugin-azure-devops package to your frontend app: + + ```bash + # From your Backstage root directory + cd packages/app + yarn add @backstage/plugin-azure-devops + ``` + +2. Second we need to add the `EntityAzureReposContent` extension to the entity page in your app: + + ```tsx + // In packages/app/src/components/catalog/EntityPage.tsx + import { + EntityAzureReposContent, + isAzureDevOpsAvailable, + } from '@backstage/plugin-azure-devops'; + + // For example in the Service section + const serviceEntityPage = ( + + // ... + + + + // ... + + ``` + +**Notes:** + +- You'll need to add the `EntityLayout.Route` above from step 2 to all the entity sections you want to see Pull Requests in. For example if you wanted to see Pull Requests when looking at Website entities then you would need to add this to the `websiteEntityPage` section. +- The `if` prop is optional on the `EntityLayout.Route`, you can remove it if you always want to see the tab even if the entity being viewed does not have the needed annotation +- The `defaultLimit` proper on the `EntityAzureReposContent` will set the max number of Pull Requests you would like to see, if not set this will default to 10 + ## Limitations - Currently multiple organizations are not supported diff --git a/plugins/azure-devops/api-report.md b/plugins/azure-devops/api-report.md index fbe28c179c..fef690ed08 100644 --- a/plugins/azure-devops/api-report.md +++ b/plugins/azure-devops/api-report.md @@ -28,6 +28,15 @@ export const EntityAzurePipelinesContent: ({ defaultLimit?: number | undefined; }) => JSX.Element; +// Warning: (ae-missing-release-tag) "EntityAzureReposContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const EntityAzureReposContent: ({ + defaultLimit, +}: { + defaultLimit?: number | undefined; +}) => JSX.Element; + // Warning: (ae-missing-release-tag) "isAzureDevOpsAvailable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/azure-devops/docs/azure-devops-builds.png b/plugins/azure-devops/docs/azure-devops-builds.png index 4788a185ed..6ff93dc7e4 100644 Binary files a/plugins/azure-devops/docs/azure-devops-builds.png and b/plugins/azure-devops/docs/azure-devops-builds.png differ diff --git a/plugins/azure-devops/docs/azure-devops-pull-requests.png b/plugins/azure-devops/docs/azure-devops-pull-requests.png new file mode 100644 index 0000000000..ce1b2a4267 Binary files /dev/null and b/plugins/azure-devops/docs/azure-devops-pull-requests.png differ diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index 25328d9e81..d44f67c73a 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -37,6 +37,7 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", + "humanize-duration": "^3.27.0", "luxon": "^2.0.2", "react": "^16.13.1", "react-dom": "^16.13.1", diff --git a/plugins/azure-devops/src/api/AzureDevOpsApi.ts b/plugins/azure-devops/src/api/AzureDevOpsApi.ts index 05377b0b7b..90d291b25d 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsApi.ts @@ -15,6 +15,8 @@ */ import { + PullRequest, + PullRequestOptions, RepoBuild, RepoBuildOptions, } from '@backstage/plugin-azure-devops-common'; @@ -33,4 +35,10 @@ export interface AzureDevOpsApi { repoName: string, options?: RepoBuildOptions, ): Promise<{ items: RepoBuild[] }>; + + getPullRequests( + projectName: string, + repoName: string, + options?: PullRequestOptions, + ): Promise<{ items: PullRequest[] }>; } diff --git a/plugins/azure-devops/src/api/AzureDevOpsClient.ts b/plugins/azure-devops/src/api/AzureDevOpsClient.ts index 4fd4e781c4..7e566e458f 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsClient.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsClient.ts @@ -16,6 +16,8 @@ import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; import { + PullRequest, + PullRequestOptions, RepoBuild, RepoBuildOptions, } from '@backstage/plugin-azure-devops-common'; @@ -46,6 +48,17 @@ export class AzureDevOpsClient implements AzureDevOpsApi { return { items }; } + async getPullRequests( + projectName: string, + repoName: string, + options?: PullRequestOptions, + ): Promise<{ items: PullRequest[] }> { + const items = await this.get( + `pull-requests/${projectName}/${repoName}?top=${options?.top}&status=${options?.status}`, + ); + return { items }; + } + private async get(path: string): Promise { const baseUrl = `${await this.discoveryApi.getBaseUrl('azure-devops')}/`; const url = new URL(path, baseUrl); diff --git a/plugins/azure-devops/src/components/AzurePipelinesIcon/AzurePipelinesIcon.tsx b/plugins/azure-devops/src/components/AzurePipelinesIcon/AzurePipelinesIcon.tsx new file mode 100644 index 0000000000..a59a088936 --- /dev/null +++ b/plugins/azure-devops/src/components/AzurePipelinesIcon/AzurePipelinesIcon.tsx @@ -0,0 +1,54 @@ +/* + * 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 { makeStyles, useTheme } from '@material-ui/core'; + +import AzurePipelinesSvg from '../../icons/alt-azure-pipelines-icon.svg'; +import React from 'react'; + +const useStyles = makeStyles({ + darkMode: { + filter: 'invert(100%)', + }, + lightMode: { + filter: 'invert(0%)', + }, +}); + +export const AzurePipelinesIcon = ({ + width, + height, +}: { + width: string; + height: string; +}) => { + const theme = useTheme(); + const classes = useStyles(); + + return ( + Azure Pipelines + ); +}; diff --git a/plugins/azure-devops/src/components/AzurePipelinesIcon/index.ts b/plugins/azure-devops/src/components/AzurePipelinesIcon/index.ts new file mode 100644 index 0000000000..4eb161f7bc --- /dev/null +++ b/plugins/azure-devops/src/components/AzurePipelinesIcon/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 { AzurePipelinesIcon } from './AzurePipelinesIcon'; diff --git a/plugins/azure-devops/src/components/AzureReposIcon/AzureReposIcon.tsx b/plugins/azure-devops/src/components/AzureReposIcon/AzureReposIcon.tsx new file mode 100644 index 0000000000..1f6ea98521 --- /dev/null +++ b/plugins/azure-devops/src/components/AzureReposIcon/AzureReposIcon.tsx @@ -0,0 +1,54 @@ +/* + * 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 { makeStyles, useTheme } from '@material-ui/core'; + +import AzureReposSvg from '../../icons/alt-azure-repos-icon.svg'; +import React from 'react'; + +const useStyles = makeStyles({ + darkMode: { + filter: 'invert(100%)', + }, + lightMode: { + filter: 'invert(0%)', + }, +}); + +export const AzureReposIcon = ({ + width, + height, +}: { + width: string; + height: string; +}) => { + const theme = useTheme(); + const classes = useStyles(); + + return ( + Azure Repos + ); +}; diff --git a/plugins/azure-devops/src/components/AzureReposIcon/index.ts b/plugins/azure-devops/src/components/AzureReposIcon/index.ts new file mode 100644 index 0000000000..2bed6bd5f0 --- /dev/null +++ b/plugins/azure-devops/src/components/AzureReposIcon/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 { AzureReposIcon } from './AzureReposIcon'; diff --git a/plugins/azure-devops/src/components/BuildTable/BuildTable.test.ts b/plugins/azure-devops/src/components/BuildTable/BuildTable.test.ts index cb240aef42..7f7d72b027 100644 --- a/plugins/azure-devops/src/components/BuildTable/BuildTable.test.ts +++ b/plugins/azure-devops/src/components/BuildTable/BuildTable.test.ts @@ -18,7 +18,10 @@ import { BuildResult, BuildStatus, } from '@backstage/plugin-azure-devops-common'; -import { getBuildResultComponent, getBuildStateComponent } from './BuildTable'; +import { + getBuildResultComponent, + getBuildStateComponent +} from './BuildTable'; import { renderInTestApp } from '@backstage/test-utils'; @@ -238,4 +241,4 @@ describe('getBuildStateComponent', () => { expect(getByText('Unknown')).toBeInTheDocument(); }); }); -}); +}); \ No newline at end of file diff --git a/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx b/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx index 5667c65711..27602f9345 100644 --- a/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx +++ b/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx @@ -20,6 +20,7 @@ import { BuildStatus, RepoBuild, } from '@backstage/plugin-azure-devops-common'; +import { DateTime, Interval } from 'luxon' import { Link, ResponseErrorPanel, @@ -33,8 +34,9 @@ import { TableColumn, } from '@backstage/core-components'; -import { DateTime } from 'luxon'; +import { AzurePipelinesIcon } from '../AzurePipelinesIcon'; import React from 'react'; +import humanizeDuration from 'humanize-duration'; export const getBuildResultComponent = (result: number | undefined) => { switch (result) { @@ -113,6 +115,43 @@ export const getBuildStateComponent = ( } }; +export const getBuildDuration = (startTime?: Date, finishTime?: Date) => { + if (!startTime || (!startTime && !finishTime)) { + return ''; + } + + const start = DateTime.fromISO(startTime.toString()); + const finish = finishTime + ? DateTime.fromISO(finishTime.toString()) + : DateTime.now(); + + const formatted = Interval.fromDateTimes(start, finish) + .toDuration() + .valueOf(); + + const shortEnglishHumanizer = humanizeDuration.humanizer({ + language: 'shortEn', + languages: { + shortEn: { + y: () => 'y', + mo: () => 'mo', + w: () => 'w', + d: () => 'd', + h: () => 'h', + m: () => 'm', + s: () => 's', + ms: () => 'ms', + }, + }, + }); + + return shortEnglishHumanizer(formatted, { + largest: 2, + round: true, + spacer: '', + }); +}; + const columns: TableColumn[] = [ { title: 'ID', @@ -144,6 +183,18 @@ const columns: TableColumn[] = [ ), }, + { + title: 'Duration', + field: 'queueTime', + width: 'auto', + render: (row: Partial) => ( + + + {getBuildDuration(row.startTime, row.finishTime)} + + + ), + }, { title: 'Age', field: 'queueTime', @@ -178,7 +229,13 @@ export const BuildTable = ({ items, loading, error }: BuildTableProps) => { pageSize: 5, showEmptyDataSourceMessage: !loading, }} - title={`Builds (${items ? items.length : 0})`} + title={ + + + + Azure Pipelines - Builds ({items ? items.length : 0}) + + } data={items ?? []} /> ); diff --git a/plugins/azure-devops/src/components/EntityPageAzureRepos/EntityPageAzureRepos.tsx b/plugins/azure-devops/src/components/EntityPageAzureRepos/EntityPageAzureRepos.tsx new file mode 100644 index 0000000000..b6050606ff --- /dev/null +++ b/plugins/azure-devops/src/components/EntityPageAzureRepos/EntityPageAzureRepos.tsx @@ -0,0 +1,26 @@ +/* + * 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 { PullRequestTable } from '../PullRequestTable/PullRequestTable'; +import React from 'react'; + +export const EntityPageAzureRepos = ({ + defaultLimit, +}: { + defaultLimit?: number; +}) => { + return ; +}; diff --git a/plugins/azure-devops/src/components/EntityPageAzureRepos/index.ts b/plugins/azure-devops/src/components/EntityPageAzureRepos/index.ts new file mode 100644 index 0000000000..517869027f --- /dev/null +++ b/plugins/azure-devops/src/components/EntityPageAzureRepos/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 { EntityPageAzureRepos } from './EntityPageAzureRepos'; diff --git a/plugins/azure-devops/src/components/PullRequestStatusButtonGroup/PullRequestStatusButtonGroup.tsx b/plugins/azure-devops/src/components/PullRequestStatusButtonGroup/PullRequestStatusButtonGroup.tsx new file mode 100644 index 0000000000..be0d9cac94 --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestStatusButtonGroup/PullRequestStatusButtonGroup.tsx @@ -0,0 +1,69 @@ +/* + * 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 { Button, ButtonGroup } from '@material-ui/core'; + +import { PullRequestStatus } from 'azure-devops-node-api/interfaces/GitInterfaces'; +import React from 'react'; + +export const PullRequestStatusButtonGroup = ({ + pullRequestStatusState, + setPullRequestsStatusState, +}: { + pullRequestStatusState: PullRequestStatus; + setPullRequestsStatusState: any; +}) => { + return ( + + + + + + ); +}; diff --git a/plugins/azure-devops/src/components/PullRequestStatusButtonGroup/index.ts b/plugins/azure-devops/src/components/PullRequestStatusButtonGroup/index.ts new file mode 100644 index 0000000000..10a667803a --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestStatusButtonGroup/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 { PullRequestStatusButtonGroup } from './PullRequestStatusButtonGroup'; diff --git a/plugins/azure-devops/src/components/PullRequestTable/PullRequestTable.tsx b/plugins/azure-devops/src/components/PullRequestTable/PullRequestTable.tsx new file mode 100644 index 0000000000..8c0e7697f4 --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestTable/PullRequestTable.tsx @@ -0,0 +1,136 @@ +/* + * 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 { Box, Chip } from '@material-ui/core'; +import { + Link, + ResponseErrorPanel, + Table, + TableColumn, +} from '@backstage/core-components'; +import React, { useState } from 'react'; + +import { AzureReposIcon } from '../AzureReposIcon'; +import { DateTime } from 'luxon'; +import { PullRequest } from '../../api/types'; +import { PullRequestStatus } from 'azure-devops-node-api/interfaces/GitInterfaces'; +import { PullRequestStatusButtonGroup } from '../PullRequestStatusButtonGroup'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { usePullRequests } from '../../hooks/usePullRequests'; + +const columns: TableColumn[] = [ + { + title: 'ID', + field: 'pullRequestId', + highlight: false, + width: 'auto', + }, + { + title: 'Title', + field: 'title', + width: 'auto', + render: (row: Partial) => ( + + {row.title} + + {row.isDraft && ( + + )} + + + ), + }, + { + title: 'Source', + field: 'sourceRefName', + width: 'auto', + }, + { + title: 'Target', + field: 'targetRefName', + width: 'auto', + }, + { + title: 'Created By', + field: 'createdBy', + width: 'auto', + }, + { + title: 'Created', + field: 'creationDate', + width: 'auto', + render: (row: Partial) => + DateTime.fromISO( + row.creationDate ? row.creationDate.toString() : new Date().toString(), + ).toRelative(), + }, +]; + +type PullRequestTableProps = { + defaultLimit?: number; +}; + +export const PullRequestTable = ({ defaultLimit }: PullRequestTableProps) => { + const [pullRequestStatusState, setPullRequestStatusState] = + useState(PullRequestStatus.Active); + const { entity } = useEntity(); + + const { items, loading, error } = usePullRequests( + entity, + defaultLimit, + pullRequestStatusState, + ); + + if (error) { + return ( +
+ +
+ ); + } + + return ( + + + + Azure Repos - Pull Requests ({items ? items.length : 0}) + + + + + } + data={items ?? []} + /> + ); +}; diff --git a/plugins/azure-devops/src/components/PullRequestTable/index.ts b/plugins/azure-devops/src/components/PullRequestTable/index.ts new file mode 100644 index 0000000000..956e8afa2d --- /dev/null +++ b/plugins/azure-devops/src/components/PullRequestTable/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 { PullRequestTable } from './PullRequestTable'; diff --git a/plugins/azure-devops/src/hooks/usePullRequests.ts b/plugins/azure-devops/src/hooks/usePullRequests.ts new file mode 100644 index 0000000000..eea0c37d58 --- /dev/null +++ b/plugins/azure-devops/src/hooks/usePullRequests.ts @@ -0,0 +1,55 @@ +/* + * 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, PullRequestOptions } from '../api/types'; + +import { AZURE_DEVOPS_DEFAULT_TOP } from '../constants'; +import { Entity } from '@backstage/catalog-model'; +import { PullRequestStatus } from 'azure-devops-node-api/interfaces/GitInterfaces'; +import { azureDevOpsApiRef } from '../api'; +import { useApi } from '@backstage/core-plugin-api'; +import { useAsync } from 'react-use'; +import { useProjectRepoFromEntity } from './useProjectRepoFromEntity'; + +export function usePullRequests( + entity: Entity, + defaultLimit?: number, + requestedStatus?: PullRequestStatus, +): { + items?: PullRequest[]; + loading: boolean; + error?: Error; +} { + const top = defaultLimit ?? AZURE_DEVOPS_DEFAULT_TOP; + const status = requestedStatus ?? PullRequestStatus.Active; + const options: PullRequestOptions = { + top: top, + status: status, + }; + + const api = useApi(azureDevOpsApiRef); + const { project, repo } = useProjectRepoFromEntity(entity); + + const { value, loading, error } = useAsync(() => { + return api.getPullRequests(project, repo, options); + }, [api, project, repo, entity, requestedStatus]); + + return { + items: value?.items, + loading, + error, + }; +} diff --git a/plugins/azure-devops/src/icons/alt-azure-pipelines-icon.svg b/plugins/azure-devops/src/icons/alt-azure-pipelines-icon.svg new file mode 100644 index 0000000000..9dd1776b5e --- /dev/null +++ b/plugins/azure-devops/src/icons/alt-azure-pipelines-icon.svg @@ -0,0 +1 @@ +Rocket \ No newline at end of file diff --git a/plugins/azure-devops/src/icons/alt-azure-repos-icon.svg b/plugins/azure-devops/src/icons/alt-azure-repos-icon.svg new file mode 100644 index 0000000000..1b2fb4415f --- /dev/null +++ b/plugins/azure-devops/src/icons/alt-azure-repos-icon.svg @@ -0,0 +1 @@ +Git Pull Request \ No newline at end of file diff --git a/plugins/azure-devops/src/index.ts b/plugins/azure-devops/src/index.ts index 71d3b654fe..94cc4ff367 100644 --- a/plugins/azure-devops/src/index.ts +++ b/plugins/azure-devops/src/index.ts @@ -16,5 +16,6 @@ export { azureDevOpsPlugin, EntityAzurePipelinesContent, + EntityAzureReposContent, isAzureDevOpsAvailable, } from './plugin'; diff --git a/plugins/azure-devops/src/plugin.ts b/plugins/azure-devops/src/plugin.ts index ab6d816460..595a168d6e 100644 --- a/plugins/azure-devops/src/plugin.ts +++ b/plugins/azure-devops/src/plugin.ts @@ -56,3 +56,14 @@ export const EntityAzurePipelinesContent = azureDevOpsPlugin.provide( mountPoint: azureDevOpsRouteRef, }), ); + +export const EntityAzureReposContent = azureDevOpsPlugin.provide( + createRoutableExtension({ + name: 'EntityAzureReposContent', + component: () => + import('./components/EntityPageAzureRepos').then( + m => m.EntityPageAzureRepos, + ), + mountPoint: azureDevOpsRouteRef, + }), +);