]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11039 Add a link to the alm repo of the linked project
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Tue, 31 Jul 2018 10:22:15 +0000 (12:22 +0200)
committerSonarTech <sonartech@sonarsource.com>
Fri, 10 Aug 2018 18:21:30 +0000 (20:21 +0200)
server/sonar-web/src/main/js/app/components/nav/component/ComponentNavHeader.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNavHeader-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavHeader-test.tsx.snap
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorial.tsx
server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSuggestion.tsx
server/sonar-web/src/main/js/helpers/__tests__/almIntegrations-test.ts [new file with mode: 0644]
server/sonar-web/src/main/js/helpers/almIntegrations.ts [new file with mode: 0644]

index e01fb137866e89c91eeef18fa021c2235eaa3b06..e616861f278a0e75c423d87038487a61b168b0ba 100644 (file)
@@ -27,8 +27,10 @@ import { getOrganizationByKey, areThereCustomOrganizations } from '../../../../s
 import OrganizationAvatar from '../../../../components/common/OrganizationAvatar';
 import OrganizationHelmet from '../../../../components/common/OrganizationHelmet';
 import OrganizationLink from '../../../../components/ui/OrganizationLink';
+import { sanitizeAlmId } from '../../../../helpers/almIntegrations';
 import { collapsePath, limitComponentName } from '../../../../helpers/path';
-import { getProjectUrl } from '../../../../helpers/urls';
+import { getProjectUrl, getBaseUrl } from '../../../../helpers/urls';
+import { isSonarCloud } from '../../../../helpers/system';
 
 interface StateProps {
   organization?: Organization;
@@ -66,6 +68,22 @@ export function ComponentNavHeader(props: Props) {
           </>
         )}
       {renderBreadcrumbs(component.breadcrumbs)}
+      {isSonarCloud() &&
+        component.almRepoUrl && (
+          <a
+            className="link-no-underline"
+            href={component.almRepoUrl}
+            rel="noopener noreferrer"
+            target="_blank">
+            <img
+              alt={sanitizeAlmId(component.almId)}
+              className="text-text-top spacer-left"
+              height={16}
+              src={`${getBaseUrl()}/images/sonarcloud/${sanitizeAlmId(component.almId)}.svg`}
+              width={16}
+            />
+          </a>
+        )}
       {props.currentBranchLike && (
         <ComponentNavBranch
           branchLikes={props.branchLikes}
index ff1fdacdf465bae4a4cd9205bf9380de110c682c..4c3eb39bf08030e091d5db06e4807234ec457a3f 100644 (file)
@@ -21,49 +21,69 @@ import * as React from 'react';
 import { shallow } from 'enzyme';
 import { ComponentNavHeader } from '../ComponentNavHeader';
 import { Visibility } from '../../../../types';
+import { isSonarCloud } from '../../../../../helpers/system';
+
+jest.mock('../../../../../helpers/system', () => ({
+  isSonarCloud: jest.fn().mockReturnValue(false)
+}));
+
+const component = {
+  breadcrumbs: [{ key: 'my-project', name: 'My Project', qualifier: 'TRK' }],
+  key: 'my-project',
+  name: 'My Project',
+  organization: 'foo',
+  qualifier: 'TRK',
+  visibility: Visibility.Public
+};
+
+const organization = {
+  key: 'foo',
+  name: 'The Foo Organization',
+  projectVisibility: Visibility.Public
+};
 
 it('should not render breadcrumbs with one element', () => {
-  const component = {
-    breadcrumbs: [{ key: 'my-project', name: 'My Project', qualifier: 'TRK' }],
-    key: 'my-project',
-    name: 'My Project',
-    organization: 'org',
-    qualifier: 'TRK',
-    visibility: Visibility.Public
-  };
-  const result = shallow(
-    <ComponentNavHeader
-      branchLikes={[]}
-      component={component}
-      currentBranchLike={undefined}
-      shouldOrganizationBeDisplayed={false}
-    />
-  );
-  expect(result).toMatchSnapshot();
+  expect(
+    shallow(
+      <ComponentNavHeader
+        branchLikes={[]}
+        component={component}
+        currentBranchLike={undefined}
+        shouldOrganizationBeDisplayed={false}
+      />
+    )
+  ).toMatchSnapshot();
 });
 
 it('should render organization', () => {
-  const component = {
-    breadcrumbs: [{ key: 'my-project', name: 'My Project', qualifier: 'TRK' }],
-    key: 'my-project',
-    name: 'My Project',
-    organization: 'foo',
-    qualifier: 'TRK',
-    visibility: Visibility.Public
-  };
-  const organization = {
-    key: 'foo',
-    name: 'The Foo Organization',
-    projectVisibility: Visibility.Public
-  };
-  const result = shallow(
-    <ComponentNavHeader
-      branchLikes={[]}
-      component={component}
-      currentBranchLike={undefined}
-      organization={organization}
-      shouldOrganizationBeDisplayed={true}
-    />
-  );
-  expect(result).toMatchSnapshot();
+  expect(
+    shallow(
+      <ComponentNavHeader
+        branchLikes={[]}
+        component={component}
+        currentBranchLike={undefined}
+        organization={organization}
+        shouldOrganizationBeDisplayed={true}
+      />
+    )
+  ).toMatchSnapshot();
+});
+
+it('should render alm links', () => {
+  (isSonarCloud as jest.Mock<any>).mockReturnValueOnce(true);
+  expect(
+    shallow(
+      <ComponentNavHeader
+        branchLikes={[]}
+        component={{
+          ...component,
+          almId: 'bitbucketcloud',
+          almRepoUrl: 'https://bitbucket.org/foo'
+        }}
+        currentBranchLike={undefined}
+        organization={organization}
+        shouldOrganizationBeDisplayed={true}
+      />
+    )
+  ).toMatchSnapshot();
 });
index b8498689bdab9d7be03cf08f661f2000cfb6a03a..ac4d42c30e4e093f965df6b6edfd73e08a6884bf 100644 (file)
@@ -35,6 +35,88 @@ exports[`should not render breadcrumbs with one element 1`] = `
 </header>
 `;
 
+exports[`should render alm links 1`] = `
+<header
+  className="navbar-context-header"
+>
+  <OrganizationHelmet
+    organization={
+      Object {
+        "key": "foo",
+        "name": "The Foo Organization",
+        "projectVisibility": "public",
+      }
+    }
+    title="My Project"
+  />
+  <React.Fragment>
+    <OrganizationAvatar
+      organization={
+        Object {
+          "key": "foo",
+          "name": "The Foo Organization",
+          "projectVisibility": "public",
+        }
+      }
+    />
+    <OrganizationLink
+      className="navbar-context-header-breadcrumb-link link-base-color link-no-underline spacer-left"
+      organization={
+        Object {
+          "key": "foo",
+          "name": "The Foo Organization",
+          "projectVisibility": "public",
+        }
+      }
+    >
+      The Foo Organization
+    </OrganizationLink>
+    <span
+      className="slash-separator"
+    />
+  </React.Fragment>
+  <React.Fragment
+    key="my-project"
+  >
+    <QualifierIcon
+      className="spacer-right"
+      qualifier="TRK"
+    />
+    <Link
+      className="navbar-context-header-breadcrumb-link link-base-color link-no-underline"
+      onlyActiveOnIndex={false}
+      style={Object {}}
+      title="My Project"
+      to={
+        Object {
+          "pathname": "/dashboard",
+          "query": Object {
+            "branch": undefined,
+            "id": "my-project",
+          },
+        }
+      }
+    >
+      My Project
+    </Link>
+  </React.Fragment>
+  <a
+    className="link-no-underline"
+    href="https://bitbucket.org/foo"
+    rel="noopener noreferrer"
+    target="_blank"
+  >
+    <img
+      alt="bitbucket"
+      className="text-text-top spacer-left"
+      height={16}
+      src="/images/sonarcloud/bitbucket.svg"
+      width={16}
+    />
+  </a>
+</header>
+`;
+
 exports[`should render organization 1`] = `
 <header
   className="navbar-context-header"
index 7ed7513f6f45e7b08a854cc4a827823a58830b98..68ed69161c6e32053408c038c6312d95dce3fc1b 100644 (file)
@@ -22,6 +22,7 @@ import AnalyzeTutorialSuggestion from './AnalyzeTutorialSuggestion';
 import ProjectAnalysisStep from '../components/ProjectAnalysisStep';
 import TokenStep from '../components/TokenStep';
 import { Component, LoggedInUser } from '../../../app/types';
+import { isVSTS } from '../../../helpers/almIntegrations';
 import { translate } from '../../../helpers/l10n';
 import '../styles.css';
 
@@ -57,7 +58,6 @@ export default class AnalyzeTutorial extends React.PureComponent<Props, State> {
     let stepNumber = 1;
 
     const almId = component.almId || currentUser.externalProvider;
-    const showTutorial = almId !== 'microsoft';
     return (
       <>
         <div className="page-header big-spacer-bottom">
@@ -67,7 +67,7 @@ export default class AnalyzeTutorial extends React.PureComponent<Props, State> {
 
         <AnalyzeTutorialSuggestion almId={almId} />
 
-        {showTutorial && (
+        {!isVSTS(almId) && (
           <>
             <TokenStep
               currentUser={currentUser}
index a93c73b94c88fef40ddaea07621e191e085e3a16..93237f3481c32c4c39b632fbe4965018fc85e726 100644 (file)
  */
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
+import { isBitbucket, isGithub, isVSTS } from '../../../helpers/almIntegrations';
 import { translate } from '../../../helpers/l10n';
 import { getBaseUrl } from '../../../helpers/urls';
 
 export default function AnalyzeTutorialSuggestion({ almId }: { almId?: string }) {
-  if (almId && almId.startsWith('bitbucket')) {
+  if (isBitbucket(almId)) {
     return (
       <div className="alert alert-info big-spacer-bottom">
         <p>{translate('onboarding.project_analysis.commands_for_analysis')}</p>
@@ -46,7 +47,7 @@ export default function AnalyzeTutorialSuggestion({ almId }: { almId?: string })
         />
       </div>
     );
-  } else if (almId === 'github') {
+  } else if (isGithub(almId)) {
     return (
       <div className="alert alert-info big-spacer-bottom">
         <p>{translate('onboarding.project_analysis.commands_for_analysis')} </p>
@@ -67,7 +68,7 @@ export default function AnalyzeTutorialSuggestion({ almId }: { almId?: string })
         />
       </div>
     );
-  } else if (almId === 'microsoft') {
+  } else if (isVSTS(almId)) {
     return (
       <p className="alert alert-info big-spacer-bottom">
         <FormattedMessage
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/almIntegrations-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/almIntegrations-test.ts
new file mode 100644 (file)
index 0000000..02353fe
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import { isBitbucket, isGithub, isVSTS, sanitizeAlmId } from '../almIntegrations';
+
+it('#isBitbucket', () => {
+  expect(isBitbucket('bitbucket')).toBeTruthy();
+  expect(isBitbucket('bitbucketcloud')).toBeTruthy();
+  expect(isBitbucket('github')).toBeFalsy();
+});
+
+it('#isGithub', () => {
+  expect(isGithub('github')).toBeTruthy();
+  expect(isGithub('bitbucket')).toBeFalsy();
+});
+
+it('#isVSTS', () => {
+  expect(isVSTS('microsoft')).toBeTruthy();
+  expect(isVSTS('github')).toBeFalsy();
+});
+
+it('#sanitizeAlmId', () => {
+  expect(sanitizeAlmId('bitbucketcloud')).toBe('bitbucket');
+  expect(sanitizeAlmId('bitbucket')).toBe('bitbucket');
+  expect(sanitizeAlmId('github')).toBe('github');
+});
diff --git a/server/sonar-web/src/main/js/helpers/almIntegrations.ts b/server/sonar-web/src/main/js/helpers/almIntegrations.ts
new file mode 100644 (file)
index 0000000..de03cb6
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+export function isBitbucket(almId?: string) {
+  return almId && almId.startsWith('bitbucket');
+}
+
+export function isGithub(almId?: string) {
+  return almId === 'github';
+}
+
+export function isVSTS(almId?: string) {
+  return almId === 'microsoft';
+}
+
+export function sanitizeAlmId(almId?: string) {
+  if (isBitbucket(almId)) {
+    return 'bitbucket';
+  }
+  return almId;
+}