feat: add auto-completion to workspaces and repositories

Signed-off-by: Benjamin Janssens <benji.janssens@gmail.com>
This commit is contained in:
Benjamin Janssens
2024-06-07 13:15:14 +02:00
parent fbbf152f50
commit 0aaf020359
6 changed files with 124 additions and 58 deletions
@@ -85,6 +85,16 @@ export class BitbucketCloudClient {
);
}
listWorkspaces(
options?: FilterAndSortOptions & PartialResponseOptions,
): WithPagination<Models.PaginatedWorkspaces, Models.Workspace> {
return new WithPagination(
paginationOptions =>
this.createUrl('/workspaces', { ...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) {
@@ -264,6 +264,17 @@ export namespace Models {
values?: Set<Project>;
}
/**
* A paginated list of workspaces.
* @public
*/
export interface PaginatedWorkspaces extends Paginated<Workspace> {
/**
* The values of the current page.
*/
values?: Set<Workspace>;
}
/**
* Object describing a user's role on resources like commits or pull requests.
* @public
@@ -17,8 +17,6 @@
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';
@@ -65,10 +63,8 @@ export const BitbucketRepoPicker = (props: {
}, [allowedOwners, host, onChange]);
const [client, setClient] = useState<BitbucketCloudClient>();
const [availableWorkspaces, setAvailableWorkspaces] = useState<string[]>([]);
const [availableProjects, setAvailableProjects] = useState<string[]>([]);
const [availableRepositories, setAvailableRepositories] = useState<string[]>(
[],
);
useEffect(() => {
if (accessToken)
@@ -81,39 +77,78 @@ export const BitbucketRepoPicker = (props: {
);
}, [accessToken]);
const onChangeWorkspace = async () => {
if (client)
if (!workspace) {
setAvailableProjects([]);
} else {
try {
for await (const page of client
.listProjectsByWorkspace(workspace)
.iteratePages()) {
const keys = [...page.values!].map(p => p.key!);
setAvailableProjects([...availableProjects, ...keys]);
}
} catch {
setAvailableProjects([]);
}
}
};
// Update available workspaces when host changes
useEffect(() => {
const updateAvailableWorkspaces = async () => {
if (client)
if (!host) {
setAvailableWorkspaces([]);
} else {
const result: string[] = [];
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]);
for await (const page of client.listWorkspaces().iteratePages()) {
const keys = [...page.values!].map(p => p.slug!);
result.push(...keys);
}
setAvailableWorkspaces(result);
}
}
};
};
updateAvailableWorkspaces();
}, [client, host]);
// Update available repositories when workspace or project changes
useEffect(() => {
const updateAvailableRepositories = async () => {
if (client && workspace)
if (!project) {
onChange({ availableRepos: [] });
} else {
const availableRepos: string[] = [];
for await (const page of client
.listRepositoriesByWorkspace(workspace, {
q: `project.key="${project}"`,
})
.iteratePages()) {
const keys = [...page.values!].map(p => p.slug!);
availableRepos.push(...keys);
}
onChange({ availableRepos });
}
};
updateAvailableRepositories();
}, [client, workspace, project, onChange]);
// Update available projects when workspace changes
useEffect(() => {
const updateAvailableProjects = async () => {
if (client)
if (!workspace) {
setAvailableProjects([]);
} else {
try {
const result: string[] = [];
for await (const page of client
.listProjectsByWorkspace(workspace)
.iteratePages()) {
const keys = [...page.values!].map(p => p.key!);
result.push(...keys);
}
setAvailableProjects(result);
} catch {
setAvailableProjects([]);
}
}
};
updateAvailableProjects();
}, [client, workspace]);
return (
<>
@@ -135,15 +170,18 @@ export const BitbucketRepoPicker = (props: {
items={ownerItems}
/>
) : (
<>
<InputLabel htmlFor="workspaceInput">Workspace</InputLabel>
<Input
id="workspaceInput"
onChange={e => onChange({ workspace: e.target.value })}
value={workspace}
onBlur={() => onChangeWorkspace()}
/>
</>
<Autocomplete
value={workspace}
onChange={(_, newValue) => {
onChange({ workspace: newValue || '' });
}}
options={availableWorkspaces}
renderInput={params => (
<TextField {...params} label="Workspace" required />
)}
freeSolo
autoSelect
/>
)}
<FormHelperText>
The Workspace that this repo will belong to
@@ -173,9 +211,10 @@ export const BitbucketRepoPicker = (props: {
onChange({ project: newValue || '' });
}}
options={availableProjects}
renderInput={params => <TextField {...params} label="Project" />}
renderInput={params => (
<TextField {...params} label="Project" required />
)}
freeSolo
onBlur={() => onChangeProject()}
autoSelect
/>
)}
@@ -232,6 +232,7 @@ export const RepoUrlPicker = (props: RepoUrlPickerProps) => {
setState(prevState => ({ ...prevState, repoName: repo }))
}
rawErrors={rawErrors}
availableRepos={state.availableRepos}
/>
</>
);
@@ -17,16 +17,17 @@ import React, { useEffect } from 'react';
import { Select, SelectItem } from '@backstage/core-components';
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 Autocomplete from '@material-ui/lab/Autocomplete';
import TextField from '@material-ui/core/TextField';
export const RepoUrlPickerRepoName = (props: {
repoName?: string;
allowedRepos?: string[];
onChange: (host: string) => void;
rawErrors: string[];
availableRepos?: string[];
}) => {
const { repoName, allowedRepos, onChange, rawErrors } = props;
const { repoName, allowedRepos, onChange, rawErrors, availableRepos } = props;
useEffect(() => {
// If there is no repoName chosen currently
@@ -61,14 +62,17 @@ export const RepoUrlPickerRepoName = (props: {
items={repoItems}
/>
) : (
<>
<InputLabel htmlFor="repoNameInput">Repository</InputLabel>
<Input
id="repoNameInput"
onChange={e => onChange(String(e.target.value))}
value={repoName}
/>
</>
<Autocomplete
value={repoName}
onChange={(_, newValue) => {
onChange(newValue || '');
}}
options={availableRepos || []}
renderInput={params => (
<TextField {...params} label="Repository" required />
)}
freeSolo
/>
)}
<FormHelperText>The name of the repository</FormHelperText>
</FormControl>
@@ -20,4 +20,5 @@ export interface RepoUrlPickerState {
organization?: string;
workspace?: string;
project?: string;
availableRepos?: string[];
}