]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10486 Display badges for applications
authorPascal Mugnier <pascal.mugnier@sonarsource.com>
Tue, 27 Mar 2018 07:14:32 +0000 (09:14 +0200)
committerSonarTech <sonartech@sonarsource.com>
Thu, 29 Mar 2018 18:20:48 +0000 (20:20 +0200)
server/sonar-web/src/main/js/apps/overview/badges/BadgesModal.tsx
server/sonar-web/src/main/js/apps/overview/badges/__tests__/BadgesModal-test.tsx
server/sonar-web/src/main/js/apps/overview/badges/__tests__/__snapshots__/BadgesModal-test.tsx.snap
server/sonar-web/src/main/js/apps/overview/meta/Meta.tsx
server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
server/sonar-web/src/main/js/apps/portfolio/components/Summary.tsx
server/sonar-web/src/main/js/apps/portfolio/components/__tests__/App-test.tsx
server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/App-test.tsx.snap
server/sonar-web/src/main/js/apps/portfolio/styles.css
server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/App-test.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index e666f5cb5c3abb27bf5264a13a9f306515ebfd36..da458f1b01445ca8224b96119093acbdbb67eadf 100644 (file)
@@ -34,6 +34,7 @@ interface Props {
   metrics: { [key: string]: Metric };
   onSonarCloud: boolean;
   project: string;
+  qualifier: string;
 }
 
 interface State {
@@ -66,7 +67,7 @@ export default class BadgesModal extends React.PureComponent<Props, State> {
   };
 
   render() {
-    const { branchLike, project } = this.props;
+    const { branchLike, project, qualifier } = this.props;
     const { selectedType, badgeOptions } = this.state;
     const header = translate('overview.badges.title');
     const fullBadgeOptions = { project, ...badgeOptions, ...getBranchLikeQuery(branchLike) };
@@ -76,7 +77,7 @@ export default class BadgesModal extends React.PureComponent<Props, State> {
     return (
       <div className="overview-meta-card">
         <Button className="js-project-badges" onClick={this.handleOpen}>
-          {translate('overview.badges.get_badge')}
+          {translate('overview.badges.get_badge', qualifier)}
         </Button>
         {this.state.open && (
           <Modal contentLabel={header} onRequestClose={this.handleClose}>
@@ -84,7 +85,9 @@ export default class BadgesModal extends React.PureComponent<Props, State> {
               <h2>{header}</h2>
             </header>
             <div className="modal-body">
-              <p className="huge-spacer-bottom">{translate('overview.badges.description')}</p>
+              <p className="huge-spacer-bottom">
+                {translate('overview.badges.description', qualifier)}
+              </p>
               <div className="badges-list spacer-bottom">
                 {badges.map(type => (
                   <BadgeButton
@@ -97,7 +100,7 @@ export default class BadgesModal extends React.PureComponent<Props, State> {
                 ))}
               </div>
               <p className="text-center note huge-spacer-bottom">
-                {translate('overview.badges', selectedType, 'description')}
+                {translate('overview.badges', selectedType, 'description', qualifier)}
               </p>
               <BadgeParams
                 className="big-spacer-bottom"
index 6d0bfbc613b3f8f3de9f63f47c91597fa8cddbb4..5cff7462feb34e44d20f14dc4aa4bce7d3ee77b6 100644 (file)
@@ -36,7 +36,13 @@ const shortBranch: ShortLivingBranch = {
 
 it('should display the modal after click on sonar cloud', () => {
   const wrapper = shallow(
-    <BadgesModal branchLike={shortBranch} metrics={{}} onSonarCloud={true} project="foo" />
+    <BadgesModal
+      branchLike={shortBranch}
+      metrics={{}}
+      onSonarCloud={true}
+      project="foo"
+      qualifier="TRK"
+    />
   );
   expect(wrapper).toMatchSnapshot();
   click(wrapper.find('Button'));
@@ -45,7 +51,13 @@ it('should display the modal after click on sonar cloud', () => {
 
 it('should display the modal after click on sonar qube', () => {
   const wrapper = shallow(
-    <BadgesModal branchLike={shortBranch} metrics={{}} onSonarCloud={false} project="foo" />
+    <BadgesModal
+      branchLike={shortBranch}
+      metrics={{}}
+      onSonarCloud={false}
+      project="foo"
+      qualifier="TRK"
+    />
   );
   expect(wrapper).toMatchSnapshot();
   click(wrapper.find('Button'));
index 4c9ba3fb24b775dea09c11da596d087e6c0a0a4a..012ed7da82100b0f91a44518af7c5f8609795a98 100644 (file)
@@ -8,7 +8,7 @@ exports[`should display the modal after click on sonar cloud 1`] = `
     className="js-project-badges"
     onClick={[Function]}
   >
-    overview.badges.get_badge
+    overview.badges.get_badge.TRK
   </Button>
 </div>
 `;
@@ -31,7 +31,7 @@ exports[`should display the modal after click on sonar cloud 2`] = `
     <p
       className="huge-spacer-bottom"
     >
-      overview.badges.description
+      overview.badges.description.TRK
     </p>
     <div
       className="badges-list spacer-bottom"
@@ -61,7 +61,7 @@ exports[`should display the modal after click on sonar cloud 2`] = `
     <p
       className="text-center note huge-spacer-bottom"
     >
-      overview.badges.measure.description
+      overview.badges.measure.description.TRK
     </p>
     <BadgeParams
       className="big-spacer-bottom"
@@ -101,7 +101,7 @@ exports[`should display the modal after click on sonar qube 1`] = `
     className="js-project-badges"
     onClick={[Function]}
   >
-    overview.badges.get_badge
+    overview.badges.get_badge.TRK
   </Button>
 </div>
 `;
@@ -124,7 +124,7 @@ exports[`should display the modal after click on sonar qube 2`] = `
     <p
       className="huge-spacer-bottom"
     >
-      overview.badges.description
+      overview.badges.description.TRK
     </p>
     <div
       className="badges-list spacer-bottom"
@@ -147,7 +147,7 @@ exports[`should display the modal after click on sonar qube 2`] = `
     <p
       className="text-center note huge-spacer-bottom"
     >
-      overview.badges.measure.description
+      overview.badges.measure.description.TRK
     </p>
     <BadgeParams
       className="big-spacer-bottom"
index ef5991a2a12e4d48aa37f2fe5b37066056a430ec..631c258e268cec86f3a64249f2377bcede5216f4 100644 (file)
@@ -104,15 +104,15 @@ export default class Meta extends React.PureComponent<Props> {
           {organizationsEnabled && <MetaOrganizationKey organization={component.organization} />}
         </div>
 
-        {isProject &&
-          !isPrivate && (
-            <BadgesModal
-              branchLike={branchLike}
-              metrics={metrics}
-              onSonarCloud={onSonarCloud}
-              project={component.key}
-            />
-          )}
+        {!isPrivate && (
+          <BadgesModal
+            branchLike={branchLike}
+            metrics={metrics}
+            onSonarCloud={onSonarCloud}
+            project={component.key}
+            qualifier={component.qualifier}
+          />
+        )}
       </div>
     );
   }
index aa0fd5f0a169d78dec8e3ee06aad741c6f5a9ad0..ab35a37e8ae16cf48e06e01c956f5bebc93eaa20 100644 (file)
@@ -34,11 +34,11 @@ import { getChildren } from '../../../api/components';
 import { translate } from '../../../helpers/l10n';
 import { fetchMetrics } from '../../../store/rootActions';
 import { getMetrics } from '../../../store/rootReducer';
-import { Metric } from '../../../app/types';
+import { Metric, Component } from '../../../app/types';
 import '../styles.css';
 
 interface OwnProps {
-  component: { key: string; name: string };
+  component: Component;
 }
 
 interface StateToProps {
@@ -184,10 +184,21 @@ export class App extends React.PureComponent<Props, State> {
           <div className="page-main">{this.renderMain()}</div>
 
           <aside className="page-sidebar-fixed">
-            {!this.isEmpty() &&
-              !this.isNotComputed() && <Summary component={component} measures={measures!} />}
-            <Activity component={component.key} metrics={this.props.metrics} />
-            <Report component={component} />
+            <div className="portfolio-meta-card">
+              <h4 className="portfolio-meta-header">
+                {translate('overview.about_this_portfolio')}
+              </h4>
+              {!this.isEmpty() &&
+                !this.isNotComputed() && <Summary component={component} measures={measures!} />}
+            </div>
+
+            <div className="portfolio-meta-card">
+              <Activity component={component.key} metrics={this.props.metrics} />
+            </div>
+
+            <div className="portfolio-meta-card">
+              <Report component={component} />
+            </div>
           </aside>
         </div>
       </div>
index 75ef3f327237c715b6ab71c364623f9f239a7ae1..b32e4cda4341d1c1423e864a02fc787ddd04deba 100644 (file)
@@ -34,7 +34,7 @@ export default function Summary({ component, measures }: Props) {
   const nclocDistribution = measures['ncloc_language_distribution'];
 
   return (
-    <section id="portfolio-summary" className="big-spacer-bottom">
+    <section className="big-spacer-bottom" id="portfolio-summary">
       {component.description && <div className="big-spacer-bottom">{component.description}</div>}
 
       <ul className="portfolio-grid">
index 4abdc4e056b745f61e1740fd50be412747f22797..58ec3447ae857df91fc0bca80506852a5ca7c7d7 100644 (file)
@@ -44,11 +44,12 @@ jest.mock('../Report', () => ({
 import * as React from 'react';
 import { shallow, mount } from 'enzyme';
 import { App } from '../App';
+import { Component } from '../../../../app/types';
 
 const getMeasures = require('../../../../api/measures').getMeasures as jest.Mock<any>;
 const getChildren = require('../../../../api/components').getChildren as jest.Mock<any>;
 
-const component = { key: 'foo', name: 'Foo' };
+const component = { key: 'foo', name: 'Foo', qualifier: 'TRK' } as Component;
 
 it('renders', () => {
   const wrapper = shallow(<App component={component} fetchMetrics={jest.fn()} metrics={{}} />);
index 76ccc5b4cb8fe7868d8847d38292c6aea7d40ae3..4a89b29c50988810e589083064e834b15d5fa9e8 100644 (file)
@@ -61,32 +61,51 @@ exports[`renders 1`] = `
     <aside
       className="page-sidebar-fixed"
     >
-      <Summary
-        component={
-          Object {
-            "key": "foo",
-            "name": "Foo",
+      <div
+        className="portfolio-meta-card"
+      >
+        <h4
+          className="portfolio-meta-header"
+        >
+          overview.about_this_portfolio
+        </h4>
+        <Summary
+          component={
+            Object {
+              "key": "foo",
+              "name": "Foo",
+              "qualifier": "TRK",
+            }
           }
-        }
-        measures={
-          Object {
-            "ncloc": "173",
-            "reliability_rating": "1",
+          measures={
+            Object {
+              "ncloc": "173",
+              "reliability_rating": "1",
+            }
           }
-        }
-      />
-      <Activity
-        component="foo"
-        metrics={Object {}}
-      />
-      <Report
-        component={
-          Object {
-            "key": "foo",
-            "name": "Foo",
+        />
+      </div>
+      <div
+        className="portfolio-meta-card"
+      >
+        <Activity
+          component="foo"
+          metrics={Object {}}
+        />
+      </div>
+      <div
+        className="portfolio-meta-card"
+      >
+        <Report
+          component={
+            Object {
+              "key": "foo",
+              "name": "Foo",
+              "qualifier": "TRK",
+            }
           }
-        }
-      />
+        />
+      </div>
     </aside>
   </div>
 </div>
@@ -116,18 +135,36 @@ exports[`renders when portfolio is empty 1`] = `
     <aside
       className="page-sidebar-fixed"
     >
-      <Activity
-        component="foo"
-        metrics={Object {}}
-      />
-      <Report
-        component={
-          Object {
-            "key": "foo",
-            "name": "Foo",
+      <div
+        className="portfolio-meta-card"
+      >
+        <h4
+          className="portfolio-meta-header"
+        >
+          overview.about_this_portfolio
+        </h4>
+      </div>
+      <div
+        className="portfolio-meta-card"
+      >
+        <Activity
+          component="foo"
+          metrics={Object {}}
+        />
+      </div>
+      <div
+        className="portfolio-meta-card"
+      >
+        <Report
+          component={
+            Object {
+              "key": "foo",
+              "name": "Foo",
+              "qualifier": "TRK",
+            }
           }
-        }
-      />
+        />
+      </div>
     </aside>
   </div>
 </div>
@@ -154,18 +191,36 @@ exports[`renders when portfolio is not computed 1`] = `
     <aside
       className="page-sidebar-fixed"
     >
-      <Activity
-        component="foo"
-        metrics={Object {}}
-      />
-      <Report
-        component={
-          Object {
-            "key": "foo",
-            "name": "Foo",
+      <div
+        className="portfolio-meta-card"
+      >
+        <h4
+          className="portfolio-meta-header"
+        >
+          overview.about_this_portfolio
+        </h4>
+      </div>
+      <div
+        className="portfolio-meta-card"
+      >
+        <Activity
+          component="foo"
+          metrics={Object {}}
+        />
+      </div>
+      <div
+        className="portfolio-meta-card"
+      >
+        <Report
+          component={
+            Object {
+              "key": "foo",
+              "name": "Foo",
+              "qualifier": "TRK",
+            }
           }
-        }
-      />
+        />
+      </div>
     </aside>
   </div>
 </div>
index 1d51166d27ae8f57cc6b4e6902a5019ed0974b57..7267fc0b4b6d9cc2d68975abdc952c06dd066d7f 100644 (file)
 .portfolio-sub-components-cell {
   width: 90px;
 }
+
+.portfolio-meta-header {
+  margin-bottom: calc(0.5 * var(--gridSize));
+  color: var(--baseFontColor);
+}
+
+.portfolio-meta-card {
+  min-width: 200px;
+  box-sizing: border-box;
+}
+
+.portfolio-meta-card + .portfolio-meta-card {
+  margin-top: calc(2 * var(--gridSize));
+  padding-top: calc(2 * var(--gridSize) - 1px);
+  border-top: 1px solid var(--barBorderColor);
+}
index cdb269fd1f8d15bf78f3e3d2d1e443380395df28..ff0be9851504590f26b3266ea2699174c7f9b681 100644 (file)
@@ -36,6 +36,7 @@ jest.mock('../../../app/utils/handleRequiredAuthorization', () => ({
 import * as React from 'react';
 import { mount } from 'enzyme';
 import App from '../App';
+import { Component } from '../../../app/types';
 
 const associateGateWithProject = require('../../../api/quality-gates')
   .associateGateWithProject as jest.Mock<any>;
@@ -62,7 +63,7 @@ const component = {
   organization: 'org',
   qualifier: 'TRK',
   version: '0.0.1'
-};
+} as Component;
 
 beforeEach(() => {
   associateGateWithProject.mockClear();
@@ -73,7 +74,10 @@ beforeEach(() => {
 it('checks permissions', () => {
   handleRequiredAuthorization.mockClear();
   mount(
-    <App component={{ ...component, configuration: undefined }} onComponentChange={jest.fn()} />
+    <App
+      component={{ ...component, configuration: undefined } as Component}
+      onComponentChange={jest.fn()}
+    />
   );
   expect(handleRequiredAuthorization).toBeCalled();
 });
index b7bc5c0ac53920006e9d92c3432f001d305378d9..67f4acdf5a4bdec83301fb10a2574fd3932dddaa 100644 (file)
@@ -2297,6 +2297,7 @@ overview.last_analysis_x=last analysis {0}
 overview.started_on_x=Started on {0}
 overview.last_analysis_on_x=Last analysis on {0}
 overview.on_new_code=On New Code
+overview.about_this_portfolio=About This Portfolio
 overview.about_this_project.APP=About This Application
 overview.about_this_project.TRK=About This Project
 overview.about_this_project.BRC=About This Sub-Project
@@ -2356,15 +2357,22 @@ overview.complexity_tooltip.file={0} files have complexity around {1}
 
 overview.deprecated_profile=This quality profile uses {0} deprecated rules and should be updated.
 
-overview.badges.get_badge=Get project badges
+
+overview.badges.get_badge.TRK=Get project badges
+overview.badges.get_badge.VW=Get portfolio badges
+overview.badges.get_badge.APP=Get application badges
 overview.badges.title=Badges
-overview.badges.description=Show the status of your project metrics on your README or website. Pick your style:
+overview.badges.description.TRK=Show the status of your project metrics on your README or website. Pick your style:
+overview.badges.description.VW=Show the status of your portfolio metrics on your README or website. Pick your style:
+overview.badges.description.APP=Show the status of your application metrics on your README or website. Pick your style:
 overview.badges.metric=Metric
 overview.badges.options.colors.white=White
 overview.badges.options.colors.black=Black
 overview.badges.options.colors.orange=Orange
 overview.badges.measure.alt=Standard badge
-overview.badges.measure.description=This badge dynamically displays the current status of one metric of your project.
+overview.badges.measure.description.TRK=This badge dynamically displays the current status of one metric of your project.
+overview.badges.measure.description.VW=This badge dynamically displays the current status of one metric of your portfolio.
+overview.badges.measure.description.APP=This badge dynamically displays the current status of one metric of your application.
 overview.badges.marketing.alt=Scanned on SonarCloud badge
 overview.badges.marketing.description=This badge lets you advertise that you're using SonarCloud for code quality.
 overview.badges.quality_gate.alt=SonarCloud Quality Gate badge