Skip to content

Commit

Permalink
Merge pull request #1448 from smymoon/update-bb-pullrequest-plugin
Browse files Browse the repository at this point in the history
Add new plugin for backstage bitbucket pullrequest
  • Loading branch information
Xantier authored Aug 7, 2024
2 parents cebd755 + c087b47 commit befa6f3
Show file tree
Hide file tree
Showing 23 changed files with 977 additions and 8 deletions.
10 changes: 10 additions & 0 deletions app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ integrations:
# - host: ghe.example.net
# apiBaseUrl: https://ghe.example.net/api/v3
# token: ${GHE_TOKEN}
bitbucket:
proxyPath: /bitbucket/api

proxy:
'/test':
Expand Down Expand Up @@ -62,6 +64,14 @@ proxy:
X-Atlassian-Token: 'no-check'
User-Agent: 'Roadie-Backstage'

'/bitbucket/api':
target: https://bitbucket.org
changeOrigin: true
headers:
Authorization: Bearer ${BITBUCKET_TOKEN}
Accept: 'application/json'
Content-Type: 'application/json'

'/travisci/api':
target: https://api.travis-ci.com
changeOrigin: true
Expand Down
3 changes: 2 additions & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
"react-router-dom": "6.0.0-beta.0",
"react-use": "^15.3.3",
"@backstage-community/plugin-github-actions": "^0.6.16",
"@backstage-community/plugin-tech-radar": "^0.7.4"
"@backstage-community/plugin-tech-radar": "^0.7.4",
"@roadiehq/backstage-plugin-bitbucket-pullrequest": "^1.0.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.0.0",
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/components/catalog/EntityPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ import {
EntityGithubPullRequestsContent,
EntityGithubPullRequestsOverviewCard,
} from '@roadiehq/backstage-plugin-github-pull-requests';

import { EntityBitbucketPullRequestsContent } from '@roadiehq/backstage-plugin-bitbucket-pullrequest';

import {
isAWSLambdaAvailable,
EntityAWSLambdaOverviewCard,
Expand Down Expand Up @@ -290,6 +293,9 @@ const serviceEntityPage = (
<EntityLayout.Route path="/pull-requests" title="Pull Requests">
<EntityGithubPullRequestsContent />
</EntityLayout.Route>
<EntityLayout.Route path="/bitbucket-pullrequests" title="Bitbucket">
<EntityBitbucketPullRequestsContent />
</EntityLayout.Route>
<EntityLayout.Route path="/code-insights" title="Code Insights">
<EntityGithubInsightsContent />
</EntityLayout.Route>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
71 changes: 71 additions & 0 deletions plugins/frontend/backstage-plugin-bitbucket-pullrequest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Bitbucket PullRequest Plugin for Backstage

![list of pull requests in the Bitbucket repo](./docs/bitbucketprimg.png)

## Features

- List of PR's from particular bitbucket repo
- Filtering like OPEN/CLOSED/MERGED/ALL PR and Search
- Able to view Creator name, Created date and last update etc.
- We can go to Particular PR by clicking ID.

## How to add Bitbucket PR plugin to Backstage app

1. Install the plugin into Backstage.

```bash
cd packages/app
yarn add @roadiehq/backstage-plugin-bitbucket-pullrequest
```

2. Add plugin API to your Backstage instance.

```ts
// packages/app/src/components/catalog/EntityPage.tsx
import { EntityBitbucketPullRequestsContent } from '@roadiehq/backstage-plugin-bitbucket-pullrequest';

...

const serviceEntityPage = (
<EntityLayout>
...
<EntityLayout.Route path="/bitbucket-pullrequests" title="Bitbucket">
<EntityBitbucketPullRequestsContent />
</EntityLayout.Route>
...
</EntityLayout>
```
3. Add proxy config
```yaml
// app-config.yaml
proxy:
'/bitbucket/api':
target: https://bitbucket.org
changeOrigin: true
headers:
Authorization: Bearer ${BITBUCKET_TOKEN}
Accept: 'application/json'
Content-Type: 'application/json'
bitbucket:
# Defaults to /bitbucket/api and can be omitted if proxy is configured for that url
proxyPath: /bitbucket/api
```
4. Run backstage app with `yarn start` and navigate to services tabs.
## How to use Bitbucket PR plugin in Backstage
- Add annotation to the yaml config file of a component
```yaml
metadata:
annotations:
bitbucket.com/project-slug: <example-bitbucket-project-name>/<example-bitbucket-repo-name>
```
## Links
- [Backstage](https://backstage.io)
- Get hosted, managed Backstage for your company: https://roadie.io
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2024 Larder Software Limited
*
* 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 { createDevApp } from '@backstage/dev-utils';
import { bitbucketPlugin } from '../src/plugin';

createDevApp().registerPlugin(bitbucketPlugin).render();
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Docs go here
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# How to release

This plugin uses the [npm-publish-action](https://github.com/marketplace/actions/publish-to-npm) GutHub Action to automate releases. It expects commits to be titled in a very specific way.

To publish a new version,

1. Manually bump the version in the `package.json` file.
2. Commit the version bump the exact title: "Release 1.2.3".

Do **not** use `yarn version --new-version [version]`. Doing so will automatically add a commit
and tag which will then clash with the commit and tag that the automation creates.
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"name": "@roadiehq/backstage-plugin-bitbucket-pullrequest",
"version": "1.0.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": true,
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"repository": {
"type": "git",
"url": "github:RoadieHQ/roadie-backstage-plugins",
"directory": "plugins/frontend/backstage-plugin-bitbucket-pullrequest"
},
"backstage": {
"role": "frontend-plugin"
},
"sideEffects": false,
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"dependencies": {
"@backstage/core-components": "^0.14.4",
"@backstage/core-plugin-api": "^1.9.2",
"@backstage/theme": "^0.5.3",
"@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "^4.0.0-alpha.60",
"react-use": "^17.2.4",
"@backstage/plugin-catalog-react": "^1.11.3",
"@backstage/catalog-model": "^1.4.5",
"moment": "^2.29.1",
"msw": "^1.0.1",
"cross-fetch": "4.0.0"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-router": "6.0.0-beta.0 || ^6.3.0"
},
"devDependencies": {
"@types/luxon": "^3.0.0",
"@backstage/cli": "^0.26.4",
"@backstage/core-app-api": "^1.12.4",
"@backstage/dev-utils": "^1.0.31",
"@backstage/test-utils": "^1.5.4",
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^14.0.0",
"jest-environment-jsdom": "^29.2.1",
"rollup-plugin-dts": "^5.2.0",
"@rollup/plugin-commonjs": "^24.0.1",
"@rollup/plugin-node-resolve": "^15.0.1",
"rollup-plugin-esbuild": "^5.0.0"
},
"files": [
"dist"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright 2024 Larder Software Limited
*
* 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 fetch from 'cross-fetch';

import { createApiRef, DiscoveryApi } from '@backstage/core-plugin-api';

export const bitbucketApiRef = createApiRef<BitbucketApi>({
id: 'plugin.bitbucket.service',
});
export type PullRequest = {
id: number;
title: string;
author: string;
created_on: string;
updated_on: string;
state: string;
description: string;
url: string;
};
const DEFAULT_PROXY_PATH = '/bitbucket/api';
type Options = {
discoveryApi: DiscoveryApi;
};
export class BitbucketApi {
private readonly discoveryApi: DiscoveryApi;

constructor(options: Options) {
this.discoveryApi = options.discoveryApi;
}

async fetchPullRequestList(
project: string,
repo: string,
state?: string,
): Promise<PullRequest[]> {
const proxyUrl = await this.discoveryApi.getBaseUrl('proxy');
const response = await fetch(
`${proxyUrl}${DEFAULT_PROXY_PATH}/projects/${project}/repos/${repo}/pull-requests?state=${
state || ''
}`,
{
headers: {
'Content-Type': 'application/json',
},
},
);
if (!response.ok) {
throw new Error('Failed to fetch pull requests');
}

const data = await response.json();

return data.values.map((pr: any) => ({
id: pr.id,
title: pr.title,
author: pr.author.user.name,
created_on: pr.createdDate,
updated_on: pr.updatedDate,
state: pr.state,
url: pr.links.self[0].href,
description: pr.description,
}));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2024 Larder Software Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { UrlPatternDiscovery } from '@backstage/core-app-api';
import { AnyApiRef } from '@backstage/core-plugin-api';
import { EntityProvider } from '@backstage/plugin-catalog-react';
import { rest } from 'msw';
import {
setupRequestMockHandlers,
TestApiProvider,
} from '@backstage/test-utils';
import { setupServer } from 'msw/node';
import { bitbucketApiRef, BitbucketApi } from '../api/BitbucketApi';
import PullRequestList from '../components/PullRequestList';
import { pullRequestsResponseStub, entityStub } from '../responseStubs';
import { render, screen, waitFor } from '@testing-library/react';

const discoveryApi = UrlPatternDiscovery.compile('http://exampleapi.com');

const apis: [AnyApiRef, Partial<unknown>][] = [
[bitbucketApiRef, new BitbucketApi({ discoveryApi })],
];

describe('PullRequestList', () => {
const worker = setupServer();
setupRequestMockHandlers(worker);

beforeEach(() => jest.resetAllMocks());

it('should display a table with the data from the requests', async () => {
worker.use(
rest.get(
'http://exampleapi.com/bitbucket/api/projects/testproject/repos/testrepo/pull-requests',
(_, res, ctx) => res(ctx.json(pullRequestsResponseStub)),
),
);
render(
<TestApiProvider apis={apis}>
<EntityProvider entity={entityStub}>
<PullRequestList />
</EntityProvider>
</TestApiProvider>,
);

// test the table header
await waitFor(() => {
expect(screen.getByText('Bitbucket Pull Requests')).toBeInTheDocument();
});

// test the table title
await waitFor(() => {
expect(screen.getByText('testproject/testrepo')).toBeInTheDocument();
});

// test each column title
await waitFor(() => {
expect(screen.getByText(/ID/i)).toBeInTheDocument();
expect(screen.getByText(/TITLE/i)).toBeInTheDocument();
expect(screen.getByText(/AUTHOR/i)).toBeInTheDocument();
expect(screen.getByText(/STATE/i)).toBeInTheDocument();
expect(screen.getByText(/CREATED/i)).toBeInTheDocument();
expect(screen.getByText(/LAST UPDATED/i)).toBeInTheDocument();
});

// check the count of row equal to mock response count
await waitFor(() => {
const rows = screen.getAllByRole('row');
expect(rows).toHaveLength(3);
});
});
});
Loading

0 comments on commit befa6f3

Please sign in to comment.