feat: support codeowners in catalog-import
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-import': patch
|
||||
---
|
||||
|
||||
Allows the CodeOwnersProcessor to set the owner automatically within the catalog-import plugin. This adds an additional checkbox that overrides the group selector and will omit the owner option in the generated catalog file yaml.
|
||||
@@ -22,6 +22,7 @@ import { StepFinishImportLocation } from '../StepFinishImportLocation';
|
||||
import { StepInitAnalyzeUrl } from '../StepInitAnalyzeUrl';
|
||||
import {
|
||||
AutocompleteTextField,
|
||||
CheckboxField,
|
||||
StepPrepareCreatePullRequest,
|
||||
} from '../StepPrepareCreatePullRequest';
|
||||
import { StepPrepareSelectLocations } from '../StepPrepareSelectLocations';
|
||||
@@ -169,74 +170,97 @@ export function defaultGenerateStepper(
|
||||
renderFormFields={({
|
||||
control,
|
||||
errors,
|
||||
watch,
|
||||
groupsLoading,
|
||||
groups,
|
||||
register,
|
||||
}) => (
|
||||
<>
|
||||
<Box marginTop={2}>
|
||||
<Typography variant="h6">Pull Request Details</Typography>
|
||||
</Box>
|
||||
}) => {
|
||||
const watchUseCodeowners = watch('useCodeowners', false);
|
||||
|
||||
<TextField
|
||||
name="title"
|
||||
label="Pull Request Title"
|
||||
placeholder="Add Backstage catalog entity descriptor files"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
inputRef={register({ required: true })}
|
||||
error={Boolean(errors.title)}
|
||||
required
|
||||
/>
|
||||
return (
|
||||
<>
|
||||
<Box marginTop={2}>
|
||||
<Typography variant="h6">
|
||||
Pull Request Details
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<TextField
|
||||
name="body"
|
||||
label="Pull Request Body"
|
||||
placeholder="A describing text with Markdown support"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
inputRef={register({ required: true })}
|
||||
error={Boolean(errors.body)}
|
||||
multiline
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
name="title"
|
||||
label="Pull Request Title"
|
||||
placeholder="Add Backstage catalog entity descriptor files"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
inputRef={register({ required: true })}
|
||||
error={Boolean(errors.title)}
|
||||
required
|
||||
/>
|
||||
|
||||
<Box marginTop={2}>
|
||||
<Typography variant="h6">Entity Configuration</Typography>
|
||||
</Box>
|
||||
<TextField
|
||||
name="body"
|
||||
label="Pull Request Body"
|
||||
placeholder="A describing text with Markdown support"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
inputRef={register({ required: true })}
|
||||
error={Boolean(errors.body)}
|
||||
multiline
|
||||
required
|
||||
/>
|
||||
|
||||
<TextField
|
||||
name="componentName"
|
||||
label="Name of the created component"
|
||||
placeholder="my-component"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
inputRef={register({ required: true })}
|
||||
error={Boolean(errors.componentName)}
|
||||
required
|
||||
/>
|
||||
<Box marginTop={2}>
|
||||
<Typography variant="h6">
|
||||
Entity Configuration
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<AutocompleteTextField
|
||||
name="owner"
|
||||
control={control}
|
||||
errors={errors}
|
||||
options={groups || []}
|
||||
loading={groupsLoading}
|
||||
loadingText="Loading groups…"
|
||||
helperText="Select an owner from the list or enter a reference to a Group or a User"
|
||||
errorHelperText="required value"
|
||||
textFieldProps={{
|
||||
label: 'Entity Owner',
|
||||
placeholder: 'my-group',
|
||||
}}
|
||||
rules={{ required: true }}
|
||||
required
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<TextField
|
||||
name="componentName"
|
||||
label="Name of the created component"
|
||||
placeholder="my-component"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
inputRef={register({ required: true })}
|
||||
error={Boolean(errors.componentName)}
|
||||
required
|
||||
/>
|
||||
|
||||
{!watchUseCodeowners && (
|
||||
<AutocompleteTextField
|
||||
name="owner"
|
||||
control={control}
|
||||
errors={errors}
|
||||
options={groups || []}
|
||||
loading={groupsLoading}
|
||||
loadingText="Loading groups…"
|
||||
helperText="Select an owner from the list or enter a reference to a Group or a User"
|
||||
errorHelperText="required value"
|
||||
textFieldProps={{
|
||||
label: 'Entity Owner',
|
||||
placeholder: 'my-group',
|
||||
}}
|
||||
rules={{ required: true }}
|
||||
required
|
||||
/>
|
||||
)}
|
||||
|
||||
<CheckboxField
|
||||
name="useCodeowners"
|
||||
inputRef={register}
|
||||
label="Use CODEOWNERS File"
|
||||
helperText="WARNING: This may fail is no CODEOWNERS file is found at the target location."
|
||||
onChange={(_, value) => {
|
||||
if (value) {
|
||||
control.setValue('owner', '');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2021 Spotify AB
|
||||
*
|
||||
* 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 { Checkbox, FormControlLabel, FormHelperText } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
|
||||
type Props<TFieldValue extends string> = {
|
||||
name: TFieldValue;
|
||||
label: React.ReactNode;
|
||||
inputRef:
|
||||
| ((instance: HTMLInputElement | null) => void)
|
||||
| React.RefObject<HTMLInputElement>
|
||||
| null
|
||||
| undefined;
|
||||
onChange?: (
|
||||
event: React.ChangeEvent<HTMLInputElement>,
|
||||
checked: boolean,
|
||||
) => void;
|
||||
helperText?: React.ReactNode | string;
|
||||
};
|
||||
|
||||
export const CheckboxField = <TFieldValue extends string>({
|
||||
name,
|
||||
label,
|
||||
inputRef,
|
||||
onChange,
|
||||
helperText,
|
||||
}: Props<TFieldValue>) => {
|
||||
return (
|
||||
<>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox name={name} inputRef={inputRef} onChange={onChange} />
|
||||
}
|
||||
label={label}
|
||||
/>
|
||||
{helperText && <FormHelperText>{helperText}</FormHelperText>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -32,7 +32,7 @@ type Props<TFieldValues extends Record<string, any>> = Pick<
|
||||
render: (
|
||||
props: Pick<
|
||||
UseFormMethods<TFieldValues>,
|
||||
'errors' | 'register' | 'control'
|
||||
'errors' | 'register' | 'control' | 'formState' | 'watch'
|
||||
> & {
|
||||
values: UnpackNestedValue<TFieldValues>;
|
||||
},
|
||||
@@ -55,13 +55,13 @@ export const PreparePullRequestForm = <
|
||||
onSubmit,
|
||||
render,
|
||||
}: Props<TFieldValues>) => {
|
||||
const { handleSubmit, watch, control, register, errors } = useForm<
|
||||
const { handleSubmit, control, register, errors, formState, watch } = useForm<
|
||||
TFieldValues
|
||||
>({ mode: 'onTouched', defaultValues });
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{render({ values: watch(), errors, register, control })}
|
||||
{render({ values: watch(), errors, register, control, formState, watch })}
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
+11
-4
@@ -48,6 +48,7 @@ type FormData = {
|
||||
body: string;
|
||||
componentName: string;
|
||||
owner: string;
|
||||
useCodeowners: boolean;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
@@ -62,7 +63,10 @@ type Props = {
|
||||
defaultBody: string;
|
||||
|
||||
renderFormFields: (
|
||||
props: Pick<UseFormMethods<FormData>, 'errors' | 'register' | 'control'> & {
|
||||
props: Pick<
|
||||
UseFormMethods<FormData>,
|
||||
'errors' | 'register' | 'control' | 'formState' | 'watch'
|
||||
> & {
|
||||
groups: string[];
|
||||
groupsLoading: boolean;
|
||||
},
|
||||
@@ -72,7 +76,7 @@ type Props = {
|
||||
export function generateEntities(
|
||||
entities: PartialEntity[],
|
||||
componentName: string,
|
||||
owner: string,
|
||||
owner?: string,
|
||||
): Entity[] {
|
||||
return entities.map(e => ({
|
||||
...e,
|
||||
@@ -84,7 +88,7 @@ export function generateEntities(
|
||||
},
|
||||
spec: {
|
||||
...e.spec,
|
||||
owner,
|
||||
...(owner ? { owner } : {}),
|
||||
},
|
||||
}));
|
||||
}
|
||||
@@ -189,13 +193,16 @@ export const StepPrepareCreatePullRequest = ({
|
||||
(analyzeResult.generatedEntities[0]?.spec?.owner as string) || '',
|
||||
componentName:
|
||||
analyzeResult.generatedEntities[0]?.metadata?.name || '',
|
||||
useCodeowners: false,
|
||||
}}
|
||||
render={({ values, errors, control, register }) => (
|
||||
render={({ values, errors, control, register, formState, watch }) => (
|
||||
<>
|
||||
{renderFormFields({
|
||||
errors,
|
||||
register,
|
||||
control,
|
||||
formState,
|
||||
watch,
|
||||
groups: groups ?? [],
|
||||
groupsLoading,
|
||||
})}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
export { AutocompleteTextField } from './AutocompleteTextField';
|
||||
export { CheckboxField } from './CheckboxField';
|
||||
export { PreparePullRequestForm } from './PreparePullRequestForm';
|
||||
export { PreviewCatalogInfoComponent } from './PreviewCatalogInfoComponent';
|
||||
export { PreviewPullRequestComponent } from './PreviewPullRequestComponent';
|
||||
|
||||
Reference in New Issue
Block a user