# Construct header
App.addInitializer ->
@codingRulesHeaderView = new CodingRulesHeaderView app: @
- @layout.headerRegion.show @codingRulesHeaderView
+ @layout.workspaceRegion.show @codingRulesHeaderView
# Define coding rules
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'
+++ /dev/null
-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()
-
'backbone'
'backbone.marionette'
'templates/component-viewer'
- 'component-viewer/header'
+ 'component-viewer/workspace'
'component-viewer/source'
], (
Backbone
Marionette
Templates
- HeaderView
+ WorkspaceView
SourceView
) ->
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
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) =>
hideCoverage: ->
- @sourceView.hideCoverage()
+ @settings.set 'coverage', false
+ @sourceView.render()
addTransition: (key, transition) ->
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
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
--- /dev/null
+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()
+
updateLayout: ->
empty = @options.app.qualityGates.length == 0
- @$(@headerRegion.el).toggle !empty
+ @$(@workspaceRegion.el).toggle !empty
@$(@detailsRegion.el).toggle !empty
qualityGateDetailHeaderView = new QualityGateDetailHeaderView
app: @app
model: qualityGate
- @app.layout.headerRegion.show qualityGateDetailHeaderView
+ @app.layout.workspaceRegion.show qualityGateDetailHeaderView
qualityGateDetailView = new QualityGateDetailView
app: @app
<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>
<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>
+++ /dev/null
-<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> Issues
- </button>
- <button data-option="coverage" {{#if settings.coverage}}class="active"{{/if}}>
- <i class="icon-coverage"></i> Coverage
- </button>
- <button data-option="duplications" {{#if settings.duplications}}class="active"{{/if}}>
- <i class="icon-duplications"></i> Duplications
- </button>
-</div>
\ No newline at end of file
-<div class="component-viewer-header"></div>
+<div class="component-viewer-workspace"></div>
<div class="component-viewer-source"></div>
\ No newline at end of file
<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
--- /dev/null
+<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
NavigatorApp.addRegions({
- headerRegion: '.navigator-header',
+ workspaceRegion: '.navigator-header',
filtersRegion: '.navigator-filters',
resultsRegion: '.navigator-results',
actionsRegion: '.navigator-actions',
app: this,
model: this.favoriteFilter
});
- this.headerRegion.show(this.issuesHeaderView);
+ this.workspaceRegion.show(this.issuesHeaderView);
this.issuesView = new Extra.IssuesView({
app: this,
@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 {
}
}
+.button-clean,
+.button-clean:hover,
+.button-clean:focus {
+ padding: 0;
+ border: none;
+ background: transparent;
+ color: @baseFontColor;
+}
+
.button-group {
display: inline-block;
vertical-align: middle;