@backstage-community/plugin-bitbucket-pull-requests 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +118 -0
- package/dist/api/BitbucketApi.esm.js +153 -0
- package/dist/api/BitbucketApi.esm.js.map +1 -0
- package/dist/components/HomePage/HomePagePullRequestsCard.esm.js +64 -0
- package/dist/components/HomePage/HomePagePullRequestsCard.esm.js.map +1 -0
- package/dist/components/HomePage/HomePagePullRequestsTable.esm.js +248 -0
- package/dist/components/HomePage/HomePagePullRequestsTable.esm.js.map +1 -0
- package/dist/components/PullRequestList.esm.js +120 -0
- package/dist/components/PullRequestList.esm.js.map +1 -0
- package/dist/components/Router.esm.js +19 -0
- package/dist/components/Router.esm.js.map +1 -0
- package/dist/components/StatusFilter.esm.js +29 -0
- package/dist/components/StatusFilter.esm.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.esm.js +3 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/plugin.esm.js +35 -0
- package/dist/plugin.esm.js.map +1 -0
- package/dist/routes.esm.js +8 -0
- package/dist/routes.esm.js.map +1 -0
- package/dist/utils/isBitbucketSlugSet.esm.js +7 -0
- package/dist/utils/isBitbucketSlugSet.esm.js.map +1 -0
- package/package.json +83 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Bitbucket PullRequest Plugin for Backstage
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- List of PR's from particular bitbucket repo
|
|
8
|
+
- Filtering like OPEN/CLOSED/MERGED/ALL PR and Search
|
|
9
|
+
- Able to view Creator name, Created date and last update etc.
|
|
10
|
+
- We can go to Particular PR by clicking ID.
|
|
11
|
+
|
|
12
|
+
## Limitations
|
|
13
|
+
|
|
14
|
+
This plugin currently only works with Bitbucket Data center. Bitbucket cloud uses a different API to get pull requests [documented here](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-pullrequests/#api-repositories-workspace-repo-slug-pullrequests-get). Contributions are welcome to add support for Bitbucket cloud.
|
|
15
|
+
|
|
16
|
+
## How to add Bitbucket PR plugin to Backstage app
|
|
17
|
+
|
|
18
|
+
1. Install the plugin into Backstage.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd packages/app
|
|
22
|
+
yarn add @backstage-community/plugin-bitbucket-pull-requests
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
2. Add plugin API to your Backstage instance.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
// packages/app/src/components/catalog/EntityPage.tsx
|
|
29
|
+
import { EntityBitbucketPullRequestsContent } from '@backstage-community/plugin-bitbucket-pull-requests';
|
|
30
|
+
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
const serviceEntityPage = (
|
|
34
|
+
<EntityLayout>
|
|
35
|
+
...
|
|
36
|
+
<EntityLayout.Route path="/bitbucket-pullrequests" title="Bitbucket">
|
|
37
|
+
<EntityBitbucketPullRequestsContent />
|
|
38
|
+
</EntityLayout.Route>
|
|
39
|
+
...
|
|
40
|
+
</EntityLayout>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
3. Add proxy config
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
// app-config.yaml
|
|
47
|
+
proxy:
|
|
48
|
+
'/bitbucket/api':
|
|
49
|
+
target: https://bitbucket.org
|
|
50
|
+
changeOrigin: true
|
|
51
|
+
headers:
|
|
52
|
+
Authorization: Bearer ${BITBUCKET_TOKEN}
|
|
53
|
+
Accept: 'application/json'
|
|
54
|
+
Content-Type: 'application/json'
|
|
55
|
+
|
|
56
|
+
bitbucket:
|
|
57
|
+
# Defaults to /bitbucket/api and can be omitted if proxy is configured for that url
|
|
58
|
+
proxyPath: /bitbucket/api
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
4. Run backstage app with `yarn start` and navigate to services tabs.
|
|
62
|
+
|
|
63
|
+
## How to use Bitbucket PR plugin in Backstage
|
|
64
|
+
|
|
65
|
+
- Add annotation to the yaml config file of a component
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
metadata:
|
|
69
|
+
annotations:
|
|
70
|
+
bitbucket.com/project-slug: <example-bitbucket-project-name>/<example-bitbucket-repo-name>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Adding Bitbucket Pull Requests to your Homepage
|
|
74
|
+
|
|
75
|
+
This plugin also provides a homepage component that displays your Bitbucket pull requests directly on your Backstage homepage.
|
|
76
|
+
|
|
77
|
+

|
|
78
|
+
|
|
79
|
+
### Features
|
|
80
|
+
|
|
81
|
+
- View pull requests assigned to you or authored by you in a tabbed interface
|
|
82
|
+
- Shows PR ID, title, repository, branch, author/reviewers information, and build status (can be disabled)
|
|
83
|
+
|
|
84
|
+
### How to add to your Homepage
|
|
85
|
+
|
|
86
|
+
1. Make sure you've installed this plugin and configured the proxy as described above.
|
|
87
|
+
|
|
88
|
+
2. Make sure you've installed the homepage plugin and configured it as described in the [official documentation](https://github.com/backstage/backstage/tree/master/plugins/home#readme).
|
|
89
|
+
|
|
90
|
+
3. Add the component to your homepage:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// packages/app/src/components/home/HomePage.tsx
|
|
94
|
+
import { HomePagePullRequestsCard } from '@backstage-community/plugin-bitbucket-pull-requests';
|
|
95
|
+
|
|
96
|
+
export const HomePage = () => (
|
|
97
|
+
<Page themeId="home">
|
|
98
|
+
<Content>
|
|
99
|
+
<Grid container spacing={3}>
|
|
100
|
+
{/* Other homepage components */}
|
|
101
|
+
<Grid item xs={12} md={6}>
|
|
102
|
+
{/* Default: includes build status column */}
|
|
103
|
+
<HomePagePullRequestsCard />
|
|
104
|
+
|
|
105
|
+
{/* Or disable the build status column */}
|
|
106
|
+
{/* <HomePagePullRequestsCard buildStatus={false} /> */}
|
|
107
|
+
</Grid>
|
|
108
|
+
</Grid>
|
|
109
|
+
</Content>
|
|
110
|
+
</Page>
|
|
111
|
+
);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Props
|
|
115
|
+
|
|
116
|
+
| Prop | Type | Default | Description |
|
|
117
|
+
| ------------- | ------- | ------- | ---------------------------------------------------------------------- |
|
|
118
|
+
| `buildStatus` | boolean | `true` | Whether to show build status column and fetch build status information |
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import fetch from 'cross-fetch';
|
|
2
|
+
import { createApiRef } from '@backstage/core-plugin-api';
|
|
3
|
+
import { parseEntityRef } from '@backstage/catalog-model';
|
|
4
|
+
|
|
5
|
+
const bitbucketApiRef = createApiRef({
|
|
6
|
+
id: "plugin.bitbucket.service"
|
|
7
|
+
});
|
|
8
|
+
const DEFAULT_PROXY_PATH = "/bitbucket/api";
|
|
9
|
+
const DEFAULT_LIMIT = 50;
|
|
10
|
+
class BitbucketApi {
|
|
11
|
+
discoveryApi;
|
|
12
|
+
identityApi;
|
|
13
|
+
configApi;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.discoveryApi = options.discoveryApi;
|
|
16
|
+
this.identityApi = options.identityApi;
|
|
17
|
+
this.configApi = options.configApi;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Gets the configured proxy path from config or returns the default
|
|
21
|
+
* @returns The configured proxy path
|
|
22
|
+
*/
|
|
23
|
+
getProxyPath() {
|
|
24
|
+
return this.configApi?.getOptionalString("bitbucket.proxyPath") || DEFAULT_PROXY_PATH;
|
|
25
|
+
}
|
|
26
|
+
async fetchPullRequestListForRepo(project, repo, state, limit = DEFAULT_LIMIT) {
|
|
27
|
+
const proxyUrl = await this.discoveryApi.getBaseUrl("proxy");
|
|
28
|
+
const url = new URL(
|
|
29
|
+
`${proxyUrl}${this.getProxyPath()}/projects/${project}/repos/${repo}/pull-requests`
|
|
30
|
+
);
|
|
31
|
+
const params = new URLSearchParams();
|
|
32
|
+
if (state) {
|
|
33
|
+
params.append("state", state);
|
|
34
|
+
}
|
|
35
|
+
params.append("limit", limit.toString());
|
|
36
|
+
const response = await fetch(`${url}?${params}`, {
|
|
37
|
+
headers: {
|
|
38
|
+
"Content-Type": "application/json"
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new Error("Failed to fetch pull requests");
|
|
43
|
+
}
|
|
44
|
+
const data = await response.json();
|
|
45
|
+
return this.mapPullRequests(data);
|
|
46
|
+
}
|
|
47
|
+
async fetchBuildStatus(commitId) {
|
|
48
|
+
const proxyUrl = await this.discoveryApi.getBaseUrl("proxy");
|
|
49
|
+
const response = await fetch(
|
|
50
|
+
`${proxyUrl}${this.getProxyPath()}/rest/build-status/latest/commits/stats/${commitId}`,
|
|
51
|
+
{ headers: { "Content-Type": "application/json" } }
|
|
52
|
+
);
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
throw new Error(`Failed to fetch build status for commit ${commitId}`);
|
|
55
|
+
}
|
|
56
|
+
return response.json();
|
|
57
|
+
}
|
|
58
|
+
determineBuildState(status) {
|
|
59
|
+
if (status.failed > 0) return "FAILED";
|
|
60
|
+
if (status.inProgress > 0) return "INPROGRESS";
|
|
61
|
+
if (status.cancelled > 0) return "STOPPED";
|
|
62
|
+
if (status.successful > 0) return "SUCCESSFUL";
|
|
63
|
+
return void 0;
|
|
64
|
+
}
|
|
65
|
+
async enhancePrWithBuildStatus(pr) {
|
|
66
|
+
if (pr.latestCommit) {
|
|
67
|
+
return this.fetchBuildStatus(pr.latestCommit).then((buildStatus) => ({
|
|
68
|
+
...pr,
|
|
69
|
+
buildStatus: this.determineBuildState(buildStatus)
|
|
70
|
+
})).catch(() => ({
|
|
71
|
+
...pr,
|
|
72
|
+
buildStatus: "UNKNOWN"
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
return pr;
|
|
76
|
+
}
|
|
77
|
+
mapPullRequests(data) {
|
|
78
|
+
return data.values?.map((pr) => ({
|
|
79
|
+
id: pr.id,
|
|
80
|
+
title: pr.title,
|
|
81
|
+
author: {
|
|
82
|
+
displayName: pr.author.user.displayName,
|
|
83
|
+
slug: pr.author.user.slug
|
|
84
|
+
},
|
|
85
|
+
createdDate: pr.createdDate,
|
|
86
|
+
updatedDate: pr.updatedDate,
|
|
87
|
+
state: pr.state,
|
|
88
|
+
url: pr.links.self[0].href,
|
|
89
|
+
repoUrl: pr.fromRef.repository.links.self[0].href,
|
|
90
|
+
description: pr.description || "",
|
|
91
|
+
fromRepo: pr.fromRef.repository.name,
|
|
92
|
+
fromProject: pr.fromRef.repository.project.key,
|
|
93
|
+
sourceBranch: pr.fromRef.displayId,
|
|
94
|
+
targetBranch: pr.toRef.displayId,
|
|
95
|
+
latestCommit: pr.fromRef.latestCommit,
|
|
96
|
+
reviewers: pr.reviewers?.map((r) => ({
|
|
97
|
+
displayName: r.user.displayName,
|
|
98
|
+
slug: r.user.slug
|
|
99
|
+
})) || []
|
|
100
|
+
})) || [];
|
|
101
|
+
}
|
|
102
|
+
async fetchUserPullRequests(role = "REVIEWER", state = "OPEN", limit = DEFAULT_LIMIT, options = { includeBuildStatus: true }) {
|
|
103
|
+
if (!this.identityApi) {
|
|
104
|
+
throw new Error("Identity API is not available");
|
|
105
|
+
}
|
|
106
|
+
const { userEntityRef } = await this.identityApi.getBackstageIdentity();
|
|
107
|
+
const { name } = parseEntityRef(userEntityRef);
|
|
108
|
+
if (!name) {
|
|
109
|
+
throw new Error("User not found");
|
|
110
|
+
}
|
|
111
|
+
const proxyUrl = await this.discoveryApi.getBaseUrl("proxy");
|
|
112
|
+
const url = new URL(
|
|
113
|
+
`${proxyUrl}${this.getProxyPath()}/dashboard/pull-requests`
|
|
114
|
+
);
|
|
115
|
+
const params = new URLSearchParams({
|
|
116
|
+
order: "participant_status",
|
|
117
|
+
limit: limit.toString(),
|
|
118
|
+
state,
|
|
119
|
+
role,
|
|
120
|
+
user: name
|
|
121
|
+
});
|
|
122
|
+
const response = await fetch(`${url}?${params}`, {
|
|
123
|
+
headers: {
|
|
124
|
+
"Content-Type": "application/json"
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
if (!response.ok) {
|
|
128
|
+
let errorMessage = "Failed to fetch pull requests from Bitbucket";
|
|
129
|
+
try {
|
|
130
|
+
const errorText = await response.text();
|
|
131
|
+
const errorJson = JSON.parse(errorText);
|
|
132
|
+
if (response.status === 404 && errorJson.errors?.[0]?.message?.includes("does not exist")) {
|
|
133
|
+
errorMessage = `User '${name}' not found in Bitbucket. Please ensure your Bitbucket account exists.`;
|
|
134
|
+
}
|
|
135
|
+
} catch (e) {
|
|
136
|
+
errorMessage = e instanceof Error ? e.message : String(e);
|
|
137
|
+
}
|
|
138
|
+
throw new Error(errorMessage);
|
|
139
|
+
}
|
|
140
|
+
const data = await response.json();
|
|
141
|
+
const pullRequests = this.mapPullRequests(data);
|
|
142
|
+
if (options.includeBuildStatus) {
|
|
143
|
+
const enhancedPullRequests = await Promise.all(
|
|
144
|
+
pullRequests.map((pr) => this.enhancePrWithBuildStatus(pr))
|
|
145
|
+
);
|
|
146
|
+
return enhancedPullRequests;
|
|
147
|
+
}
|
|
148
|
+
return pullRequests;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export { BitbucketApi, bitbucketApiRef };
|
|
153
|
+
//# sourceMappingURL=BitbucketApi.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BitbucketApi.esm.js","sources":["../../src/api/BitbucketApi.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fetch from 'cross-fetch';\n\nimport {\n createApiRef,\n DiscoveryApi,\n IdentityApi,\n ConfigApi,\n} from '@backstage/core-plugin-api';\nimport { parseEntityRef } from '@backstage/catalog-model';\n\nexport const bitbucketApiRef = createApiRef<BitbucketApi>({\n id: 'plugin.bitbucket.service',\n});\n\nexport type User = {\n displayName: string;\n slug: string;\n};\n\nexport type BuildStatus = {\n cancelled: number;\n failed: number;\n inProgress: number;\n successful: number;\n unknown: number;\n};\n\nexport type PullRequest = {\n id: number;\n title: string;\n author: User;\n createdDate: number;\n updatedDate: number;\n state: string;\n description: string;\n url: string;\n repoUrl: string;\n fromRepo: string;\n fromProject: string;\n sourceBranch: string;\n targetBranch: string;\n latestCommit?: string;\n buildStatus?: 'SUCCESSFUL' | 'FAILED' | 'INPROGRESS' | 'STOPPED' | 'UNKNOWN';\n reviewers: User[];\n};\nconst DEFAULT_PROXY_PATH = '/bitbucket/api';\nconst DEFAULT_LIMIT = 50;\ntype Options = {\n discoveryApi: DiscoveryApi;\n identityApi: IdentityApi;\n configApi?: ConfigApi;\n};\nexport class BitbucketApi {\n private readonly discoveryApi: DiscoveryApi;\n private readonly identityApi: IdentityApi;\n private readonly configApi?: ConfigApi;\n\n constructor(options: Options) {\n this.discoveryApi = options.discoveryApi;\n this.identityApi = options.identityApi;\n this.configApi = options.configApi;\n }\n\n /**\n * Gets the configured proxy path from config or returns the default\n * @returns The configured proxy path\n */\n private getProxyPath(): string {\n return (\n this.configApi?.getOptionalString('bitbucket.proxyPath') ||\n DEFAULT_PROXY_PATH\n );\n }\n\n async fetchPullRequestListForRepo(\n project: string,\n repo: string,\n state?: string,\n limit: number = DEFAULT_LIMIT,\n ): Promise<PullRequest[]> {\n const proxyUrl = await this.discoveryApi.getBaseUrl('proxy');\n const url = new URL(\n `${proxyUrl}${this.getProxyPath()}/projects/${project}/repos/${repo}/pull-requests`,\n );\n\n const params = new URLSearchParams();\n if (state) {\n params.append('state', state);\n }\n params.append('limit', limit.toString());\n\n const response = await fetch(`${url}?${params}`, {\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n if (!response.ok) {\n throw new Error('Failed to fetch pull requests');\n }\n\n const data = await response.json();\n return this.mapPullRequests(data);\n }\n\n private async fetchBuildStatus(commitId: string): Promise<BuildStatus> {\n const proxyUrl = await this.discoveryApi.getBaseUrl('proxy');\n const response = await fetch(\n `${proxyUrl}${this.getProxyPath()}/rest/build-status/latest/commits/stats/${commitId}`,\n { headers: { 'Content-Type': 'application/json' } },\n );\n\n if (!response.ok) {\n throw new Error(`Failed to fetch build status for commit ${commitId}`);\n }\n\n return response.json();\n }\n\n private determineBuildState(\n status: BuildStatus,\n ): 'SUCCESSFUL' | 'FAILED' | 'INPROGRESS' | 'STOPPED' | undefined {\n if (status.failed > 0) return 'FAILED';\n if (status.inProgress > 0) return 'INPROGRESS';\n if (status.cancelled > 0) return 'STOPPED';\n if (status.successful > 0) return 'SUCCESSFUL';\n return undefined;\n }\n\n private async enhancePrWithBuildStatus(\n pr: PullRequest,\n ): Promise<PullRequest> {\n if (pr.latestCommit) {\n return this.fetchBuildStatus(pr.latestCommit)\n .then(buildStatus => ({\n ...pr,\n buildStatus: this.determineBuildState(buildStatus),\n }))\n .catch(() => ({\n ...pr,\n buildStatus: 'UNKNOWN' as const,\n }));\n }\n return pr;\n }\n\n public mapPullRequests(data: any): PullRequest[] {\n return (\n data.values?.map((pr: any) => ({\n id: pr.id,\n title: pr.title,\n author: {\n displayName: pr.author.user.displayName,\n slug: pr.author.user.slug,\n },\n createdDate: pr.createdDate,\n updatedDate: pr.updatedDate,\n state: pr.state,\n url: pr.links.self[0].href,\n repoUrl: pr.fromRef.repository.links.self[0].href,\n description: pr.description || '',\n fromRepo: pr.fromRef.repository.name,\n fromProject: pr.fromRef.repository.project.key,\n sourceBranch: pr.fromRef.displayId,\n targetBranch: pr.toRef.displayId,\n latestCommit: pr.fromRef.latestCommit,\n reviewers:\n pr.reviewers?.map((r: any) => ({\n displayName: r.user.displayName,\n slug: r.user.slug,\n })) || [],\n })) || []\n );\n }\n\n async fetchUserPullRequests(\n role: 'REVIEWER' | 'AUTHOR' = 'REVIEWER',\n state: 'OPEN' | 'MERGED' | 'DECLINED' | 'ALL' = 'OPEN',\n limit: number = DEFAULT_LIMIT,\n options: { includeBuildStatus?: boolean } = { includeBuildStatus: true },\n ): Promise<PullRequest[]> {\n if (!this.identityApi) {\n throw new Error('Identity API is not available');\n }\n\n const { userEntityRef } = await this.identityApi.getBackstageIdentity();\n\n const { name } = parseEntityRef(userEntityRef);\n\n if (!name) {\n throw new Error('User not found');\n }\n\n const proxyUrl = await this.discoveryApi.getBaseUrl('proxy');\n const url = new URL(\n `${proxyUrl}${this.getProxyPath()}/dashboard/pull-requests`,\n );\n\n const params = new URLSearchParams({\n order: 'participant_status',\n limit: limit.toString(),\n state,\n role,\n user: name,\n });\n\n const response = await fetch(`${url}?${params}`, {\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n let errorMessage = 'Failed to fetch pull requests from Bitbucket';\n\n try {\n const errorText = await response.text();\n const errorJson = JSON.parse(errorText);\n\n if (\n response.status === 404 &&\n errorJson.errors?.[0]?.message?.includes('does not exist')\n ) {\n errorMessage = `User '${name}' not found in Bitbucket. Please ensure your Bitbucket account exists.`;\n }\n } catch (e) {\n errorMessage = e instanceof Error ? e.message : String(e);\n }\n\n throw new Error(errorMessage);\n }\n\n const data = await response.json();\n const pullRequests = this.mapPullRequests(data);\n if (options.includeBuildStatus) {\n const enhancedPullRequests = await Promise.all(\n pullRequests.map(pr => this.enhancePrWithBuildStatus(pr)),\n );\n return enhancedPullRequests;\n }\n return pullRequests;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Tabs, Tab } from '@material-ui/core';
|
|
4
|
+
import { HomePagePullRequestsTable } from './HomePagePullRequestsTable.esm.js';
|
|
5
|
+
|
|
6
|
+
function TabPanel(props) {
|
|
7
|
+
const { children, value, index, ...other } = props;
|
|
8
|
+
return /* @__PURE__ */ jsx(
|
|
9
|
+
"div",
|
|
10
|
+
{
|
|
11
|
+
role: "tabpanel",
|
|
12
|
+
hidden: value !== index,
|
|
13
|
+
id: `pr-tabpanel-${index}`,
|
|
14
|
+
"aria-labelledby": `pr-tab-${index}`,
|
|
15
|
+
...other,
|
|
16
|
+
children: value === index && /* @__PURE__ */ jsx(Box, { children })
|
|
17
|
+
}
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
function a11yProps(index) {
|
|
21
|
+
return {
|
|
22
|
+
id: `pr-tab-${index}`,
|
|
23
|
+
"aria-controls": `pr-tabpanel-${index}`
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const HomePagePullRequestsCard = ({
|
|
27
|
+
buildStatus = true
|
|
28
|
+
} = {}) => {
|
|
29
|
+
const [value, setValue] = useState(0);
|
|
30
|
+
const handleChange = (_event, newValue) => {
|
|
31
|
+
setValue(newValue);
|
|
32
|
+
};
|
|
33
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
34
|
+
/* @__PURE__ */ jsxs(
|
|
35
|
+
Tabs,
|
|
36
|
+
{
|
|
37
|
+
value,
|
|
38
|
+
onChange: handleChange,
|
|
39
|
+
"aria-label": "Pull Requests tabs",
|
|
40
|
+
children: [
|
|
41
|
+
/* @__PURE__ */ jsx(Tab, { label: "Authored by Me", ...a11yProps(0) }),
|
|
42
|
+
/* @__PURE__ */ jsx(Tab, { label: "Assigned to Me", ...a11yProps(1) })
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
),
|
|
46
|
+
/* @__PURE__ */ jsx(TabPanel, { value, index: 0, children: /* @__PURE__ */ jsx(
|
|
47
|
+
HomePagePullRequestsTable,
|
|
48
|
+
{
|
|
49
|
+
userRole: "AUTHOR",
|
|
50
|
+
buildStatus
|
|
51
|
+
}
|
|
52
|
+
) }),
|
|
53
|
+
/* @__PURE__ */ jsx(TabPanel, { value, index: 1, children: /* @__PURE__ */ jsx(
|
|
54
|
+
HomePagePullRequestsTable,
|
|
55
|
+
{
|
|
56
|
+
userRole: "REVIEWER",
|
|
57
|
+
buildStatus
|
|
58
|
+
}
|
|
59
|
+
) })
|
|
60
|
+
] });
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export { HomePagePullRequestsCard };
|
|
64
|
+
//# sourceMappingURL=HomePagePullRequestsCard.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HomePagePullRequestsCard.esm.js","sources":["../../../src/components/HomePage/HomePagePullRequestsCard.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { useState } from 'react';\nimport { Tabs, Tab, Box } from '@material-ui/core';\nimport { HomePagePullRequestsTable } from './HomePagePullRequestsTable';\n\ninterface TabPanelProps {\n children?: React.ReactNode;\n index: number;\n value: number;\n}\n\nfunction TabPanel(props: Readonly<TabPanelProps>) {\n const { children, value, index, ...other } = props;\n\n return (\n <div\n role=\"tabpanel\"\n hidden={value !== index}\n id={`pr-tabpanel-${index}`}\n aria-labelledby={`pr-tab-${index}`}\n {...other}\n >\n {value === index && <Box>{children}</Box>}\n </div>\n );\n}\n\nfunction a11yProps(index: number) {\n return {\n id: `pr-tab-${index}`,\n 'aria-controls': `pr-tabpanel-${index}`,\n };\n}\n\n/**\n * Props for the HomePagePullRequestsCard component\n *\n * @public\n */\nexport interface HomePagePullRequestsCardProps {\n /**\n * Flag to determine whether to display build status information in the pull requests table.\n * Defaults to true if not specified.\n */\n buildStatus?: boolean;\n}\n\n/**\n * Component to display pull requests as tabs on the homepage\n *\n * @public\n */\nexport const HomePagePullRequestsCard = ({\n buildStatus = true,\n}: HomePagePullRequestsCardProps = {}) => {\n const [value, setValue] = useState(0);\n\n const handleChange = (\n _event: globalThis.React.ChangeEvent<{}>,\n newValue: number,\n ) => {\n setValue(newValue);\n };\n\n return (\n <Box>\n <Tabs\n value={value}\n onChange={handleChange}\n aria-label=\"Pull Requests tabs\"\n >\n <Tab label=\"Authored by Me\" {...a11yProps(0)} />\n <Tab label=\"Assigned to Me\" {...a11yProps(1)} />\n </Tabs>\n <TabPanel value={value} index={0}>\n <HomePagePullRequestsTable\n userRole=\"AUTHOR\"\n buildStatus={buildStatus}\n />\n </TabPanel>\n <TabPanel value={value} index={1}>\n <HomePagePullRequestsTable\n userRole=\"REVIEWER\"\n buildStatus={buildStatus}\n />\n </TabPanel>\n </Box>\n );\n};\n"],"names":[],"mappings":";;;;;AAyBA,SAAS,SAAS,KAAA,EAAgC;AAChD,EAAA,MAAM,EAAE,QAAA,EAAU,KAAA,EAAO,KAAA,EAAO,GAAG,OAAM,GAAI,KAAA;AAE7C,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,IAAA,EAAK,UAAA;AAAA,MACL,QAAQ,KAAA,KAAU,KAAA;AAAA,MAClB,EAAA,EAAI,eAAe,KAAK,CAAA,CAAA;AAAA,MACxB,iBAAA,EAAiB,UAAU,KAAK,CAAA,CAAA;AAAA,MAC/B,GAAG,KAAA;AAAA,MAEH,QAAA,EAAA,KAAA,KAAU,KAAA,oBAAS,GAAA,CAAC,GAAA,EAAA,EAAK,QAAA,EAAS;AAAA;AAAA,GACrC;AAEJ;AAEA,SAAS,UAAU,KAAA,EAAe;AAChC,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,UAAU,KAAK,CAAA,CAAA;AAAA,IACnB,eAAA,EAAiB,eAAe,KAAK,CAAA;AAAA,GACvC;AACF;AAoBO,MAAM,2BAA2B,CAAC;AAAA,EACvC,WAAA,GAAc;AAChB,CAAA,GAAmC,EAAC,KAAM;AACxC,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAS,CAAC,CAAA;AAEpC,EAAA,MAAM,YAAA,GAAe,CACnB,MAAA,EACA,QAAA,KACG;AACH,IAAA,QAAA,CAAS,QAAQ,CAAA;AAAA,EACnB,CAAA;AAEA,EAAA,4BACG,GAAA,EAAA,EACC,QAAA,EAAA;AAAA,oBAAA,IAAA;AAAA,MAAC,IAAA;AAAA,MAAA;AAAA,QACC,KAAA;AAAA,QACA,QAAA,EAAU,YAAA;AAAA,QACV,YAAA,EAAW,oBAAA;AAAA,QAEX,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,OAAI,KAAA,EAAM,gBAAA,EAAkB,GAAG,SAAA,CAAU,CAAC,CAAA,EAAG,CAAA;AAAA,8BAC7C,GAAA,EAAA,EAAI,KAAA,EAAM,kBAAkB,GAAG,SAAA,CAAU,CAAC,CAAA,EAAG;AAAA;AAAA;AAAA,KAChD;AAAA,oBACA,GAAA,CAAC,QAAA,EAAA,EAAS,KAAA,EAAc,KAAA,EAAO,CAAA,EAC7B,QAAA,kBAAA,GAAA;AAAA,MAAC,yBAAA;AAAA,MAAA;AAAA,QACC,QAAA,EAAS,QAAA;AAAA,QACT;AAAA;AAAA,KACF,EACF,CAAA;AAAA,oBACA,GAAA,CAAC,QAAA,EAAA,EAAS,KAAA,EAAc,KAAA,EAAO,CAAA,EAC7B,QAAA,kBAAA,GAAA;AAAA,MAAC,yBAAA;AAAA,MAAA;AAAA,QACC,QAAA,EAAS,UAAA;AAAA,QACT;AAAA;AAAA,KACF,EACF;AAAA,GAAA,EACF,CAAA;AAEJ;;;;"}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { Typography, Box, Avatar, Tooltip } from '@material-ui/core';
|
|
4
|
+
import { makeStyles } from '@material-ui/core/styles';
|
|
5
|
+
import { InfoCard, Table, Link } from '@backstage/core-components';
|
|
6
|
+
import { EntityPeekAheadPopover } from '@backstage/plugin-catalog-react';
|
|
7
|
+
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
|
|
8
|
+
import ErrorIcon from '@material-ui/icons/Error';
|
|
9
|
+
import HourglassEmptyIcon from '@material-ui/icons/HourglassEmpty';
|
|
10
|
+
import StopIcon from '@material-ui/icons/Stop';
|
|
11
|
+
import { useApi } from '@backstage/core-plugin-api';
|
|
12
|
+
import { bitbucketApiRef } from '../../api/BitbucketApi.esm.js';
|
|
13
|
+
|
|
14
|
+
const useStyles = makeStyles((theme) => ({
|
|
15
|
+
avatar: {
|
|
16
|
+
width: theme.spacing(2.5),
|
|
17
|
+
height: theme.spacing(2.5),
|
|
18
|
+
fontSize: "0.8rem"
|
|
19
|
+
},
|
|
20
|
+
authorContainer: {
|
|
21
|
+
display: "flex",
|
|
22
|
+
alignItems: "center",
|
|
23
|
+
gap: theme.spacing(1)
|
|
24
|
+
},
|
|
25
|
+
reviewersContainer: {
|
|
26
|
+
display: "flex",
|
|
27
|
+
flexWrap: "nowrap",
|
|
28
|
+
marginTop: theme.spacing(0.5),
|
|
29
|
+
marginLeft: theme.spacing(3)
|
|
30
|
+
},
|
|
31
|
+
reviewerAvatar: {
|
|
32
|
+
width: theme.spacing(2.5),
|
|
33
|
+
height: theme.spacing(2.5),
|
|
34
|
+
fontSize: "0.8rem",
|
|
35
|
+
marginLeft: theme.spacing(-0.7),
|
|
36
|
+
border: "1px solid white",
|
|
37
|
+
"&:first-child": {
|
|
38
|
+
marginLeft: 0
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
authorSection: {
|
|
42
|
+
display: "flex",
|
|
43
|
+
flexDirection: "column",
|
|
44
|
+
gap: theme.spacing(1)
|
|
45
|
+
},
|
|
46
|
+
moreReviewersText: {
|
|
47
|
+
alignSelf: "center",
|
|
48
|
+
cursor: "pointer",
|
|
49
|
+
paddingLeft: theme.spacing(0.6),
|
|
50
|
+
"&:hover": {
|
|
51
|
+
textDecoration: "underline"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
buildIcon: {
|
|
55
|
+
fontSize: 20
|
|
56
|
+
}
|
|
57
|
+
}));
|
|
58
|
+
const BuildIcon = ({ state }) => {
|
|
59
|
+
const classes = useStyles();
|
|
60
|
+
const getIcon = () => {
|
|
61
|
+
switch (state) {
|
|
62
|
+
case "SUCCESSFUL":
|
|
63
|
+
return /* @__PURE__ */ jsx(CheckCircleIcon, { color: "primary", className: classes.buildIcon });
|
|
64
|
+
case "FAILED":
|
|
65
|
+
return /* @__PURE__ */ jsx(ErrorIcon, { color: "error", className: classes.buildIcon });
|
|
66
|
+
case "INPROGRESS":
|
|
67
|
+
return /* @__PURE__ */ jsx(HourglassEmptyIcon, { color: "action", className: classes.buildIcon });
|
|
68
|
+
case "STOPPED":
|
|
69
|
+
return /* @__PURE__ */ jsx(StopIcon, { color: "error", className: classes.buildIcon });
|
|
70
|
+
default:
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const statusText = state ? state.charAt(0) + state.slice(1).toLowerCase() : "Unknown";
|
|
75
|
+
return /* @__PURE__ */ jsx(Tooltip, { title: statusText, arrow: true, children: /* @__PURE__ */ jsx(Box, { display: "flex", alignItems: "center", children: getIcon() }) });
|
|
76
|
+
};
|
|
77
|
+
const HomePagePullRequestsTable = ({
|
|
78
|
+
userRole,
|
|
79
|
+
maxItems = 25,
|
|
80
|
+
buildStatus = true
|
|
81
|
+
}) => {
|
|
82
|
+
const classes = useStyles();
|
|
83
|
+
const [pullRequests, setPullRequests] = useState([]);
|
|
84
|
+
const bitbucketApi = useApi(bitbucketApiRef);
|
|
85
|
+
const [loading, setLoading] = useState(true);
|
|
86
|
+
const [error, setError] = useState(null);
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const loadPullRequests = async () => {
|
|
89
|
+
try {
|
|
90
|
+
setLoading(true);
|
|
91
|
+
const response = await bitbucketApi.fetchUserPullRequests(
|
|
92
|
+
userRole,
|
|
93
|
+
"OPEN",
|
|
94
|
+
maxItems,
|
|
95
|
+
{ includeBuildStatus: buildStatus }
|
|
96
|
+
);
|
|
97
|
+
setPullRequests(response.slice(0, maxItems));
|
|
98
|
+
} catch (err) {
|
|
99
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
100
|
+
setError(`Failed to load pull requests: ${errorMessage}`);
|
|
101
|
+
} finally {
|
|
102
|
+
setLoading(false);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
loadPullRequests();
|
|
106
|
+
}, [maxItems, bitbucketApi, userRole, buildStatus]);
|
|
107
|
+
const baseColumns = [
|
|
108
|
+
{
|
|
109
|
+
title: "PR ID",
|
|
110
|
+
field: "id",
|
|
111
|
+
width: "8%",
|
|
112
|
+
render: (row) => /* @__PURE__ */ jsx(Box, { display: "flex", alignItems: "center", children: /* @__PURE__ */ jsxs(Link, { to: row.url, target: "_blank", children: [
|
|
113
|
+
"PR #",
|
|
114
|
+
row.id
|
|
115
|
+
] }) })
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
title: "Title",
|
|
119
|
+
field: "title",
|
|
120
|
+
render: (row) => /* @__PURE__ */ jsx(Typography, { variant: "body2", children: row.title })
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
title: "Repository",
|
|
124
|
+
field: "repository",
|
|
125
|
+
render: (row) => /* @__PURE__ */ jsx(Typography, { variant: "body2", children: /* @__PURE__ */ jsx(Link, { to: `${row.repoUrl}?at=${row.sourceBranch}`, target: "_blank", children: row.fromRepo }) })
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
title: "Branch",
|
|
129
|
+
field: "branch",
|
|
130
|
+
render: (row) => /* @__PURE__ */ jsx(Typography, { variant: "body2", children: /* @__PURE__ */ jsx(Link, { to: `${row.repoUrl}?at=${row.sourceBranch}`, target: "_blank", children: row.sourceBranch }) })
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
title: "Author/Reviewers",
|
|
134
|
+
field: "author.displayName",
|
|
135
|
+
render: (row) => {
|
|
136
|
+
const userEntityRef = `user:default/${row.author.slug}`;
|
|
137
|
+
const userAvatarUrl = `https://bitbucket.athenahealth.com/users/${row.author.slug}/avatar.png`;
|
|
138
|
+
return /* @__PURE__ */ jsxs("div", { className: classes.authorSection, children: [
|
|
139
|
+
/* @__PURE__ */ jsx("div", { className: classes.authorContainer, children: /* @__PURE__ */ jsx(EntityPeekAheadPopover, { entityRef: userEntityRef, children: /* @__PURE__ */ jsxs("div", { className: classes.authorContainer, children: [
|
|
140
|
+
/* @__PURE__ */ jsx(
|
|
141
|
+
Avatar,
|
|
142
|
+
{
|
|
143
|
+
src: userAvatarUrl,
|
|
144
|
+
alt: row.author.displayName,
|
|
145
|
+
className: classes.avatar,
|
|
146
|
+
children: row.author.displayName.charAt(0).toUpperCase()
|
|
147
|
+
}
|
|
148
|
+
),
|
|
149
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", children: row.author.displayName })
|
|
150
|
+
] }) }) }),
|
|
151
|
+
row.reviewers.length > 0 && /* @__PURE__ */ jsxs("div", { className: classes.reviewersContainer, children: [
|
|
152
|
+
row.reviewers.slice(0, 4).map((reviewer) => /* @__PURE__ */ jsx(
|
|
153
|
+
Tooltip,
|
|
154
|
+
{
|
|
155
|
+
title: reviewer.displayName,
|
|
156
|
+
arrow: true,
|
|
157
|
+
children: /* @__PURE__ */ jsx(
|
|
158
|
+
Avatar,
|
|
159
|
+
{
|
|
160
|
+
src: `https://bitbucket.athenahealth.com/users/${reviewer.slug}/avatar.png`,
|
|
161
|
+
alt: reviewer.displayName,
|
|
162
|
+
className: classes.reviewerAvatar,
|
|
163
|
+
children: reviewer.displayName.charAt(0).toUpperCase()
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
},
|
|
167
|
+
reviewer.slug
|
|
168
|
+
)),
|
|
169
|
+
row.reviewers.length > 4 && /* @__PURE__ */ jsx(
|
|
170
|
+
Tooltip,
|
|
171
|
+
{
|
|
172
|
+
title: /* @__PURE__ */ jsx("div", { children: row.reviewers.slice(4).map((reviewer) => /* @__PURE__ */ jsxs("div", { style: { margin: "4px 0" }, children: [
|
|
173
|
+
/* @__PURE__ */ jsx(
|
|
174
|
+
Avatar,
|
|
175
|
+
{
|
|
176
|
+
src: `https://bitbucket.athenahealth.com/users/${reviewer.slug}/avatar.png`,
|
|
177
|
+
style: {
|
|
178
|
+
width: 20,
|
|
179
|
+
height: 20,
|
|
180
|
+
display: "inline-block",
|
|
181
|
+
marginRight: 8,
|
|
182
|
+
verticalAlign: "middle"
|
|
183
|
+
},
|
|
184
|
+
children: reviewer.displayName.charAt(0).toUpperCase()
|
|
185
|
+
}
|
|
186
|
+
),
|
|
187
|
+
reviewer.displayName
|
|
188
|
+
] }, reviewer.slug)) }),
|
|
189
|
+
arrow: true,
|
|
190
|
+
placement: "top",
|
|
191
|
+
children: /* @__PURE__ */ jsxs(
|
|
192
|
+
Typography,
|
|
193
|
+
{
|
|
194
|
+
variant: "caption",
|
|
195
|
+
className: classes.moreReviewersText,
|
|
196
|
+
children: [
|
|
197
|
+
"+",
|
|
198
|
+
row.reviewers.length - 4,
|
|
199
|
+
" more"
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
)
|
|
205
|
+
] })
|
|
206
|
+
] });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
];
|
|
210
|
+
const buildStatusColumn = {
|
|
211
|
+
title: "Build Status",
|
|
212
|
+
field: "buildSummaries",
|
|
213
|
+
width: "5%",
|
|
214
|
+
headerStyle: {
|
|
215
|
+
textAlign: "center",
|
|
216
|
+
padding: "0 8px"
|
|
217
|
+
},
|
|
218
|
+
cellStyle: {
|
|
219
|
+
display: "flex",
|
|
220
|
+
justifyContent: "center",
|
|
221
|
+
alignItems: "center",
|
|
222
|
+
padding: "0 8px",
|
|
223
|
+
height: "100%",
|
|
224
|
+
minHeight: "48px"
|
|
225
|
+
// Ensure consistent row height
|
|
226
|
+
},
|
|
227
|
+
render: (row) => /* @__PURE__ */ jsx(Box, { display: "flex", justifyContent: "center", children: /* @__PURE__ */ jsx(BuildIcon, { state: row.buildStatus }) })
|
|
228
|
+
};
|
|
229
|
+
const columns = buildStatus ? [...baseColumns, buildStatusColumn] : baseColumns;
|
|
230
|
+
if (error) {
|
|
231
|
+
return /* @__PURE__ */ jsx(InfoCard, { children: /* @__PURE__ */ jsx(Typography, { color: "error", children: error }) });
|
|
232
|
+
}
|
|
233
|
+
return /* @__PURE__ */ jsx(InfoCard, { noPadding: true, children: /* @__PURE__ */ jsx(
|
|
234
|
+
Table,
|
|
235
|
+
{
|
|
236
|
+
options: {
|
|
237
|
+
padding: "dense"
|
|
238
|
+
},
|
|
239
|
+
title: `Pull Requests (${pullRequests.length})`,
|
|
240
|
+
data: pullRequests,
|
|
241
|
+
columns,
|
|
242
|
+
isLoading: loading
|
|
243
|
+
}
|
|
244
|
+
) });
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
export { HomePagePullRequestsTable };
|
|
248
|
+
//# sourceMappingURL=HomePagePullRequestsTable.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HomePagePullRequestsTable.esm.js","sources":["../../../src/components/HomePage/HomePagePullRequestsTable.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { useState, useEffect } from 'react';\nimport { Typography, Box, Avatar, Tooltip } from '@material-ui/core';\nimport { makeStyles } from '@material-ui/core/styles';\nimport { Table, TableColumn, InfoCard, Link } from '@backstage/core-components';\nimport { EntityPeekAheadPopover } from '@backstage/plugin-catalog-react';\nimport CheckCircleIcon from '@material-ui/icons/CheckCircle';\nimport ErrorIcon from '@material-ui/icons/Error';\nimport HourglassEmptyIcon from '@material-ui/icons/HourglassEmpty';\nimport StopIcon from '@material-ui/icons/Stop';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { bitbucketApiRef, PullRequest } from '../../api/BitbucketApi';\n\nconst useStyles = makeStyles(theme => ({\n avatar: {\n width: theme.spacing(2.5),\n height: theme.spacing(2.5),\n fontSize: '0.8rem',\n },\n authorContainer: {\n display: 'flex',\n alignItems: 'center',\n gap: theme.spacing(1),\n },\n reviewersContainer: {\n display: 'flex',\n flexWrap: 'nowrap',\n marginTop: theme.spacing(0.5),\n marginLeft: theme.spacing(3),\n },\n reviewerAvatar: {\n width: theme.spacing(2.5),\n height: theme.spacing(2.5),\n fontSize: '0.8rem',\n marginLeft: theme.spacing(-0.7),\n border: '1px solid white',\n '&:first-child': {\n marginLeft: 0,\n },\n },\n authorSection: {\n display: 'flex',\n flexDirection: 'column',\n gap: theme.spacing(1),\n },\n moreReviewersText: {\n alignSelf: 'center',\n cursor: 'pointer',\n paddingLeft: theme.spacing(0.6),\n '&:hover': {\n textDecoration: 'underline',\n },\n },\n buildIcon: {\n fontSize: 20,\n },\n}));\n\n// This is a React component (capital first letter) so it can use hooks\nconst BuildIcon = ({ state }: { state: string | undefined }) => {\n const classes = useStyles();\n\n const getIcon = () => {\n switch (state) {\n case 'SUCCESSFUL':\n return (\n <CheckCircleIcon color=\"primary\" className={classes.buildIcon} />\n );\n case 'FAILED':\n return <ErrorIcon color=\"error\" className={classes.buildIcon} />;\n case 'INPROGRESS':\n return (\n <HourglassEmptyIcon color=\"action\" className={classes.buildIcon} />\n );\n case 'STOPPED':\n return <StopIcon color=\"error\" className={classes.buildIcon} />;\n default:\n return null;\n }\n };\n\n const statusText = state\n ? state.charAt(0) + state.slice(1).toLowerCase()\n : 'Unknown';\n\n return (\n <Tooltip title={statusText} arrow>\n <Box display=\"flex\" alignItems=\"center\">\n {getIcon()}\n </Box>\n </Tooltip>\n );\n};\n\nexport interface BitbucketPullRequestsProps {\n maxItems?: number;\n userRole?: 'REVIEWER' | 'AUTHOR';\n buildStatus?: boolean;\n}\n\nexport const HomePagePullRequestsTable = ({\n userRole,\n maxItems = 25,\n buildStatus = true,\n}: BitbucketPullRequestsProps) => {\n const classes = useStyles();\n const [pullRequests, setPullRequests] = useState<PullRequest[]>([]);\n const bitbucketApi = useApi(bitbucketApiRef);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const loadPullRequests = async () => {\n try {\n setLoading(true);\n const response = await bitbucketApi.fetchUserPullRequests(\n userRole,\n 'OPEN',\n maxItems,\n { includeBuildStatus: buildStatus },\n );\n setPullRequests(response.slice(0, maxItems));\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n setError(`Failed to load pull requests: ${errorMessage}`);\n } finally {\n setLoading(false);\n }\n };\n\n loadPullRequests();\n }, [maxItems, bitbucketApi, userRole, buildStatus]);\n\n const baseColumns: TableColumn<PullRequest>[] = [\n {\n title: 'PR ID',\n field: 'id',\n width: '8%',\n render: row => (\n <Box display=\"flex\" alignItems=\"center\">\n <Link to={row.url} target=\"_blank\">\n PR #{row.id}\n </Link>\n </Box>\n ),\n },\n {\n title: 'Title',\n field: 'title',\n render: row => <Typography variant=\"body2\">{row.title}</Typography>,\n },\n {\n title: 'Repository',\n field: 'repository',\n render: row => (\n <Typography variant=\"body2\">\n <Link to={`${row.repoUrl}?at=${row.sourceBranch}`} target=\"_blank\">\n {row.fromRepo}\n </Link>\n </Typography>\n ),\n },\n {\n title: 'Branch',\n field: 'branch',\n render: row => (\n <Typography variant=\"body2\">\n <Link to={`${row.repoUrl}?at=${row.sourceBranch}`} target=\"_blank\">\n {row.sourceBranch}\n </Link>\n </Typography>\n ),\n },\n {\n title: 'Author/Reviewers',\n field: 'author.displayName',\n render: row => {\n const userEntityRef = `user:default/${row.author.slug}`;\n const userAvatarUrl = `https://bitbucket.athenahealth.com/users/${row.author.slug}/avatar.png`;\n\n return (\n <div className={classes.authorSection}>\n <div className={classes.authorContainer}>\n <EntityPeekAheadPopover entityRef={userEntityRef}>\n <div className={classes.authorContainer}>\n <Avatar\n src={userAvatarUrl}\n alt={row.author.displayName}\n className={classes.avatar}\n >\n {row.author.displayName.charAt(0).toUpperCase()}\n </Avatar>\n <Typography variant=\"body2\">\n {row.author.displayName}\n </Typography>\n </div>\n </EntityPeekAheadPopover>\n </div>\n {row.reviewers.length > 0 && (\n <div className={classes.reviewersContainer}>\n {row.reviewers.slice(0, 4).map((reviewer: any) => (\n <Tooltip\n key={reviewer.slug}\n title={reviewer.displayName}\n arrow\n >\n <Avatar\n src={`https://bitbucket.athenahealth.com/users/${reviewer.slug}/avatar.png`}\n alt={reviewer.displayName}\n className={classes.reviewerAvatar}\n >\n {reviewer.displayName.charAt(0).toUpperCase()}\n </Avatar>\n </Tooltip>\n ))}\n {row.reviewers.length > 4 && (\n <Tooltip\n title={\n <div>\n {row.reviewers.slice(4).map((reviewer: any) => (\n <div key={reviewer.slug} style={{ margin: '4px 0' }}>\n <Avatar\n src={`https://bitbucket.athenahealth.com/users/${reviewer.slug}/avatar.png`}\n style={{\n width: 20,\n height: 20,\n display: 'inline-block',\n marginRight: 8,\n verticalAlign: 'middle',\n }}\n >\n {reviewer.displayName.charAt(0).toUpperCase()}\n </Avatar>\n {reviewer.displayName}\n </div>\n ))}\n </div>\n }\n arrow\n placement=\"top\"\n >\n <Typography\n variant=\"caption\"\n className={classes.moreReviewersText}\n >\n +{row.reviewers.length - 4} more\n </Typography>\n </Tooltip>\n )}\n </div>\n )}\n </div>\n );\n },\n },\n ];\n\n const buildStatusColumn: TableColumn<PullRequest> = {\n title: 'Build Status',\n field: 'buildSummaries',\n width: '5%',\n headerStyle: {\n textAlign: 'center',\n padding: '0 8px',\n },\n cellStyle: {\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n padding: '0 8px',\n height: '100%',\n minHeight: '48px', // Ensure consistent row height\n },\n render: row => (\n <Box display=\"flex\" justifyContent=\"center\">\n <BuildIcon state={row.buildStatus} />\n </Box>\n ),\n };\n\n const columns = buildStatus\n ? [...baseColumns, buildStatusColumn]\n : baseColumns;\n\n if (error) {\n return (\n <InfoCard>\n <Typography color=\"error\">{error}</Typography>\n </InfoCard>\n );\n }\n\n return (\n <InfoCard noPadding>\n <Table\n options={{\n padding: 'dense',\n }}\n title={`Pull Requests (${pullRequests.length})`}\n data={pullRequests}\n columns={columns}\n isLoading={loading}\n />\n </InfoCard>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;;;;;AA2BA,MAAM,SAAA,GAAY,WAAW,CAAA,KAAA,MAAU;AAAA,EACrC,MAAA,EAAQ;AAAA,IACN,KAAA,EAAO,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAAA,IACxB,MAAA,EAAQ,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAAA,IACzB,QAAA,EAAU;AAAA,GACZ;AAAA,EACA,eAAA,EAAiB;AAAA,IACf,OAAA,EAAS,MAAA;AAAA,IACT,UAAA,EAAY,QAAA;AAAA,IACZ,GAAA,EAAK,KAAA,CAAM,OAAA,CAAQ,CAAC;AAAA,GACtB;AAAA,EACA,kBAAA,EAAoB;AAAA,IAClB,OAAA,EAAS,MAAA;AAAA,IACT,QAAA,EAAU,QAAA;AAAA,IACV,SAAA,EAAW,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAAA,IAC5B,UAAA,EAAY,KAAA,CAAM,OAAA,CAAQ,CAAC;AAAA,GAC7B;AAAA,EACA,cAAA,EAAgB;AAAA,IACd,KAAA,EAAO,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAAA,IACxB,MAAA,EAAQ,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAAA,IACzB,QAAA,EAAU,QAAA;AAAA,IACV,UAAA,EAAY,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA;AAAA,IAC9B,MAAA,EAAQ,iBAAA;AAAA,IACR,eAAA,EAAiB;AAAA,MACf,UAAA,EAAY;AAAA;AACd,GACF;AAAA,EACA,aAAA,EAAe;AAAA,IACb,OAAA,EAAS,MAAA;AAAA,IACT,aAAA,EAAe,QAAA;AAAA,IACf,GAAA,EAAK,KAAA,CAAM,OAAA,CAAQ,CAAC;AAAA,GACtB;AAAA,EACA,iBAAA,EAAmB;AAAA,IACjB,SAAA,EAAW,QAAA;AAAA,IACX,MAAA,EAAQ,SAAA;AAAA,IACR,WAAA,EAAa,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAAA,IAC9B,SAAA,EAAW;AAAA,MACT,cAAA,EAAgB;AAAA;AAClB,GACF;AAAA,EACA,SAAA,EAAW;AAAA,IACT,QAAA,EAAU;AAAA;AAEd,CAAA,CAAE,CAAA;AAGF,MAAM,SAAA,GAAY,CAAC,EAAE,KAAA,EAAM,KAAqC;AAC9D,EAAA,MAAM,UAAU,SAAA,EAAU;AAE1B,EAAA,MAAM,UAAU,MAAM;AACpB,IAAA,QAAQ,KAAA;AAAO,MACb,KAAK,YAAA;AACH,QAAA,2BACG,eAAA,EAAA,EAAgB,KAAA,EAAM,SAAA,EAAU,SAAA,EAAW,QAAQ,SAAA,EAAW,CAAA;AAAA,MAEnE,KAAK,QAAA;AACH,QAAA,2BAAQ,SAAA,EAAA,EAAU,KAAA,EAAM,OAAA,EAAQ,SAAA,EAAW,QAAQ,SAAA,EAAW,CAAA;AAAA,MAChE,KAAK,YAAA;AACH,QAAA,2BACG,kBAAA,EAAA,EAAmB,KAAA,EAAM,QAAA,EAAS,SAAA,EAAW,QAAQ,SAAA,EAAW,CAAA;AAAA,MAErE,KAAK,SAAA;AACH,QAAA,2BAAQ,QAAA,EAAA,EAAS,KAAA,EAAM,OAAA,EAAQ,SAAA,EAAW,QAAQ,SAAA,EAAW,CAAA;AAAA,MAC/D;AACE,QAAA,OAAO,IAAA;AAAA;AACX,EACF,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,KAAA,GACf,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,GAAI,KAAA,CAAM,KAAA,CAAM,CAAC,CAAA,CAAE,WAAA,EAAY,GAC7C,SAAA;AAEJ,EAAA,uBACE,GAAA,CAAC,OAAA,EAAA,EAAQ,KAAA,EAAO,UAAA,EAAY,OAAK,IAAA,EAC/B,QAAA,kBAAA,GAAA,CAAC,GAAA,EAAA,EAAI,OAAA,EAAQ,MAAA,EAAO,UAAA,EAAW,QAAA,EAC5B,QAAA,EAAA,OAAA,IACH,CAAA,EACF,CAAA;AAEJ,CAAA;AAQO,MAAM,4BAA4B,CAAC;AAAA,EACxC,QAAA;AAAA,EACA,QAAA,GAAW,EAAA;AAAA,EACX,WAAA,GAAc;AAChB,CAAA,KAAkC;AAChC,EAAA,MAAM,UAAU,SAAA,EAAU;AAC1B,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAI,QAAA,CAAwB,EAAE,CAAA;AAClE,EAAA,MAAM,YAAA,GAAe,OAAO,eAAe,CAAA;AAC3C,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,IAAI,CAAA;AAC3C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAwB,IAAI,CAAA;AAEtD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,mBAAmB,YAAY;AACnC,MAAA,IAAI;AACF,QAAA,UAAA,CAAW,IAAI,CAAA;AACf,QAAA,MAAM,QAAA,GAAW,MAAM,YAAA,CAAa,qBAAA;AAAA,UAClC,QAAA;AAAA,UACA,MAAA;AAAA,UACA,QAAA;AAAA,UACA,EAAE,oBAAoB,WAAA;AAAY,SACpC;AACA,QAAA,eAAA,CAAgB,QAAA,CAAS,KAAA,CAAM,CAAA,EAAG,QAAQ,CAAC,CAAA;AAAA,MAC7C,SAAS,GAAA,EAAK;AACZ,QAAA,MAAM,eAAe,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AACpE,QAAA,QAAA,CAAS,CAAA,8BAAA,EAAiC,YAAY,CAAA,CAAE,CAAA;AAAA,MAC1D,CAAA,SAAE;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,CAAA;AAEA,IAAA,gBAAA,EAAiB;AAAA,EACnB,GAAG,CAAC,QAAA,EAAU,YAAA,EAAc,QAAA,EAAU,WAAW,CAAC,CAAA;AAElD,EAAA,MAAM,WAAA,GAA0C;AAAA,IAC9C;AAAA,MACE,KAAA,EAAO,OAAA;AAAA,MACP,KAAA,EAAO,IAAA;AAAA,MACP,KAAA,EAAO,IAAA;AAAA,MACP,MAAA,EAAQ,CAAA,GAAA,qBACN,GAAA,CAAC,GAAA,EAAA,EAAI,SAAQ,MAAA,EAAO,UAAA,EAAW,QAAA,EAC7B,QAAA,kBAAA,IAAA,CAAC,IAAA,EAAA,EAAK,EAAA,EAAI,GAAA,CAAI,GAAA,EAAK,QAAO,QAAA,EAAS,QAAA,EAAA;AAAA,QAAA,MAAA;AAAA,QAC5B,GAAA,CAAI;AAAA,OAAA,EACX,CAAA,EACF;AAAA,KAEJ;AAAA,IACA;AAAA,MACE,KAAA,EAAO,OAAA;AAAA,MACP,KAAA,EAAO,OAAA;AAAA,MACP,QAAQ,CAAA,GAAA,qBAAO,GAAA,CAAC,cAAW,OAAA,EAAQ,OAAA,EAAS,cAAI,KAAA,EAAM;AAAA,KACxD;AAAA,IACA;AAAA,MACE,KAAA,EAAO,YAAA;AAAA,MACP,KAAA,EAAO,YAAA;AAAA,MACP,MAAA,EAAQ,yBACN,GAAA,CAAC,UAAA,EAAA,EAAW,SAAQ,OAAA,EAClB,QAAA,kBAAA,GAAA,CAAC,QAAK,EAAA,EAAI,CAAA,EAAG,IAAI,OAAO,CAAA,IAAA,EAAO,IAAI,YAAY,CAAA,CAAA,EAAI,QAAO,QAAA,EACvD,QAAA,EAAA,GAAA,CAAI,UACP,CAAA,EACF;AAAA,KAEJ;AAAA,IACA;AAAA,MACE,KAAA,EAAO,QAAA;AAAA,MACP,KAAA,EAAO,QAAA;AAAA,MACP,MAAA,EAAQ,yBACN,GAAA,CAAC,UAAA,EAAA,EAAW,SAAQ,OAAA,EAClB,QAAA,kBAAA,GAAA,CAAC,QAAK,EAAA,EAAI,CAAA,EAAG,IAAI,OAAO,CAAA,IAAA,EAAO,IAAI,YAAY,CAAA,CAAA,EAAI,QAAO,QAAA,EACvD,QAAA,EAAA,GAAA,CAAI,cACP,CAAA,EACF;AAAA,KAEJ;AAAA,IACA;AAAA,MACE,KAAA,EAAO,kBAAA;AAAA,MACP,KAAA,EAAO,oBAAA;AAAA,MACP,QAAQ,CAAA,GAAA,KAAO;AACb,QAAA,MAAM,aAAA,GAAgB,CAAA,aAAA,EAAgB,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA,CAAA;AACrD,QAAA,MAAM,aAAA,GAAgB,CAAA,yCAAA,EAA4C,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA,WAAA,CAAA;AAEjF,QAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,OAAA,CAAQ,aAAA,EACtB,QAAA,EAAA;AAAA,0BAAA,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,OAAA,CAAQ,eAAA,EACtB,QAAA,kBAAA,GAAA,CAAC,sBAAA,EAAA,EAAuB,SAAA,EAAW,aAAA,EACjC,QAAA,kBAAA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAW,OAAA,CAAQ,eAAA,EACtB,QAAA,EAAA;AAAA,4BAAA,GAAA;AAAA,cAAC,MAAA;AAAA,cAAA;AAAA,gBACC,GAAA,EAAK,aAAA;AAAA,gBACL,GAAA,EAAK,IAAI,MAAA,CAAO,WAAA;AAAA,gBAChB,WAAW,OAAA,CAAQ,MAAA;AAAA,gBAElB,cAAI,MAAA,CAAO,WAAA,CAAY,MAAA,CAAO,CAAC,EAAE,WAAA;AAAY;AAAA,aAChD;AAAA,gCACC,UAAA,EAAA,EAAW,OAAA,EAAQ,OAAA,EACjB,QAAA,EAAA,GAAA,CAAI,OAAO,WAAA,EACd;AAAA,WAAA,EACF,GACF,CAAA,EACF,CAAA;AAAA,UACC,GAAA,CAAI,UAAU,MAAA,GAAS,CAAA,yBACrB,KAAA,EAAA,EAAI,SAAA,EAAW,QAAQ,kBAAA,EACrB,QAAA,EAAA;AAAA,YAAA,GAAA,CAAI,UAAU,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,QAAA,qBAC9B,GAAA;AAAA,cAAC,OAAA;AAAA,cAAA;AAAA,gBAEC,OAAO,QAAA,CAAS,WAAA;AAAA,gBAChB,KAAA,EAAK,IAAA;AAAA,gBAEL,QAAA,kBAAA,GAAA;AAAA,kBAAC,MAAA;AAAA,kBAAA;AAAA,oBACC,GAAA,EAAK,CAAA,yCAAA,EAA4C,QAAA,CAAS,IAAI,CAAA,WAAA,CAAA;AAAA,oBAC9D,KAAK,QAAA,CAAS,WAAA;AAAA,oBACd,WAAW,OAAA,CAAQ,cAAA;AAAA,oBAElB,QAAA,EAAA,QAAA,CAAS,WAAA,CAAY,MAAA,CAAO,CAAC,EAAE,WAAA;AAAY;AAAA;AAC9C,eAAA;AAAA,cAVK,QAAA,CAAS;AAAA,aAYjB,CAAA;AAAA,YACA,GAAA,CAAI,SAAA,CAAU,MAAA,GAAS,CAAA,oBACtB,GAAA;AAAA,cAAC,OAAA;AAAA,cAAA;AAAA,gBACC,uBACE,GAAA,CAAC,KAAA,EAAA,EACE,QAAA,EAAA,GAAA,CAAI,SAAA,CAAU,MAAM,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,6BAC3B,IAAA,CAAC,KAAA,EAAA,EAAwB,OAAO,EAAE,MAAA,EAAQ,SAAQ,EAChD,QAAA,EAAA;AAAA,kCAAA,GAAA;AAAA,oBAAC,MAAA;AAAA,oBAAA;AAAA,sBACC,GAAA,EAAK,CAAA,yCAAA,EAA4C,QAAA,CAAS,IAAI,CAAA,WAAA,CAAA;AAAA,sBAC9D,KAAA,EAAO;AAAA,wBACL,KAAA,EAAO,EAAA;AAAA,wBACP,MAAA,EAAQ,EAAA;AAAA,wBACR,OAAA,EAAS,cAAA;AAAA,wBACT,WAAA,EAAa,CAAA;AAAA,wBACb,aAAA,EAAe;AAAA,uBACjB;AAAA,sBAEC,QAAA,EAAA,QAAA,CAAS,WAAA,CAAY,MAAA,CAAO,CAAC,EAAE,WAAA;AAAY;AAAA,mBAC9C;AAAA,kBACC,QAAA,CAAS;AAAA,iBAAA,EAAA,EAbF,QAAA,CAAS,IAcnB,CACD,CAAA,EACH,CAAA;AAAA,gBAEF,KAAA,EAAK,IAAA;AAAA,gBACL,SAAA,EAAU,KAAA;AAAA,gBAEV,QAAA,kBAAA,IAAA;AAAA,kBAAC,UAAA;AAAA,kBAAA;AAAA,oBACC,OAAA,EAAQ,SAAA;AAAA,oBACR,WAAW,OAAA,CAAQ,iBAAA;AAAA,oBACpB,QAAA,EAAA;AAAA,sBAAA,GAAA;AAAA,sBACG,GAAA,CAAI,UAAU,MAAA,GAAS,CAAA;AAAA,sBAAE;AAAA;AAAA;AAAA;AAC7B;AAAA;AACF,WAAA,EAEJ;AAAA,SAAA,EAEJ,CAAA;AAAA,MAEJ;AAAA;AACF,GACF;AAEA,EAAA,MAAM,iBAAA,GAA8C;AAAA,IAClD,KAAA,EAAO,cAAA;AAAA,IACP,KAAA,EAAO,gBAAA;AAAA,IACP,KAAA,EAAO,IAAA;AAAA,IACP,WAAA,EAAa;AAAA,MACX,SAAA,EAAW,QAAA;AAAA,MACX,OAAA,EAAS;AAAA,KACX;AAAA,IACA,SAAA,EAAW;AAAA,MACT,OAAA,EAAS,MAAA;AAAA,MACT,cAAA,EAAgB,QAAA;AAAA,MAChB,UAAA,EAAY,QAAA;AAAA,MACZ,OAAA,EAAS,OAAA;AAAA,MACT,MAAA,EAAQ,MAAA;AAAA,MACR,SAAA,EAAW;AAAA;AAAA,KACb;AAAA,IACA,MAAA,EAAQ,CAAA,GAAA,qBACN,GAAA,CAAC,GAAA,EAAA,EAAI,OAAA,EAAQ,MAAA,EAAO,cAAA,EAAe,QAAA,EACjC,QAAA,kBAAA,GAAA,CAAC,SAAA,EAAA,EAAU,KAAA,EAAO,GAAA,CAAI,aAAa,CAAA,EACrC;AAAA,GAEJ;AAEA,EAAA,MAAM,UAAU,WAAA,GACZ,CAAC,GAAG,WAAA,EAAa,iBAAiB,CAAA,GAClC,WAAA;AAEJ,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,2BACG,QAAA,EAAA,EACC,QAAA,kBAAA,GAAA,CAAC,cAAW,KAAA,EAAM,OAAA,EAAS,iBAAM,CAAA,EACnC,CAAA;AAAA,EAEJ;AAEA,EAAA,uBACE,GAAA,CAAC,QAAA,EAAA,EAAS,SAAA,EAAS,IAAA,EACjB,QAAA,kBAAA,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAS;AAAA,QACP,OAAA,EAAS;AAAA,OACX;AAAA,MACA,KAAA,EAAO,CAAA,eAAA,EAAkB,YAAA,CAAa,MAAM,CAAA,CAAA,CAAA;AAAA,MAC5C,IAAA,EAAM,YAAA;AAAA,MACN,OAAA;AAAA,MACA,SAAA,EAAW;AAAA;AAAA,GACb,EACF,CAAA;AAEJ;;;;"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { Table, MarkdownContent, Link } from '@backstage/core-components';
|
|
4
|
+
import { DateTime } from 'luxon';
|
|
5
|
+
import { useApi } from '@backstage/core-plugin-api';
|
|
6
|
+
import { Box, Typography } from '@material-ui/core';
|
|
7
|
+
import { isBitbucketSlugSet } from '../utils/isBitbucketSlugSet.esm.js';
|
|
8
|
+
import { bitbucketApiRef } from '../api/BitbucketApi.esm.js';
|
|
9
|
+
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
|
|
10
|
+
import CancelIcon from '@material-ui/icons/Cancel';
|
|
11
|
+
import HourglassEmptyIcon from '@material-ui/icons/HourglassEmpty';
|
|
12
|
+
import Tooltip from '@material-ui/core/Tooltip';
|
|
13
|
+
import StatusFilter from './StatusFilter.esm.js';
|
|
14
|
+
import { useEntity } from '@backstage/plugin-catalog-react';
|
|
15
|
+
|
|
16
|
+
const GetElapsedTime = ({ start }) => DateTime.fromMillis(start).toRelative();
|
|
17
|
+
const RenderStateIcon = ({ status }) => {
|
|
18
|
+
switch (status) {
|
|
19
|
+
case "OPEN":
|
|
20
|
+
return /* @__PURE__ */ jsx(Tooltip, { title: "Open", placement: "top", children: /* @__PURE__ */ jsx(Typography, { component: "span", children: /* @__PURE__ */ jsx(HourglassEmptyIcon, { color: "primary" }) }) });
|
|
21
|
+
case "MERGED":
|
|
22
|
+
return /* @__PURE__ */ jsx(Tooltip, { title: "Merged", placement: "top", children: /* @__PURE__ */ jsx(Typography, { component: "span", children: /* @__PURE__ */ jsx(CheckCircleIcon, { color: "action" }) }) });
|
|
23
|
+
case "DECLINED":
|
|
24
|
+
return /* @__PURE__ */ jsx(Tooltip, { title: "Declined", placement: "top", children: /* @__PURE__ */ jsx(Typography, { component: "span", children: /* @__PURE__ */ jsx(CancelIcon, { color: "error" }) }) });
|
|
25
|
+
default:
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const PullRequestDetailPanel = ({ rowData }) => /* @__PURE__ */ jsx(Box, { marginLeft: "14px", children: /* @__PURE__ */ jsx(
|
|
30
|
+
MarkdownContent,
|
|
31
|
+
{
|
|
32
|
+
content: rowData.description ?? "_No description provided._",
|
|
33
|
+
dialect: "gfm"
|
|
34
|
+
}
|
|
35
|
+
) });
|
|
36
|
+
const PullRequestList = () => {
|
|
37
|
+
const [pullRequests, setPullRequests] = useState([]);
|
|
38
|
+
const [stateFilter, setStateFilter] = useState("All");
|
|
39
|
+
const [loading, setLoading] = useState(true);
|
|
40
|
+
const { entity } = useEntity();
|
|
41
|
+
const project = isBitbucketSlugSet(entity);
|
|
42
|
+
const bitbucketApi = useApi(bitbucketApiRef);
|
|
43
|
+
const projectName = project.split("/")[0];
|
|
44
|
+
const repoName = project.split("/")[1];
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
setLoading(true);
|
|
47
|
+
bitbucketApi.fetchPullRequestListForRepo(
|
|
48
|
+
projectName,
|
|
49
|
+
repoName,
|
|
50
|
+
stateFilter !== "All" ? stateFilter : void 0
|
|
51
|
+
).then((data) => {
|
|
52
|
+
setPullRequests(data);
|
|
53
|
+
setLoading(false);
|
|
54
|
+
}).catch((error) => error);
|
|
55
|
+
}, [stateFilter, projectName, repoName, bitbucketApi]);
|
|
56
|
+
const columns = [
|
|
57
|
+
{
|
|
58
|
+
title: "ID",
|
|
59
|
+
field: "id",
|
|
60
|
+
highlight: true,
|
|
61
|
+
width: "20%",
|
|
62
|
+
render: (row) => /* @__PURE__ */ jsx(Box, { fontWeight: "fontWeightBold", children: /* @__PURE__ */ jsxs(Link, { to: `${row.url}`, children: [
|
|
63
|
+
"#",
|
|
64
|
+
row.id
|
|
65
|
+
] }) })
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
title: "TITLE",
|
|
69
|
+
field: "title",
|
|
70
|
+
highlight: true,
|
|
71
|
+
width: "30%",
|
|
72
|
+
render: (rowData) => /* @__PURE__ */ jsx(Box, { fontWeight: "fontWeightBold", children: rowData.title })
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
title: "STATE",
|
|
76
|
+
field: "state",
|
|
77
|
+
highlight: true,
|
|
78
|
+
width: "10%",
|
|
79
|
+
render: (rowData) => /* @__PURE__ */ jsx(RenderStateIcon, { status: rowData.state })
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
title: "AUTHOR",
|
|
83
|
+
field: "author",
|
|
84
|
+
highlight: true,
|
|
85
|
+
width: "20%",
|
|
86
|
+
render: (row) => /* @__PURE__ */ jsx(Box, { fontWeight: "fontWeightBold", children: row.author?.displayName })
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
title: "CREATED",
|
|
90
|
+
field: "createdDate",
|
|
91
|
+
highlight: true,
|
|
92
|
+
width: "20%",
|
|
93
|
+
render: (row) => /* @__PURE__ */ jsx(GetElapsedTime, { start: row.createdDate })
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
title: "LAST UPDATED",
|
|
97
|
+
field: "updatedDate",
|
|
98
|
+
highlight: true,
|
|
99
|
+
width: "20%",
|
|
100
|
+
render: (rowData) => /* @__PURE__ */ jsx(GetElapsedTime, { start: rowData.updatedDate })
|
|
101
|
+
}
|
|
102
|
+
];
|
|
103
|
+
return /* @__PURE__ */ jsx(
|
|
104
|
+
Table,
|
|
105
|
+
{
|
|
106
|
+
columns,
|
|
107
|
+
data: pullRequests,
|
|
108
|
+
detailPanel: PullRequestDetailPanel,
|
|
109
|
+
isLoading: loading,
|
|
110
|
+
title: /* @__PURE__ */ jsxs(Box, { display: "flex", alignItems: "center", children: [
|
|
111
|
+
/* @__PURE__ */ jsx(Box, { mr: 1 }),
|
|
112
|
+
"Bitbucket Pull Requests",
|
|
113
|
+
/* @__PURE__ */ jsx(Box, { position: "absolute", right: 320, top: 20, children: /* @__PURE__ */ jsx(StatusFilter, { onFilterChange: setStateFilter }) })
|
|
114
|
+
] })
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export { PullRequestList as default };
|
|
120
|
+
//# sourceMappingURL=PullRequestList.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PullRequestList.esm.js","sources":["../../src/components/PullRequestList.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { FC, useEffect, useState } from 'react';\nimport {\n TableColumn,\n Table,\n MarkdownContent,\n Link,\n} from '@backstage/core-components';\nimport { DateTime } from 'luxon';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { Box, Typography } from '@material-ui/core';\nimport { isBitbucketSlugSet } from '../utils/isBitbucketSlugSet';\n\nimport { bitbucketApiRef, PullRequest } from '../api/BitbucketApi';\nimport CheckCircleIcon from '@material-ui/icons/CheckCircle';\nimport CancelIcon from '@material-ui/icons/Cancel';\nimport HourglassEmptyIcon from '@material-ui/icons/HourglassEmpty';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport StatusFilter from '../components/StatusFilter';\nimport { useEntity } from '@backstage/plugin-catalog-react';\n\nconst GetElapsedTime = ({ start }: { start: number }) =>\n DateTime.fromMillis(start).toRelative();\n\nconst RenderStateIcon = ({ status }: { status: string }) => {\n switch (status) {\n case 'OPEN':\n return (\n <Tooltip title=\"Open\" placement=\"top\">\n <Typography component=\"span\">\n <HourglassEmptyIcon color=\"primary\" />\n </Typography>\n </Tooltip>\n );\n case 'MERGED':\n return (\n <Tooltip title=\"Merged\" placement=\"top\">\n <Typography component=\"span\">\n <CheckCircleIcon color=\"action\" />\n </Typography>\n </Tooltip>\n );\n case 'DECLINED':\n return (\n <Tooltip title=\"Declined\" placement=\"top\">\n <Typography component=\"span\">\n <CancelIcon color=\"error\" />\n </Typography>\n </Tooltip>\n );\n default:\n return null;\n }\n};\n\nconst PullRequestDetailPanel = ({ rowData }: { rowData: PullRequest }) => (\n <Box marginLeft=\"14px\">\n <MarkdownContent\n content={rowData.description ?? '_No description provided._'}\n dialect=\"gfm\"\n />\n </Box>\n);\n\nconst PullRequestList: FC = () => {\n const [pullRequests, setPullRequests] = useState<PullRequest[]>([]);\n const [stateFilter, setStateFilter] = useState<string>('All');\n const [loading, setLoading] = useState(true);\n const { entity } = useEntity();\n const project = isBitbucketSlugSet(entity);\n const bitbucketApi = useApi(bitbucketApiRef);\n const projectName = project.split('/')[0];\n const repoName = project.split('/')[1];\n\n useEffect(() => {\n setLoading(true);\n bitbucketApi\n .fetchPullRequestListForRepo(\n projectName,\n repoName,\n stateFilter !== 'All' ? stateFilter : undefined,\n )\n .then(data => {\n setPullRequests(data);\n setLoading(false);\n })\n .catch(error => error);\n }, [stateFilter, projectName, repoName, bitbucketApi]);\n\n const columns: TableColumn<PullRequest>[] = [\n {\n title: 'ID',\n field: 'id',\n highlight: true,\n width: '20%',\n render: (row: Partial<PullRequest>) => (\n <Box fontWeight=\"fontWeightBold\">\n <Link to={`${row.url}`}>#{row.id}</Link>\n </Box>\n ),\n },\n {\n title: 'TITLE',\n field: 'title',\n highlight: true,\n width: '30%',\n render: rowData => <Box fontWeight=\"fontWeightBold\">{rowData.title}</Box>,\n },\n {\n title: 'STATE',\n field: 'state',\n highlight: true,\n width: '10%',\n render: rowData => <RenderStateIcon status={rowData.state} />,\n },\n {\n title: 'AUTHOR',\n field: 'author',\n highlight: true,\n width: '20%',\n render: (row: Partial<PullRequest>) => (\n <Box fontWeight=\"fontWeightBold\">{row.author?.displayName}</Box>\n ),\n },\n {\n title: 'CREATED',\n field: 'createdDate',\n highlight: true,\n width: '20%',\n render: (row: Partial<PullRequest>) => (\n <GetElapsedTime start={row.createdDate!} />\n ),\n },\n {\n title: 'LAST UPDATED',\n field: 'updatedDate',\n highlight: true,\n width: '20%',\n render: rowData => <GetElapsedTime start={rowData.updatedDate} />,\n },\n ];\n\n return (\n <Table\n columns={columns}\n data={pullRequests}\n detailPanel={PullRequestDetailPanel}\n isLoading={loading}\n title={\n <Box display=\"flex\" alignItems=\"center\">\n <Box mr={1} />\n Bitbucket Pull Requests\n <Box position=\"absolute\" right={320} top={20}>\n <StatusFilter onFilterChange={setStateFilter} />\n </Box>\n </Box>\n }\n />\n );\n};\nexport default PullRequestList;\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { Routes, Route } from 'react-router';
|
|
3
|
+
import PullRequestList from './PullRequestList.esm.js';
|
|
4
|
+
import { BITBUCKET_PULL_REQUESTS_ANNOTATION } from '../utils/isBitbucketSlugSet.esm.js';
|
|
5
|
+
import { useEntity, MissingAnnotationEmptyState } from '@backstage/plugin-catalog-react';
|
|
6
|
+
|
|
7
|
+
const isBitbucketPullRequestsAvailable = (entity) => Boolean(entity.metadata.annotations?.[BITBUCKET_PULL_REQUESTS_ANNOTATION]);
|
|
8
|
+
const Router = () => {
|
|
9
|
+
const { entity } = useEntity();
|
|
10
|
+
return !isBitbucketPullRequestsAvailable(entity) ? /* @__PURE__ */ jsx(
|
|
11
|
+
MissingAnnotationEmptyState,
|
|
12
|
+
{
|
|
13
|
+
annotation: BITBUCKET_PULL_REQUESTS_ANNOTATION
|
|
14
|
+
}
|
|
15
|
+
) : /* @__PURE__ */ jsx(Routes, { children: /* @__PURE__ */ jsx(Route, { path: "/", element: /* @__PURE__ */ jsx(PullRequestList, {}) }) });
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export { Router, isBitbucketPullRequestsAvailable };
|
|
19
|
+
//# sourceMappingURL=Router.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Router.esm.js","sources":["../../src/components/Router.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity } from '@backstage/catalog-model';\nimport { Route, Routes } from 'react-router';\nimport PullRequestList from './PullRequestList';\nimport { BITBUCKET_PULL_REQUESTS_ANNOTATION } from '../utils/isBitbucketSlugSet';\nimport {\n useEntity,\n MissingAnnotationEmptyState,\n} from '@backstage/plugin-catalog-react';\n\nexport const isBitbucketPullRequestsAvailable = (entity: Entity) =>\n Boolean(entity.metadata.annotations?.[BITBUCKET_PULL_REQUESTS_ANNOTATION]);\n\nexport const Router = () => {\n const { entity } = useEntity();\n return !isBitbucketPullRequestsAvailable(entity) ? (\n <MissingAnnotationEmptyState\n annotation={BITBUCKET_PULL_REQUESTS_ANNOTATION}\n />\n ) : (\n <Routes>\n <Route path=\"/\" element={<PullRequestList />} />\n </Routes>\n );\n};\n"],"names":[],"mappings":";;;;;;AAyBO,MAAM,gCAAA,GAAmC,CAAC,MAAA,KAC/C,OAAA,CAAQ,OAAO,QAAA,CAAS,WAAA,GAAc,kCAAkC,CAAC;AAEpE,MAAM,SAAS,MAAM;AAC1B,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,SAAA,EAAU;AAC7B,EAAA,OAAO,CAAC,gCAAA,CAAiC,MAAM,CAAA,mBAC7C,GAAA;AAAA,IAAC,2BAAA;AAAA,IAAA;AAAA,MACC,UAAA,EAAY;AAAA;AAAA,GACd,mBAEA,GAAA,CAAC,MAAA,EAAA,EACC,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAM,IAAA,EAAK,GAAA,EAAI,OAAA,kBAAS,GAAA,CAAC,eAAA,EAAA,EAAgB,CAAA,EAAI,CAAA,EAChD,CAAA;AAEJ;;;;"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { ButtonGroup, Button } from '@material-ui/core';
|
|
4
|
+
|
|
5
|
+
const StatusFilter = ({ onFilterChange }) => {
|
|
6
|
+
const [status, setStatus] = useState("ALL");
|
|
7
|
+
const handleStatusChange = (newStatus) => {
|
|
8
|
+
setStatus(newStatus);
|
|
9
|
+
onFilterChange(newStatus);
|
|
10
|
+
};
|
|
11
|
+
const buttons = [
|
|
12
|
+
{ value: "OPEN", label: "Open" },
|
|
13
|
+
{ value: "MERGED", label: "Merged" },
|
|
14
|
+
{ value: "DECLINED", label: "Declined" },
|
|
15
|
+
{ value: "ALL", label: "All" }
|
|
16
|
+
];
|
|
17
|
+
return /* @__PURE__ */ jsx(ButtonGroup, { color: "primary", "aria-label": "pull request status filter", children: buttons.map(({ value, label }) => /* @__PURE__ */ jsx(
|
|
18
|
+
Button,
|
|
19
|
+
{
|
|
20
|
+
onClick: () => handleStatusChange(value),
|
|
21
|
+
variant: status === value ? "contained" : "outlined",
|
|
22
|
+
children: label
|
|
23
|
+
},
|
|
24
|
+
value
|
|
25
|
+
)) });
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export { StatusFilter as default };
|
|
29
|
+
//# sourceMappingURL=StatusFilter.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StatusFilter.esm.js","sources":["../../src/components/StatusFilter.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { FC, useState } from 'react';\nimport { ButtonGroup, Button } from '@material-ui/core';\n\ninterface StatusFilterProps {\n onFilterChange: (filter: string) => void;\n}\nconst StatusFilter: FC<StatusFilterProps> = ({ onFilterChange }) => {\n const [status, setStatus] = useState('ALL');\n\n const handleStatusChange = (newStatus: any) => {\n setStatus(newStatus);\n onFilterChange(newStatus);\n };\n\n const buttons = [\n { value: 'OPEN', label: 'Open' },\n { value: 'MERGED', label: 'Merged' },\n { value: 'DECLINED', label: 'Declined' },\n { value: 'ALL', label: 'All' },\n ];\n\n return (\n <ButtonGroup color=\"primary\" aria-label=\"pull request status filter\">\n {buttons.map(({ value, label }) => (\n <Button\n key={value}\n onClick={() => handleStatusChange(value)}\n variant={status === value ? 'contained' : 'outlined'}\n >\n {label}\n </Button>\n ))}\n </ButtonGroup>\n );\n};\n\nexport default StatusFilter;\n"],"names":[],"mappings":";;;;AAsBA,MAAM,YAAA,GAAsC,CAAC,EAAE,cAAA,EAAe,KAAM;AAClE,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAS,KAAK,CAAA;AAE1C,EAAA,MAAM,kBAAA,GAAqB,CAAC,SAAA,KAAmB;AAC7C,IAAA,SAAA,CAAU,SAAS,CAAA;AACnB,IAAA,cAAA,CAAe,SAAS,CAAA;AAAA,EAC1B,CAAA;AAEA,EAAA,MAAM,OAAA,GAAU;AAAA,IACd,EAAE,KAAA,EAAO,MAAA,EAAQ,KAAA,EAAO,MAAA,EAAO;AAAA,IAC/B,EAAE,KAAA,EAAO,QAAA,EAAU,KAAA,EAAO,QAAA,EAAS;AAAA,IACnC,EAAE,KAAA,EAAO,UAAA,EAAY,KAAA,EAAO,UAAA,EAAW;AAAA,IACvC,EAAE,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,KAAA;AAAM,GAC/B;AAEA,EAAA,uBACE,GAAA,CAAC,WAAA,EAAA,EAAY,KAAA,EAAM,SAAA,EAAU,YAAA,EAAW,4BAAA,EACrC,QAAA,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAC,EAAE,KAAA,EAAO,KAAA,EAAM,qBAC3B,GAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MAEC,OAAA,EAAS,MAAM,kBAAA,CAAmB,KAAK,CAAA;AAAA,MACvC,OAAA,EAAS,MAAA,KAAW,KAAA,GAAQ,WAAA,GAAc,UAAA;AAAA,MAEzC,QAAA,EAAA;AAAA,KAAA;AAAA,IAJI;AAAA,GAMR,CAAA,EACH,CAAA;AAEJ;;;;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Props for the HomePagePullRequestsCard component
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
*/
|
|
9
|
+
interface HomePagePullRequestsCardProps {
|
|
10
|
+
/**
|
|
11
|
+
* Flag to determine whether to display build status information in the pull requests table.
|
|
12
|
+
* Defaults to true if not specified.
|
|
13
|
+
*/
|
|
14
|
+
buildStatus?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Component to display pull requests as tabs on the homepage
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
declare const HomePagePullRequestsCard: ({ buildStatus, }?: HomePagePullRequestsCardProps) => react_jsx_runtime.JSX.Element;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Plugin for Bitbucket pull requests integration
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
declare const bitbucketPlugin: _backstage_core_plugin_api.BackstagePlugin<{
|
|
28
|
+
root: _backstage_core_plugin_api.RouteRef<undefined>;
|
|
29
|
+
}, {}, {}>;
|
|
30
|
+
/**
|
|
31
|
+
* Component for displaying Bitbucket pull requests in the entity page
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
declare const EntityBitbucketPullRequestsContent: () => react_jsx_runtime.JSX.Element;
|
|
35
|
+
|
|
36
|
+
export { EntityBitbucketPullRequestsContent, HomePagePullRequestsCard, bitbucketPlugin };
|
|
37
|
+
export type { HomePagePullRequestsCardProps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createPlugin, createApiFactory, configApiRef, identityApiRef, discoveryApiRef, createRoutableExtension } from '@backstage/core-plugin-api';
|
|
2
|
+
import { rootRouteRef } from './routes.esm.js';
|
|
3
|
+
import { BitbucketApi, bitbucketApiRef } from './api/BitbucketApi.esm.js';
|
|
4
|
+
import 'react/jsx-runtime';
|
|
5
|
+
import 'react';
|
|
6
|
+
import '@material-ui/core';
|
|
7
|
+
import './components/HomePage/HomePagePullRequestsTable.esm.js';
|
|
8
|
+
|
|
9
|
+
const bitbucketPlugin = createPlugin({
|
|
10
|
+
id: "bitbucket-pullrequests",
|
|
11
|
+
apis: [
|
|
12
|
+
createApiFactory({
|
|
13
|
+
api: bitbucketApiRef,
|
|
14
|
+
deps: {
|
|
15
|
+
discoveryApi: discoveryApiRef,
|
|
16
|
+
identityApi: identityApiRef,
|
|
17
|
+
configApi: configApiRef
|
|
18
|
+
},
|
|
19
|
+
factory: ({ discoveryApi, identityApi, configApi }) => new BitbucketApi({ discoveryApi, identityApi, configApi })
|
|
20
|
+
})
|
|
21
|
+
],
|
|
22
|
+
routes: {
|
|
23
|
+
root: rootRouteRef
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
const EntityBitbucketPullRequestsContent = bitbucketPlugin.provide(
|
|
27
|
+
createRoutableExtension({
|
|
28
|
+
name: "EntityBitbucketPullRequestsContent",
|
|
29
|
+
component: () => import('./components/Router.esm.js').then((m) => m.Router),
|
|
30
|
+
mountPoint: rootRouteRef
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export { EntityBitbucketPullRequestsContent, bitbucketPlugin };
|
|
35
|
+
//# sourceMappingURL=plugin.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.esm.js","sources":["../src/plugin.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n createPlugin,\n createApiFactory,\n discoveryApiRef,\n createRoutableExtension,\n identityApiRef,\n configApiRef,\n} from '@backstage/core-plugin-api';\nimport { rootRouteRef } from './routes';\nimport { bitbucketApiRef, BitbucketApi } from './api/BitbucketApi';\n\n/**\n * Plugin for Bitbucket pull requests integration\n * @public\n */\nexport const bitbucketPlugin = createPlugin({\n id: 'bitbucket-pullrequests',\n apis: [\n createApiFactory({\n api: bitbucketApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n identityApi: identityApiRef,\n configApi: configApiRef,\n },\n factory: ({ discoveryApi, identityApi, configApi }) =>\n new BitbucketApi({ discoveryApi, identityApi, configApi }),\n }),\n ],\n routes: {\n root: rootRouteRef,\n },\n});\n\n/**\n * Component for displaying Bitbucket pull requests in the entity page\n * @public\n */\nexport const EntityBitbucketPullRequestsContent = bitbucketPlugin.provide(\n createRoutableExtension({\n name: 'EntityBitbucketPullRequestsContent',\n component: () => import('./components/Router').then(m => m.Router),\n mountPoint: rootRouteRef,\n }),\n);\n\nexport { HomePagePullRequestsCard } from './components/HomePage/HomePagePullRequestsCard';\nexport type { HomePagePullRequestsCardProps } from './components/HomePage/HomePagePullRequestsCard';\n"],"names":[],"mappings":";;;;;;;;AA+BO,MAAM,kBAAkB,YAAA,CAAa;AAAA,EAC1C,EAAA,EAAI,wBAAA;AAAA,EACJ,IAAA,EAAM;AAAA,IACJ,gBAAA,CAAiB;AAAA,MACf,GAAA,EAAK,eAAA;AAAA,MACL,IAAA,EAAM;AAAA,QACJ,YAAA,EAAc,eAAA;AAAA,QACd,WAAA,EAAa,cAAA;AAAA,QACb,SAAA,EAAW;AAAA,OACb;AAAA,MACA,OAAA,EAAS,CAAC,EAAE,YAAA,EAAc,WAAA,EAAa,SAAA,EAAU,KAC/C,IAAI,YAAA,CAAa,EAAE,YAAA,EAAc,WAAA,EAAa,WAAW;AAAA,KAC5D;AAAA,GACH;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM;AAAA;AAEV,CAAC;AAMM,MAAM,qCAAqC,eAAA,CAAgB,OAAA;AAAA,EAChE,uBAAA,CAAwB;AAAA,IACtB,IAAA,EAAM,oCAAA;AAAA,IACN,SAAA,EAAW,MAAM,OAAO,4BAAqB,EAAE,IAAA,CAAK,CAAA,CAAA,KAAK,EAAE,MAAM,CAAA;AAAA,IACjE,UAAA,EAAY;AAAA,GACb;AACH;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.esm.js","sources":["../src/routes.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createRouteRef } from '@backstage/core-plugin-api';\n\nexport const rootRouteRef = createRouteRef({\n id: 'bitbucket-pullrequests',\n});\n"],"names":[],"mappings":";;AAkBO,MAAM,eAAe,cAAA,CAAe;AAAA,EACzC,EAAA,EAAI;AACN,CAAC;;;;"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const BITBUCKET_PULL_REQUESTS_ANNOTATION = "bitbucket.com/project-slug";
|
|
2
|
+
const isBitbucketSlugSet = (entity) => {
|
|
3
|
+
return entity?.metadata.annotations?.[BITBUCKET_PULL_REQUESTS_ANNOTATION] ?? "";
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export { BITBUCKET_PULL_REQUESTS_ANNOTATION, isBitbucketSlugSet };
|
|
7
|
+
//# sourceMappingURL=isBitbucketSlugSet.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isBitbucketSlugSet.esm.js","sources":["../../src/utils/isBitbucketSlugSet.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Entity } from '@backstage/catalog-model';\n\nexport const BITBUCKET_PULL_REQUESTS_ANNOTATION = 'bitbucket.com/project-slug';\n\nexport const isBitbucketSlugSet = (entity: Entity) => {\n return (\n entity?.metadata.annotations?.[BITBUCKET_PULL_REQUESTS_ANNOTATION] ?? ''\n );\n};\n"],"names":[],"mappings":"AAkBO,MAAM,kCAAA,GAAqC;AAE3C,MAAM,kBAAA,GAAqB,CAAC,MAAA,KAAmB;AACpD,EAAA,OACE,MAAA,EAAQ,QAAA,CAAS,WAAA,GAAc,kCAAkC,CAAA,IAAK,EAAA;AAE1E;;;;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@backstage-community/plugin-bitbucket-pull-requests",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"main": "dist/index.esm.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"backstage",
|
|
9
|
+
"plugin",
|
|
10
|
+
"bitbucket",
|
|
11
|
+
"pullrequest"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public",
|
|
15
|
+
"main": "dist/index.esm.js",
|
|
16
|
+
"types": "dist/index.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://backstage.io",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/backstage/community-plugins",
|
|
22
|
+
"directory": "workspaces/bitbucket-pull-requests/plugins/bitbucket-pull-requests"
|
|
23
|
+
},
|
|
24
|
+
"backstage": {
|
|
25
|
+
"role": "frontend-plugin",
|
|
26
|
+
"pluginId": "bitbucket-pull-requests",
|
|
27
|
+
"pluginPackages": [
|
|
28
|
+
"@backstage-community/plugin-bitbucket-pull-requests"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"scripts": {
|
|
33
|
+
"start": "backstage-cli package start",
|
|
34
|
+
"build": "backstage-cli package build",
|
|
35
|
+
"lint": "backstage-cli package lint",
|
|
36
|
+
"test": "backstage-cli package test",
|
|
37
|
+
"clean": "backstage-cli package clean",
|
|
38
|
+
"prepack": "backstage-cli package prepack",
|
|
39
|
+
"postpack": "backstage-cli package postpack"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@backstage/catalog-model": "^1.7.5",
|
|
43
|
+
"@backstage/core-components": "^0.17.4",
|
|
44
|
+
"@backstage/core-plugin-api": "^1.10.9",
|
|
45
|
+
"@backstage/plugin-catalog-react": "^1.19.1",
|
|
46
|
+
"@material-ui/core": "^4.9.13",
|
|
47
|
+
"@material-ui/icons": "^4.9.1",
|
|
48
|
+
"cross-fetch": "^4.0.0",
|
|
49
|
+
"luxon": "^3.0.0",
|
|
50
|
+
"msw": "^1.0.1",
|
|
51
|
+
"react-router": "^6.3.0",
|
|
52
|
+
"react-router-dom": "^6.3.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
|
56
|
+
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
|
57
|
+
"react-router": "6.0.0-beta.0 || ^6.3.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@backstage/cli": "^0.33.1",
|
|
61
|
+
"@backstage/core-app-api": "^1.18.0",
|
|
62
|
+
"@backstage/dev-utils": "^1.1.12",
|
|
63
|
+
"@backstage/test-utils": "^1.7.10",
|
|
64
|
+
"@testing-library/jest-dom": "^6.0.0",
|
|
65
|
+
"@testing-library/react": "^14.0.0",
|
|
66
|
+
"@types/luxon": "^3.0.0",
|
|
67
|
+
"@types/react": "^18",
|
|
68
|
+
"@types/react-dom": "^18",
|
|
69
|
+
"react": "^18",
|
|
70
|
+
"react-dom": "^18"
|
|
71
|
+
},
|
|
72
|
+
"files": [
|
|
73
|
+
"dist"
|
|
74
|
+
],
|
|
75
|
+
"typesVersions": {
|
|
76
|
+
"*": {
|
|
77
|
+
"package.json": [
|
|
78
|
+
"package.json"
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"module": "./dist/index.esm.js"
|
|
83
|
+
}
|