From 748fe66f0369b7ae8d753f3c7f57afd37bf25eb3 Mon Sep 17 00:00:00 2001 From: Mathieu Suen Date: Fri, 28 Feb 2020 11:33:45 +0100 Subject: [PATCH] SONAR-13127: Adding front config for summary analysis comment for GH. --- .../PRDecorationBinding.tsx | 69 ++++++--- .../PRDecorationBindingRenderer.tsx | 138 +++++++++++------- .../__tests__/PRDecorationBinding-test.tsx | 91 +++++++++--- .../PRDecorationBindingRenderer-test.tsx | 13 +- .../PRDecorationBinding-test.tsx.snap | 2 + .../PRDecorationBindingRenderer-test.tsx.snap | 27 ++++ .../src/main/js/types/alm-settings.ts | 2 + .../resources/org/sonar/l10n/core.properties | 2 + 8 files changed, 250 insertions(+), 94 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx index f43c1f2201b..a0fe97c4c29 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBinding.tsx @@ -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 { - 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 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; + 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 ( + + ); +} + +function renderBooleanField( + props: Omit & { + value: boolean; + } +) { + const { id, value, onFieldChange, propKey } = props; return (
- + {renderLabel({ ...props, optional: true })} + onFieldChange(propKey, v)} + value={value} + /> +
+ ); +} + +function renderField( + props: CommonFieldProps & { + value: string; + } +) { + const { id, propKey, value, onFieldChange } = props; + return ( +
+ {renderLabel(props)} i.key === key); const alm = selected && selected.alm; - const isChanged = !isDataSame({ key, repository, slug }, originalData || { key: '' }); - return (
@@ -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 {translate('save')} )} - {originalData && ( + {isConfigured && ( diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx index 0049df40d7b..ed59ce74aff 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBinding-test.tsx @@ -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 () => { diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx index 019f6e42103..a9940e730e2 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/PRDecorationBindingRenderer-test.tsx @@ -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 = {}) { 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} diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBinding-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBinding-test.tsx.snap index 499ffbfa605..093bf3a21ae 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBinding-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBinding-test.tsx.snap @@ -8,6 +8,8 @@ exports[`should render correctly 1`] = ` } } instances={Array []} + isChanged={false} + isConfigured={false} isValid={false} loading={true} onFieldChange={[Function]} diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap index 66d782641f1..9b9f4ddfb49 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap @@ -458,6 +458,33 @@ exports[`should render multiple instances correctly 2`] = ` value="account/repo" />
+
+ + +
diff --git a/server/sonar-web/src/main/js/types/alm-settings.ts b/server/sonar-web/src/main/js/types/alm-settings.ts index 6f138d94e4d..a25148c6fb2 100644 --- a/server/sonar-web/src/main/js/types/alm-settings.ts +++ b/server/sonar-web/src/main/js/types/alm-settings.ts @@ -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 { diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index aa865428be5..0ed6ae378b6 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -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 -- 2.39.5