feat: add BitbucketRepoBranchPicker
Signed-off-by: Benjamin Janssens <benji.janssens@gmail.com>
This commit is contained in:
+145
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* 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 {
|
||||
scaffolderApiRef,
|
||||
useTemplateSecrets,
|
||||
} from '@backstage/plugin-scaffolder-react';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Autocomplete from '@material-ui/lab/Autocomplete';
|
||||
import useDebounce from 'react-use/esm/useDebounce';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { scmAuthApiRef } from '@backstage/integration-react';
|
||||
import { BitbucketRepoBranchPickerProps } from './schema';
|
||||
|
||||
export const BitbucketRepoBranchPicker = ({
|
||||
onChange,
|
||||
rawErrors,
|
||||
required,
|
||||
formData,
|
||||
formContext: {
|
||||
formData: { repoUrl },
|
||||
},
|
||||
uiSchema,
|
||||
}: BitbucketRepoBranchPickerProps) => {
|
||||
const { secrets, setSecrets } = useTemplateSecrets();
|
||||
|
||||
const accessToken = useMemo(
|
||||
() =>
|
||||
uiSchema?.['ui:options']?.requestUserCredentials?.secretsKey &&
|
||||
secrets[uiSchema['ui:options'].requestUserCredentials.secretsKey],
|
||||
[secrets, uiSchema],
|
||||
);
|
||||
|
||||
const [host, setHost] = useState<string>();
|
||||
const [workspace, setWorkspace] = useState<string | null>(null);
|
||||
const [repository, setRepository] = useState<string | null>(null);
|
||||
const [availableBranches, setAvailableBranches] = useState<string[]>([]);
|
||||
|
||||
const scmAuthApi = useApi(scmAuthApiRef);
|
||||
const scaffolderApi = useApi(scaffolderApiRef);
|
||||
|
||||
useEffect(() => {
|
||||
if (repoUrl) {
|
||||
const url = new URL(`https://${repoUrl}`);
|
||||
setHost(url.host);
|
||||
setWorkspace(url.searchParams.get('workspace'));
|
||||
setRepository(url.searchParams.get('repo'));
|
||||
}
|
||||
}, [repoUrl]);
|
||||
|
||||
useDebounce(
|
||||
async () => {
|
||||
const { requestUserCredentials } = uiSchema?.['ui:options'] ?? {};
|
||||
|
||||
if (!requestUserCredentials || !host) {
|
||||
return;
|
||||
}
|
||||
|
||||
// don't show login prompt if secret value is already in state
|
||||
if (secrets[requestUserCredentials.secretsKey]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// user has requested that we use the users credentials
|
||||
// so lets grab them using the scmAuthApi and pass through
|
||||
// any additional scopes from the ui:options
|
||||
const { token } = await scmAuthApi.getCredentials({
|
||||
url: `https://${host}`,
|
||||
additionalScope: {
|
||||
repoWrite: true,
|
||||
customScopes: requestUserCredentials.additionalScopes,
|
||||
},
|
||||
});
|
||||
|
||||
// set the secret using the key provided in the ui:options for use
|
||||
// in the templating the manifest with ${{ secrets[secretsKey] }}
|
||||
setSecrets({ [requestUserCredentials.secretsKey]: token });
|
||||
},
|
||||
500,
|
||||
[host, uiSchema],
|
||||
);
|
||||
|
||||
useDebounce(
|
||||
() => {
|
||||
const updateAvailableBranches = async () => {
|
||||
if (
|
||||
host === 'bitbucket.org' &&
|
||||
accessToken &&
|
||||
workspace &&
|
||||
repository
|
||||
) {
|
||||
const result = await scaffolderApi.autocomplete(
|
||||
accessToken,
|
||||
'bitbucketCloud',
|
||||
'branches',
|
||||
{ workspace, repository },
|
||||
);
|
||||
|
||||
setAvailableBranches(result);
|
||||
} else {
|
||||
setAvailableBranches([]);
|
||||
}
|
||||
};
|
||||
|
||||
updateAvailableBranches().catch(() => setAvailableBranches([]));
|
||||
},
|
||||
500,
|
||||
[host, accessToken, workspace, repository],
|
||||
);
|
||||
|
||||
return (
|
||||
<FormControl
|
||||
margin="normal"
|
||||
required={required}
|
||||
error={rawErrors?.length > 0 && !formData}
|
||||
>
|
||||
<Autocomplete
|
||||
onChange={(_, newValue) => {
|
||||
onChange(newValue || '');
|
||||
}}
|
||||
options={availableBranches}
|
||||
renderInput={params => (
|
||||
<TextField {...params} label="Branch" required={required} />
|
||||
)}
|
||||
freeSolo
|
||||
autoSelect
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export {
|
||||
BitbucketRepoBranchPickerFieldSchema,
|
||||
type BitbucketRepoBranchPickerUiOptions,
|
||||
} from './schema';
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* 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 { z } from 'zod';
|
||||
import { makeFieldSchemaFromZod } from '../utils';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const BitbucketRepoBranchPickerFieldSchema = makeFieldSchemaFromZod(
|
||||
z.string(),
|
||||
z.object({
|
||||
requestUserCredentials: z
|
||||
.object({
|
||||
secretsKey: z
|
||||
.string()
|
||||
.describe(
|
||||
'Key used within the template secrets context to store the credential',
|
||||
),
|
||||
additionalScopes: z
|
||||
.object({
|
||||
gitea: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('Additional Gitea scopes to request'),
|
||||
gerrit: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('Additional Gerrit scopes to request'),
|
||||
github: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('Additional GitHub scopes to request'),
|
||||
gitlab: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('Additional GitLab scopes to request'),
|
||||
bitbucket: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('Additional BitBucket scopes to request'),
|
||||
azure: z
|
||||
.array(z.string())
|
||||
.optional()
|
||||
.describe('Additional Azure scopes to request'),
|
||||
})
|
||||
.optional()
|
||||
.describe('Additional permission scopes to request'),
|
||||
})
|
||||
.optional()
|
||||
.describe(
|
||||
'If defined will request user credentials to auth against the given SCM platform',
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* The input props that can be specified under `ui:options` for the
|
||||
* `BitbucketRepoBranchPicker` field extension.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type BitbucketRepoBranchPickerUiOptions =
|
||||
typeof BitbucketRepoBranchPickerFieldSchema.uiOptionsType;
|
||||
|
||||
export type BitbucketRepoBranchPickerProps =
|
||||
typeof BitbucketRepoBranchPickerFieldSchema.type;
|
||||
|
||||
export const BitbucketRepoBranchPickerSchema =
|
||||
BitbucketRepoBranchPickerFieldSchema.schema;
|
||||
@@ -50,6 +50,8 @@ import {
|
||||
MultiEntityPickerSchema,
|
||||
validateMultiEntityPickerValidation,
|
||||
} from '../components/fields/MultiEntityPicker/MultiEntityPicker';
|
||||
import { BitbucketRepoBranchPicker } from '../components/fields/BitbucketRepoBranchPicker/BitbucketRepoBranchPicker';
|
||||
import { BitbucketRepoBranchPickerSchema } from '../components/fields/BitbucketRepoBranchPicker/schema';
|
||||
|
||||
export const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [
|
||||
{
|
||||
@@ -99,4 +101,9 @@ export const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [
|
||||
schema: MultiEntityPickerSchema,
|
||||
validation: validateMultiEntityPickerValidation,
|
||||
},
|
||||
{
|
||||
component: BitbucketRepoBranchPicker,
|
||||
name: 'BitbucketRepoBranchPicker',
|
||||
schema: BitbucketRepoBranchPickerSchema,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -30,6 +30,7 @@ export {
|
||||
MyGroupsPickerFieldExtension,
|
||||
RepoUrlPickerFieldExtension,
|
||||
MultiEntityPickerFieldExtension,
|
||||
BitbucketRepoBranchPickerFieldExtension,
|
||||
ScaffolderPage,
|
||||
scaffolderPlugin,
|
||||
} from './plugin';
|
||||
|
||||
@@ -73,6 +73,8 @@ import {
|
||||
MyGroupsPicker,
|
||||
MyGroupsPickerSchema,
|
||||
} from './components/fields/MyGroupsPicker/MyGroupsPicker';
|
||||
import { BitbucketRepoBranchPicker } from './components/fields/BitbucketRepoBranchPicker/BitbucketRepoBranchPicker';
|
||||
import { BitbucketRepoBranchPickerSchema } from './components/fields/BitbucketRepoBranchPicker/schema';
|
||||
|
||||
/**
|
||||
* The main plugin export for the scaffolder.
|
||||
@@ -231,3 +233,16 @@ export const EntityTagsPickerFieldExtension = scaffolderPlugin.provide(
|
||||
schema: EntityTagsPickerSchema,
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* A field extension to select a branch from a repository.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const BitbucketRepoBranchPickerFieldExtension = scaffolderPlugin.provide(
|
||||
createScaffolderFieldExtension({
|
||||
component: BitbucketRepoBranchPicker,
|
||||
name: 'BitbucketRepoBranchPicker',
|
||||
schema: BitbucketRepoBranchPickerSchema,
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user