]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-14873 Ease comprehension of PR Decoration settings form
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Fri, 28 May 2021 08:00:03 +0000 (10:00 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 10 Jun 2021 20:03:26 +0000 (20:03 +0000)
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/AlmSpecificForm.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/PRDecorationBindingRenderer.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/AlmSpecificForm-test.tsx
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/AlmSpecificForm-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/pullRequestDecorationBinding/__tests__/__snapshots__/PRDecorationBindingRenderer-test.tsx.snap
server/sonar-web/src/main/js/components/tutorials/utils.ts
server/sonar-web/src/main/js/helpers/__tests__/urls-test.ts
server/sonar-web/src/main/js/helpers/urls.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index d476039cc8bb0a8cfd09ff8f30f1beb88327059a..4f4672e6bf152752adf78594dd0c3efa70588169 100644 (file)
 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',
index d2d2c62bf56fc19734af829bf67e7cb9ef04ad52..7fc2133fa00bb9f8cf4d8704392c6f6f50077321 100644 (file)
@@ -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}>
index 5f118c3f2252136ecb85815280f1a45e05fa008e..fb76261471ea6d11a15c3b81284a6171ac64ef4d 100644 (file)
@@ -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: '',
index 1ce4d32cfe769e4a5c238544b3f70eb734b0d191..1cb1269f6ddcf0ade6169a34368e2b1e94511802 100644 (file)
 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>
index cd949808fd37f4bba53bf03b3a9ab60f489ab2df..b231621bdbcdbdff0e7aee6dada057ebefff2bb1 100644 (file)
@@ -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"
index 4ce77e21f2f6bef11f597b93be98d8020be8d7bf..cfb550e852bfc556fe05e53de859eea3f32731dc 100644 (file)
@@ -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}`;
 }
index fcd47d20572643f6ca0d30abd1b276a997f8f89e..bed9b1df8fb20d47ed1145c58f608d6c9b487f9c 100644 (file)
 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({
index 3d2556402691c88ca2a6905873df6f82f643da8e..1adb6ee8ec67f9019e5fbcf976a3da8e7942c608 100644 (file)
@@ -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(/\/$/, '');
+}
index 774264c96a99b62b9b88a4ba3fc8a824412779f6..9c77337826f1732b6d910f9f147ff7b73900af3e 100644 (file)
@@ -1170,25 +1170,27 @@ settings.pr_decoration.binding.title=DevOps Platform Integration
 settings.pr_decoration.binding.description=Display your Quality Gate status directly in your DevOps Platform. 
 settings.pr_decoration.binding.form.url=Project location
 settings.pr_decoration.binding.form.name=Configuration name
+settings.pr_decoration.binding.form.name.help=Each DevOps Platform instance must be configured globally first, and given a unique name. Pick the instance your project is hosted on.
 settings.pr_decoration.binding.form.monorepo=Enable mono repository support
 settings.pr_decoration.binding.form.monorepo.help=Enable this setting if your project is part of a mono repository. {doc_link}
 settings.pr_decoration.binding.form.monorepo.warning=This setting must be enabled for all SonarQube projects that are part of a mono repository.
-settings.pr_decoration.binding.form.azure.project=Project Name
-settings.pr_decoration.binding.form.azure.project.help=The name of the Azure DevOps 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 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.azure.project=Project name
+settings.pr_decoration.binding.form.azure.project.help=The name of the Azure DevOps project containing your repository. You can find this name on your project's Overview page.
+settings.pr_decoration.binding.form.azure.repository=Repository name
+settings.pr_decoration.binding.form.azure.repository.help=The name of your Azure DevOps repository. You can find this name on your project's Repos page.
+settings.pr_decoration.binding.form.github.repository=Repository name
+settings.pr_decoration.binding.form.github.repository.help=The full name of your repository, including the organization. You can find this name in your repository's URL. This name is case-sensitive! 
 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, a 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
-settings.pr_decoration.binding.form.bitbucket.slug.help=The Repository Slug is part of your Bitbucket Server repository URL. Example: ({example})
-settings.pr_decoration.binding.form.bitbucketcloud.repository=Repository SLUG
-settings.pr_decoration.binding.form.bitbucketcloud.repository.help=The Repository SLUG is part of your Bitbucket Cloud URL. Example: {example}
+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. This is case-sensitive!
+settings.pr_decoration.binding.form.bitbucket.slug=Repository slug
+settings.pr_decoration.binding.form.bitbucket.slug.help=The repository slug is part of your Bitbucket Server repository URL. This slug is case-sensitive!
+settings.pr_decoration.binding.form.bitbucketcloud.repository=Repository slug
+settings.pr_decoration.binding.form.bitbucketcloud.repository.help=The repository slug is part of your Bitbucket Cloud repository URL.
 
 settings.pr_decoration.binding.form.gitlab.repository=Project ID
+settings.pr_decoration.binding.form.gitlab.repository.help=The Project ID is a numerical unique identifier for your project. You can find it on your Project Overview.
 
 property.category.general=General
 property.category.general.email=Email