Sfoglia il codice sorgente

SONAR-13127: Adding front config for summary analysis comment for GH.

tags/8.3.0.34182
Mathieu Suen 4 anni fa
parent
commit
748fe66f03

+ 49
- 20
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx Vedi File

@@ -38,9 +38,11 @@ interface Props {
interface State {
formData: ProjectAlmBinding;
instances: AlmSettingsInstance[];
isChanged: boolean;
isConfigured: boolean;
isValid: boolean;
loading: boolean;
originalData?: ProjectAlmBinding;
orignalData?: ProjectAlmBinding;
saving: boolean;
success: boolean;
}
@@ -59,6 +61,8 @@ export default class PRDecorationBinding extends React.PureComponent<Props, Stat
state: State = {
formData: { key: '' },
instances: [],
isChanged: false,
isConfigured: false,
isValid: false,
loading: true,
saving: false,
@@ -84,9 +88,11 @@ export default class PRDecorationBinding extends React.PureComponent<Props, Stat
return {
formData: newFormData,
instances: instances || [],
isChanged: false,
isConfigured: !!originalData,
isValid: this.validateForm(newFormData),
loading: false,
originalData
orignalData: newFormData
};
});
}
@@ -125,7 +131,8 @@ export default class PRDecorationBinding extends React.PureComponent<Props, Stat
repository: '',
slug: ''
},
originalData: undefined,
isChanged: false,
isConfigured: false,
saving: false,
success: true
});
@@ -161,14 +168,20 @@ export default class PRDecorationBinding extends React.PureComponent<Props, Stat
});
}
case AlmKeys.GitHub: {
const repository = almSpecificFields && almSpecificFields.repository;
const repository = almSpecificFields?.repository;
// By default it must remain true.
const summaryCommentEnabled =
almSpecificFields?.summaryCommentEnabled === undefined
? true
: almSpecificFields?.summaryCommentEnabled;
if (!repository) {
return Promise.reject();
}
return setProjectGithubBinding({
almSetting,
project,
repository
repository,
summaryCommentEnabled
});
}

@@ -198,23 +211,38 @@ export default class PRDecorationBinding extends React.PureComponent<Props, Stat
return;
}

if (key) {
this.submitProjectAlmBinding(selected.alm, key, additionalFields)
.then(() => {
if (this.mounted) {
this.setState({
saving: false,
success: true
});
}
})
.then(this.fetchDefinitions)
.catch(this.catchError);
}
this.submitProjectAlmBinding(selected.alm, key, additionalFields)
.then(() => {
if (this.mounted) {
this.setState({
saving: false,
success: true
});
}
})
.then(this.fetchDefinitions)
.catch(this.catchError);
};

handleFieldChange = (id: keyof ProjectAlmBinding, value: string) => {
this.setState(({ formData }) => {
isDataSame(
{ key, repository = '', slug = '', summaryCommentEnabled = false }: ProjectAlmBinding,
{
key: oKey = '',
repository: oRepository = '',
slug: oSlug = '',
summaryCommentEnabled: osummaryCommentEnabled = false
}: ProjectAlmBinding
) {
return (
key === oKey &&
repository === oRepository &&
slug === oSlug &&
summaryCommentEnabled === osummaryCommentEnabled
);
}

handleFieldChange = (id: keyof ProjectAlmBinding, value: string | boolean) => {
this.setState(({ formData, orignalData }) => {
const newFormData = {
...formData,
[id]: value
@@ -222,6 +250,7 @@ export default class PRDecorationBinding extends React.PureComponent<Props, Stat
return {
formData: newFormData,
isValid: this.validateForm(newFormData),
isChanged: !this.isDataSame(newFormData, orignalData || { key: '' }),
success: false
};
});

+ 88
- 50
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx Vedi File

@@ -28,20 +28,34 @@ import { Alert } from 'sonar-ui-common/components/ui/Alert';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { AlmKeys, AlmSettingsInstance, ProjectAlmBinding } from '../../../../types/alm-settings';
import InputForBoolean from '../inputs/InputForBoolean';

export interface PRDecorationBindingRendererProps {
formData: ProjectAlmBinding;
instances: AlmSettingsInstance[];
isChanged: boolean;
isConfigured: boolean;
isValid: boolean;
loading: boolean;
onFieldChange: (id: keyof ProjectAlmBinding, value: string) => void;
onFieldChange: (id: keyof ProjectAlmBinding, value: string | boolean) => void;
onReset: () => void;
onSubmit: () => void;
originalData?: ProjectAlmBinding;
saving: boolean;
success: boolean;
}

interface LabelProps {
help?: boolean;
helpParams?: T.Dict<string | JSX.Element>;
id: string;
optional?: boolean;
}

interface CommonFieldProps extends LabelProps {
onFieldChange: (id: keyof ProjectAlmBinding, value: string | boolean) => void;
propKey: keyof ProjectAlmBinding;
}

function optionRenderer(instance: AlmSettingsInstance) {
return instance.url ? (
<>
@@ -53,35 +67,57 @@ function optionRenderer(instance: AlmSettingsInstance) {
);
}

function renderField(props: {
help?: boolean;
helpParams?: { [key: string]: string | JSX.Element };
id: string;
onFieldChange: (id: keyof ProjectAlmBinding, value: string) => void;
optional?: boolean;
propKey: keyof ProjectAlmBinding;
value: string;
}) {
const { help, helpParams, id, propKey, optional, value, onFieldChange } = props;
function renderLabel(props: LabelProps) {
const { help, helpParams, optional, id } = props;
return (
<label className="display-flex-center" htmlFor={id}>
{translate('settings.pr_decoration.binding.form', id)}
{!optional && <em className="mandatory">*</em>}
{help && (
<HelpTooltip
className="spacer-left"
overlay={
<FormattedMessage
defaultMessage={translate('settings.pr_decoration.binding.form', id, 'help')}
id={`settings.pr_decoration.binding.form.${id}.help`}
values={helpParams}
/>
}
placement="right"
/>
)}
</label>
);
}

function renderBooleanField(
props: Omit<CommonFieldProps, 'optional'> & {
value: boolean;
}
) {
const { id, value, onFieldChange, propKey } = props;
return (
<div className="form-field">
<label className="display-flex-center" htmlFor={id}>
{translate('settings.pr_decoration.binding.form', id)}
{!optional && <em className="mandatory">*</em>}
{help && (
<HelpTooltip
className="spacer-left"
overlay={
<FormattedMessage
defaultMessage={translate('settings.pr_decoration.binding.form', id, 'help')}
id={`settings.pr_decoration.binding.form.${id}.help`}
values={helpParams}
/>
}
placement="right"
/>
)}
</label>
{renderLabel({ ...props, optional: true })}
<InputForBoolean
isDefault={true}
name={id}
onChange={v => onFieldChange(propKey, v)}
value={value}
/>
</div>
);
}

function renderField(
props: CommonFieldProps & {
value: string;
}
) {
const { id, propKey, value, onFieldChange } = props;
return (
<div className="form-field">
{renderLabel(props)}
<input
className="input-super-large"
id={id}
@@ -95,20 +131,14 @@ function renderField(props: {
);
}

function isDataSame(
{ key, repository = '', slug = '' }: ProjectAlmBinding,
{ key: oKey = '', repository: oRepository = '', slug: oSlug = '' }: ProjectAlmBinding
) {
return key === oKey && repository === oRepository && slug === oSlug;
}

export default function PRDecorationBindingRenderer(props: PRDecorationBindingRendererProps) {
const {
formData: { key, repository, slug },
formData: { key, repository, slug, summaryCommentEnabled },
instances,
isChanged,
isConfigured,
isValid,
loading,
originalData,
saving,
success
} = props;
@@ -140,8 +170,6 @@ export default function PRDecorationBindingRenderer(props: PRDecorationBindingRe
const selected = key && instances.find(i => i.key === key);
const alm = selected && selected.alm;

const isChanged = !isDataSame({ key, repository, slug }, originalData || { key: '' });

return (
<div>
<header className="page-header">
@@ -218,15 +246,25 @@ export default function PRDecorationBindingRenderer(props: PRDecorationBindingRe
</>
)}

{alm === AlmKeys.GitHub &&
renderField({
help: true,
helpParams: { example: 'SonarSource/sonarqube' },
id: 'github.repository',
onFieldChange: props.onFieldChange,
propKey: 'repository',
value: repository || ''
})}
{alm === AlmKeys.GitHub && (
<>
{renderField({
help: true,
helpParams: { example: 'SonarSource/sonarqube' },
id: 'github.repository',
onFieldChange: props.onFieldChange,
propKey: 'repository',
value: repository || ''
})}
{renderBooleanField({
help: true,
id: 'github.summary_comment_setting',
onFieldChange: props.onFieldChange,
propKey: 'summaryCommentEnabled',
value: summaryCommentEnabled === undefined ? true : summaryCommentEnabled
})}
</>
)}

{alm === AlmKeys.GitLab &&
renderField({
@@ -245,7 +283,7 @@ export default function PRDecorationBindingRenderer(props: PRDecorationBindingRe
<span data-test="project-settings__alm-save">{translate('save')}</span>
</SubmitButton>
)}
{originalData && (
{isConfigured && (
<Button className="spacer-right" onClick={props.onReset}>
<span data-test="project-settings__alm-reset">{translate('reset_verb')}</span>
</Button>

+ 73
- 18
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx Vedi File

@@ -66,7 +66,7 @@ it('should fill selects and fill formdata', async () => {

expect(wrapper.state().loading).toBe(false);
expect(wrapper.state().formData).toEqual(formdata);
expect(wrapper.state().originalData).toEqual(formdata);
expect(wrapper.state().isChanged).toBe(false);
});

it('should handle reset', async () => {
@@ -84,7 +84,7 @@ it('should handle reset', async () => {

expect(deleteProjectAlmBinding).toBeCalledWith(PROJECT_KEY);
expect(wrapper.state().formData).toEqual({ key: '', repository: '', slug: '' });
expect(wrapper.state().originalData).toBeUndefined();
expect(wrapper.state().isChanged).toBe(false);
});

describe('handleSubmit', () => {
@@ -99,14 +99,19 @@ describe('handleSubmit', () => {
await waitAndUpdate(wrapper);
const githubKey = 'github';
const repository = 'repo/path';
wrapper.setState({ formData: { key: githubKey, repository }, instances });
const summaryCommentEnabled = true;
wrapper.setState({
formData: { key: githubKey, repository, summaryCommentEnabled },
instances
});
wrapper.instance().handleSubmit();
await waitAndUpdate(wrapper);

expect(setProjectGithubBinding).toBeCalledWith({
almSetting: githubKey,
project: PROJECT_KEY,
repository
repository,
summaryCommentEnabled
});
expect(wrapper.state().success).toBe(true);
});
@@ -146,23 +151,31 @@ describe('handleSubmit', () => {
});
});

it('should handle failures gracefully', async () => {
(getProjectAlmBinding as jest.Mock).mockRejectedValueOnce({ status: 500 });
(setProjectGithubBinding as jest.Mock).mockRejectedValueOnce({ status: 500 });
(deleteProjectAlmBinding as jest.Mock).mockRejectedValueOnce({ status: 500 });

const wrapper = shallowRender();
await waitAndUpdate(wrapper);
wrapper.setState({
formData: {
describe.each([[500], [404]])('For status %i', status => {
it('should handle failures gracefully', async () => {
const newFormData = {
key: 'whatever',
repository: 'something/else'
}
});
};

wrapper.instance().handleSubmit();
await waitAndUpdate(wrapper);
wrapper.instance().handleReset();
(getProjectAlmBinding as jest.Mock).mockRejectedValueOnce({ status });
(setProjectGithubBinding as jest.Mock).mockRejectedValueOnce({ status });
(deleteProjectAlmBinding as jest.Mock).mockRejectedValueOnce({ status });

const wrapper = shallowRender();
await waitAndUpdate(wrapper);
wrapper.setState({
formData: newFormData,
orignalData: undefined
});

wrapper.instance().handleSubmit();
await waitAndUpdate(wrapper);
expect(wrapper.instance().state.orignalData).toBeUndefined();
wrapper.instance().handleReset();
await waitAndUpdate(wrapper);
expect(wrapper.instance().state.formData).toEqual(newFormData);
});
});

it('should handle field changes', async () => {
@@ -189,6 +202,48 @@ it('should handle field changes', async () => {
key: 'instance2',
repository
});

wrapper.instance().handleFieldChange('summaryCommentEnabled', true);
await waitAndUpdate(wrapper);
expect(wrapper.state().formData).toEqual({
key: 'instance2',
repository,
summaryCommentEnabled: true
});
});

it('should reject submit github settings', async () => {
const wrapper = shallowRender();

expect.assertions(1);
await expect(
wrapper.instance().submitProjectAlmBinding(AlmKeys.GitHub, 'github-binding', {})
).rejects.toBe(undefined);
});

it('should accept submit github settings', async () => {
(setProjectGithubBinding as jest.Mock).mockRestore();
const wrapper = shallowRender();
await wrapper
.instance()
.submitProjectAlmBinding(AlmKeys.GitHub, 'github-binding', { repository: 'foo' });
expect(setProjectGithubBinding).toHaveBeenCalledWith({
almSetting: 'github-binding',
project: PROJECT_KEY,
repository: 'foo',
summaryCommentEnabled: true
});

await wrapper.instance().submitProjectAlmBinding(AlmKeys.GitHub, 'github-binding', {
repository: 'foo',
summaryCommentEnabled: true
});
expect(setProjectGithubBinding).toHaveBeenCalledWith({
almSetting: 'github-binding',
project: PROJECT_KEY,
repository: 'foo',
summaryCommentEnabled: true
});
});

it('should validate form', async () => {

+ 7
- 6
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx Vedi File

@@ -84,12 +84,10 @@ it('should render multiple instances correctly', () => {
key: 'i1',
repository: 'account/repo'
},
isChanged: false,
isConfigured: true,
instances,
loading: false,
originalData: {
key: 'i1',
repository: 'account/repo'
}
loading: false
})
).toMatchSnapshot();
});
@@ -137,6 +135,8 @@ it('should render optional fields correctly', () => {
formData: {
key: 'key'
},
isChanged: true,
isConfigured: false,
instances: [{ key: 'key', url: 'http://example.com', alm: AlmKeys.GitLab }],
loading: false
})
@@ -151,12 +151,13 @@ function shallowRender(props: Partial<PRDecorationBindingRendererProps> = {}) {
repository: ''
}}
instances={[]}
isChanged={false}
isConfigured={false}
isValid={false}
loading={true}
onFieldChange={jest.fn()}
onReset={jest.fn()}
onSubmit={jest.fn()}
originalData={undefined}
saving={false}
success={false}
{...props}

+ 2
- 0
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBinding-test.tsx.snap Vedi File

@@ -8,6 +8,8 @@ exports[`should render correctly 1`] = `
}
}
instances={Array []}
isChanged={false}
isConfigured={false}
isValid={false}
loading={true}
onFieldChange={[Function]}

+ 27
- 0
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap Vedi File

@@ -458,6 +458,33 @@ exports[`should render multiple instances correctly 2`] = `
value="account/repo"
/>
</div>
<div
className="form-field"
>
<label
className="display-flex-center"
htmlFor="github.summary_comment_setting"
>
settings.pr_decoration.binding.form.github.summary_comment_setting
<HelpTooltip
className="spacer-left"
overlay={
<FormattedMessage
defaultMessage="settings.pr_decoration.binding.form.github.summary_comment_setting.help"
id="settings.pr_decoration.binding.form.github.summary_comment_setting.help"
values={Object {}}
/>
}
placement="right"
/>
</label>
<InputForBoolean
isDefault={true}
name="github.summary_comment_setting"
onChange={[Function]}
value={true}
/>
</div>
<div
className="display-flex-center"
>

+ 2
- 0
server/sonar-web/src/main/js/types/alm-settings.ts Vedi File

@@ -52,6 +52,7 @@ export interface ProjectAlmBinding {
key: string;
repository?: string;
slug?: string;
summaryCommentEnabled?: boolean;
}

export interface AzureProjectAlmBinding {
@@ -70,6 +71,7 @@ export interface GithubProjectAlmBinding {
almSetting: string;
project: string;
repository: string;
summaryCommentEnabled: boolean;
}

export interface GitlabProjectAlmBinding {

+ 2
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Vedi File

@@ -1076,6 +1076,8 @@ settings.pr_decoration.binding.form.url=Project location
settings.pr_decoration.binding.form.name=Configuration name
settings.pr_decoration.binding.form.github.repository=Repository identifier
settings.pr_decoration.binding.form.github.repository.help=The path of your repository URL. Example: {example}
settings.pr_decoration.binding.form.github.summary_comment_setting=Enable analysis summary under the GitHub Conversation tab
settings.pr_decoration.binding.form.github.summary_comment_setting.help=When enabled, Pull Request analysis summary is displayed under the GitHub Conversation tab. Notifications may be sent by GitHub depending on your settings.
settings.pr_decoration.binding.form.bitbucket.repository=Project Key
settings.pr_decoration.binding.form.bitbucket.repository.help=The project key is part of your Bitbucket Server repository URL. Example: ({example})
settings.pr_decoration.binding.form.bitbucket.slug=Repository SLUG

Loading…
Annulla
Salva