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:
@@ -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
|
||||
|
||||
@@ -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' && (
|
||||
|
||||
@@ -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:^"
|
||||
|
||||
Reference in New Issue
Block a user