From c3848a7939baa77a7be0eb2dc8413f0e65211926 Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Wed, 18 Nov 2020 09:29:46 +0100 Subject: [PATCH] SONAR-14133 Add identifying fields to azure project binding --- .../AlmSpecificForm.tsx | 202 +++++++++++++ .../PRDecorationBinding.tsx | 17 +- .../PRDecorationBindingRenderer.tsx | 165 +---------- .../__tests__/AlmSpecificForm-test.tsx | 44 +++ .../__tests__/PRDecorationBinding-test.tsx | 44 ++- .../PRDecorationBindingRenderer-test.tsx | 14 - .../AlmSpecificForm-test.tsx.snap | 269 ++++++++++++++++++ .../PRDecorationBindingRenderer-test.tsx.snap | 96 +------ .../src/main/js/types/alm-settings.ts | 5 +- .../resources/org/sonar/l10n/core.properties | 4 + 10 files changed, 594 insertions(+), 266 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx create mode 100644 server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/AlmSpecificForm-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx new file mode 100644 index 00000000000..6483db5cec3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx @@ -0,0 +1,202 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { FormattedMessage } from 'react-intl'; +import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { AlmKeys, ProjectAlmBindingResponse } from '../../../../types/alm-settings'; +import InputForBoolean from '../inputs/InputForBoolean'; + +export interface AlmSpecificFormProps { + alm: AlmKeys; + formData: T.Omit; + onFieldChange: (id: keyof ProjectAlmBindingResponse, value: string | boolean) => void; +} + +interface LabelProps { + help?: boolean; + helpParams?: T.Dict; + id: string; + optional?: boolean; +} + +interface CommonFieldProps extends LabelProps { + onFieldChange: (id: keyof ProjectAlmBindingResponse, value: string | boolean) => void; + propKey: keyof ProjectAlmBindingResponse; +} + +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)} + onFieldChange(propKey, e.currentTarget.value)} + type="text" + value={value} + /> +
+ ); +} + +export default function AlmSpecificForm(props: AlmSpecificFormProps) { + const { + alm, + formData: { repository, slug, summaryCommentEnabled } + } = props; + + switch (alm) { + case AlmKeys.Azure: + return ( + <> + {renderField({ + help: true, + id: 'azure.project', + onFieldChange: props.onFieldChange, + propKey: 'slug', + value: slug || '' + })} + {renderField({ + help: true, + id: 'azure.repository', + onFieldChange: props.onFieldChange, + propKey: 'repository', + value: repository || '' + })} + + ); + case AlmKeys.Bitbucket: + return ( + <> + {renderField({ + help: true, + helpParams: { + example: ( + <> + {'.../projects/'} + {'{KEY}'} + {'/repos/{SLUG}/browse'} + + ) + }, + id: 'bitbucket.repository', + onFieldChange: props.onFieldChange, + propKey: 'repository', + value: repository || '' + })} + {renderField({ + help: true, + helpParams: { + example: ( + <> + {'.../projects/{KEY}/repos/'} + {'{SLUG}'} + {'/browse'} + + ) + }, + id: 'bitbucket.slug', + onFieldChange: props.onFieldChange, + propKey: 'slug', + value: slug || '' + })} + + ); + case AlmKeys.GitHub: + return ( + <> + {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 + })} + + ); + case AlmKeys.GitLab: + return renderField({ + id: 'gitlab.repository', + onFieldChange: props.onFieldChange, + propKey: 'repository', + value: repository || '' + }); + default: + return null; + } +} 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 0b4dc3e2a06..18336d366de 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 @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { HttpStatus } from 'sonar-ui-common/helpers/request'; import { deleteProjectAlmBinding, getAlmSettings, @@ -56,7 +57,7 @@ interface State { const REQUIRED_FIELDS_BY_ALM: { [almKey in AlmKeys]: Array>; } = { - [AlmKeys.Azure]: [], + [AlmKeys.Azure]: ['repository', 'slug'], [AlmKeys.Bitbucket]: ['repository', 'slug'], [AlmKeys.GitHub]: ['repository'], [AlmKeys.GitLab]: ['repository'] @@ -112,7 +113,7 @@ export default class PRDecorationBinding extends React.PureComponent { return getProjectAlmBinding(project).catch((response: Response) => { - if (response && response.status === 404) { + if (response && response.status === HttpStatus.NotFound) { return undefined; } return throwGlobalError(response); @@ -156,11 +157,19 @@ export default class PRDecorationBinding extends React.PureComponent; @@ -48,18 +43,6 @@ export interface PRDecorationBindingRendererProps { success: boolean; } -interface LabelProps { - help?: boolean; - helpParams?: T.Dict; - id: string; - optional?: boolean; -} - -interface CommonFieldProps extends LabelProps { - onFieldChange: (id: keyof ProjectAlmBindingResponse, value: string | boolean) => void; - propKey: keyof ProjectAlmBindingResponse; -} - function optionRenderer(instance: AlmSettingsInstance) { return instance.url ? ( <> @@ -71,81 +54,8 @@ function optionRenderer(instance: AlmSettingsInstance) { ); } -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)} - onFieldChange(propKey, e.currentTarget.value)} - type="text" - value={value} - /> -
- ); -} - export default function PRDecorationBindingRenderer(props: PRDecorationBindingRendererProps) { - const { - formData: { key, repository, slug, summaryCommentEnabled }, - instances, - isChanged, - isConfigured, - isValid, - loading, - saving, - success - } = props; + const { formData, instances, isChanged, isConfigured, isValid, loading, saving, success } = props; if (loading) { return ; @@ -171,7 +81,7 @@ export default function PRDecorationBindingRenderer(props: PRDecorationBindingRe ); } - const selected = key && instances.find(i => i.key === key); + const selected = formData.key && instances.find(i => i.key === formData.key); const alm = selected && selected.alm; return ( @@ -207,77 +117,16 @@ export default function PRDecorationBindingRenderer(props: PRDecorationBindingRe optionRenderer={optionRenderer} options={instances} searchable={false} - value={key} + value={formData.key} valueKey="key" valueRenderer={optionRenderer} /> - {alm === AlmKeys.Bitbucket && ( - <> - {renderField({ - help: true, - helpParams: { - example: ( - <> - {'.../projects/'} - {'{KEY}'} - {'/repos/{SLUG}/browse'} - - ) - }, - id: 'bitbucket.repository', - onFieldChange: props.onFieldChange, - propKey: 'repository', - value: repository || '' - })} - {renderField({ - help: true, - helpParams: { - example: ( - <> - {'.../projects/{KEY}/repos/'} - {'{SLUG}'} - {'/browse'} - - ) - }, - id: 'bitbucket.slug', - onFieldChange: props.onFieldChange, - propKey: 'slug', - value: slug || '' - })} - + {alm && ( + )} - {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({ - id: 'gitlab.repository', - onFieldChange: props.onFieldChange, - propKey: 'repository', - value: repository || '' - })} -
{isChanged && ( diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/AlmSpecificForm-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/AlmSpecificForm-test.tsx new file mode 100644 index 00000000000..57a241633d4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/AlmSpecificForm-test.tsx @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { shallow } from 'enzyme'; +import * as React from 'react'; +import { AlmKeys } from '../../../../../types/alm-settings'; +import AlmSpecificForm, { AlmSpecificFormProps } from '../AlmSpecificForm'; + +it.each([[AlmKeys.Azure], [AlmKeys.Bitbucket], [AlmKeys.GitHub], [AlmKeys.GitLab]])( + 'it should render correctly for %s', + alm => { + expect(shallowRender(alm)).toMatchSnapshot(); + } +); + +function shallowRender(alm: AlmKeys, props: Partial = {}) { + return shallow( + + ); +} 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 bbdbf6f7da0..2599949af78 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 @@ -120,13 +120,20 @@ describe('handleSubmit', () => { const wrapper = shallowRender(); await waitAndUpdate(wrapper); const azureKey = 'azure'; - wrapper.setState({ formData: { key: azureKey }, instances }); + const repository = 'az-rep'; + const slug = 'az-project'; + wrapper.setState({ + formData: { key: azureKey, repository, slug }, + instances + }); wrapper.instance().handleSubmit(); await waitAndUpdate(wrapper); expect(setProjectAzureBinding).toBeCalledWith({ almSetting: azureKey, - project: PROJECT_KEY + project: PROJECT_KEY, + projectName: slug, + repositoryName: repository }); expect(wrapper.state().success).toBe(true); }); @@ -212,6 +219,33 @@ it('should handle field changes', async () => { }); }); +it('should reject submitted azure settings', async () => { + const wrapper = shallowRender(); + + expect.assertions(2); + await expect( + wrapper.instance().submitProjectAlmBinding(AlmKeys.Azure, 'azure-binding', { slug: 'project' }) + ).rejects.toBeUndefined(); + await expect( + wrapper + .instance() + .submitProjectAlmBinding(AlmKeys.Azure, 'azure-binding', { repository: 'repo' }) + ).rejects.toBeUndefined(); +}); + +it('should accept submit azure settings', async () => { + const wrapper = shallowRender(); + await wrapper + .instance() + .submitProjectAlmBinding(AlmKeys.Azure, 'azure', { repository: 'az-repo', slug: 'az-project' }); + expect(setProjectAzureBinding).toHaveBeenCalledWith({ + almSetting: 'azure', + project: PROJECT_KEY, + repositoryName: 'az-repo', + projectName: 'az-project' + }); +}); + it('should reject submit github settings', async () => { const wrapper = shallowRender(); @@ -262,7 +296,11 @@ it('should validate form', async () => { ] }); - expect(wrapper.instance().validateForm({ key: 'azure' })).toBe(true); + expect(wrapper.instance().validateForm({ key: 'azure', repository: 'rep' })).toBe(false); + expect(wrapper.instance().validateForm({ key: 'azure', slug: 'project' })).toBe(false); + expect( + wrapper.instance().validateForm({ key: 'azure', repository: 'repo', slug: 'project' }) + ).toBe(true); expect(wrapper.instance().validateForm({ key: 'github', repository: '' })).toBe(false); expect(wrapper.instance().validateForm({ key: 'github', repository: 'asdf' })).toBe(true); 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 735fe355f7c..3144d479b51 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 @@ -129,20 +129,6 @@ it('should render select options correctly', async () => { expect(optionRenderer!(instances[1])).toMatchSnapshot(); }); -it('should render optional fields correctly', () => { - expect( - shallowRender({ - formData: { - key: 'key' - }, - isChanged: true, - isConfigured: false, - instances: [{ key: 'key', url: 'http://example.com', alm: AlmKeys.GitHub }], - loading: false - }).find('label[htmlFor="github.summary_comment_setting"]') - ).toMatchSnapshot(); -}); - function shallowRender(props: Partial = {}) { return shallow( +
+ + +
+
+ + +
+ +`; + +exports[`it should render correctly for bitbucket 1`] = ` + +
+ + +
+
+ + +
+
+`; + +exports[`it should render correctly for github 1`] = ` + +
+ + +
+
+ + +
+
+`; + +exports[`it should render correctly for gitlab 1`] = ` +
+ + +
+`; 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 50eceef7346..8f87ef8a1cb 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 @@ -411,72 +411,16 @@ exports[`should render multiple instances correctly 2`] = ` valueRenderer={[Function]} />
-
- - -
-
- - -
+
@@ -499,26 +443,6 @@ exports[`should render multiple instances correctly 2`] = `
`; -exports[`should render optional fields correctly 1`] = ` - -`; - exports[`should render select options correctly 1`] = ` azure 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 8ae38c80b91..c27018afb25 100644 --- a/server/sonar-web/src/main/js/types/alm-settings.ts +++ b/server/sonar-web/src/main/js/types/alm-settings.ts @@ -82,7 +82,10 @@ export interface ProjectAlmBindingParams { project: string; } -export interface AzureProjectAlmBindingParams extends ProjectAlmBindingParams {} +export interface AzureProjectAlmBindingParams extends ProjectAlmBindingParams { + projectName: string; + repositoryName: string; +} export interface BitbucketProjectAlmBindingParams extends ProjectAlmBindingParams { repository: string; 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 2bf94b588ac..a0717a0f97e 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1123,6 +1123,10 @@ settings.pr_decoration.binding.title=Pull Request Decoration settings.pr_decoration.binding.description=Enable Pull Request Decoration for this project. settings.pr_decoration.binding.form.url=Project location settings.pr_decoration.binding.form.name=Configuration name +settings.pr_decoration.binding.form.azure.project=Project Name +settings.pr_decoration.binding.form.azure.project.help=The name of the Azure DevOps Server project containing your repository. +settings.pr_decoration.binding.form.azure.repository=Repository Name +settings.pr_decoration.binding.form.azure.repository.help=The name of your Azure DevOps Server repository. 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 -- 2.39.5