]> source.dussan.org Git - sonarqube.git/commitdiff
Component Viewer: ui improvements
authorStas Vilchik <vilchiks@gmail.com>
Tue, 22 Apr 2014 12:08:45 +0000 (18:08 +0600)
committerStas Vilchik <vilchiks@gmail.com>
Tue, 22 Apr 2014 12:08:54 +0000 (18:08 +0600)
16 files changed:
sonar-server/src/main/coffee/coding-rules/app.coffee
sonar-server/src/main/coffee/component-viewer/coverage-popup.coffee
sonar-server/src/main/coffee/component-viewer/header.coffee [deleted file]
sonar-server/src/main/coffee/component-viewer/main.coffee
sonar-server/src/main/coffee/component-viewer/source.coffee
sonar-server/src/main/coffee/component-viewer/workspace.coffee [new file with mode: 0644]
sonar-server/src/main/coffee/quality-gate/layout.coffee
sonar-server/src/main/coffee/quality-gate/router.coffee
sonar-server/src/main/hbs/component-viewer/coveragePopup.hbs
sonar-server/src/main/hbs/component-viewer/header.hbs [deleted file]
sonar-server/src/main/hbs/component-viewer/layout.hbs
sonar-server/src/main/hbs/component-viewer/source.hbs
sonar-server/src/main/hbs/component-viewer/workspace.hbs [new file with mode: 0644]
sonar-server/src/main/js/issues/app.js
sonar-server/src/main/less/component-viewer.less
sonar-server/src/main/less/ui.less

index 9d1393c46b26c950fd8ba56df82cdcf0cc01d4da..149e9f44838a9e7e16abfb0d4ba95a07efd8f934 100644 (file)
@@ -180,7 +180,7 @@ requirejs [
   # Construct header
   App.addInitializer ->
     @codingRulesHeaderView = new CodingRulesHeaderView app: @
-    @layout.headerRegion.show @codingRulesHeaderView
+    @layout.workspaceRegion.show @codingRulesHeaderView
 
 
   # Define coding rules
index c225a07b428363c9582c5cbe859009ecd620da66..cc5ba87677cabf2b9e2d803222fafe700a2ee8c0 100644 (file)
@@ -20,9 +20,7 @@ define [
 
     onRender: ->
       @$el.detach().appendTo $('body')
-      @$el.css
-        top: @options.triggerEl.offset().top
-        left: @options.triggerEl.offset().left + @options.triggerEl.outerWidth()
+      @$el.css 'top', @options.triggerEl.offset().top
 
       $('body').on 'click.coverage-popup', =>
         $('body').off 'click.coverage-popup'
diff --git a/sonar-server/src/main/coffee/component-viewer/header.coffee b/sonar-server/src/main/coffee/component-viewer/header.coffee
deleted file mode 100644 (file)
index e64f546..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-define [
-  'backbone.marionette'
-  'templates/component-viewer'
-], (
-  Marionette
-  Templates
-) ->
-
-  $ = jQuery
-
-
-  class HeaderView extends Marionette.ItemView
-    template: Templates['header']
-
-
-    events:
-      'click .component-viewer-workspace-item [data-key]': 'goToWorkspaceItem'
-      'click [data-option=coverage]': 'toggleCoverage'
-
-
-    onRender: ->
-      @delegateEvents()
-
-
-    goToWorkspaceItem: (e) ->
-      key = $(e.currentTarget).data 'key'
-      workspace = @options.main.workspace
-      workspaceItem = workspace.findWhere key: key
-      workspaceItemIndex = workspace.indexOf workspaceItem
-      workspace.reset workspace.initial(workspace.length - workspaceItemIndex)
-      @options.main.addTransition workspaceItem.get('key'), workspaceItem.get('transition')
-
-
-    toggleCoverage: (e) ->
-      el = $(e.currentTarget)
-      active = el.is '.active'
-      el.toggleClass 'active'
-      if active then @options.main.hideCoverage() else @options.main.showCoverage()
-
-
-    serializeData: ->
-      _.extend super,
-        workspace: @options.main.workspace.toJSON()
-        settings: @options.main.settings.toJSON()
-
index c207d8c3f87c70c9491b85ebc698bb4dcc9c25b1..2471f3fa1ea3c083d0ba92cee5641d7aa26b382f 100644 (file)
@@ -2,13 +2,13 @@ define [
   'backbone'
   'backbone.marionette'
   'templates/component-viewer'
-  'component-viewer/header'
+  'component-viewer/workspace'
   'component-viewer/source'
 ], (
   Backbone
   Marionette
   Templates
-  HeaderView
+  WorkspaceView
   SourceView
 ) ->
 
@@ -24,25 +24,27 @@ define [
 
 
     regions:
-      headerRegion: '.component-viewer-header'
+      workspaceRegion: '.component-viewer-workspace'
       sourceRegion: '.component-viewer-source'
 
 
     initialize: ->
       @workspace = new Backbone.Collection()
       @component = new Backbone.Model()
-      @headerView = new HeaderView
-        model: @component
+      @workspaceView = new WorkspaceView
+        collection: @workspace
         main: @
 
       @source = new Backbone.Model()
-      @sourceView = new SourceView model: @source, main: @
+      @sourceView = new SourceView
+        model: @source
+        main: @
 
       @settings = new Backbone.Model issues: false, coverage: true, duplications: false
 
 
     onRender: ->
-      @headerRegion.show @headerView
+      @workspaceRegion.show @workspaceView
       @sourceRegion.show @sourceView
 
 
@@ -92,6 +94,7 @@ define [
 
 
     showCoverage: ->
+      @settings.set 'coverage', true
       unless @source.has 'coverage'
         metrics = 'coverage_line_hits_data,covered_conditions_by_line,conditions_by_line'
         @requestComponent(@key, metrics).done (data) =>
@@ -102,7 +105,8 @@ define [
 
 
     hideCoverage: ->
-      @sourceView.hideCoverage()
+      @settings.set 'coverage', false
+      @sourceView.render()
 
 
     addTransition: (key, transition) ->
index e4ea8c7502af2f9311b9d9adc67fc18519287a0f..27f72f9da408cb3ac7d8777121eda1963bcf542a 100644 (file)
@@ -17,42 +17,61 @@ define [
 
 
     events:
-      'click .coverage a': 'showCoveragePopup'
+      'click .settings-toggle button': 'toggleSettings'
+      'change #source-coverage': 'toggleCoverage'
+      'click .coverage-tests': 'showCoveragePopup'
 
 
     onRender: ->
       @delegateEvents()
+      @showSettings = false
 
 
     showSpinner: ->
       @$el.html '<div style="padding: 10px;"><i class="spinner"></i></div>'
 
 
-    hideCoverage: ->
-      @$('.coverage').hide()
+
+    toggleSettings: ->
+      @$('.settings-toggle button').toggleClass 'open'
+      @$('.component-viewer-source-settings').toggleClass 'open'
+
+
+    toggleCoverage: (e) ->
+      active = $(e.currentTarget).is ':checked'
+      @showSettings = true
+      if active then @options.main.showCoverage() else @options.main.hideCoverage()
 
 
     showCoveragePopup: (e) ->
       e.stopPropagation()
       $('body').click()
       popup = new CoveragePopupView
-        triggerEl: $(e.currentTarget).closest('td')
+        triggerEl: $(e.currentTarget)
         main: @options.main
       popup.render()
 
 
-    serializeData: ->
+    prepareSource: ->
       source = @model.get 'source'
       coverage = @model.get 'coverage'
       coverageConditions = @model.get 'coverageConditions'
       conditions = @model.get 'conditions'
-      source = _.map source, (code, line) ->
+      _.map source, (code, line) ->
         lineCoverage = coverage? && coverage[line]? && coverage[line]
+        lineCoverage = +lineCoverage if _.isString lineCoverage
         lineCoverageConditions = coverageConditions? && coverageConditions[line]? && coverageConditions[line]
+        lineCoverageConditions = +lineCoverageConditions if _.isString lineCoverageConditions
         lineConditions = conditions? && conditions[line]? && conditions[line]
-        lineCoverageStatus = lineCoverage? &&  if lineCoverage > 0 then 'green' else 'red'
+        lineConditions = +lineConditions if _.isString lineConditions
+
+        lineCoverageStatus = null
+        if _.isNumber lineCoverage
+          lineCoverageStatus = 'red' if lineCoverage == 0
+          lineCoverageStatus = 'green' if lineCoverage > 0
+
         lineCoverageConditionsStatus = null
-        if lineCoverageConditions? && conditions?
+        if _.isNumber(lineCoverageConditions) && _.isNumber(conditions)
           lineCoverageConditionsStatus = 'red' if lineCoverageConditions == 0
           lineCoverageConditionsStatus = 'orange' if lineCoverageConditions > 0 && lineCoverageConditions < lineConditions
           lineCoverageConditionsStatus = 'green' if lineCoverageConditions == lineConditions
@@ -63,5 +82,10 @@ define [
         coverageStatus: lineCoverageStatus
         coverageConditions: lineCoverageConditions
         conditions: lineConditions
-        coverageConditionsStatus: lineCoverageConditionsStatus
-      source: source
\ No newline at end of file
+        coverageConditionsStatus: lineCoverageConditionsStatus || lineCoverageStatus
+
+
+    serializeData: ->
+      source: @prepareSource()
+      settings: @options.main.settings.toJSON()
+      showSettings: @showSettings
\ No newline at end of file
diff --git a/sonar-server/src/main/coffee/component-viewer/workspace.coffee b/sonar-server/src/main/coffee/component-viewer/workspace.coffee
new file mode 100644 (file)
index 0000000..4eaf851
--- /dev/null
@@ -0,0 +1,37 @@
+define [
+  'backbone.marionette'
+  'templates/component-viewer'
+], (
+  Marionette
+  Templates
+) ->
+
+  $ = jQuery
+
+
+  class WorkspaceView extends Marionette.ItemView
+    template: Templates['workspace']
+
+
+    events:
+      'click .component-viewer-workspace-item [data-key]': 'goToWorkspaceItem'
+
+
+    onRender: ->
+      @delegateEvents()
+
+
+    goToWorkspaceItem: (e) ->
+      key = $(e.currentTarget).data 'key'
+      workspace = @options.main.workspace
+      workspaceItem = workspace.findWhere key: key
+      workspaceItemIndex = workspace.indexOf workspaceItem
+      workspace.reset workspace.initial(workspace.length - workspaceItemIndex)
+      @options.main.addTransition workspaceItem.get('key'), workspaceItem.get('transition')
+
+
+    serializeData: ->
+      _.extend super,
+        workspace: @options.main.workspace.toJSON()
+        settings: @options.main.settings.toJSON()
+
index 0b5941655d54a34b2fe60e3483f823bb74be5de9..0edc32c223e7e902babae578a52e99faf95a02c7 100644 (file)
@@ -25,7 +25,7 @@ define [
 
     updateLayout: ->
       empty = @options.app.qualityGates.length == 0
-      @$(@headerRegion.el).toggle !empty
+      @$(@workspaceRegion.el).toggle !empty
       @$(@detailsRegion.el).toggle !empty
 
 
index f21161a97bfc9c7a42ac6fc2efb3108dba404d41..834da1bf9ca1b9202b0edc506534be1cb302ac6e 100644 (file)
@@ -28,7 +28,7 @@ define [
         qualityGateDetailHeaderView = new QualityGateDetailHeaderView
           app: @app
           model: qualityGate
-        @app.layout.headerRegion.show qualityGateDetailHeaderView
+        @app.layout.workspaceRegion.show qualityGateDetailHeaderView
 
         qualityGateDetailView = new QualityGateDetailView
           app: @app
index 1e8d81aa27d108f87c4d726d816ba0670d61fc2e..d11d2221090eea46b8b1b38f74b58addc37520c3 100644 (file)
@@ -1,7 +1,7 @@
 <div class="component-viewer-popup-section">
   <a data-key="org.codehaus.sonar:sonar-plugin-api:src/test/java/org/sonar/api/resources/ResourceTypeTreeTest.java"
      class="component-viewer-popup-section-title">
-    src/test/java/org/sonar/api/resources/ResourceTypeTreeTest.java
+    ResourceTypeTreeTest.java
   </a>
   <ul class="component-viewer-popup-list">
     <li><i class="icon-alert-ok"></i> 216ms forbidDuplicatedType</li>
@@ -12,7 +12,7 @@
 <div class="component-viewer-popup-section">
   <a data-key="org.codehaus.sonar:sonar-plugin-api:src/test/java/org/sonar/api/resources/ResourceTypesTest.java"
      class="component-viewer-popup-section-title">
-    src/test/java/org/sonar/api/resources/ResourceTypesTest.java
+    ResourceTypesTest.java
   </a>
   <ul class="component-viewer-popup-list">
     <li><i class="icon-alert-ok"></i> 115ms fail_on_duplicated_qualifier</li>
diff --git a/sonar-server/src/main/hbs/component-viewer/header.hbs b/sonar-server/src/main/hbs/component-viewer/header.hbs
deleted file mode 100644 (file)
index 0e05fdf..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<div class="component-viewer-workspace">
-  <ul class="component-viewer-workspace-list">
-    {{#each workspace}}
-      <li class="component-viewer-workspace-item">
-        {{#if transition}}
-          <span class="component-viewer-workspace-transition">
-            <i class="icon-move-right"></i>
-          </span>
-        {{/if}}
-        <a data-key="{{key}}">{{qualifierIcon component.qualifier}} {{component.name}}</a>
-      </li>
-    {{/each}}
-  </ul>
-</div>
-
-<div class="component-viewer-options button-group">
-  <button data-option="issues" {{#if settings.issues}}class="active"{{/if}}>
-    <i class="icon-issue"></i>&nbsp;&nbsp;Issues
-  </button>
-  <button data-option="coverage" {{#if settings.coverage}}class="active"{{/if}}>
-    <i class="icon-coverage"></i>&nbsp;&nbsp;Coverage
-  </button>
-  <button data-option="duplications" {{#if settings.duplications}}class="active"{{/if}}>
-    <i class="icon-duplications"></i>&nbsp;&nbsp;Duplications
-  </button>
-</div>
\ No newline at end of file
index 78b06dad14738b3dac2ce16b8188403f3c8c0359..853611d0a3068a9d3d5389f8451d781cce8ad007 100644 (file)
@@ -1,2 +1,2 @@
-<div class="component-viewer-header"></div>
+<div class="component-viewer-workspace"></div>
 <div class="component-viewer-source"></div>
\ No newline at end of file
index 5692d126f2a9e55fcdc8bd7c0fb9af425fe604bc..151109a45b47f8ee50e090178a2828cdc239e81c 100644 (file)
@@ -1,20 +1,59 @@
 <table class="sources2 code" cellpadding="0" cellspacing="0">
-  {{#each source}}
-    <tr class="row">
-      <td class="coverage {{#if coverage}}coverage-{{coverageStatus}}{{/if}}">
-        {{#if coverage}}<a title="Line is covered by {{coverage}} tests">{{coverage}}</a>{{/if}}
-      </td>
-      <td class="coverage {{#if coverageConditions}}coverage-{{coverageConditionsStatus}}{{/if}}">
-          {{#if coverageConditions}}
-            {{#if conditions}}
-              <span title="{{coverageConditions}} branches are covered by tests">
-                {{coverageConditions}}/{{conditions}}
-              </span>
+  <thead>
+  <tr>
+    {{#if settings.coverage}}
+      <th class="stat"></th>
+      <th class="stat"></th>
+    {{/if}}
+    <th class="stat settings-toggle">
+      <button class="button-clean">
+        <i class="icon-settings"></i></i>
+      </button>
+    </th>
+    <th class="stat settings">
+      <ul class="component-viewer-source-settings {{#if showSettings}}open{{/if}}">
+        <li>
+          <input id="source-issues" type="checkbox" {{#if settings.issues}}checked{{/if}}>
+          <label for="source-issues">Issues</label>
+        </li>
+        <li>
+          <input id="source-coverage" type="checkbox" {{#if settings.coverage}}checked{{/if}}>
+          <label for="source-coverage">Coverage</label>
+        </li>
+        <li>
+          <input id="source-duplications" type="checkbox" {{#if settings.duplications}}checked{{/if}}>
+          <label for="source-duplications">Duplications</label>
+        </li>
+      </ul>
+    </th>
+  </tr>
+  </thead>
+  <tbody>
+    {{#each source}}
+      <tr class="row {{#if ../settings.coverage}}{{#if coverageStatus}}coverage-{{coverageStatus}}{{/if}}{{/if}}">
+
+        {{#if ../settings.coverage}}
+          <td class="stat coverage-tests">
+            {{#if coverage}}
+              <span title="Line is covered by {{coverage}} tests">{{coverage}}</span>
             {{/if}}
-          {{/if}}
-      </td>
-      <td class="lid">{{lineNumber}}</td>
-      <td class="line"><pre>{{{code}}}</pre></td>
-    </tr>
-  {{/each}}
+          </td>
+
+          <td class="stat coverage-conditions">
+              {{#if coverageConditions}}
+                {{#if conditions}}
+                  <span title="{{coverageConditions}} branches are covered by tests">
+                    {{coverageConditions}}/{{conditions}}
+                  </span>
+                {{/if}}
+              {{/if}}
+          </td>
+        {{/if}}
+
+        <td class="stat lid">{{lineNumber}}</td>
+
+        <td class="line"><pre>{{{code}}}</pre></td>
+      </tr>
+    {{/each}}
+  </tbody>
 </table>
\ No newline at end of file
diff --git a/sonar-server/src/main/hbs/component-viewer/workspace.hbs b/sonar-server/src/main/hbs/component-viewer/workspace.hbs
new file mode 100644 (file)
index 0000000..f25d693
--- /dev/null
@@ -0,0 +1,12 @@
+<ul class="component-viewer-workspace-list">
+  {{#each workspace}}
+    <li class="component-viewer-workspace-item">
+      {{#if transition}}
+        <span>
+            <i class="icon-move-right"></i>
+          </span>
+      {{/if}}
+      <a data-key="{{key}}">{{qualifierIcon component.qualifier}} {{component.name}}</a>
+    </li>
+  {{/each}}
+</ul>
\ No newline at end of file
index 24bcceea5ad591ff604d6b39fec0fde5bfa23457..86dc3f5794c13f000ab3bbc6caa0c3cf65ed413d 100644 (file)
@@ -53,7 +53,7 @@ requirejs(
 
 
       NavigatorApp.addRegions({
-        headerRegion: '.navigator-header',
+        workspaceRegion: '.navigator-header',
         filtersRegion: '.navigator-filters',
         resultsRegion: '.navigator-results',
         actionsRegion: '.navigator-actions',
@@ -85,7 +85,7 @@ requirejs(
           app: this,
           model: this.favoriteFilter
         });
-        this.headerRegion.show(this.issuesHeaderView);
+        this.workspaceRegion.show(this.issuesHeaderView);
 
         this.issuesView = new Extra.IssuesView({
           app: this,
index f8b175635eefaf30057c82086d119801640ba49d..03adee5ec97e20f2296f5ff6bf4431efcdcfcca5 100644 (file)
@@ -1,6 +1,8 @@
 @import "variables";
 @import "mixins";
 
+@workspaceWidth: 260px;
+
 
 .component-viewer {
   width: 100%;
 }
 
 
-.component-viewer-header {
-  position: relative;
-  height: 40px;
-  padding: 0 10px;
-  border-bottom: 1px solid @barBorderColor;
-  background-color: @barBackgroundColor;
-}
-
-
 .component-viewer-workspace {
-  height: 40px;
-  line-height: 40px;
-  font-size: 0;
+  float: left;
+  width: @workspaceWidth;
+  .box-sizing(border-box);
 }
 
 .component-viewer-workspace-item {
-  display: inline-block;
-  vertical-align: middle;
-  font-size: @baseFontSize;
+  height: 30px;
+  line-height: 30px;
+  padding: 0 10px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 
-  &:last-child { font-weight: bold; }
-}
+  &:first-child {
+    border-bottom: 1px solid @barBorderColor;
+  }
 
-.component-viewer-workspace-transition {
-  padding: 0 10px;
+  &:last-child {
+    font-weight: bold;
+  }
 }
 
+.component-viewer-workspace-transition {
 
-.component-viewer-options {
-  position: absolute;
-  top: 8px; right: 10px;
 }
 
 
 .component-viewer-source {
+  border-left: @workspaceWidth solid @barBackgroundColor;
+
+  .sources2 {
+    border-left: 1px solid @barBorderColor;
+  }
+
+  .sources2 th {
+    height: 30px;
+    .box-sizing(border-box);
+
+    &.stat {
+      padding-top: 4px;
+      padding-bottom: 4px;
+      border-left: none;
+      border-right: none;
+      border-bottom: 1px solid @barBorderColor;
+    }
+
+    &.lid {
+      border-right: 1px solid @barBorderColor;
+    }
+
+    &.settings {
+      text-align: left;
+    }
+
+    &.settings-toggle {
+      border-right: 1px solid @barBorderColor;
+      text-align: center;
+      white-space: nowrap;
+
+      .icon-settings { font-size: @iconSmallFontSize; }
+    }
+  }
 
   .sources2 td.line {
     padding: 1px 5px;
     }
   }
 
-  .sources2 td.lid {
+  .sources2 .stat {
+    min-width: 12px;
     padding: 1px 5px;
-    border-color: @barBorderColor;
+    border-left: 1px solid @barBorderColor;
+    border-right: 1px solid @barBorderColor;
     background-color: @barBackgroundColor;
     color: #888;
     font-size: 11px;
+    text-align: right;
+    cursor: default;
   }
 
-  .sources2 td.coverage {
-    padding: 1px 5px;
-    background-color: @barBackgroundColor;
-    color: #fff;
-
-    &.coverage-green { background-color: @green;}
-    &.coverage-red { background-color: @red; }
-    &.coverage-orange { background-color: @orange; }
+  .sources2 .lid {
+    min-width: 18px;
+    padding-right: 10px;
+  }
 
-    a { color: #fff; }
+  .sources2 .coverage-tests {
+    cursor: pointer;
   }
 
-  .sources2 .row:hover {
-    td.lid { background-color: darken(@barBackgroundColor, 5%); }
-    td.line { background-color: @barBackgroundColor; }
-    td.coverage { background-color: darken(@barBackgroundColor, 5%); }
-    td.coverage.coverage-green { background-color: darken(@green, 5%); }
-    td.coverage.coverage-red { background-color: darken(@red, 5%); }
-    td.coverage.coverage-orange { background-color: darken(@orange, 5%); }
+  .sources2 .row {
+
+    &.coverage-green td.stat {
+      background-color: lighten(@green, 15%);
+      border-color: lighten(@green, 7%);
+      color: @baseFontColor;
+
+      &.lid { border-right-color: lighten(@green, 15%); }
+    }
+
+    &.coverage-red td.stat {
+      background-color: lighten(@red, 15%);
+      border-color: lighten(@red, 10%);
+      color: @baseFontColor;
+
+      &.lid { border-right-color: lighten(@red, 15%); }
+    }
+
+    &:hover {
+      td.line { background-color: @barBackgroundColor; }
+
+      td.stat {
+        background-color: darken(@barBackgroundColor, 3%);
+        color: @baseFontColor;
+      }
+
+      &.coverage-green td.stat { background-color: lighten(@green, 10%); }
+      &.coverage-red td.stat { background-color: lighten(@red, 10%); }
+    }
   }
 
 }
 
 
+.component-viewer-source-settings {
+  visibility: hidden;
+
+  &.open { visibility: visible; }
+
+  & > li {
+    display: inline;
+    margin-right: 10px;
+  }
+}
+
+
 .component-viewer-popup {
   position: absolute;
   z-index: 100;
+  left: 0;
+  width: @workspaceWidth;
   padding: 10px;
-  border: 1px solid @barBorderColor;
+  border-top: 1px solid @barBorderColor;
+  border-bottom: 1px solid @barBorderColor;
+  .box-sizing(border-box);
   background-color: @white;
-  box-shadow: @defaultShadow;
 }
 
 .component-viewer-popup-section + .component-viewer-popup-section {
index 9d904c6aeaa1deebd86a055be73fe5b483760f34..b688616dc5bc75f729228388652a68ba882c7616 100644 (file)
@@ -95,6 +95,15 @@ input[type=button] {
   }
 }
 
+.button-clean,
+.button-clean:hover,
+.button-clean:focus {
+  padding: 0;
+  border: none;
+  background: transparent;
+  color: @baseFontColor;
+}
+
 .button-group {
   display: inline-block;
   vertical-align: middle;