]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8611 Add shortcuts to project activity page on project dashboard
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 7 Jul 2017 14:21:18 +0000 (16:21 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Thu, 13 Jul 2017 12:34:17 +0000 (14:34 +0200)
server/sonar-web/src/main/js/apps/overview/main/BugsAndVulnerabilities.js
server/sonar-web/src/main/js/apps/overview/main/CodeSmells.js
server/sonar-web/src/main/js/apps/overview/main/Coverage.js
server/sonar-web/src/main/js/apps/overview/main/Duplications.js
server/sonar-web/src/main/js/apps/overview/main/enhance.js
server/sonar-web/src/main/js/apps/overview/styles.css
tests/src/test/java/org/sonarqube/pageobjects/ProjectDashboardPage.java
tests/src/test/java/org/sonarqube/tests/measure/ProjectDashboardTest.java

index db8f370c60fea8e22e16505230378f8fc610bb0d..1de56542c213ee6752321407364c4cf9d4b362cb 100644 (file)
@@ -69,7 +69,7 @@ class BugsAndVulnerabilities extends React.PureComponent {
               {this.props.renderRating('new_reliability_rating')}
             </div>
             <div className="overview-domain-measure-label">
-              <span className="little-spacer-right"><BugIcon /></span>
+              <BugIcon className="little-spacer-right" />
               {getMetricName('new_bugs')}
             </div>
           </div>
@@ -82,7 +82,7 @@ class BugsAndVulnerabilities extends React.PureComponent {
               {this.props.renderRating('new_security_rating')}
             </div>
             <div className="overview-domain-measure-label">
-              <span className="little-spacer-right"><VulnerabilityIcon /></span>
+              <VulnerabilityIcon className="little-spacer-right" />
               {getMetricName('new_vulnerabilities')}
             </div>
           </div>
@@ -103,8 +103,9 @@ class BugsAndVulnerabilities extends React.PureComponent {
                 {this.props.renderRating('reliability_rating')}
               </div>
               <div className="overview-domain-measure-label">
-                <span className="little-spacer-right"><BugIcon /></span>
+                <BugIcon className="little-spacer-right " />
                 {getMetricName('bugs')}
+                {this.props.renderHistoryLink('bugs')}
               </div>
             </div>
           </div>
@@ -116,8 +117,9 @@ class BugsAndVulnerabilities extends React.PureComponent {
                 {this.props.renderRating('security_rating')}
               </div>
               <div className="overview-domain-measure-label">
-                <span className="little-spacer-right"><VulnerabilityIcon /></span>
+                <VulnerabilityIcon className="little-spacer-right " />
                 {getMetricName('vulnerabilities')}
+                {this.props.renderHistoryLink('vulnerabilities')}
               </div>
             </div>
           </div>
index 44ef34fa0a40c47a80f898776c50d02905dfc7da..6ae5012f443b9768d14d090091d8bba98babb3da 100644 (file)
@@ -99,7 +99,7 @@ class CodeSmells extends React.PureComponent {
               {this.props.renderIssues('new_code_smells', 'CODE_SMELL')}
             </div>
             <div className="overview-domain-measure-label">
-              <span className="little-spacer-right"><CodeSmellIcon /></span>
+              <CodeSmellIcon className="little-spacer-right" />
               {getMetricName('new_code_smells')}
             </div>
           </div>
@@ -123,6 +123,7 @@ class CodeSmells extends React.PureComponent {
               </div>
               <div className="overview-domain-measure-label">
                 {getMetricName('effort')}
+                {this.props.renderHistoryLink('sqale_rating')}
               </div>
             </div>
           </div>
@@ -133,8 +134,9 @@ class CodeSmells extends React.PureComponent {
                 {this.props.renderIssues('code_smells', 'CODE_SMELL')}
               </div>
               <div className="overview-domain-measure-label">
-                <span className="little-spacer-right"><CodeSmellIcon /></span>
+                <CodeSmellIcon className="little-spacer-right " />
                 {getMetricName('code_smells')}
+                {this.props.renderHistoryLink('code_smells')}
               </div>
             </div>
           </div>
index b2eeb752d25cfa84d7d1a5943d51935af67536f7..2c86b6e6970f9d07b8a8aee511dca2f0f00d0c64 100644 (file)
@@ -76,6 +76,7 @@ class Coverage extends React.PureComponent {
 
           <div className="overview-domain-measure-label">
             {getMetricName('coverage')}
+            {this.props.renderHistoryLink('coverage')}
           </div>
         </div>
       </div>
index d0989dcddf1623d41dc7c46cfef889404fdb13b7..c6e9a8cd18f8b02a19d7dd9b48b8b7dd7d63664c 100644 (file)
@@ -58,6 +58,7 @@ class Duplications extends React.PureComponent {
 
           <div className="overview-domain-measure-label">
             {getMetricName('duplications')}
+            {this.props.renderHistoryLink('duplicated_lines_density')}
           </div>
         </div>
       </div>
index f6d450c661aea4fcd166adb14d2c4ec9780f38f5..2bd0e72530bdcd34eb5c4666f2e20a049862100e 100644 (file)
@@ -21,6 +21,7 @@ import React from 'react';
 import { Link } from 'react-router';
 import moment from 'moment';
 import { DrilldownLink } from '../../../components/shared/drilldown-link';
+import HistoryIcon from '../../../components/icons-components/HistoryIcon';
 import Rating from './../../../components/ui/Rating';
 import Timeline from '../components/Timeline';
 import {
@@ -33,13 +34,13 @@ import {
 } from '../../../helpers/measures';
 import { translateWithParameters } from '../../../helpers/l10n';
 import { getPeriodDate } from '../../../helpers/periods';
-import { getComponentIssuesUrl } from '../../../helpers/urls';
+import { getComponentIssuesUrl, getComponentMeasureHistory } from '../../../helpers/urls';
 
 export default function enhance(ComposedComponent) {
   return class extends React.PureComponent {
     static displayName = `enhance(${ComposedComponent.displayName})}`;
 
-    getValue(measure) {
+    getValue = measure => {
       const { leakPeriod } = this.props;
 
       if (!measure) {
@@ -49,9 +50,9 @@ export default function enhance(ComposedComponent) {
       return isDiffMetric(measure.metric.key)
         ? getPeriodValue(measure, leakPeriod.index)
         : measure.value;
-    }
+    };
 
-    renderHeader(domain, label) {
+    renderHeader = (domain, label) => {
       const { component } = this.props;
       const domainUrl = {
         pathname: `/component_measures/domain/${domain}`,
@@ -65,9 +66,9 @@ export default function enhance(ComposedComponent) {
           </div>
         </div>
       );
-    }
+    };
 
-    renderMeasure(metricKey) {
+    renderMeasure = metricKey => {
       const { measures, component } = this.props;
       const measure = measures.find(measure => measure.metric.key === metricKey);
 
@@ -87,12 +88,13 @@ export default function enhance(ComposedComponent) {
 
           <div className="overview-domain-measure-label">
             {measure.metric.name}
+            {this.renderHistoryLink(measure.metric.key)}
           </div>
         </div>
       );
-    }
+    };
 
-    renderMeasureVariation(metricKey, customLabel) {
+    renderMeasureVariation = (metricKey, customLabel) => {
       const NO_VALUE = '—';
       const { measures, leakPeriod } = this.props;
       const measure = measures.find(measure => measure.metric.key === metricKey);
@@ -111,8 +113,8 @@ export default function enhance(ComposedComponent) {
           </div>
         </div>
       );
-    }
-    renderRating(metricKey) {
+    };
+    renderRating = metricKey => {
       const { component, measures } = this.props;
       const measure = measures.find(measure => measure.metric.key === metricKey);
       if (!measure) {
@@ -127,8 +129,8 @@ export default function enhance(ComposedComponent) {
           </DrilldownLink>
         </div>
       );
-    }
-    renderIssues(metric, type) {
+    };
+    renderIssues = (metric, type) => {
       const { measures, component } = this.props;
       const measure = measures.find(measure => measure.metric.key === metric);
       const value = this.getValue(measure);
@@ -148,8 +150,19 @@ export default function enhance(ComposedComponent) {
           </span>
         </Link>
       );
-    }
-    renderTimeline(metricKey, range, children) {
+    };
+    renderHistoryLink = metricKey => {
+      const linkClass =
+        'button button-small button-compact spacer-left overview-domain-measure-history-link';
+      return (
+        <Link
+          className={linkClass}
+          to={getComponentMeasureHistory(this.props.component.key, metricKey)}>
+          <HistoryIcon />
+        </Link>
+      );
+    };
+    renderTimeline = (metricKey, range, children) => {
       if (!this.props.history) {
         return null;
       }
@@ -167,18 +180,19 @@ export default function enhance(ComposedComponent) {
           {children}
         </div>
       );
-    }
+    };
     render() {
       return (
         <ComposedComponent
           {...this.props}
-          getValue={this.getValue.bind(this)}
-          renderHeader={this.renderHeader.bind(this)}
-          renderMeasure={this.renderMeasure.bind(this)}
-          renderMeasureVariation={this.renderMeasureVariation.bind(this)}
-          renderRating={this.renderRating.bind(this)}
-          renderIssues={this.renderIssues.bind(this)}
-          renderTimeline={this.renderTimeline.bind(this)}
+          getValue={this.getValue}
+          renderHeader={this.renderHeader}
+          renderHistoryLink={this.renderHistoryLink}
+          renderMeasure={this.renderMeasure}
+          renderMeasureVariation={this.renderMeasureVariation}
+          renderRating={this.renderRating}
+          renderIssues={this.renderIssues}
+          renderTimeline={this.renderTimeline}
         />
       );
     }
index 45911113921c71a4b83a1c76b93b73e90ef5c31f..5de0ceca3207de83b197c908bb49a1f147bc5fbd 100644 (file)
   margin-top: 10px;
 }
 
+.overview-domain-measure-label > svg {
+  margin-top: 3px;
+}
+
 .overview-domain-leak .overview-domain-measure-label {
   text-align: center;
 }
 
+.overview-domain-leak .overview-domain-measure-label > svg {
+  margin-top: 0;
+}
+
+.overview-domain-measure-history-link {
+  vertical-align: bottom;
+  visibility: hidden;
+}
+
+.overview-domain-measure:hover .overview-domain-measure-history-link {
+  visibility: visible;
+}
+
 .overview-domain-measure-sup {
   display: inline-block;
   vertical-align: top;
index f576ea4cc329d05af73f38cf19257cb7b1f72bfd..60969c43fa5de913a9c7a9080230295604f4f3a6 100644 (file)
  */
 package org.sonarqube.pageobjects;
 
+import com.codeborne.selenide.ElementsCollection;
 import com.codeborne.selenide.SelenideElement;
 import java.util.Arrays;
 
 import static com.codeborne.selenide.Condition.exist;
 import static com.codeborne.selenide.Condition.hasText;
+import static com.codeborne.selenide.Condition.text;
 import static com.codeborne.selenide.Condition.visible;
 import static com.codeborne.selenide.Selenide.$;
+import static com.codeborne.selenide.Selenide.$$;
 
 public class ProjectDashboardPage {
 
@@ -45,6 +48,12 @@ public class ProjectDashboardPage {
     return element;
   }
 
+  public SelenideElement getOverviewMeasure(String measure) {
+    ElementsCollection measures = $$(".overview-domain-measure");
+    SelenideElement element = measures.find(text(measure)).shouldBe(visible);
+    return element;
+  }
+
   private SelenideElement getTagsMeta() {
     SelenideElement element = $(".overview-meta-tags");
     element.shouldBe(visible);
index 584938877da8d08f10dd0d07bfea981c4c6e6157..956c8e7f109dcd43a5ae45f36d9959881fc0b6f0 100644 (file)
@@ -34,8 +34,10 @@ import org.sonarqube.pageobjects.Navigation;
 import org.sonarqube.pageobjects.ProjectDashboardPage;
 import util.user.UserRule;
 
+import static com.codeborne.selenide.Condition.exist;
 import static com.codeborne.selenide.Condition.hasText;
 import static com.codeborne.selenide.Condition.text;
+import static com.codeborne.selenide.Condition.visible;
 import static util.ItUtils.newAdminWsClient;
 import static util.ItUtils.projectDir;
 import static util.selenium.Selenese.runSelenese;
@@ -121,6 +123,20 @@ public class ProjectDashboardTest {
       .shouldHaveTags("test");
   }
 
+  @Test
+  public void display_project_activity_shortcut() {
+    executeBuild("shared/xoo-sample", "sample-with-tags", "Sample with tags");
+    // Add some tags to another project to have them in the list
+    wsClient.wsConnector().call(
+      new PostRequest("api/project_tags/set")
+        .setParam("project", "sample-with-tags")
+        .setParam("tags", "foo,bar,baz"));
+
+    executeBuild("shared/xoo-sample", "sample", "Sample");
+    ProjectDashboardPage page = nav.logIn().submitCredentials(adminUser).openProjectDashboard("sample");
+    page.getOverviewMeasure("Debt").$(".overview-domain-measure-history-link").should(exist);
+  }
+
   @Test
   @Ignore("there is no more place to show the error")
   public void display_a_nice_error_when_requesting_unknown_project() {