]> source.dussan.org Git - sonarqube.git/commitdiff
GOV-288 Portfolio containing only provisioned projects is not 'empty'
authorPascal Mugnier <pascal.mugnier@sonarsource.com>
Wed, 11 Apr 2018 12:32:57 +0000 (14:32 +0200)
committerSonarTech <sonartech@sonarsource.com>
Tue, 17 Apr 2018 18:20:47 +0000 (20:20 +0200)
server/sonar-web/src/main/js/apps/issues/components/App.tsx
server/sonar-web/src/main/js/apps/overview/components/OverviewApp.tsx
server/sonar-web/src/main/js/apps/overview/meta/MetaSize.tsx
server/sonar-web/src/main/js/apps/overview/styles.css
server/sonar-web/src/main/js/apps/portfolio/components/App.tsx
server/sonar-web/src/main/js/apps/portfolio/components/RatingFreshness.tsx
server/sonar-web/src/main/js/apps/portfolio/components/Summary.tsx
server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/App-test.tsx.snap
server/sonar-web/src/main/js/apps/portfolio/components/__tests__/__snapshots__/RatingFreshness-test.tsx.snap
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 37e354aca8dd25f03ef0140f69f13b44dbd55455..a433057b301b8401fbe458f4eefb3662c3aa79d5 100644 (file)
@@ -67,6 +67,7 @@ import {
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { RawQuery } from '../../../helpers/query';
 import { scrollToElement } from '../../../helpers/scrolling';
+import EmptySearch from '../../../components/common/EmptySearch';
 import Checkbox from '../../../components/controls/Checkbox';
 
 import '../styles.css';
@@ -926,6 +927,17 @@ export default class App extends React.PureComponent<Props, State> {
       return null;
     }
 
+    let noIssuesMessage = null;
+    if (paging.total === 0) {
+      if (this.isFiltered()) {
+        noIssuesMessage = <EmptySearch />;
+      } else if (this.state.myIssues) {
+        noIssuesMessage = <NoMyIssues />;
+      } else {
+        noIssuesMessage = <NoIssues />;
+      }
+    }
+
     return (
       <div>
         {paging.total > 0 && (
@@ -949,8 +961,7 @@ export default class App extends React.PureComponent<Props, State> {
           <ListFooter count={issues.length} loadMore={this.fetchMoreIssues} total={paging.total} />
         )}
 
-        {paging.total === 0 &&
-          (this.state.myIssues && !this.isFiltered() ? <NoMyIssues /> : <NoIssues />)}
+        {noIssuesMessage}
       </div>
     );
   }
index 4a0fce0690500ef0fb579778bfd0487a95f07128..f1860ce427e04db5ba8de735052a7026d9f71977 100644 (file)
@@ -45,6 +45,7 @@ import { isSameBranchLike, getBranchLikeQuery } from '../../../helpers/branches'
 import { fetchMetrics } from '../../../store/rootActions';
 import { getMetrics } from '../../../store/rootReducer';
 import { BranchLike, Component, Metric } from '../../../app/types';
+import { translate } from '../../../helpers/l10n';
 import '../styles.css';
 
 interface OwnProps {
@@ -158,6 +159,10 @@ export class OverviewApp extends React.PureComponent<Props, State> {
       ? { index: 1 }
       : undefined;
 
+  isEmpty = () =>
+    this.state.measures === undefined ||
+    this.state.measures.find(measure => measure.metric.key === 'ncloc') === undefined;
+
   renderLoading() {
     return (
       <div className="text-center">
@@ -166,14 +171,22 @@ export class OverviewApp extends React.PureComponent<Props, State> {
     );
   }
 
-  render() {
-    const { branchLike, component } = this.props;
-    const { loading, measures, periods, history, historyStartDate } = this.state;
-
-    if (loading) {
-      return this.renderLoading();
-    }
+  renderEmpty() {
+    return (
+      <div className="overview-main page-main">
+        <h3>
+          {!this.state.measures ||
+          !this.state.measures.find(measure => measure.metric.key === 'projects')
+            ? translate('portfolio.app.empty')
+            : translate('portfolio.app.no_lines_of_code')}
+        </h3>
+      </div>
+    );
+  }
 
+  renderMain() {
+    const { branchLike, component } = this.props;
+    const { periods, measures, history, historyStartDate } = this.state;
     const leakPeriod =
       component.qualifier === 'APP' ? this.getApplicationLeakPeriod() : getLeakPeriod(periods);
     const domainProps = {
@@ -185,23 +198,40 @@ export class OverviewApp extends React.PureComponent<Props, State> {
       historyStartDate
     };
 
+    if (this.isEmpty()) {
+      return this.renderEmpty();
+    }
+
+    return (
+      <div className="overview-main page-main">
+        {component.qualifier === 'APP' ? (
+          <ApplicationQualityGate component={component} />
+        ) : (
+          <QualityGate branchLike={branchLike} component={component} measures={measures} />
+        )}
+
+        <div className="overview-domains-list">
+          <BugsAndVulnerabilities {...domainProps} />
+          <CodeSmells {...domainProps} />
+          <Coverage {...domainProps} />
+          <Duplications {...domainProps} />
+        </div>
+      </div>
+    );
+  }
+
+  render() {
+    const { branchLike, component } = this.props;
+    const { loading, measures, history } = this.state;
+
+    if (loading) {
+      return this.renderLoading();
+    }
+
     return (
       <div className="page page-limited">
         <div className="overview page-with-sidebar">
-          <div className="overview-main page-main">
-            {component.qualifier === 'APP' ? (
-              <ApplicationQualityGate component={component} />
-            ) : (
-              <QualityGate branchLike={branchLike} component={component} measures={measures} />
-            )}
-
-            <div className="overview-domains-list">
-              <BugsAndVulnerabilities {...domainProps} />
-              <CodeSmells {...domainProps} />
-              <Coverage {...domainProps} />
-              <Duplications {...domainProps} />
-            </div>
-          </div>
+          {this.renderMain()}
 
           <div className="overview-sidebar page-sidebar-fixed">
             <Meta
index 0fe760979f5d080c203736e5ae0589f071314d8a..243a5765d779768f9374438f0a24a76b2b681ee6 100644 (file)
@@ -34,21 +34,27 @@ interface Props {
 }
 
 export default class MetaSize extends React.PureComponent<Props> {
-  renderLoC = (ncloc: MeasureEnhanced) => (
+  renderLoC = (ncloc?: MeasureEnhanced) => (
     <div
-      id="overview-ncloc"
       className={classNames('overview-meta-size-ncloc', {
         'is-half-width': this.props.component.qualifier === 'APP'
-      })}>
-      <span className="spacer-right">
-        <SizeRating value={Number(ncloc.value)} />
-      </span>
-      <DrilldownLink
-        branchLike={this.props.branchLike}
-        component={this.props.component.key}
-        metric="ncloc">
-        {formatMeasure(ncloc.value, 'SHORT_INT')}
-      </DrilldownLink>
+      })}
+      id="overview-ncloc">
+      {ncloc && (
+        <span className="spacer-right">
+          <SizeRating value={Number(ncloc.value)} />
+        </span>
+      )}
+      {ncloc ? (
+        <DrilldownLink
+          branchLike={this.props.branchLike}
+          component={this.props.component.key}
+          metric="ncloc">
+          {formatMeasure(ncloc.value, 'SHORT_INT')}
+        </DrilldownLink>
+      ) : (
+        <span>0</span>
+      )}
       <div className="spacer-top text-muted">{getMetricName('ncloc')}</div>
     </div>
   );
@@ -70,24 +76,27 @@ export default class MetaSize extends React.PureComponent<Props> {
 
   renderProjects = () => {
     const projects = this.props.measures.find(measure => measure.metric.key === 'projects');
-
-    return projects ? (
-      <div id="overview-projects" className="overview-meta-size-ncloc is-half-width">
-        <DrilldownLink
-          branchLike={this.props.branchLike}
-          component={this.props.component.key}
-          metric="projects">
-          {formatMeasure(projects.value, 'SHORT_INT')}
-        </DrilldownLink>
+    return (
+      <div className="overview-meta-size-ncloc is-half-width" id="overview-projects">
+        {projects ? (
+          <DrilldownLink
+            branchLike={this.props.branchLike}
+            component={this.props.component.key}
+            metric="projects">
+            {formatMeasure(projects.value, 'SHORT_INT')}
+          </DrilldownLink>
+        ) : (
+          <span>0</span>
+        )}
         <div className="spacer-top text-muted">{translate('metric.projects.name')}</div>
       </div>
-    ) : null;
+    );
   };
 
   render() {
     const ncloc = this.props.measures.find(measure => measure.metric.key === 'ncloc');
 
-    if (ncloc == null) {
+    if (ncloc == null && this.props.component.qualifier !== 'APP') {
       return null;
     }
 
index 5b556ae3a88e86367942be1a0039ad56caf1fd58..ac492720ff53645528ed33bdb80507434f28944e 100644 (file)
   box-sizing: border-box;
 }
 
-.overview-meta-size-ncloc a {
+.overview-meta-size-ncloc a,
+.overview-meta-size-ncloc span {
   line-height: var(--controlHeight);
   font-size: 18px;
   font-weight: 300;
index ab35a37e8ae16cf48e06e01c956f5bebc93eaa20..188130d59e11a01024fd4f6f079c3458e0e1c3c2 100644 (file)
@@ -123,8 +123,11 @@ export class App extends React.PureComponent<Props, State> {
   renderEmpty() {
     return (
       <div className="empty-search">
-        <h3>{translate('portfolio.empty')}</h3>
-        <p>{translate('portfolio.empty.hint')}</p>
+        <h3>
+          {!this.state.measures || !this.state.measures['projects']
+            ? translate('portfolio.empty')
+            : translate('portfolio.no_lines_of_code')}
+        </h3>
       </div>
     );
   }
@@ -188,8 +191,7 @@ export class App extends React.PureComponent<Props, State> {
               <h4 className="portfolio-meta-header">
                 {translate('overview.about_this_portfolio')}
               </h4>
-              {!this.isEmpty() &&
-                !this.isNotComputed() && <Summary component={component} measures={measures!} />}
+              <Summary component={component} measures={measures || {}} />
             </div>
 
             <div className="portfolio-meta-card">
index bb28c4be116489886ec8aa079576288bb6066f2f..13c37926c03913adc071a4979d8d38f639d3e06a 100644 (file)
@@ -33,9 +33,11 @@ export default function RatingFreshness({ lastChange, rating }: Props) {
     return (
       <div className="portfolio-freshness">
         {rating && (
-          <>
-            {translate('portfolio.has_always_been')} <Rating small={true} value={rating} />
-          </>
+          <FormattedMessage
+            defaultMessage={translate('portfolio.has_always_been_x')}
+            id="portfolio.has_always_been_x"
+            values={{ rating: <Rating small={true} value={rating} /> }}
+          />
         )}
       </div>
     );
index b32e4cda4341d1c1423e864a02fc787ddd04deba..7d438b6167f3cb82eaccf1a3f91142e3c521509d 100644 (file)
@@ -40,17 +40,25 @@ export default function Summary({ component, measures }: Props) {
       <ul className="portfolio-grid">
         <li>
           <div className="portfolio-measure-secondary-value">
-            <Link to={getComponentDrilldownUrl(component.key, 'projects')}>
-              <Measure metricKey="projects" metricType="SHORT_INT" value={projects} />
-            </Link>
+            {projects ? (
+              <Link to={getComponentDrilldownUrl(component.key, 'projects')}>
+                <Measure metricKey="projects" metricType="SHORT_INT" value={projects} />
+              </Link>
+            ) : (
+              '0'
+            )}
           </div>
           <div className="spacer-top text-muted">{translate('projects')}</div>
         </li>
         <li>
           <div className="portfolio-measure-secondary-value">
-            <Link to={getComponentDrilldownUrl(component.key, 'ncloc')}>
-              <Measure metricKey="ncloc" metricType="SHORT_INT" value={ncloc} />
-            </Link>
+            {ncloc ? (
+              <Link to={getComponentDrilldownUrl(component.key, 'ncloc')}>
+                <Measure metricKey="ncloc" metricType="SHORT_INT" value={ncloc} />
+              </Link>
+            ) : (
+              '0'
+            )}
           </div>
           <div className="spacer-top text-muted">{translate('metric.ncloc.name')}</div>
         </li>
index 4a89b29c50988810e589083064e834b15d5fa9e8..bee7a5e17b9f6b3c31dea5a12df98dec55fbe48c 100644 (file)
@@ -127,9 +127,6 @@ exports[`renders when portfolio is empty 1`] = `
         <h3>
           portfolio.empty
         </h3>
-        <p>
-          portfolio.empty.hint
-        </p>
       </div>
     </div>
     <aside
@@ -143,6 +140,20 @@ exports[`renders when portfolio is empty 1`] = `
         >
           overview.about_this_portfolio
         </h4>
+        <Summary
+          component={
+            Object {
+              "key": "foo",
+              "name": "Foo",
+              "qualifier": "TRK",
+            }
+          }
+          measures={
+            Object {
+              "reliability_rating": "1",
+            }
+          }
+        />
       </div>
       <div
         className="portfolio-meta-card"
@@ -199,6 +210,20 @@ exports[`renders when portfolio is not computed 1`] = `
         >
           overview.about_this_portfolio
         </h4>
+        <Summary
+          component={
+            Object {
+              "key": "foo",
+              "name": "Foo",
+              "qualifier": "TRK",
+            }
+          }
+          measures={
+            Object {
+              "ncloc": "173",
+            }
+          }
+        />
       </div>
       <div
         className="portfolio-meta-card"
index 4cb8a857051fe5b9e192f707e7bd3ef1381b21c2..e7bb08f34e3bba1e058f3bca2a0817f2b7659e80 100644 (file)
@@ -32,13 +32,17 @@ exports[`renders has always been 1`] = `
 <div
   className="portfolio-freshness"
 >
-  <React.Fragment>
-    portfolio.has_always_been
-     
-    <Rating
-      small={true}
-      value="A"
-    />
-  </React.Fragment>
+  <FormattedMessage
+    defaultMessage="portfolio.has_always_been_x"
+    id="portfolio.has_always_been_x"
+    values={
+      Object {
+        "rating": <Rating
+          small={true}
+          value="A"
+        />,
+      }
+    }
+  />
 </div>
 `;
index d93850a56e63ebcbae50325036b467b1e9f4656b..4de16e024be637c3fac62f81bb885327f45815bc 100644 (file)
@@ -2678,12 +2678,14 @@ branches.see_the_pr=See the PR
 # PORTFOLIOS
 #
 #------------------------------------------------------------------------------
-portfolio.has_always_been=has always been
+portfolio.has_always_been_x=has always been {rating}
 portfolio.was_x_y=was {rating} {date}
 portfolio.x_in_y={projects} in {rating}
 portfolio.empty=This portfolio is empty.
-portfolio.empty.hint=This portfolio has no projects, or none of associated projects has lines of code.
+portfolio.no_lines_of_code=All projects in this portfolio are empty
 portfolio.not_computed=This portfolio is not yet computed.
+portfolio.app.empty=This application is empty.
+portfolio.app.no_lines_of_code=All projects in this application are empty
 
 
 #------------------------------------------------------------------------------