feat: fetch workspaces and projects in BitbucketRepoPicker; add support for access tokens to BitbucketCloudClient

Signed-off-by: Benjamin Janssens <benji.janssens@gmail.com>
This commit is contained in:
Benjamin Janssens
2024-06-06 09:54:41 +02:00
parent c0981a83eb
commit 39e5dfd79b
7 changed files with 94 additions and 1 deletions
@@ -46,6 +46,11 @@ export type BitbucketCloudIntegrationConfig = {
* See https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/
*/
appPassword?: string;
/**
* The access token to use for requests to Bitbucket Cloud (bitbucket.org).
*/
accessToken?: string;
};
/**
@@ -69,6 +69,22 @@ export class BitbucketCloudClient {
);
}
listProjectsByWorkspace(
workspace: string,
options?: FilterAndSortOptions & PartialResponseOptions,
): WithPagination<Models.PaginatedProjects, Models.Project> {
const workspaceEnc = encodeURIComponent(workspace);
return new WithPagination(
paginationOptions =>
this.createUrl(`/workspaces/${workspaceEnc}/projects`, {
...paginationOptions,
...options,
}),
url => this.getTypeMapped(url),
);
}
private createUrl(endpoint: string, options?: RequestOptions): URL {
const request = new URL(this.config.apiBaseUrl + endpoint);
for (const key in options) {
@@ -115,6 +131,9 @@ export class BitbucketCloudClient {
headers.Authorization = `Basic ${buffer.toString('base64')}`;
}
if (this.config.accessToken)
headers.Authorization = `Bearer ${this.config.accessToken}`;
return headers;
}
}
@@ -253,6 +253,17 @@ export namespace Models {
values?: Set<Repository>;
}
/**
* A paginated list of projects.
* @public
*/
export interface PaginatedProjects extends Paginated<Project> {
/**
* The values of the current page.
*/
values?: Set<Project>;
}
/**
* Object describing a user's role on resources like commits or pull requests.
* @public
+1
View File
@@ -67,6 +67,7 @@
"@backstage/frontend-plugin-api": "workspace:^",
"@backstage/integration": "workspace:^",
"@backstage/integration-react": "workspace:^",
"@backstage/plugin-bitbucket-cloud-common": "workspace:^",
"@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-catalog-react": "workspace:^",
"@backstage/plugin-permission-react": "workspace:^",
@@ -13,13 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import FormControl from '@material-ui/core/FormControl';
import FormHelperText from '@material-ui/core/FormHelperText';
import Input from '@material-ui/core/Input';
import InputLabel from '@material-ui/core/InputLabel';
import { Select, SelectItem } from '@backstage/core-components';
import { RepoUrlPickerState } from './types';
import { BitbucketCloudClient } from '@backstage/plugin-bitbucket-cloud-common';
/**
* The underlying component that is rendered in the form for the `BitbucketRepoPicker`
@@ -36,6 +37,7 @@ export const BitbucketRepoPicker = (props: {
onChange: (state: RepoUrlPickerState) => void;
state: RepoUrlPickerState;
rawErrors: string[];
accessToken?: string;
}) => {
const {
allowedOwners = [],
@@ -43,6 +45,7 @@ export const BitbucketRepoPicker = (props: {
onChange,
rawErrors,
state,
accessToken,
} = props;
const { host, workspace, project } = state;
const ownerItems: SelectItem[] = allowedOwners
@@ -58,6 +61,53 @@ export const BitbucketRepoPicker = (props: {
}
}, [allowedOwners, host, onChange]);
const [client, setClient] = useState<BitbucketCloudClient>();
const [availableProjects, setAvailableProjects] = useState<string[]>([]);
const [availableRepositories, setAvailableRepositories] = useState<string[]>(
[],
);
useEffect(() => {
if (accessToken)
setClient(
BitbucketCloudClient.fromConfig({
host: 'bitbucket.org',
apiBaseUrl: 'https://api.bitbucket.org/2.0',
accessToken,
}),
);
}, [accessToken]);
const onChangeWorkspace = async () => {
if (client)
if (!workspace) {
setAvailableProjects([]);
} else {
for await (const page of client
.listProjectsByWorkspace(workspace)
.iteratePages()) {
const keys = [...page.values!].map(p => p.key!);
setAvailableProjects([...availableProjects, ...keys]);
}
}
};
const onChangeProject = async () => {
if (client && workspace)
if (!project) {
setAvailableRepositories([]);
} else {
for await (const page of client
.listRepositoriesByWorkspace(workspace, {
q: `project.key="${project}"`,
})
.iteratePages()) {
const keys = [...page.values!].map(p => p.slug!);
setAvailableRepositories([...availableRepositories, ...keys]);
}
}
};
return (
<>
{host === 'bitbucket.org' && (
@@ -84,6 +134,7 @@ export const BitbucketRepoPicker = (props: {
id="workspaceInput"
onChange={e => onChange({ workspace: e.target.value })}
value={workspace}
onBlur={() => onChangeWorkspace()}
/>
</>
)}
@@ -115,6 +166,7 @@ export const BitbucketRepoPicker = (props: {
id="projectInput"
onChange={e => onChange({ project: e.target.value })}
value={project}
onBlur={() => onChangeProject()}
/>
</>
)}
@@ -203,6 +203,10 @@ export const RepoUrlPicker = (props: RepoUrlPickerProps) => {
rawErrors={rawErrors}
state={state}
onChange={updateLocalState}
accessToken={
uiSchema?.['ui:options']?.requestUserCredentials?.secretsKey &&
secrets[uiSchema['ui:options'].requestUserCredentials.secretsKey]
}
/>
)}
{hostType === 'azure' && (
+1
View File
@@ -7268,6 +7268,7 @@ __metadata:
"@backstage/frontend-plugin-api": "workspace:^"
"@backstage/integration": "workspace:^"
"@backstage/integration-react": "workspace:^"
"@backstage/plugin-bitbucket-cloud-common": "workspace:^"
"@backstage/plugin-catalog": "workspace:^"
"@backstage/plugin-catalog-common": "workspace:^"
"@backstage/plugin-catalog-react": "workspace:^"