diff options
author | Wouter Admiraal <wouter.admiraal@sonarsource.com> | 2021-05-28 10:00:03 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2021-06-10 20:03:26 +0000 |
commit | 06681cd89f2a24144ac5706ca97a07374f1b6c72 (patch) | |
tree | e12385fd74f5b33d2730394c52abca0e1bebd5bc /server | |
parent | a9ff34a88b68a2f1df68bc1fe54ebf5b17f4e70a (diff) | |
download | sonarqube-06681cd89f2a24144ac5706ca97a07374f1b6c72.tar.gz sonarqube-06681cd89f2a24144ac5706ca97a07374f1b6c72.zip |
SONAR-14873 Ease comprehension of PR Decoration settings form
Diffstat (limited to 'server')
8 files changed, 1293 insertions, 663 deletions
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 index d476039cc8b..4f4672e6bf1 100644 --- 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 @@ -20,52 +20,80 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { Link } from 'react-router'; -import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; import { Alert } from 'sonar-ui-common/components/ui/Alert'; import MandatoryFieldMarker from 'sonar-ui-common/components/ui/MandatoryFieldMarker'; import { translate } from 'sonar-ui-common/helpers/l10n'; import { ALM_DOCUMENTATION_PATHS } from '../../../../helpers/constants'; -import { AlmKeys, ProjectAlmBindingResponse } from '../../../../types/alm-settings'; +import { convertGithubApiUrlToLink, stripTrailingSlash } from '../../../../helpers/urls'; +import { + AlmKeys, + AlmSettingsInstance, + ProjectAlmBindingResponse +} from '../../../../types/alm-settings'; import InputForBoolean from '../inputs/InputForBoolean'; export interface AlmSpecificFormProps { alm: AlmKeys; + instances: AlmSettingsInstance[]; formData: T.Omit<ProjectAlmBindingResponse, 'alm'>; onFieldChange: (id: keyof ProjectAlmBindingResponse, value: string | boolean) => void; monorepoEnabled: boolean; } interface LabelProps { - help?: boolean; - helpParams?: T.Dict<string | JSX.Element>; id: string; optional?: boolean; } interface CommonFieldProps extends LabelProps { + help?: boolean; + helpParams?: T.Dict<string | JSX.Element>; + helpExample?: JSX.Element; onFieldChange: (id: keyof ProjectAlmBindingResponse, value: string | boolean) => void; propKey: keyof ProjectAlmBindingResponse; } +function renderFieldWrapper( + label: React.ReactNode, + input: React.ReactNode, + help?: React.ReactNode +) { + return ( + <div className="settings-definition"> + <div className="settings-definition-left"> + {label} + {help && <div className="markdown small spacer-top">{help}</div>} + </div> + <div className="settings-definition-right padded-top">{input}</div> + </div> + ); +} + +function renderHelp({ help, helpExample, helpParams, id }: CommonFieldProps) { + return ( + help && ( + <> + <FormattedMessage + defaultMessage={translate('settings.pr_decoration.binding.form', id, 'help')} + id={`settings.pr_decoration.binding.form.${id}.help`} + values={helpParams} + /> + {helpExample && ( + <div className="spacer-top nowrap"> + {translate('example')}: <em>{helpExample}</em> + </div> + )} + </> + ) + ); +} + function renderLabel(props: LabelProps) { - const { help, helpParams, optional, id } = props; + const { optional, id } = props; return ( - <label className="display-flex-center" htmlFor={id}> + <label className="h3" htmlFor={id}> {translate('settings.pr_decoration.binding.form', id)} {!optional && <MandatoryFieldMarker />} - {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> ); } @@ -77,19 +105,18 @@ function renderBooleanField( } ) { const { id, value, onFieldChange, propKey, inputExtra } = props; - return ( - <div className="form-field"> - {renderLabel({ ...props, optional: true })} - <div className="display-flex-center"> - <InputForBoolean - isDefault={true} - name={id} - onChange={v => onFieldChange(propKey, v)} - value={value} - /> - {inputExtra} - </div> - </div> + return renderFieldWrapper( + renderLabel({ ...props, optional: true }), + <div className="display-flex-center big-spacer-top"> + <InputForBoolean + isDefault={true} + name={id} + onChange={v => onFieldChange(propKey, v)} + value={value} + /> + {inputExtra} + </div>, + renderHelp(props) ); } @@ -99,30 +126,31 @@ function renderField( } ) { const { id, propKey, value, onFieldChange } = props; - return ( - <div className="form-field"> - {renderLabel(props)} - <input - className="input-super-large" - id={id} - maxLength={256} - name={id} - onChange={e => onFieldChange(propKey, e.currentTarget.value)} - type="text" - value={value} - /> - </div> + return renderFieldWrapper( + renderLabel(props), + <input + className="input-super-large big-spacer-top" + id={id} + maxLength={256} + name={id} + onChange={e => onFieldChange(propKey, e.currentTarget.value)} + type="text" + value={value} + />, + renderHelp(props) ); } export default function AlmSpecificForm(props: AlmSpecificFormProps) { const { alm, + instances, formData: { repository, slug, summaryCommentEnabled, monorepo }, monorepoEnabled } = props; let formFields: JSX.Element; + const instance = instances.find(i => i.alm === alm); switch (alm) { case AlmKeys.Azure: @@ -130,6 +158,7 @@ export default function AlmSpecificForm(props: AlmSpecificFormProps) { <> {renderField({ help: true, + helpExample: <strong>My Project</strong>, id: 'azure.project', onFieldChange: props.onFieldChange, propKey: 'slug', @@ -137,6 +166,7 @@ export default function AlmSpecificForm(props: AlmSpecificFormProps) { })} {renderField({ help: true, + helpExample: <strong>My Repository</strong>, id: 'azure.repository', onFieldChange: props.onFieldChange, propKey: 'repository', @@ -150,15 +180,15 @@ export default function AlmSpecificForm(props: AlmSpecificFormProps) { <> {renderField({ help: true, - helpParams: { - example: ( - <> - {'.../projects/'} - <strong>{'{KEY}'}</strong> - {'/repos/{SLUG}/browse'} - </> - ) - }, + helpExample: ( + <> + {instance?.url + ? `${stripTrailingSlash(instance.url)}/projects/` + : 'https://bb.company.com/projects/'} + <strong>{'MY_PROJECT_KEY'}</strong> + {'/repos/my-repository-slug/browse'} + </> + ), id: 'bitbucket.repository', onFieldChange: props.onFieldChange, propKey: 'repository', @@ -166,15 +196,15 @@ export default function AlmSpecificForm(props: AlmSpecificFormProps) { })} {renderField({ help: true, - helpParams: { - example: ( - <> - {'.../projects/{KEY}/repos/'} - <strong>{'{SLUG}'}</strong> - {'/browse'} - </> - ) - }, + helpExample: ( + <> + {instance?.url + ? `${stripTrailingSlash(instance.url)}/projects/MY_PROJECT_KEY/repos/` + : 'https://bb.company.com/projects/MY_PROJECT_KEY/repos/'} + <strong>{'my-repository-slug'}</strong> + {'/browse'} + </> + ), id: 'bitbucket.slug', onFieldChange: props.onFieldChange, propKey: 'slug', @@ -188,14 +218,12 @@ export default function AlmSpecificForm(props: AlmSpecificFormProps) { <> {renderField({ help: true, - helpParams: { - example: ( - <> - {'https://bitbucket.org/{workspace}/'} - <strong>{'{repository}'}</strong> - </> - ) - }, + helpExample: ( + <> + {'https://bitbucket.org/my-workspace/'} + <strong>{'my-repository-slug'}</strong> + </> + ), id: 'bitbucketcloud.repository', onFieldChange: props.onFieldChange, propKey: 'repository', @@ -209,7 +237,14 @@ export default function AlmSpecificForm(props: AlmSpecificFormProps) { <> {renderField({ help: true, - helpParams: { example: 'SonarSource/sonarqube' }, + helpExample: ( + <> + {instance?.url + ? `${stripTrailingSlash(convertGithubApiUrlToLink(instance.url))}/` + : 'https://github.com/'} + <strong>{'sonarsource/sonarqube'}</strong> + </> + ), id: 'github.repository', onFieldChange: props.onFieldChange, propKey: 'repository', @@ -229,6 +264,8 @@ export default function AlmSpecificForm(props: AlmSpecificFormProps) { formFields = ( <> {renderField({ + help: true, + helpExample: <strong>123456</strong>, id: 'gitlab.repository', onFieldChange: props.onFieldChange, propKey: 'repository', diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx index d2d2c62bf56..7fc2133fa00 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx @@ -114,40 +114,48 @@ export default function PRDecorationBindingRenderer(props: PRDecorationBindingRe }}> <MandatoryFieldsExplanation className="form-field" /> - <div className="form-field"> - <label htmlFor="name"> - {translate('settings.pr_decoration.binding.form.name')} - <MandatoryFieldMarker className="spacer-right" /> - </label> - <Select - autosize={true} - className="abs-width-400" - clearable={false} - id="name" - menuContainerStyle={{ - maxWidth: '210%' /* Allow double the width of the select */, - width: 'auto' - }} - onChange={(instance: AlmSettingsInstance) => props.onFieldChange('key', instance.key)} - optionRenderer={optionRenderer} - options={instances} - searchable={false} - value={formData.key} - valueKey="key" - valueRenderer={optionRenderer} - /> + <div className="settings-definition big-spacer-bottom"> + <div className="settings-definition-left"> + <label className="h3" htmlFor="name"> + {translate('settings.pr_decoration.binding.form.name')} + <MandatoryFieldMarker className="spacer-right" /> + </label> + <div className="markdown small spacer-top"> + {translate('settings.pr_decoration.binding.form.name.help')} + </div> + </div> + <div className="settings-definition-right"> + <Select + autosize={true} + className="abs-width-400 big-spacer-top" + clearable={false} + id="name" + menuContainerStyle={{ + maxWidth: '210%' /* Allow double the width of the select */, + width: 'auto' + }} + onChange={(instance: AlmSettingsInstance) => props.onFieldChange('key', instance.key)} + optionRenderer={optionRenderer} + options={instances} + searchable={false} + value={formData.key} + valueKey="key" + valueRenderer={optionRenderer} + /> + </div> </div> {alm && ( <AlmSpecificForm alm={alm} + instances={instances} formData={formData} onFieldChange={props.onFieldChange} monorepoEnabled={monorepoEnabled} /> )} - <div className="display-flex-center"> + <div className="display-flex-center big-spacer-top"> <DeferredSpinner className="spacer-right" loading={saving} /> {isChanged && ( <SubmitButton className="spacer-right button-success" disabled={saving || !isValid}> 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 index 5f118c3f225..fb76261471e 100644 --- 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 @@ -19,7 +19,8 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; -import { AlmKeys } from '../../../../../types/alm-settings'; +import { mockAlmSettingsInstance } from '../../../../../helpers/mocks/alm-settings'; +import { AlmKeys, AlmSettingsInstance } from '../../../../../types/alm-settings'; import AlmSpecificForm, { AlmSpecificFormProps } from '../AlmSpecificForm'; it.each([ @@ -32,6 +33,20 @@ it.each([ expect(shallowRender(alm)).toMatchSnapshot(); }); +it.each([ + [ + AlmKeys.BitbucketServer, + [mockAlmSettingsInstance({ alm: AlmKeys.BitbucketServer, url: 'http://bbs.example.com' })] + ], + [AlmKeys.GitHub, [mockAlmSettingsInstance({ url: 'http://example.com/api/v3' })]], + [AlmKeys.GitHub, [mockAlmSettingsInstance({ url: 'http://api.github.com' })]] +])( + 'it should render correctly for %s if an instance URL is provided', + (alm: AlmKeys, instances: AlmSettingsInstance[]) => { + expect(shallowRender(alm, { instances })).toMatchSnapshot(); + } +); + it('should render the monorepo field when the feature is supported', () => { expect(shallowRender(AlmKeys.Azure, { monorepoEnabled: true })).toMatchSnapshot(); }); @@ -40,6 +55,7 @@ function shallowRender(alm: AlmKeys, props: Partial<AlmSpecificFormProps> = {}) return shallow( <AlmSpecificForm alm={alm} + instances={[]} formData={{ key: '', repository: '', diff --git a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap index 1ce4d32cfe7..1cb1269f6dd 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap @@ -3,66 +3,100 @@ exports[`it should render correctly for azure 1`] = ` <Fragment> <div - className="form-field" + className="settings-definition" > - <label - className="display-flex-center" - htmlFor="azure.project" - > - settings.pr_decoration.binding.form.azure.project - <MandatoryFieldMarker /> - <HelpTooltip - className="spacer-left" - overlay={ - <FormattedMessage - defaultMessage="settings.pr_decoration.binding.form.azure.project.help" - id="settings.pr_decoration.binding.form.azure.project.help" - values={Object {}} - /> - } - placement="right" + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="azure.project" + > + settings.pr_decoration.binding.form.azure.project + <MandatoryFieldMarker /> + </label> + <div + className="markdown small spacer-top" + > + <FormattedMessage + defaultMessage="settings.pr_decoration.binding.form.azure.project.help" + id="settings.pr_decoration.binding.form.azure.project.help" + values={Object {}} + /> + <div + className="spacer-top nowrap" + > + example + : + <em> + <strong> + My Project + </strong> + </em> + </div> + </div> + </div> + <div + className="settings-definition-right padded-top" + > + <input + className="input-super-large big-spacer-top" + id="azure.project" + maxLength={256} + name="azure.project" + onChange={[Function]} + type="text" + value="" /> - </label> - <input - className="input-super-large" - id="azure.project" - maxLength={256} - name="azure.project" - onChange={[Function]} - type="text" - value="" - /> + </div> </div> <div - className="form-field" + className="settings-definition" > - <label - className="display-flex-center" - htmlFor="azure.repository" - > - settings.pr_decoration.binding.form.azure.repository - <MandatoryFieldMarker /> - <HelpTooltip - className="spacer-left" - overlay={ - <FormattedMessage - defaultMessage="settings.pr_decoration.binding.form.azure.repository.help" - id="settings.pr_decoration.binding.form.azure.repository.help" - values={Object {}} - /> - } - placement="right" + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="azure.repository" + > + settings.pr_decoration.binding.form.azure.repository + <MandatoryFieldMarker /> + </label> + <div + className="markdown small spacer-top" + > + <FormattedMessage + defaultMessage="settings.pr_decoration.binding.form.azure.repository.help" + id="settings.pr_decoration.binding.form.azure.repository.help" + values={Object {}} + /> + <div + className="spacer-top nowrap" + > + example + : + <em> + <strong> + My Repository + </strong> + </em> + </div> + </div> + </div> + <div + className="settings-definition-right padded-top" + > + <input + className="input-super-large big-spacer-top" + id="azure.repository" + maxLength={256} + name="azure.repository" + onChange={[Function]} + type="text" + value="" /> - </label> - <input - className="input-super-large" - id="azure.repository" - maxLength={256} - name="azure.repository" - onChange={[Function]} - type="text" - value="" - /> + </div> </div> </Fragment> `; @@ -70,86 +104,209 @@ exports[`it should render correctly for azure 1`] = ` exports[`it should render correctly for bitbucket 1`] = ` <Fragment> <div - className="form-field" + className="settings-definition" > - <label - className="display-flex-center" - htmlFor="bitbucket.repository" - > - settings.pr_decoration.binding.form.bitbucket.repository - <MandatoryFieldMarker /> - <HelpTooltip - className="spacer-left" - overlay={ - <FormattedMessage - defaultMessage="settings.pr_decoration.binding.form.bitbucket.repository.help" - id="settings.pr_decoration.binding.form.bitbucket.repository.help" - values={ - Object { - "example": <React.Fragment> - .../projects/ - <strong> - {KEY} - </strong> - /repos/{SLUG}/browse - </React.Fragment>, - } - } - /> - } - placement="right" + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="bitbucket.repository" + > + settings.pr_decoration.binding.form.bitbucket.repository + <MandatoryFieldMarker /> + </label> + <div + className="markdown small spacer-top" + > + <FormattedMessage + defaultMessage="settings.pr_decoration.binding.form.bitbucket.repository.help" + id="settings.pr_decoration.binding.form.bitbucket.repository.help" + values={Object {}} + /> + <div + className="spacer-top nowrap" + > + example + : + <em> + https://bb.company.com/projects/ + <strong> + MY_PROJECT_KEY + </strong> + /repos/my-repository-slug/browse + </em> + </div> + </div> + </div> + <div + className="settings-definition-right padded-top" + > + <input + className="input-super-large big-spacer-top" + id="bitbucket.repository" + maxLength={256} + name="bitbucket.repository" + onChange={[Function]} + type="text" + value="" /> - </label> - <input - className="input-super-large" - id="bitbucket.repository" - maxLength={256} - name="bitbucket.repository" - onChange={[Function]} - type="text" - value="" - /> + </div> </div> <div - className="form-field" + className="settings-definition" > - <label - className="display-flex-center" - htmlFor="bitbucket.slug" - > - settings.pr_decoration.binding.form.bitbucket.slug - <MandatoryFieldMarker /> - <HelpTooltip - className="spacer-left" - overlay={ - <FormattedMessage - defaultMessage="settings.pr_decoration.binding.form.bitbucket.slug.help" - id="settings.pr_decoration.binding.form.bitbucket.slug.help" - values={ - Object { - "example": <React.Fragment> - .../projects/{KEY}/repos/ - <strong> - {SLUG} - </strong> - /browse - </React.Fragment>, - } - } - /> - } - placement="right" + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="bitbucket.slug" + > + settings.pr_decoration.binding.form.bitbucket.slug + <MandatoryFieldMarker /> + </label> + <div + className="markdown small spacer-top" + > + <FormattedMessage + defaultMessage="settings.pr_decoration.binding.form.bitbucket.slug.help" + id="settings.pr_decoration.binding.form.bitbucket.slug.help" + values={Object {}} + /> + <div + className="spacer-top nowrap" + > + example + : + <em> + https://bb.company.com/projects/MY_PROJECT_KEY/repos/ + <strong> + my-repository-slug + </strong> + /browse + </em> + </div> + </div> + </div> + <div + className="settings-definition-right padded-top" + > + <input + className="input-super-large big-spacer-top" + id="bitbucket.slug" + maxLength={256} + name="bitbucket.slug" + onChange={[Function]} + type="text" + value="" + /> + </div> + </div> +</Fragment> +`; + +exports[`it should render correctly for bitbucket if an instance URL is provided 1`] = ` +<Fragment> + <div + className="settings-definition" + > + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="bitbucket.repository" + > + settings.pr_decoration.binding.form.bitbucket.repository + <MandatoryFieldMarker /> + </label> + <div + className="markdown small spacer-top" + > + <FormattedMessage + defaultMessage="settings.pr_decoration.binding.form.bitbucket.repository.help" + id="settings.pr_decoration.binding.form.bitbucket.repository.help" + values={Object {}} + /> + <div + className="spacer-top nowrap" + > + example + : + <em> + http://bbs.example.com/projects/ + <strong> + MY_PROJECT_KEY + </strong> + /repos/my-repository-slug/browse + </em> + </div> + </div> + </div> + <div + className="settings-definition-right padded-top" + > + <input + className="input-super-large big-spacer-top" + id="bitbucket.repository" + maxLength={256} + name="bitbucket.repository" + onChange={[Function]} + type="text" + value="" /> - </label> - <input - className="input-super-large" - id="bitbucket.slug" - maxLength={256} - name="bitbucket.slug" - onChange={[Function]} - type="text" - value="" - /> + </div> + </div> + <div + className="settings-definition" + > + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="bitbucket.slug" + > + settings.pr_decoration.binding.form.bitbucket.slug + <MandatoryFieldMarker /> + </label> + <div + className="markdown small spacer-top" + > + <FormattedMessage + defaultMessage="settings.pr_decoration.binding.form.bitbucket.slug.help" + id="settings.pr_decoration.binding.form.bitbucket.slug.help" + values={Object {}} + /> + <div + className="spacer-top nowrap" + > + example + : + <em> + http://bbs.example.com/projects/MY_PROJECT_KEY/repos/ + <strong> + my-repository-slug + </strong> + /browse + </em> + </div> + </div> + </div> + <div + className="settings-definition-right padded-top" + > + <input + className="input-super-large big-spacer-top" + id="bitbucket.slug" + maxLength={256} + name="bitbucket.slug" + onChange={[Function]} + type="text" + value="" + /> + </div> </div> </Fragment> `; @@ -157,44 +314,53 @@ exports[`it should render correctly for bitbucket 1`] = ` exports[`it should render correctly for bitbucketcloud 1`] = ` <Fragment> <div - className="form-field" + className="settings-definition" > - <label - className="display-flex-center" - htmlFor="bitbucketcloud.repository" - > - settings.pr_decoration.binding.form.bitbucketcloud.repository - <MandatoryFieldMarker /> - <HelpTooltip - className="spacer-left" - overlay={ - <FormattedMessage - defaultMessage="settings.pr_decoration.binding.form.bitbucketcloud.repository.help" - id="settings.pr_decoration.binding.form.bitbucketcloud.repository.help" - values={ - Object { - "example": <React.Fragment> - https://bitbucket.org/{workspace}/ - <strong> - {repository} - </strong> - </React.Fragment>, - } - } - /> - } - placement="right" + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="bitbucketcloud.repository" + > + settings.pr_decoration.binding.form.bitbucketcloud.repository + <MandatoryFieldMarker /> + </label> + <div + className="markdown small spacer-top" + > + <FormattedMessage + defaultMessage="settings.pr_decoration.binding.form.bitbucketcloud.repository.help" + id="settings.pr_decoration.binding.form.bitbucketcloud.repository.help" + values={Object {}} + /> + <div + className="spacer-top nowrap" + > + example + : + <em> + https://bitbucket.org/my-workspace/ + <strong> + my-repository-slug + </strong> + </em> + </div> + </div> + </div> + <div + className="settings-definition-right padded-top" + > + <input + className="input-super-large big-spacer-top" + id="bitbucketcloud.repository" + maxLength={256} + name="bitbucketcloud.repository" + onChange={[Function]} + type="text" + value="" /> - </label> - <input - className="input-super-large" - id="bitbucketcloud.repository" - maxLength={256} - name="bitbucketcloud.repository" - onChange={[Function]} - type="text" - value="" - /> + </div> </div> </Fragment> `; @@ -202,95 +368,325 @@ exports[`it should render correctly for bitbucketcloud 1`] = ` exports[`it should render correctly for github 1`] = ` <Fragment> <div - className="form-field" + className="settings-definition" > - <label - className="display-flex-center" - htmlFor="github.repository" - > - settings.pr_decoration.binding.form.github.repository - <MandatoryFieldMarker /> - <HelpTooltip - className="spacer-left" - overlay={ - <FormattedMessage - defaultMessage="settings.pr_decoration.binding.form.github.repository.help" - id="settings.pr_decoration.binding.form.github.repository.help" - values={ - Object { - "example": "SonarSource/sonarqube", - } - } - /> - } - placement="right" + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="github.repository" + > + settings.pr_decoration.binding.form.github.repository + <MandatoryFieldMarker /> + </label> + <div + className="markdown small spacer-top" + > + <FormattedMessage + defaultMessage="settings.pr_decoration.binding.form.github.repository.help" + id="settings.pr_decoration.binding.form.github.repository.help" + values={Object {}} + /> + <div + className="spacer-top nowrap" + > + example + : + <em> + https://github.com/ + <strong> + sonarsource/sonarqube + </strong> + </em> + </div> + </div> + </div> + <div + className="settings-definition-right padded-top" + > + <input + className="input-super-large big-spacer-top" + id="github.repository" + maxLength={256} + name="github.repository" + onChange={[Function]} + type="text" + value="" /> - </label> - <input - className="input-super-large" - id="github.repository" - maxLength={256} - name="github.repository" - onChange={[Function]} - type="text" - value="" - /> + </div> + </div> + <div + className="settings-definition" + > + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="github.summary_comment_setting" + > + settings.pr_decoration.binding.form.github.summary_comment_setting + </label> + <div + className="markdown small spacer-top" + > + <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 {}} + /> + </div> + </div> + <div + className="settings-definition-right padded-top" + > + <div + className="display-flex-center big-spacer-top" + > + <InputForBoolean + isDefault={true} + name="github.summary_comment_setting" + onChange={[Function]} + value={true} + /> + </div> + </div> </div> +</Fragment> +`; + +exports[`it should render correctly for github if an instance URL is provided 1`] = ` +<Fragment> <div - className="form-field" + className="settings-definition" > - <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" + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="github.repository" + > + settings.pr_decoration.binding.form.github.repository + <MandatoryFieldMarker /> + </label> + <div + className="markdown small spacer-top" + > + <FormattedMessage + defaultMessage="settings.pr_decoration.binding.form.github.repository.help" + id="settings.pr_decoration.binding.form.github.repository.help" + values={Object {}} + /> + <div + className="spacer-top nowrap" + > + example + : + <em> + http://example.com/ + <strong> + sonarsource/sonarqube + </strong> + </em> + </div> + </div> + </div> + <div + className="settings-definition-right padded-top" + > + <input + className="input-super-large big-spacer-top" + id="github.repository" + maxLength={256} + name="github.repository" + onChange={[Function]} + type="text" + value="" /> - </label> + </div> + </div> + <div + className="settings-definition" + > + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="github.summary_comment_setting" + > + settings.pr_decoration.binding.form.github.summary_comment_setting + </label> + <div + className="markdown small spacer-top" + > + <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 {}} + /> + </div> + </div> + <div + className="settings-definition-right padded-top" + > + <div + className="display-flex-center big-spacer-top" + > + <InputForBoolean + isDefault={true} + name="github.summary_comment_setting" + onChange={[Function]} + value={true} + /> + </div> + </div> + </div> +</Fragment> +`; + +exports[`it should render correctly for github if an instance URL is provided 2`] = ` +<Fragment> + <div + className="settings-definition" + > + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="github.repository" + > + settings.pr_decoration.binding.form.github.repository + <MandatoryFieldMarker /> + </label> + <div + className="markdown small spacer-top" + > + <FormattedMessage + defaultMessage="settings.pr_decoration.binding.form.github.repository.help" + id="settings.pr_decoration.binding.form.github.repository.help" + values={Object {}} + /> + <div + className="spacer-top nowrap" + > + example + : + <em> + https://github.com/ + <strong> + sonarsource/sonarqube + </strong> + </em> + </div> + </div> + </div> <div - className="display-flex-center" + className="settings-definition-right padded-top" > - <InputForBoolean - isDefault={true} - name="github.summary_comment_setting" + <input + className="input-super-large big-spacer-top" + id="github.repository" + maxLength={256} + name="github.repository" onChange={[Function]} - value={true} + type="text" + value="" /> </div> </div> + <div + className="settings-definition" + > + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="github.summary_comment_setting" + > + settings.pr_decoration.binding.form.github.summary_comment_setting + </label> + <div + className="markdown small spacer-top" + > + <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 {}} + /> + </div> + </div> + <div + className="settings-definition-right padded-top" + > + <div + className="display-flex-center big-spacer-top" + > + <InputForBoolean + isDefault={true} + name="github.summary_comment_setting" + onChange={[Function]} + value={true} + /> + </div> + </div> + </div> </Fragment> `; exports[`it should render correctly for gitlab 1`] = ` <Fragment> <div - className="form-field" + className="settings-definition" > - <label - className="display-flex-center" - htmlFor="gitlab.repository" - > - settings.pr_decoration.binding.form.gitlab.repository - <MandatoryFieldMarker /> - </label> - <input - className="input-super-large" - id="gitlab.repository" - maxLength={256} - name="gitlab.repository" - onChange={[Function]} - type="text" - value="" - /> + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="gitlab.repository" + > + settings.pr_decoration.binding.form.gitlab.repository + <MandatoryFieldMarker /> + </label> + <div + className="markdown small spacer-top" + > + <FormattedMessage + defaultMessage="settings.pr_decoration.binding.form.gitlab.repository.help" + id="settings.pr_decoration.binding.form.gitlab.repository.help" + values={Object {}} + /> + <div + className="spacer-top nowrap" + > + example + : + <em> + <strong> + 123456 + </strong> + </em> + </div> + </div> + </div> + <div + className="settings-definition-right padded-top" + > + <input + className="input-super-large big-spacer-top" + id="gitlab.repository" + maxLength={256} + name="gitlab.repository" + onChange={[Function]} + type="text" + value="" + /> + </div> </div> </Fragment> `; @@ -298,107 +694,147 @@ exports[`it should render correctly for gitlab 1`] = ` exports[`should render the monorepo field when the feature is supported 1`] = ` <Fragment> <div - className="form-field" + className="settings-definition" > - <label - className="display-flex-center" - htmlFor="azure.project" - > - settings.pr_decoration.binding.form.azure.project - <MandatoryFieldMarker /> - <HelpTooltip - className="spacer-left" - overlay={ - <FormattedMessage - defaultMessage="settings.pr_decoration.binding.form.azure.project.help" - id="settings.pr_decoration.binding.form.azure.project.help" - values={Object {}} - /> - } - placement="right" + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="azure.project" + > + settings.pr_decoration.binding.form.azure.project + <MandatoryFieldMarker /> + </label> + <div + className="markdown small spacer-top" + > + <FormattedMessage + defaultMessage="settings.pr_decoration.binding.form.azure.project.help" + id="settings.pr_decoration.binding.form.azure.project.help" + values={Object {}} + /> + <div + className="spacer-top nowrap" + > + example + : + <em> + <strong> + My Project + </strong> + </em> + </div> + </div> + </div> + <div + className="settings-definition-right padded-top" + > + <input + className="input-super-large big-spacer-top" + id="azure.project" + maxLength={256} + name="azure.project" + onChange={[Function]} + type="text" + value="" /> - </label> - <input - className="input-super-large" - id="azure.project" - maxLength={256} - name="azure.project" - onChange={[Function]} - type="text" - value="" - /> + </div> </div> <div - className="form-field" + className="settings-definition" > - <label - className="display-flex-center" - htmlFor="azure.repository" - > - settings.pr_decoration.binding.form.azure.repository - <MandatoryFieldMarker /> - <HelpTooltip - className="spacer-left" - overlay={ - <FormattedMessage - defaultMessage="settings.pr_decoration.binding.form.azure.repository.help" - id="settings.pr_decoration.binding.form.azure.repository.help" - values={Object {}} - /> - } - placement="right" + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="azure.repository" + > + settings.pr_decoration.binding.form.azure.repository + <MandatoryFieldMarker /> + </label> + <div + className="markdown small spacer-top" + > + <FormattedMessage + defaultMessage="settings.pr_decoration.binding.form.azure.repository.help" + id="settings.pr_decoration.binding.form.azure.repository.help" + values={Object {}} + /> + <div + className="spacer-top nowrap" + > + example + : + <em> + <strong> + My Repository + </strong> + </em> + </div> + </div> + </div> + <div + className="settings-definition-right padded-top" + > + <input + className="input-super-large big-spacer-top" + id="azure.repository" + maxLength={256} + name="azure.repository" + onChange={[Function]} + type="text" + value="" /> - </label> - <input - className="input-super-large" - id="azure.repository" - maxLength={256} - name="azure.repository" - onChange={[Function]} - type="text" - value="" - /> + </div> </div> <div - className="form-field" + className="settings-definition" > - <label - className="display-flex-center" - htmlFor="monorepo" - > - settings.pr_decoration.binding.form.monorepo - <HelpTooltip - className="spacer-left" - overlay={ - <FormattedMessage - defaultMessage="settings.pr_decoration.binding.form.monorepo.help" - id="settings.pr_decoration.binding.form.monorepo.help" - values={ - Object { - "doc_link": <Link - onlyActiveOnIndex={false} - style={Object {}} - target="_blank" - to="/documentation/analysis/azuredevops-integration/" - > - learn_more - </Link>, - } + <div + className="settings-definition-left" + > + <label + className="h3" + htmlFor="monorepo" + > + settings.pr_decoration.binding.form.monorepo + </label> + <div + className="markdown small spacer-top" + > + <FormattedMessage + defaultMessage="settings.pr_decoration.binding.form.monorepo.help" + id="settings.pr_decoration.binding.form.monorepo.help" + values={ + Object { + "doc_link": <Link + onlyActiveOnIndex={false} + style={Object {}} + target="_blank" + to="/documentation/analysis/azuredevops-integration/" + > + learn_more + </Link>, } - /> - } - placement="right" - /> - </label> + } + /> + </div> + </div> <div - className="display-flex-center" + className="settings-definition-right padded-top" > - <InputForBoolean - isDefault={true} - name="monorepo" - onChange={[Function]} - value={false} - /> + <div + className="display-flex-center big-spacer-top" + > + <InputForBoolean + isDefault={true} + name="monorepo" + onChange={[Function]} + value={false} + /> + </div> </div> </div> </Fragment> 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 cd949808fd3..b231621bdbc 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 @@ -23,46 +23,60 @@ exports[`should display action state correctly 1`] = ` className="form-field" /> <div - className="form-field" + className="settings-definition big-spacer-bottom" > - <label - htmlFor="name" + <div + className="settings-definition-left" > - settings.pr_decoration.binding.form.name - <MandatoryFieldMarker - className="spacer-right" - /> - </label> - <Select - autosize={true} - className="abs-width-400" - clearable={false} - id="name" - menuContainerStyle={ - Object { - "maxWidth": "210%", - "width": "auto", - } - } - onChange={[Function]} - optionRenderer={[Function]} - options={ - Array [ + <label + className="h3" + htmlFor="name" + > + settings.pr_decoration.binding.form.name + <MandatoryFieldMarker + className="spacer-right" + /> + </label> + <div + className="markdown small spacer-top" + > + settings.pr_decoration.binding.form.name.help + </div> + </div> + <div + className="settings-definition-right" + > + <Select + autosize={true} + className="abs-width-400 big-spacer-top" + clearable={false} + id="name" + menuContainerStyle={ Object { - "alm": "github", - "key": "key", - "url": "http://url.com", - }, - ] - } - searchable={false} - value="" - valueKey="key" - valueRenderer={[Function]} - /> + "maxWidth": "210%", + "width": "auto", + } + } + onChange={[Function]} + optionRenderer={[Function]} + options={ + Array [ + Object { + "alm": "github", + "key": "key", + "url": "http://url.com", + }, + ] + } + searchable={false} + value="" + valueKey="key" + valueRenderer={[Function]} + /> + </div> </div> <div - className="display-flex-center" + className="display-flex-center big-spacer-top" > <DeferredSpinner className="spacer-right" @@ -96,46 +110,60 @@ exports[`should display action state correctly 2`] = ` className="form-field" /> <div - className="form-field" + className="settings-definition big-spacer-bottom" > - <label - htmlFor="name" + <div + className="settings-definition-left" > - settings.pr_decoration.binding.form.name - <MandatoryFieldMarker - className="spacer-right" - /> - </label> - <Select - autosize={true} - className="abs-width-400" - clearable={false} - id="name" - menuContainerStyle={ - Object { - "maxWidth": "210%", - "width": "auto", - } - } - onChange={[Function]} - optionRenderer={[Function]} - options={ - Array [ + <label + className="h3" + htmlFor="name" + > + settings.pr_decoration.binding.form.name + <MandatoryFieldMarker + className="spacer-right" + /> + </label> + <div + className="markdown small spacer-top" + > + settings.pr_decoration.binding.form.name.help + </div> + </div> + <div + className="settings-definition-right" + > + <Select + autosize={true} + className="abs-width-400 big-spacer-top" + clearable={false} + id="name" + menuContainerStyle={ Object { - "alm": "github", - "key": "key", - "url": "http://url.com", - }, - ] - } - searchable={false} - value="" - valueKey="key" - valueRenderer={[Function]} - /> + "maxWidth": "210%", + "width": "auto", + } + } + onChange={[Function]} + optionRenderer={[Function]} + options={ + Array [ + Object { + "alm": "github", + "key": "key", + "url": "http://url.com", + }, + ] + } + searchable={false} + value="" + valueKey="key" + valueRenderer={[Function]} + /> + </div> </div> <div - className="display-flex-center" + className="display-flex-center big-spacer-top" > <DeferredSpinner className="spacer-right" @@ -177,46 +205,60 @@ exports[`should display action state correctly 3`] = ` className="form-field" /> <div - className="form-field" + className="settings-definition big-spacer-bottom" > - <label - htmlFor="name" + <div + className="settings-definition-left" > - settings.pr_decoration.binding.form.name - <MandatoryFieldMarker - className="spacer-right" - /> - </label> - <Select - autosize={true} - className="abs-width-400" - clearable={false} - id="name" - menuContainerStyle={ - Object { - "maxWidth": "210%", - "width": "auto", - } - } - onChange={[Function]} - optionRenderer={[Function]} - options={ - Array [ + <label + className="h3" + htmlFor="name" + > + settings.pr_decoration.binding.form.name + <MandatoryFieldMarker + className="spacer-right" + /> + </label> + <div + className="markdown small spacer-top" + > + settings.pr_decoration.binding.form.name.help + </div> + </div> + <div + className="settings-definition-right" + > + <Select + autosize={true} + className="abs-width-400 big-spacer-top" + clearable={false} + id="name" + menuContainerStyle={ Object { - "alm": "github", - "key": "key", - "url": "http://url.com", - }, - ] - } - searchable={false} - value="" - valueKey="key" - valueRenderer={[Function]} - /> + "maxWidth": "210%", + "width": "auto", + } + } + onChange={[Function]} + optionRenderer={[Function]} + options={ + Array [ + Object { + "alm": "github", + "key": "key", + "url": "http://url.com", + }, + ] + } + searchable={false} + value="" + valueKey="key" + valueRenderer={[Function]} + /> + </div> </div> <div - className="display-flex-center" + className="display-flex-center big-spacer-top" > <DeferredSpinner className="spacer-right" @@ -277,60 +319,74 @@ exports[`should render multiple instances correctly 1`] = ` className="form-field" /> <div - className="form-field" + className="settings-definition big-spacer-bottom" > - <label - htmlFor="name" + <div + className="settings-definition-left" > - settings.pr_decoration.binding.form.name - <MandatoryFieldMarker - className="spacer-right" - /> - </label> - <Select - autosize={true} - className="abs-width-400" - clearable={false} - id="name" - menuContainerStyle={ - Object { - "maxWidth": "210%", - "width": "auto", - } - } - onChange={[Function]} - optionRenderer={[Function]} - options={ - Array [ - Object { - "alm": "github", - "key": "i1", - "url": "http://github.enterprise.com", - }, - Object { - "alm": "github", - "key": "i2", - "url": "http://github.enterprise.com", - }, - Object { - "alm": "bitbucket", - "key": "i3", - "url": "http://bbs.enterprise.com", - }, + <label + className="h3" + htmlFor="name" + > + settings.pr_decoration.binding.form.name + <MandatoryFieldMarker + className="spacer-right" + /> + </label> + <div + className="markdown small spacer-top" + > + settings.pr_decoration.binding.form.name.help + </div> + </div> + <div + className="settings-definition-right" + > + <Select + autosize={true} + className="abs-width-400 big-spacer-top" + clearable={false} + id="name" + menuContainerStyle={ Object { - "alm": "azure", - "key": "i4", - }, - ] - } - searchable={false} - value="" - valueKey="key" - valueRenderer={[Function]} - /> + "maxWidth": "210%", + "width": "auto", + } + } + onChange={[Function]} + optionRenderer={[Function]} + options={ + Array [ + Object { + "alm": "github", + "key": "i1", + "url": "http://github.enterprise.com", + }, + Object { + "alm": "github", + "key": "i2", + "url": "http://github.enterprise.com", + }, + Object { + "alm": "bitbucket", + "key": "i3", + "url": "http://bbs.enterprise.com", + }, + Object { + "alm": "azure", + "key": "i4", + }, + ] + } + searchable={false} + value="" + valueKey="key" + valueRenderer={[Function]} + /> + </div> </div> <div - className="display-flex-center" + className="display-flex-center big-spacer-top" > <DeferredSpinner className="spacer-right" @@ -364,57 +420,71 @@ exports[`should render multiple instances correctly 2`] = ` className="form-field" /> <div - className="form-field" + className="settings-definition big-spacer-bottom" > - <label - htmlFor="name" + <div + className="settings-definition-left" > - settings.pr_decoration.binding.form.name - <MandatoryFieldMarker - className="spacer-right" - /> - </label> - <Select - autosize={true} - className="abs-width-400" - clearable={false} - id="name" - menuContainerStyle={ - Object { - "maxWidth": "210%", - "width": "auto", - } - } - onChange={[Function]} - optionRenderer={[Function]} - options={ - Array [ - Object { - "alm": "github", - "key": "i1", - "url": "http://github.enterprise.com", - }, - Object { - "alm": "github", - "key": "i2", - "url": "http://github.enterprise.com", - }, - Object { - "alm": "bitbucket", - "key": "i3", - "url": "http://bbs.enterprise.com", - }, + <label + className="h3" + htmlFor="name" + > + settings.pr_decoration.binding.form.name + <MandatoryFieldMarker + className="spacer-right" + /> + </label> + <div + className="markdown small spacer-top" + > + settings.pr_decoration.binding.form.name.help + </div> + </div> + <div + className="settings-definition-right" + > + <Select + autosize={true} + className="abs-width-400 big-spacer-top" + clearable={false} + id="name" + menuContainerStyle={ Object { - "alm": "azure", - "key": "i4", - }, - ] - } - searchable={false} - value="i1" - valueKey="key" - valueRenderer={[Function]} - /> + "maxWidth": "210%", + "width": "auto", + } + } + onChange={[Function]} + optionRenderer={[Function]} + options={ + Array [ + Object { + "alm": "github", + "key": "i1", + "url": "http://github.enterprise.com", + }, + Object { + "alm": "github", + "key": "i2", + "url": "http://github.enterprise.com", + }, + Object { + "alm": "bitbucket", + "key": "i3", + "url": "http://bbs.enterprise.com", + }, + Object { + "alm": "azure", + "key": "i4", + }, + ] + } + searchable={false} + value="i1" + valueKey="key" + valueRenderer={[Function]} + /> + </div> </div> <AlmSpecificForm alm="github" @@ -425,11 +495,34 @@ exports[`should render multiple instances correctly 2`] = ` "repository": "account/repo", } } + instances={ + Array [ + Object { + "alm": "github", + "key": "i1", + "url": "http://github.enterprise.com", + }, + Object { + "alm": "github", + "key": "i2", + "url": "http://github.enterprise.com", + }, + Object { + "alm": "bitbucket", + "key": "i3", + "url": "http://bbs.enterprise.com", + }, + Object { + "alm": "azure", + "key": "i4", + }, + ] + } monorepoEnabled={false} onFieldChange={[MockFunction]} /> <div - className="display-flex-center" + className="display-flex-center big-spacer-top" > <DeferredSpinner className="spacer-right" @@ -493,46 +586,60 @@ exports[`should render single instance correctly 1`] = ` className="form-field" /> <div - className="form-field" + className="settings-definition big-spacer-bottom" > - <label - htmlFor="name" + <div + className="settings-definition-left" > - settings.pr_decoration.binding.form.name - <MandatoryFieldMarker - className="spacer-right" - /> - </label> - <Select - autosize={true} - className="abs-width-400" - clearable={false} - id="name" - menuContainerStyle={ - Object { - "maxWidth": "210%", - "width": "auto", - } - } - onChange={[Function]} - optionRenderer={[Function]} - options={ - Array [ + <label + className="h3" + htmlFor="name" + > + settings.pr_decoration.binding.form.name + <MandatoryFieldMarker + className="spacer-right" + /> + </label> + <div + className="markdown small spacer-top" + > + settings.pr_decoration.binding.form.name.help + </div> + </div> + <div + className="settings-definition-right" + > + <Select + autosize={true} + className="abs-width-400 big-spacer-top" + clearable={false} + id="name" + menuContainerStyle={ Object { - "alm": "github", - "key": "single", - "url": "http://single.url", - }, - ] - } - searchable={false} - value="" - valueKey="key" - valueRenderer={[Function]} - /> + "maxWidth": "210%", + "width": "auto", + } + } + onChange={[Function]} + optionRenderer={[Function]} + options={ + Array [ + Object { + "alm": "github", + "key": "single", + "url": "http://single.url", + }, + ] + } + searchable={false} + value="" + valueKey="key" + valueRenderer={[Function]} + /> + </div> </div> <div - className="display-flex-center" + className="display-flex-center big-spacer-top" > <DeferredSpinner className="spacer-right" diff --git a/server/sonar-web/src/main/js/components/tutorials/utils.ts b/server/sonar-web/src/main/js/components/tutorials/utils.ts index 4ce77e21f2f..cfb550e852b 100644 --- a/server/sonar-web/src/main/js/components/tutorials/utils.ts +++ b/server/sonar-web/src/main/js/components/tutorials/utils.ts @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { convertGithubApiUrlToLink, stripTrailingSlash } from '../../helpers/urls'; import { AlmSettingsInstance, ProjectAlmBindingResponse } from '../../types/alm-settings'; export function quote(os: string): (s: string) => string { @@ -64,10 +65,7 @@ export function buildGithubLink( } // strip the api path: - const urlRoot = almBinding.url - .replace(/\/api\/v\d+\/?$/, '') // GH Enterprise - .replace(/^https?:\/\/api\.github\.com/, 'https://github.com') // GH.com - .replace(/\/$/, ''); + const urlRoot = convertGithubApiUrlToLink(almBinding.url); - return `${urlRoot}/${projectBinding.repository}`; + return `${stripTrailingSlash(urlRoot)}/${projectBinding.repository}`; } diff --git a/server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts index fcd47d20572..bed9b1df8fb 100644 --- a/server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts +++ b/server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts @@ -20,19 +20,37 @@ import { ComponentQualifier } from '../../types/component'; import { IssueType } from '../../types/issues'; import { + convertGithubApiUrlToLink, getComponentDrilldownUrl, getComponentIssuesUrl, getComponentOverviewUrl, getComponentSecurityHotspotsUrl, getIssuesUrl, getQualityGatesUrl, - getQualityGateUrl + getQualityGateUrl, + stripTrailingSlash } from '../urls'; const SIMPLE_COMPONENT_KEY = 'sonarqube'; const COMPLEX_COMPONENT_KEY = 'org.sonarsource.sonarqube:sonarqube'; const METRIC = 'coverage'; +describe('#convertGithubApiUrlToLink', () => { + it('should correctly convert a GitHub API URL to a Web URL', () => { + expect(convertGithubApiUrlToLink('https://api.github.com')).toBe('https://github.com'); + expect(convertGithubApiUrlToLink('https://company.github.com/api/v3')).toBe( + 'https://company.github.com' + ); + }); +}); + +describe('#stripTrailingSlash', () => { + it('should correctly strip trailing slashes from any URL', () => { + expect(stripTrailingSlash('https://example.com/')).toBe('https://example.com'); + expect(convertGithubApiUrlToLink('https://example.com')).toBe('https://example.com'); + }); +}); + describe('#getComponentIssuesUrl', () => { it('should work without parameters', () => { expect(getComponentIssuesUrl(SIMPLE_COMPONENT_KEY, {})).toEqual({ diff --git a/server/sonar-web/src/main/js/helpers/urls.ts b/server/sonar-web/src/main/js/helpers/urls.ts index 3d255640269..1adb6ee8ec6 100644 --- a/server/sonar-web/src/main/js/helpers/urls.ts +++ b/server/sonar-web/src/main/js/helpers/urls.ts @@ -280,3 +280,13 @@ export function getHomePageUrl(homepage: T.HomePage) { // should never happen, but just in case... return '/projects'; } + +export function convertGithubApiUrlToLink(url: string) { + return url + .replace(/^https?:\/\/api\.github\.com/, 'https://github.com') // GH.com + .replace(/\/api\/v\d+\/?$/, ''); // GH Enterprise +} + +export function stripTrailingSlash(url: string) { + return url.replace(/\/$/, ''); +} |